AI应用开发实战:从大模型API接入到Agent闭环搭建
如果你和我一样前两天还在对着大模型的理论知识发呆那今天这篇文章值得认真读完。今天是我AI应用开发学习打卡的第3天主线任务只有一个让大模型真正跑进业务代码里。day01我整理了大模型的推理流程day02玩了半天提示词技巧到了day03我突然发现如果只想做一个能落地的小应用最该学的不是模型内部结构而是接口接入、工程封装和Agent基础闭环的搭建。这篇文章就围绕这三块展开适合已经能调用大模型API、但还没写过完整应用的开发者也适合准备系统走一遍AI应用开发学习路线的人照着抄作业。1. 先理清一件事AI应用开发到底在学什么我见过不少同学一上来就啃模型结构、训练原理结果啃了一周还是写不出一个能跑的业务模块。倒不是说理论知识没用而是AI应用开发的主战场从来不在模型内部而在模型的接入、编排与落地。1.1 从“聊天窗口”到“业务模块”的思维转换我们日常用模型时依赖的都是聊天窗口这个东西最大的价值是帮你快速验证模型能力边界它能写代码、能总结文档、能翻译术语。但聊天窗口不是一个产品至少不是一个能被业务系统直接调用的模块。一个真正能用的AI应用至少要具备这些基本要素有标准的请求入口让外部系统能用HTTP方式调用有鉴权和密钥管理不能把模型密钥直接写在代码里有日志和异常处理调用失败时能定位问题有输出格式化能力模型返回的结果能被业务代码直接解析有超时和重试机制API抖动时系统不至于直接崩掉。day03的核心目标就是把一个“能对话的模型”改造成一个“能被业务系统调用的服务”。这个过程比的不是谁懂更多模型原理而是谁更懂工程化谁更熟悉应用开发的完整链路。1.2 今天要过的三座山我把day03的学习内容拆成三座必须翻过去的山第一座是接口接入。搞清楚怎么用代码稳定地拿到模型输出包括鉴权、参数传递、超时控制、异常重试。第二座是工程封装。把裸API封装成业务代码可以直接调用的服务模块让上层业务不用关心模型接口细节。第三座是Agent初探。让模型不只是“回答问题”而是能“调用工具、完成任务”这就要走一个简单的Agent循环。三座山一天翻完有点赶但翻完之后的成就感是实实在在的。我建议你也按这个顺序来先把基础链路跑通再去碰那些花哨的编排框架。2. 环境准备与工程脚手架搭建在开始写业务代码之前先把环境搭好。这里我不建议在技术选型上纠结太久尤其是刚开始学习的阶段选一套自己最熟悉的组合先把最小闭环跑通比什么都重要。2.1 技术选型别在框架选择上浪费太多时间目前主流的应用后端方案有几个方向Python系的FastAPI、Flask、DjangoJava系的Spring AI等。我的建议是如果你之前用过Python直接用FastAPI如果你本来就是Java后端完全可以在Spring生态里继续没必要为了学AI应用再换语言。给大家一个快速决策逻辑目的只是验证AI应用开发流程选Python FastAPI代码量最小要对接已有业务系统且团队以Java为主选Spring系要快速做前端Demo演示甚至可以先用Flask渲染一个简单页面。大模型接口本身是HTTP JSON格式任何语言都能调用SDK本质上只是帮你封装了请求过程。所以框架选型不是瓶颈想清楚要解决什么问题才是关键。我在day03用的组合是Python FastAPI。一是代码短二是我后续想把Agent循环、结构化输出、文件处理这些功能串起来FastAPI对异步支持非常好后面扩展会顺手很多。2.2 搭建最小工程骨架开始之前先确认Python环境。建议用Python 3.10以上版本避免一些新语法兼容问题。创建项目目录并初始化虚拟环境mkdir ai-app-day03 cd ai-app-day03 python3 -m venv venv source venv/bin/activate # Windows下执行 venv\Scripts\activate接下来安装依赖我只装最必要的几个pip install fastapi uvicorn openai python-dotenv httpx这里简单解释一下每个库的用途fastapi提供Web接口能力接收HTTP请求并返回结果uvicornFastAPI的开发服务器openai用于调用大模型API的官方SDK目前市面上主流模型都提供OpenAI兼容接口所以用它最通用python-dotenv读取.env配置文件用来管理密钥httpx一个优秀的HTTP客户端后面写Agent工具调用时会用到。然后创建一个简单的项目目录结构ai-app-day03/ ├── .env ├── .gitignore ├── main.py ├── llm_client.py └── requirements.txt这里一定要把.env加入到.gitignore里密钥文件不能进版本库这是最基本的工程素养。requirements.txt内容是刚才安装的那几个包及版本号。可以先执行pip freeze requirements.txt再手工精简只保留顶层依赖。最简单的验证方式是先写一个FastAPI测试接口确认环境没问题。from fastapi import FastAPI app FastAPI() app.get(/health) def health_check(): return {status: ok}然后启动服务uvicorn main:app --reload --port 8000浏览器访问http://localhost:8000/health看到{status:ok}说明环境已经通了。别小看这一步我见过太多人卡在环境问题上花了一个小时装各种包最后连个Hello World都没跑起来所以一定要先跑通最小工程再往下走。3. 大模型接口接入与提示词工程实战环境搭好之后正式接入大模型接口。这一步我会把裸API调用变成一个可复用的服务模块同时把提示词的坑讲透。3.1 接口封装把裸API变成可靠的服务我定义一个LLMClient类把模型接口的鉴权、请求、超时、重试都封装在内部上层业务代码只需要传入提示词拿到返回文本。先创建.env文件内容如下LLM_API_KEYyour-api-key-here LLM_BASE_URLhttps://api.example.com/v1 LLM_MODELyour-model-name我用example.com做示例地址实际使用的时候替换成你所用模型服务商提供的地址、密钥和模型名。下面是对应的llm_client.py代码import os import time from openai import OpenAI from dotenv import load_dotenv load_dotenv() class LLMClient: def __init__(self): self.api_key os.getenv(LLM_API_KEY) self.base_url os.getenv(LLM_BASE_URL) self.model os.getenv(LLM_MODEL) self.client OpenAI(api_keyself.api_key, base_urlself.base_url) self.max_retries 3 self.timeout 30 def chat(self, messages, temperature0.2, max_tokens2048): for attempt in range(self.max_retries): try: response self.client.chat.completions.create( modelself.model, messagesmessages, temperaturetemperature, max_tokensmax_tokens, timeoutself.timeout ) return response.choices[0].message.content except Exception as e: if attempt self.max_retries - 1: raise RuntimeError(f模型调用失败: {e}) wait_time 2 ** attempt print(f调用失败{wait_time}秒后重试...) time.sleep(wait_time)有几个参数需要展开说明也是实际开发中必须理解的temperature控制输出的随机性。取值0到2之间数值越低回答越保守和稳定数值越高越发散。做业务功能时默认0.2比较合适写作文、做创意类内容可以调高max_tokens限制生成的最大长度。这个值需要根据业务场景估算比如做摘要2000字以内的输出基本够用做对话4K或8K可能还不够。重试策略指数退避重试非常重要。实际模型接口经常出现偶发超时第一次失败立刻重试大概率还是失败等2秒、4秒再试成功率会高很多。这里有一个所有初学者都会踩的坑把API密钥硬编码在代码里。我有一个朋友在某平台上公开了自己的Git仓库结果半天时间密钥就被爬虫抓走给他刷了几百块钱的额度。所以密钥一定走环境变量.env文件一定不提交到版本库。3.2 让模型输出结构化服务端代码才好写直接让模型返回一篇自由文本后续业务代码解析起来非常痛苦。比如你想让模型返回一个新闻分类结果它可能给你来一句“根据我的分析这条新闻的类别是科技”而不是一个干净的JSON。解决这个问题最实用的办法是使用接口层的结构化输出能力。以OpenAI兼容接口为例可以设置response_format{type: json_object}要求模型返回JSON同时配合提示词约束。我把llm_client.py里的chat方法做一个扩展增加response_format参数def chat_json(self, messages, temperature0.2, max_tokens2048): for attempt in range(self.max_retries): try: response self.client.chat.completions.create( modelself.model, messagesmessages, temperaturetemperature, max_tokensmax_tokens, response_format{type: json_object} ) content response.choices[0].message.content return content except Exception as e: if attempt self.max_retries - 1: raise RuntimeError(f模型调用失败: {e}) wait_time 2 ** attempt time.sleep(wait_time)注意使用json_object模式时提示词里必须包含“返回JSON格式”或“请以JSON格式输出”之类的明确要求否则模型可能不遵守约束。我还强烈建议在被动调用之外加一层主动校验。模型返回的JSON偶尔会多出解释文字或者漏掉字段所以拿到返回结果后不要直接json.loads()而是先解析如果解析失败则触发一次重试。一个很实用的技巧是在消息里塞入一个输出示例也就是few-shot范例。模型看到你期望的格式样例后生成结果会稳定非常多。下面这段提示词我实测下来效果很稳请对下面的用户反馈进行情感分类。 要求 1. 只输出JSON不要输出任何额外解释。 2. JSON格式为 {sentiment: positive|neutral|negative, confidence: 0.0-1.0} 用户反馈这个功能太好用了推荐给所有朋友。这段提示词有三层结构任务描述、格式约束、输入内容。如果再配一个输入输出示例比如先给一条“产品很一般”对应{sentiment: neutral, ...}模型输出的稳定性会再上一个台阶。3.3 提示词设计最容易踩的几个坑提示词工程看着简单实际一跑就露馅。我整理了几个高频问题第一个坑是输出截断。模型生成超过max_tokens上限时会突然中断导致JSON不完整或回答半句话。排查方法很直接看返回结果有没有finish_reason为length字段如果是说明截断了。解决方式是调大max_tokens或者把任务拆成更小的子任务一次只让模型做一件事。第二个坑是格式飘忽不定。今天返回JSON明天加个Markdown代码块后天又多了一句解释词。这个靠提示词能改善但不能完全杜绝根本解法是写一个健壮的解析函数能从前缀乱串的文本中提取出合法的JSON片段。第三个坑是模型“太聪明”。你让它输出JSON它会在JSON里加注释甚至把布尔值写成True而不是true这在严格的JSON解析器下是会报错的。所以做业务系统时后端解析层一定要容忍这些噪点。我在实际项目中遇到过最离谱的一次是模型在JSON里嵌套了Markdown表格解析器直接崩溃。后来我加了一个防御性解析函数先尝试标准解析失败后用正则提取JSON子串再尝试解析这才能保证业务不被模型的不稳定输出拖垮。4. Agent初探让AI从“回答问题”变成“完成任务”到这里你已经能把大模型接进自己的业务代码里了但你会发现一个问题模型只能基于它自己的知识回答没法访问实时数据也没法操作系统里的任何工具。想要让模型真正“干活”就得引入Agent的机制。4.1 Agent不是玄学本质就是一个循环很多人把Agent说得神乎其神拆开来看其实就是一套经典循环模型思考、决定调用工具、拿到工具结果、继续思考直到能回答用户的问题。用一个生活化的类比你雇了一个新实习生他专业知识很强但不了解你们公司的内部系统。你给他的工作方式是遇到不知道的信息可以去查内部资料库查完再回答你。这个“查资料”的动作就是工具调用而这个“查完再想下一步”的过程就是Agent循环。所以Agent架构里至少有四个核心角色大模型大脑负责理解任务、生成计划、整理答案工具手模型可以调用外部函数比如查天气、查数据库、执行代码记忆短期记忆保存当前任务的上下文长期记忆保存历史信息和用户偏好控制循环调度中心决定什么时候调用模型、什么时候调用工具、什么时候结束。这里我特别想强调一点学习Agent请不要从重框架开始。很多人第一天就上LangChain结果被各种概念绕晕最后还是不会自己写。先自己手写一个最简循环对Agent的理解会深刻得多。4.2 手写一个最小可用的ReAct循环ReAct是一种思维与行动交替进行的模式。我写了一个简化版本保留核心逻辑方便你看清楚整个循环的运转方式。import json import datetime from llm_client import LLMClient client LLMClient() def get_current_time(): 工具1获取当前时间 return datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) def calculate(expression: str): 工具2计算数学表达式 return str(eval(expression)) TOOLS { get_current_time: get_current_time, calculate: calculate } def run_agent(user_query): messages [ {role: system, content: 你是一个智能助手可以调用工具解决问题。 当需要使用工具时只输出JSON{\tool\: \工具名\, \args\: \参数\}。 当你不需要调用工具时直接输出答案。}, {role: user, content: user_query} ] for step in range(5): response client.chat(messages, temperature0.1) print(f[思考/回答] {response}) try: action json.loads(response) tool_name action.get(tool) args action.get(args) if tool_name in TOOLS: tool_result TOOLS[tool_name](args) print(f[工具结果] {tool_result}) messages.append({role: assistant, content: response}) messages.append({role: tool, content: str(tool_result)}) else: break except json.JSONDecodeError: break return messages[-1][content] if messages else 未获取到结果 if __name__ __main__: print(run_agent(现在几点))运行这段代码可以看到模型会自己决定调用get_current_time工具拿到结果后组织最终答案。这段代码里的关键设计你需要理解三个点一是“模型先说话系统再决定下一步”。大模型并不真正执行代码它只是在输出文本文本描述它想调用哪个函数控制循环来解析这个文本并真正执行。这是Agent最容易被误解的地方。二是“工具结果回填对话历史”。模型调用完工具后必须把观察结果作为一条新的对话消息追加进去模型才能基于这个结果继续推理。如果漏掉这一步模型就会陷入自问自答。三是“控制循环要设置步数上限”。我设了5步防止模型陷入死循环。真实场景中步数上限、超时控制、失败重试都要设计好否则模型会在不确定性里一直打转。我自己最开始跑这个Demo时犯过一个低级错误把工具名写成中文名比如“获取当前时间”而工具函数字典里的key是英文的导致解析后匹配不上模型就一直空转也不知道发生了什么。这个问题排查速度极快打印出中间变量一眼就能看出来所以写Agent代码时一定要多打印中间步骤把思考过程晒出来。5. 部署、排查与工程化落地Demo跑通之后接下来要考虑的是怎么把东西交给真实用户用。这一步的差距比很多人想象的都要大。5.1 从“本地能跑”到“线上能用”之间差了这些细节本地跑通和线上能用之间不是一回事。我列出几个线上环境必须处理好的点第一密钥管理。线上环境不要再用.env文件了改用环境变量注入有些云平台还支持密钥管理服务那比明文环境变量更安全。第二超时控制。大模型接口的响应时间波动非常大短的时候一两秒长的时候几十秒。如果服务端没有合理的超时设置一个慢请求可能拖垮整个服务。建议给每个模型调用设置独立的超时时间并区分对话场景和批量处理场景。第三日志和观测。上线前必须把请求参数、模型返回、耗时、token使用量都记录下来。没有日志出了问题就是大海捞针。我强烈建议你在LLMClient里加一个简单的日志函数记录时间、模型名、输入token数、输出token数和耗时后面排查问题省太多事。第四并发控制。如果你直接用一个API Key请求模型服务商通常会有并发限制。本地测试没事一上线并发一上来就报429限流错误。解决方式有几种简单粗暴的上限是加一个信号量控制最大并发数稍微复杂一点的是做请求队列规模型团队再考虑缓存和负载均衡。第五输入长度控制。模型上下文窗口有限不能用塞入无限长的文本。业务上要做截断、摘要或者分批处理确保每次请求的token数在安全范围内。5.2 常见问题与排查速查表把day03跑通过程中最可能遇到的问题整理成一张表方便你对症下药现象可能原因排查方法解决方案调用API返回401API Key错误或过期检查.env配置是否正确重新生成密钥确认没有多余空格返回结果突然中断max_tokens设置过小查看finish_reason是否为length调大max_tokens或拆分任务提示“上下文过长”请求token超过模型上限查看请求消息体大小截断历史消息或做摘要压缩模型输出不符合JSON格式提示词未明确约束查看原始输出内容使用response_format并加few-shot示例接口偶发超时网络或服务端不稳定查看耗时记录加指数退避重试设置合理超时上线后并发报错API限流查看响应头中限流信息增加并发控制或申请更高配额代码本地能用但服务器不能用环境配置不一致对比本地和服务器环境变量统一使用环境变量注入密钥与配置这张表是AI应用开发从入门到进阶都会反复用到的基础排查清单。5.3 关于“AI模型部署”的一个边界认知热词里经常出现“AI模型部署”这四个字但它至少有三层完全不同的含义很多人把它们混为一谈。第一层是调用在线API。你的业务系统通过HTTP请求别人已经部署好的模型服务这是绝大多数AI应用的实际形态。优点是便宜、省事缺点是数据要过外部服务且受制于服务商的可用性。第二层是私有化部署开源模型。你把开源模型权重下载到自己的服务器上用推理框架跑起来然后对外提供API。这一步的优势是数据可控缺点是硬件成本高、运维复杂度高实话说不是所有团队都适合自己做。第三层是模型训练和微调。这才是真正意义上的“模型部署”上游工程普通人暂时不用碰。我给学习者的建议是先走第一层把业务应用跑出价值再评估是否值得为数据安全或成本做第二层。不要一上来就想着自己部署一个大模型我见过太多团队卡在GPU驱动、显存不够、推理速度太慢这些运维问题上连业务都还没验证就放弃了。day03这个Demo做到在线API接入这一步已经足够。等你想清楚业务场景、用户量和数据敏感度之后再回头研究私有化部署才是正确的顺序。说实话今天这天的内容比我预想中要重得多。从搭环境到封装接口从结构化输出到手写Agent最小循环每一步都在逼我从“用AI聊天”走向“把AI变成应用的一部分”。我个人最大的收获不是掌握了某个具体的函数写法而是建立了一个重要认知大模型对应用开发者来说就是一个能力很强但需要规范的组件。你越是把它当成普通服务去设计接口、鉴权、超时、日志、容错你的应用就越稳。最后再分享一个我实操中很喜欢的小技巧无论是提示词还是Agent工具都先把输入输出边界写清楚再让代码跑起来。给模型画好格式和流程的红线它就不会轻易跑偏。如果你已经把今天的Demo跑通了明天可以试着做两件事一是给Agent增加一个网络查询工具让它可以访问外部信息二是尝试让两个Agent角色分工协作分别承担不同任务你会发现多智能体协作比单Agent复杂很多但也更有意思。