Repository navigation
Expand file tree
/
Copy pathstep1_loop.py
More file actions
102 lines (82 loc) · 4.45 KB
/
Copy pathstep1_loop.py
File metadata and controls
102 lines (82 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""
step1_loop.py —— 【入门】Agent 的"心跳循环"
这是整个 agent 最核心、最本质的东西:一个 while(True) 循环。
一句话概括 agent:把"模型 + 工具"塞进一个循环里,让模型反复地
"思考 → 调工具 → 看结果 → 再思考",直到它认为任务完成、给出最终答案。
对账 opencode(commit 518772c):
- 循环本体 while(true) → packages/opencode/src/session/prompt.ts L1088-L1096
- 循环内调用模型 → packages/opencode/src/session/prompt.ts L1272-L1286
- 终止条件(没有工具调用了)→ packages/opencode/src/session/prompt.ts L1100-L1130
- 步数上限保护 maxSteps → packages/opencode/src/session/prompt.ts L1178-L1181
运行:
cd agent && python3 step1_loop.py "算一下 2*(3+4) 等于多少"
(不配 API key 也能跑,会走 llm.py 里的离线 mock 模式)
"""
from __future__ import annotations
import json
import sys
from llm import chat
from tools import build_registry, dispatch
# 系统提示词:给 agent 立"人设"和"纪律"。
# 对账 opencode:真实的系统提示词见 packages/opencode/src/session/prompt/default.txt、beast.txt,
# 由 packages/opencode/src/session/system.ts L26-L40 按模型选择。
SYSTEM_PROMPT = (
"你是一个能干活的助理。你可以调用工具来完成任务。"
"当你需要计算、读文件、列目录或执行命令时,就调用对应的工具。"
"拿到工具结果后,如果任务完成了,就直接用自然语言回答用户,不要再调用工具。"
)
# 步数上限:防止模型陷入死循环,无限调工具。
# 对账 opencode:agent.steps ?? Infinity,见 prompt.ts L1178。
MAX_STEPS = 10
def run_agent(user_input: str) -> str:
registry = build_registry()
tools_schema = [t.to_openai_schema() for t in registry.values()]
# 对话历史。注意:模型是"无状态"的,每轮都要把完整历史发过去。
# 对账 opencode:prompt.ts L1257-L1286 每轮 toModelMessagesEffect(msgs) 重发全部历史。
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_input},
]
# ────────────────── 这就是 agent 的"心跳" ──────────────────
for step in range(1, MAX_STEPS + 1):
print(f"\n─── 第 {step} 步:把完整历史发给模型,让它决定下一步 ───")
response = chat(messages, tools_schema)
# 情况 A:模型没有要调工具 → 它给出了最终答案 → 跳出循环。
# 对账 opencode:finish != "tool-calls" && !hasToolCalls 时 break(prompt.ts L1111-L1129)。
if not response.tool_calls:
print("模型没有再调工具,判定任务完成,退出循环。")
return response.content
# 情况 B:模型要调一个或多个工具 → 逐个执行,把结果塞回历史,继续下一轮。
# 先把模型这条"要调工具"的 assistant 消息记入历史(保持上下文连贯)。
messages.append(
{
"role": "assistant",
"content": response.content or None,
"tool_calls": response.tool_calls,
}
)
for call in response.tool_calls:
fn = call["function"]
name = fn["name"]
arguments = fn.get("arguments", "{}")
print(f" → 模型想调用工具: {name}({arguments})")
# 工具路由 + 执行。对账 opencode:tools.ts resolve() 的 execute 回调。
result = dispatch(registry, name, arguments)
print(f" ← 工具返回: {result[:200]}")
# 把工具结果作为一条 role=tool 的消息塞回历史,让模型下一轮能读到。
messages.append(
{
"role": "tool",
"tool_call_id": call["id"],
"content": result,
}
)
# 循环回到顶部,带着"工具结果"再问一次模型。
return "(达到最大步数,强制停止。对账 opencode:isLastStep 时会注入 MAX_STEPS_PROMPT 让模型收尾。)"
if __name__ == "__main__":
query = sys.argv[1] if len(sys.argv) > 1 else "列一下当前目录有哪些文件"
print(f"用户输入:{query}")
answer = run_agent(query)
print("\n" + "=" * 50)
print("最终答案:")
print(answer)