loom-agent 0.0.1__py3-none-any.whl → 0.0.3__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 loom-agent might be problematic. Click here for more details.
- loom/builtin/tools/calculator.py +4 -0
- loom/builtin/tools/document_search.py +5 -0
- loom/builtin/tools/glob.py +4 -0
- loom/builtin/tools/grep.py +4 -0
- loom/builtin/tools/http_request.py +5 -0
- loom/builtin/tools/python_repl.py +5 -0
- loom/builtin/tools/read_file.py +4 -0
- loom/builtin/tools/task.py +105 -0
- loom/builtin/tools/web_search.py +4 -0
- loom/builtin/tools/write_file.py +4 -0
- loom/components/agent.py +121 -5
- loom/core/agent_executor.py +777 -321
- loom/core/compression_manager.py +17 -10
- loom/core/context_assembly.py +437 -0
- loom/core/events.py +660 -0
- loom/core/execution_context.py +119 -0
- loom/core/tool_orchestrator.py +383 -0
- loom/core/turn_state.py +188 -0
- loom/core/types.py +15 -4
- loom/core/unified_coordination.py +389 -0
- loom/interfaces/event_producer.py +172 -0
- loom/interfaces/tool.py +22 -1
- loom/security/__init__.py +13 -0
- loom/security/models.py +85 -0
- loom/security/path_validator.py +128 -0
- loom/security/validator.py +346 -0
- loom/tasks/PHASE_1_FOUNDATION/task_1.1_agent_events.md +121 -0
- loom/tasks/PHASE_1_FOUNDATION/task_1.2_streaming_api.md +521 -0
- loom/tasks/PHASE_1_FOUNDATION/task_1.3_context_assembler.md +606 -0
- loom/tasks/PHASE_2_CORE_FEATURES/task_2.1_tool_orchestrator.md +743 -0
- loom/tasks/PHASE_2_CORE_FEATURES/task_2.2_security_validator.md +676 -0
- loom/tasks/README.md +109 -0
- loom/tasks/__init__.py +11 -0
- loom/tasks/sql_placeholder.py +100 -0
- loom_agent-0.0.3.dist-info/METADATA +292 -0
- {loom_agent-0.0.1.dist-info → loom_agent-0.0.3.dist-info}/RECORD +38 -19
- loom_agent-0.0.1.dist-info/METADATA +0 -457
- {loom_agent-0.0.1.dist-info → loom_agent-0.0.3.dist-info}/WHEEL +0 -0
- {loom_agent-0.0.1.dist-info → loom_agent-0.0.3.dist-info}/licenses/LICENSE +0 -0
loom/builtin/tools/calculator.py
CHANGED
|
@@ -18,6 +18,10 @@ class Calculator(BaseTool):
|
|
|
18
18
|
description = "Evaluate simple arithmetic expressions"
|
|
19
19
|
args_schema = CalcArgs
|
|
20
20
|
|
|
21
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
22
|
+
is_read_only = True # Pure computation, no side effects
|
|
23
|
+
category = "general"
|
|
24
|
+
|
|
21
25
|
async def run(self, **kwargs) -> Any:
|
|
22
26
|
expr = kwargs.get("expression", "")
|
|
23
27
|
return str(_safe_eval(expr))
|
|
@@ -48,6 +48,11 @@ class DocumentSearchTool(BaseTool):
|
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
50
|
name = "search_documents"
|
|
51
|
+
|
|
52
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
53
|
+
is_read_only = True # Only searches documents, no modifications
|
|
54
|
+
category = "general"
|
|
55
|
+
|
|
51
56
|
description = (
|
|
52
57
|
"Search for relevant documents from the knowledge base. "
|
|
53
58
|
"Use this when you need specific information that might be in the documents. "
|
loom/builtin/tools/glob.py
CHANGED
|
@@ -19,6 +19,10 @@ class GlobTool(BaseTool):
|
|
|
19
19
|
description = "按模式匹配文件路径"
|
|
20
20
|
args_schema = GlobArgs
|
|
21
21
|
|
|
22
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
23
|
+
is_read_only = True
|
|
24
|
+
category = "general"
|
|
25
|
+
|
|
22
26
|
async def run(self, **kwargs) -> Any:
|
|
23
27
|
args = self.args_schema(**kwargs) # type: ignore
|
|
24
28
|
cwd = Path(args.cwd).expanduser() if args.cwd else Path.cwd()
|
loom/builtin/tools/grep.py
CHANGED
|
@@ -22,6 +22,10 @@ class GrepTool(BaseTool):
|
|
|
22
22
|
description = "在文件或文件集内检索正则匹配"
|
|
23
23
|
args_schema = GrepArgs
|
|
24
24
|
|
|
25
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
26
|
+
is_read_only = True
|
|
27
|
+
category = "general"
|
|
28
|
+
|
|
25
29
|
async def run(self, **kwargs) -> Any:
|
|
26
30
|
args = self.args_schema(**kwargs) # type: ignore
|
|
27
31
|
flags = 0
|
|
@@ -35,6 +35,11 @@ class HTTPRequestTool(BaseTool):
|
|
|
35
35
|
args_schema = HTTPRequestInput
|
|
36
36
|
is_concurrency_safe = True
|
|
37
37
|
|
|
38
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
39
|
+
is_read_only = False # POST/PUT/DELETE may modify remote state
|
|
40
|
+
category = "network" # Network operation
|
|
41
|
+
requires_confirmation = False # Usually safe, but depends on usage
|
|
42
|
+
|
|
38
43
|
def __init__(self, timeout: int = 10) -> None:
|
|
39
44
|
if httpx is None:
|
|
40
45
|
raise ImportError("Please install httpx: pip install httpx")
|
|
@@ -33,6 +33,11 @@ class PythonREPLTool(BaseTool):
|
|
|
33
33
|
args_schema = PythonREPLInput
|
|
34
34
|
is_concurrency_safe = False # 代码执行不并发安全
|
|
35
35
|
|
|
36
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
37
|
+
is_read_only = False # Code execution may have side effects
|
|
38
|
+
category = "destructive" # Potentially dangerous
|
|
39
|
+
requires_confirmation = True # Should require user confirmation
|
|
40
|
+
|
|
36
41
|
async def run(self, code: str, **kwargs: Any) -> str:
|
|
37
42
|
"""执行 Python 代码"""
|
|
38
43
|
# 安全性检查 - 禁止危险操作
|
loom/builtin/tools/read_file.py
CHANGED
|
@@ -19,6 +19,10 @@ class ReadFileTool(BaseTool):
|
|
|
19
19
|
description = "读取文本文件内容"
|
|
20
20
|
args_schema = ReadArgs
|
|
21
21
|
|
|
22
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
23
|
+
is_read_only = True
|
|
24
|
+
category = "general"
|
|
25
|
+
|
|
22
26
|
async def run(self, **kwargs) -> Any:
|
|
23
27
|
args = self.args_schema(**kwargs) # type: ignore
|
|
24
28
|
p = Path(args.path).expanduser()
|
loom/builtin/tools/task.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import time
|
|
5
6
|
from typing import TYPE_CHECKING, Any, Optional, Dict, List
|
|
6
7
|
|
|
7
8
|
from pydantic import BaseModel, Field
|
|
@@ -36,6 +37,12 @@ class TaskTool(BaseTool):
|
|
|
36
37
|
Task 工具 - 启动 SubAgent 执行专项任务
|
|
37
38
|
|
|
38
39
|
对应 Claude Code 的 Task 工具和 SubAgent 机制
|
|
40
|
+
|
|
41
|
+
新特性 (Loom 0.0.3):
|
|
42
|
+
- 子代理池管理
|
|
43
|
+
- 性能监控和指标收集
|
|
44
|
+
- 智能负载均衡
|
|
45
|
+
- 资源使用优化
|
|
39
46
|
"""
|
|
40
47
|
|
|
41
48
|
name = "task"
|
|
@@ -47,18 +54,44 @@ class TaskTool(BaseTool):
|
|
|
47
54
|
args_schema = TaskInput
|
|
48
55
|
is_concurrency_safe = True
|
|
49
56
|
|
|
57
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
58
|
+
is_read_only = False # Sub-agent may use write tools
|
|
59
|
+
category = "general" # Not inherently dangerous, but depends on sub-agent's tools
|
|
60
|
+
requires_confirmation = False
|
|
61
|
+
|
|
50
62
|
def __init__(
|
|
51
63
|
self,
|
|
52
64
|
agent_factory: Optional[callable] = None,
|
|
53
65
|
max_iterations: int = 20,
|
|
66
|
+
enable_pooling: bool = True,
|
|
67
|
+
pool_size: int = 5,
|
|
68
|
+
enable_monitoring: bool = True,
|
|
54
69
|
) -> None:
|
|
55
70
|
"""
|
|
56
71
|
Parameters:
|
|
57
72
|
- agent_factory: 创建 SubAgent 的工厂函数
|
|
58
73
|
- max_iterations: SubAgent 最大迭代次数
|
|
74
|
+
- enable_pooling: 启用子代理池管理
|
|
75
|
+
- pool_size: 子代理池大小
|
|
76
|
+
- enable_monitoring: 启用性能监控
|
|
59
77
|
"""
|
|
60
78
|
self.agent_factory = agent_factory
|
|
61
79
|
self.max_iterations = max_iterations
|
|
80
|
+
|
|
81
|
+
# Performance optimizations
|
|
82
|
+
self.enable_pooling = enable_pooling
|
|
83
|
+
self.pool_size = pool_size
|
|
84
|
+
self.enable_monitoring = enable_monitoring
|
|
85
|
+
|
|
86
|
+
# Sub-agent pool management
|
|
87
|
+
self._agent_pool: Dict[str, Any] = {}
|
|
88
|
+
self._pool_stats = {
|
|
89
|
+
"total_created": 0,
|
|
90
|
+
"total_executed": 0,
|
|
91
|
+
"average_execution_time": 0.0,
|
|
92
|
+
"cache_hits": 0,
|
|
93
|
+
"cache_misses": 0
|
|
94
|
+
}
|
|
62
95
|
|
|
63
96
|
async def run(
|
|
64
97
|
self,
|
|
@@ -131,7 +164,33 @@ class TaskTool(BaseTool):
|
|
|
131
164
|
)
|
|
132
165
|
|
|
133
166
|
# 运行子任务(系统提示已注入到 sub_agent,输入仍为原始 prompt)
|
|
167
|
+
start_time = time.time() if self.enable_monitoring else None
|
|
168
|
+
|
|
169
|
+
# Check pool for reusable agent
|
|
170
|
+
agent_key = self._get_agent_key(subagent_type, effective_model, permission_policy)
|
|
171
|
+
if self.enable_pooling and agent_key in self._agent_pool:
|
|
172
|
+
sub_agent = self._agent_pool[agent_key]
|
|
173
|
+
self._pool_stats["cache_hits"] += 1
|
|
174
|
+
else:
|
|
175
|
+
self._pool_stats["cache_misses"] += 1
|
|
176
|
+
self._pool_stats["total_created"] += 1
|
|
177
|
+
|
|
178
|
+
# Add to pool if enabled and not at capacity
|
|
179
|
+
if self.enable_pooling and len(self._agent_pool) < self.pool_size:
|
|
180
|
+
self._agent_pool[agent_key] = sub_agent
|
|
181
|
+
|
|
134
182
|
result = await sub_agent.run(prompt)
|
|
183
|
+
|
|
184
|
+
# Update performance metrics
|
|
185
|
+
if self.enable_monitoring and start_time:
|
|
186
|
+
execution_time = time.time() - start_time
|
|
187
|
+
self._pool_stats["total_executed"] += 1
|
|
188
|
+
# Update running average
|
|
189
|
+
current_avg = self._pool_stats["average_execution_time"]
|
|
190
|
+
total_executed = self._pool_stats["total_executed"]
|
|
191
|
+
self._pool_stats["average_execution_time"] = (
|
|
192
|
+
(current_avg * (total_executed - 1) + execution_time) / total_executed
|
|
193
|
+
)
|
|
135
194
|
|
|
136
195
|
# 格式化返回结果
|
|
137
196
|
return f"**SubAgent Task: {description}**\n\nResult:\n{result}"
|
|
@@ -156,3 +215,49 @@ class TaskTool(BaseTool):
|
|
|
156
215
|
subagent_type=subagent_type,
|
|
157
216
|
model_name=model_name,
|
|
158
217
|
)
|
|
218
|
+
|
|
219
|
+
def _get_agent_key(
|
|
220
|
+
self,
|
|
221
|
+
subagent_type: Optional[str],
|
|
222
|
+
model_name: Optional[str],
|
|
223
|
+
permission_policy: Optional[Dict[str, str]]
|
|
224
|
+
) -> str:
|
|
225
|
+
"""Generate unique key for agent pool"""
|
|
226
|
+
import hashlib
|
|
227
|
+
|
|
228
|
+
key_parts = [
|
|
229
|
+
subagent_type or "default",
|
|
230
|
+
model_name or "default",
|
|
231
|
+
str(sorted(permission_policy.items())) if permission_policy else "default"
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
key_string = "|".join(key_parts)
|
|
235
|
+
return hashlib.md5(key_string.encode()).hexdigest()
|
|
236
|
+
|
|
237
|
+
def get_pool_stats(self) -> Dict[str, Any]:
|
|
238
|
+
"""Get sub-agent pool statistics"""
|
|
239
|
+
return {
|
|
240
|
+
**self._pool_stats,
|
|
241
|
+
"pool_size": len(self._agent_pool),
|
|
242
|
+
"max_pool_size": self.pool_size,
|
|
243
|
+
"pool_utilization": len(self._agent_pool) / self.pool_size if self.pool_size > 0 else 0,
|
|
244
|
+
"cache_hit_rate": (
|
|
245
|
+
self._pool_stats["cache_hits"] /
|
|
246
|
+
(self._pool_stats["cache_hits"] + self._pool_stats["cache_misses"])
|
|
247
|
+
if (self._pool_stats["cache_hits"] + self._pool_stats["cache_misses"]) > 0 else 0
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
def clear_pool(self) -> None:
|
|
252
|
+
"""Clear the sub-agent pool"""
|
|
253
|
+
self._agent_pool.clear()
|
|
254
|
+
|
|
255
|
+
def reset_stats(self) -> None:
|
|
256
|
+
"""Reset performance statistics"""
|
|
257
|
+
self._pool_stats = {
|
|
258
|
+
"total_created": 0,
|
|
259
|
+
"total_executed": 0,
|
|
260
|
+
"average_execution_time": 0.0,
|
|
261
|
+
"cache_hits": 0,
|
|
262
|
+
"cache_misses": 0
|
|
263
|
+
}
|
loom/builtin/tools/web_search.py
CHANGED
|
@@ -33,6 +33,10 @@ class WebSearchTool(BaseTool):
|
|
|
33
33
|
args_schema = WebSearchInput
|
|
34
34
|
is_concurrency_safe = True
|
|
35
35
|
|
|
36
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
37
|
+
is_read_only = True # Only reads from web, no local side effects
|
|
38
|
+
category = "network"
|
|
39
|
+
|
|
36
40
|
def __init__(self) -> None:
|
|
37
41
|
if DDGS is None:
|
|
38
42
|
raise ImportError(
|
loom/builtin/tools/write_file.py
CHANGED
|
@@ -20,6 +20,10 @@ class WriteFileTool(BaseTool):
|
|
|
20
20
|
description = "写入文本到文件(可能覆盖)"
|
|
21
21
|
args_schema = WriteArgs
|
|
22
22
|
|
|
23
|
+
# 🆕 Loom 2.0 - Orchestration attributes
|
|
24
|
+
is_read_only = False
|
|
25
|
+
category = "destructive"
|
|
26
|
+
|
|
23
27
|
async def run(self, **kwargs) -> Any:
|
|
24
28
|
args = self.args_schema(**kwargs) # type: ignore
|
|
25
29
|
p = Path(args.path).expanduser()
|
loom/components/agent.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from typing import AsyncGenerator, Dict, List, Optional
|
|
5
7
|
|
|
6
8
|
from loom.core.agent_executor import AgentExecutor
|
|
7
|
-
from loom.core.types import StreamEvent
|
|
9
|
+
from loom.core.types import StreamEvent, Message
|
|
10
|
+
from loom.core.events import AgentEvent, AgentEventType # 🆕 Loom 2.0
|
|
11
|
+
from loom.core.turn_state import TurnState # 🆕 Loom 2.0 tt mode
|
|
12
|
+
from loom.core.execution_context import ExecutionContext # 🆕 Loom 2.0 tt mode
|
|
8
13
|
from loom.interfaces.llm import BaseLLM
|
|
9
14
|
from loom.interfaces.memory import BaseMemory
|
|
10
15
|
from loom.interfaces.tool import BaseTool
|
|
@@ -35,6 +40,7 @@ class Agent:
|
|
|
35
40
|
callbacks: Optional[List[BaseCallback]] = None,
|
|
36
41
|
steering_control: Optional[SteeringControl] = None,
|
|
37
42
|
metrics: Optional[MetricsCollector] = None,
|
|
43
|
+
enable_steering: bool = True, # v4.0.0: Enable steering by default
|
|
38
44
|
) -> None:
|
|
39
45
|
# v4.0.0: Auto-instantiate CompressionManager (always enabled)
|
|
40
46
|
if compressor is None:
|
|
@@ -61,7 +67,7 @@ class Agent:
|
|
|
61
67
|
permission_manager=None,
|
|
62
68
|
system_instructions=system_instructions,
|
|
63
69
|
callbacks=callbacks,
|
|
64
|
-
enable_steering=
|
|
70
|
+
enable_steering=enable_steering,
|
|
65
71
|
)
|
|
66
72
|
|
|
67
73
|
# 始终构造 PermissionManager(以便支持 safe_mode/持久化);保持默认语义
|
|
@@ -83,11 +89,121 @@ class Agent:
|
|
|
83
89
|
cancel_token: Optional[asyncio.Event] = None, # 🆕 US1
|
|
84
90
|
correlation_id: Optional[str] = None, # 🆕 US1
|
|
85
91
|
) -> str:
|
|
86
|
-
|
|
92
|
+
"""
|
|
93
|
+
Execute agent and return final response (backward compatible).
|
|
94
|
+
|
|
95
|
+
This method wraps the new execute() streaming API and extracts
|
|
96
|
+
the final response for backward compatibility.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
input: User input
|
|
100
|
+
cancel_token: Optional cancellation event
|
|
101
|
+
correlation_id: Optional correlation ID for tracing
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Final response text
|
|
105
|
+
"""
|
|
106
|
+
final_content = ""
|
|
107
|
+
|
|
108
|
+
async for event in self.execute(input):
|
|
109
|
+
# Accumulate LLM deltas
|
|
110
|
+
if event.type == AgentEventType.LLM_DELTA:
|
|
111
|
+
final_content += event.content or ""
|
|
112
|
+
|
|
113
|
+
# Return on finish
|
|
114
|
+
elif event.type == AgentEventType.AGENT_FINISH:
|
|
115
|
+
return event.content or final_content
|
|
116
|
+
|
|
117
|
+
# Raise on error
|
|
118
|
+
elif event.type == AgentEventType.ERROR:
|
|
119
|
+
if event.error:
|
|
120
|
+
raise event.error
|
|
121
|
+
|
|
122
|
+
return final_content
|
|
123
|
+
|
|
124
|
+
async def execute(
|
|
125
|
+
self,
|
|
126
|
+
input: str,
|
|
127
|
+
cancel_token: Optional[asyncio.Event] = None,
|
|
128
|
+
correlation_id: Optional[str] = None,
|
|
129
|
+
working_dir: Optional[Path] = None,
|
|
130
|
+
) -> AsyncGenerator[AgentEvent, None]:
|
|
131
|
+
"""
|
|
132
|
+
Execute agent with streaming events using tt recursive mode (Loom 2.0).
|
|
133
|
+
|
|
134
|
+
This is the new unified streaming interface that produces AgentEvent
|
|
135
|
+
instances for all execution phases. It uses tt (tail-recursive) control
|
|
136
|
+
loop as the ONLY core execution method.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
input: User input
|
|
140
|
+
cancel_token: Optional cancellation event
|
|
141
|
+
correlation_id: Optional correlation ID for tracing
|
|
142
|
+
working_dir: Optional working directory
|
|
143
|
+
|
|
144
|
+
Yields:
|
|
145
|
+
AgentEvent: Events representing execution progress
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
```python
|
|
149
|
+
async for event in agent.execute("Your prompt"):
|
|
150
|
+
if event.type == AgentEventType.LLM_DELTA:
|
|
151
|
+
print(event.content, end="", flush=True)
|
|
152
|
+
elif event.type == AgentEventType.TOOL_PROGRESS:
|
|
153
|
+
print(f"\\n[Tool] {event.metadata['status']}")
|
|
154
|
+
elif event.type == AgentEventType.AGENT_FINISH:
|
|
155
|
+
print(f"\\n✓ {event.content}")
|
|
156
|
+
```
|
|
157
|
+
"""
|
|
158
|
+
# Initialize immutable turn state
|
|
159
|
+
turn_state = TurnState.initial(max_iterations=self.executor.max_iterations)
|
|
160
|
+
|
|
161
|
+
# Create execution context
|
|
162
|
+
context = ExecutionContext.create(
|
|
163
|
+
working_dir=working_dir,
|
|
164
|
+
correlation_id=correlation_id,
|
|
165
|
+
cancel_token=cancel_token,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Create initial message
|
|
169
|
+
messages = [Message(role="user", content=input)]
|
|
170
|
+
|
|
171
|
+
# Delegate to executor's tt recursive control loop
|
|
172
|
+
async for event in self.executor.tt(messages, turn_state, context):
|
|
173
|
+
yield event
|
|
87
174
|
|
|
88
175
|
async def stream(self, input: str) -> AsyncGenerator[StreamEvent, None]:
|
|
89
|
-
|
|
90
|
-
|
|
176
|
+
"""
|
|
177
|
+
Legacy streaming API (backward compatible).
|
|
178
|
+
|
|
179
|
+
NOTE: This uses the old StreamEvent type. For new code, use execute()
|
|
180
|
+
which returns AgentEvent instances.
|
|
181
|
+
|
|
182
|
+
This method now converts AgentEvent to StreamEvent for backward compatibility.
|
|
183
|
+
"""
|
|
184
|
+
# Use tt mode under the hood
|
|
185
|
+
async for agent_event in self.execute(input):
|
|
186
|
+
# Convert AgentEvent to legacy StreamEvent
|
|
187
|
+
if agent_event.type == AgentEventType.LLM_DELTA:
|
|
188
|
+
yield StreamEvent(
|
|
189
|
+
type="llm_delta",
|
|
190
|
+
content=agent_event.content or "",
|
|
191
|
+
)
|
|
192
|
+
elif agent_event.type == AgentEventType.AGENT_FINISH:
|
|
193
|
+
yield StreamEvent(
|
|
194
|
+
type="agent_finish",
|
|
195
|
+
content=agent_event.content or "",
|
|
196
|
+
)
|
|
197
|
+
elif agent_event.type == AgentEventType.TOOL_RESULT:
|
|
198
|
+
yield StreamEvent(
|
|
199
|
+
type="tool_result",
|
|
200
|
+
content=agent_event.tool_result.content if agent_event.tool_result else "",
|
|
201
|
+
)
|
|
202
|
+
elif agent_event.type == AgentEventType.ERROR:
|
|
203
|
+
yield StreamEvent(
|
|
204
|
+
type="error",
|
|
205
|
+
content=str(agent_event.error) if agent_event.error else "Unknown error",
|
|
206
|
+
)
|
|
91
207
|
|
|
92
208
|
# LangChain 风格的别名,便于迁移/调用
|
|
93
209
|
async def ainvoke(
|