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,304 +1,305 @@
1
- import json
2
- import uuid
3
- from typing import Optional
4
-
5
- from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
6
- from langgraph.graph.state import CompiledStateGraph
7
- from opentelemetry.trace import TracerProvider
8
-
9
- from quraite.adapters.base import BaseAdapter
10
- from quraite.logger import get_logger
11
- from quraite.schema.message import AgentMessage, AssistantMessage, MessageContentText
12
- from quraite.schema.message import SystemMessage as QuraiteSystemMessage
13
- from quraite.schema.message import ToolCall
14
- from quraite.schema.message import ToolMessage as QuraiteToolMessage
15
- from quraite.schema.message import UserMessage
16
- from quraite.schema.response import AgentInvocationResponse
17
- from quraite.tracing.constants import QURAITE_ADAPTER_TRACE_PREFIX, Framework
18
- from quraite.tracing.trace import AgentSpan, AgentTrace
19
-
20
- LangchainMessage = HumanMessage | SystemMessage | AIMessage | ToolMessage
21
-
22
- logger = get_logger(__name__)
23
-
24
-
25
- class LanggraphAdapter(BaseAdapter):
26
- """
27
- LangGraph adapter wrapper that converts any LangGraph agent
28
- to a standardized callable interface (invoke) and converts the output to List[AgentMessage].
29
-
30
- This class wraps any LangGraph CompiledGraph and provides:
31
- - Synchronous invocation via invoke()
32
- - Asynchronous invocation via ainvoke()
33
- - Automatic conversion to List[AgentMessage] format
34
- """
35
-
36
- def __init__(
37
- self,
38
- agent_graph: CompiledStateGraph,
39
- agent_name: str = "LangGraph Agent",
40
- tracer_provider: Optional[TracerProvider] = None,
41
- ):
42
- """
43
- Initialize with a pre-configured LangGraph agent
44
-
45
- Args:
46
- agent_graph: Any CompiledGraph from LangGraph (must have invoke/ainvoke methods)
47
- agent_name: Name of the agent for trajectory metadata
48
- """
49
- logger.debug("Initializing LanggraphAdapter with agent_name=%s", agent_name)
50
- self.agent_graph = agent_graph
51
- self.agent_name = agent_name
52
- self._init_tracing(tracer_provider, required=False)
53
- logger.info(
54
- "LanggraphAdapter initialized successfully (tracing_enabled=%s)",
55
- bool(tracer_provider),
56
- )
57
-
58
- def _prepare_input(
59
- self, input: list[AgentMessage]
60
- ) -> dict[str, list[HumanMessage]]:
61
- """
62
- Prepare input for LangGraph agent from List[Message].
63
-
64
- Args:
65
- input: List[AgentMessage] containing user_message
66
-
67
- Returns:
68
- Dictionary with 'messages' key containing the prepared UserMessage
69
- """
70
- logger.debug("Preparing input from %d messages", len(input))
71
-
72
- if not input or input[-1].role != "user":
73
- logger.error(
74
- "Invalid input: no user message found (input_length=%d)", len(input)
75
- )
76
- raise ValueError("No user message found in the input")
77
-
78
- last_user_message = input[-1]
79
-
80
- if not last_user_message.content:
81
- logger.error("User message has no content")
82
- raise ValueError("User message has no content")
83
-
84
- text_content = next(
85
- (
86
- content_item.text
87
- for content_item in last_user_message.content
88
- if content_item.type == "text" and content_item.text
89
- ),
90
- None,
91
- )
92
-
93
- if not text_content:
94
- logger.error("No text content found in user message")
95
- raise ValueError("No text content found in user message")
96
-
97
- logger.debug("Prepared input with text_content length=%d", len(text_content))
98
- return {"messages": [HumanMessage(content=text_content)]}
99
-
100
- def _convert_langchain_messages_to_quraite_messages(
101
- self,
102
- messages: list[LangchainMessage],
103
- ) -> list[AgentMessage]:
104
- logger.debug(
105
- "Converting %d langchain messages to quraite format", len(messages)
106
- )
107
- converted_messages: list[AgentMessage] = []
108
-
109
- for idx, msg in enumerate(messages):
110
- match msg:
111
- case SystemMessage():
112
- logger.debug("Converting SystemMessage at index %d", idx)
113
- converted_messages.append(
114
- QuraiteSystemMessage(
115
- content=[MessageContentText(type="text", text=msg.content)]
116
- )
117
- )
118
-
119
- case HumanMessage():
120
- logger.debug("Converting HumanMessage at index %d", idx)
121
- converted_messages.append(
122
- UserMessage(
123
- content=[MessageContentText(type="text", text=msg.content)]
124
- )
125
- )
126
-
127
- case AIMessage():
128
- logger.debug(
129
- "Converting AIMessage at index %d (has_tool_calls=%s)",
130
- idx,
131
- bool(msg.tool_calls),
132
- )
133
- text_content, tool_calls = self._extract_ai_message_content(msg)
134
- converted_messages.append(
135
- AssistantMessage(
136
- content=text_content if text_content else None,
137
- tool_calls=tool_calls if tool_calls else None,
138
- )
139
- )
140
-
141
- case ToolMessage():
142
- logger.debug(
143
- "Converting ToolMessage at index %d (tool_call_id=%s)",
144
- idx,
145
- msg.tool_call_id,
146
- )
147
- if not msg.content:
148
- tool_message_content = ""
149
- elif isinstance(msg.content, str):
150
- tool_message_content = msg.content
151
- else:
152
- tool_message_content = json.dumps(msg.content)
153
-
154
- converted_messages.append(
155
- QuraiteToolMessage(
156
- tool_call_id=msg.tool_call_id,
157
- content=[
158
- MessageContentText(
159
- type="text", text=tool_message_content
160
- )
161
- ],
162
- )
163
- )
164
-
165
- logger.info("Converted %d messages successfully", len(converted_messages))
166
- return converted_messages
167
-
168
- def _extract_ai_message_content(
169
- self, msg: AIMessage
170
- ) -> tuple[list[MessageContentText], list[ToolCall]]:
171
- text_content = []
172
-
173
- if msg.content:
174
- match msg.content:
175
- case str(text):
176
- text_content.append(MessageContentText(type="text", text=text))
177
- case list():
178
- text_content.extend(
179
- MessageContentText(type="text", text=content.get("text"))
180
- for content in msg.content
181
- if isinstance(content, dict) and content.get("type") == "text"
182
- )
183
-
184
- tool_calls = []
185
- if msg.tool_calls:
186
- logger.debug("Extracting %d tool calls from AIMessage", len(msg.tool_calls))
187
- tool_calls.extend(
188
- ToolCall(
189
- id=tool_call.get("id"), # type: ignore[union-attr]
190
- name=tool_call.get("name"), # type: ignore[union-attr]
191
- arguments=tool_call.get("args"), # type: ignore[union-attr]
192
- )
193
- for tool_call in msg.tool_calls
194
- )
195
-
196
- return text_content, tool_calls
197
-
198
- async def ainvoke(
199
- self,
200
- input: list[AgentMessage],
201
- session_id: str | None,
202
- ) -> AgentInvocationResponse:
203
- """
204
- Asynchronous invocation method - invokes the LangGraph agent and converts the output to List[AgentMessage]
205
-
206
- Args:
207
- input: List[AgentMessage] containing user_message
208
- session_id: Optional conversation ID for maintaining context
209
-
210
- Returns:
211
- List[AgentMessage] or AgentTrace - converted messages from the agent's response or trace
212
- """
213
- logger.info(
214
- "ainvoke called (session_id=%s, input_messages=%d)", session_id, len(input)
215
- )
216
-
217
- try:
218
- agent_input = self._prepare_input(input)
219
- config = {"configurable": {"thread_id": session_id}} if session_id else {}
220
-
221
- if self.tracer_provider:
222
- logger.debug("Invoking with tracing enabled")
223
- return await self._ainvoke_with_tracing(agent_input, config)
224
-
225
- logger.debug("Invoking without tracing")
226
- return await self._ainvoke_without_tracing(agent_input, config)
227
-
228
- except ValueError:
229
- logger.exception("ValueError during ainvoke")
230
- raise
231
- except Exception:
232
- logger.exception("Unexpected error during ainvoke")
233
- raise
234
-
235
- async def _ainvoke_with_tracing(
236
- self,
237
- agent_input: dict[str, list[HumanMessage]],
238
- config: dict,
239
- ) -> AgentInvocationResponse:
240
- """Execute ainvoke with tracing enabled."""
241
- adapter_trace_id = f"{QURAITE_ADAPTER_TRACE_PREFIX}-{uuid.uuid4()}"
242
- logger.debug("Starting traced invocation (trace_id=%s)", adapter_trace_id)
243
-
244
- with self.tracer.start_as_current_span(name=adapter_trace_id):
245
- result = await self.agent_graph.ainvoke(agent_input, config=config)
246
-
247
- trace_readable_spans = self.quraite_span_exporter.get_trace_by_testcase(
248
- adapter_trace_id
249
- )
250
-
251
- if trace_readable_spans:
252
- logger.info("Retrieved %d spans from trace", len(trace_readable_spans))
253
- agent_trace = AgentTrace(
254
- spans=[
255
- AgentSpan.from_readable_oi_span(span)
256
- for span in trace_readable_spans
257
- ]
258
- )
259
-
260
- trajectory = agent_trace.to_agent_trajectory(framework=Framework.LANGGRAPH)
261
- logger.debug("Generated trajectory with %d messages", len(trajectory))
262
-
263
- return AgentInvocationResponse(
264
- agent_trace=agent_trace,
265
- agent_trajectory=trajectory,
266
- )
267
-
268
- logger.warning("No trace spans found for trace_id=%s", adapter_trace_id)
269
- return AgentInvocationResponse()
270
-
271
- async def _ainvoke_without_tracing(
272
- self,
273
- agent_input: dict[str, list[HumanMessage]],
274
- config: dict,
275
- ) -> AgentInvocationResponse:
276
- """Execute ainvoke without tracing."""
277
- logger.debug("Starting non-traced invocation")
278
- agent_messages = []
279
-
280
- try:
281
- async for event in self.agent_graph.astream(agent_input, config=config):
282
- logger.debug(
283
- "Received stream event with %d values", len(event.values())
284
- )
285
- for result in event.values():
286
- if messages := result.get("messages"):
287
- logger.debug("Processing %d messages from event", len(messages))
288
- agent_messages.extend(messages)
289
-
290
- logger.info(
291
- "Streaming complete, received %d total messages", len(agent_messages)
292
- )
293
-
294
- agent_trajectory = self._convert_langchain_messages_to_quraite_messages(
295
- agent_messages
296
- )
297
-
298
- return AgentInvocationResponse(
299
- agent_trajectory=agent_trajectory,
300
- )
301
-
302
- except ValueError:
303
- logger.exception("Error converting messages to List[AgentMessage]")
304
- return AgentInvocationResponse()
1
+ import json
2
+ from typing import Optional
3
+
4
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
5
+ from langgraph.graph.state import CompiledStateGraph
6
+ from opentelemetry.trace import TracerProvider
7
+
8
+ from quraite.adapters.base import BaseAdapter
9
+ from quraite.logger import get_logger
10
+ from quraite.schema.message import AgentMessage, AssistantMessage, MessageContentText
11
+ from quraite.schema.message import SystemMessage as QuraiteSystemMessage
12
+ from quraite.schema.message import ToolCall
13
+ from quraite.schema.message import ToolMessage as QuraiteToolMessage
14
+ from quraite.schema.message import UserMessage
15
+ from quraite.schema.response import AgentInvocationResponse
16
+ from quraite.tracing.constants import Framework
17
+ from quraite.tracing.trace import AgentSpan, AgentTrace
18
+
19
+ LangchainMessage = HumanMessage | SystemMessage | AIMessage | ToolMessage
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class LangchainAdapter(BaseAdapter):
25
+ """
26
+ LangChain adapter wrapper that converts any LangChain agent
27
+ to a standardized callable interface (invoke) and converts the output to List[AgentMessage].
28
+
29
+ This class wraps any LangChain CompiledGraph and provides:
30
+ - Synchronous invocation via invoke()
31
+ - Asynchronous invocation via ainvoke()
32
+ - Automatic conversion to List[AgentMessage] format
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ agent_graph: CompiledStateGraph,
38
+ agent_name: str = "LangChain Agent",
39
+ tracer_provider: Optional[TracerProvider] = None,
40
+ ):
41
+ """
42
+ Initialize with a pre-configured LangChain agent
43
+
44
+ Args:
45
+ agent_graph: Any CompiledGraph from LangChain (must have invoke/ainvoke methods)
46
+ agent_name: Name of the agent for trajectory metadata
47
+ """
48
+ logger.debug("Initializing LangchainAdapter with agent_name=%s", agent_name)
49
+ self.agent_graph = agent_graph
50
+ self.agent_name = agent_name
51
+ self._init_tracing(tracer_provider, required=False)
52
+ logger.info(
53
+ "LangchainAdapter initialized successfully (tracing_enabled=%s)",
54
+ bool(tracer_provider),
55
+ )
56
+
57
+ def _prepare_input(
58
+ self, input: list[AgentMessage]
59
+ ) -> dict[str, list[HumanMessage]]:
60
+ """
61
+ Prepare input for LangChain agent from List[Message].
62
+
63
+ Args:
64
+ input: List[AgentMessage] containing user_message
65
+
66
+ Returns:
67
+ Dictionary with 'messages' key containing the prepared UserMessage
68
+ """
69
+ logger.debug("Preparing input from %d messages", len(input))
70
+
71
+ if not input or input[-1].role != "user":
72
+ logger.error(
73
+ "Invalid input: no user message found (input_length=%d)", len(input)
74
+ )
75
+ raise ValueError("No user message found in the input")
76
+
77
+ last_user_message = input[-1]
78
+
79
+ if not last_user_message.content:
80
+ logger.error("User message has no content")
81
+ raise ValueError("User message has no content")
82
+
83
+ text_content = next(
84
+ (
85
+ content_item.text
86
+ for content_item in last_user_message.content
87
+ if content_item.type == "text" and content_item.text
88
+ ),
89
+ None,
90
+ )
91
+
92
+ if not text_content:
93
+ logger.error("No text content found in user message")
94
+ raise ValueError("No text content found in user message")
95
+
96
+ logger.debug("Prepared input with text_content length=%d", len(text_content))
97
+ return {"messages": [HumanMessage(content=text_content)]}
98
+
99
+ def _convert_langchain_messages_to_quraite_messages(
100
+ self,
101
+ messages: list[LangchainMessage],
102
+ ) -> list[AgentMessage]:
103
+ logger.debug(
104
+ "Converting %d langchain messages to quraite format", len(messages)
105
+ )
106
+ converted_messages: list[AgentMessage] = []
107
+
108
+ for idx, msg in enumerate(messages):
109
+ match msg:
110
+ case SystemMessage():
111
+ logger.debug("Converting SystemMessage at index %d", idx)
112
+ converted_messages.append(
113
+ QuraiteSystemMessage(
114
+ content=[MessageContentText(type="text", text=msg.content)]
115
+ )
116
+ )
117
+
118
+ case HumanMessage():
119
+ logger.debug("Converting HumanMessage at index %d", idx)
120
+ converted_messages.append(
121
+ UserMessage(
122
+ content=[MessageContentText(type="text", text=msg.content)]
123
+ )
124
+ )
125
+
126
+ case AIMessage():
127
+ logger.debug(
128
+ "Converting AIMessage at index %d (has_tool_calls=%s)",
129
+ idx,
130
+ bool(msg.tool_calls),
131
+ )
132
+ text_content, tool_calls = self._extract_ai_message_content(msg)
133
+ converted_messages.append(
134
+ AssistantMessage(
135
+ content=text_content if text_content else None,
136
+ tool_calls=tool_calls if tool_calls else None,
137
+ )
138
+ )
139
+
140
+ case ToolMessage():
141
+ logger.debug(
142
+ "Converting ToolMessage at index %d (tool_call_id=%s)",
143
+ idx,
144
+ msg.tool_call_id,
145
+ )
146
+ if not msg.content:
147
+ tool_message_content = ""
148
+ elif isinstance(msg.content, str):
149
+ tool_message_content = msg.content
150
+ else:
151
+ tool_message_content = json.dumps(msg.content)
152
+
153
+ converted_messages.append(
154
+ QuraiteToolMessage(
155
+ tool_call_id=msg.tool_call_id,
156
+ content=[
157
+ MessageContentText(
158
+ type="text", text=tool_message_content
159
+ )
160
+ ],
161
+ )
162
+ )
163
+
164
+ logger.info("Converted %d messages successfully", len(converted_messages))
165
+ return converted_messages
166
+
167
+ def _extract_ai_message_content(
168
+ self, msg: AIMessage
169
+ ) -> tuple[list[MessageContentText], list[ToolCall]]:
170
+ text_content = []
171
+
172
+ if msg.content:
173
+ match msg.content:
174
+ case str(text):
175
+ text_content.append(MessageContentText(type="text", text=text))
176
+ case list():
177
+ text_content.extend(
178
+ MessageContentText(type="text", text=content.get("text"))
179
+ for content in msg.content
180
+ if isinstance(content, dict) and content.get("type") == "text"
181
+ )
182
+
183
+ tool_calls = []
184
+ if msg.tool_calls:
185
+ logger.debug("Extracting %d tool calls from AIMessage", len(msg.tool_calls))
186
+ tool_calls.extend(
187
+ ToolCall(
188
+ id=tool_call.get("id"), # type: ignore[union-attr]
189
+ name=tool_call.get("name"), # type: ignore[union-attr]
190
+ arguments=tool_call.get("args"), # type: ignore[union-attr]
191
+ )
192
+ for tool_call in msg.tool_calls
193
+ )
194
+
195
+ return text_content, tool_calls
196
+
197
+ async def ainvoke(
198
+ self,
199
+ input: list[AgentMessage],
200
+ session_id: str | None,
201
+ ) -> AgentInvocationResponse:
202
+ """
203
+ Asynchronous invocation method - invokes the LangChain agent and converts the output to List[AgentMessage]
204
+
205
+ Args:
206
+ input: List[AgentMessage] containing user_message
207
+ session_id: Optional conversation ID for maintaining context
208
+
209
+ Returns:
210
+ List[AgentMessage] or AgentTrace - converted messages from the agent's response or trace
211
+ """
212
+ logger.info(
213
+ "ainvoke called (session_id=%s, input_messages=%d)", session_id, len(input)
214
+ )
215
+
216
+ try:
217
+ agent_input = self._prepare_input(input)
218
+ config = {"configurable": {"thread_id": session_id}} if session_id else {}
219
+
220
+ if self.tracer_provider:
221
+ logger.debug("Invoking with tracing enabled")
222
+ return await self._ainvoke_with_tracing(agent_input, config)
223
+
224
+ logger.debug("Invoking without tracing")
225
+ return await self._ainvoke_without_tracing(agent_input, config)
226
+
227
+ except ValueError:
228
+ logger.exception("ValueError during ainvoke")
229
+ raise
230
+ except Exception:
231
+ logger.exception("Unexpected error during ainvoke")
232
+ raise
233
+
234
+ async def _ainvoke_with_tracing(
235
+ self,
236
+ agent_input: dict[str, list[HumanMessage]],
237
+ config: dict,
238
+ ) -> AgentInvocationResponse:
239
+ """Execute ainvoke with tracing enabled."""
240
+ with self.tracer.start_as_current_span("langchain_invocation") as span:
241
+ trace_id = span.get_span_context().trace_id
242
+ logger.debug(
243
+ "Starting LangChain traced invocation (trace_id=%s)",
244
+ trace_id,
245
+ )
246
+ _ = await self.agent_graph.ainvoke(agent_input, config=config)
247
+
248
+ trace_readable_spans = self.quraite_span_exporter.get_spans_by_trace_id(
249
+ trace_id
250
+ )
251
+
252
+ if trace_readable_spans:
253
+ logger.info("Retrieved %d spans from trace", len(trace_readable_spans))
254
+ agent_trace = AgentTrace(
255
+ spans=[
256
+ AgentSpan.from_readable_oi_span(span)
257
+ for span in trace_readable_spans
258
+ ]
259
+ )
260
+
261
+ trajectory = agent_trace.to_agent_trajectory(framework=Framework.LANGCHAIN)
262
+ logger.debug("Generated trajectory with %d messages", len(trajectory))
263
+
264
+ return AgentInvocationResponse(
265
+ agent_trace=agent_trace,
266
+ agent_trajectory=trajectory,
267
+ )
268
+
269
+ logger.warning("No trace spans found for trace_id=%s", trace_id)
270
+ return AgentInvocationResponse()
271
+
272
+ async def _ainvoke_without_tracing(
273
+ self,
274
+ agent_input: dict[str, list[HumanMessage]],
275
+ config: dict,
276
+ ) -> AgentInvocationResponse:
277
+ """Execute ainvoke without tracing."""
278
+ logger.debug("Starting non-traced invocation")
279
+ agent_messages = []
280
+
281
+ try:
282
+ async for event in self.agent_graph.astream(agent_input, config=config):
283
+ logger.debug(
284
+ "Received stream event with %d values", len(event.values())
285
+ )
286
+ for result in event.values():
287
+ if messages := result.get("messages"):
288
+ logger.debug("Processing %d messages from event", len(messages))
289
+ agent_messages.extend(messages)
290
+
291
+ logger.info(
292
+ "Streaming complete, received %d total messages", len(agent_messages)
293
+ )
294
+
295
+ agent_trajectory = self._convert_langchain_messages_to_quraite_messages(
296
+ agent_messages
297
+ )
298
+
299
+ return AgentInvocationResponse(
300
+ agent_trajectory=agent_trajectory,
301
+ )
302
+
303
+ except ValueError:
304
+ logger.exception("Error converting messages to List[AgentMessage]")
305
+ return AgentInvocationResponse()