本文通过一个简单的50行Python代码示例详细介绍了如何将语言模型LLM从基础的“聊天机器人”升级为具备推理、行动和决策能力的“智能体”。作者从零开始构建了一个基础的Agent通过接入OpenAI和Ollama本地模型展示了Agent如何通过“思考-行动-观察-决定”的ReAct循环来完成任务。文章还介绍了如何定义工具、使用MCP模型上下文协议从外部服务器发现工具并与Claude CLI进行了对比。最后作者强调了理解Agent底层工作原理的重要性并建议在需要复杂功能时使用LangGraph等框架。我每天都在用 Claude、Codex、Cursor、Gemini、Copilot 或 Junie但我依然说不清到底是哪一行代码让“chatbot”变成了“agent”也解释不了它们为何算是 agent。于是我从零写了一个最天真的版本自己找答案。对我来说理解一个新概念的最好方式就是把它做出来再讲给别人听。本文两者兼顾。我把实验故事和实操教程结合在一起我敢保证你会觉得有用。我们将从区区 50 行 Python 开始先接入 OpenAI再通过 Ollama 切换到本地模型构建本地云端混合模式加入 tools接入 MCP最后和 Claude CLI 做个对比。看完你就能清清楚楚地看到“底层到底发生了什么”。不需要 LangChain。也不需要 LangGraph、CrewAI。只用 Python、一个 LLM再加一个 while 循环。我们要构建什么规格说明在动手之前先定义清楚它是什么、做什么spec。一个 AI agent下文统一用 Agent 指代“智能体”是一个程序它会接受用户给出的高层任务推理“下一步该做什么”采取行动调用 tool、搜索网页、读取文件观察结果决定是继续迭代还是返回最终答案维护对话历史让每次决策都建立在先前步骤之上普通的 LLM 调用是一次性one-shot你发出 prompt它给你回复结束。Agent 不同在于它会循环。它接收高层任务推理下一步调用 action观察结果如此反复直到任务完成。这个“思考-行动-观察-决定”的循环才是把语言模型变成 agent 的关键。多数 Agent 采用一种叫 ReActReason Act的模式。LLM 不会直接跳到最终答案它会先产出“thought想法/计划”再产出“action工具调用”然后等待“observe观察”结果再决定下一步做什么。Figure 1: ReAct 循环——先 Reason 再 Act观察结果再次推理。循环往复直到模型有足够信息可以直接作答。模型没有“意识”也没有任何有意义的“自我反省”。它拥有的只是对话历史它做过的每个动作、得到的每个结果都在 context window上下文窗口里。ReAct 模式把这些变成类似“自我反省/自我纠错”的行为而且确实有效。每一轮循环会发生这些事你把当前对话发给 LLMsystem prompt、用户消息、以及之前的 tool 结果LLM 返回要么是最终答案要么是一组它想调用的 tools如果是最终答案就结束如果是 tool 调用就执行它们把结果追加到对话历史再回到第 1 步这就是全部架构。最小实现云端 API 作为大脑先用云端 API 当大脑。我选了 OpenAI因为它的 tool calling 接口最干净但任何 OpenAI-compatible API 都可以用。Gemini、Anthropic 等也都支持。核心 Agent 机制就是这样def run_agent(task: str, client: OpenAI, model: str gpt-4o-mini) - str: messages [ { role: system, content: ( You are a helpful assistant. Use tools when needed. When you have a final answer, respond without calling any tools. ), }, {role: user, content: task}, ] while True: response client.chat.completions.create( modelmodel, messagesmessages, toolsTOOLS, tool_choiceauto, ) message response.choices[0].message messages.append(message) # This is the decision point: does the model have an answer, or does it need tools? if not message.tool_calls: return message.content # If we reach here, the model called one or more tools for tool_call in message.tool_calls: name tool_call.function.name args json.loads(tool_call.function.arguments) print(f calling {name}({args})) fn TOOL_FUNCTIONS.get(name) result fn(**args) if fn else fUnknown tool: {name} messages.append({ role: tool, tool_call_id: tool_call.id, content: result, }) # End of iteration - go back to the top of the while loop关键行是if not message.tool_calls。如果模型返回的是文本、且没有请求任何 tool就表示它已经具备回答所需的一切。Agent 退出并返回该文本。如果模型请求了 toolAgent 就执行它们把结果追加到对话历史然后把所有内容再发回模型进入下一轮。messages列表就是 Agent 的短期记忆。每次 tool 调用和每个结果都会被追加进去。等 LLM 判断“完成”时它已经“看过”自己做过的所有事和学到的所有信息。system prompt 同样很重要。它是方向盘告诉模型何时用 tool、何时停止、最终答案应该是什么样子。在真实的生产 Agent 中这个 system prompt 往往相当庞大这点从 Anthropic、Apple 等偶尔的泄露中也能看见。定义 Tools先来三个简单的便于具体化当前日期/时间、计算器和天气stub。在真实 Agent 里你会把 stub 换成实际 API 调用。import jsonimport osfrom datetime import datetimefrom openai import OpenAIdef get_current_date() - str: return datetime.now().strftime(%Y-%m-%d %H:%M:%S)def calculate(expression: str) - str: try: result eval(expression, {__builtins__: {}}, {}) return str(result) except Exception as e: return fError: {e}def get_weather(city: str) - str: # Replace with a real weather API call return fWeather in {city}: 72°F, partly cloudyTOOL_FUNCTIONS { get_current_date: get_current_date, calculate: calculate, get_weather: get_weather,}工具 schema 告诉 LLM 有哪些可用的东西。这段 JSON 就是模型在决定调用哪个 tool、用什么参数时所“看到”的内容TOOLS [ { type: function, function: { name: get_current_date, description: Returns the current date and time, parameters: {type: object, properties: {}, required: []}, }, }, { type: function, function: { name: calculate, description: Evaluates a math expression and returns the result, parameters: { type: object, properties: { expression: { type: string, description: A Python math expression, e.g. 2 2 or 100 * 0.15, } }, required: [expression], }, }, }, { type: function, function: { name: get_weather, description: Gets current weather for a city, parameters: { type: object, properties: { city: {type: string, description: City name} }, required: [city], }, }, },]运行if __name__ __main__: client OpenAI(api_keyos.environ[OPENAI_API_KEY]) task Whats todays date? Also, what is 15% of 847? And whats the weather in Tokyo? print(fTask: {task}\n) answer run_agent(task, client) print(f\nAnswer: {answer})输出Task: Whats todays date? Also, what is 15% of 847? And whats the weather in Tokyo? calling get_current_date({}) calling calculate({expression: 847 * 0.15}) calling get_weather({city: Tokyo})Answer: Today is 2026-04-30 09:14:22. 15% of 847 is 127.05.The weather in Tokyo is 72°F and partly cloudy.在第一轮LLM 识别到它需要三个 tool逐个调用、拿到结果然后组装出最终答案。没有框架、没有编排层。用 Ollama 将云端 API 换成本地 LLMOllama 暴露的是 OpenAI-compatible API所以同样的 Agent 代码只需要改一处就能跑在本地模型上ollama_client OpenAI( base_urlhttp://localhost:11434/v1, api_keyollama, # required by the client library, ignored by Ollama)answer run_agent(task, ollama_client, modelqwen2.5)就这样。代码根本不知道它是在和 OpenAI 的服务器对话还是在和你机器上的模型对话。让 Ollama 跑起来# install from ollama.com, then:ollama pull qwen2.5ollama serve之后Agent 就完全离线运行。我用这种方式测试新 tool既不烧 API 点数也能在敏感数据不出本机的场景下使用。不是所有本地模型都支持 tool calling这一点会坑到你。我一开始试了mistralMistral 7B它经常被推荐为很能打的本地模型。Agent 运行没报错但输出类似Answer: I need to call get_current_date() to find todays date.Let me use the calculate tool: calculate(expression847 * 0.15)...纯文字“描述”将要调用哪些 tool但没有真正发出 tool 调用。response.tool_calls每次都是空的于是它立刻带着模型写下的文字就退出了。这不是 Agent 代码的 bug。代码严格按写的逻辑工作检查有无 tool 调用没找到就返回。问题在于 Mistral 7B 并不支持 OpenAI 风格的结构化 function calling。它被训练成用散文“描述”动作而不是产出结构化 JSON。模型“幻写”了它以为我想要的语法。通过 Ollama、能稳定支持 function calling 的模型有Table 1如果你的 Agent 立即返回、从不调用任何 tool先怀疑模型不要怀疑代码。换成qwen2.5试试看看行为有没有变化。构建混合模式本地编排云端委托你可以在本地做编排只有在任务真的需要时才为云端调用买单。默认用本地模型跑 Agent 和简单工具但提供一个 tool把复杂推理问题丢给云端模型。本地模型负责循环和简单工具遇到需要更深推理的内容就委托。def ask_cloud_expert(question: str) - str: Delegate complex questions to a cloud model. cloud_client OpenAI(api_keyos.environ[OPENAI_API_KEY]) response cloud_client.chat.completions.create( modelgpt-4o, messages[{role: user, content: question}], ) return response.choices[0].message.content把它加入TOOL_FUNCTIONS并把对应 schema 加进TOOLS。现在当你运行answer run_agent( taskWhats 22? Also, explain the philosophical implications of the Ship of Theseus paradox., clientollama_client, modelqwen2.5)本地模型会处理22通过 calculator tool并意识到哲学问题超出它的深度于是调用ask_cloud_expert()从 GPT-4 拿到一个像样的答案。你只为一次云端 API 调用买单而不是为数十次买单。增加更多 Tools让我们扩展出更贴近真实世界的工具web_search、read_file和write_file。from pathlib import Pathdef web_search(query: str) - str: # Stub - replace with Brave Search API, SerpAPI, or Tavily return ( fSearch results for {query}:\n f1. Wikipedia: comprehensive overview\n f2. Recent article: explained in 5 minutes\n f3. Official docs )def read_file(path: str) - str: # Safe path validation omitted for brevity return Path(path).read_text()def write_file(path: str, content: str) - str: Path(path).write_text(content) return fwrote {len(content)} chars to {path}TOOL_FUNCTIONS { get_current_date: get_current_date, calculate: calculate, get_weather: get_weather, web_search: web_search, read_file: read_file, write_file: write_file,}把它们的 schema 加进TOOLSAgent 现在就能搜索网页、并把结果持久化到磁盘。上述web_search是 stub文件操作也做了简化。完整项目在 github.com/sergenes/mini_agent 中包含了更严谨的路径校验与错误处理。有了这 6 个 toolAgent 现在可以回答需要当前信息的问题日期/时间进行计算查询天气搜索网页读写文件这已经足够做实事了。剩下的问题是每个 tool 都是硬编码在脚本里的。没法和别的 Agent 共享也没法直接使用别人做好的工具。MCP 客户端从外部服务器发现工具上面的 Agent 缺少一件事跨项目共享/复用工具的能力。一切都硬编码在脚本里。想让另一个 Agent 用同样的工具复制粘贴。想用别人的工具改写。MCPModel Context Protocol模型上下文协议由 Anthropic 于 2024 年 11 月推出它就是为此而生的标准。它定义了一种统一方式让任何 Agent 都能从任何服务器发现并调用工具无论是你自己的还是第三方的 GitHub、Slack、Postgres、Google Drive 等等成百上千的工具。Figure 2: MCP 架构——一个客户端你的 Agent多个服务器。每个服务器暴露自己的工具。Agent 以同样的方式发现和调用它们而不关心服务器背后是什么。你的 DIY Agent 变成一个 MCP 客户端。你不再硬编码 tool 定义而是去调用服务器拿回它暴露出来的工具已经被发现、已经带描述可以直接传给你的 LLM。Agent 的逻辑不变变化的是工具的来源以及谁来维护它们。配套项目里包含mcp_client.py它会把 MCP 服务器以子进程的方式拉起来并通过 JSON-RPC 调用工具。从 Agent 的视角看MCP 工具和本地定义的工具没有任何区别同样出现在TOOLS同样被调用、同样返回结果。关键洞见是Agent 并不在乎一个 tool 是不是同一文件里的 Python 函数还是在互联网上另一端的服务。只要它说 MCP 这门“语言”就能用。MCP 服务器把你的工具暴露给任意 Agent反过来如果你想把自己的工具暴露给所有 MCP-compatible 的 Agent 使用你就写一个 MCP server。下面是一个完整的 MCP 服务器暴露了两个工具——to_uppercase和count_words# mcp_server.py - a real MCP server in 10 linesfrom mcp.server.fastmcp import FastMCPmcp FastMCP(mini-tools)mcp.tool()def to_uppercase(text: str) - str: Convert text to uppercase. return text.upper()mcp.tool()def count_words(text: str) - int: Count the number of words in a string. return len(text.split())if __name__ __main__: mcp.run()我故意让它很“平”重点在边界mcp_server.py是一个独立进程。Agent 调用一个 tool子进程启动、完成 JSON-RPC 握手、结果回传。你完全可以把它换成跑在互联网上另一头的服务器而 Agent 代码一行都不用改。现在任何 MCP-compatible 的 Agent 都可以用你的工具——Claude Desktop、Cursor、你的 DIY Agent谁都行。你发布 server大家在配置里指过去就能直接用。生态就是这样扩展的。不是每个 Agent 都去重复实现“调用 GitHub API”或“查询 Postgres”而是某个人写一个 MCP server大家共用。与 Claude CLI 的对比Claude Code 是生产力工具。我的 Agent 是学习工具。这是诚实的对比也值得弄明白为什么。Claude Code 做了我的 Agent 做不到的事当任务很大时会启动带隔离上下文窗口的子 Agent在执行破坏性命令前会请求确认在会话间保持持久记忆在工具调用失败时会调整参数重试在逼近上下文上限时会压缩历史消息。我的 Agent 全都没有。它只有 6 个工具、一个messages列表没有安全网。工具抛异常它就崩。我的 Agent 的优势是我能读懂它的每一行。出问题时我知道去哪一行看。我可以让它完全离线地用 Ollama 跑或者接混合模式只为确实需要的云端调用付费。Claude Code 是按消息计费。而我的 Agent除非我让它调用 GPT-4否则不花钱。如果我要交付可靠的东西我会用 Claude Code。如果我要搞清楚“底层到底在发生什么”或者要原型实现一些用框架会“打架”的东西我就从这个循环开始。该轮到框架出场的时刻你不需要 LangGraph 才能弄懂什么是 Agent。你需要它是当重试、检查点、审批闸门变得不可或缺的时候。上面的代码没有错误处理。工具抛异常Agent 就崩。没有重试逻辑。没有在风险操作前暂停等待人工批准。除了单条对话没有其它记忆能力。也不会为并行工作生成子 Agent。LangGraph 用“状态机state machine”的方式解决这些问题用显式的节点与边来建模 Agent。你定义每一步做什么、什么条件触发下一步。代价是前期搭建更多但你能得到 checkpointing、结构化错误处理、human-in-the-loop 步骤以及对 Agent 在干什么、为什么这么干的全量可观测性。CrewAI 和 AutoGen 专注多 Agent 协作。不是一个 Agent 用很多工具而是定义多个角色researcher、writer、critic编排它们如何沟通。适合复杂任务需要不同步骤用不同 prompt 或不同模型的场景。Claude Agents SDK 和 OpenAI Assistants API 则是托管运行时你把状态管理、tool 路由、线程等交给平台。控制力少一些但交付更快。50 行版本是一张草图。LangGraph 则是把这张草图变成有“承重墙”的建筑。要做生产用框架。要搞懂本质自己写这个循环。我从这个项目里学到了什么我想弄懂 AI Agent 是如何工作的。现在我懂了。搭起来之后我补齐了之前缺失的心智模型。我能看见 Agent 会卡在哪里、为什么会偏好某个 tool、以及什么时候“加更多工具”反而会变糟。我知道当 Claude Code 启动子 Agent 时发生了什么或者 Cursor 决定重试一次失败操作时发生了什么。我有一些项目需要 agentic behaviorAgent 式行为。有些会用 LangGraph 或 Claude Agents SDK——这些框架确实解决了我不想重复造轮子的真实问题。但也有些会从这 50 行版本起步因为我确切知道它做了什么而且我可以在不与陌生抽象“搏斗”的前提下改它。你现在也看到了没有魔法。模型观察对话历史判断自己是否“够了可以直接回答”或者“还需要一个 tool”然后不断重复直到完成。其它的一切——重试逻辑、human-in-the-loop、记忆、多 Agent 编排——都是在这个循环之上搭起来的。当你去用一个框架时你会知道它在替你做什么。当你不需要它时你也不会引入一个你调不通的依赖。先把天真的版本造出来。再做决定。AI行业迎来前所未有的爆发式增长从DeepSeek百万年薪招聘AI研究员到百度、阿里、腾讯等大厂疯狂布局AI Agent再到国家政策大力扶持数字经济和AI人才培养所有信号都在告诉我们AI的黄金十年真的来了在行业火爆之下AI人才争夺战也日趋白热化其就业前景一片蓝海我给大家准备了一份全套的《AI大模型零基础入门进阶学习资源包》包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。有需要的小伙伴可以V扫描下方二维码免费领取人才缺口巨大人力资源社会保障部有关报告显示据测算当前****我国人工智能人才缺口超过500万****供求比例达1∶10。脉脉最新数据也显示AI新发岗位量较去年初暴增29倍超1000家AI企业释放7.2万岗位……单拿今年的秋招来说各互联网大厂释放出来的招聘信息中我们就能感受到AI浪潮比如百度90%的技术岗都与AI相关就业薪资超高在旺盛的市场需求下AI岗位不仅招聘量大薪资待遇更是“一骑绝尘”。企业为抢AI核心人才薪资给的非常慷慨过去一年懂AI的人才普遍涨薪40%脉脉高聘发布的《2025年度人才迁徙报告》显示在2025年1月-10月的高薪岗位Top20排行中AI相关岗位占了绝大多数并且平均薪资月薪都超过6w在去年的秋招中小红书给算法相关岗位的薪资为50k起字节开出228万元的超高年薪据《2025年秋季校园招聘白皮书》AI算法类平均年薪达36.9万遥遥领先其他行业总结来说当前人工智能岗位需求多薪资高前景好。在职场里选对赛道就能赢在起跑线。抓住AI风口轻松实现高薪就业但现实却是仍有很多同学不知道如何抓住AI机遇会遇到很多就业难题比如❌ 技术过时只会CRUD的开发者在AI浪潮中沦为“职场裸奔者”❌ 薪资停滞初级岗位内卷到白菜价传统开发3年经验薪资涨幅不足15%❌ 转型无门想学AI却找不到系统路径83%自学党中途放弃。他们的就业难题解决问题的关键在于不仅要选对赛道更要跟对老师我给大家准备了一份全套的《AI大模型零基础入门进阶学习资源包》包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。有需要的小伙伴可以V扫描下方二维码免费领取