aip-agents-binary 0.5.25b9__py3-none-any.whl → 0.6.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.
Potentially problematic release.
This version of aip-agents-binary might be problematic. Click here for more details.
- aip_agents/agent/base_langgraph_agent.py +137 -68
- aip_agents/agent/base_langgraph_agent.pyi +3 -2
- aip_agents/agent/langgraph_react_agent.py +252 -16
- aip_agents/agent/langgraph_react_agent.pyi +40 -1
- aip_agents/examples/compare_streaming_client.py +2 -2
- aip_agents/examples/compare_streaming_server.py +1 -1
- aip_agents/examples/hello_world_ptc.py +51 -0
- aip_agents/examples/hello_world_ptc.pyi +5 -0
- aip_agents/examples/hello_world_tool_output_client.py +9 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +2 -2
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +1 -1
- aip_agents/guardrails/engines/base.py +6 -6
- aip_agents/mcp/client/connection_manager.py +36 -1
- aip_agents/mcp/client/connection_manager.pyi +3 -0
- aip_agents/mcp/client/persistent_session.py +318 -68
- aip_agents/mcp/client/persistent_session.pyi +9 -0
- aip_agents/mcp/client/transports.py +33 -2
- aip_agents/mcp/client/transports.pyi +9 -0
- aip_agents/ptc/__init__.py +48 -0
- aip_agents/ptc/__init__.pyi +10 -0
- aip_agents/ptc/doc_gen.py +122 -0
- aip_agents/ptc/doc_gen.pyi +40 -0
- aip_agents/ptc/exceptions.py +39 -0
- aip_agents/ptc/exceptions.pyi +22 -0
- aip_agents/ptc/executor.py +143 -0
- aip_agents/ptc/executor.pyi +73 -0
- aip_agents/ptc/mcp/__init__.py +45 -0
- aip_agents/ptc/mcp/__init__.pyi +7 -0
- aip_agents/ptc/mcp/sandbox_bridge.py +668 -0
- aip_agents/ptc/mcp/sandbox_bridge.pyi +47 -0
- aip_agents/ptc/mcp/templates/__init__.py +1 -0
- aip_agents/ptc/mcp/templates/__init__.pyi +0 -0
- aip_agents/ptc/mcp/templates/mcp_client.py.template +239 -0
- aip_agents/ptc/naming.py +184 -0
- aip_agents/ptc/naming.pyi +76 -0
- aip_agents/ptc/payload.py +26 -0
- aip_agents/ptc/payload.pyi +15 -0
- aip_agents/ptc/prompt_builder.py +571 -0
- aip_agents/ptc/prompt_builder.pyi +55 -0
- aip_agents/ptc/ptc_helper.py +16 -0
- aip_agents/ptc/ptc_helper.pyi +1 -0
- aip_agents/ptc/sandbox_bridge.py +58 -0
- aip_agents/ptc/sandbox_bridge.pyi +25 -0
- aip_agents/ptc/template_utils.py +33 -0
- aip_agents/ptc/template_utils.pyi +13 -0
- aip_agents/ptc/templates/__init__.py +1 -0
- aip_agents/ptc/templates/__init__.pyi +0 -0
- aip_agents/ptc/templates/ptc_helper.py.template +134 -0
- aip_agents/sandbox/__init__.py +43 -0
- aip_agents/sandbox/__init__.pyi +5 -0
- aip_agents/sandbox/defaults.py +9 -0
- aip_agents/sandbox/defaults.pyi +2 -0
- aip_agents/sandbox/e2b_runtime.py +267 -0
- aip_agents/sandbox/e2b_runtime.pyi +51 -0
- aip_agents/sandbox/template_builder.py +131 -0
- aip_agents/sandbox/template_builder.pyi +36 -0
- aip_agents/sandbox/types.py +24 -0
- aip_agents/sandbox/types.pyi +14 -0
- aip_agents/sandbox/validation.py +50 -0
- aip_agents/sandbox/validation.pyi +20 -0
- aip_agents/tools/__init__.py +2 -0
- aip_agents/tools/__init__.pyi +2 -1
- aip_agents/tools/browser_use/browser_use_tool.py +8 -0
- aip_agents/tools/browser_use/streaming.py +2 -0
- aip_agents/tools/execute_ptc_code.py +305 -0
- aip_agents/tools/execute_ptc_code.pyi +87 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +26 -1
- aip_agents/utils/langgraph/tool_output_management.py +80 -0
- aip_agents/utils/langgraph/tool_output_management.pyi +37 -0
- {aip_agents_binary-0.5.25b9.dist-info → aip_agents_binary-0.6.1.dist-info}/METADATA +51 -48
- {aip_agents_binary-0.5.25b9.dist-info → aip_agents_binary-0.6.1.dist-info}/RECORD +73 -27
- {aip_agents_binary-0.5.25b9.dist-info → aip_agents_binary-0.6.1.dist-info}/WHEEL +0 -0
- {aip_agents_binary-0.5.25b9.dist-info → aip_agents_binary-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -21,13 +21,13 @@ from concurrent.futures import Future
|
|
|
21
21
|
from contextlib import suppress
|
|
22
22
|
from contextvars import ContextVar
|
|
23
23
|
from dataclasses import dataclass
|
|
24
|
-
from typing import Annotated, Any
|
|
24
|
+
from typing import Annotated, Any, cast
|
|
25
25
|
|
|
26
26
|
from a2a.types import AgentCard
|
|
27
27
|
from aiostream import stream as astream
|
|
28
|
-
from gllm_core.event import EventEmitter
|
|
29
|
-
from gllm_core.event.handler import StreamEventHandler
|
|
30
|
-
from gllm_core.schema import Chunk
|
|
28
|
+
from gllm_core.event import EventEmitter # type: ignore[import-untyped]
|
|
29
|
+
from gllm_core.event.handler import StreamEventHandler # type: ignore[import-untyped]
|
|
30
|
+
from gllm_core.schema import Chunk # type: ignore[import-untyped]
|
|
31
31
|
from langchain_core.messages import AIMessage, BaseMessage, ToolMessage
|
|
32
32
|
from langchain_core.tools import BaseTool
|
|
33
33
|
from langgraph.graph import StateGraph
|
|
@@ -197,6 +197,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
197
197
|
self.enable_a2a_token_streaming = enable_a2a_token_streaming
|
|
198
198
|
self.event_emitter = event_emitter
|
|
199
199
|
self.checkpointer = checkpointer
|
|
200
|
+
self.tool_output_manager = None
|
|
200
201
|
|
|
201
202
|
self._mem0_client: Any | None = None
|
|
202
203
|
self.memory: BaseMemory | None = None
|
|
@@ -384,10 +385,13 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
384
385
|
try:
|
|
385
386
|
user_id = override_user_id or self.memory_agent_id
|
|
386
387
|
if hasattr(self.memory, MemoryMethod.SEARCH):
|
|
387
|
-
results = self.memory.search(
|
|
388
|
+
results = self.memory.search( # type: ignore[attr-defined]
|
|
389
|
+
query=query,
|
|
390
|
+
user_id=user_id,
|
|
391
|
+
limit=self.memory_retrieval_limit,
|
|
392
|
+
)
|
|
388
393
|
return results if isinstance(results, list) else []
|
|
389
|
-
|
|
390
|
-
return []
|
|
394
|
+
return []
|
|
391
395
|
except Exception as e: # noqa: BLE001
|
|
392
396
|
logger.debug(f"Memory: search ignored error: {e}")
|
|
393
397
|
return []
|
|
@@ -415,7 +419,11 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
415
419
|
future = save_async(user_text=str(user_text), ai_text=str(ai_text), user_id=user_id)
|
|
416
420
|
self._watch_memory_future(future, user_id)
|
|
417
421
|
elif hasattr(self.memory, MemoryMethod.SAVE_INTERACTION):
|
|
418
|
-
self.memory.save_interaction(
|
|
422
|
+
self.memory.save_interaction( # type: ignore[attr-defined]
|
|
423
|
+
user_text=str(user_text),
|
|
424
|
+
ai_text=str(ai_text),
|
|
425
|
+
user_id=user_id,
|
|
426
|
+
)
|
|
419
427
|
else:
|
|
420
428
|
logger.warning(
|
|
421
429
|
"Memory: save_interaction method NOT available on memory adapter "
|
|
@@ -560,7 +568,11 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
560
568
|
return
|
|
561
569
|
|
|
562
570
|
try:
|
|
563
|
-
tool
|
|
571
|
+
set_tool_config = getattr(tool, "set_tool_config", None)
|
|
572
|
+
if callable(set_tool_config):
|
|
573
|
+
set_tool_config(tool_config_data)
|
|
574
|
+
else:
|
|
575
|
+
raise AttributeError("set_tool_config not available")
|
|
564
576
|
logger.info(f"Agent '{self.name}': Configured tool '{tool.name}' with agent defaults: {tool_config_data}")
|
|
565
577
|
except Exception as e:
|
|
566
578
|
logger.warning(f"Agent '{self.name}': Failed to configure tool '{tool.name}': {e}")
|
|
@@ -598,7 +610,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
598
610
|
self._sanitize_tool_names()
|
|
599
611
|
try:
|
|
600
612
|
if self.state_schema:
|
|
601
|
-
graph_builder = StateGraph(self.state_schema)
|
|
613
|
+
graph_builder: StateGraph = StateGraph(self.state_schema)
|
|
602
614
|
else:
|
|
603
615
|
|
|
604
616
|
class DefaultAgentState(TypedDict):
|
|
@@ -715,7 +727,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
715
727
|
return None
|
|
716
728
|
last_item = list_state[-1]
|
|
717
729
|
if isinstance(last_item, AIMessage) and getattr(last_item, "content", None) is not None:
|
|
718
|
-
output_content = last_item.content
|
|
730
|
+
output_content = self._normalize_event_content(last_item.content)
|
|
719
731
|
elif isinstance(last_item, str):
|
|
720
732
|
output_content = last_item
|
|
721
733
|
return output_content
|
|
@@ -995,7 +1007,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
995
1007
|
key = self.thread_id_key or "thread_id"
|
|
996
1008
|
return configurable.get(key)
|
|
997
1009
|
|
|
998
|
-
def _process_langgraph_event(self, event:
|
|
1010
|
+
def _process_langgraph_event(self, event: Any) -> str | dict[str, Any] | A2AEvent | None:
|
|
999
1011
|
"""Process a single LangGraph streaming event.
|
|
1000
1012
|
|
|
1001
1013
|
Args:
|
|
@@ -1045,6 +1057,36 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1045
1057
|
|
|
1046
1058
|
return True
|
|
1047
1059
|
|
|
1060
|
+
@staticmethod
|
|
1061
|
+
def _normalize_usage_metadata(usage: Any | None) -> dict[str, Any] | None:
|
|
1062
|
+
"""Normalize usage metadata to a dictionary when possible.
|
|
1063
|
+
|
|
1064
|
+
Args:
|
|
1065
|
+
usage: Usage metadata from LangChain messages.
|
|
1066
|
+
|
|
1067
|
+
Returns:
|
|
1068
|
+
A dictionary copy when usage is available, otherwise None.
|
|
1069
|
+
"""
|
|
1070
|
+
if usage is None:
|
|
1071
|
+
return None
|
|
1072
|
+
if isinstance(usage, dict):
|
|
1073
|
+
return dict(usage)
|
|
1074
|
+
return cast(dict[str, Any], usage)
|
|
1075
|
+
|
|
1076
|
+
@staticmethod
|
|
1077
|
+
def _normalize_event_content(content: Any) -> str:
|
|
1078
|
+
"""Normalize event content to a string payload.
|
|
1079
|
+
|
|
1080
|
+
Args:
|
|
1081
|
+
content: Raw content payload from LangChain/LangGraph.
|
|
1082
|
+
|
|
1083
|
+
Returns:
|
|
1084
|
+
String representation suitable for A2A events.
|
|
1085
|
+
"""
|
|
1086
|
+
if isinstance(content, str):
|
|
1087
|
+
return content
|
|
1088
|
+
return json.dumps(content)
|
|
1089
|
+
|
|
1048
1090
|
async def _stream_with_langgraph(self, query: str, **kwargs: Any) -> AsyncGenerator[str | dict[str, Any], None]:
|
|
1049
1091
|
"""Handle streaming for LangChain models using LangGraph's native streaming.
|
|
1050
1092
|
|
|
@@ -1135,9 +1177,13 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1135
1177
|
logger.info(f"Agent '{self.name}': Initializing MCP tools with persistent sessions.")
|
|
1136
1178
|
|
|
1137
1179
|
# Add timeout for initialization to prevent hanging
|
|
1138
|
-
|
|
1180
|
+
mcp_client = self.mcp_client
|
|
1181
|
+
if mcp_client is None:
|
|
1182
|
+
return
|
|
1183
|
+
|
|
1184
|
+
await asyncio.wait_for(mcp_client.initialize(), timeout=30.0)
|
|
1139
1185
|
|
|
1140
|
-
mcp_tools = await
|
|
1186
|
+
mcp_tools = await mcp_client.get_tools()
|
|
1141
1187
|
|
|
1142
1188
|
if not mcp_tools:
|
|
1143
1189
|
logger.warning(f"Agent '{self.name}': No MCP tools retrieved from configured servers.")
|
|
@@ -1169,7 +1215,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1169
1215
|
logger.warning(f"Agent '{self.name}': Error during MCP client cleanup: {e}")
|
|
1170
1216
|
# Don't re-raise - cleanup should be best-effort
|
|
1171
1217
|
|
|
1172
|
-
async def arun_a2a_stream(self, query: str, **kwargs: Any) -> AsyncGenerator[
|
|
1218
|
+
async def arun_a2a_stream(self, query: str, **kwargs: Any) -> AsyncGenerator[A2AEvent, None]:
|
|
1173
1219
|
"""Asynchronously streams the agent's response in A2A format.
|
|
1174
1220
|
|
|
1175
1221
|
Args:
|
|
@@ -1190,7 +1236,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1190
1236
|
task_id: str | None = None,
|
|
1191
1237
|
context_id: str | None = None,
|
|
1192
1238
|
**kwargs: Any,
|
|
1193
|
-
) -> AsyncGenerator[
|
|
1239
|
+
) -> AsyncGenerator[A2AEvent, None]:
|
|
1194
1240
|
"""Stream agent response as SSE-compatible chunks.
|
|
1195
1241
|
|
|
1196
1242
|
This method wraps arun_a2a_stream and transforms output to the normalized
|
|
@@ -1222,7 +1268,8 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1222
1268
|
pii_mapping = kwargs.get("pii_mapping")
|
|
1223
1269
|
transformer = SSEChunkTransformer(task_id=task_id, context_id=context_id, pii_mapping=pii_mapping)
|
|
1224
1270
|
try:
|
|
1225
|
-
|
|
1271
|
+
stream = self.arun_a2a_stream(query, **kwargs)
|
|
1272
|
+
async for chunk in transformer.transform_stream(stream):
|
|
1226
1273
|
yield chunk
|
|
1227
1274
|
except Exception as e:
|
|
1228
1275
|
# Lazy import to support optional guardrails dependency
|
|
@@ -1358,14 +1405,20 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1358
1405
|
Returns:
|
|
1359
1406
|
A2AEvent with TOOL_CALL event type and structured tool information.
|
|
1360
1407
|
"""
|
|
1361
|
-
tool_calls_details = [
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1408
|
+
tool_calls_details: list[dict[str, Any]] = []
|
|
1409
|
+
manager = getattr(self, "tool_output_manager", None)
|
|
1410
|
+
thread_id = _THREAD_ID_CVAR.get()
|
|
1411
|
+
for tool_call in message.tool_calls:
|
|
1412
|
+
args = tool_call["args"]
|
|
1413
|
+
if manager and thread_id and isinstance(args, dict):
|
|
1414
|
+
args = manager.rewrite_args_with_latest_reference(args, thread_id)
|
|
1415
|
+
tool_calls_details.append(
|
|
1416
|
+
{
|
|
1417
|
+
"id": tool_call.get("id"),
|
|
1418
|
+
"name": tool_call["name"],
|
|
1419
|
+
"args": args,
|
|
1420
|
+
}
|
|
1421
|
+
)
|
|
1369
1422
|
tool_names = [details["name"] for details in tool_calls_details]
|
|
1370
1423
|
|
|
1371
1424
|
event = self._create_a2a_event(
|
|
@@ -1374,7 +1427,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1374
1427
|
tool_info={"tool_calls": tool_calls_details, "status": "running"},
|
|
1375
1428
|
metadata={"status": Status.RUNNING},
|
|
1376
1429
|
is_final=False,
|
|
1377
|
-
step_usage=message.usage_metadata,
|
|
1430
|
+
step_usage=self._normalize_usage_metadata(message.usage_metadata),
|
|
1378
1431
|
)
|
|
1379
1432
|
|
|
1380
1433
|
self._record_emitted_tool_calls(tool_calls_details)
|
|
@@ -1594,8 +1647,9 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1594
1647
|
"""
|
|
1595
1648
|
is_final_response = self._is_final_response(message)
|
|
1596
1649
|
metadata = self._build_metadata_for_final_response(is_final_response, state)
|
|
1650
|
+
raw_content = message.content
|
|
1597
1651
|
content = deanonymize_final_response_content(
|
|
1598
|
-
content=
|
|
1652
|
+
content=raw_content if isinstance(raw_content, str) else json.dumps(raw_content),
|
|
1599
1653
|
is_final_response=is_final_response,
|
|
1600
1654
|
metadata=metadata,
|
|
1601
1655
|
)
|
|
@@ -1605,7 +1659,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1605
1659
|
tool_info=None,
|
|
1606
1660
|
metadata=metadata,
|
|
1607
1661
|
is_final=is_final_response,
|
|
1608
|
-
step_usage=message.usage_metadata,
|
|
1662
|
+
step_usage=self._normalize_usage_metadata(message.usage_metadata),
|
|
1609
1663
|
)
|
|
1610
1664
|
return event, is_final_response
|
|
1611
1665
|
|
|
@@ -1882,7 +1936,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1882
1936
|
"""
|
|
1883
1937
|
current_thread_id: str | None = None
|
|
1884
1938
|
try:
|
|
1885
|
-
configurable = config.get("configurable", {})
|
|
1939
|
+
configurable = config.get("configurable", {})
|
|
1886
1940
|
thread_key = self.thread_id_key or "thread_id"
|
|
1887
1941
|
current_thread_id = str(configurable.get(thread_key)) if configurable.get(thread_key) else None
|
|
1888
1942
|
except Exception:
|
|
@@ -1964,7 +2018,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
1964
2018
|
)
|
|
1965
2019
|
return events, is_final, updated_message_count
|
|
1966
2020
|
|
|
1967
|
-
async def _arun_a2a_stream(self, query: str, **kwargs: Any) -> AsyncGenerator[
|
|
2021
|
+
async def _arun_a2a_stream(self, query: str, **kwargs: Any) -> AsyncGenerator[A2AEvent, None]:
|
|
1968
2022
|
"""Internal implementation of arun_a2a_stream without MCP handling.
|
|
1969
2023
|
|
|
1970
2024
|
Args:
|
|
@@ -2031,7 +2085,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2031
2085
|
enable_token_streaming=self.enable_a2a_token_streaming,
|
|
2032
2086
|
)
|
|
2033
2087
|
|
|
2034
|
-
async def _handle_streaming_process(self, context: "_StreamingContext") -> AsyncGenerator[
|
|
2088
|
+
async def _handle_streaming_process(self, context: "_StreamingContext") -> AsyncGenerator[A2AEvent, None]:
|
|
2035
2089
|
"""Handle the main streaming process including initial status and event processing.
|
|
2036
2090
|
|
|
2037
2091
|
Args:
|
|
@@ -2048,7 +2102,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2048
2102
|
self._log_streaming_event_debug("process_stream_item", event)
|
|
2049
2103
|
yield event
|
|
2050
2104
|
|
|
2051
|
-
def _create_initial_status_event(self) ->
|
|
2105
|
+
def _create_initial_status_event(self) -> A2AEvent:
|
|
2052
2106
|
"""Create and setup the initial status event."""
|
|
2053
2107
|
initial_status_event = self._create_a2a_event(
|
|
2054
2108
|
event_type=A2AStreamEventType.STATUS_UPDATE, content=DefaultStepMessages.EN.value
|
|
@@ -2065,7 +2119,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2065
2119
|
|
|
2066
2120
|
return initial_status_event
|
|
2067
2121
|
|
|
2068
|
-
async def _process_streaming_items(self, context: "_StreamingContext") -> AsyncGenerator[
|
|
2122
|
+
async def _process_streaming_items(self, context: "_StreamingContext") -> AsyncGenerator[A2AEvent, None]:
|
|
2069
2123
|
"""Process individual streaming items from the LangGraph execution.
|
|
2070
2124
|
|
|
2071
2125
|
Handles the core streaming logic by iterating through items produced by
|
|
@@ -2098,9 +2152,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2098
2152
|
async for event in self._create_graph_stream_events(enhanced_input, context):
|
|
2099
2153
|
yield event
|
|
2100
2154
|
|
|
2101
|
-
async def _process_a2a_streaming_with_tokens(
|
|
2102
|
-
self, context: "_StreamingContext"
|
|
2103
|
-
) -> AsyncGenerator[dict[str, Any], None]:
|
|
2155
|
+
async def _process_a2a_streaming_with_tokens(self, context: "_StreamingContext") -> AsyncGenerator[A2AEvent, None]:
|
|
2104
2156
|
"""Process A2A streaming with token streaming support using aiostream.
|
|
2105
2157
|
|
|
2106
2158
|
Supports both LM Invoker and LangChain models by detecting the appropriate
|
|
@@ -2133,6 +2185,9 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2133
2185
|
token_stream, enhanced_input = self._create_token_stream(context)
|
|
2134
2186
|
graph_stream = self._create_graph_stream_events(enhanced_input, context)
|
|
2135
2187
|
|
|
2188
|
+
if token_stream is None:
|
|
2189
|
+
raise RuntimeError(f"Agent '{self.name}': Token stream not available for LM invoker.")
|
|
2190
|
+
|
|
2136
2191
|
merged = astream.merge(token_stream, graph_stream)
|
|
2137
2192
|
async with merged.stream() as merged_stream:
|
|
2138
2193
|
async for event in merged_stream:
|
|
@@ -2148,7 +2203,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2148
2203
|
logger.error(f"Agent '{self.name}': Error during A2A token streaming: {e}")
|
|
2149
2204
|
raise
|
|
2150
2205
|
|
|
2151
|
-
async def _create_lm_invoker_token_stream(self) -> AsyncGenerator[
|
|
2206
|
+
async def _create_lm_invoker_token_stream(self) -> AsyncGenerator[A2AEvent, None]:
|
|
2152
2207
|
"""Generate A2A events from LM Invoker token stream.
|
|
2153
2208
|
|
|
2154
2209
|
Uses StreamEventHandler to capture tokens emitted by LM Invoker.
|
|
@@ -2160,6 +2215,8 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2160
2215
|
RuntimeError: If no StreamEventHandler is found in event_emitter.
|
|
2161
2216
|
"""
|
|
2162
2217
|
stream_handler = self._get_stream_handler()
|
|
2218
|
+
if stream_handler is None:
|
|
2219
|
+
raise RuntimeError(f"Agent '{self.name}': StreamEventHandler is required for token streaming.")
|
|
2163
2220
|
|
|
2164
2221
|
try:
|
|
2165
2222
|
async for event in stream_handler.stream():
|
|
@@ -2175,7 +2232,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2175
2232
|
def _create_token_stream(
|
|
2176
2233
|
self,
|
|
2177
2234
|
context: "_StreamingContext",
|
|
2178
|
-
) -> tuple[AsyncGenerator[
|
|
2235
|
+
) -> tuple[AsyncGenerator[A2AEvent, None] | None, dict[str, Any]]:
|
|
2179
2236
|
"""Create appropriate token stream and enhanced input for the active model backend.
|
|
2180
2237
|
|
|
2181
2238
|
Args:
|
|
@@ -2197,7 +2254,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2197
2254
|
|
|
2198
2255
|
async def _create_graph_stream_events(
|
|
2199
2256
|
self, enhanced_input: dict[str, Any], context: "_StreamingContext"
|
|
2200
|
-
) -> AsyncGenerator[
|
|
2257
|
+
) -> AsyncGenerator[A2AEvent, None]:
|
|
2201
2258
|
"""Generate A2A events from graph execution.
|
|
2202
2259
|
|
|
2203
2260
|
Args:
|
|
@@ -2216,8 +2273,9 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2216
2273
|
async for item in graph_execution:
|
|
2217
2274
|
stream_mode, stream_data = item
|
|
2218
2275
|
|
|
2219
|
-
if stream_mode == StreamMode.MESSAGES:
|
|
2220
|
-
|
|
2276
|
+
if stream_mode == StreamMode.MESSAGES.value:
|
|
2277
|
+
message_data = cast(tuple[Any, dict[str, Any]], stream_data)
|
|
2278
|
+
async for token_event in self._process_message_stream_item(message_data):
|
|
2221
2279
|
yield token_event
|
|
2222
2280
|
continue
|
|
2223
2281
|
|
|
@@ -2236,10 +2294,10 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2236
2294
|
Returns:
|
|
2237
2295
|
List of stream modes to use for graph execution.
|
|
2238
2296
|
"""
|
|
2239
|
-
stream_modes = [StreamMode.VALUES, StreamMode.CUSTOM]
|
|
2297
|
+
stream_modes = [StreamMode.VALUES.value, StreamMode.CUSTOM.value]
|
|
2240
2298
|
|
|
2241
2299
|
if context.enable_token_streaming and not self._has_lm_invoker():
|
|
2242
|
-
stream_modes.append(StreamMode.MESSAGES)
|
|
2300
|
+
stream_modes.append(StreamMode.MESSAGES.value)
|
|
2243
2301
|
|
|
2244
2302
|
return stream_modes
|
|
2245
2303
|
|
|
@@ -2249,7 +2307,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2249
2307
|
stream_mode: str,
|
|
2250
2308
|
stream_data: Any,
|
|
2251
2309
|
context: "_StreamingContext",
|
|
2252
|
-
) -> AsyncGenerator[
|
|
2310
|
+
) -> AsyncGenerator[A2AEvent, None]:
|
|
2253
2311
|
"""Process a single graph stream item and yield A2A events.
|
|
2254
2312
|
|
|
2255
2313
|
Args:
|
|
@@ -2261,10 +2319,12 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2261
2319
|
Yields:
|
|
2262
2320
|
A2A events generated from the stream item.
|
|
2263
2321
|
"""
|
|
2264
|
-
context.final_state = copy.copy(stream_data) if stream_mode == StreamMode.VALUES else context.final_state
|
|
2322
|
+
context.final_state = copy.copy(stream_data) if stream_mode == StreamMode.VALUES.value else context.final_state
|
|
2265
2323
|
|
|
2324
|
+
pending_artifacts = context.pending_artifacts if context.pending_artifacts is not None else []
|
|
2325
|
+
seen_artifact_hashes = context.seen_artifact_hashes if context.seen_artifact_hashes is not None else set()
|
|
2266
2326
|
events, is_final, context.processed_message_count = self._handle_stream_item(
|
|
2267
|
-
item,
|
|
2327
|
+
item, pending_artifacts, seen_artifact_hashes, context.processed_message_count
|
|
2268
2328
|
)
|
|
2269
2329
|
|
|
2270
2330
|
if is_final:
|
|
@@ -2277,7 +2337,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2277
2337
|
|
|
2278
2338
|
async def _process_message_stream_item(
|
|
2279
2339
|
self, message_data: tuple[Any, dict[str, Any]]
|
|
2280
|
-
) -> AsyncGenerator[
|
|
2340
|
+
) -> AsyncGenerator[A2AEvent, None]:
|
|
2281
2341
|
"""Process message stream items to extract token events.
|
|
2282
2342
|
|
|
2283
2343
|
The "messages" stream mode yields tuples of (AIMessageChunk, metadata).
|
|
@@ -2314,9 +2374,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2314
2374
|
except Exception as e:
|
|
2315
2375
|
logger.error(f"Agent '{self.name}': Error processing message stream item: {e}")
|
|
2316
2376
|
|
|
2317
|
-
def _update_final_response_for_streaming(
|
|
2318
|
-
self, context: "_StreamingContext", event: dict[str, Any]
|
|
2319
|
-
) -> dict[str, Any]:
|
|
2377
|
+
def _update_final_response_for_streaming(self, context: "_StreamingContext", event: A2AEvent) -> A2AEvent:
|
|
2320
2378
|
"""Update final response events with appropriate streaming configuration.
|
|
2321
2379
|
|
|
2322
2380
|
For FINAL_RESPONSE events, this method updates the metadata and optionally clears
|
|
@@ -2330,13 +2388,17 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2330
2388
|
The processed event dictionary with updated metadata and content
|
|
2331
2389
|
"""
|
|
2332
2390
|
if event.get("event_type") == A2AStreamEventType.FINAL_RESPONSE:
|
|
2333
|
-
event
|
|
2391
|
+
metadata = event.get("metadata")
|
|
2392
|
+
if not isinstance(metadata, dict):
|
|
2393
|
+
metadata = {}
|
|
2394
|
+
event["metadata"] = metadata
|
|
2395
|
+
metadata[MetadataFieldKeys.TOKEN_STREAMING] = False
|
|
2334
2396
|
if context.enable_token_streaming:
|
|
2335
2397
|
event["content"] = ""
|
|
2336
|
-
|
|
2398
|
+
metadata[MetadataFieldKeys.TOKEN_STREAMING] = True
|
|
2337
2399
|
return event
|
|
2338
2400
|
|
|
2339
|
-
def _convert_raw_token_to_a2a_event(self, raw_event: str) ->
|
|
2401
|
+
def _convert_raw_token_to_a2a_event(self, raw_event: str) -> A2AEvent | None:
|
|
2340
2402
|
"""Parse raw token event into A2A event.
|
|
2341
2403
|
|
|
2342
2404
|
Args:
|
|
@@ -2359,7 +2421,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2359
2421
|
logger.debug(f"Agent '{self.name}': Error parsing token event: {e}")
|
|
2360
2422
|
return None
|
|
2361
2423
|
|
|
2362
|
-
def _capture_final_content_if_needed(self, context: "_StreamingContext", event:
|
|
2424
|
+
def _capture_final_content_if_needed(self, context: "_StreamingContext", event: A2AEvent) -> None:
|
|
2363
2425
|
"""Capture final content from A2A events for memory persistence.
|
|
2364
2426
|
|
|
2365
2427
|
Monitors A2A events for final response content and triggers early memory
|
|
@@ -2437,7 +2499,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2437
2499
|
except Exception:
|
|
2438
2500
|
pass
|
|
2439
2501
|
|
|
2440
|
-
async def _ensure_final_completion(self, context: "_StreamingContext") -> AsyncGenerator[
|
|
2502
|
+
async def _ensure_final_completion(self, context: "_StreamingContext") -> AsyncGenerator[A2AEvent, None]:
|
|
2441
2503
|
"""Ensure final completion events are yielded if not already done.
|
|
2442
2504
|
|
|
2443
2505
|
Args:
|
|
@@ -2448,7 +2510,9 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2448
2510
|
dict[str, Any]: The final completion event.
|
|
2449
2511
|
"""
|
|
2450
2512
|
if not context.final_event_yielded:
|
|
2451
|
-
|
|
2513
|
+
pending_artifacts = context.pending_artifacts if context.pending_artifacts is not None else []
|
|
2514
|
+
final_state = context.final_state or {}
|
|
2515
|
+
completion_event = self._create_completion_event(pending_artifacts, final_state)
|
|
2452
2516
|
self._log_streaming_event_debug("final_completion", completion_event)
|
|
2453
2517
|
yield completion_event
|
|
2454
2518
|
|
|
@@ -2456,7 +2520,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2456
2520
|
self,
|
|
2457
2521
|
context: "_StreamingContext",
|
|
2458
2522
|
error: Exception,
|
|
2459
|
-
) -> AsyncGenerator[
|
|
2523
|
+
) -> AsyncGenerator[A2AEvent, None]:
|
|
2460
2524
|
"""Handle streaming errors gracefully.
|
|
2461
2525
|
|
|
2462
2526
|
Provides error handling for the A2A streaming process, ensuring errors
|
|
@@ -2473,11 +2537,14 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2473
2537
|
error message, optionally including any pending artifacts.
|
|
2474
2538
|
"""
|
|
2475
2539
|
logger.error(f"Error in agent stream: {error}", exc_info=True)
|
|
2476
|
-
error_event =
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2540
|
+
error_event = self._create_a2a_event(
|
|
2541
|
+
event_type=A2AStreamEventType.ERROR,
|
|
2542
|
+
content=f"Error: {str(error)}",
|
|
2543
|
+
metadata={"status": "failed"},
|
|
2544
|
+
artifacts=context.pending_artifacts,
|
|
2545
|
+
is_final=True,
|
|
2546
|
+
)
|
|
2547
|
+
error_event["status"] = "failed"
|
|
2481
2548
|
self._log_streaming_event_debug("error_event", error_event)
|
|
2482
2549
|
yield error_event
|
|
2483
2550
|
|
|
@@ -2533,7 +2600,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2533
2600
|
|
|
2534
2601
|
return metadata
|
|
2535
2602
|
|
|
2536
|
-
def _create_completion_event(self, pending_artifacts: list, final_state: dict[str, Any]):
|
|
2603
|
+
def _create_completion_event(self, pending_artifacts: list, final_state: dict[str, Any]) -> A2AEvent:
|
|
2537
2604
|
"""Helper to create the completion event with artifacts and references if available.
|
|
2538
2605
|
|
|
2539
2606
|
This method is used to create the completion event with artifacts and references if available.
|
|
@@ -2587,7 +2654,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2587
2654
|
else:
|
|
2588
2655
|
return tool_name[:4]
|
|
2589
2656
|
|
|
2590
|
-
def _generate_tool_call_step_id(self, tool_info: dict[str, Any], counter: int) -> str:
|
|
2657
|
+
def _generate_tool_call_step_id(self, tool_info: dict[str, Any] | None, counter: int) -> str:
|
|
2591
2658
|
"""Generate step_id for tool call events.
|
|
2592
2659
|
|
|
2593
2660
|
Args:
|
|
@@ -2623,7 +2690,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2623
2690
|
|
|
2624
2691
|
return f"{category}_{combined_name}_parent_{counter:03d}"
|
|
2625
2692
|
|
|
2626
|
-
def _generate_tool_result_step_id(self, tool_info: dict[str, Any], counter: int) -> str:
|
|
2693
|
+
def _generate_tool_result_step_id(self, tool_info: dict[str, Any] | None, counter: int) -> str:
|
|
2627
2694
|
"""Generate step_id for tool result events.
|
|
2628
2695
|
|
|
2629
2696
|
Args:
|
|
@@ -2749,7 +2816,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2749
2816
|
def _create_a2a_event( # noqa: PLR0913
|
|
2750
2817
|
self,
|
|
2751
2818
|
event_type: A2AStreamEventType,
|
|
2752
|
-
content:
|
|
2819
|
+
content: Any,
|
|
2753
2820
|
metadata: dict[str, Any] | None = None,
|
|
2754
2821
|
tool_info: dict[str, Any] | None = None,
|
|
2755
2822
|
thinking_and_activity_info: dict[str, Any] | None = None,
|
|
@@ -2787,9 +2854,11 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2787
2854
|
# Inject cumulative time since the first STATUS_UPDATE for this thread
|
|
2788
2855
|
# Do not set cumulative time here; server executor enforces it for all SSE events
|
|
2789
2856
|
|
|
2857
|
+
normalized_content = self._normalize_event_content(content)
|
|
2858
|
+
|
|
2790
2859
|
event = {
|
|
2791
2860
|
"event_type": event_type,
|
|
2792
|
-
"content":
|
|
2861
|
+
"content": normalized_content,
|
|
2793
2862
|
"metadata": enriched_metadata,
|
|
2794
2863
|
"tool_info": tool_info,
|
|
2795
2864
|
"is_final": is_final,
|
|
@@ -2803,7 +2872,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
2803
2872
|
event["thinking_and_activity_info"] = thinking_and_activity_info
|
|
2804
2873
|
|
|
2805
2874
|
try:
|
|
2806
|
-
content_preview =
|
|
2875
|
+
content_preview = normalized_content
|
|
2807
2876
|
logger.info(
|
|
2808
2877
|
"A2A emitting event: type=%s step_id=%s final=%s preview=%s",
|
|
2809
2878
|
getattr(event_type, "value", event_type),
|
|
@@ -83,6 +83,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
83
83
|
enable_a2a_token_streaming: Incomplete
|
|
84
84
|
event_emitter: Incomplete
|
|
85
85
|
checkpointer: Incomplete
|
|
86
|
+
tool_output_manager: Incomplete
|
|
86
87
|
memory: BaseMemory | None
|
|
87
88
|
a2a_tool_manager: Incomplete
|
|
88
89
|
delegation_tool_manager: Incomplete
|
|
@@ -196,7 +197,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
196
197
|
Errors during cleanup are logged but do not raise exceptions to ensure
|
|
197
198
|
the cleanup process completes gracefully.
|
|
198
199
|
"""
|
|
199
|
-
async def arun_a2a_stream(self, query: str, **kwargs: Any) -> AsyncGenerator[
|
|
200
|
+
async def arun_a2a_stream(self, query: str, **kwargs: Any) -> AsyncGenerator[A2AEvent, None]:
|
|
200
201
|
'''Asynchronously streams the agent\'s response in A2A format.
|
|
201
202
|
|
|
202
203
|
Args:
|
|
@@ -207,7 +208,7 @@ class BaseLangGraphAgent(BaseAgent):
|
|
|
207
208
|
Dictionaries with "status" and "content" keys.
|
|
208
209
|
Possible statuses: "working", "completed", "failed", "canceled".
|
|
209
210
|
'''
|
|
210
|
-
async def arun_sse_stream(self, query: str, task_id: str | None = None, context_id: str | None = None, **kwargs: Any) -> AsyncGenerator[
|
|
211
|
+
async def arun_sse_stream(self, query: str, task_id: str | None = None, context_id: str | None = None, **kwargs: Any) -> AsyncGenerator[A2AEvent, None]:
|
|
211
212
|
'''Stream agent response as SSE-compatible chunks.
|
|
212
213
|
|
|
213
214
|
This method wraps arun_a2a_stream and transforms output to the normalized
|