quraite 0.0.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 -0
  2. quraite/adapters/__init__.py +134 -0
  3. quraite/adapters/agno_adapter.py +159 -0
  4. quraite/adapters/base.py +123 -0
  5. quraite/adapters/bedrock_agents_adapter.py +343 -0
  6. quraite/adapters/flowise_adapter.py +275 -0
  7. quraite/adapters/google_adk_adapter.py +209 -0
  8. quraite/adapters/http_adapter.py +239 -0
  9. quraite/adapters/langflow_adapter.py +192 -0
  10. quraite/adapters/langgraph_adapter.py +304 -0
  11. quraite/adapters/langgraph_server_adapter.py +252 -0
  12. quraite/adapters/n8n_adapter.py +220 -0
  13. quraite/adapters/openai_agents_adapter.py +269 -0
  14. quraite/adapters/pydantic_ai_adapter.py +312 -0
  15. quraite/adapters/smolagents_adapter.py +152 -0
  16. quraite/logger.py +62 -0
  17. quraite/schema/__init__.py +0 -0
  18. quraite/schema/message.py +54 -0
  19. quraite/schema/response.py +16 -0
  20. quraite/serve/__init__.py +1 -0
  21. quraite/serve/cloudflared.py +210 -0
  22. quraite/serve/local_agent.py +360 -0
  23. quraite/traces/traces_adk_openinference.json +379 -0
  24. quraite/traces/traces_agno_multi_agent.json +669 -0
  25. quraite/traces/traces_agno_openinference.json +321 -0
  26. quraite/traces/traces_crewai_openinference.json +155 -0
  27. quraite/traces/traces_langgraph_openinference.json +349 -0
  28. quraite/traces/traces_langgraph_openinference_multi_agent.json +2705 -0
  29. quraite/traces/traces_langgraph_traceloop.json +510 -0
  30. quraite/traces/traces_openai_agents_multi_agent_1.json +402 -0
  31. quraite/traces/traces_openai_agents_openinference.json +341 -0
  32. quraite/traces/traces_pydantic_openinference.json +286 -0
  33. quraite/traces/traces_pydantic_openinference_multi_agent_1.json +399 -0
  34. quraite/traces/traces_pydantic_openinference_multi_agent_2.json +398 -0
  35. quraite/traces/traces_smol_agents_openinference.json +397 -0
  36. quraite/traces/traces_smol_agents_tool_calling_openinference.json +704 -0
  37. quraite/tracing/__init__.py +24 -0
  38. quraite/tracing/constants.py +16 -0
  39. quraite/tracing/span_exporter.py +115 -0
  40. quraite/tracing/span_processor.py +49 -0
  41. quraite/tracing/tool_extractors.py +290 -0
  42. quraite/tracing/trace.py +494 -0
  43. quraite/tracing/types.py +179 -0
  44. quraite/tracing/utils.py +170 -0
  45. quraite/utils/__init__.py +0 -0
  46. quraite/utils/json_utils.py +269 -0
  47. quraite-0.0.1.dist-info/METADATA +44 -0
  48. quraite-0.0.1.dist-info/RECORD +49 -0
  49. quraite-0.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,209 @@
1
+ import uuid
2
+ from typing import List, Union
3
+
4
+ from google.adk.agents import Agent
5
+ from google.adk.apps.app import App
6
+ from google.adk.errors.already_exists_error import AlreadyExistsError
7
+ from google.adk.runners import Runner
8
+ from google.adk.sessions import BaseSessionService, InMemorySessionService
9
+ from google.genai import types
10
+ from opentelemetry.trace import TracerProvider
11
+
12
+ from quraite.adapters.base import BaseAdapter
13
+ from quraite.logger import get_logger
14
+ from quraite.schema.message import AgentMessage
15
+ from quraite.schema.response import AgentInvocationResponse
16
+ from quraite.tracing.constants import QURAITE_ADAPTER_TRACE_PREFIX, Framework
17
+ from quraite.tracing.trace import AgentSpan, AgentTrace
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class GoogleADKAdapter(BaseAdapter):
23
+ """
24
+ Google ADK adapter wrapper that converts any Google ADK agent
25
+ to a standardized callable interface (ainvoke) with tracing support.
26
+
27
+ This class wraps any Google ADK Agent and provides:
28
+ - Asynchronous invocation via ainvoke()
29
+ - OpenTelemetry tracing integration
30
+ - Session management for multi-turn conversations
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ agent: Agent,
36
+ agent_name: str = "Google ADK Agent",
37
+ tracer_provider: TracerProvider = None,
38
+ app_name: str = "google_adk_agent",
39
+ user_id: str = str(uuid.uuid4()),
40
+ session_service: BaseSessionService = InMemorySessionService(),
41
+ ):
42
+ """
43
+ Initialize with a pre-configured Google ADK agent
44
+
45
+ Args:
46
+ agent: A Google ADK Agent instance
47
+ app_name: Application name for ADK runner
48
+ agent_name: Name of the agent for trajectory metadata
49
+ tracer_provider: TracerProvider for tracing (required)
50
+ """
51
+ logger.debug(
52
+ "Initializing GoogleADKAdapter (agent_name=%s, app_name=%s)",
53
+ agent_name,
54
+ app_name,
55
+ )
56
+ self._init_tracing(tracer_provider, required=True)
57
+
58
+ self.agent: Agent = agent
59
+ self.app_name = app_name
60
+ self.agent_name = agent_name
61
+ self.session_service = session_service
62
+ self.user_id = user_id
63
+ self.app = App(
64
+ name=app_name,
65
+ root_agent=agent,
66
+ )
67
+ self.runner = Runner(
68
+ app=self.app,
69
+ session_service=session_service,
70
+ )
71
+ logger.info("GoogleADKAdapter initialized successfully")
72
+
73
+ def _prepare_input(self, input: List[AgentMessage]) -> str:
74
+ """
75
+ Prepare input for Google ADK agent from List[Message].
76
+
77
+ Args:
78
+ input: List[Message] containing user_message
79
+
80
+ Returns:
81
+ str: User message text
82
+ """
83
+ logger.debug("Preparing Google ADK input from %d messages", len(input))
84
+ if not input or input[-1].role != "user":
85
+ logger.error("Google ADK input missing user message")
86
+ raise ValueError("No user message found in the input")
87
+
88
+ last_user_message = input[-1]
89
+ # Check if content list is not empty and has text
90
+ if not last_user_message.content:
91
+ logger.error("Google ADK user message missing content")
92
+ raise ValueError("User message has no content")
93
+
94
+ # Find the first text content item
95
+ text_content = None
96
+ for content_item in last_user_message.content:
97
+ if content_item.type == "text" and content_item.text:
98
+ text_content = content_item.text
99
+ break
100
+
101
+ if not text_content:
102
+ logger.error("Google ADK user message missing text content")
103
+ raise ValueError("No text content found in user message")
104
+
105
+ logger.debug("Prepared Google ADK input (text_length=%d)", len(text_content))
106
+ return text_content
107
+
108
+ async def ainvoke(
109
+ self,
110
+ input: List[AgentMessage],
111
+ session_id: Union[str, None] = None,
112
+ ) -> AgentInvocationResponse:
113
+ """
114
+ Asynchronous invocation method - invokes the Google ADK agent with tracing
115
+
116
+ Args:
117
+ input: List[AgentMessage] containing user_message
118
+ session_id: Optional conversation ID for maintaining context
119
+
120
+ Returns:
121
+ AgentInvocationResponse - response containing agent trace, trajectory, and final response.
122
+ """
123
+ logger.info(
124
+ "Google ADK ainvoke called (session_id=%s, input_messages=%d)",
125
+ session_id,
126
+ len(input),
127
+ )
128
+ agent_input = self._prepare_input(input)
129
+ session_id = session_id or str(uuid.uuid4())
130
+
131
+ try:
132
+ return await self._ainvoke_with_tracing(agent_input, session_id)
133
+
134
+ except Exception as e:
135
+ logger.exception("Error invoking Google ADK agent")
136
+ raise RuntimeError(f"Error invoking Google ADK agent: {e}") from e
137
+
138
+ async def _ainvoke_with_tracing(
139
+ self,
140
+ agent_input: str,
141
+ session_id: str,
142
+ ) -> AgentInvocationResponse:
143
+ """Execute ainvoke with tracing enabled."""
144
+ adapter_trace_id = f"{QURAITE_ADAPTER_TRACE_PREFIX}-{uuid.uuid4()}"
145
+ logger.debug(
146
+ "Starting Google ADK traced invocation (trace_id=%s, session_id=%s)",
147
+ adapter_trace_id,
148
+ session_id,
149
+ )
150
+
151
+ with self.tracer.start_as_current_span(name=adapter_trace_id):
152
+ # Create session if it doesn't exist
153
+ try:
154
+ await self.session_service.create_session(
155
+ app_name=self.app_name,
156
+ user_id=self.user_id,
157
+ session_id=session_id,
158
+ )
159
+ except AlreadyExistsError:
160
+ logger.info("Session already exists: %s", session_id)
161
+ except Exception as e:
162
+ logger.exception("Error creating Google ADK session")
163
+ raise RuntimeError(f"Error creating session: {e}") from e
164
+
165
+ # Create content for ADK
166
+ content = types.Content(
167
+ role="user",
168
+ parts=[types.Part(text=agent_input)],
169
+ )
170
+
171
+ # Run async and consume events
172
+ events = self.runner.run_async(
173
+ new_message=content,
174
+ user_id=self.user_id,
175
+ session_id=session_id,
176
+ )
177
+
178
+ # Consume all events (tracing captures everything)
179
+ async for event in events:
180
+ pass # Just consume events, tracing handles capture
181
+
182
+ # Get trace spans
183
+ trace_readable_spans = self.quraite_span_exporter.get_trace_by_testcase(
184
+ adapter_trace_id
185
+ )
186
+
187
+ if trace_readable_spans:
188
+ agent_trace = AgentTrace(
189
+ spans=[
190
+ AgentSpan.from_readable_oi_span(span)
191
+ for span in trace_readable_spans
192
+ ],
193
+ )
194
+ logger.info(
195
+ "Google ADK trace collected %d spans for trace_id=%s",
196
+ len(trace_readable_spans),
197
+ adapter_trace_id,
198
+ )
199
+ else:
200
+ logger.warning(
201
+ "No spans exported for Google ADK trace_id=%s", adapter_trace_id
202
+ )
203
+
204
+ return AgentInvocationResponse(
205
+ agent_trace=agent_trace,
206
+ agent_trajectory=agent_trace.to_agent_trajectory(
207
+ framework=Framework.GOOGLE_ADK
208
+ ),
209
+ )
@@ -0,0 +1,239 @@
1
+ """
2
+ HTTP Adapter - Connects to remote agent servers via HTTP.
3
+
4
+ This module provides a BaseAdapter implementation that calls remote agent
5
+ servers via HTTP endpoints, enabling distributed agent evaluation.
6
+ """
7
+
8
+ from typing import Any, Dict, List, Optional, Union
9
+
10
+ import httpx
11
+
12
+ from quraite.adapters.base import BaseAdapter
13
+ from quraite.logger import get_logger
14
+ from quraite.schema.message import AgentMessage
15
+ from quraite.schema.response import AgentInvocationResponse
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class HttpAdapter(BaseAdapter):
21
+ """
22
+ HTTP adapter client that communicates with agent servers via HTTP.
23
+
24
+ This class implements the BaseAdapter interface and forwards adapter
25
+ requests to a HTTP agent server, handling serialization, network errors,
26
+ and retries.
27
+
28
+ Args:
29
+ url: The full URL of the remote agent endpoint (e.g., "http://localhost:8000/v1/agents/completions")
30
+ headers: Optional dictionary of HTTP headers to include in requests
31
+ timeout: Request timeout in seconds (default: 60)
32
+ max_retries: Maximum number of retry attempts (default: 3)
33
+ retry_delay: Initial retry delay in seconds (default: 1)
34
+
35
+ Example:
36
+ ```python
37
+ remote_agent = HttpAdapter(
38
+ url="http://localhost:8000/v1/agents/completions",
39
+ headers={"Authorization": "Bearer secret_key"}
40
+ )
41
+
42
+ result = remote_agent.ainvoke(
43
+ input=[UserMessage(...)],
44
+ session_id="conv_123"
45
+ )
46
+ ```
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ url: str,
52
+ headers: Optional[Dict[str, str]] = None,
53
+ timeout: float = 60.0,
54
+ max_retries: int = 3,
55
+ retry_delay: float = 1.0,
56
+ ):
57
+ """Initialize the HTTP adapter client."""
58
+ self.url = url.rstrip("/")
59
+ self.headers = headers or {}
60
+ self.timeout = timeout
61
+ self.max_retries = max_retries
62
+ self.retry_delay = retry_delay
63
+
64
+ # Create HTTP client with user-provided headers
65
+ # Always include Content-Type if not provided
66
+ client_headers = {"Content-Type": "application/json"}
67
+ client_headers.update(self.headers)
68
+
69
+ self.async_client = httpx.AsyncClient(
70
+ headers=client_headers,
71
+ timeout=self.timeout,
72
+ )
73
+ logger.info(
74
+ "HttpAdapter initialized (url=%s, timeout=%s, max_retries=%s)",
75
+ self.url,
76
+ self.timeout,
77
+ self.max_retries,
78
+ )
79
+
80
+ def _serialize_request(
81
+ self,
82
+ input: List[AgentMessage],
83
+ session_id: Union[str, None],
84
+ ) -> Dict[str, Any]:
85
+ """
86
+ Serialize invocation request to JSON-compatible dict.
87
+
88
+ Args:
89
+ input: List[AgentMessage] containing user_message
90
+ session_id: Optional conversation ID for maintaining context
91
+
92
+ Returns:
93
+ Dictionary ready for JSON serialization
94
+ """
95
+ logger.debug(
96
+ "Serializing HTTP request (messages=%d, session_id=%s)",
97
+ len(input),
98
+ session_id,
99
+ )
100
+ return {
101
+ "input": [msg.model_dump(mode="json") for msg in input],
102
+ "session_id": session_id,
103
+ }
104
+
105
+ async def _make_request_with_retry_async(
106
+ self,
107
+ method: str,
108
+ url: str,
109
+ payload: Dict[str, Any],
110
+ ) -> Dict[str, Any]:
111
+ """
112
+ Make HTTP request with retry logic (asynchronous).
113
+
114
+ Args:
115
+ method: HTTP method (POST)
116
+ url: Full URL of the endpoint
117
+ payload: Request payload
118
+
119
+ Returns:
120
+ Response data as dictionary
121
+
122
+ Raises:
123
+ httpx.HTTPError: If all retries fail
124
+ ValueError: If response format is invalid
125
+ """
126
+ last_exception = None
127
+
128
+ for attempt in range(self.max_retries):
129
+ try:
130
+ response = await self.async_client.request(
131
+ method=method,
132
+ url=url,
133
+ json=payload,
134
+ )
135
+ response.raise_for_status()
136
+ logger.info(
137
+ "HTTP request succeeded (status=%s, attempt=%d)",
138
+ response.status_code,
139
+ attempt + 1,
140
+ )
141
+ return response.json()
142
+
143
+ except httpx.HTTPStatusError as e:
144
+ # Don't retry on 4xx errors (client errors)
145
+ if 400 <= e.response.status_code < 500:
146
+ error_detail = e.response.text
147
+ logger.error(
148
+ "HTTP adapter received 4xx response (status=%s, detail=%s)",
149
+ e.response.status_code,
150
+ error_detail,
151
+ )
152
+ raise ValueError(
153
+ f"Agent server error ({e.response.status_code}): {error_detail}"
154
+ ) from e
155
+
156
+ # Retry on 5xx errors
157
+ last_exception = e
158
+ if attempt < self.max_retries - 1:
159
+ delay = self.retry_delay * (2**attempt)
160
+ logger.warning(
161
+ "HTTP adapter retrying after server error (status=%s, retry_in=%.2fs)",
162
+ e.response.status_code,
163
+ delay,
164
+ )
165
+ await self._async_sleep(delay)
166
+
167
+ except (httpx.ConnectError, httpx.TimeoutException) as e:
168
+ # Retry on network errors
169
+ last_exception = e
170
+ if attempt < self.max_retries - 1:
171
+ delay = self.retry_delay * (2**attempt)
172
+ logger.warning(
173
+ "HTTP adapter retrying after network error (retry_in=%.2fs)",
174
+ delay,
175
+ )
176
+ await self._async_sleep(delay)
177
+
178
+ # All retries failed
179
+ logger.exception(
180
+ "HTTP adapter failed after %d attempts (url=%s)",
181
+ self.max_retries,
182
+ self.url,
183
+ )
184
+ raise RuntimeError(
185
+ f"Failed to connect to agent server at {self.url} after "
186
+ f"{self.max_retries} attempts. Last error: {last_exception}"
187
+ ) from last_exception
188
+
189
+ @staticmethod
190
+ async def _async_sleep(seconds: float):
191
+ """Helper for async sleep."""
192
+ import asyncio
193
+
194
+ await asyncio.sleep(seconds)
195
+
196
+ async def ainvoke(
197
+ self,
198
+ input: List[AgentMessage],
199
+ session_id: Union[str, None],
200
+ ) -> AgentInvocationResponse:
201
+ """
202
+ Asynchronously invoke the HTTP agent.
203
+
204
+ Args:
205
+ input: List[AgentMessage] containing user_message
206
+ session_id: Optional conversation ID for maintaining context
207
+
208
+ Returns:
209
+ AgentInvocationResponse: Response containing agent trace, trajectory, and final response.
210
+ """
211
+ logger.info(
212
+ "HTTP ainvoke called (session_id=%s, input_messages=%d)",
213
+ session_id,
214
+ len(input),
215
+ )
216
+ payload = self._serialize_request(input, session_id)
217
+ response_data = await self._make_request_with_retry_async(
218
+ method="POST", url=self.url, payload=payload
219
+ )
220
+ logger.debug(
221
+ "HTTP adapter received response keys: %s", list(response_data.keys())
222
+ )
223
+
224
+ return AgentInvocationResponse.model_validate(
225
+ response_data.get("agent_response", {})
226
+ )
227
+
228
+ async def aclose(self):
229
+ """Close async HTTP client."""
230
+ await self.async_client.aclose()
231
+ logger.debug("HTTP adapter client closed")
232
+
233
+ async def __aenter__(self):
234
+ """Async context manager entry."""
235
+ return self
236
+
237
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
238
+ """Async context manager exit."""
239
+ await self.aclose()
@@ -0,0 +1,192 @@
1
+ import asyncio
2
+ import json
3
+ import uuid
4
+ from typing import Any, Dict, List, Union
5
+
6
+ import aiohttp
7
+
8
+ from quraite.adapters.base import BaseAdapter
9
+ from quraite.logger import get_logger
10
+ from quraite.schema.message import (
11
+ AgentMessage,
12
+ AssistantMessage,
13
+ MessageContentText,
14
+ ToolCall,
15
+ ToolMessage,
16
+ )
17
+ from quraite.schema.response import AgentInvocationResponse
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class LangflowAdapter(BaseAdapter):
23
+ def __init__(self, api_url: str, x_api_key: str, timeout: int = 60):
24
+ self.api_url = api_url
25
+ self.x_api_key = x_api_key
26
+ self.headers = {"Content-Type": "application/json", "x-api-key": self.x_api_key}
27
+ self.timeout = timeout
28
+ logger.info(
29
+ "LangflowAdapter initialized (api_url=%s, timeout=%s)",
30
+ self.api_url,
31
+ timeout,
32
+ )
33
+
34
+ def _convert_api_output_to_messages(
35
+ self,
36
+ response: Dict[str, Any],
37
+ ) -> List[AgentMessage]:
38
+ logger.debug(
39
+ "Converting Langflow response (root_keys=%s)",
40
+ list(response.keys()),
41
+ )
42
+ content_blocks = response["outputs"][0]["outputs"][0]["results"]["message"][
43
+ "content_blocks"
44
+ ]
45
+ contents = content_blocks[0]["contents"]
46
+
47
+ # Assume everything sequential.
48
+ ai_trajectory: List[AgentMessage] = []
49
+ for step in contents:
50
+ if step["type"] == "text":
51
+ if step["header"]["title"] == "Input":
52
+ continue
53
+ else:
54
+ ai_trajectory.append(
55
+ AssistantMessage(
56
+ content=[
57
+ MessageContentText(type="text", text=step["text"])
58
+ ],
59
+ )
60
+ )
61
+ elif step["type"] == "tool_use":
62
+ tool_id = str(uuid.uuid4())
63
+ tool_input = step.get("tool_input", {})
64
+ if not isinstance(tool_input, dict):
65
+ tool_input = {"value": tool_input}
66
+
67
+ # Create AssistantMessage with tool call
68
+ ai_trajectory.append(
69
+ AssistantMessage(
70
+ tool_calls=[
71
+ ToolCall(
72
+ id=tool_id,
73
+ name=step["name"],
74
+ arguments=tool_input,
75
+ )
76
+ ],
77
+ )
78
+ )
79
+ # Create ToolMessage with tool result
80
+ tool_output = step.get("output", "")
81
+ ai_trajectory.append(
82
+ ToolMessage(
83
+ tool_name=step["name"],
84
+ tool_call_id=tool_id,
85
+ content=[
86
+ MessageContentText(type="text", text=str(tool_output))
87
+ ],
88
+ )
89
+ )
90
+
91
+ logger.info(
92
+ "Converted Langflow response into %d trajectory messages",
93
+ len(ai_trajectory),
94
+ )
95
+ return ai_trajectory
96
+
97
+ def _prepare_input(self, input: List[AgentMessage]) -> str:
98
+ logger.debug("Preparing Langflow input from %d messages", len(input))
99
+ if not input or input[-1].role != "user":
100
+ logger.error("Langflow input missing user message")
101
+ raise ValueError("No user message found in the input")
102
+
103
+ last_user_message = input[-1]
104
+ if not last_user_message.content:
105
+ logger.error("Langflow input user message missing content")
106
+ raise ValueError("User message has no content")
107
+
108
+ text_content = None
109
+ for content_item in last_user_message.content:
110
+ if content_item.type == "text" and content_item.text:
111
+ text_content = content_item.text
112
+ break
113
+
114
+ if not text_content:
115
+ logger.error("Langflow input missing text content")
116
+ raise ValueError("No text content found in user message")
117
+
118
+ logger.debug("Prepared Langflow input (text_length=%d)", len(text_content))
119
+ return text_content
120
+
121
+ async def _aapi_call(
122
+ self,
123
+ query: str,
124
+ sessionId: str,
125
+ ) -> Dict[str, Any]:
126
+ payload = {
127
+ "output_type": "chat",
128
+ "input_type": "chat",
129
+ "input_value": query,
130
+ "session_id": sessionId,
131
+ }
132
+
133
+ async with aiohttp.ClientSession() as session:
134
+ try:
135
+ async with session.post(
136
+ self.api_url,
137
+ headers=self.headers,
138
+ json=payload,
139
+ timeout=aiohttp.ClientTimeout(total=self.timeout),
140
+ ) as response:
141
+ response.raise_for_status()
142
+ logger.info(
143
+ "Langflow API call succeeded (status=%s)", response.status
144
+ )
145
+ return await response.json()
146
+
147
+ except (aiohttp.ClientError, asyncio.TimeoutError) as e:
148
+ logger.exception("Langflow API request failed")
149
+ raise aiohttp.ClientError(f"Async API request failed: {str(e)}") from e
150
+
151
+ except json.JSONDecodeError as e:
152
+ logger.exception("Langflow API response decoding failed")
153
+ raise ValueError(f"Failed to decode JSON response: {e}") from e
154
+
155
+ async def ainvoke(
156
+ self,
157
+ input: List[AgentMessage],
158
+ session_id: Union[str, None],
159
+ ) -> AgentInvocationResponse:
160
+ """Asynchronous invocation method - invokes the Langflow agent and converts to List[AgentMessage]."""
161
+ logger.info(
162
+ "Langflow ainvoke called (session_id=%s, input_messages=%d)",
163
+ session_id,
164
+ len(input),
165
+ )
166
+ agent_input = self._prepare_input(input)
167
+
168
+ try:
169
+ agent_output = await self._aapi_call(
170
+ query=agent_input,
171
+ sessionId=session_id if session_id else uuid.uuid4(),
172
+ )
173
+ logger.debug(
174
+ "Langflow API returned payload with top-level keys: %s",
175
+ list(agent_output.keys()),
176
+ )
177
+ except Exception as e:
178
+ logger.exception("Error calling Langflow endpoint")
179
+ raise RuntimeError(f"Error calling langflow endpoint: {e}") from e
180
+
181
+ try:
182
+ agent_trajectory = self._convert_api_output_to_messages(agent_output)
183
+ logger.info(
184
+ "Langflow conversion produced %d trajectory messages",
185
+ len(agent_trajectory),
186
+ )
187
+ return AgentInvocationResponse(
188
+ agent_trajectory=agent_trajectory,
189
+ )
190
+ except Exception as e:
191
+ logger.exception("Error processing Langflow response")
192
+ raise RuntimeError(f"Error processing langflow response: {e}") from e