API服务化,用FastAPI把Agent封装成RESTful接口
API服务化用FastAPI把Agent封装成RESTful接口前面几十篇做的Agent都是在本地脚本里跑自己用没问题。但真要给别人用或者接到产品系统里你总不能让别人也开个终端跑Python。把Agent封装成一个HTTP接口别人发个请求过来Agent处理完返回结果这才是生产环境该有的样子。今天这篇就用FastAPI把Agent包成一个RESTful服务。从请求响应模型到会话管理到流式输出一步步搭出来最后给一套能直接跑的完整代码。为什么选FastAPIPython写Web框架不少Django太重Flask太裸FastAPI卡在中间。它天生支持异步写Agent这种IO密集的场景正合适。自带请求参数校验用Pydantic定义模型请求体的字段类型、必填可选全帮你管好。还有自动文档服务跑起来访问/docs就能看到接口文档和在线调试页面省得手写。安装就一行pip install fastapi uvicorn。fastapi是框架本身uvicorn是跑它的ASGI服务器。服务化架构怎么设计Agent变成服务以后要处理几个问题。请求来了怎么传给Agent。用户发一个JSON过来你得解析成Agent能理解的输入格式。Agent的输出也得转成JSON返回给用户。这中间需要请求模型和响应模型做转换。会话怎么保持。Agent是有记忆的用户说第二句话的时候Agent得记得第一句说了什么。HTTP是无状态的每个请求独立你得自己管会话状态。最简单的做法是用session_id关联一组对话历史存在内存里。慢请求怎么处理。Agent调大模型慢的时候一个请求十几秒用户盯着转圈圈很焦虑。流式响应能解决这个问题Agent生成一点就往外吐一点用户看到内容一个字一个字蹦出来体验好很多。并发怎么扛。多个用户同时请求Agent处理要异步不能一个请求阻塞了其他全等着。FastAPI的异步路由天然支持这个但你的Agent底层也得是异步的才行。请求响应模型设计用Pydantic定义模型请求体和响应体都有明确的字段定义。请求模型至少要有用户输入的消息内容可选带上session_id表示是哪个会话。响应模型要有Agent的回复内容加上session_id方便客户端关联再带个元数据字段放token用量之类的信息。下面是完整代码包含一个简单的Agent封装、会话管理、普通接口和流式接口。importuuidimportasynciofromtypingimportOptional,AsyncGeneratorfromfastapiimportFastAPI,HTTPException,DependsfrompydanticimportBaseModel,Fieldfromcontextlibimportasynccontextmanager# ---------- 会话管理 ----------classSessionManager:管理用户会话每个session_id对应一组对话历史def__init__(self):self.sessions{}# session_id - 消息历史列表defcreate_session(self)-str:创建新会话返回session_idsession_idstr(uuid.uuid4())# 生成唯一会话IDself.sessions[session_id][]# 初始化空的消息历史returnsession_iddefget_history(self,session_id:str)-list:获取某个会话的消息历史ifsession_idnotinself.sessions:raiseHTTPException(status_code404,detailf会话{session_id}不存在)returnself.sessions[session_id]defadd_message(self,session_id:str,role:str,content:str):往会话历史里加一条消息ifsession_idnotinself.sessions:self.sessions[session_id][]self.sessions[session_id].append({role:role,content:content})defdelete_session(self,session_id:str):删除某个会话ifsession_idinself.sessions:delself.sessions[session_id]# 全局会话管理器整个应用共用一个session_managerSessionManager()# ---------- Agent封装 ----------classSimpleAgent:一个简单的Agent封装实际项目里换成你的Agent实现def__init__(self):self.name助手Agent# Agent名字asyncdefchat(self,message:str,history:list)-str:异步聊天接口接收消息和历史返回回复# 这里用模拟回复代替真实LLM调用# 实际项目里换成 await llm.ainvoke(message, history)awaitasyncio.sleep(0.5)# 模拟网络延迟replyf收到你的消息:{message}。ifhistory:replyf我记得你之前说了{len(history)}句话。returnreplyasyncdefstream_chat(self,message:str,history:list)-AsyncGenerator[str,None]:流式聊天接口逐字返回回复内容replyawaitself.chat(message,history)# 把回复拆成一个字一个字往外吐forcharinreply:awaitasyncio.sleep(0.02)# 模拟生成延迟yieldchar# 每次yield一个字符# 全局Agent实例agentSimpleAgent()# ---------- Pydantic模型 ----------classChatRequest(BaseModel):聊天请求模型定义了客户端发过来的JSON结构message:strField(# 用户的消息内容必填...,min_length1,max_length2000,description用户输入的消息)session_id:Optional[str]Field(# 会话ID可选不传就新建会话None,description会话ID首次对话不传)classChatResponse(BaseModel):聊天响应模型定义了返回给客户端的JSON结构reply:strField(...,descriptionAgent的回复内容)session_id:strField(...,description会话ID后续对话要带上)message_count:intField(# 当前会话的消息总数...,description当前会话累计消息数)classSessionResponse(BaseModel):创建会话的响应模型session_id:strField(...,description新创建的会话ID)# ---------- FastAPI应用 ----------asynccontextmanagerasyncdeflifespan(app:FastAPI):应用生命周期管理启动和关闭时执行print(Agent服务启动)# 启动时打印日志yield# 应用运行期间print(Agent服务关闭)# 关闭时打印日志appFastAPI(titleAgent API服务,description把Agent封装成RESTful接口,version1.0.0,lifespanlifespan,)# ---------- 接口定义 ----------app.post(/sessions,response_modelSessionResponse)asyncdefcreate_session():创建新会话返回session_idsession_idsession_manager.create_session()returnSessionResponse(session_idsession_id)app.delete(/sessions/{session_id})asyncdefdelete_session(session_id:str):删除指定会话session_manager.delete_session(session_id)return{status:已删除}app.post(/chat,response_modelChatResponse)asyncdefchat(request:ChatRequest):普通聊天接口等Agent处理完一次性返回# 如果没传session_id自动创建新会话session_idrequest.session_idifsession_idisNone:session_idsession_manager.create_session()# 获取会话历史historysession_manager.get_history(session_id)# 记录用户消息session_manager.add_message(session_id,user,request.message)# 调用Agent获取回复replyawaitagent.chat(request.message,history)# 记录Agent回复session_manager.add_message(session_id,assistant,reply)# 返回响应returnChatResponse(replyreply,session_idsession_id,message_countlen(session_manager.get_history(session_id)),)app.post(/chat/stream)asyncdefchat_stream(request:ChatRequest):流式聊天接口逐字返回Agent的回复fromfastapi.responsesimportStreamingResponse# 同样处理会话逻辑session_idrequest.session_idifsession_idisNone:session_idsession_manager.create_session()historysession_manager.get_history(session_id)session_manager.add_message(session_id,user,request.message)asyncdefgenerate():生成器函数逐字yield回复内容full_replyasyncforcharinagent.stream_chat(request.message,history):full_replychar# 拼接完整回复yieldchar# 往客户端吐一个字符# 流结束后把完整回复存入历史session_manager.add_message(session_id,assistant,full_reply)# 返回流式响应媒体类型设为纯文本returnStreamingResponse(generate(),media_typetext/plain,headers{X-Session-Id:session_id}# 通过header返回session_id)app.get(/sessions/{session_id}/history)asyncdefget_history(session_id:str):获取某个会话的完整历史记录historysession_manager.get_history(session_id)return{session_id:session_id,history:history}# ---------- 启动服务 ----------if__name____main__:importuvicorn# host设0.0.0.0允许外部访问port按需改# reloadTrue开发时自动重载生产环境关掉uvicorn.run(app,host0.0.0.0,port8000)效果验证服务跑起来以后有几种方式验证。最简单的是直接访问http://localhost:8000/docsFastAPI自动生成的文档页面。在里面找到POST /chat接口点Try it out填一段message点Execute能看到Agent的回复JSON。session_id留空第一次会自动创建后续请求把返回的session_id填进去就能保持对话上下文。用curl测普通接口发一个POST请求。curl-XPOST http://localhost:8000/chat\-HContent-Type: application/json\-d{message: 你好}返回的JSON里有reply、session_id、message_count三个字段。拿着返回的session_id再发一条message_count会变成2说明会话历史在累加。测流式接口用curl加-N参数能看到内容一个字一个字返回。curl-N-XPOST http://localhost:8000/chat/stream\-HContent-Type: application/json\-d{message: 讲个故事}判断成功的标准普通接口返回200状态码和完整的JSON。流式接口返回200内容逐步输出不卡顿。连续发多条消息message_count递增说明会话状态保持正常。常见报错422 Validation Error说明请求体格式不对检查message字段有没有传、长度有没有超限。404说明session_id不对检查是不是拼错了或者会话已经被删了。踩坑记录第一个坑流式接口里会话历史没存上。我一开始在generate函数外面调了add_message存用户消息Agent回复在generate函数里流式输出但完整回复的存储放在了generate函数外面。结果流式响应返回以后generate函数还没跑完外面的代码先执行了存了一个空字符串。后来把完整回复的存储挪到generate函数内部流结束后再存问题才解决。异步生成器的执行顺序跟同步代码不一样跟数据存储相关的操作一定要放在生成器内部。第二个坑并发请求串会话。一开始会话管理器用字典存没加锁。两个请求同时操作同一个session_id一个在读历史一个在写偶发数据错乱。测试的时候单线程测不出来上了并发压测才暴露。后来给会话操作加了asyncio.Lock同一个session_id的操作串行化不同session_id之间不阻塞。如果你的会话量大锁的粒度可以细到每个session_id一把锁别全局一把锁把并发全卡死了。第三个坑生产环境内存涨。会话历史存在内存里用户多了或者对话长了内存一直涨。一开始没做清理跑了一天内存就爆了。后来加了两个策略一是限制每个会话的历史长度超过20条就裁掉最早的只保留最近20条。二是设个过期时间24小时没活动的会话自动清理。生产环境建议把会话存Redis别放内存里重启就丢了。部署注意事项开发阶段用uvicorn的reload模式很方便改了代码自动重启。上生产别用reload性能差。用uvicorn或者gunicorn跑多个worker进程扛并发。模型API的key别硬编码在代码里用环境变量或者配置文件管理。FastAPI可以用Settings管理配置从环境变量读。跨域问题别忘了。前端调你的接口域名不一样浏览器会拦。FastAPI加个CORSMiddleware把允许的域名配上就行。开发阶段可以设allow_origins为星号放开所有域名生产环境收敛到具体域名。日志要打好。每个请求记session_id、消息内容、处理耗时、token用量。出了问题能根据session_id追溯完整对话过程。FastAPI可以加中间件统一记录请求日志不用每个接口里手写。延伸与判断这个服务化架构可以扩展的方向很多。把上一篇文章的多Agent项目团队封装成接口用户发一个需求三个Agent跑完返回代码和测试报告。接口设计上把/chat换成/project请求体里带上需求描述响应体里返回多阶段结果。认证授权也得加。生产环境的接口不能裸奔加个API key或者JWT认证。FastAPI用Depends做依赖注入写个认证依赖挂在路由上就行。如果有长任务比如多Agent跑一遍要几分钟同步等不了。可以改成任务队列模式用户发请求返回一个task_id后台慢慢跑用户拿task_id轮询结果或者通过WebSocket推送进度。结尾把Agent封装成API服务核心就四件事请求响应模型定义好会话状态管好慢请求用流式响应兜住并发和资源该限制的限制。这套东西搭完你的Agent就能被任何系统调用了从本地脚本变成了真正的在线服务。AI Agent企业级实战系列到这里就收尾了从单个Agent的搭建到多Agent协作再到服务化部署整条路走通了。

相关新闻

ArcGIS Pro圆弧线半径标注加载项:原理、安装与实战应用

ArcGIS Pro圆弧线半径标注加载项:原理、安装与实战应用

如果你在 ArcGIS Pro 中处理过道路设计、管线规划或任何涉及圆弧要素的 GIS 数据,一定遇到过这个痛点:如何快速、准确、批量地为地图上的圆弧线标注半径?ArcGIS Pro 自带的标注功能很强大,但对于圆弧这类特殊几何,其“…

2026/10/7 11:44:06 阅读更多 →
太阳能智慧井盖:城市地下管网智能化监测解决方案

太阳能智慧井盖:城市地下管网智能化监测解决方案

一、产品概述太阳能井盖是一种集成了环境感知、数据采集、无线通信与远程监控功能的设备,旨在实现对各类地下管网窨井内部设备状态与井盖自身状态的实时监测与预警。该设备是构建城市管网管理系统的重要组成部分,能够有效应对传统井下监测面临的诸多挑战…

2026/9/28 14:08:39 阅读更多 →
WhisperX:离线语音识别的革命性突破,70倍速精准转文字

WhisperX:离线语音识别的革命性突破,70倍速精准转文字

WhisperX:离线语音识别的革命性突破,70倍速精准转文字 【免费下载链接】whisperX WhisperX: Automatic Speech Recognition with Word-level Timestamps (& Diarization) 项目地址: https://gitcode.com/gh_mirrors/wh/whisperX 在数字化办公…

2026/10/1 7:41:32 阅读更多 →

最新新闻

从AlphaGo到LLM:缺失的“神之一手”与决策闭环

从AlphaGo到LLM:缺失的“神之一手”与决策闭环

2016年3月,AlphaGo以4比1战胜李世石,那场棋被几十种语言直播,连我父母都守在电视前看了半宿。十年过去,我在AI行业摸爬滚打,这两年主要做LLM(大语言模型)的评测、推理加速和智能体落地&#xff…

2026/10/7 11:43:59 阅读更多 →
Linux CFS 调度器深度解析:调度时机、vruntime 选任务与多核负载均衡

Linux CFS 调度器深度解析:调度时机、vruntime 选任务与多核负载均衡

1. 从一次线上抖动说起:为什么要啃 CFS 调度器前阵子帮朋友排查一个服务响应毛刺的问题,现象很典型:一台 8 核的机器,跑着一个多线程的数据处理服务,平时 P99 延迟稳定在 20ms 左右,但每隔几分钟就会冒出一…

2026/10/7 11:43:59 阅读更多 →
ODT详解:DDR信号完整性中的片上端接核心机制

ODT详解:DDR信号完整性中的片上端接核心机制

1. 什么是ODT,它为什么在DDR设计里根本绕不开? 你手里的手机、笔记本、服务器主板,只要用的是DDR3/DDR4/DDR5内存,那它的信号走线底下就一定藏着ODT——片上端接(On-Die Termination)。这个词听起来像教科书…

2026/10/7 11:43:59 阅读更多 →
虚拟电厂VPP能源数字化:数据采集、功率预测与调度实战

虚拟电厂VPP能源数字化:数据采集、功率预测与调度实战

简介:一份聚焦虚拟电厂(VPP)能源数字化与碳中和的Word文档,面向新能源、电力系统及碳管理领域从业者与研究者。内容以平衡机器科技(深圳)的Smartrams产品为切入,系统讲解云端风光功率猜想、虚拟…

2026/10/7 11:43:59 阅读更多 →
数据结构实验:线性表与多项式运算的完整代码与避坑指南

数据结构实验:线性表与多项式运算的完整代码与避坑指南

简介:南京邮电大学《数据结构》课程实验一的完整实验报告,围绕线性表基本运算及多项式算术运算展开,适合正在学习数据结构、需要参考实验报告写法与C语言实现代码的本科生。压缩包内为1个docx文档,大小约444KB,内容涵盖…

2026/10/7 11:43:59 阅读更多 →
智能体技能工程实战:从工具调用到可复用技能库的完整设计指南

智能体技能工程实战:从工具调用到可复用技能库的完整设计指南

直接说结论:如果你正在做智能体(Agent)应用,无论是跑在RAG框架里、套在自动化工作流里,还是嵌在对话产品里,agent-skills这个名字背后涉及的,就是给大模型配一套“可复用、可组合、可评测”的行…

2026/10/7 11:42:57 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:29:10 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 11:43:46 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →