quraite 0.1.0__py3-none-any.whl → 0.1.2__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 +157 -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 +211 -209
  8. quraite/adapters/http_adapter.py +255 -239
  9. quraite/adapters/{langgraph_adapter.py → langchain_adapter.py} +305 -304
  10. quraite/adapters/{langgraph_server_adapter.py → langchain_server_adapter.py} +252 -252
  11. quraite/adapters/langflow_adapter.py +192 -192
  12. quraite/adapters/n8n_adapter.py +220 -220
  13. quraite/adapters/openai_agents_adapter.py +267 -269
  14. quraite/adapters/pydantic_ai_adapter.py +307 -312
  15. quraite/adapters/smolagents_adapter.py +148 -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 +25 -24
  37. quraite/tracing/constants.py +15 -16
  38. quraite/tracing/span_exporter.py +101 -115
  39. quraite/tracing/span_processor.py +47 -49
  40. quraite/tracing/tool_extractors.py +309 -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.2.dist-info/METADATA +386 -0
  46. quraite-0.1.2.dist-info/RECORD +49 -0
  47. {quraite-0.1.0.dist-info → quraite-0.1.2.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,269 +1,267 @@
1
- import json
2
- import uuid
3
- from typing import Dict, List, Optional, Union
4
-
5
- from agents import (
6
- Agent,
7
- MessageOutputItem,
8
- ReasoningItem,
9
- RunItem,
10
- Runner,
11
- SQLiteSession,
12
- ToolCallItem,
13
- ToolCallOutputItem,
14
- TResponseInputItem,
15
- )
16
- from agents.memory import Session
17
- from opentelemetry.trace import TracerProvider
18
-
19
- from quraite.adapters.base import BaseAdapter
20
- from quraite.logger import get_logger
21
- from quraite.schema.message import (
22
- AgentMessage,
23
- AssistantMessage,
24
- MessageContentReasoning,
25
- MessageContentText,
26
- ToolCall,
27
- ToolMessage,
28
- )
29
- from quraite.schema.response import AgentInvocationResponse
30
- from quraite.tracing.constants import QURAITE_ADAPTER_TRACE_PREFIX, Framework
31
- from quraite.tracing.trace import AgentSpan, AgentTrace
32
-
33
- logger = get_logger(__name__)
34
-
35
-
36
- class OpenaiAgentsAdapter(BaseAdapter):
37
- def __init__(
38
- self,
39
- agent: Agent,
40
- agent_name: str = "OpenAI Agents",
41
- tracer_provider: Optional[TracerProvider] = None,
42
- ):
43
- self.agent = agent
44
- self.sessions: Dict[str, Session] = {}
45
- self._init_tracing(tracer_provider, required=False)
46
- self.agent_name = agent_name
47
- logger.info(
48
- "OpenaiAgentsAdapter initialized (agent_name=%s, tracing_enabled=%s)",
49
- agent_name,
50
- bool(tracer_provider),
51
- )
52
-
53
- def _convert_run_items_to_messages(
54
- self, run_items: List[RunItem]
55
- ) -> List[AgentMessage]:
56
- logger.debug("Converting %d OpenAI run items to messages", len(run_items))
57
- messages: List[AgentMessage] = []
58
- text_content: List[MessageContentText] = []
59
- reasoning_content: List[MessageContentReasoning] = []
60
- tool_calls: List[ToolCall] = []
61
-
62
- def flush_assistant_message():
63
- nonlocal text_content, reasoning_content, tool_calls
64
- if text_content or reasoning_content or tool_calls:
65
- content = []
66
- if text_content:
67
- content.extend(text_content)
68
- if reasoning_content:
69
- content.extend(reasoning_content)
70
- messages.append(
71
- AssistantMessage(
72
- content=content if content else None,
73
- tool_calls=tool_calls if tool_calls else None,
74
- )
75
- )
76
- text_content = []
77
- reasoning_content = []
78
- tool_calls = []
79
-
80
- for item in run_items:
81
- if item.type in [
82
- "handoff_call_item",
83
- "handoff_output_item",
84
- "mcp_list_tools_item",
85
- "mcp_approval_request_item",
86
- "mcp_approval_response_item",
87
- ]:
88
- continue
89
-
90
- if isinstance(item, MessageOutputItem):
91
- text_parts = []
92
- for content_item in item.raw_item.content:
93
- if hasattr(content_item, "text"):
94
- text_parts.append(content_item.text)
95
- if text_parts:
96
- text_content.append(
97
- MessageContentText(type="text", text="".join(text_parts))
98
- )
99
-
100
- elif isinstance(item, ReasoningItem):
101
- if item.raw_item.summary:
102
- summary = ""
103
- for summary_item in item.raw_item.summary:
104
- summary += summary_item.text
105
- summary += "\n"
106
- reasoning_content.append(
107
- MessageContentReasoning(type="reasoning", reasoning=summary)
108
- )
109
-
110
- elif isinstance(item, ToolCallItem):
111
- raw = item.raw_item
112
- arguments = None
113
- if hasattr(raw, "arguments"):
114
- try:
115
- arguments = (
116
- json.loads(raw.arguments)
117
- if isinstance(raw.arguments, str)
118
- else raw.arguments
119
- )
120
- except:
121
- arguments = {"raw": str(raw.arguments)}
122
- tool_calls.append(
123
- ToolCall(
124
- id=getattr(raw, "call_id", ""),
125
- name=getattr(raw, "name", ""),
126
- arguments=arguments or {},
127
- )
128
- )
129
-
130
- elif isinstance(item, ToolCallOutputItem):
131
- flush_assistant_message()
132
- tool_result = json.dumps({"output": item.output})
133
- messages.append(
134
- ToolMessage(
135
- tool_call_id=item.raw_item.get("call_id", ""),
136
- content=[MessageContentText(type="text", text=tool_result)],
137
- )
138
- )
139
- continue
140
-
141
- flush_assistant_message()
142
- logger.info("Converted OpenAI agent run into %d messages", len(messages))
143
- return messages
144
-
145
- def _prepare_input(self, input: List[AgentMessage]) -> str:
146
- logger.debug("Preparing OpenAI input from %d messages", len(input))
147
- if not input or input[-1].role != "user":
148
- logger.error("OpenAI input missing user message")
149
- raise ValueError("No user message found in the input")
150
-
151
- last_user_message = input[-1]
152
- if not last_user_message.content:
153
- logger.error("OpenAI user message missing content")
154
- raise ValueError("User message has no content")
155
-
156
- text_content = None
157
- for content_item in last_user_message.content:
158
- if content_item.type == "text" and content_item.text:
159
- text_content = content_item.text
160
- break
161
-
162
- if not text_content:
163
- logger.error("OpenAI user message missing text content")
164
- raise ValueError("No text content found in user message")
165
-
166
- logger.debug("Prepared OpenAI input (text_length=%d)", len(text_content))
167
- return text_content
168
-
169
- async def ainvoke(
170
- self,
171
- input: List[AgentMessage],
172
- session_id: Union[str, None] = None,
173
- ) -> AgentInvocationResponse:
174
- """Asynchronous invocation method - invokes the OpenAI Agents agent and converts to List[AgentMessage]."""
175
- try:
176
- logger.info(
177
- "OpenAI ainvoke called (session_id=%s, input_messages=%d)",
178
- session_id,
179
- len(input),
180
- )
181
- agent_input: Union[str, List[TResponseInputItem]] = self._prepare_input(
182
- input
183
- )
184
-
185
- if session_id not in self.sessions:
186
- self.sessions[session_id] = SQLiteSession(session_id=session_id)
187
- session = self.sessions[session_id]
188
-
189
- if self.tracer_provider:
190
- return await self._ainvoke_with_tracing(agent_input, session)
191
-
192
- return await self._ainvoke_without_tracing(agent_input, session)
193
- except Exception as exc:
194
- logger.exception("Error invoking OpenAI agent")
195
- raise Exception(f"Error invoking Openai agent: {exc}") from exc
196
-
197
- async def _ainvoke_with_tracing(
198
- self,
199
- agent_input: Union[str, List[TResponseInputItem]],
200
- session: Session,
201
- ) -> AgentInvocationResponse:
202
- """Execute ainvoke with tracing enabled."""
203
- adapter_trace_id = f"{QURAITE_ADAPTER_TRACE_PREFIX}-{uuid.uuid4()}"
204
- logger.debug(
205
- "Starting OpenAI traced invocation (trace_id=%s, session_id=%s)",
206
- adapter_trace_id,
207
- session.session_id if session else None,
208
- )
209
-
210
- with self.tracer.start_as_current_span(name=adapter_trace_id):
211
- await Runner.run(
212
- self.agent,
213
- input=agent_input,
214
- session=session,
215
- )
216
-
217
- trace_readable_spans = self.quraite_span_exporter.get_trace_by_testcase(
218
- adapter_trace_id
219
- )
220
-
221
- if trace_readable_spans:
222
- agent_trace = AgentTrace(
223
- spans=[
224
- AgentSpan.from_readable_oi_span(span)
225
- for span in trace_readable_spans
226
- ]
227
- )
228
- logger.info(
229
- "OpenAI trace collected %d spans for trace_id=%s",
230
- len(trace_readable_spans),
231
- adapter_trace_id,
232
- )
233
- else:
234
- logger.warning(
235
- "No spans exported for OpenAI trace_id=%s",
236
- adapter_trace_id,
237
- )
238
-
239
- return AgentInvocationResponse(
240
- agent_trace=agent_trace,
241
- agent_trajectory=agent_trace.to_agent_trajectory(
242
- framework=Framework.OPENAI_AGENTS
243
- ),
244
- )
245
-
246
- async def _ainvoke_without_tracing(
247
- self,
248
- agent_input: Union[str, List[TResponseInputItem]],
249
- session: Session,
250
- ) -> AgentInvocationResponse:
251
- """Execute ainvoke without tracing."""
252
- result = await Runner.run(
253
- self.agent,
254
- input=agent_input,
255
- session=session,
256
- )
257
-
258
- try:
259
- agent_trajectory = self._convert_run_items_to_messages(result.new_items)
260
- logger.info(
261
- "OpenAI agent produced %d trajectory messages (no tracing)",
262
- len(agent_trajectory),
263
- )
264
- return AgentInvocationResponse(
265
- agent_trajectory=agent_trajectory,
266
- )
267
- except Exception as exc:
268
- logger.exception("Error converting OpenAI run items to messages")
269
- raise Exception(f"Error converting run items to messages: {exc}") from exc
1
+ import json
2
+ from typing import Dict, List, Optional, Union
3
+
4
+ from agents import (
5
+ Agent,
6
+ MessageOutputItem,
7
+ ReasoningItem,
8
+ RunItem,
9
+ Runner,
10
+ SQLiteSession,
11
+ ToolCallItem,
12
+ ToolCallOutputItem,
13
+ TResponseInputItem,
14
+ )
15
+ from agents.memory import Session
16
+ from opentelemetry.trace import TracerProvider
17
+
18
+ from quraite.adapters.base import BaseAdapter
19
+ from quraite.logger import get_logger
20
+ from quraite.schema.message import (
21
+ AgentMessage,
22
+ AssistantMessage,
23
+ MessageContentReasoning,
24
+ MessageContentText,
25
+ ToolCall,
26
+ ToolMessage,
27
+ )
28
+ from quraite.schema.response import AgentInvocationResponse
29
+ from quraite.tracing.constants import Framework
30
+ from quraite.tracing.trace import AgentSpan, AgentTrace
31
+
32
+ logger = get_logger(__name__)
33
+
34
+
35
+ class OpenaiAgentsAdapter(BaseAdapter):
36
+ def __init__(
37
+ self,
38
+ agent: Agent,
39
+ agent_name: str = "OpenAI Agents",
40
+ tracer_provider: Optional[TracerProvider] = None,
41
+ ):
42
+ self.agent = agent
43
+ self.sessions: Dict[str, Session] = {}
44
+ self._init_tracing(tracer_provider, required=False)
45
+ self.agent_name = agent_name
46
+ logger.info(
47
+ "OpenaiAgentsAdapter initialized (agent_name=%s, tracing_enabled=%s)",
48
+ agent_name,
49
+ bool(tracer_provider),
50
+ )
51
+
52
+ def _convert_run_items_to_messages(
53
+ self, run_items: List[RunItem]
54
+ ) -> List[AgentMessage]:
55
+ logger.debug("Converting %d OpenAI run items to messages", len(run_items))
56
+ messages: List[AgentMessage] = []
57
+ text_content: List[MessageContentText] = []
58
+ reasoning_content: List[MessageContentReasoning] = []
59
+ tool_calls: List[ToolCall] = []
60
+
61
+ def flush_assistant_message():
62
+ nonlocal text_content, reasoning_content, tool_calls
63
+ if text_content or reasoning_content or tool_calls:
64
+ content = []
65
+ if text_content:
66
+ content.extend(text_content)
67
+ if reasoning_content:
68
+ content.extend(reasoning_content)
69
+ messages.append(
70
+ AssistantMessage(
71
+ content=content if content else None,
72
+ tool_calls=tool_calls if tool_calls else None,
73
+ )
74
+ )
75
+ text_content = []
76
+ reasoning_content = []
77
+ tool_calls = []
78
+
79
+ for item in run_items:
80
+ if item.type in [
81
+ "handoff_call_item",
82
+ "handoff_output_item",
83
+ "mcp_list_tools_item",
84
+ "mcp_approval_request_item",
85
+ "mcp_approval_response_item",
86
+ ]:
87
+ continue
88
+
89
+ if isinstance(item, MessageOutputItem):
90
+ text_parts = []
91
+ for content_item in item.raw_item.content:
92
+ if hasattr(content_item, "text"):
93
+ text_parts.append(content_item.text)
94
+ if text_parts:
95
+ text_content.append(
96
+ MessageContentText(type="text", text="".join(text_parts))
97
+ )
98
+
99
+ elif isinstance(item, ReasoningItem):
100
+ if item.raw_item.summary:
101
+ summary = ""
102
+ for summary_item in item.raw_item.summary:
103
+ summary += summary_item.text
104
+ summary += "\n"
105
+ reasoning_content.append(
106
+ MessageContentReasoning(type="reasoning", reasoning=summary)
107
+ )
108
+
109
+ elif isinstance(item, ToolCallItem):
110
+ raw = item.raw_item
111
+ arguments = None
112
+ if hasattr(raw, "arguments"):
113
+ try:
114
+ arguments = (
115
+ json.loads(raw.arguments)
116
+ if isinstance(raw.arguments, str)
117
+ else raw.arguments
118
+ )
119
+ except:
120
+ arguments = {"raw": str(raw.arguments)}
121
+ tool_calls.append(
122
+ ToolCall(
123
+ id=getattr(raw, "call_id", ""),
124
+ name=getattr(raw, "name", ""),
125
+ arguments=arguments or {},
126
+ )
127
+ )
128
+
129
+ elif isinstance(item, ToolCallOutputItem):
130
+ flush_assistant_message()
131
+ tool_result = json.dumps({"output": item.output})
132
+ messages.append(
133
+ ToolMessage(
134
+ tool_call_id=item.raw_item.get("call_id", ""),
135
+ content=[MessageContentText(type="text", text=tool_result)],
136
+ )
137
+ )
138
+ continue
139
+
140
+ flush_assistant_message()
141
+ logger.info("Converted OpenAI agent run into %d messages", len(messages))
142
+ return messages
143
+
144
+ def _prepare_input(self, input: List[AgentMessage]) -> str:
145
+ logger.debug("Preparing OpenAI input from %d messages", len(input))
146
+ if not input or input[-1].role != "user":
147
+ logger.error("OpenAI input missing user message")
148
+ raise ValueError("No user message found in the input")
149
+
150
+ last_user_message = input[-1]
151
+ if not last_user_message.content:
152
+ logger.error("OpenAI user message missing content")
153
+ raise ValueError("User message has no content")
154
+
155
+ text_content = None
156
+ for content_item in last_user_message.content:
157
+ if content_item.type == "text" and content_item.text:
158
+ text_content = content_item.text
159
+ break
160
+
161
+ if not text_content:
162
+ logger.error("OpenAI user message missing text content")
163
+ raise ValueError("No text content found in user message")
164
+
165
+ logger.debug("Prepared OpenAI input (text_length=%d)", len(text_content))
166
+ return text_content
167
+
168
+ async def ainvoke(
169
+ self,
170
+ input: List[AgentMessage],
171
+ session_id: Union[str, None] = None,
172
+ ) -> AgentInvocationResponse:
173
+ """Asynchronous invocation method - invokes the OpenAI Agents agent and converts to List[AgentMessage]."""
174
+ try:
175
+ logger.info(
176
+ "OpenAI ainvoke called (session_id=%s, input_messages=%d)",
177
+ session_id,
178
+ len(input),
179
+ )
180
+ agent_input: Union[str, List[TResponseInputItem]] = self._prepare_input(
181
+ input
182
+ )
183
+
184
+ if session_id not in self.sessions:
185
+ self.sessions[session_id] = SQLiteSession(session_id=session_id)
186
+ session = self.sessions[session_id]
187
+
188
+ if self.tracer_provider:
189
+ return await self._ainvoke_with_tracing(agent_input, session)
190
+
191
+ return await self._ainvoke_without_tracing(agent_input, session)
192
+ except Exception as exc:
193
+ logger.exception("Error invoking OpenAI agent")
194
+ raise Exception(f"Error invoking Openai agent: {exc}") from exc
195
+
196
+ async def _ainvoke_with_tracing(
197
+ self,
198
+ agent_input: Union[str, List[TResponseInputItem]],
199
+ session: Session,
200
+ ) -> AgentInvocationResponse:
201
+ """Execute ainvoke with tracing enabled."""
202
+ with self.tracer.start_as_current_span("openai_invocation") as span:
203
+ trace_id = span.get_span_context().trace_id
204
+ logger.debug(
205
+ "Starting OpenAI traced invocation (trace_id=%s session_id=%s)",
206
+ trace_id,
207
+ session.session_id if session else None,
208
+ )
209
+ await Runner.run(
210
+ self.agent,
211
+ input=agent_input,
212
+ session=session,
213
+ )
214
+
215
+ trace_readable_spans = self.quraite_span_exporter.get_spans_by_trace_id(
216
+ trace_id
217
+ )
218
+
219
+ if trace_readable_spans:
220
+ agent_trace = AgentTrace(
221
+ spans=[
222
+ AgentSpan.from_readable_oi_span(span)
223
+ for span in trace_readable_spans
224
+ ]
225
+ )
226
+ logger.info(
227
+ "OpenAI trace collected %d spans for trace_id=%s",
228
+ len(trace_readable_spans),
229
+ trace_id,
230
+ )
231
+ else:
232
+ logger.warning(
233
+ "No spans exported for OpenAI trace_id=%s",
234
+ trace_id,
235
+ )
236
+
237
+ return AgentInvocationResponse(
238
+ agent_trace=agent_trace,
239
+ agent_trajectory=agent_trace.to_agent_trajectory(
240
+ framework=Framework.OPENAI_AGENTS
241
+ ),
242
+ )
243
+
244
+ async def _ainvoke_without_tracing(
245
+ self,
246
+ agent_input: Union[str, List[TResponseInputItem]],
247
+ session: Session,
248
+ ) -> AgentInvocationResponse:
249
+ """Execute ainvoke without tracing."""
250
+ result = await Runner.run(
251
+ self.agent,
252
+ input=agent_input,
253
+ session=session,
254
+ )
255
+
256
+ try:
257
+ agent_trajectory = self._convert_run_items_to_messages(result.new_items)
258
+ logger.info(
259
+ "OpenAI agent produced %d trajectory messages (no tracing)",
260
+ len(agent_trajectory),
261
+ )
262
+ return AgentInvocationResponse(
263
+ agent_trajectory=agent_trajectory,
264
+ )
265
+ except Exception as exc:
266
+ logger.exception("Error converting OpenAI run items to messages")
267
+ raise Exception(f"Error converting run items to messages: {exc}") from exc