拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AI Agent开发入门:从环境搭建到可调试最小闭环

1. 这门课不是“学完就能造Agent”而是帮你拆掉第一堵墙很多人点开“AI Agent 开发学习路线-第三课”时心里想的是这节课该教我写个能自动订机票、查天气、回邮件的智能体了吧结果发现连本地大模型都还没跑起来——挫败感立刻上来。我带过二十多个从零起步的开发者做Agent项目80%的人卡在第三课之前不是因为代码写不对而是根本没搞清这门课真正的教学锚点在哪里。它不教你怎么堆功能而是教你怎么把“Agent”这个概念从PPT里的抽象框图变成你电脑上可调试、可打断、可单步跟踪的一段真实进程。关键词里反复出现的ollama、winget、FastAPI、LangChain不是随便列的工具清单它们各自承担着一个不可替代的“认知解耦”角色ollama 负责把大模型从云端黑盒里拽出来放在你硬盘上看得见摸得着winget 是 Windows 环境下第一道“去魔改化”的基建门槛让你不用再手动下载exe、解压、配PATH、改注册表FastAPI 不是单纯为了写个API它是把Agent的“决策流”和“执行流”第一次物理隔离的手术刀而LangChain恰恰是多数人误以为的“主角”其实它在这门课里更像一块“认知缓冲垫”——帮你暂时绕过LLM底层token调度、prompt工程、tool calling协议这些硬骨头先建立对Agent工作流的肌肉记忆。所以这节课的起点不是写代码而是确认你本地环境里有没有一个可交互、可观察、可中断的最小闭环输入一句话看到模型思考过程不是只看最终输出知道它调用了哪个工具清楚返回结果怎么被解析进下一步。没有这个闭环后面所有Agent编排、多跳推理、记忆管理全是空中楼阁。我见过太多人直接clone LangChain官方demo跑通就以为学会了结果一加个自定义工具就报错debug三天才发现根本没理解tool call的JSON schema是怎么被parse_and_call函数反序列化的。这节课要干的就是亲手把这个闭环拧紧。2. WingetWindows开发者的环境治理第一课不是包管理器那么简单很多人把winget当成Linux下的apt或macOS的brew觉得“不就是装软件嘛”。但如果你真这么用第三课大概率会在第一步就卡死。winget在Windows生态里的真实定位是开发者环境的标准化治理协议它的核心价值不在“装得快”而在“装得稳、卸得净、版本可追溯”。举个最典型的坑你在PowerShell里敲winget install --id 9plm9xgg6vks -s msstore表面看是装了个App实际它悄悄修改了系统级的AppExecutionAlias注册表项还可能触发Windows Store后台服务的权限提升。而后续你要用ollama启动一个本地模型服务如果这个服务监听的端口默认11434恰好被刚才那个App的某个后台进程占用了FastAPI服务启动时就会报OSError: [WinError 10013]——但错误日志里根本不会提那款App的名字你只能靠netstat一个个排查。这就是为什么这节课必须从winget开始它强迫你建立“环境即代码”的意识。我们来实操一次真正符合Agent开发需求的winget初始化首先别急着装东西。先运行winget source list你会看到默认只有msstore源。但msstore源里的软件包质量参差不齐比如某些“AI助手”类App会静默安装浏览器插件或修改主页。Agent开发需要的是干净、可控、无副作用的命令行工具链。所以第一步是添加微软官方维护的社区源winget source add --name winget-pkgs https://github.com/microsoft/winget-pkgs.git注意这里不是加镜像站而是直接指向GitHub仓库。因为winget-pkgs的CI/CD流程会自动验证每个PR里的YAML包定义是否包含恶意脚本、是否声明了所有依赖项、是否提供了清晰的卸载逻辑。接着更新源索引winget source update这一步耗时可能长达2分钟但它在后台做的其实是下载整个仓库的manifests目录结构校验每个YAML文件的SHA256签名构建本地缓存索引。你看到的“Updating source…”进度条背后是winget在为你建立一个可信的软件供应链地图。然后才是关键操作安装ollama。但别用winget install ollama——这个命令会从msstore源安装而msstore版的ollama是打包成UWP应用的它运行在受限容器里无法访问你本地的Docker Desktop或WSL2更没法绑定到localhost:11434供FastAPI调用。正确姿势是winget install --id Ollama.Ollama --source winget-pkgs这个--source winget-pkgs参数强制指定了安装源确保你拿到的是GitHub仓库里维护的、面向开发者设计的原生Windows二进制版。装完后验证ollama list # 应该返回空列表说明服务已启动但没拉取模型 ollama run llama3 # 第一次运行会自动下载约4GB模型此时观察任务管理器——CPU占用稳定在30%-50%内存增长平缓没有瞬间飙到90%的异常波动这才是健康状态提示winget安装的ollama默认配置文件在%LOCALAPPDATA%\Programs\Ollama\settings.json。如果你需要修改模型存储路径比如C盘空间不足不要直接改这个文件而应该用setx OLLAMA_MODELS D:\ollama_models设置系统环境变量然后重启终端。因为winget安装的ollama服务是作为Windows服务运行的它读取的是系统级环境变量而非当前PowerShell会话的临时变量。最后清理一个高频陷阱很多人装完ollama后顺手用winget uninstall microsoftwindows.client.webexperienc卸载Windows自带的Web Experience Pack以为能释放空间。但这个包里包含了Windows Terminal的核心渲染引擎。卸载后你用PowerShell启动ollama时终端窗口会变成纯黑底白字且无法复制粘贴——而ollama的streaming输出依赖ANSI转义序列控制光标位置一旦终端渲染异常FastAPI调用ollama API时就会收到乱码响应。正确的做法是用winget list | findstr Web确认是否真的安装了这个包如果只是预装未启用完全不需要卸载。3. Ollama本地部署不是“下载模型就行”而是构建你的私有推理沙盒ollama常被简化为“本地大模型运行器”但它的本质是一个轻量级模型服务网关。它不直接执行推理而是把请求转发给底层的llama.cpp或transformers runtime并统一管理模型加载、上下文缓存、HTTP API暴露。所以“ollama run llama3”这行命令背后至少发生了三层解耦第一层ollama进程监听11434端口接收JSON-RPC格式的chat request第二层它根据模型名llama3查找本地模型文件加载到内存并初始化tokenizer第三层它把用户输入分词后交给底层runtime执行forward pass再把logits解码成token流。这三层中任何一层出问题都会导致Agent调用失败但错误表现却截然不同。我们来拆解一个真实场景你想用llama3做一个文件内容摘要Agent但上传PDF后ollama返回{error:context length exceeded}。大多数人第一反应是“模型太小”于是去换qwen2:7b。但问题根源往往在第二层——ollama加载模型时默认的context window是2048而PDF文本分词后可能超过这个长度。解决方案不是换模型而是重新创建一个context更大的模型实例ollama create my-llama3-8k -f Modelfile其中Modelfile内容为FROM llama3 PARAMETER num_ctx 8192 PARAMETER num_gqa 8注意num_gqa 8这个参数——它启用了Grouped-Query Attention这是llama3-8B模型支持长上下文的关键优化。如果不加这行即使设了8192推理时也会因KV cache内存爆炸而OOM。这个细节在ollama官方文档里藏得很深但在LangChain的tool calling流程里至关重要当Agent需要同时处理用户query、工具返回的原始数据、历史对话摘要三段文本时总token数很容易突破4096。再来看国内用户最头疼的“下载太慢”问题。网上流传的各种“国内镜像源”教程90%都在教你改~/.ollama/config.json里的library字段。但这是个致命误区。ollama的library字段只控制模型元数据如tag、size的获取源真正的模型文件.gguf格式还是从官方Hugging Face仓库下载。正确解法是利用ollama的--load参数配合本地文件# 先用其他方式下载好llama3.Q4_K_M.gguf比如用IDM从hf-mirror下载 # 然后创建模型时指定本地路径 ollama create my-llama3-local -f - EOF FROM ./llama3.Q4_K_M.gguf PARAMETER num_ctx 4096 EOF这样ollama会直接加载本地GGUF文件跳过网络下载环节。而且你会发现用本地文件创建的模型启动速度比ollama run llama3快3倍以上——因为少了从HF仓库校验sha256的步骤。注意ollama的模型命名规则是namespace/model:tag但namespace在本地部署时毫无意义。ollama run my-llama3-local和ollama run my-llama3-local:latest效果完全一样。LangChain调用ollama时URL固定为http://localhost:11434/api/chat根本不关心模型名里的namespace。所以别被命名迷惑专注模型能力本身。最后一个被严重低估的调试技巧ollama提供--verbose模式但不是所有版本都支持。在PowerShell里启动时用$env:OLLAMA_DEBUG1 ollama serve这时你会看到详细的HTTP请求日志包括每个chat request的完整payload、底层runtime的GPU显存占用、token生成的逐个延迟。当LangChain的Agent卡在“thinking”阶段不动时看这个日志能立刻判断是网络超时request没发出去、模型卡住response stream停在某个token、还是FastAPI中间件拦截了request被cors middleware丢弃。这种颗粒度的可观测性是云服务绝对给不了的。4. FastAPI LangChain不是“胶水拼接”而是定义Agent的神经突触很多教程把FastAPI和LangChain的关系描述成“用FastAPI暴露LangChain的接口”这就像说“用USB线连接大脑和机械臂”——技术上没错但完全忽略了信号协议的设计。FastAPI在这里的真实角色是Agent决策流的协议转换器它把HTTP请求里的非结构化自然语言转换成LangChain能理解的structured input再把LangChain输出的structured response转换回HTTP可传输的JSON。而LangChain则是Agent行为逻辑的编排引擎它不负责具体计算而是决定“下一步该调哪个工具、用什么参数、如何合并结果”。我们以一个典型Agent需求为例用户上传一份销售报表PDF要求Agent分析Q3销售额环比变化。传统做法是写个FastAPI endpoint里面调用PyPDF2读取文本再用LangChain的LLMChain跑prompt。但这样写Agent就失去了“工具调用”的核心能力——它无法动态选择是否需要调用Python REPL来计算增长率也无法在PDF解析失败时自动切换到OCR工具。真正的解法是让FastAPI只做三件事验证文件类型、生成唯一task_id、投递到消息队列。而LangChain的Agent应该监听这个队列自主完成全流程。具体实现时关键在Tool的定义。LangChain的BaseTool要求你实现_run方法但很多人直接在里面写业务逻辑class PDFAnalyzerTool(BaseTool): name pdf_analyzer description Analyze PDF sales report and extract Q3 revenue data def _run(self, pdf_path: str) - str: # 这里直接写PyPDF2代码... return result这会导致两个问题一是_run方法必须是同步阻塞的而PDF解析可能耗时数秒会拖垮整个FastAPI事件循环二是无法做细粒度错误分类——是文件损坏密码保护还是表格识别失败正确做法是把_run变成一个异步任务分发器from celery import Celery celery_app Celery(pdf_tasks, brokerredis://localhost:6379/0) class PDFAnalyzerTool(BaseTool): name pdf_analyzer description Analyze PDF sales report and extract Q3 revenue data def _run(self, pdf_path: str) - str: # 立即返回task_id不等待结果 task celery_app.send_task(pdf_analyze, args[pdf_path]) return fTask submitted: {task.id}然后在FastAPI里用WebSocket或Server-Sent Events实时推送task状态app.get(/task/{task_id}) async def get_task_status(task_id: str): task celery_app.AsyncResult(task_id) if task.ready(): return {status: completed, result: task.result} else: return {status: processing, progress: task.info.get(progress, 0)}这样Agent的“思考”过程就变成了可观察、可中断、可重试的状态机。用户上传PDF后页面显示“正在解析PDF32%”而不是白屏等待10秒。提示LangChain的AgentExecutor默认使用ZeroShotAgent它依赖LLM自己生成tool call的JSON。但在中文场景下LLM经常把pdf_analyzer写成pdf_analysis导致tool not found。解决方案是改用StructuredTool并强制指定tool namefrom langchain.tools import StructuredTool pdf_tool StructuredTool.from_function( funcpdf_analyze_func, namepdf_analyzer, # 必须和description里写的完全一致 descriptionAnalyze PDF sales report..., args_schemaPDFAnalysisInput )args_schema必须继承BaseModel且字段名要和_run方法的参数名严格匹配。这是LangChain做tool call validation的唯一依据漏掉这个Agent就会在生产环境随机失败。最后一个实战经验不要在FastAPI的main.py里直接初始化LangChain的LLM。因为ollama服务可能还没启动完成或者模型还在加载。正确做法是做成懒加载from langchain_community.llms import Ollama _llm_instance None def get_llm() - Ollama: global _llm_instance if _llm_instance is None: try: _llm_instance Ollama(modelmy-llama3-local, timeout120) # 测试连通性 _llm_instance.invoke(test) except Exception as e: raise RuntimeError(fFailed to initialize Ollama: {e}) return _llm_instance这样第一次API调用时才会触发初始化避免服务启动失败。而且你可以在这个函数里加入重试逻辑——比如连续3次connect timeout后自动执行ollama serve命令重启服务。5. LangChain架构解耦从“玩具框架”到“生产级Agent底盘”的跃迁路径LangChain常被吐槽“太重”“文档混乱”“升级不兼容”但它的核心价值从来不是提供开箱即用的Agent而是把Agent开发中重复出现的模式提炼成可组合、可替换的组件契约。所谓“LangChain和LangGraph的区别”本质是“声明式编排”和“图式编排”的范式差异。LangChain的AgentExecutor适合线性流程用户问→选工具→执行→返回而LangGraph适合复杂状态机比如客服Agent先意图识别→若需查订单则调用API→若API超时则降级到知识库检索→若知识库无结果则转人工。但这不是非此即彼的选择而是渐进式演进。我们来看一个真实案例某电商公司要做售后Agent初始需求很简单——用户说“我要退货”Agent回复退货政策。用LangChain几行代码就能搞定from langchain.agents import Tool, AgentExecutor, ZeroShotAgent from langchain.chains import LLMChain tools [ Tool( namereturn_policy, funclambda x: 退货需在签收后7天内商品未拆封..., descriptionGet return policy information ) ] agent ZeroShotAgent.from_llm_and_tools(llmget_llm(), toolstools) executor AgentExecutor(agentagent, toolstools, verboseTrue)但上线后发现用户经常问“我的订单123456能退吗”这时就需要调用订单查询API。如果硬塞进同一个Agentreturn_policy工具就得改成lambda x: query_order_api(x) get_return_policy()违背了单一职责原则。正确解法是引入LangGraph把Agent拆成三个节点intent_node: 用LLM分类用户意图policy_query / order_query / refund_applypolicy_node: 调用静态知识库order_node: 调用外部API带超时和重试每个节点都是独立函数通过StateGraph连接from langgraph.graph import StateGraph, END class AgentState(TypedDict): messages: list[BaseMessage] intent: str order_id: Optional[str] workflow StateGraph(AgentState) workflow.add_node(intent, intent_node) workflow.add_node(policy, policy_node) workflow.add_node(order, order_node) workflow.set_entry_point(intent) workflow.add_conditional_edges( intent, lambda x: x[intent], { policy_query: policy, order_query: order, refund_apply: END } )注意intent_node的输出必须是AgentState的子集且字段名要和StateGraph定义的TypedDict完全一致。这是LangGraph的契约精神——不靠魔法字符串而靠类型系统保证数据流安全。经验总结LangChain的Runnable体系RunnableSequence,RunnableParallel是过渡期的最佳选择。比如你需要同时调用PDF解析和OCR两个工具但不确定哪个更快就可以from langchain.schema.runnable import RunnableParallel parallel_chain RunnableParallel({ pdf_text: pdf_parser_chain, ocr_text: ocr_chain }) result parallel_chain.invoke({pdf_path: /tmp/report.pdf})RunnableParallel会自动管理并发、错误隔离、结果合并。等业务稳定后再逐步迁移到LangGraph的图节点。这种渐进式架构比一开始就上LangGraph更稳健。最后一个血泪教训永远不要在LangChain的prompt里写死系统指令。比如prompt PromptTemplate.from_template( You are a helpful AI assistant. {input} )当LLM版本升级比如从llama3换成qwen2这个system prompt可能引发幻觉。正确做法是用ChatPromptTemplate的MessagesPlaceholderfrom langchain.prompts import ChatPromptTemplate, MessagesPlaceholder prompt ChatPromptTemplate.from_messages([ (system, You are a domain-specific agent for e-commerce after-sales.), MessagesPlaceholder(variable_namemessages), ])MessagesPlaceholder会把历史对话自动插入到messages列表末尾LLM能更好理解上下文。而且你可以随时替换system message而不改代码结构——这才是框架该有的弹性。6. 从第三课出发构建你的第一个可调试Agent的七步实操清单现在把前面所有环节串起来给你一份可立即执行的、零容错的实操清单。这不是理论推演而是我在客户现场手把手陪调三天后沉淀下来的步骤。每一步都对应一个明确的验证点任何一个失败都意味着前序环节有隐藏问题。第1步验证winget环境纯净性在管理员权限PowerShell中执行winget list | Where-Object {$_.Name -match Ollama|Python|Redis} | ft Name,Id,Version预期输出必须只包含Ollama.Ollama、Python.Python.3.11、Redis.Redis三行。如果有其他AI相关App如“Copilot”、“AI Assistant Pro”立即winget uninstall。原因这些App会劫持localhost:11434端口或修改DNS解析策略。第2步强制重置ollama服务关闭所有终端任务管理器结束ollama.exe进程然后# 删除旧模型缓存保留D:\ollama_models目录的话跳过此步 rm -Recurse $env:LOCALAPPDATA\Programs\Ollama\models # 清空配置 rm $env:LOCALAPPDATA\Programs\Ollama\settings.json # 重启服务 start-process C:\Users\$env:USERNAME\AppData\Local\Programs\Ollama\ollama.exe -ArgumentList serve -WindowStyle Hidden等待30秒访问http://localhost:11434应返回{models:[]}。这是健康状态的黄金指标。第3步创建最小可行模型下载llama3.Q4_K_M.gguf约3.2GB到D:\ollama_models\然后cd D:\ollama_models ollama create my-agent-base -f - EOF FROM ./llama3.Q4_K_M.gguf PARAMETER num_ctx 4096 PARAMETER stop EOFstop 是关键——它告诉ollama当LLM生成代码块时主动截断避免无限生成。验证ollama run my-agent-base输入Hello应1秒内返回Hi there!。第4步FastAPI服务骨架检查创建main.pyfrom fastapi import FastAPI from pydantic import BaseModel app FastAPI() class Query(BaseModel): text: str app.post(/chat) async def chat(query: Query): return {response: OK}运行uvicorn main:app --reload访问http://localhost:8000/docsSwagger UI必须正常加载。如果报ImportError: cannot import name main说明uvicorn没装在当前虚拟环境——用python -m pip install uvicorn而非pip install uvicorn。第5步LangChain连接测试在main.py同目录创建test_langchain.pyfrom langchain_community.llms import Ollama llm Ollama(modelmy-agent-base, base_urlhttp://localhost:11434) print(llm.invoke(22)) # 应输出4如果超时检查防火墙是否阻止了11434端口如果返回{error:model not found, 检查ollama list输出是否包含my-agent-base。第6步工具链集成验证创建tools/pdf_tool.pyimport fitz # PyMuPDF def parse_pdf(pdf_path: str) - str: doc fitz.open(pdf_path) text for page in doc: text page.get_text() return text[:500] # 截断防超长 # 验证python -c from tools.pdf_tool import parse_pdf; print(len(parse_pdf(test.pdf)))注意fitz必须用pip install PyMuPDF安装不能用pdfminer——后者在Windows上编译失败率极高。第7步端到端冒烟测试准备一个test.pdf1页纯文本然后运行# test_end2end.py from langchain.agents import Tool, AgentExecutor, ZeroShotAgent from langchain.chains import LLMChain from tools.pdf_tool import parse_pdf tool Tool( namepdf_reader, funcparse_pdf, descriptionRead PDF file and return first 500 chars ) llm Ollama(modelmy-agent-base) agent ZeroShotAgent.from_llm_and_tools(llmllm, tools[tool]) executor AgentExecutor(agentagent, tools[tool], verboseTrue) result executor.invoke({input: Read this PDF: test.pdf}) print(result[output])预期输出是test.pdf的前500字符。如果卡在 Finished chain.后无输出说明tool call的JSON schema没匹配上——检查tool.description里是否写了pdf_reader而不是pdf_reader_tool。这七步走完你手上就有了一个可打断、可单步、可替换任意组件的Agent最小闭环。第三课的价值不在于教会你多少API而在于帮你建立起对Agent各层职责的清晰边界感winget管环境确定性ollama管模型确定性FastAPI管协议确定性LangChain管逻辑确定性。接下来的课程不过是把这四层确定性像搭积木一样向上堆叠。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门