【S01】开源项目learn-claude-code学习
S01: Agent Loop — 一个循环就够了问题当我们提出一个问题给大模型“帮我读取我当前目录有哪些文件并执行start.bat”模型能输出一条bash命令但输出完就停了它不会去跑这条命令也看不到执行后的结果再继续推理这些都需要用户自己手动跑并将结果复制给大模型让它接着干。等下一个命令出来再跑下一个命令再贴回去每个来回都由人来做中间层把人从中解放出来就是我们接下来要做的解决方案使用一个while True 循环模型调用工具就继续不调用工具就停。循环通过检查响应块中内进行判断信号含义循环动作包含tool_use block模型要求调用工具执行-结果喂回去-继续不包含tool_use_block模型没有调用工具结束循环工作原理将方案转换成代码分步来看看简单写个伪代码演示一下第1步 把用户问题加入到消息队列中messages[{role:user,content:query}]第2步将消息和工具定义一起发给LLMresponseclient.chat.completions.create(modeldeepseek-flash,messagesmessages,#type: ignoretoolsTOOLS,max_tokens8000)第3步追加模型回答检查它是否调用了工具。没调-结束messages.append({role:assistant,content:response.content})tool_calls[blockforblockinresponse.contentifblock.typetool_use]ifnottool_calls:return只有实际存在tool_calls block才会进入执行阶段因此不会追加空的工具结果消息第4步执行模型要求工具收集结果results[]forblockintool_calls:outputrun_bash(block.input[command])results.append({type:tool_result,tool_use_id:block.id,content:output})第5步把工具结果作为新新消息追加回到第2步messages.append(role:user,content:results})组装成一个完整函数defagent_loop(messages):whileTrue:responseclient.chat.completions.create(modeldeepseek-flash,messagesmessages,#type: ignoretoolsTOOLS,max_tokens8000)messages.append({role:assistant,content:response.content})tool_calls[blockforblockinresponse.contentifblock.typetool_use]ifnottool_calls:returnresults[]forblockintool_calls:outputrun_bash(block.input[command])results.append({type:tool_result,tool_use_id:block.id,content:output})messages.append(role:user,content:results})三十多行这就是最小可运行的agent harness内核。它为模型提供持续行动的最小运行框架模型负责决策harness负责执行。后面的16个章节也都说在这个循环上叠加的循环始终不变试一试这是整个代码我把原cc格式改成openai格式#!/usr/bin/env python3 s01_agent_loop.py - The Agent Loop (OpenAI format) while True: response LLM(messages, tools) if response contains no tool_calls: break execute tools append tool results Usage: pip install openai python-dotenv DEEPSEEK_URL... DEEPSEEK_API_KEY... MODEL_ID... python s01_agent_loop/code.py importjsonimportosimportsubprocesstry:importreadline readline.parse_and_bind(set bind-tty-special-chars off)readline.parse_and_bind(set input-meta on)readline.parse_and_bind(set output-meta on)readline.parse_and_bind(set convert-meta off)exceptImportError:passfromopenaiimportOpenAIfromdotenvimportload_dotenv load_dotenv(overrideTrue)clientOpenAI(base_urlos.getenv(DEEPSEEK_URL),api_keyos.getenv(DEEPSEEK_API_KEY))MODELos.environ[MODEL_ID]ENVIRONMENT_PROMPT(Windows: the bash tool runs through cmd.exe; use cmd.exe syntax, not Unix Bash or PowerShell syntaxifos.namentelseUnix-like: the bash tool runs the system shell)SYSTEM(fYou are a coding agent at{os.getcwd()}. Environment:{ENVIRONMENT_PROMPT}. Use bash to solve tasks. Act, dont explain.)# -- Tool definition: OpenAI function-calling format --TOOLS[{type:function,function:{name:bash,description:Run a shell command.,parameters:{type:object,properties:{command:{type:string}},required:[command],},},}]# -- Tool execution (unchanged) --defrun_bash(command:str)-str:dangerous[rm -rf /,sudo,shutdown,reboot, /dev/]ifany(dincommandfordindangerous):returnError: Dangerous command blockedtry:rsubprocess.run(command,shellTrue,cwdos.getcwd(),capture_outputTrue,textTrue,errorsreplace,timeout120)out(r.stdoutr.stderr).strip()returnout[:50000]ifoutelse(no output)exceptsubprocess.TimeoutExpired:returnError: Timeout (120s)except(FileNotFoundError,OSError)ase:returnfError:{e}# -- The core loop: call tools until the model stops --defagent_loop(messages:list):whileTrue:responseclient.chat.completions.create(modelMODEL,messagesmessages,toolsTOOLS,max_tokens8000,)msgresponse.choices[0].message# 1) 回填 assistant 消息如果有 tool_calls 必须一并回填assistant_msg{role:assistant,content:msg.content}ifmsg.tool_calls:assistant_msg[tool_calls][{id:c.id,type:function,function:{name:c.function.name,arguments:c.function.arguments,# JSON 字符串原样回填},}forcinmsg.tool_calls]messages.append(assistant_msg)# 2) 没有工具调用 → 结束ifnotmsg.tool_calls:return# 3) 执行每个工具调用并把结果以 roletool 回填forcallinmsg.tool_calls:try:argsjson.loads(call.function.argumentsor{})exceptjson.JSONDecodeError:args{}commandargs.get(command,)print(f\033[33m${command}\033[0m)outputrun_bash(command)print(output[:200])messages.append({role:tool,tool_call_id:call.id,content:output,})# -- Entry point --if__name____main__:print(s01: Agent Loop)print(Enter a question, press Enter to send. Type q to quit.\n)history[{role:system,content:SYSTEM}]whileTrue:try:queryinput(\001\033[36m\002s01 \001\033[0m\002)except(EOFError,KeyboardInterrupt):breakifquery.strip().lower()in(q,exit,):breakhistory.append({role:user,content:query})agent_loop(history)# agent_loop 正常返回时最后一条一定是没有 tool_calls 的 assistant 消息finalhistory[-1]iffinal.get(role)assistantandfinal.get(content):print(final[content])print()试试这些 promptCreate a file called hello.py that prints Hello, World!List all Python files in this directoryWhat is the current git branch?观察重点模型什么时候调用工具循环继续什么时候不调用循环结束代码源码地址https://github.com/shareAI-lab/learn-claude-code蓝奏云地址https://wwbfs.lanzouv.com/iva494dg0h4f密码:cc39