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.
@@ -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
@@ -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"]