opentelemetry-overmind 0.53.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opentelemetry_overmind-0.53.0/PKG-INFO +16 -0
- opentelemetry_overmind-0.53.0/README.md +0 -0
- opentelemetry_overmind-0.53.0/opentelemetry/overmind/__init__.py +0 -0
- opentelemetry_overmind-0.53.0/opentelemetry/overmind/attrs.py +35 -0
- opentelemetry_overmind-0.53.0/opentelemetry/overmind/processor.py +106 -0
- opentelemetry_overmind-0.53.0/opentelemetry/overmind/prompt.py +53 -0
- opentelemetry_overmind-0.53.0/pyproject.toml +15 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opentelemetry-overmind
|
|
3
|
+
Version: 0.53.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: pritamsoni-hsr
|
|
6
|
+
Author-email: 23050213+pritamsoni-hsr@users.noreply.github.com
|
|
7
|
+
Requires-Python: >=3.10,<4
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class OvermindAttributes:
|
|
2
|
+
# Prompt attributes
|
|
3
|
+
GEN_AI_PROMPT_ID = "overmind.prompt.id"
|
|
4
|
+
GEN_AI_PROMPT_HASH = "overmind.prompt.hash"
|
|
5
|
+
GEN_AI_PROMPT_VERSION = "overmind.prompt.version"
|
|
6
|
+
GEN_AI_PROMPT_KWARGS = "overmind.prompt.kwargs"
|
|
7
|
+
GEN_AI_PROMPT_TEMPLATE = "overmind.prompt.template"
|
|
8
|
+
GEN_AI_REFERENCE_OUTPUT = "overmind.reference.output"
|
|
9
|
+
GEN_AI_REASONING_EFFORT = "overmind.reasoning.effort"
|
|
10
|
+
|
|
11
|
+
# Request attributes
|
|
12
|
+
GEN_AI_REQUEST_REASONING_EFFORT = "gen_ai.request.reasoning_effort"
|
|
13
|
+
GEN_AI_REQUEST_MAX_COMPLETION_TOKENS = "gen_ai.request.max_completion_tokens"
|
|
14
|
+
GEN_AI_REQUEST_SEED = "gen_ai.request.seed"
|
|
15
|
+
GEN_AI_REQUEST_STOP_SEQUENCES = "gen_ai.request.stop_sequences"
|
|
16
|
+
GEN_AI_REQUEST_N = "gen_ai.request.n"
|
|
17
|
+
GEN_AI_REQUEST_RESPONSE_FORMAT = "gen_ai.request.response_format"
|
|
18
|
+
GEN_AI_REQUEST_SERVICE_TIER = "gen_ai.request.service_tier"
|
|
19
|
+
GEN_AI_REQUEST_PARALLEL_TOOL_CALLS = "gen_ai.request.parallel_tool_calls"
|
|
20
|
+
GEN_AI_REQUEST_TOOL_CHOICE = "gen_ai.request.tool_choice"
|
|
21
|
+
GEN_AI_REQUEST_LOGPROBS = "gen_ai.request.logprobs"
|
|
22
|
+
GEN_AI_REQUEST_TOP_LOGPROBS = "gen_ai.request.top_logprobs"
|
|
23
|
+
GEN_AI_REQUEST_MODALITIES = "gen_ai.request.modalities"
|
|
24
|
+
|
|
25
|
+
# Response attributes
|
|
26
|
+
GEN_AI_RESPONSE_ID = "gen_ai.response.id"
|
|
27
|
+
GEN_AI_RESPONSE_SYSTEM_FINGERPRINT = "gen_ai.response.system_fingerprint"
|
|
28
|
+
GEN_AI_RESPONSE_SERVICE_TIER = "gen_ai.response.service_tier"
|
|
29
|
+
|
|
30
|
+
# Usage detail attributes
|
|
31
|
+
GEN_AI_USAGE_REASONING_TOKENS = "gen_ai.usage.reasoning_tokens"
|
|
32
|
+
GEN_AI_USAGE_CACHED_TOKENS = "gen_ai.usage.cached_tokens"
|
|
33
|
+
GEN_AI_USAGE_AUDIO_TOKENS = "gen_ai.usage.audio_tokens"
|
|
34
|
+
GEN_AI_USAGE_ACCEPTED_PREDICTION_TOKENS = "gen_ai.usage.accepted_prediction_tokens"
|
|
35
|
+
GEN_AI_USAGE_REJECTED_PREDICTION_TOKENS = "gen_ai.usage.rejected_prediction_tokens"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Mapping, Sequence
|
|
3
|
+
|
|
4
|
+
from opentelemetry.trace import Span
|
|
5
|
+
from opentelemetry.overmind.prompt import PromptString
|
|
6
|
+
from opentelemetry.overmind.attrs import OvermindAttributes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _store_prompt_attributes(span: Span, content: PromptString | str) -> None:
|
|
10
|
+
if isinstance(content, PromptString):
|
|
11
|
+
span.set_attribute(OvermindAttributes.GEN_AI_PROMPT_ID, content.id)
|
|
12
|
+
span.set_attribute(OvermindAttributes.GEN_AI_PROMPT_HASH, content.hash)
|
|
13
|
+
span.set_attribute(OvermindAttributes.GEN_AI_PROMPT_KWARGS, json.dumps(content.kwargs))
|
|
14
|
+
span.set_attribute(OvermindAttributes.GEN_AI_PROMPT_TEMPLATE, content.template)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
MAX_PROMPT_COUNT = 1
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _walk_and_store_prompts(span: Span, value, counter: list[int]) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Recursively walk an arbitrary value (PromptString, str, list, dict, etc.)
|
|
23
|
+
and store prompt attributes for any PromptString instances found.
|
|
24
|
+
"""
|
|
25
|
+
if isinstance(value, PromptString):
|
|
26
|
+
_store_prompt_attributes(span, value)
|
|
27
|
+
counter[0] += 1
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
# Plain strings are not traced unless wrapped in PromptString
|
|
31
|
+
if isinstance(value, str):
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
if isinstance(value, Mapping):
|
|
35
|
+
for v in value.values():
|
|
36
|
+
_walk_and_store_prompts(span, v, counter)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
|
40
|
+
for item in value:
|
|
41
|
+
_walk_and_store_prompts(span, item, counter)
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def clean_kwargs(kwargs: dict) -> dict:
|
|
46
|
+
"""remove additional keys from kwargs, so that the default methods can be used, but returns a copy of the original args so tracing can work"""
|
|
47
|
+
|
|
48
|
+
reference_output = kwargs.pop("reference_output", None)
|
|
49
|
+
return {"reference_output": reference_output, **kwargs}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def request_processor(span: Span, kwargs: dict, type: str = "openai.chat") -> None:
|
|
53
|
+
"""
|
|
54
|
+
Normalize prompt capture across providers.
|
|
55
|
+
|
|
56
|
+
Supported types:
|
|
57
|
+
- openai.chat
|
|
58
|
+
- openai.responses
|
|
59
|
+
- anthropic.messages
|
|
60
|
+
- google.genai.responses
|
|
61
|
+
- agno.agent.run
|
|
62
|
+
"""
|
|
63
|
+
if reference_output := kwargs.pop("reference_output", None):
|
|
64
|
+
span.set_attribute(OvermindAttributes.GEN_AI_REFERENCE_OUTPUT, reference_output)
|
|
65
|
+
|
|
66
|
+
prompt_count = [0]
|
|
67
|
+
|
|
68
|
+
if type == "openai.chat":
|
|
69
|
+
# OpenAI chat: classic messages and newer content blocks
|
|
70
|
+
for message in kwargs.get("messages", []):
|
|
71
|
+
content = message.get("content")
|
|
72
|
+
_walk_and_store_prompts(span, content, prompt_count)
|
|
73
|
+
|
|
74
|
+
if prompt_count[0] > MAX_PROMPT_COUNT:
|
|
75
|
+
raise ValueError("Too many prompts")
|
|
76
|
+
|
|
77
|
+
elif type == "openai.response":
|
|
78
|
+
# OpenAI Responses API: instructions + input (and potentially messages/system)
|
|
79
|
+
if "instructions" in kwargs:
|
|
80
|
+
_walk_and_store_prompts(span, kwargs["instructions"], prompt_count)
|
|
81
|
+
if "input" in kwargs:
|
|
82
|
+
_walk_and_store_prompts(span, kwargs["input"], prompt_count)
|
|
83
|
+
|
|
84
|
+
elif type == "openai.embeddings":
|
|
85
|
+
if "input" in kwargs:
|
|
86
|
+
_store_prompt_attributes(span, kwargs["input"])
|
|
87
|
+
|
|
88
|
+
elif type == "anthropic.messages":
|
|
89
|
+
# Anthropic messages API (including beta and Bedrock)
|
|
90
|
+
if "system" in kwargs:
|
|
91
|
+
_walk_and_store_prompts(span, kwargs["system"], prompt_count)
|
|
92
|
+
for message in kwargs.get("messages", []):
|
|
93
|
+
_walk_and_store_prompts(span, message.get("content"), prompt_count)
|
|
94
|
+
|
|
95
|
+
elif type == "google.genai.responses":
|
|
96
|
+
# Google Generative AI / Gemini generate_content
|
|
97
|
+
if "system_instruction" in kwargs:
|
|
98
|
+
_walk_and_store_prompts(span, kwargs["system_instruction"], prompt_count)
|
|
99
|
+
if "contents" in kwargs:
|
|
100
|
+
_walk_and_store_prompts(span, kwargs["contents"], prompt_count)
|
|
101
|
+
|
|
102
|
+
elif type == "agno.agent.run":
|
|
103
|
+
# Agno Agent / Team run entrypoints
|
|
104
|
+
for key in ("prompt", "system_prompt", "messages", "input"):
|
|
105
|
+
if key in kwargs:
|
|
106
|
+
_walk_and_store_prompts(span, kwargs[key], prompt_count)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PromptString(str):
|
|
5
|
+
id: str
|
|
6
|
+
template: str
|
|
7
|
+
kwargs: dict
|
|
8
|
+
hash: str
|
|
9
|
+
traceable: bool
|
|
10
|
+
|
|
11
|
+
def __new__(
|
|
12
|
+
cls,
|
|
13
|
+
*args,
|
|
14
|
+
id: str | None = None,
|
|
15
|
+
template: str | None = None,
|
|
16
|
+
kwargs: dict | None = None,
|
|
17
|
+
**_extra,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Supports two construction patterns:
|
|
21
|
+
1) Our explicit usage:
|
|
22
|
+
PromptString(id=\"...\", template=\"...\", kwargs={...})
|
|
23
|
+
2) Library/internal usage (e.g. Agno) that may call:
|
|
24
|
+
PromptString(\"prompt text\", ...)
|
|
25
|
+
"""
|
|
26
|
+
if id is not None and template is not None:
|
|
27
|
+
# Explicit, structured construction with template formatting.
|
|
28
|
+
kwargs = kwargs or {}
|
|
29
|
+
prompt = template.format(**kwargs)
|
|
30
|
+
traceable = True
|
|
31
|
+
elif args:
|
|
32
|
+
# Fallback: treat first positional arg as the already-formatted prompt.
|
|
33
|
+
base_prompt = str(args[0])
|
|
34
|
+
prompt = base_prompt
|
|
35
|
+
id = id or "prompt"
|
|
36
|
+
template = template or base_prompt
|
|
37
|
+
kwargs = kwargs or {}
|
|
38
|
+
traceable = False
|
|
39
|
+
else:
|
|
40
|
+
# Extremely defensive fallback – empty prompt.
|
|
41
|
+
prompt = ""
|
|
42
|
+
id = id or "prompt"
|
|
43
|
+
template = template or ""
|
|
44
|
+
kwargs = kwargs or {}
|
|
45
|
+
traceable = False
|
|
46
|
+
|
|
47
|
+
obj = super().__new__(cls, prompt)
|
|
48
|
+
obj.id = id
|
|
49
|
+
obj.template = template
|
|
50
|
+
obj.kwargs = kwargs
|
|
51
|
+
obj.hash = hashlib.md5(template.encode()).hexdigest() if template else ""
|
|
52
|
+
obj.traceable = traceable
|
|
53
|
+
return obj
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
authors = [
|
|
3
|
+
{ name = "pritamsoni-hsr", email = "23050213+pritamsoni-hsr@users.noreply.github.com" },
|
|
4
|
+
]
|
|
5
|
+
name = "opentelemetry-overmind"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.10,<4"
|
|
8
|
+
version = "0.53.0"
|
|
9
|
+
|
|
10
|
+
[tool.poetry]
|
|
11
|
+
packages = [{ include = "opentelemetry/overmind" }]
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
build-backend = "poetry.core.masonry.api"
|
|
15
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|