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
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
# Task 1.2: 重构 Agent.execute() 为流式接口
|
|
2
|
+
|
|
3
|
+
**状态**: ⏳ 待开始
|
|
4
|
+
**优先级**: P0
|
|
5
|
+
**预计时间**: 1-2 天
|
|
6
|
+
**依赖**: Task 1.1 (AgentEvent 模型) ✅
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 📋 任务概述
|
|
11
|
+
|
|
12
|
+
### 目标
|
|
13
|
+
|
|
14
|
+
将 `Agent.execute()` 方法改为返回 `AsyncGenerator[AgentEvent, None]`,实现全链路流式架构。
|
|
15
|
+
|
|
16
|
+
### 为什么需要这个任务?
|
|
17
|
+
|
|
18
|
+
**当前问题**:
|
|
19
|
+
```python
|
|
20
|
+
# Loom 1.0 - 非流式
|
|
21
|
+
result = await agent.run(prompt) # 等待完成,无实时进度
|
|
22
|
+
print(result)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**期望结果**:
|
|
26
|
+
```python
|
|
27
|
+
# Loom 2.0 - 流式
|
|
28
|
+
async for event in agent.execute(prompt):
|
|
29
|
+
if event.type == AgentEventType.LLM_DELTA:
|
|
30
|
+
print(event.content, end="", flush=True)
|
|
31
|
+
elif event.type == AgentEventType.TOOL_PROGRESS:
|
|
32
|
+
print(f"\n[Tool] {event.metadata['status']}")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 📐 详细步骤
|
|
38
|
+
|
|
39
|
+
### Step 1: 修改 Agent 接口
|
|
40
|
+
|
|
41
|
+
**文件**: `loom/components/agent.py`
|
|
42
|
+
|
|
43
|
+
**当前代码** (简化):
|
|
44
|
+
```python
|
|
45
|
+
class Agent:
|
|
46
|
+
async def run(self, input: str) -> str:
|
|
47
|
+
"""Execute agent and return final response"""
|
|
48
|
+
return await self.executor.execute(input)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**修改为**:
|
|
52
|
+
```python
|
|
53
|
+
class Agent:
|
|
54
|
+
async def execute(self, input: str) -> AsyncGenerator[AgentEvent, None]:
|
|
55
|
+
"""
|
|
56
|
+
Execute agent with streaming events.
|
|
57
|
+
|
|
58
|
+
Yields:
|
|
59
|
+
AgentEvent: Events representing execution progress
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
```python
|
|
63
|
+
async for event in agent.execute("Your prompt"):
|
|
64
|
+
if event.type == AgentEventType.LLM_DELTA:
|
|
65
|
+
print(event.content, end="")
|
|
66
|
+
```
|
|
67
|
+
"""
|
|
68
|
+
# 初始化 turn state
|
|
69
|
+
turn_state = TurnState(turn_counter=0, turn_id=str(uuid.uuid4()))
|
|
70
|
+
|
|
71
|
+
# 创建初始消息
|
|
72
|
+
messages = [Message(role="user", content=input)]
|
|
73
|
+
|
|
74
|
+
# 调用 executor 的流式接口
|
|
75
|
+
async for event in self.executor.execute_stream(messages, turn_state):
|
|
76
|
+
yield event
|
|
77
|
+
|
|
78
|
+
async def run(self, input: str) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Execute agent and return final response (backward compatible).
|
|
81
|
+
|
|
82
|
+
This is a convenience method that wraps execute() and extracts
|
|
83
|
+
the final response.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
input: User input
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Final response text
|
|
90
|
+
"""
|
|
91
|
+
final_content = ""
|
|
92
|
+
|
|
93
|
+
async for event in self.execute(input):
|
|
94
|
+
# Accumulate LLM deltas
|
|
95
|
+
if event.type == AgentEventType.LLM_DELTA:
|
|
96
|
+
final_content += event.content or ""
|
|
97
|
+
|
|
98
|
+
# Return on finish
|
|
99
|
+
elif event.type == AgentEventType.AGENT_FINISH:
|
|
100
|
+
return event.content or final_content
|
|
101
|
+
|
|
102
|
+
# Raise on error
|
|
103
|
+
elif event.type == AgentEventType.ERROR:
|
|
104
|
+
raise event.error
|
|
105
|
+
|
|
106
|
+
return final_content
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**新增数据类**:
|
|
110
|
+
```python
|
|
111
|
+
from dataclasses import dataclass
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class TurnState:
|
|
115
|
+
"""State for recursive agent execution"""
|
|
116
|
+
turn_counter: int
|
|
117
|
+
turn_id: str
|
|
118
|
+
compacted: bool = False
|
|
119
|
+
max_iterations: int = 10
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Step 2: 修改 AgentExecutor 接口
|
|
125
|
+
|
|
126
|
+
**文件**: `loom/core/agent_executor.py`
|
|
127
|
+
|
|
128
|
+
**当前代码** (简化):
|
|
129
|
+
```python
|
|
130
|
+
class AgentExecutor:
|
|
131
|
+
async def execute(self, user_input: str) -> str:
|
|
132
|
+
"""Execute agent and return final response"""
|
|
133
|
+
# ... 复杂的逻辑
|
|
134
|
+
return final_response
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**修改为**:
|
|
138
|
+
```python
|
|
139
|
+
class AgentExecutor:
|
|
140
|
+
async def execute_stream(
|
|
141
|
+
self,
|
|
142
|
+
messages: List[Message],
|
|
143
|
+
turn_state: TurnState
|
|
144
|
+
) -> AsyncGenerator[AgentEvent, None]:
|
|
145
|
+
"""
|
|
146
|
+
Execute agent with streaming events.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
messages: Conversation history
|
|
150
|
+
turn_state: Current turn state
|
|
151
|
+
|
|
152
|
+
Yields:
|
|
153
|
+
AgentEvent: Events representing execution progress
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
# Phase 0: Iteration check
|
|
157
|
+
yield AgentEvent(
|
|
158
|
+
type=AgentEventType.ITERATION_START,
|
|
159
|
+
iteration=turn_state.turn_counter,
|
|
160
|
+
turn_id=turn_state.turn_id
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if turn_state.turn_counter >= turn_state.max_iterations:
|
|
164
|
+
yield AgentEvent(type=AgentEventType.MAX_ITERATIONS_REACHED)
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# Phase 1: Context assembly
|
|
168
|
+
yield AgentEvent.phase_start("context_assembly")
|
|
169
|
+
|
|
170
|
+
system_prompt = self.system_prompt_builder.build()
|
|
171
|
+
# TODO: 后续任务会替换为 ContextAssembler
|
|
172
|
+
|
|
173
|
+
yield AgentEvent.phase_end(
|
|
174
|
+
"context_assembly",
|
|
175
|
+
tokens_used=self._count_tokens(system_prompt)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Phase 2: RAG retrieval (if enabled)
|
|
179
|
+
if self.context_retriever:
|
|
180
|
+
yield AgentEvent(type=AgentEventType.RETRIEVAL_START)
|
|
181
|
+
|
|
182
|
+
retrieved_docs = await self.context_retriever.retrieve(
|
|
183
|
+
messages[-1].content
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
for doc in retrieved_docs:
|
|
187
|
+
yield AgentEvent(
|
|
188
|
+
type=AgentEventType.RETRIEVAL_PROGRESS,
|
|
189
|
+
metadata={
|
|
190
|
+
"doc_title": doc.metadata.get("title", "Unknown"),
|
|
191
|
+
"relevance_score": doc.metadata.get("score", 0.0)
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
yield AgentEvent(type=AgentEventType.RETRIEVAL_COMPLETE)
|
|
196
|
+
|
|
197
|
+
# TODO: 正确注入 RAG 上下文(Task 1.3)
|
|
198
|
+
|
|
199
|
+
# Phase 3: LLM call
|
|
200
|
+
yield AgentEvent(type=AgentEventType.LLM_START)
|
|
201
|
+
|
|
202
|
+
# 构建完整消息
|
|
203
|
+
full_messages = [
|
|
204
|
+
Message(role="system", content=system_prompt),
|
|
205
|
+
*messages
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
# 流式调用 LLM
|
|
209
|
+
llm_response = ""
|
|
210
|
+
tool_calls = []
|
|
211
|
+
|
|
212
|
+
async for chunk in self.llm.stream(full_messages, tools=self.tools):
|
|
213
|
+
if chunk.get("type") == "text_delta":
|
|
214
|
+
text = chunk["content"]
|
|
215
|
+
llm_response += text
|
|
216
|
+
yield AgentEvent.llm_delta(text)
|
|
217
|
+
|
|
218
|
+
elif chunk.get("type") == "tool_calls":
|
|
219
|
+
tool_calls = chunk["tool_calls"]
|
|
220
|
+
yield AgentEvent(
|
|
221
|
+
type=AgentEventType.LLM_TOOL_CALLS,
|
|
222
|
+
metadata={"tool_count": len(tool_calls)}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
yield AgentEvent(type=AgentEventType.LLM_COMPLETE)
|
|
226
|
+
|
|
227
|
+
# Phase 4: Tool execution (if needed)
|
|
228
|
+
if tool_calls:
|
|
229
|
+
yield AgentEvent(type=AgentEventType.TOOL_CALLS_START)
|
|
230
|
+
|
|
231
|
+
tool_results = []
|
|
232
|
+
|
|
233
|
+
for tool_call in tool_calls:
|
|
234
|
+
# TODO: 后续会使用 ToolOrchestrator(Task 2.1)
|
|
235
|
+
# 现在简单顺序执行
|
|
236
|
+
|
|
237
|
+
tool = self.tools[tool_call.name]
|
|
238
|
+
|
|
239
|
+
yield AgentEvent(
|
|
240
|
+
type=AgentEventType.TOOL_EXECUTION_START,
|
|
241
|
+
tool_call=ToolCall(
|
|
242
|
+
id=tool_call.id,
|
|
243
|
+
name=tool_call.name,
|
|
244
|
+
arguments=tool_call.arguments
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
result_content = await tool.execute(tool_call.arguments)
|
|
250
|
+
|
|
251
|
+
result = ToolResult(
|
|
252
|
+
tool_call_id=tool_call.id,
|
|
253
|
+
tool_name=tool_call.name,
|
|
254
|
+
content=result_content,
|
|
255
|
+
is_error=False
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
yield AgentEvent.tool_result(result)
|
|
259
|
+
tool_results.append(result)
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
result = ToolResult(
|
|
263
|
+
tool_call_id=tool_call.id,
|
|
264
|
+
tool_name=tool_call.name,
|
|
265
|
+
content=str(e),
|
|
266
|
+
is_error=True
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
yield AgentEvent(
|
|
270
|
+
type=AgentEventType.TOOL_ERROR,
|
|
271
|
+
tool_result=result,
|
|
272
|
+
error=e
|
|
273
|
+
)
|
|
274
|
+
tool_results.append(result)
|
|
275
|
+
|
|
276
|
+
# Phase 5: Recursion (if tools were executed)
|
|
277
|
+
# 创建新消息包含工具结果
|
|
278
|
+
new_messages = [
|
|
279
|
+
*messages,
|
|
280
|
+
Message(role="assistant", content=llm_response, tool_calls=tool_calls),
|
|
281
|
+
*[Message(role="tool", content=r.content, tool_call_id=r.tool_call_id)
|
|
282
|
+
for r in tool_results]
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
# 递归调用
|
|
286
|
+
new_turn_state = TurnState(
|
|
287
|
+
turn_counter=turn_state.turn_counter + 1,
|
|
288
|
+
turn_id=turn_state.turn_id
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
async for event in self.execute_stream(new_messages, new_turn_state):
|
|
292
|
+
yield event
|
|
293
|
+
|
|
294
|
+
else:
|
|
295
|
+
# Phase 5: Finish (no tools)
|
|
296
|
+
yield AgentEvent.agent_finish(llm_response)
|
|
297
|
+
|
|
298
|
+
# 保留向后兼容方法
|
|
299
|
+
async def execute(self, user_input: str) -> str:
|
|
300
|
+
"""Legacy method - wraps execute_stream"""
|
|
301
|
+
messages = [Message(role="user", content=user_input)]
|
|
302
|
+
turn_state = TurnState(turn_counter=0, turn_id=str(uuid.uuid4()))
|
|
303
|
+
|
|
304
|
+
final_response = ""
|
|
305
|
+
async for event in self.execute_stream(messages, turn_state):
|
|
306
|
+
if event.type == AgentEventType.AGENT_FINISH:
|
|
307
|
+
return event.content or final_response
|
|
308
|
+
elif event.type == AgentEventType.LLM_DELTA:
|
|
309
|
+
final_response += event.content or ""
|
|
310
|
+
|
|
311
|
+
return final_response
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
### Step 3: 修改 LLM 接口(可选)
|
|
317
|
+
|
|
318
|
+
**文件**: `loom/interfaces/llm.py`
|
|
319
|
+
|
|
320
|
+
如果 LLM 接口不支持流式工具调用,可以添加 fallback:
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
class BaseLLM(ABC):
|
|
324
|
+
# 现有方法...
|
|
325
|
+
|
|
326
|
+
async def stream(
|
|
327
|
+
self,
|
|
328
|
+
messages: List[Dict],
|
|
329
|
+
tools: List[Dict] = None
|
|
330
|
+
) -> AsyncGenerator[Dict, None]:
|
|
331
|
+
"""
|
|
332
|
+
Stream LLM response.
|
|
333
|
+
|
|
334
|
+
Yields:
|
|
335
|
+
Dict with one of:
|
|
336
|
+
- {"type": "text_delta", "content": "..."}
|
|
337
|
+
- {"type": "tool_calls", "tool_calls": [...]}
|
|
338
|
+
"""
|
|
339
|
+
# Default implementation: fallback to non-streaming
|
|
340
|
+
response = await self.generate_with_tools(messages, tools)
|
|
341
|
+
|
|
342
|
+
# Yield as single chunk
|
|
343
|
+
if "tool_calls" in response:
|
|
344
|
+
yield {"type": "tool_calls", "tool_calls": response["tool_calls"]}
|
|
345
|
+
else:
|
|
346
|
+
yield {"type": "text_delta", "content": response["content"]}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 🧪 测试要求
|
|
352
|
+
|
|
353
|
+
### 单元测试
|
|
354
|
+
|
|
355
|
+
**文件**: `tests/unit/test_streaming_api.py`
|
|
356
|
+
|
|
357
|
+
测试用例:
|
|
358
|
+
|
|
359
|
+
1. ✅ `test_agent_execute_returns_generator` - 验证返回类型
|
|
360
|
+
2. ✅ `test_agent_run_backward_compatible` - 验证向后兼容
|
|
361
|
+
3. ✅ `test_llm_delta_events` - 验证 LLM 流式事件
|
|
362
|
+
4. ✅ `test_tool_execution_events` - 验证工具执行事件
|
|
363
|
+
5. ✅ `test_iteration_events` - 验证迭代事件
|
|
364
|
+
6. ✅ `test_error_propagation` - 验证错误处理
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
import pytest
|
|
368
|
+
from loom import Agent
|
|
369
|
+
from loom.core.events import AgentEventType, EventCollector
|
|
370
|
+
|
|
371
|
+
@pytest.mark.asyncio
|
|
372
|
+
async def test_agent_execute_returns_generator():
|
|
373
|
+
"""Test that execute() returns AsyncGenerator"""
|
|
374
|
+
agent = Agent(llm=mock_llm, tools=[])
|
|
375
|
+
|
|
376
|
+
result = agent.execute("test")
|
|
377
|
+
|
|
378
|
+
# Should be async generator
|
|
379
|
+
assert hasattr(result, '__aiter__')
|
|
380
|
+
|
|
381
|
+
@pytest.mark.asyncio
|
|
382
|
+
async def test_agent_run_backward_compatible():
|
|
383
|
+
"""Test that run() still works"""
|
|
384
|
+
agent = Agent(llm=mock_llm, tools=[])
|
|
385
|
+
|
|
386
|
+
result = await agent.run("test")
|
|
387
|
+
|
|
388
|
+
# Should return string
|
|
389
|
+
assert isinstance(result, str)
|
|
390
|
+
|
|
391
|
+
@pytest.mark.asyncio
|
|
392
|
+
async def test_llm_delta_events():
|
|
393
|
+
"""Test LLM streaming produces delta events"""
|
|
394
|
+
agent = Agent(llm=streaming_mock_llm, tools=[])
|
|
395
|
+
collector = EventCollector()
|
|
396
|
+
|
|
397
|
+
async for event in agent.execute("test"):
|
|
398
|
+
collector.add(event)
|
|
399
|
+
|
|
400
|
+
# Should have LLM_START, LLM_DELTA, LLM_COMPLETE
|
|
401
|
+
assert any(e.type == AgentEventType.LLM_START for e in collector.events)
|
|
402
|
+
assert any(e.type == AgentEventType.LLM_DELTA for e in collector.events)
|
|
403
|
+
assert any(e.type == AgentEventType.LLM_COMPLETE for e in collector.events)
|
|
404
|
+
|
|
405
|
+
# Reconstructed content should match
|
|
406
|
+
llm_content = collector.get_llm_content()
|
|
407
|
+
assert len(llm_content) > 0
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### 集成测试
|
|
411
|
+
|
|
412
|
+
**文件**: `tests/integration/test_agent_streaming.py`
|
|
413
|
+
|
|
414
|
+
测试真实场景:
|
|
415
|
+
|
|
416
|
+
1. ✅ End-to-end 流式执行
|
|
417
|
+
2. ✅ 工具调用 + 递归
|
|
418
|
+
3. ✅ RAG 集成(如果启用)
|
|
419
|
+
4. ✅ 错误恢复
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## ✅ 验收标准
|
|
424
|
+
|
|
425
|
+
| 标准 | 要求 | 检查 |
|
|
426
|
+
|------|------|------|
|
|
427
|
+
| API 修改 | `execute()` 返回 `AsyncGenerator[AgentEvent]` | [ ] |
|
|
428
|
+
| 向后兼容 | `run()` 方法仍工作 | [ ] |
|
|
429
|
+
| 事件产生 | 产生所有必需事件类型 | [ ] |
|
|
430
|
+
| 测试覆盖 | ≥ 80% 覆盖率 | [ ] |
|
|
431
|
+
| 所有测试通过 | 单元 + 集成测试 | [ ] |
|
|
432
|
+
| 文档更新 | 更新 API 文档和示例 | [ ] |
|
|
433
|
+
| 性能 | 无明显性能下降 | [ ] |
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## 📋 实施检查清单
|
|
438
|
+
|
|
439
|
+
### 代码修改
|
|
440
|
+
|
|
441
|
+
- [ ] 修改 `loom/components/agent.py`
|
|
442
|
+
- [ ] 新增 `execute()` 方法(返回 AsyncGenerator)
|
|
443
|
+
- [ ] 修改 `run()` 为包装方法(向后兼容)
|
|
444
|
+
- [ ] 添加 `TurnState` 数据类
|
|
445
|
+
|
|
446
|
+
- [ ] 修改 `loom/core/agent_executor.py`
|
|
447
|
+
- [ ] 新增 `execute_stream()` 方法
|
|
448
|
+
- [ ] 产生所有必需事件
|
|
449
|
+
- [ ] 保留 `execute()` 向后兼容
|
|
450
|
+
|
|
451
|
+
- [ ] 修改 `loom/interfaces/llm.py` (可选)
|
|
452
|
+
- [ ] 添加 `stream()` 方法
|
|
453
|
+
|
|
454
|
+
### 测试
|
|
455
|
+
|
|
456
|
+
- [ ] 创建 `tests/unit/test_streaming_api.py`
|
|
457
|
+
- [ ] 6+ 个单元测试
|
|
458
|
+
- [ ] Mock LLM 和 Tools
|
|
459
|
+
|
|
460
|
+
- [ ] 创建 `tests/integration/test_agent_streaming.py`
|
|
461
|
+
- [ ] End-to-end 测试
|
|
462
|
+
- [ ] 真实 LLM 集成(可选)
|
|
463
|
+
|
|
464
|
+
- [ ] 运行所有测试
|
|
465
|
+
```bash
|
|
466
|
+
pytest tests/unit/test_streaming_api.py -v
|
|
467
|
+
pytest tests/integration/test_agent_streaming.py -v
|
|
468
|
+
pytest tests/ -v # 确保没有破坏现有测试
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### 文档
|
|
472
|
+
|
|
473
|
+
- [ ] 更新 `docs/api_reference.md`
|
|
474
|
+
- [ ] 记录新的 `execute()` API
|
|
475
|
+
- [ ] 记录事件流模式
|
|
476
|
+
|
|
477
|
+
- [ ] 更新 `README.md`
|
|
478
|
+
- [ ] 添加流式 API 示例
|
|
479
|
+
|
|
480
|
+
- [ ] 创建 `examples/streaming_example.py`
|
|
481
|
+
- [ ] 基础流式示例
|
|
482
|
+
- [ ] 工具执行示例
|
|
483
|
+
- [ ] 错误处理示例
|
|
484
|
+
|
|
485
|
+
### 完成
|
|
486
|
+
|
|
487
|
+
- [ ] 创建 `docs/TASK_1.2_COMPLETION_SUMMARY.md`
|
|
488
|
+
- [ ] 更新 `LOOM_2.0_DEVELOPMENT_PLAN.md`
|
|
489
|
+
- [ ] 更新 `loom/tasks/README.md`
|
|
490
|
+
- [ ] 代码审查
|
|
491
|
+
- [ ] 合并到主分支
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 🔗 参考资源
|
|
496
|
+
|
|
497
|
+
- [Task 1.1: AgentEvent 模型](task_1.1_agent_events.md)
|
|
498
|
+
- [AgentEvent 使用指南](../../../docs/agent_events_guide.md)
|
|
499
|
+
- [Claude Code 控制流程](../../../cc分析/Control%20Flow.md)
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## 📝 注意事项
|
|
504
|
+
|
|
505
|
+
### 关键决策
|
|
506
|
+
|
|
507
|
+
1. **递归 vs 循环**: 当前使用递归实现多轮对话,后续可能改为循环(Task 3.3)
|
|
508
|
+
2. **工具执行**: 暂时顺序执行,Task 2.1 会改为智能并行
|
|
509
|
+
3. **RAG 注入**: 暂时保留原有逻辑,Task 1.3 会修复
|
|
510
|
+
|
|
511
|
+
### 潜在问题
|
|
512
|
+
|
|
513
|
+
1. **性能**: 流式可能引入轻微延迟,需要性能测试
|
|
514
|
+
2. **错误处理**: 确保错误正确传播到事件流
|
|
515
|
+
3. **内存**: 大量事件可能占用内存,需要考虑事件限流
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
**创建日期**: 2025-10-25
|
|
520
|
+
**预计开始**: 2025-10-26
|
|
521
|
+
**预计完成**: 2025-10-27
|