quraite 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

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.
Files changed (49) hide show
  1. quraite/__init__.py +3 -3
  2. quraite/adapters/__init__.py +134 -134
  3. quraite/adapters/agno_adapter.py +159 -159
  4. quraite/adapters/base.py +123 -123
  5. quraite/adapters/bedrock_agents_adapter.py +343 -343
  6. quraite/adapters/flowise_adapter.py +275 -275
  7. quraite/adapters/google_adk_adapter.py +209 -209
  8. quraite/adapters/http_adapter.py +251 -239
  9. quraite/adapters/langflow_adapter.py +192 -192
  10. quraite/adapters/langgraph_adapter.py +304 -304
  11. quraite/adapters/langgraph_server_adapter.py +252 -252
  12. quraite/adapters/n8n_adapter.py +220 -220
  13. quraite/adapters/openai_agents_adapter.py +269 -269
  14. quraite/adapters/pydantic_ai_adapter.py +312 -312
  15. quraite/adapters/smolagents_adapter.py +152 -152
  16. quraite/logger.py +61 -61
  17. quraite/schema/message.py +91 -91
  18. quraite/schema/response.py +16 -16
  19. quraite/serve/__init__.py +1 -1
  20. quraite/serve/cloudflared.py +210 -210
  21. quraite/serve/local_agent.py +360 -360
  22. quraite/traces/traces_adk_openinference.json +379 -0
  23. quraite/traces/traces_agno_multi_agent.json +669 -0
  24. quraite/traces/traces_agno_openinference.json +321 -0
  25. quraite/traces/traces_crewai_openinference.json +155 -0
  26. quraite/traces/traces_langgraph_openinference.json +349 -0
  27. quraite/traces/traces_langgraph_openinference_multi_agent.json +2705 -0
  28. quraite/traces/traces_langgraph_traceloop.json +510 -0
  29. quraite/traces/traces_openai_agents_multi_agent_1.json +402 -0
  30. quraite/traces/traces_openai_agents_openinference.json +341 -0
  31. quraite/traces/traces_pydantic_openinference.json +286 -0
  32. quraite/traces/traces_pydantic_openinference_multi_agent_1.json +399 -0
  33. quraite/traces/traces_pydantic_openinference_multi_agent_2.json +398 -0
  34. quraite/traces/traces_smol_agents_openinference.json +397 -0
  35. quraite/traces/traces_smol_agents_tool_calling_openinference.json +704 -0
  36. quraite/tracing/__init__.py +24 -24
  37. quraite/tracing/constants.py +16 -16
  38. quraite/tracing/span_exporter.py +115 -115
  39. quraite/tracing/span_processor.py +49 -49
  40. quraite/tracing/tool_extractors.py +290 -290
  41. quraite/tracing/trace.py +564 -564
  42. quraite/tracing/types.py +179 -179
  43. quraite/tracing/utils.py +170 -170
  44. quraite/utils/json_utils.py +269 -269
  45. quraite-0.1.1.dist-info/METADATA +377 -0
  46. quraite-0.1.1.dist-info/RECORD +49 -0
  47. {quraite-0.1.0.dist-info → quraite-0.1.1.dist-info}/WHEEL +1 -1
  48. quraite-0.1.0.dist-info/METADATA +0 -44
  49. quraite-0.1.0.dist-info/RECORD +0 -35
@@ -1,24 +1,24 @@
1
- """Tracing infrastructure for OpenTelemetry span collection and processing."""
2
-
3
- from quraite.tracing.span_exporter import QuraiteInMemorySpanExporter
4
- from quraite.tracing.span_processor import QuraiteSimpleSpanProcessor
5
- from quraite.tracing.tool_extractors import Framework, ToolCallInfo, get_tool_extractor
6
- from quraite.tracing.trace import AgentSpan, AgentTrace, CostInfo, TokenInfo
7
- from quraite.tracing.types import Event, Link, Resource, SpanContext, Status
8
-
9
- __all__ = [
10
- "AgentSpan",
11
- "AgentTrace",
12
- "CostInfo",
13
- "TokenInfo",
14
- "Framework",
15
- "ToolCallInfo",
16
- "get_tool_extractor",
17
- "QuraiteInMemorySpanExporter",
18
- "QuraiteSimpleSpanProcessor",
19
- "Event",
20
- "Link",
21
- "Resource",
22
- "SpanContext",
23
- "Status",
24
- ]
1
+ """Tracing infrastructure for OpenTelemetry span collection and processing."""
2
+
3
+ from quraite.tracing.span_exporter import QuraiteInMemorySpanExporter
4
+ from quraite.tracing.span_processor import QuraiteSimpleSpanProcessor
5
+ from quraite.tracing.tool_extractors import Framework, ToolCallInfo, get_tool_extractor
6
+ from quraite.tracing.trace import AgentSpan, AgentTrace, CostInfo, TokenInfo
7
+ from quraite.tracing.types import Event, Link, Resource, SpanContext, Status
8
+
9
+ __all__ = [
10
+ "AgentSpan",
11
+ "AgentTrace",
12
+ "CostInfo",
13
+ "TokenInfo",
14
+ "Framework",
15
+ "ToolCallInfo",
16
+ "get_tool_extractor",
17
+ "QuraiteInMemorySpanExporter",
18
+ "QuraiteSimpleSpanProcessor",
19
+ "Event",
20
+ "Link",
21
+ "Resource",
22
+ "SpanContext",
23
+ "Status",
24
+ ]
@@ -1,16 +1,16 @@
1
- from enum import Enum
2
-
3
- QURAITE_ADAPTER_TRACE_PREFIX = "quraite-adapter"
4
-
5
- QURAITE_TRACER_NAME = "quraite.instrumentation"
6
-
7
-
8
- class Framework(str, Enum):
9
- """Supported agent frameworks."""
10
-
11
- PYDANTIC = "pydantic"
12
- LANGGRAPH = "langgraph"
13
- GOOGLE_ADK = "google_adk"
14
- OPENAI_AGENTS = "openai_agents"
15
- AGNO = "agno"
16
- SMOLAGENTS = "smolagents"
1
+ from enum import Enum
2
+
3
+ QURAITE_ADAPTER_TRACE_PREFIX = "quraite-adapter"
4
+
5
+ QURAITE_TRACER_NAME = "quraite.instrumentation"
6
+
7
+
8
+ class Framework(str, Enum):
9
+ """Supported agent frameworks."""
10
+
11
+ PYDANTIC = "pydantic"
12
+ LANGGRAPH = "langgraph"
13
+ GOOGLE_ADK = "google_adk"
14
+ OPENAI_AGENTS = "openai_agents"
15
+ AGNO = "agno"
16
+ SMOLAGENTS = "smolagents"
@@ -1,115 +1,115 @@
1
- import json
2
- import os
3
- import threading
4
- import typing
5
- from collections import defaultdict
6
-
7
- from opentelemetry.sdk.trace import ReadableSpan
8
- from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
9
-
10
-
11
- class QuraiteInMemorySpanExporter(SpanExporter):
12
- def __init__(self) -> None:
13
- # self.spans: typing.List[ReadableSpan] = []
14
- self.traces: typing.Dict[int, typing.List[ReadableSpan]] = defaultdict(list)
15
- self.testcase_to_trace: typing.Dict[str, int] = {}
16
-
17
- self._stopped = False
18
- self._lock = threading.Lock()
19
-
20
- def handle_testcase_trace(self, span: ReadableSpan) -> None:
21
- """Handle a testcase trace."""
22
- # print(f"🟢 testcase trace received: {span.name}")
23
- # print(f"🟢 Span: {span.context}")
24
- formatted_trace_id = format(span.context.trace_id, "032x")[:8]
25
- # print(f"🟢 testcase formatted trace id: {formatted_trace_id}")
26
- self.traces[formatted_trace_id] = []
27
- self.testcase_to_trace[span.name] = formatted_trace_id
28
-
29
- def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
30
- """Stores a list of spans in memory."""
31
- if self._stopped:
32
- return SpanExportResult.FAILURE
33
-
34
- with self._lock:
35
- for span in spans:
36
- formatted_trace_id = format(span.context.trace_id, "032x")[:8]
37
- # print(f"🟢 span formatted context trace id: {formatted_trace_id}")
38
- self.traces[formatted_trace_id].append(span)
39
- # self.spans.append(span)
40
-
41
- return SpanExportResult.SUCCESS
42
-
43
- def shutdown(self) -> None:
44
- """Shut downs the exporter.
45
-
46
- Calls to export after the exporter has been shut down will fail.
47
- """
48
- print("Shutting down exporter")
49
- self._stopped = True
50
-
51
- def force_flush(self, timeout_millis: int = 30000) -> bool:
52
- return True
53
-
54
- def get_traces(self) -> typing.Dict[int, typing.List[ReadableSpan]]:
55
- """Get all spans grouped by trace ID"""
56
- return dict(self.traces)
57
-
58
- def get_trace_by_testcase(self, testcase_name: str) -> typing.List[ReadableSpan]:
59
- """Get all spans for a specific testcase"""
60
- with self._lock:
61
- return self.traces.get(self.testcase_to_trace.get(testcase_name, None), [])
62
-
63
- def get_trace(self, trace_id: int) -> typing.List[ReadableSpan]:
64
- """Get all spans for a specific trace"""
65
- return self.traces.get(trace_id, [])
66
-
67
- def get_trace_count(self) -> int:
68
- """Get the number of unique traces"""
69
- return len(self.traces)
70
-
71
- def print_trace_summary(self):
72
- """Print a summary of all traces"""
73
- print(f"\n{'='*60}")
74
- print(f"Total Traces: {self.get_trace_count()}")
75
- for trace_id, spans in self.traces.items():
76
- print(f"Trace ID: {trace_id}...")
77
- print(f"Spans in trace: {len(spans)}")
78
- print(f"Total Testcases: {self.testcase_to_trace}")
79
- print(f"TraceIDs: {self.traces.keys()}")
80
- print(f"{'='*60}\n")
81
-
82
- # for trace_id, spans in self.traces.items():
83
- # trace_id_hex = format(trace_id, '032x')
84
- # print(f"📊 Trace ID: {trace_id_hex}")
85
- # print(f" Spans in trace: {len(spans)}")
86
-
87
- # # Sort spans by start time to show execution order
88
- # sorted_spans = sorted(spans, key=lambda s: s.start_time)
89
-
90
- # for span in sorted_spans:
91
- # duration_ms = (span.end_time - span.start_time) / 1e6
92
- # parent_id = format(span.parent.span_id, '016x') if span.parent else "None"
93
- # indent = " " if span.parent else ""
94
- # print(f" {indent}├─ {span.name}")
95
- # print(f" {indent} ├─ Span ID: {format(span.context.span_id, '016x')}")
96
- # print(f" {indent} ├─ Parent ID: {parent_id}")
97
- # print(f" {indent} ├─ Duration: {duration_ms:.2f}ms")
98
- # if span.attributes:
99
- # print(f" {indent} └─ Attributes: {dict(span.attributes)}")
100
- # print()
101
-
102
- def save_traces_to_file(self, filename: typing.Optional[str] = "traces.json"):
103
- """Save a trace to a file"""
104
- traces = []
105
- for trace_id, spans in self.traces.items():
106
- traces.append(
107
- {
108
- "trace_id": trace_id,
109
- "spans": [json.loads(span.to_json()) for span in spans],
110
- }
111
- )
112
-
113
- os.makedirs(os.path.dirname(f"traces/{filename}"), exist_ok=True)
114
- with open(f"traces/{filename}", "w") as f:
115
- json.dump(traces, f, indent=2)
1
+ import json
2
+ import os
3
+ import threading
4
+ import typing
5
+ from collections import defaultdict
6
+
7
+ from opentelemetry.sdk.trace import ReadableSpan
8
+ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
9
+
10
+
11
+ class QuraiteInMemorySpanExporter(SpanExporter):
12
+ def __init__(self) -> None:
13
+ # self.spans: typing.List[ReadableSpan] = []
14
+ self.traces: typing.Dict[int, typing.List[ReadableSpan]] = defaultdict(list)
15
+ self.testcase_to_trace: typing.Dict[str, int] = {}
16
+
17
+ self._stopped = False
18
+ self._lock = threading.Lock()
19
+
20
+ def handle_testcase_trace(self, span: ReadableSpan) -> None:
21
+ """Handle a testcase trace."""
22
+ # print(f"🟢 testcase trace received: {span.name}")
23
+ # print(f"🟢 Span: {span.context}")
24
+ formatted_trace_id = format(span.context.trace_id, "032x")[:8]
25
+ # print(f"🟢 testcase formatted trace id: {formatted_trace_id}")
26
+ self.traces[formatted_trace_id] = []
27
+ self.testcase_to_trace[span.name] = formatted_trace_id
28
+
29
+ def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
30
+ """Stores a list of spans in memory."""
31
+ if self._stopped:
32
+ return SpanExportResult.FAILURE
33
+
34
+ with self._lock:
35
+ for span in spans:
36
+ formatted_trace_id = format(span.context.trace_id, "032x")[:8]
37
+ # print(f"🟢 span formatted context trace id: {formatted_trace_id}")
38
+ self.traces[formatted_trace_id].append(span)
39
+ # self.spans.append(span)
40
+
41
+ return SpanExportResult.SUCCESS
42
+
43
+ def shutdown(self) -> None:
44
+ """Shut downs the exporter.
45
+
46
+ Calls to export after the exporter has been shut down will fail.
47
+ """
48
+ print("Shutting down exporter")
49
+ self._stopped = True
50
+
51
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
52
+ return True
53
+
54
+ def get_traces(self) -> typing.Dict[int, typing.List[ReadableSpan]]:
55
+ """Get all spans grouped by trace ID"""
56
+ return dict(self.traces)
57
+
58
+ def get_trace_by_testcase(self, testcase_name: str) -> typing.List[ReadableSpan]:
59
+ """Get all spans for a specific testcase"""
60
+ with self._lock:
61
+ return self.traces.get(self.testcase_to_trace.get(testcase_name, None), [])
62
+
63
+ def get_trace(self, trace_id: int) -> typing.List[ReadableSpan]:
64
+ """Get all spans for a specific trace"""
65
+ return self.traces.get(trace_id, [])
66
+
67
+ def get_trace_count(self) -> int:
68
+ """Get the number of unique traces"""
69
+ return len(self.traces)
70
+
71
+ def print_trace_summary(self):
72
+ """Print a summary of all traces"""
73
+ print(f"\n{'='*60}")
74
+ print(f"Total Traces: {self.get_trace_count()}")
75
+ for trace_id, spans in self.traces.items():
76
+ print(f"Trace ID: {trace_id}...")
77
+ print(f"Spans in trace: {len(spans)}")
78
+ print(f"Total Testcases: {self.testcase_to_trace}")
79
+ print(f"TraceIDs: {self.traces.keys()}")
80
+ print(f"{'='*60}\n")
81
+
82
+ # for trace_id, spans in self.traces.items():
83
+ # trace_id_hex = format(trace_id, '032x')
84
+ # print(f"📊 Trace ID: {trace_id_hex}")
85
+ # print(f" Spans in trace: {len(spans)}")
86
+
87
+ # # Sort spans by start time to show execution order
88
+ # sorted_spans = sorted(spans, key=lambda s: s.start_time)
89
+
90
+ # for span in sorted_spans:
91
+ # duration_ms = (span.end_time - span.start_time) / 1e6
92
+ # parent_id = format(span.parent.span_id, '016x') if span.parent else "None"
93
+ # indent = " " if span.parent else ""
94
+ # print(f" {indent}├─ {span.name}")
95
+ # print(f" {indent} ├─ Span ID: {format(span.context.span_id, '016x')}")
96
+ # print(f" {indent} ├─ Parent ID: {parent_id}")
97
+ # print(f" {indent} ├─ Duration: {duration_ms:.2f}ms")
98
+ # if span.attributes:
99
+ # print(f" {indent} └─ Attributes: {dict(span.attributes)}")
100
+ # print()
101
+
102
+ def save_traces_to_file(self, filename: typing.Optional[str] = "traces.json"):
103
+ """Save a trace to a file"""
104
+ traces = []
105
+ for trace_id, spans in self.traces.items():
106
+ traces.append(
107
+ {
108
+ "trace_id": trace_id,
109
+ "spans": [json.loads(span.to_json()) for span in spans],
110
+ }
111
+ )
112
+
113
+ os.makedirs(os.path.dirname(f"traces/{filename}"), exist_ok=True)
114
+ with open(f"traces/{filename}", "w") as f:
115
+ json.dump(traces, f, indent=2)
@@ -1,49 +1,49 @@
1
- import typing
2
-
3
- from opentelemetry.context import (
4
- _SUPPRESS_INSTRUMENTATION_KEY,
5
- Context,
6
- attach,
7
- detach,
8
- set_value,
9
- )
10
- from opentelemetry.sdk.trace.export import ReadableSpan, SpanExporter, SpanProcessor
11
- from opentelemetry.trace import Span, logger
12
-
13
- from quraite.tracing.constants import QURAITE_ADAPTER_TRACE_PREFIX
14
- from quraite.tracing.span_exporter import QuraiteInMemorySpanExporter
15
-
16
-
17
- class QuraiteSimpleSpanProcessor(SpanProcessor):
18
- """Simple SpanProcessor implementation.
19
-
20
- SimpleSpanProcessor is an implementation of `SpanProcessor` that
21
- passes ended spans directly to the configured `SpanExporter`.
22
- """
23
-
24
- def __init__(self, span_exporter: SpanExporter):
25
- self.span_exporter: QuraiteInMemorySpanExporter = span_exporter
26
-
27
- def on_start(
28
- self, span: Span, parent_context: typing.Optional[Context] = None
29
- ) -> None:
30
- if QURAITE_ADAPTER_TRACE_PREFIX in span.name:
31
- self.span_exporter.handle_testcase_trace(span)
32
-
33
- def on_end(self, span: ReadableSpan) -> None:
34
- if not span.context.trace_flags.sampled:
35
- return
36
- token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
37
- try:
38
- self.span_exporter.export((span,))
39
- # pylint: disable=broad-exception-caught
40
- except Exception:
41
- logger.exception("Exception while exporting Span.")
42
- detach(token)
43
-
44
- def shutdown(self) -> None:
45
- self.span_exporter.shutdown()
46
-
47
- def force_flush(self, timeout_millis: int = 30000) -> bool:
48
- # pylint: disable=unused-argument
49
- return True
1
+ import typing
2
+
3
+ from opentelemetry.context import (
4
+ _SUPPRESS_INSTRUMENTATION_KEY,
5
+ Context,
6
+ attach,
7
+ detach,
8
+ set_value,
9
+ )
10
+ from opentelemetry.sdk.trace.export import ReadableSpan, SpanExporter, SpanProcessor
11
+ from opentelemetry.trace import Span, logger
12
+
13
+ from quraite.tracing.constants import QURAITE_ADAPTER_TRACE_PREFIX
14
+ from quraite.tracing.span_exporter import QuraiteInMemorySpanExporter
15
+
16
+
17
+ class QuraiteSimpleSpanProcessor(SpanProcessor):
18
+ """Simple SpanProcessor implementation.
19
+
20
+ SimpleSpanProcessor is an implementation of `SpanProcessor` that
21
+ passes ended spans directly to the configured `SpanExporter`.
22
+ """
23
+
24
+ def __init__(self, span_exporter: SpanExporter):
25
+ self.span_exporter: QuraiteInMemorySpanExporter = span_exporter
26
+
27
+ def on_start(
28
+ self, span: Span, parent_context: typing.Optional[Context] = None
29
+ ) -> None:
30
+ if QURAITE_ADAPTER_TRACE_PREFIX in span.name:
31
+ self.span_exporter.handle_testcase_trace(span)
32
+
33
+ def on_end(self, span: ReadableSpan) -> None:
34
+ if not span.context.trace_flags.sampled:
35
+ return
36
+ token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
37
+ try:
38
+ self.span_exporter.export((span,))
39
+ # pylint: disable=broad-exception-caught
40
+ except Exception:
41
+ logger.exception("Exception while exporting Span.")
42
+ detach(token)
43
+
44
+ def shutdown(self) -> None:
45
+ self.span_exporter.shutdown()
46
+
47
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
48
+ # pylint: disable=unused-argument
49
+ return True