quraite 0.1.2__tar.gz → 0.1.4__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.
Files changed (59) hide show
  1. {quraite-0.1.2 → quraite-0.1.4}/PKG-INFO +54 -62
  2. {quraite-0.1.2 → quraite-0.1.4}/README.md +53 -61
  3. {quraite-0.1.2 → quraite-0.1.4}/pyproject.toml +25 -4
  4. quraite-0.1.4/quraite/__init__.py +7 -0
  5. quraite-0.1.4/quraite/adapters/agno_adapter.py +115 -0
  6. quraite-0.1.4/quraite/adapters/base.py +73 -0
  7. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/bedrock_agents_adapter.py +23 -76
  8. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/flowise_adapter.py +31 -72
  9. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/google_adk_adapter.py +28 -94
  10. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/http_adapter.py +28 -44
  11. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/langchain_adapter.py +51 -118
  12. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/langchain_server_adapter.py +37 -89
  13. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/langflow_adapter.py +15 -60
  14. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/n8n_adapter.py +19 -63
  15. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/openai_agents_adapter.py +35 -59
  16. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/pydantic_ai_adapter.py +27 -97
  17. quraite-0.1.4/quraite/adapters/smolagents_adapter.py +87 -0
  18. quraite-0.1.2/quraite/tracing/constants.py → quraite-0.1.4/quraite/constants/framework.py +1 -2
  19. quraite-0.1.4/quraite/schema/__init__.py +4 -0
  20. quraite-0.1.4/quraite/schema/invoke.py +46 -0
  21. {quraite-0.1.2 → quraite-0.1.4}/quraite/schema/message.py +20 -21
  22. quraite-0.1.4/quraite/serve/__init__.py +5 -0
  23. {quraite-0.1.2 → quraite-0.1.4}/quraite/serve/cloudflared.py +3 -2
  24. quraite-0.1.4/quraite/serve/server.py +305 -0
  25. {quraite-0.1.2 → quraite-0.1.4}/quraite/tracing/__init__.py +8 -5
  26. quraite-0.1.4/quraite/tracing/constants.py +1 -0
  27. quraite-0.1.4/quraite/tracing/setup.py +129 -0
  28. {quraite-0.1.2 → quraite-0.1.4}/quraite/tracing/span_exporter.py +6 -6
  29. {quraite-0.1.2 → quraite-0.1.4}/quraite/tracing/span_processor.py +6 -7
  30. {quraite-0.1.2 → quraite-0.1.4}/quraite/tracing/tool_extractors.py +1 -1
  31. {quraite-0.1.2 → quraite-0.1.4}/quraite/tracing/trace.py +36 -24
  32. {quraite-0.1.2 → quraite-0.1.4}/quraite/utils/json_utils.py +2 -2
  33. quraite-0.1.2/quraite/__init__.py +0 -3
  34. quraite-0.1.2/quraite/adapters/agno_adapter.py +0 -157
  35. quraite-0.1.2/quraite/adapters/base.py +0 -123
  36. quraite-0.1.2/quraite/adapters/smolagents_adapter.py +0 -148
  37. quraite-0.1.2/quraite/schema/response.py +0 -16
  38. quraite-0.1.2/quraite/serve/__init__.py +0 -1
  39. quraite-0.1.2/quraite/serve/local_agent.py +0 -360
  40. quraite-0.1.2/quraite/traces/traces_adk_openinference.json +0 -379
  41. quraite-0.1.2/quraite/traces/traces_agno_multi_agent.json +0 -669
  42. quraite-0.1.2/quraite/traces/traces_agno_openinference.json +0 -321
  43. quraite-0.1.2/quraite/traces/traces_crewai_openinference.json +0 -155
  44. quraite-0.1.2/quraite/traces/traces_langgraph_openinference.json +0 -349
  45. quraite-0.1.2/quraite/traces/traces_langgraph_openinference_multi_agent.json +0 -2705
  46. quraite-0.1.2/quraite/traces/traces_langgraph_traceloop.json +0 -510
  47. quraite-0.1.2/quraite/traces/traces_openai_agents_multi_agent_1.json +0 -402
  48. quraite-0.1.2/quraite/traces/traces_openai_agents_openinference.json +0 -341
  49. quraite-0.1.2/quraite/traces/traces_pydantic_openinference.json +0 -286
  50. quraite-0.1.2/quraite/traces/traces_pydantic_openinference_multi_agent_1.json +0 -399
  51. quraite-0.1.2/quraite/traces/traces_pydantic_openinference_multi_agent_2.json +0 -398
  52. quraite-0.1.2/quraite/traces/traces_smol_agents_openinference.json +0 -397
  53. quraite-0.1.2/quraite/traces/traces_smol_agents_tool_calling_openinference.json +0 -704
  54. quraite-0.1.2/quraite/utils/__init__.py +0 -0
  55. {quraite-0.1.2 → quraite-0.1.4}/quraite/adapters/__init__.py +1 -1
  56. {quraite-0.1.2 → quraite-0.1.4}/quraite/logger.py +0 -0
  57. {quraite-0.1.2 → quraite-0.1.4}/quraite/tracing/types.py +0 -0
  58. {quraite-0.1.2 → quraite-0.1.4}/quraite/tracing/utils.py +0 -0
  59. {quraite-0.1.2/quraite/schema → quraite-0.1.4/quraite/utils}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: quraite
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: This project provides adaptors and methods to integrate with the Quraite platform
5
5
  Author: Shiv Mohith
6
6
  Author-email: Shiv Mohith <shivmohith8@gmail.com>
@@ -92,55 +92,43 @@ pip install 'quraite[langchain,pydantic-ai,agno]'
92
92
  Pass your compiled LangChain agent to the adapter and expose it as an HTTP API:
93
93
 
94
94
  ```python
95
- import asyncio
96
- import uvicorn
97
95
  from dotenv import load_dotenv
98
- from openinference.instrumentation import TracerProvider
99
- from openinference.instrumentation.langchain import LangChainInstrumentor
100
96
 
101
- from quraite.adapters import LangChainAdapter
102
- from quraite.serve.local_agent import LocalAgentServer
103
- from quraite.tracing.span_exporter import QuraiteInMemorySpanExporter
104
- from quraite.tracing.span_processor import QuraiteSimpleSpanProcessor
97
+ from quraite import run_agent
98
+ from quraite.adapters import LangchainAdapter
99
+ from quraite.tracing import Framework, setup_tracing
105
100
 
106
101
  load_dotenv()
107
102
 
108
- # Set up tracing
109
- # Use Quraite's in-memory span exporter to capture the agent trajectory
110
- # and use it for evaluation.
111
- tracer_provider = TracerProvider()
112
- quraite_span_exporter = QuraiteInMemorySpanExporter()
113
- quraite_span_processor = QuraiteSimpleSpanProcessor(quraite_span_exporter)
114
- tracer_provider.add_span_processor(quraite_span_processor)
115
- LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
116
-
117
103
  # Your compiled LangChain agent (created elsewhere)
118
104
  # agent = create_agent(...)
119
105
 
120
- # Wrap with Quraite adapter
121
- adapter = LangChainAdapter(
106
+ # Setup tracing once
107
+ tracer_provider = setup_tracing([Framework.LANGCHAIN])
108
+
109
+ # Create adapter with tracer provider
110
+ adapter = LangchainAdapter(
122
111
  agent_graph=agent, # Pass your compiled LangChain agent here
123
112
  tracer_provider=tracer_provider,
124
113
  )
125
114
 
126
- # Create and start server with Cloudflare tunnel
127
- server = LocalAgentServer(
128
- wrapped_agent=adapter,
115
+ # Option 1: Use run_agent to start the server (simplest)
116
+ run_agent(
117
+ adapter,
129
118
  agent_id="your-agent-id", # Optional: for Quraite platform integration
130
- )
131
-
132
- app = server.create_app(
133
119
  port=8080,
134
120
  host="0.0.0.0",
135
121
  tunnel="cloudflare", # Options: "cloudflare", "ngrok", or "none"
136
122
  )
137
123
 
138
- # Option 1: Use the start method to start the server
139
- asyncio.run(server.start(host="0.0.0.0", port=8080))
124
+ # Option 2: Use create_app for more control (e.g., with uvicorn reload)
125
+ from quraite import create_app
126
+ import uvicorn
127
+
128
+ app = create_app(adapter, agent_id="your-agent-id")
140
129
 
141
- # Option 2: Use uvicorn to start the server for auto-reload
142
- # if __name__ == "__main__":
143
- # uvicorn.run("local_server:app", host="0.0.0.0", port=8080, reload=True)
130
+ if __name__ == "__main__":
131
+ uvicorn.run("local_server:app", host="0.0.0.0", port=8080, reload=True)
144
132
  ```
145
133
 
146
134
  The server exposes:
@@ -191,20 +179,14 @@ Most agent frameworks return agent steps, but lack critical observability data.
191
179
  To enable tracing:
192
180
 
193
181
  ```python
194
- from openinference.instrumentation import TracerProvider
195
- from quraite.tracing.span_exporter import QuraiteInMemorySpanExporter
196
- from quraite.tracing.span_processor import QuraiteSimpleSpanProcessor
197
-
198
- tracer_provider = TracerProvider()
182
+ from quraite.tracing import Framework, setup_tracing
199
183
 
200
- # Add Quraite span exporter and processor to the tracer provider
201
- quraite_span_exporter = QuraiteInMemorySpanExporter()
202
- quraite_span_processor = QuraiteSimpleSpanProcessor(quraite_span_exporter)
203
- tracer_provider.add_span_processor(quraite_span_processor)
184
+ # Setup tracing once at app startup
185
+ tracer_provider = setup_tracing([Framework.LANGCHAIN])
204
186
 
205
- # Instrument your framework with OpenInference
206
- from openinference.instrumentation.langchain import LangChainInstrumentor
207
- LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
187
+ # Pass to adapter
188
+ from quraite.adapters import LangchainAdapter
189
+ adapter = LangchainAdapter(agent_graph=agent, tracer_provider=tracer_provider)
208
190
  ```
209
191
 
210
192
  ### Message Schema
@@ -245,16 +227,18 @@ tool_msg = ToolMessage(
245
227
  )
246
228
  ```
247
229
 
248
- ### Response Format
230
+ ### Invoke Input and Output
249
231
 
250
- Agent invocations return an `AgentInvocationResponse`:
232
+ Agent invocations use structured `InvokeInput` and return `InvokeOutput`:
251
233
 
252
234
  ```python
253
- from quraite.schema.response import AgentInvocationResponse
235
+ from quraite.schema.invoke import InvokeInput, InvokeOutput
254
236
 
255
- response: AgentInvocationResponse = await adapter.ainvoke(
256
- input=[user_msg],
257
- session_id="session-123"
237
+ response: InvokeOutput = await adapter.ainvoke(
238
+ input=InvokeInput(
239
+ user_message=user_msg,
240
+ session_id="session-123"
241
+ )
258
242
  )
259
243
 
260
244
  # Access trajectory (list of messages)
@@ -292,37 +276,45 @@ All adapters inherit from `BaseAdapter`:
292
276
 
293
277
  ```python
294
278
  from quraite.adapters.base import BaseAdapter
279
+ from quraite.schema.invoke import InvokeInput, InvokeOutput
295
280
 
296
281
  class MyAdapter(BaseAdapter):
297
282
  async def ainvoke(
298
283
  self,
299
- input: List[AgentMessage],
300
- session_id: str | None,
301
- ) -> AgentInvocationResponse:
284
+ input: InvokeInput,
285
+ ) -> InvokeOutput:
302
286
  # Implementation
287
+ # Access user_message: input.user_message
288
+ # Access session_id: input.session_id
303
289
  pass
304
290
  ```
305
291
 
306
- ### LocalAgentServer
292
+ ### Running Your Agent
307
293
 
308
- Create a local HTTP server for your agent:
294
+ Use `run_agent` or `create_app` to start a local HTTP server for your agent:
309
295
 
310
296
  ```python
311
- import asyncio
312
- from quraite.serve.local_agent import LocalAgentServer
297
+ from quraite import run_agent, create_app
313
298
 
314
- server = LocalAgentServer(
315
- wrapped_agent=adapter,
299
+ # Simple approach: run_agent handles everything
300
+ run_agent(
301
+ adapter,
316
302
  agent_id="optional-agent-id",
317
- )
318
-
319
- app = server.create_app(
320
303
  port=8080,
321
304
  host="0.0.0.0",
322
305
  tunnel="cloudflare", # or "ngrok" or "none"
323
306
  )
324
307
 
325
- asyncio.run(server.start(host="0.0.0.0", port=8080))
308
+ # Advanced approach: create_app for more control (e.g., uvicorn reload)
309
+ import uvicorn
310
+
311
+ app = create_app(
312
+ adapter,
313
+ agent_id="optional-agent-id",
314
+ )
315
+
316
+ if __name__ == "__main__":
317
+ uvicorn.run("local_server:app", host="0.0.0.0", port=8080, reload=True)
326
318
  ```
327
319
 
328
320
  ## Development
@@ -57,55 +57,43 @@ pip install 'quraite[langchain,pydantic-ai,agno]'
57
57
  Pass your compiled LangChain agent to the adapter and expose it as an HTTP API:
58
58
 
59
59
  ```python
60
- import asyncio
61
- import uvicorn
62
60
  from dotenv import load_dotenv
63
- from openinference.instrumentation import TracerProvider
64
- from openinference.instrumentation.langchain import LangChainInstrumentor
65
61
 
66
- from quraite.adapters import LangChainAdapter
67
- from quraite.serve.local_agent import LocalAgentServer
68
- from quraite.tracing.span_exporter import QuraiteInMemorySpanExporter
69
- from quraite.tracing.span_processor import QuraiteSimpleSpanProcessor
62
+ from quraite import run_agent
63
+ from quraite.adapters import LangchainAdapter
64
+ from quraite.tracing import Framework, setup_tracing
70
65
 
71
66
  load_dotenv()
72
67
 
73
- # Set up tracing
74
- # Use Quraite's in-memory span exporter to capture the agent trajectory
75
- # and use it for evaluation.
76
- tracer_provider = TracerProvider()
77
- quraite_span_exporter = QuraiteInMemorySpanExporter()
78
- quraite_span_processor = QuraiteSimpleSpanProcessor(quraite_span_exporter)
79
- tracer_provider.add_span_processor(quraite_span_processor)
80
- LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
81
-
82
68
  # Your compiled LangChain agent (created elsewhere)
83
69
  # agent = create_agent(...)
84
70
 
85
- # Wrap with Quraite adapter
86
- adapter = LangChainAdapter(
71
+ # Setup tracing once
72
+ tracer_provider = setup_tracing([Framework.LANGCHAIN])
73
+
74
+ # Create adapter with tracer provider
75
+ adapter = LangchainAdapter(
87
76
  agent_graph=agent, # Pass your compiled LangChain agent here
88
77
  tracer_provider=tracer_provider,
89
78
  )
90
79
 
91
- # Create and start server with Cloudflare tunnel
92
- server = LocalAgentServer(
93
- wrapped_agent=adapter,
80
+ # Option 1: Use run_agent to start the server (simplest)
81
+ run_agent(
82
+ adapter,
94
83
  agent_id="your-agent-id", # Optional: for Quraite platform integration
95
- )
96
-
97
- app = server.create_app(
98
84
  port=8080,
99
85
  host="0.0.0.0",
100
86
  tunnel="cloudflare", # Options: "cloudflare", "ngrok", or "none"
101
87
  )
102
88
 
103
- # Option 1: Use the start method to start the server
104
- asyncio.run(server.start(host="0.0.0.0", port=8080))
89
+ # Option 2: Use create_app for more control (e.g., with uvicorn reload)
90
+ from quraite import create_app
91
+ import uvicorn
92
+
93
+ app = create_app(adapter, agent_id="your-agent-id")
105
94
 
106
- # Option 2: Use uvicorn to start the server for auto-reload
107
- # if __name__ == "__main__":
108
- # uvicorn.run("local_server:app", host="0.0.0.0", port=8080, reload=True)
95
+ if __name__ == "__main__":
96
+ uvicorn.run("local_server:app", host="0.0.0.0", port=8080, reload=True)
109
97
  ```
110
98
 
111
99
  The server exposes:
@@ -156,20 +144,14 @@ Most agent frameworks return agent steps, but lack critical observability data.
156
144
  To enable tracing:
157
145
 
158
146
  ```python
159
- from openinference.instrumentation import TracerProvider
160
- from quraite.tracing.span_exporter import QuraiteInMemorySpanExporter
161
- from quraite.tracing.span_processor import QuraiteSimpleSpanProcessor
162
-
163
- tracer_provider = TracerProvider()
147
+ from quraite.tracing import Framework, setup_tracing
164
148
 
165
- # Add Quraite span exporter and processor to the tracer provider
166
- quraite_span_exporter = QuraiteInMemorySpanExporter()
167
- quraite_span_processor = QuraiteSimpleSpanProcessor(quraite_span_exporter)
168
- tracer_provider.add_span_processor(quraite_span_processor)
149
+ # Setup tracing once at app startup
150
+ tracer_provider = setup_tracing([Framework.LANGCHAIN])
169
151
 
170
- # Instrument your framework with OpenInference
171
- from openinference.instrumentation.langchain import LangChainInstrumentor
172
- LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
152
+ # Pass to adapter
153
+ from quraite.adapters import LangchainAdapter
154
+ adapter = LangchainAdapter(agent_graph=agent, tracer_provider=tracer_provider)
173
155
  ```
174
156
 
175
157
  ### Message Schema
@@ -210,16 +192,18 @@ tool_msg = ToolMessage(
210
192
  )
211
193
  ```
212
194
 
213
- ### Response Format
195
+ ### Invoke Input and Output
214
196
 
215
- Agent invocations return an `AgentInvocationResponse`:
197
+ Agent invocations use structured `InvokeInput` and return `InvokeOutput`:
216
198
 
217
199
  ```python
218
- from quraite.schema.response import AgentInvocationResponse
200
+ from quraite.schema.invoke import InvokeInput, InvokeOutput
219
201
 
220
- response: AgentInvocationResponse = await adapter.ainvoke(
221
- input=[user_msg],
222
- session_id="session-123"
202
+ response: InvokeOutput = await adapter.ainvoke(
203
+ input=InvokeInput(
204
+ user_message=user_msg,
205
+ session_id="session-123"
206
+ )
223
207
  )
224
208
 
225
209
  # Access trajectory (list of messages)
@@ -257,37 +241,45 @@ All adapters inherit from `BaseAdapter`:
257
241
 
258
242
  ```python
259
243
  from quraite.adapters.base import BaseAdapter
244
+ from quraite.schema.invoke import InvokeInput, InvokeOutput
260
245
 
261
246
  class MyAdapter(BaseAdapter):
262
247
  async def ainvoke(
263
248
  self,
264
- input: List[AgentMessage],
265
- session_id: str | None,
266
- ) -> AgentInvocationResponse:
249
+ input: InvokeInput,
250
+ ) -> InvokeOutput:
267
251
  # Implementation
252
+ # Access user_message: input.user_message
253
+ # Access session_id: input.session_id
268
254
  pass
269
255
  ```
270
256
 
271
- ### LocalAgentServer
257
+ ### Running Your Agent
272
258
 
273
- Create a local HTTP server for your agent:
259
+ Use `run_agent` or `create_app` to start a local HTTP server for your agent:
274
260
 
275
261
  ```python
276
- import asyncio
277
- from quraite.serve.local_agent import LocalAgentServer
262
+ from quraite import run_agent, create_app
278
263
 
279
- server = LocalAgentServer(
280
- wrapped_agent=adapter,
264
+ # Simple approach: run_agent handles everything
265
+ run_agent(
266
+ adapter,
281
267
  agent_id="optional-agent-id",
282
- )
283
-
284
- app = server.create_app(
285
268
  port=8080,
286
269
  host="0.0.0.0",
287
270
  tunnel="cloudflare", # or "ngrok" or "none"
288
271
  )
289
272
 
290
- asyncio.run(server.start(host="0.0.0.0", port=8080))
273
+ # Advanced approach: create_app for more control (e.g., uvicorn reload)
274
+ import uvicorn
275
+
276
+ app = create_app(
277
+ adapter,
278
+ agent_id="optional-agent-id",
279
+ )
280
+
281
+ if __name__ == "__main__":
282
+ uvicorn.run("local_server:app", host="0.0.0.0", port=8080, reload=True)
291
283
  ```
292
284
 
293
285
  ## Development
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "quraite"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "This project provides adaptors and methods to integrate with the Quraite platform"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -49,10 +49,12 @@ module-root = ""
49
49
 
50
50
  [dependency-groups]
51
51
  dev = [
52
- "autoflake>=2.3.1",
53
- "black>=25.9.0",
54
52
  "ipykernel>=7.1.0",
55
- "isort>=6.1.0",
53
+ "mypy>=1.14.1",
54
+ "ruff>=0.9.4",
55
+ ]
56
+ temp = [
57
+ "arize-phoenix-otel>=0.14.0",
56
58
  ]
57
59
  test = [
58
60
  "numpy>=2.2.6",
@@ -72,3 +74,22 @@ test = [
72
74
  "pytest-env>=1.2.0",
73
75
  "pytest-dotenv>=0.5.2",
74
76
  ]
77
+
78
+ [tool.ruff]
79
+ line-length = 88
80
+ target-version = "py310"
81
+
82
+ [tool.ruff.lint]
83
+ select = ["E", "F", "I", "UP", "B", "SIM"]
84
+ ignore = []
85
+
86
+ [tool.ruff.lint.isort]
87
+ known-first-party = ["quraite"]
88
+
89
+ [tool.mypy]
90
+ python_version = "3.10"
91
+ warn_return_any = true
92
+ warn_unused_configs = true
93
+ disallow_untyped_defs = false
94
+ ignore_missing_imports = true
95
+ explicit_package_bases = true
@@ -0,0 +1,7 @@
1
+ """Quraite Python SDK"""
2
+
3
+ from quraite.serve import create_app, run_agent, setup_tunnel
4
+
5
+ __version__ = "0.6.0"
6
+
7
+ __all__ = ["create_app", "run_agent", "setup_tunnel"]
@@ -0,0 +1,115 @@
1
+ import uuid
2
+
3
+ from agno.agent import Agent
4
+ from agno.team import Team
5
+ from openinference.instrumentation.agno.utils import _AGNO_PARENT_NODE_CONTEXT_KEY
6
+ from opentelemetry import context as context_api
7
+ from opentelemetry.trace import TracerProvider
8
+
9
+ from quraite.adapters.base import BaseAdapter
10
+ from quraite.constants.framework import Framework
11
+ from quraite.logger import get_logger
12
+ from quraite.schema.invoke import InvokeInput, InvokeOutput
13
+ from quraite.tracing.trace import AgentSpan, AgentTrace
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ class AgnoAdapter(BaseAdapter):
19
+ """Agno adapter for Agent and Team (requires tracing)."""
20
+
21
+ def __init__(
22
+ self,
23
+ agent: Agent | Team,
24
+ *,
25
+ tracer_provider: TracerProvider | None = None,
26
+ agent_name: str = "Agno Agent",
27
+ ):
28
+ """
29
+ Initialize Agno adapter.
30
+
31
+ Args:
32
+ agent: Agno Agent or Team instance
33
+ tracer_provider: TracerProvider from setup_tracing() (required)
34
+ agent_name: Agent name for metadata
35
+ """
36
+ if tracer_provider is None:
37
+ raise ValueError(
38
+ "Agno adapter requires tracing. Use setup_tracing([Framework.AGNO]) first."
39
+ )
40
+
41
+ self.agent: Agent | Team = agent
42
+ self.agent_name = agent_name
43
+ self._init_tracer(tracer_provider)
44
+ logger.info("AgnoAdapter initialized")
45
+
46
+ async def ainvoke(self, input: InvokeInput) -> InvokeOutput:
47
+ """Invoke Agno agent and return response with trace."""
48
+ logger.info("ainvoke called (session_id=%s)", input.session_id)
49
+ session_id = input.session_id or str(uuid.uuid4())
50
+
51
+ try:
52
+ return await self._ainvoke_with_tracing(
53
+ input.user_message_str(), session_id
54
+ )
55
+ except Exception as e:
56
+ logger.exception("Error invoking Agno agent")
57
+ raise RuntimeError(f"Error invoking Agno agent: {e}") from e
58
+
59
+ async def _ainvoke_with_tracing(
60
+ self,
61
+ user_message: str,
62
+ session_id: str,
63
+ ) -> InvokeOutput:
64
+ """Execute ainvoke with tracing enabled."""
65
+ with self.tracer.start_as_current_span("agno_invocation") as span:
66
+ # Workaround: openinference-instrumentation-agno>=0.1.25 forces a new root
67
+ # trace for Team instances unless _AGNO_PARENT_NODE_CONTEXT_KEY is set.
68
+ # This ensures child spans inherit our trace_id.
69
+ # See: https://github.com/Arize-ai/openinference/pull/2533
70
+ #
71
+ # Context API notes:
72
+ # - set_value() creates a new Context with the key-value pair (doesn't activate it)
73
+ # - attach() makes the context active and returns a token (restore point)
74
+ # - detach(token) restores the previous context (cleanup)
75
+ ctx = context_api.set_value(
76
+ _AGNO_PARENT_NODE_CONTEXT_KEY,
77
+ format(span.get_span_context().span_id, '016x')
78
+ )
79
+ token = context_api.attach(ctx)
80
+ trace_id = span.get_span_context().trace_id
81
+
82
+ logger.debug(
83
+ "Starting traced invocation (session_id=%s) with trace_id=%s",
84
+ session_id,
85
+ trace_id,
86
+ )
87
+ try:
88
+ await self.agent.arun(user_message, session_id=session_id)
89
+ finally:
90
+ context_api.detach(token)
91
+
92
+ # Get trace spans
93
+ trace_readable_spans = self.quraite_span_exporter.get_spans_by_trace_id(
94
+ trace_id
95
+ )
96
+
97
+ if trace_readable_spans:
98
+ logger.info(
99
+ "Retrieved %d spans for trace_id=%s",
100
+ len(trace_readable_spans),
101
+ trace_id,
102
+ )
103
+ agent_trace = AgentTrace(
104
+ spans=[
105
+ AgentSpan.from_readable_oi_span(span)
106
+ for span in trace_readable_spans
107
+ ],
108
+ )
109
+ else:
110
+ logger.warning("No spans found for trace_id=%s", trace_id)
111
+
112
+ return InvokeOutput(
113
+ agent_trace=agent_trace,
114
+ agent_trajectory=agent_trace.to_agent_trajectory(framework=Framework.AGNO),
115
+ )
@@ -0,0 +1,73 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from opentelemetry.sdk.trace import TracerProvider as SDKTracerProvider
4
+ from opentelemetry.trace import Tracer, TracerProvider
5
+
6
+ from quraite.logger import get_logger
7
+ from quraite.schema.invoke import InvokeInput, InvokeOutput
8
+ from quraite.schema.message import AssistantMessage, MessageContentText
9
+ from quraite.tracing.constants import QURAITE_TRACER_NAME
10
+ from quraite.tracing.span_exporter import QuraiteSpanExporter
11
+ from quraite.tracing.span_processor import QuraiteSpanProcessor
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ class BaseAdapter(ABC):
17
+ """Base adapter for all framework adapters."""
18
+
19
+ tracer_provider: TracerProvider | None = None
20
+ tracer: Tracer | None = None
21
+ quraite_span_exporter: QuraiteSpanExporter = QuraiteSpanExporter()
22
+
23
+ def _init_tracer(self, tracer_provider: SDKTracerProvider | None) -> None:
24
+ """Initialize tracer components from TracerProvider."""
25
+ if tracer_provider is None:
26
+ return
27
+
28
+ self.tracer_provider = tracer_provider
29
+ self.tracer = tracer_provider.get_tracer(QURAITE_TRACER_NAME)
30
+
31
+ # Find Quraite span exporter
32
+ quraite_span_exporter = next(
33
+ (
34
+ processor.span_exporter
35
+ for processor in tracer_provider._active_span_processor._span_processors
36
+ if isinstance(processor, QuraiteSpanProcessor)
37
+ ),
38
+ None,
39
+ )
40
+
41
+ if quraite_span_exporter is None:
42
+ raise ValueError(
43
+ "Quraite span exporter not found. "
44
+ "Use setup_tracing() to configure tracing properly."
45
+ )
46
+
47
+ self.quraite_span_exporter = quraite_span_exporter
48
+
49
+ @abstractmethod
50
+ async def ainvoke(
51
+ self,
52
+ input: InvokeInput,
53
+ ) -> InvokeOutput:
54
+ """Invoke agent with input and return response."""
55
+ raise NotImplementedError("Not implemented")
56
+
57
+
58
+ class DummyAdapter(BaseAdapter):
59
+ """Dummy adapter for testing."""
60
+
61
+ async def ainvoke(
62
+ self,
63
+ input: InvokeInput,
64
+ ) -> InvokeOutput:
65
+ """Returns dummy response."""
66
+ return InvokeOutput(
67
+ agent_trajectory=[
68
+ input.user_message,
69
+ AssistantMessage(
70
+ content=[MessageContentText(text="Dummy response")],
71
+ ),
72
+ ]
73
+ )