尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

手写 Agentic Loop:用 while 循环把函数调用变成真正的 Agent(LLM Zoomcamp 实战)

手写 Agentic Loop:用 while 循环把函数调用变成真正的 Agent(LLM Zoomcamp 实战) 手写 Agentic Loop用 while 循环把函数调用变成真正的 AgentLLM Zoomcamp 实战【免费下载链接】llm-zoomcampLLM Zoomcamp - a free online course about real-life applications of LLMs. In 10 weeks you will learn how to build an AI system that answers questions about your knowledge base. Register here 项目地址: https://gitcode.com/GitHub_Trending/ll/llm-zoomcamp在 LLM Zoomcamp 的 2026 课程中上一课 13-function-calling.md 演示了手动函数调用发送一条消息、拿到一次函数调用、执行、把结果发回去、再拿到答案。这种方式对单次调用可行但当模型想要连续搜索多次、或第一次搜索没命中时就会失效——因为我们事先无法知道模型到底想调用几次工具。本篇以课程文档《The Agentic Loop》为核心手把手用while循环把函数调用串成完整的 Agent 循环并对照仓库源码agents.ipynb、rag_helper.py说明其底层原理。读完你不仅能写出可复用的agent_loop函数还能理解所有 Agent 框架LangChain、PydanticAI、OpenAI Agents SDK 等背后隐藏的同一套模式。为什么需要 Agentic Loop单次函数调用的局限在手动函数调用场景中我们完整地经历了模型决定调用search→ 我们执行搜索 → 把结果送回 → 模型给出最终答案的完整回合整个流程被拆成了两次 API 调用。这正是普通 RAG 和 Agent 的分水岭RAG 管道是固定的——搜索、构建 prompt、调用 LLM开发者提前定死了步骤搜索永远只跑一次且永远使用用户的原始 queryAgent 则把 LLM 放到驾驶位——由它决定何时搜索、搜索什么、何时停止。手动方式的问题在于它只适用于恰好一次函数调用的场景。而真实场景中模型可能想连续搜索多次第一次搜索可能因为错别字如 Olama vs Ollama或措辞偏差而一无所获模型需要根据第一次搜索结果调整关键词再做第二次搜索。这些情况我们无法在写代码时预知调用次数。Agentic Loop 的答案就是一个持续调用模型、执行工具、直到模型主动停下来的循环。Agent 本质上就是这个循环。在仓库中可以看到这条演进线索11-agents-intro.md 说明了为什么要从固定管道转向 Agent13-function-calling.md 教会了单回合的函数调用机制而本篇的 Agentic Loop 正是把单回合推广到多回合。Agent 的三要素指令、工具、记忆让 LLM 坐上驾驶位我们就得到了一个 Agent——一个以帮助用户为目标的 AI 助手。它由三个部分组成要素作用在代码中的形态Instructions指令定义 Agent 的角色与行为方式指令质量直接决定助手的表现以developer角色消息传入Tools工具Agent 可以调用的函数用于执行具体任务本课程中只有searchMemory记忆消息历史Agent 靠它知道自己已经尝试过什么每次追加 prompt、模型输出与工具结果的消息列表值得注意的是记忆的实现方式LLM 在两次 API 调用之间是无状态的所谓记忆就是每次请求时通过input传入的完整消息列表。Agent 每执行一步我们都把这一轮的输出追加进去下一次调用就能看到之前的所有尝试。下面所有代码都是把这三个要素在一个循环里串起来的实现。编写 Developer 提示词先给 Agent 定义角色到目前为止是否搜索、搜什么完全由模型自己揣摩。为了让行为更可靠我们用一条developer消息把期望的行为明确写出来这也是给 Agent 赋予角色的地方。这条消息同时会推动模型进行多次搜索这样我们就能在运行循环时观察到多轮迭代instructions Youre a course teaching assistant. Youre given a question from a course student and your task is to answer it. If you want to look up information, use the search function. Use as many keywords from the user question as possible when making first requests. Make multiple searches. Try to expand your search by using new keywords based on the results you get from the search. At the end, ask if there are other areas that the user wants to explore. .strip()这段提示词包含了几个关键设计角色设定course teaching assistant让模型进入助教身份首次搜索策略Use as many keywords from the user question as possible鼓励首次搜索尽量穷尽原问题中的关键词多轮搜索策略Make multiple searches 与 expand your search by using new keywords based on the results明确要求模型根据结果扩展关键词收尾行为最后询问用户是否还有其他想探索的领域这是课程助手应有的交互习惯。在仓库的 agents.ipynb 中这段提示词作为instructions变量原样出现并被放进{role: developer, content: instructions}消息中。这也呼应了 rag_helper.py 里RAGBase的做法——把instructions作为developer消息、prompt作为user消息传给模型只是 RAG 场景下指令偏向只依据 CONTEXT 回答。函数调用辅助函数make_call循环里会反复执行函数调用因此我们把解析参数 → 调用函数 → 序列化结果封装成一个小助手。目前只有一个工具所以直接按函数名分发def make_call(call): args json.loads(call.arguments) if call.name search: result search(**args) result_json json.dumps(result, indent2) return { type: function_call_output, call_id: call.call_id, output: result_json, }这个助手返回的正是 Responses API 期待的结构type固定为function_call_outputcall_id把工具结果与模型请求的特定函数调用关联起来如果一轮中模型发起了多个函数调用每个都有自己的call_idoutput是序列化后的搜索结果 JSON。以后每增加一个工具只需在这个函数里加一个if分支或改用注册表分发机制。在 agents.ipynb 中make_call的实现与本课文档完全一致。这里的search函数来自上一课直接查询 minsearch 索引见 ingest.py 中的build_indexdef search(query): boost_dict {question: 3.0, section: 0.5} filter_dict {course: llm-zoomcamp} return index.search( query, num_results5, boost_dictboost_dict, filter_dictfilter_dict )其中boost_dict让question字段权重3.0高于section0.5filter_dict把结果限定在当前课程course: llm-zoomcampnum_results5控制返回条数——这些参数与 rag_helper.py 中RAGBase.search的默认配置保持一致可从中推断这是课程统一的检索配置。处理单次响应把模型输出与工具结果都追加进对话现在处理一次模型响应。我们把响应中的每条输出都追加到对话里打印消息内容并执行所有函数调用函数调用的结果同样被追加进对话question I just discovered the course. Can I join it? messages [ {role: developer, content: instructions}, {role: user, content: question}, ] response openai_client.responses.create( modelgpt-5.4-mini, inputmessages, tools[search_tool], ) messages.extend(response.output) has_function_calls False for item in response.output: if item.type function_call: print(function_call:, item.name, item.arguments) call_output make_call(item) messages.append(call_output) has_function_calls True elif item.type message: print(ASSISTANT:) print(item.content[0].text)关键细节解析messages.extend(response.output)先把模型本次的全部输出包括函数调用条目追加进历史——模型需要看到它自己提出的函数调用遍历response.output时按item.type分派function_call类型执行工具、message类型打印助手文本has_function_calls标志记录本轮是否出现了函数调用它决定是否需要再发起一次 API 请求。从 agents.ipynb 中可以看到这个流程的实际运行痕迹模型把 I just discovered the course. Can I join it? 改写成了类似join course discovered late can I join enroll late join course的搜索关键词这说明模型不会原样照搬用户问题它会自主改写 query 以提高检索命中率。而工具返回的结果是包含id、course、section、question、answer五个字段的 FAQ 条目数组模型正是基于这些字段组织最终答案。完整 Agent 循环while True 直到模型不再调用工具把上面的处理逻辑包进while循环循环会持续调用模型直到它返回一个不含任何函数调用的响应为止。同时维护一个迭代计数器方便观察发生了多少次往返it 1 while True: print(fiteration #{it}...) has_function_calls False response openai_client.responses.create( modelgpt-5.4-mini, inputmessages, tools[search_tool], ) messages.extend(response.output) for item in response.output: if item.type function_call: print(function_call:, item.name, item.arguments) call_output make_call(item) messages.append(call_output) has_function_calls True elif item.type message: print(ASSISTANT:) print(item.content[0].text) it it 1 if has_function_calls False: break这就是 Agent 循环的核心模型负责推理下一步行动你的代码负责执行模型在下一轮看到执行结果。当模型返回最终答案、不再请求工具时循环结束。关于这个循环有三点值得深入搜索次数不由我们决定模型搜几次、搜什么都由它自己决定我们只是持续循环直到它停止请求工具退出条件是最简单的一种本轮没有函数调用即结束。从代码结构看has_function_calls这个布尔标志就是整个循环的刹车片生产环境需要安全网文档明确提示其他框架会在其上叠加安全措施——最大迭代次数比如最多 5 轮、最后一轮强制给出答案、token 预算、墙钟时间限制等但核心依然是这一个标志位。在 agents.ipynb 中可以找到这段循环的逐行实现以及一次真实的运行输出iteration #1中模型发起了三次search调用分别为课程加入、新生晚加入、课程访问截止日期等不同关键词iteration #2中模型直接给出了最终答案——Yes — you can still join the course. If you want a certificate, make sure to submit your project while submissions are still open...。封装为可复用的 agent_loop 函数把循环包进函数接受指令和问题作为参数返回最终答案这样就能反复使用def agent_loop(instructions, question, modelgpt-5.4-mini) - str: messages [ {role: developer, content: instructions}, {role: user, content: question} ] it 1 while True: print(fiteration #{it}...) has_function_calls False response openai_client.responses.create( modelmodel, inputmessages, tools[search_tool] ) messages.extend(response.output) for item in response.output: if item.type function_call: print(function_call:, item.name, item.arguments) call_output make_call(item) messages.append(call_output) has_function_calls True elif item.type message: print(ASSISTANT:) last_answer item.content[0].text print(item.content[0].text) it it 1 if has_function_calls False: break return last_answer注意与裸循环的两处差异model作为可配置参数默认gpt-5.4-mini以及在message分支里用last_answer记录最后的文本输出供函数返回。用带错别字的问题试试agent_loop(instructions, How do I run Olama locally?)观察运行过程Agent 先搜索 Olama结果很差随后它用 Ollama 再次搜索并找到了答案。循环让模型自己从一次糟糕的搜索中恢复过来——这正是走向 Agentic 的全部意义。在 agents.ipynb 中可以看到同样的恢复过程第一轮搜索 Olama locally run install local FAQ 返回的多是无关条目第二轮搜索 Ollama run llama3 local server localhost:11434 FAQ 才命中 Ollama 安装 FAQ最终给出分步骤的完整回答。再试试课程报名问题agent_loop(instructions, I just discovered the course. Can I still join it?)用指令鼓励多次搜索这里有一个微妙的问题模型经常在第一次搜索后就给出答案即使更多搜索会更有帮助——它认为自己知道得够多了。为了推动它更深入探索我们改写指令instructions Youre a course teaching assistant. Youre given a question from a course student and your task is to answer it. If you want to look up information, use the search function. Use as many keywords from the user question as possible when making first requests. Make multiple searches. First perform search, analyze the results and then perform more searches. At the end, ask if there are other areas that the user wants to explore. .strip() agent_loop(instructions, I just discovered the course. Can I join it?)关键改动在 Make multiple searches. First perform search, analyze the results and then perform more searches.——明确要求先搜索、分析结果、再继续搜索。改完后Agent 会针对每个问题发起多次搜索而不是在第一轮结果后就收手。指令是我们操控 Agent 的主要手段但要清醒认识到模型有时仍会跳过某些步骤不要指望它每次运行都严格照做。在 agents.ipynb 的运行记录中可以看到这一版指令的实际效果报名问题上iteration #1搜索了 join course discovered course can I join enrollment late joining FAQiteration #2搜索了 new student can I join course after start FAQ enrollment 与 course access enrollment deadline can join FAQ三轮搜索后才进入最终回答阶段。限制话题范围轻量级的输入护栏目前的 Agent 有问必答。问它国际象棋的事它照样会尝试回答agent_loop(instructions, whats queen gambit?)但我们想要的是课程助手而不是通用聊天机器人。于是收紧指令让 Agent 只从 FAQ 回答问题。对我们的自有场景让它基于通用知识作答或许也无妨所以这里主要作为如何通过指令划定范围的示例instructions Youre a course teaching assistant. Youre given a question from a course student and your task is to answer it. If you want to look up information, use the search function. Use as many keywords from the user question as possible when making first requests. Make multiple searches. First perform search, analyze the results and then perform more searches. The question has to be about the course or its logistics, offtopic questions shouldnt be answered. If the search returns nothing, its likely an off-topic question. If you cant answer the question using FAQ, dont do it yourself. Only use the facts from the FAQ database. At the end, ask if there are other areas that the user wants to explore. .strip() agent_loop(instructions, whats queen gambit?)新增的两条规则非常关键范围判定The question has to be about the course or its logistics, offtopic questions shouldnt be answered. If the search returns nothing, its likely an off-topic question.——用搜索无结果作为离题问题的信号禁止自由发挥If you cant answer the question using FAQ, dont do it yourself. Only use the facts from the FAQ database.——防止模型用通用知识编造答案。在 agents.ipynb 中可以找到这版指令的实测输出Agent 先搜索 queen gambit再搜索 gambit chess opening queens gambit course FAQ随后回答 I couldnt find a course FAQ entry for queen gambit, so it looks like this may be off-topic for the course.并且明确表示 I cant answer outside the course FAQ。这其实就是一种轻量级的输入护栏input guardrail通过指令告诉 Agent 什么在范围内、什么不在。真正的护栏会在 Agent 运行之前检查输入直接拦截离题问题——那是另一个主题但指令是入手的第一步。手写循环的意义所有框架都隐藏着同一个模式这个手写循环是理解框架背后机制的最佳途径。每一个 Agent 框架——无论是 LangChain、PydanticAI还是 OpenAI Agents SDK——本质上都包装了同样的模式while True循环调用模型、处理函数调用、把工具结果加回消息历史、直到模型停止请求工具。下一课 15-frameworks.md 正好印证了这一点课程引入的 ToyAIKit 库做的事情和我们的手写循环一样但样板代码更少并且明确说明如果你打开它的runners代码会找到我们手写的一模一样的while True循环。区别只在于框架把make_call的分发、消息管理、迭代安全网都替你封装好了还顺带统计了 token 用量与成本。成本提醒每次循环迭代都是一次付费 API 调用而且后续调用会把完整历史作为输入重发所以越到后面输入 token 越多、单次成本越高。上一课 13-function-calling.md 中专门演示了如何读取response.usageinput_tokens与output_tokens并结合每百万 token 单价估算成本——真实 Agent 循环可能发起多次调用开发时务必留意usage字段。小结本文从单次函数调用不够用出发完整走过了 Agentic Loop 的构建历程三要素指令developer消息、工具search、记忆消息历史辅助函数make_call把函数调用转换成 Responses API 期望的function_call_output结构循环本体while True反复调用模型、执行工具、追加历史直到has_function_calls为 False封装复用agent_loop(instructions, question, model)一行即可运行错别字问题Olama → Ollama被模型自主修复指令即控制通过改写指令推动多次搜索、收紧话题范围实现轻量级输入护栏。配套的完整可运行代码见 agents.ipynb检索基础与 RAG 基类见 ingest.py 与 rag_helper.py。理解了这一个循环你就拿到了阅读任何 Agent 框架源码的钥匙。【免费下载链接】llm-zoomcampLLM Zoomcamp - a free online course about real-life applications of LLMs. In 10 weeks you will learn how to build an AI system that answers questions about your knowledge base. Register here 项目地址: https://gitcode.com/GitHub_Trending/ll/llm-zoomcamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表