视频链接:
LangGraph+deepseek-r1+FastAPI+Gradio实现拥有记忆的流量包推荐智能客服web端用例,同时也支持gpt、国产大模型、Ollama_哔哩哔哩_bilibili
开源代码如下:
作为电脑小白,每天靠通勤时间用手机阅读代码,后果是前后读了半个月才读完,但是等读完,还是觉得大脑空空,于是在读完之后又和豆包提了两件事,一个是总结提炼内容找到可以复用的点,二是如果自己进行开发,如何写提示词才能获得这个程序。通过反向思考,获得知识的升维
项目核心知识点总结 & Agent 项目复用指南
这个项目是一套完整的大模型对话 Agent 工程化实现,从前端交互、模型对接、后端服务、会话管理、流式响应做了全链路封装,你运行完后没感知到可复用点,核心是因为没把「代码功能」和「Agent 项目通用需求」做对应。下面先拆解你实际学到的工程化知识点,再分模块讲Agent 项目可直接复用 / 改造的代码 & 思路,所有复用点都贴合开源 Agent 开发的高频需求。
一、你从这套代码中学到的核心知识点
这套代码覆盖了大模型 Agent 开发的全流程工程化能力,不是单纯的 API 调用,而是能落地的生产级代码,核心学到的内容分 6 类:
1. 大模型对接工程化:多源模型统一封装
- 学会用
langchain_openai.ChatOpenAI做多厂商模型的统一适配(OpenAI / 阿里通义千问 / 本地 Ollama/OneAPI),不管模型接口差异多大,都能通过统一的ChatOpenAI实例调用,不用为每个模型写单独的请求逻辑; - 掌握模型配置的解耦设计(
MODEL_CONFIGS字典),通过配置切换模型,而非硬编码,符合工程化「开闭原则」; - 学会自定义异常、重试机制、超时配置,让模型调用从「裸奔的 API 请求」变成「有容错能力的服务」。
2. 后端服务开发:FastAPI 构建标准大模型 API
- 掌握用 FastAPI 搭建兼容 OpenAI 格式的聊天接口(
/v1/chat/completions),支持流式 / 非流式两种响应,这是大模型 Agent 的标准后端接口规范,对接前端 / 第三方工具都通用; - 学会用 Pydantic 做请求 / 响应的数据校验(
Message/ChatCompletionRequest等类),解决大模型开发中「参数格式混乱、数据类型错误」的高频问题; - 掌握 FastAPI 的生命周期管理(
lifespan异步上下文管理器),实现「服务启动时全局初始化模型 / 图,全局只执行一次」,避免重复创建资源导致的性能问题。
3. 会话记忆:基于 LangGraph 实现持久化上下文
- 学会用LangGraph 的 MemorySaver做会话记忆,通过
thread_id(userId+conversationId)区分不同用户 / 会话,实现「多用户多会话的上下文隔离」,这是 Agent 能连续对话的核心; - 掌握 LangGraph 的状态图(StateGraph) 基础用法,将大模型调用封装为「节点」,通过图的方式管理 Agent 的执行流程,为后续扩展复杂 Agent(多节点 / 分支 / 工具调用)打下基础。
4. 交互层:Gradio 快速搭建调试前端
- 掌握 Gradio 的Chatbot 组件 + 流式响应对接,快速实现一个可交互的聊天前端,不用写前端代码就能做 Agent 的本地调试;
- 学会 Gradio 的事件绑定(按钮点击 / 回车提交)、输入框清空、对话历史管理,满足 Agent 调试的基本交互需求。
5. 流式响应全链路实现
- 掌握后端(FastAPI)流式响应:通过
StreamingResponse生成 SSE(服务器发送事件)流,按块返回大模型输出; - 掌握前端(Gradio)流式接收:通过生成器(yield)实时更新聊天界面,实现「边生成边显示」的体验;
- 学会流式数据的格式规范(兼容 OpenAI 的 chunk 格式),解决流式开发中「数据解析错误、断流、空行」的问题。
6. 工程化基础:日志 / 格式化 / 代码解耦
- 掌握 Python 项目的标准化日志配置,按级别记录请求、响应、错误,方便 Agent 开发中的问题排查;
- 学会大模型输出格式化:通过正则处理代码块、段落分隔,让 Agent 的输出更易读;
- 掌握代码模块化拆分:按功能拆分为
webUI.py(前端)、llms.py(模型)、main.py(后端服务),避免单文件臃肿,便于维护和扩展。
二、做 Agent 项目可直接复用 / 改造的代码 & 思路
这套代码是Agent 开发的「基础脚手架」,不管你做对话型 Agent、工具调用型 Agent、多轮任务型 Agent,以下模块都能直接复用或少量改造后使用,按「核心度从高到低」排序:
🔥 核心复用模块 1:多源模型对接(llms.py)—— 直接复用,零改造
这是整个项目中复用性最高的代码,几乎所有 Agent 项目都需要对接大模型,而这套代码已经做了「多厂商 + 容错 + 配置解耦」,复用方式:
- 直接复制
llms.py到你的 Agent 项目; - 根据需要修改
MODEL_CONFIGS:添加新的模型(如智谱 AI / 百度文心)、修改现有模型的base_url/api_key/model; - 调用
get_llm(llm_type)即可获取初始化好的模型实例,比如做工具调用 Agent 时,直接用llm = get_llm("ollama")获取本地模型,不用再写模型初始化代码。
复用价值:省去至少 80% 的模型对接工作,不用为每个模型写单独的请求逻辑,且自带异常处理、重试机制,比自己写裸的 API 请求更稳定。
🔥 核心复用模块 2:FastAPI 标准后端服务(main.py)—— 少量改造,适配 Agent 需求
这套代码实现的/v1/chat/completions是兼容 OpenAI 的标准接口,Agent 项目的后端几乎都能基于此改造,核心复用 / 改造点:
可直接复用的部分
- Pydantic 数据模型(
Message/ChatCompletionRequest/ChatCompletionResponse):直接复用,解决请求 / 响应的校验问题; - 生命周期管理(
lifespan):直接复用,实现 Agent 启动时全局初始化模型 / 工具 / 配置,避免重复创建; - 日志配置、异常处理(
HTTPException):直接复用,标准化 Agent 的日志和错误返回; - 会话隔离(
thread_id = userId+"@@"+conversationId):直接复用,实现多用户多会话的上下文管理。
针对 Agent 的改造点(核心是扩展 LangGraph 的状态图)
这套代码的 LangGraph 只有一个「chatbot 节点」(纯对话),做工具调用 Agent / 多任务 Agent时,只需修改create_graph函数,为 LangGraph 添加「工具调用节点」「分支判断节点」,示例改造思路:
python
运行
# 原代码:只有chatbot节点
def create_graph(llm) -> StateGraph:
graph_builder = StateGraph(State)
def chatbot(state: State) -> dict:
return {"messages": [llm.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge(chatbot, END)
return graph_builder.compile(checkpointer=MemorySaver())
# 改造后:添加工具调用节点+分支判断(工具调用Agent)
def create_graph(llm, tools) -> StateGraph:
graph_builder = StateGraph(State)
# 节点1:大模型判断是否需要调用工具
def judge_tool(state: State) -> dict:
# 用llm分析用户问题,返回是否调用工具、调用哪个工具
return {"messages": [llm.invoke(state["messages"])], "tool_name": "xxx"}
# 节点2:工具调用执行
def call_tool(state: State) -> dict:
tool_name = state["tool_name"]
tool_result = tools[tool_name].run(state["messages"][-1].content)
return {"messages": [HumanMessage(content=tool_result)]}
# 节点3:纯对话
def chatbot(state: State) -> dict:
return {"messages": [llm.invoke(state["messages"])]}
# 添加节点
graph_builder.add_node("judge_tool", judge_tool)
graph_builder.add_node("call_tool", call_tool)
graph_builder.add_node("chatbot", chatbot)
# 添加边+分支判断
graph_builder.add_edge(START, "judge_tool")
# 分支:需要调用工具则走call_tool,否则走chatbot
graph_builder.add_conditional_edges(
"judge_tool",
lambda state: "call_tool" if state["tool_name"] else "chatbot",
{"call_tool": "call_tool", "chatbot": "chatbot"}
)
graph_builder.add_edge("call_tool", "chatbot") # 工具调用结果返回给大模型做总结
graph_builder.add_edge("chatbot", END)
return graph_builder.compile(checkpointer=MemorySaver())
复用价值:基于现成的 FastAPI 后端脚手架,不用从零写接口,只需聚焦 Agent 的核心逻辑(节点 / 流程 / 工具调用),节省后端开发时间。
🔥 核心复用模块 3:流式响应全链路(main.py+webUI.py)—— 直接复用,适配任意 Agent
流式响应是 Agent 的基础体验需求(尤其是大模型生成慢 / 工具调用耗时的场景),这套代码实现了「后端流式生成 + 前端流式接收」的全链路,可直接复用到任意 Agent 项目:
- 后端流式:复制
main.py中generate_stream函数,只需修改内部的graph.astream入参(比如添加工具调用的状态),流式返回格式完全兼容 OpenAI,不用改前端; - 前端流式:复制
webUI.py的全部代码,只需修改后端接口地址(url),就能快速得到一个可调试的流式聊天前端,不用写任何前端代码。
复用价值:省去流式响应的全链路开发,解决「断流、数据解析、实时更新界面」等高频坑,直接实现 Agent 的流式交互。
📌 高频复用模块 4:会话记忆与 LangGraph 基础(main.py)—— 直接复用,扩展即可
Agent 的核心能力之一是上下文记忆 + 流程管理,这套代码基于 LangGraph 实现了基础的会话记忆,可直接复用到所有需要上下文的 Agent:
- 直接复用
State类(messages: Annotated[list, add_messages]):LangGraph 的add_messages会自动拼接对话历史,不用自己手动管理; - 直接复用
MemorySaver:实现会话记忆的持久化(本地内存,可改造为 Redis/MongoDB 实现分布式); - 直接复用
config = {"configurable": {"thread_id": ...}}:通过thread_id实现多会话隔离,做多用户 Agent 时直接用。
扩展思路:做长会话 Agent 时,只需在chatbot节点中添加「对话历史总结」逻辑,将长对话总结为摘要,避免上下文过长导致的 token 浪费,示例:
python
运行
def chatbot(state: State) -> dict:
# 对话历史总结:如果历史消息超过10条,总结为摘要
if len(state["messages"]) > 10:
summary = llm.invoke([HumanMessage(content=f"总结以下对话:{state['messages'][:-1]}")]).content
# 用摘要+最新问题作为新的上下文
new_messages = [SystemMessage(content=f"对话摘要:{summary}"), state["messages"][-1]]
return {"messages": [llm.invoke(new_messages)]}
else:
return {"messages": [llm.invoke(state["messages"])]}
📌 高频复用模块 5:输出格式化 + Prompt 管理(main.py+prompt_template_user.txt)—— 直接复用,按需修改
- 输出格式化(main.py 的
format_response) :直接复用到 Agent 项目,解决大模型输出「代码块无格式、段落混乱」的问题,让 Agent 的输出更专业;可根据需要修改正则(比如添加表格格式化、列表格式化); - Prompt 管理(main.py+prompt_template_*.txt) :这套代码将 Prompt 拆分为单独的 txt 文件,而非硬编码在代码中,可直接复用这种 Prompt 管理方式,做 Agent 时只需修改 txt 文件中的 Prompt 内容(比如工具调用的 Prompt、任务规划的 Prompt),不用改代码,便于 Prompt 调优。
📌 基础复用模块 6:Gradio 调试前端(webUI.py)—— 直接复用,零改造
做 Agent 开发时,本地调试是高频需求,这套代码的 Gradio 前端可直接复用,只需修改后端接口地址(url = "http://localhost:8012/v1/chat/completions"),就能快速实现:
- 流式聊天界面;
- 回车 / 按钮提交;
- 对话历史展示;
- 输入框自动清空。
复用价值:不用写前端代码,就能快速调试 Agent 的核心逻辑(对话 / 工具调用 / 上下文),提高开发效率。
三、基于这套代码做 Agent 项目的整体开发思路
以工具调用型 Agent(最常见的开源 Agent 类型)为例,基于这套代码的开发流程只需3 步,其余代码全部复用,聚焦核心业务:
- 复制脚手架:将
llms.py/main.py/webUI.py复制到项目,保留所有工程化代码(模型对接 / 后端接口 / 流式 / 日志); - 扩展核心逻辑:修改
main.py的create_graph函数,为 LangGraph 添加工具调用节点、分支判断节点、工具结果总结节点,并引入需要的工具(如 LangChain 的SerpAPI/PythonREPLTool/ 自定义工具); - 调优 Prompt + 配置:修改
prompt_template_system.txt为工具调用的系统 Prompt(比如「你是一个工具调用助手,根据用户问题选择合适的工具,工具调用结果后再回答用户」),并在llms.py中配置合适的模型。
四、总结
这套代码的核心价值不是「实现了一个简单的聊天机器人」,而是提供了一套大模型 Agent 开发的「工程化脚手架」,覆盖了 Agent 开发中90% 的通用工程化需求(模型对接 / 后端服务 / 流式 / 会话记忆 / 调试前端),让你不用从零写重复代码,只需聚焦 Agent 的核心业务逻辑(节点设计 / 流程管理 / 工具调用 / 任务规划)。
你后续做任何 Agent 项目,都可以把这套代码作为基础模板,在此之上做扩展即可 —— 比如做 RAG Agent,只需添加「文档加载 / 检索节点」;做任务规划 Agent,只需添加「任务拆分 / 子任务执行节点」;做多智能体协作 Agent,只需添加「智能体通信节点」。
简单来说:这套代码帮你解决了 Agent 开发的「工程化基建」,你只需要专注于 Agent 的「智能逻辑」。
精准生成该项目代码的提示词(分 3 版,从基础到极致)
想要让大模型生成和这套LangGraph+FastAPI+Gradio 多模型对话 Agent 脚手架完全匹配的代码,核心是把技术栈、功能模块、工程化要求、交互逻辑全部明确告知模型,避免模糊表述。以下按「基础可用版(快速生成核心代码)、精准匹配版(和原项目 90% 一致)、极致复刻版(1:1 还原原项目细节)」分级编写提示词,你可根据需求直接复制使用,也可按需删减模块。
基础可用版(快速生成核心代码,适合快速搭建原型)
核心需求
生成一套多模型对接 + FastAPI 后端 + Gradio 前端的流式对话 Agent 脚手架,包含基础工程化配置,可直接运行。
plaintext
请帮我编写一套基于Python的大模型流式对话Agent脚手架代码,要求如下:
1. 技术栈:使用LangChain OpenAI、LangGraph、FastAPI、Gradio、Pydantic,日志用Python logging;
2. 模型对接:支持多源模型(OpenAI、本地Ollama、阿里通义千问、OneAPI),通过配置字典解耦,可通过参数切换模型,自带异常处理、超时、重试机制;
3. 后端服务:基于FastAPI搭建兼容OpenAI格式的/v1/chat/completions接口,支持流式/非流式两种响应,用Pydantic做请求/响应数据校验,实现多用户多会话的上下文隔离(基于userId+conversationId),服务启动时全局初始化模型和LangGraph,只执行一次;
4. 会话管理:基于LangGraph的StateGraph和MemorySaver实现会话记忆,自动拼接对话历史,支持上下文连续对话;
5. 前端界面:基于Gradio搭建聊天前端,支持流式实时刷新回复、按钮/回车提交、输入框自动清空、对话历史展示;
6. 工程化:代码按功能模块化拆分(模型对接、后端服务、前端界面),大模型输出做格式化处理(优化代码块、段落分隔),全流程添加日志记录(请求、响应、错误、初始化);
7. 运行要求:所有代码可直接运行,给出清晰的启动步骤和环境依赖说明(requirements.txt)。
精准匹配版(和原项目 90% 一致,还原核心模块 + 逻辑)
核心需求
完全匹配原项目的模块拆分、函数命名、核心逻辑,生成可直接复用的工程化代码,包含原项目所有核心功能。
plaintext
请帮我编写一套和「LangGraph+FastAPI+Gradio多模型流式对话Agent」完全匹配的Python工程化代码,严格按以下要求实现,代码可直接运行:
### 一、代码结构(按文件拆分,和工业级项目一致)
1. llms.py:专门做模型对接,封装多源模型初始化逻辑;
2. main.py:FastAPI后端服务核心,实现OpenAI标准接口、LangGraph状态图、流式/非流式响应、会话管理;
3. webUI.py:Gradio前端界面,实现流式聊天交互;
4. prompt_template_system.txt、prompt_template_user.txt:单独存放Prompt模板,不硬编码在代码中;
5. requirements.txt:列出所有依赖包及对应版本。
### 二、各文件核心功能要求
#### 1. llms.py
- 定义MODEL_CONFIGS字典,配置OpenAI、oneapi、qwen、ollama的base_url、api_key、model;
- 自定义LLMInitializationError异常类,处理模型初始化错误;
- 实现initialize_llm函数:初始化ChatOpenAI实例,支持超时30s、最大重试2次,ollama做特殊环境变量处理;
- 实现get_llm函数:封装initialize_llm,默认使用openai,初始化失败时自动重试默认配置。
#### 2. main.py
- 配置LangSmith环境变量做应用跟踪;
- 从txt文件加载系统/用户Prompt模板,支持动态替换用户问题{query};
- 用Pydantic定义Message、ChatCompletionRequest、ChatCompletionResponse等数据模型,严格兼容OpenAI格式;
- 用LangGraph的StateGraph定义State状态(带add_messages的messages列表),create_graph函数构建只有chatbot节点的状态图,用MemorySaver做内存会话存储;
- 实现format_response函数:用正则优化大模型输出,处理代码块(```)、段落分隔、句子换行;
- 用FastAPI的lifespan做生命周期管理,服务启动时全局初始化模型、创建StateGraph、可视化保存图为png;
- 实现/v1/chat/completions POST接口:支持流式(StreamingResponse返回SSE流)/非流式(JSONResponse),通过userId+conversationId拼接thread_id实现会话隔离,流式响应最后返回finish_reason=stop。
#### 3. webUI.py
- 配置后端接口地址为http://localhost:8012/v1/chat/completions;
- 实现send_message生成器函数:封装请求参数,流式接收后端响应,实时更新Gradio Chatbot,对模型输出中的和
做替换(转为思考过程+最终回复);
- Gradio界面包含Chatbot、文本输入框、发送按钮,支持按钮点击/回车提交,提交后清空输入框,流式实时刷新回复;
- 全流程添加日志,处理请求异常、JSON解析异常、空行/空字符串等情况。
### 三、工程化要求
1. 所有文件添加标准化日志配置(INFO级别,包含时间、名称、级别、消息);
2. 所有异常做捕获处理,返回友好的错误提示(如请求失败、解析错误、服务未初始化);
3. 流式响应严格兼容OpenAI的chunk格式,前端能正常解析;
4. 代码添加清晰的注释,关键函数/逻辑说明用途;
5. 给出详细的启动步骤:先装依赖、再启动后端、最后启动前端。
极致复刻版(1:1 还原原项目代码,包含所有细节 / 注释 / 变量名)
核心需求
完全复刻你提供的原项目代码,包括变量名、函数名、注释、日志内容、异常处理分支、甚至空行 / 代码格式,适合需要精准还原的场景。
plaintext
请帮我1:1复刻一套Python大模型对话Agent代码,完全匹配以下所有细节,变量名、函数名、注释、日志内容、异常分支、代码格式均保持一致,代码可直接运行:
### 一、项目文件结构
llms.py、main.py、webUI.py、prompt_template_system.txt、prompt_template_user.txt、requirements.txt
### 二、各文件精准复刻要求(完全匹配原代码)
#### 1. llms.py
- 模型配置字典MODEL_CONFIGS:openai(base_url=https://api.chatanywhere.tech,model=gpt-5-mini)、oneapi(base_url=http://139.224.72.218:3000/v1,api_key=sk-ROhn6RNxulVXhlkZ0713F29093Ea49AcAcA29b96125aF1Ff,model=qwen-max)、qwen(base_url=https://dashscope.aliyuncs.com/compatible-mode/v1,api_key=sk-5cee351038c943648971907366eabafe,model=qwen-max)、ollama(base_url=http://localhost:11434/v1,api_key=ollama,model=deepseek-r1:14b);
- 默认配置DEFAULT_LLM_TYPE="openai",DEFAULT_TEMPERATURE=0.7;
- 自定义异常LLMInitializationError,实现initialize_llm和get_llm函数,函数内的异常捕获、日志内容、返回值完全一致;
- 底部保留示例使用的注释和if __name__ == "__main__"的测试代码。
#### 2. main.py
- 配置LangSmith环境变量:LANGCHAIN_TRACING_V2="true",LANGCHAIN_API_KEY="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";
- 端口PORT=8012,默认llm_type="openai",全局变量graph=None;
- Pydantic模型(Message、ChatCompletionRequest、ChatCompletionResponse等)的字段、默认值、注释完全一致;
- State类为TypedDict,messages字段是Annotated[list, add_messages];
- create_graph函数、save_graph_visualization函数、format_response函数的实现逻辑、正则表达式、代码分支完全一致;
- lifespan异步上下文管理器的启动/关闭逻辑、日志内容完全一致;
- /v1/chat/completions接口的逻辑:请求校验、config拼接(userId+"@@"+conversationId)、Prompt拼接、流式/非流式处理分支、日志内容、异常处理完全一致;
- 流式响应的generate_stream函数、非流式响应的结果处理完全一致,返回格式严格匹配原代码;
- 底部if __name__ == "__main__"的启动代码为uvicorn.run(app, host="0.0.0.0", port=PORT)。
#### 3. webUI.py
- 后端接口url="http://localhost:8012/v1/chat/completions",默认stream_flag=True;
- send_message函数的逻辑:参数封装(userId=123,conversationId=123)、等待状态(正在生成回复...)、format_response函数对
和
核心文件总览
共 4 个核心文件,分工明确:配置模型 + 工具类 (llms.py) →后端 API 服务 (main.py) →前端交互界面 (webUI.py) →用户提示词模板 (prompt_template_user.txt) ,实现「前端输入→后端接口请求→模型推理→结果返回展示」的完整聊天流程。
各文件作用
1. main.py - 后端 API 服务(核心枢纽)
核心作用:基于 FastAPI 搭建后端服务,暴露标准的/v1/chat/completions接口,连接模型 (llms.py) 和前端 (webUI.py) ,处理会话上下文、流式 / 非流式响应、请求分发。
-
初始化配置:加载 LangSmith 跟踪(调试用)、定义服务端口 8012、读取系统 / 用户提示词模板;
-
数据模型:用 Pydantic 定义请求 / 响应格式(和 OpenAI API 兼容),保证参数规范;
-
会话管理:基于 LangGraph 搭建状态图,用
MemorySaver实现上下文记忆,通过userId+conversationId区分不同会话; -
核心逻辑:
- 应用启动时(lifespan),通过
llms.py初始化模型、创建 LangGraph 图、生成可视化图; - 接收前端请求,解析用户问题,拼接系统 + 用户提示词;
- 支持流式响应(逐字返回)和非流式响应(一次性返回),对模型结果做格式化(分段、代码块处理);
- 暴露 POST 接口,供前端调用,统一返回 JSON / 流数据。
- 应用启动时(lifespan),通过
-
辅助功能:
format_response()美化模型输出,提升可读性;save_graph_visualization()生成状态图 PNG,方便调试。
2. llms.py - 模型初始化工具类(核心依赖)
核心作用:封装不同大模型的配置,提供统一的模型初始化接口,是整个项目的模型入口。
- 定义 4 类模型配置(openai/oneapi/qwen/ollama),包含
base_url/api_key/model等关键信息; - 自定义异常
LLMInitializationError,专门处理模型初始化失败; - 核心函数
initialize_llm():根据传入的模型类型,创建并返回ChatOpenAI实例,做了配置校验、异常捕获; - 封装函数
get_llm():调用初始化函数,失败时自动重试默认模型(openai),简化上层调用; - 支持本地开源模型(ollama/deepseek-r1)、云端模型(openai / 千问)、代理模型(oneapi)。
3. webUI.py - 前端可视化界面(用户交互层)
核心作用:基于 Gradio 快速搭建可视化聊天界面,无需前端开发,用户可直接输入问题,展示模型回复,是用户操作入口。
-
配置:指定后端 API 地址(localhost:8012)、默认开启流式输出;
-
核心函数
send_message():- 接收用户输入和聊天历史,封装成后端要求的请求参数;
- 向
main.py的/v1/chat/completions接口发送 POST 请求; - 流式接收后端返回的结果,实时更新聊天界面;
- 对模型返回的
/标记做格式化(转为「思考过程 / 最终回复」),优化展示; - 异常处理:请求失败、JSON 解析错误时,给用户友好提示;
-
界面布局:简单的聊天窗口 + 输入框 + 发送按钮,支持回车 / 点击发送,发送后清空输入框。
4. prompt_template_user.txt - 用户提示词模板(简单配置)
核心作用:定义用户问题的固定模板,供main.py读取和拼接,方便后续统一修改用户提示词格式,解耦提示词和业务代码。
- 内容仅为
用户问:{query},{query}是占位符,会被实际的用户问题替换; - 配合系统提示词模板,共同组成发给模型的完整提示词。
4 个文件协同工作流程(完整调用链)
启动阶段(先启动后端,再启动前端)
-
运行
main.py:- 触发
lifespan启动逻辑,调用llms.py的get_llm()初始化指定模型(默认 openai); - 创建 LangGraph 状态图(带上下文记忆),生成可视化图;
- 启动 FastAPI 服务,监听
0.0.0.0:8012,等待前端请求。
- 触发
-
运行
webUI.py:- 启动 Gradio 前端,监听
127.0.0.1:7860,生成可视化聊天界面; - 前端默认指向后端 API 地址
localhost:8012,建立连接。
- 启动 Gradio 前端,监听
聊天交互阶段(用户操作→结果展示)
plaintext
用户在webUI输入问题→点击发送
↓
webUI.py封装请求参数(messages/stream/userId等)→向后端8012端口发送POST请求
↓
main.py接收请求→解析用户问题→拼接系统提示词+prompt_template_user.txt的模板(替换{query})
↓
main.py调用LangGraph的stream/astream方法→底层调用llms.py初始化的模型实例进行推理
↓
模型返回推理结果→main.py对结果格式化(分段/代码块)→按流式/非流式返回给webUI.py
↓
webUI.py接收后端结果→实时更新聊天界面(流式逐字展示)→对`/`标记二次格式化,展示「思考过程+最终回复」
↓
用户在前端看到完整回复,可继续输入下一个问题(main.py通过userId+conversationId保留上下文)
核心协同关键点
- 接口兼容:后端
main.py实现了和 OpenAI 一致的/v1/chat/completions接口,前端webUI.py按该规范请求,解耦前后端; - 模型统一入口:
main.py不直接写模型配置,而是通过llms.py的get_llm()获取模型,后续换模型只需改llm_type参数,无需修改业务代码; - 流式响应打通:从前端
stream_flag=True→后端request.stream→LangGraphastream→前端逐行接收,全链路支持流式输出; - 上下文记忆:
main.py通过 LangGraph+MemorySaver,结合userId+conversationId实现会话记忆,前端只需传递历史,无需处理上下文逻辑。
关键亮点
- 前后端分离,后端提供标准 API,前端快速可视化;
- 多模型兼容,换模型仅需修改
llm_type; - 支持流式 / 非流式响应,上下文记忆,异常全链路捕获;
- 提示词解耦,格式修改无需改代码;
- 符合 OpenAI API 规范,后续可对接其他兼容该规范的前端 / 工具。
main
代码整体概述
这是一个基于 FastAPI 搭建的大模型聊天接口服务,整合了 LangGraph(状态图)、LangChain 等库实现带上下文记忆的对话功能,支持流式 / 非流式响应,还包含日志、状态图可视化、响应格式化等辅助能力,最终对外提供标准的 /v1/chat/completions 接口(对齐 OpenAI 接口格式)。
逐段详细讲解
1. 导入依赖模块
python
运行
import os
import re
import uuid
import time
import json
import logging
from contextlib import asynccontextmanager
from pydantic import BaseModel, Field
from typing import List, Optional
from langchain_core.prompts import PromptTemplate
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from llms import get_llm
from langgraph.checkpoint.memory import MemorySaver
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
import uvicorn
os:用于读取 / 设置环境变量;re:正则表达式,用于后续响应文本的格式化处理;uuid:生成唯一 ID,用于接口响应的id字段;time:获取时间戳,用于接口响应的created字段;json:处理 JSON 数据序列化 / 反序列化,尤其流式响应中需手动构造 JSON 字符串;logging:配置日志,记录服务运行的关键信息(如请求、错误、初始化状态);asynccontextmanager:创建异步上下文管理器,用于管理 FastAPI 应用的生命周期;pydantic相关(BaseModel,Field):定义数据模型,做请求 / 响应的参数校验和结构化;typing相关(List,Optional,Annotated):类型注解,约束变量 / 函数的类型,提升代码可读性和健壮性;TypedDict:定义带类型的字典(状态图的状态结构);langgraph相关(StateGraph,START,END,add_messages):构建对话状态图,实现上下文记忆和流程控制;PromptTemplate:LangChain 提供的提示词模板类,用于标准化提示词格式;get_llm:自定义的获取大模型实例的函数(来自llms模块,代码中未展示实现);MemorySaver:LangGraph 的内存检查点,用于存储会话上下文(内存级别的会话记忆);FastAPI/HTTPException:搭建 Web 服务、抛出 HTTP 异常;JSONResponse/StreamingResponse:FastAPI 的响应类,分别返回 JSON 格式响应、流式响应;uvicorn:运行 FastAPI 应用的 ASGI 服务器。
2. 环境变量与日志配置
python
运行
# 设置LangSmith环境变量 进行应用跟踪,实时了解应用中的每一步发生了什么
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "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"
# 设置日志模版
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
- 配置 LangSmith 环境变量:
LANGCHAIN_TRACING_V2开启 LangChain 的跟踪功能,LANGCHAIN_API_KEY是访问 LangSmith 的密钥,用于监控大模型调用、状态图执行等流程; - 配置日志:设置日志级别为
INFO(会输出 INFO 及以上级别日志),定义日志格式(包含时间、日志器名称、级别、消息),并创建当前模块的日志器logger。
3. 提示词模板与全局配置
python
运行
# prompt模版设置相关 根据自己的实际业务进行调整
PROMPT_TEMPLATE_TXT_SYS = "prompt_template_system.txt"
with open(PROMPT_TEMPLATE_TXT_SYS, "r", encoding="utf-8") as f: sys_prompt = f.read()
PROMPT_TEMPLATE_TXT_USER = "prompt_template_user.txt"
with open(PROMPT_TEMPLATE_TXT_USER, "r", encoding="utf-8") as f: user_prompt = f.read()
# openai:调用gpt模型,oneapi:调用oneapi方案支持的模型,ollama:调用本地开源大模型,qwen:调用阿里通义千问大模型
llm_type = "openai"
# API服务设置相关
PORT = 8012
# 申明全局变量 全局调用
graph = None
- 读取提示词模板:从
prompt_template_system.txt(系统提示词)、prompt_template_user.txt(用户提示词)文件中读取文本内容,分别赋值给sys_prompt、user_prompt,后续用于构造大模型的输入提示; - 定义大模型类型:
llm_type指定要调用的大模型类型(如 openai/gpt、oneapi、ollama、通义千问); - 定义服务端口:
PORT = 8012表示服务将运行在 8012 端口; - 声明全局变量
graph:用于存储后续构建的 LangGraph 状态图对象,全局复用。
4. Pydantic 模型定义(请求 / 响应结构)
python
运行
# 定义消息类,用于封装API接口返回数据
# 定义Message类,规定之后使用messages时,每个消息对象都必须包含role和content两个字段
class Message(BaseModel):
role: str
content: str
# 定义ChatCompletionRequest类
class ChatCompletionRequest(BaseModel):
messages: List[Message]
"""List:表示这个字段的类型是列表
[Message]:是对列表的强约束—— 列表里不能随便塞值,每个元素都必须是定义的Message类的对象"""
stream: Optional[bool] = False
userId: Optional[str] = None
conversationId: Optional[str] = None
# 定义ChatCompletionResponseChoice类
class ChatCompletionResponseChoice(BaseModel):
index: int
message: Message
finish_reason: Optional[str] = None
# 定义ChatCompletionResponse类
class ChatCompletionResponse(BaseModel):
id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex}") #每次创建对象,都会生成一个全新的、不重复的 ID
object: str = "chat.completion"
created: int = Field(default_factory=lambda: int(time.time())) #每次创建对象,都会获取当前的系统时间,生成全新的时间戳
choices: List[ChatCompletionResponseChoice]
system_fingerprint: Optional[str] = None
-
Message类:标准化消息结构,role(角色,如 user/assistant/system)和content(消息内容)是必选字段; -
ChatCompletionRequest类:定义接口的请求参数结构:messages:消息列表,每个元素都是Message实例,存储对话上下文;stream:可选布尔值,标识是否需要流式响应(默认 False);userId/conversationId:可选字符串,分别标识用户 ID、会话 ID,用于区分不同用户 / 不同会话的上下文;
-
ChatCompletionResponseChoice类:定义响应中choices数组的元素结构:index:选项索引(此处固定为 0,因为只有一个回复);message:Message实例,存储助手的回复内容;finish_reason:可选字符串,标识回复结束原因(如 stop);
-
ChatCompletionResponse类:定义接口的响应结构(对齐 OpenAI 接口格式):id:响应唯一 ID,通过uuid.uuid4().hex生成,default_factory表示创建实例时自动生成;object:固定值chat.completion,标识响应类型;created:响应创建时间戳,创建实例时自动获取当前时间;choices:回复列表,元素为ChatCompletionResponseChoice实例;system_fingerprint:可选字段,标识系统指纹(此处未使用)。
5. 定义 LangGraph 状态结构
python
运行
# 定义chatbot的状态
class State(TypedDict):
messages: Annotated[list, add_messages] #实现会话上下文的连续(比如用户问 “你好”,再问 “你是谁”,模型能知道上下文);
-
State类:继承TypedDict,定义 LangGraph 状态图的状态结构; -
messages字段:类型为list,并通过add_messages注解实现「消息追加」—— 每次执行状态图时,新消息会追加到messages列表中,从而保留对话上下文。 -
Annotated是类型注解,不改变变量的实际类型,只是给类型附加额外信息
-
TypedDict定义一个字典类型约束,State本质是一个字典 -
强制这个字典必须有
messages键,且值为指定类型,add_messages:LangGraph 内置函数 -
总之,这个State定义了两个信息:
-
状态字段的基础类型(
list) -
状态更新的特殊规则(
add_messages:追加而非覆盖)
6. 创建 LangGraph 状态图
python
运行
# 创建和配置chatbot的状态图
def create_graph(llm) -> StateGraph: #创建一个最简单的状态图(只有一个 chatbot 节点),把 LLM 模型封装成带内存记忆的聊天机器人,返回可执行的状态图对象
try:
# 构建graph
graph_builder = StateGraph(State) #State类保存整个会话的所有关键数据;
# 定义chatbot的node
def chatbot(state: State) -> dict:
# 处理当前状态并返回 LLM 响应
return {"messages": [llm.invoke(state["messages"])]}
# 配置graph
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)
# 这里使用内存存储 也可以持久化到数据库
memory = MemorySaver()
# 编译生成graph并返回
return graph_builder.compile(checkpointer=memory)
except Exception as e:
raise RuntimeError(f"Failed to create graph: {str(e)}")
-
函数作用:接收大模型实例
llm,构建并返回一个带内存记忆的 LangGraph 状态图; -
步骤拆解:
-
初始化状态图构建器:
graph_builder = StateGraph(State),绑定状态结构State;这个步骤就是画图纸,目前空白 -
定义
chatbot节点函数:接收当前状态state,调用llm.invoke(state["messages"])让大模型处理上下文消息,返回包含新消息的字典(新消息会追加到状态的messages中); -
配置状态图:
add_node("chatbot", chatbot):添加名为chatbot的节点,关联上述节点函数;add_edge(START, "chatbot"):添加「起始节点 → chatbot 节点」的边,即流程从 START 开始,先执行 chatbot 节点;add_edge("chatbot", END):添加「chatbot 节点 → 结束节点」的边,即 chatbot 节点执行完后流程结束;
-
配置内存存储:
MemorySaver()是 LangGraph 的内存检查点,用于存储不同会话的状态(上下文),也可替换为数据库持久化方案; -
编译状态图:
graph_builder.compile(checkpointer=memory)生成可执行的状态图对象,返回该对象; -
异常处理:捕获构建过程中的异常,包装为
RuntimeError抛出。
-
7. 状态图可视化保存
# 将构建的graph可视化保存为 PNG 文件
def save_graph_visualization(graph: StateGraph, filename: str = "graph.png") -> None:
try:
with open(filename, "wb") as f: #打开一个文件,给它起个小名 `f`,用完自动关闭。
f.write(graph.get_graph().draw_mermaid_png())
'''把你的**流程图转换成一张 PNG 图片**然后**写入文件里保存**'''
logger.info(f"Graph visualization saved as {filename}") #控制台打印:
except IOError as e:
logger.info(f"Warning: Failed to save graph visualization: {str(e)}")
-
函数作用:将 LangGraph 状态图转换为 Mermaid 格式的 PNG 图片并保存;
-
步骤:
graph.get_graph().draw_mermaid_png():获取状态图的图形结构,生成 PNG 格式的二进制数据;- 将二进制数据写入指定文件(默认
graph.png); - 捕获 IO 异常(如文件写入失败),记录警告日志。
8. 响应格式化函数
python
运行
# 格式化响应,对输入的文本进行段落分隔、添加适当的换行符,以及在代码块中增加标记,以便生成更具可读性的输出
def format_response(response):
# 使用正则表达式 \n{2, }将输入的response按照两个或更多的连续换行符进行分割。这样可以将文本分割成多个段落,每个段落由连续的非空行组成
paragraphs = re.split(r'\n{2,}', response)
# 空列表,用于存储格式化后的段落
formatted_paragraphs = []
# 遍历每个段落进行处理
for para in paragraphs:
# 检查段落中是否包含代码块标记
if '```' in para:
# 将段落按照```分割成多个部分,代码块和普通文本交替出现
parts = para.split('```')
for i, part in enumerate(parts):
# 检查当前部分的索引是否为奇数,奇数部分代表代码块
if i % 2 == 1: # 这是代码块
# 将代码块部分用换行符和```包围,并去除多余的空白字符
parts[i] = f"\n```\n{part.strip()}\n```\n"
# 将分割后的部分重新组合成一个字符串
para = ''.join(parts)
else:
# 否则,将句子中的句点后面的空格替换为换行符,以便句子之间有明确的分隔
para = para.replace('. ', '.\n')
# 将格式化后的段落添加到formatted_paragraphs列表
# strip()方法用于移除字符串开头和结尾的空白字符(包括空格、制表符 \t、换行符 \n等)
formatted_paragraphs.append(para.strip())
# 将所有格式化后的段落用两个换行符连接起来,以形成一个具有清晰段落分隔的文本
return '\n\n'.join(formatted_paragraphs)
-
函数作用:优化大模型回复的格式,提升可读性(处理段落、代码块、换行);
-
步骤:
-
分割段落:用正则
\n{2,}把响应文本按「两个及以上换行」分割成段落; -
遍历处理每个段落:
- 若段落包含代码块标记
`:按` 分割,奇数索引部分(代码块内容)包裹标准的代码块标记(加换行、去首尾空白),再重新拼接; - 若无代码块:将句点 + 空格(
.)替换为句点 + 换行(.\n),让句子分行;
- 若段落包含代码块标记
-
清理并拼接:每个段落去首尾空白后,用两个换行连接所有段落,返回格式化后的文本。
-
9. FastAPI 应用生命周期管理
python
运行
# 定义了一个异步函数lifespan,它接收一个FastAPI应用实例app作为参数。这个函数将管理应用的生命周期,包括启动和关闭时的操作
# 函数在应用启动时执行一些初始化操作,如加载上下文数据、以及初始化问题生成器
# 函数在应用关闭时执行一些清理操作
# @asynccontextmanager 装饰器用于创建一个异步上下文管理器,它允许你在 yield 之前和之后执行特定的代码块,分别表示启动和关闭时的操作
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动时执行
# 申明引用全局变量,在函数中被初始化,并在整个应用中使用
global graph
try:
logger.info("正在初始化模型、定义Graph...")
#(1)初始化LLM
llm = get_llm(llm_type)
#(2)定义Graph
graph = create_graph(llm)
#(3)将Graph可视化图保存
save_graph_visualization(graph)
logger.info("初始化完成")
except Exception as e:
logger.error(f"初始化过程中出错: {str(e)}")
# raise 关键字重新抛出异常,以确保程序不会在错误状态下继续运行
raise
# yield 关键字将控制权交还给FastAPI框架,使应用开始运行
# 分隔了启动和关闭的逻辑。在yield 之前的代码在应用启动时运行,yield 之后的代码在应用关闭时运行
yield
# 关闭时执行
logger.info("正在关闭...")
# lifespan参数用于在应用程序生命周期的开始和结束时执行一些初始化或清理工作,lifespan的核心价值就是 「全局只执行一次」
app = FastAPI(lifespan=lifespan)
-
@asynccontextmanager:装饰器将lifespan转为异步上下文管理器,管理 FastAPI 应用的「启动 - 运行 - 关闭」全生命周期; -
启动阶段:
- 声明使用全局变量
graph; - 调用
get_llm(llm_type)获取大模型实例; - 调用
create_graph(llm)构建状态图,赋值给全局graph; - 调用
save_graph_visualization(graph)保存状态图可视化图片; - 捕获初始化异常,记录错误日志并重新抛出(避免应用在错误状态启动);
- 声明使用全局变量
-
yield:交出控制权,FastAPI 应用开始接收请求; -
关闭阶段:
yield后的代码在应用关闭时执行,仅记录「正在关闭」日志; -
初始化应用:
app = FastAPI(lifespan=lifespan)绑定生命周期函数,确保初始化逻辑「全局只执行一次」。
10. 核心接口:/v1/chat/completions
python
运行
# 封装POST请求接口,与大模型进行问答
@app.post("/v1/chat/completions")
async def chat_completions(request: ChatCompletionRequest):
# 判断初始化是否完成
if not graph:
logger.error("服务未初始化")
raise HTTPException(status_code=500, detail="服务未初始化")
try:
logger.info(f"收到聊天完成请求: {request}")
query_prompt = request.messages[-1].content
logger.info(f"用户问题是: {query_prompt}")
config = {"configurable": {"thread_id": request.userId+"@@"+request.conversationId}}
logger.info(f"用户当前会话信息: {config}")
prompt_template_system = PromptTemplate.from_template(sys_prompt)
prompt_template_user = PromptTemplate.from_template(user_prompt)
prompt = [
{"role": "system", "content": prompt_template_system.template}, {"role": "user", "content": prompt_template_user.format(query=query_prompt)}
]
# 处理流式响应
if request.stream:
async def generate_stream():
chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
async for message_chunk, metadata in graph.astream({"messages": prompt}, config, stream_mode="messages"):
chunk = message_chunk.content
logger.info(f"chunk: {chunk}")
# 在处理过程中产生每个块
yield f"data: {json.dumps({'id': chunk_id,'object': 'chat.completion.chunk','created': int(time.time()),'choices': [{'index': 0,'delta': {'content': chunk},'finish_reason': None}]})}\n\n"
# 流结束的最后一块
yield f"data: {json.dumps({'id': chunk_id,'object': 'chat.completion.chunk','created': int(time.time()),'choices': [{'index': 0,'delta': {},'finish_reason': 'stop'}]})}\n\n"
# 返回fastapi.responses中StreamingResponse对象
return StreamingResponse(generate_stream(), media_type="text/event-stream")
# 处理非流式响应处理
else:
try:
events = graph.stream({"messages": prompt}, config)
for event in events:
for value in event.values():
result = value["messages"][-1].content
except Exception as e:
logger.info(f"Error processing response: {str(e)}")
formatted_response = str(format_response(result))
logger.info(f"格式化的搜索结果: {formatted_response}")
response = ChatCompletionResponse(
choices=[
ChatCompletionResponseChoice(
index=0,
message=Message(role="assistant", content=formatted_response),
finish_reason="stop"
)
]
)
logger.info(f"发送响应内容: \n{response}")
# 返回fastapi.responses中JSONResponse对象
# model_dump()方法通常用于将Pydantic模型实例的内容转换为一个标准的Python字典,以便进行序列化
return JSONResponse(content=response.model_dump())
except Exception as e:
logger.error(f"处理聊天完成时出错:\n\n {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
-
接口作用:对外提供 POST 类型的聊天接口,对齐 OpenAI 的
/v1/chat/completions格式,支持流式 / 非流式响应; -
前置检查:判断全局
graph是否初始化,未初始化则抛出 500 异常; -
核心逻辑(try 块内):
-
日志记录:打印收到的请求、用户的问题(取
messages最后一条的content); -
构建会话配置:
config中的thread_id由userId@@conversationId拼接而成,用于 LangGraph 区分不同用户 / 会话的上下文; -
构造提示词:
- 用
PromptTemplate.from_template分别加载系统 / 用户提示词模板; - 拼接为
prompt列表(system 角色 + user 角色),其中用户提示词通过format(query=query_prompt)填充用户问题;
- 用
-
流式响应处理(
request.stream=True):- 定义异步生成器
generate_stream(); - 调用
graph.astream(...)异步流式执行状态图,stream_mode="messages"表示按消息块返回; - 遍历每个消息块,构造 SSE(Server-Sent Events)格式的响应(
data: {JSON字符串}\n\n),逐块 yield; - 流结束时,返回最后一个块(
delta为空,finish_reason="stop"); - 返回
StreamingResponse,媒体类型为text/event-stream(SSE 格式);
- 定义异步生成器
-
非流式响应处理(
request.stream=False):- 调用
graph.stream(...)执行状态图,遍历返回的事件,提取大模型的回复内容(value["messages"][-1].content); - 调用
format_response(result)格式化回复内容; - 构造
ChatCompletionResponse实例(按 Pydantic 模型结构化); - 调用
model_dump()将 Pydantic 模型转为字典,通过JSONResponse返回;
- 调用
-
-
异常处理:捕获接口处理过程中的所有异常,记录错误日志并抛出 500 异常。
11. 启动服务
python
运行
if __name__ == "__main__":
logger.info(f"在端口 {PORT} 上启动服务器")
# uvicorn是一个用于运行ASGI应用的轻量级、超快速的ASGI服务器实现
# 用于部署基于FastAPI框架的异步PythonWeb应用程序
uvicorn.run(app, host="0.0.0.0", port=PORT)
-
当脚本直接运行时(
__name__ == "__main__"):- 记录启动日志(端口号);
- 调用
uvicorn.run启动 FastAPI 应用,host="0.0.0.0"表示监听所有网络接口,port=PORT绑定配置的 8012 端口。
代码核心流程总结
-
服务启动:通过
lifespan初始化大模型、构建 LangGraph 状态图(带内存记忆)、保存状态图可视化; -
接收请求:客户端调用
/v1/chat/completions接口,传入对话上下文、用户 / 会话 ID、流式标识; -
处理请求:
- 构造带上下文的提示词;
- 流式:异步流式执行状态图,逐块返回 SSE 格式响应;
- 非流式:执行状态图,格式化回复后返回 JSON 响应;
-
上下文管理:通过
thread_id(userId+conversationId)和MemorySaver实现不同会话的上下文隔离与记忆。
llm
代码整体概述
这段 Python 代码主要实现了对不同类型大语言模型(LLM)的初始化和获取功能,基于langchain_openai库的ChatOpenAI类封装,支持 OpenAI、OneAPI、通义千问(Qwen)、Ollama 四种模型类型,包含日志记录、异常处理、默认配置回退等特性。
逐行 / 逐段详细讲解
1. 导入模块与基础配置
python
运行
import os
from typing import Optional
import logging
from langchain_openai import ChatOpenAI
import os:导入 Python 内置的os模块,用于访问操作系统相关功能(如环境变量、系统路径)。from typing import Optional:从typing模块导入Optional类型注解,用于标识变量 / 返回值可以是指定类型或None。import logging:导入 Python 内置的logging模块,用于记录程序运行时的日志(如信息、错误)。from langchain_openai import ChatOpenAI:从langchain_openai库中导入ChatOpenAI类,该类是 LangChain 框架对 OpenAI 风格 API 的大模型客户端封装,可用于调用兼容 OpenAI 接口的 LLM。
python
运行
# 设置日志模版
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
-
logging.basicConfig(...):配置日志的基础格式和级别:level=logging.INFO:设置日志默认级别为 INFO,即只输出 INFO 及更高级别(WARNING、ERROR、CRITICAL)的日志。format=...:定义日志输出格式,包含%(asctime)s(日志记录时间)、%(name)s(日志器名称)、%(levelname)s(日志级别)、%(message)s(日志内容)。
-
logger = logging.getLogger(__name__):创建当前模块的日志器实例,__name__是 Python 内置变量,代表当前模块的名称,后续通过该实例记录日志。
2. 模型配置字典
python
运行
# 模型配置字典
MODEL_CONFIGS = {
"openai": {
"base_url": "https://api.chatanywhere.tech",
"api_key": os.getenv("OPENAI_API_KEY"),
"model": "gpt-5-mini"
},
"oneapi": {
"base_url": "http://139.224.72.218:3000/v1",
"api_key": "sk-ROhn6RNxulVXhlkZ0713F29093Ea49AcAcA29b96125aF1Ff",
"model": "qwen-max"
},
"qwen": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key": "sk-5cee351038c943648971907366eabafe",
"model": "qwen-max"
},
"ollama": {
"base_url": "http://localhost:11434/v1",
"api_key": "ollama",
"model": "deepseek-r1:14b"
}
}
-
定义字典
MODEL_CONFIGS,键是模型类型(openai/oneapi/qwen/ollama),值是对应模型的配置字典,包含三个核心参数:-
base_url:模型 API 的基础请求地址(不同平台的接口地址不同,如 Ollama 是本地地址,通义千问是阿里云兼容 OpenAI 的地址)。 -
api_key:调用模型的身份验证密钥:openai的api_key通过os.getenv("OPENAI_API_KEY")从系统环境变量中获取,避免硬编码敏感信息。oneapi/qwen/ollama的api_key直接硬编码(实际开发中不推荐,此处为示例)。
-
model:指定具体的模型版本(如gpt-5-mini、qwen-max、deepseek-r1:14b)。
-
3. 默认配置常量
python
运行
# 默认配置
DEFAULT_LLM_TYPE = "openai"
DEFAULT_TEMPERATURE = 0.7
DEFAULT_LLM_TYPE = "openai":定义默认的 LLM 类型为openai,当指定模型类型无效 / 初始化失败时,回退使用该默认值。DEFAULT_TEMPERATURE = 0.7:定义模型生成文本的温度参数默认值为 0.7(温度越高,生成结果越随机;越低越精准)。
4. 自定义异常类
python
运行
class LLMInitializationError(Exception):
"""自定义异常类用于LLM初始化错误"""
pass
- 定义自定义异常类
LLMInitializationError,继承自 Python 内置的Exception类,专门用于标识 LLM 初始化过程中出现的错误,使异常类型更具语义化。 - 类内的文档字符串说明该异常的用途,
pass表示类体无额外逻辑(仅作为异常标识)。
5. 核心函数:初始化 LLM 实例
python
运行
def initialize_llm(llm_type: str = DEFAULT_LLM_TYPE) -> Optional[ChatOpenAI]:
"""
初始化LLM实例
Args:
llm_type (str): LLM类型,可选值为 'openai', 'oneapi', 'qwen', 'ollama'
Returns:
ChatOpenAI: 初始化后的LLM实例
Raises:
LLMInitializationError: 当LLM初始化失败时抛出
"""
-
定义函数
initialize_llm,功能是根据指定的llm_type初始化对应的 LLM 实例:- 函数参数
llm_type: str = DEFAULT_LLM_TYPE:参数类型注解为字符串,默认值是DEFAULT_LLM_TYPE(即openai)。 - 返回值注解
-> Optional[ChatOpenAI]:表示返回值可以是ChatOpenAI实例或None(实际代码中成功则返回实例,失败则抛异常,None仅为类型注解的兼容)。 - 文档字符串(docstring):说明函数功能、参数、返回值、抛出的异常类型,提升代码可读性。
- 函数参数
python
运行
try:
# 检查llm_type是否有效
if llm_type not in MODEL_CONFIGS:
raise ValueError(f"不支持的LLM类型: {llm_type}. 可用的类型: {list(MODEL_CONFIGS.keys())}")
-
进入
try代码块,捕获初始化过程中的异常:- 首先检查传入的
llm_type是否在MODEL_CONFIGS的键中(即是否是支持的模型类型)。 - 如果不在,主动抛出
ValueError,提示不支持的类型和可用类型列表。
- 首先检查传入的
python
运行
config = MODEL_CONFIGS[llm_type]
# 特殊处理ollama类型
if llm_type == "ollama":
os.environ["OPENAI_API_KEY"] = "NA"
- 从
MODEL_CONFIGS中取出对应llm_type的配置字典,赋值给config。 - 对
ollama类型做特殊处理:设置系统环境变量OPENAI_API_KEY为"NA"(因为 Ollama 本地部署的模型不需要真实的 OpenAI API Key,该操作是为了兼容ChatOpenAI类的参数要求)。
python
运行
# 创建LLM实例
llm = ChatOpenAI(
base_url=config["base_url"],
api_key=config["api_key"],
model=config["model"],
temperature=DEFAULT_TEMPERATURE,
timeout=30, # 添加超时配置(秒)
max_retries=2 # 添加重试次数
)
-
创建
ChatOpenAI类的实例llm,传入以下参数:base_url:从配置字典中取base_url,指定模型 API 地址。api_key:从配置字典中取api_key,用于身份验证。model:从配置字典中取model,指定具体模型版本。temperature:使用默认值DEFAULT_TEMPERATURE(0.7)。timeout=30:设置请求超时时间为 30 秒,避免请求长时间阻塞。max_retries=2:设置请求失败后的最大重试次数为 2 次,提升容错性。
python
运行
logger.info(f"成功初始化 {llm_type} LLM")
return llm
- 记录 INFO 级别的日志,提示成功初始化指定类型的 LLM。
- 返回初始化好的
llm实例。
python
运行
except ValueError as ve:
logger.error(f"LLM配置错误: {str(ve)}")
raise LLMInitializationError(f"LLM配置错误: {str(ve)}")
except Exception as e:
logger.error(f"初始化LLM失败: {str(e)}")
raise LLMInitializationError(f"初始化LLM失败: {str(e)}")
-
捕获
ValueError(如无效模型类型):- 记录 ERROR 级别的日志,说明配置错误原因。
- 抛出自定义异常
LLMInitializationError,并携带错误信息。
-
捕获其他所有异常(如网络错误、API 密钥错误等):
- 记录 ERROR 级别的日志,说明初始化失败原因。
- 抛出自定义异常
LLMInitializationError,并携带错误信息。
6. 封装函数:获取 LLM 实例(带默认回退)
python
运行
def get_llm(llm_type: str = DEFAULT_LLM_TYPE) -> ChatOpenAI:
"""
获取LLM实例的封装函数,提供默认值和错误处理
Args:
llm_type (str): LLM类型
Returns:
ChatOpenAI: LLM实例
"""
-
定义封装函数
get_llm,功能是获取 LLM 实例,相比initialize_llm增加了 “默认配置回退” 的逻辑:- 参数和返回值注解与
initialize_llm基本一致,返回值明确为ChatOpenAI(无Optional,因为失败会回退默认配置)。 - 文档字符串说明函数是封装层,提供默认值和错误处理。
- 参数和返回值注解与
python
运行
try:
return initialize_llm(llm_type)
except LLMInitializationError as e:
logger.warning(f"使用默认配置重试: {str(e)}")
if llm_type != DEFAULT_LLM_TYPE:
return initialize_llm(DEFAULT_LLM_TYPE)
raise # 如果默认配置也失败,则抛出异常
-
尝试调用
initialize_llm获取指定类型的 LLM 实例,若成功则直接返回。 -
若捕获到
LLMInitializationError(初始化失败):- 记录 WARNING 级别的日志,提示使用默认配置重试。
- 检查当前失败的
llm_type是否不是默认类型(DEFAULT_LLM_TYPE):如果是,则调用initialize_llm使用默认类型重试,并返回结果。 - 如果默认类型也初始化失败(
llm_type已是默认值),则执行raise重新抛出异常,终止流程。
7. 示例使用代码
python
运行
# 示例使用
"""赋值为"__main__":当你直接运行这个 Python 文件时(比如在 VS Code 里点▶️、终端执行python xxx.py)
赋值为文件名:当这个 Python 文件被其他 Python 文件导入作为模块使用时"""
if __name__ == "__main__":
try:
# 测试不同类型的LLM初始化
llm_openai = get_llm("openai")
llm_qwen = get_llm("qwen")
# 测试无效类型
llm_invalid = get_llm("invalid_type")
except LLMInitializationError as e:
logger.error(f"程序终止: {str(e)}")
-
注释说明
__name__的作用:Python 中,当文件被直接运行时,__name__等于"__main__";当文件被导入为模块时,__name__等于文件名。 -
if __name__ == "__main__"::仅当文件被直接运行时,执行以下测试代码。 -
进入
try块,测试 LLM 初始化:llm_openai = get_llm("openai"):尝试获取openai类型的 LLM 实例。llm_qwen = get_llm("qwen"):尝试获取qwen类型的 LLM 实例。llm_invalid = get_llm("invalid_type"):尝试获取无效类型的 LLM 实例(会触发错误,进而回退到默认类型)。
-
捕获
LLMInitializationError:若所有重试(包括默认类型)都失败,记录 ERROR 级别的日志,提示程序终止及错误原因。
代码核心逻辑总结
- 配置层面:定义多类模型的 API 地址、密钥、版本,以及默认参数。
- 初始化层面:封装
initialize_llm函数,校验模型类型、处理特殊模型(Ollama)、创建ChatOpenAI实例,捕获异常并抛出自定义异常。 - 容错层面:封装
get_llm函数,在指定模型初始化失败时,自动回退到默认模型重试。 - 测试层面:通过
if __name__ == "__main__"编写测试代码,验证不同模型类型的初始化效果。
整体代码结构清晰,兼顾了配置管理、异常处理、日志记录和容错机制,是典型的 “配置 - 初始化 - 封装 - 测试” 的工具类代码风格。
webui
- 导入库
- 日志设置
- 接口与变量配置
- 核心聊天函数(最重要)
- Gradio 界面 + 启动
1. 导入需要的工具库
python
运行
import gradio as gr
import requests
import json
import logging
import re
逐行解释:
import gradio as gr导入做网页聊天界面的库,gr是简写,后面用它做输入框、发送按钮、对话窗口。import requests用来给后端 AI 服务发请求,相当于 “给 AI 发消息” 的工具。import json用来处理JSON 格式数据,AI 接口收发消息都用这种格式。import logging用来打印日志,方便看程序运行有没有出错、收到了什么数据。import re正则表达式,用来替换文字、格式化内容(比如把思考过程标记换成中文)。
2. 设置日志模版(方便看运行信息)
python
运行
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
logging.basicConfig(...)设置日志格式:时间 + 日志级别 + 消息内容。logger = logging.getLogger(__name__)创建一个日志对象,后面用logger.info()打印信息,比print()更专业。
3. 配置 AI 接口地址和请求头
python
运行
url = "http://localhost:8012/v1/chat/completions"
headers = {"Content-Type": "application/json"}
url = ...AI 后端服务的地址,程序要把消息发给这里才能得到 AI 回复。headers = ...告诉服务器:我发的是 JSON 格式数据,必须带这个,否则接口不认。
4. 是否开启流式输出(一字一字蹦出来)
python
运行
stream_flag = True
True:AI 一边思考一边输出,像 ChatGPT 那样打字机效果。False:AI 想完再一次性返回。
5. 核心函数:send_message(发消息给 AI)
这是整个代码的灵魂,我逐行讲!
python
运行
def send_message(user_message, history):
-
定义函数,接收两个参数:
user_message:你输入的文字history:聊天历史记录
python
运行
data = {
"messages": [{"role": "user", "content": user_message}],
"stream": stream_flag,
"userId": "123",
"conversationId": "123"
}
-
把你的消息打包成 AI 接口要求的格式:
- 告诉 AI 这是用户发的消息
- 告诉 AI 是否流式输出
- 附带用户 ID、对话 ID(用来区分不同人)
python
运行
history = history + [["user", user_message], ["assistant", "正在生成回复..."]]
yield history
- 先把你的消息显示到界面
- 同时显示 AI:
正在生成回复... yield history= 立刻更新界面,不用等 AI 返回
python
运行
def format_response(full_text):
formatted_text = full_text
formatted_text = re.sub(r'', '**思考过程**:\n', formatted_text)
formatted_text = re.sub(r'', '\n\n**最终回复**:\n', formatted_text)
return formatted_text.strip()
- 定义一个格式化文字的小函数
- 把 AI 内部的思考标记
和换成中文:思考过程、最终回复 - 让界面显示更清晰
6. 流式输出逻辑(True 时走这里)
python
运行
if stream_flag:
开启流式输出时执行这段代码。
python
运行
assistant_response = ""
用来一点点拼接 AI 返回的文字。
python
运行
with requests.post(url, headers=headers, data=json.dumps(data), stream=True) as response:
- 发 POST 请求给 AI
stream=True:一点点接收数据,不是一次性接收json.dumps(data):把字典转成 JSON 字符串发送
python
运行
for line in response.iter_lines():
- 循环读取每一行返回数据
- 流式输出就是一行一行来的
python
运行
if line:
json_str = line.decode('utf-8').strip("data: ")
- 把收到的二进制数据转成字符串
- 去掉前面的
data:标记(OpenAI 格式都带这个)
python
运行
if json_str.startswith('{') and json_str.endswith('}'):
判断是不是合法 JSON 数据。
python
运行
response_data = json.loads(json_str)
把 JSON 字符串转成 Python 字典。
python
运行
if 'delta' in response_data['choices'][0]:
content = response_data['choices'][0]['delta'].get('content', '')
- 取出 AI 最新返回的几个字
- 流式输出就是靠
delta一点点传内容
python
运行
formatted_content = format_response(content)
assistant_response += formatted_content
- 格式化内容
- 拼接到总回复里
python
运行
updated_history = history[:-1] + [["assistant", assistant_response]]
yield updated_history
history[:-1]:删掉 “正在生成” 那一行- 替换成当前已生成的内容
yield:实时刷新界面 → 你看到打字机效果
python
运行
if finish_reason == "stop":
break
AI 回复结束,停止循环。
7. 非流式输出(stream_flag=False 时)
python
运行
else:
response = requests.post(url, headers=headers, data=json.dumps(data))
response_json = response.json()
assistant_content = response_json['choices'][0]['message']['content']
- 一次性请求
- 一次性拿到完整回复
8. Gradio 网页界面(你看到的聊天窗口)
python
运行
with gr.Blocks() as demo:
chatbot = gr.Chatbot(label="聊天对话")
- 创建一个界面容器
- 创建一个聊天对话窗口
python
运行
with gr.Row():
with gr.Column(scale=8):
message = gr.Textbox(label="请输入消息", placeholder="在此输入您的消息")
with gr.Column(scale=2):
send = gr.Button("发送")
- 一行布局
- 左边 80% 宽度:输入框
- 右边 20% 宽度:发送按钮
python
运行
send.click(send_message, [message, chatbot], chatbot)
message.submit(send_message, [message, chatbot], chatbot)
- 点发送按钮 → 执行发消息函数
- 按回车 → 也执行发消息函数
python
运行
send.click(lambda: "", None, message)
message.submit(lambda: "", None, message)
发送后清空输入框,方便你继续聊天。
9. 启动程序
python
运行
if __name__ == "__main__":
demo.launch(server_name="127.0.0.1", server_port=7860)
- 运行脚本时才启动界面
- 启动在本地 7860 端口
- 浏览器打开:
http://127.0.0.1:7860
超级精简总结(你一定能记住)
-
导入工具:界面、发请求、JSON、日志、文字替换
-
设置日志:方便看运行状态
-
配置 AI 地址:告诉程序把消息发去哪里
-
send_message:
- 打包消息
- 发给 AI
- 接收回复
- 格式化文字
- 返回界面显示
-
Gradio 界面:输入框 + 发送按钮 + 聊天窗口
-
启动:打开浏览器就能聊天
你只需要知道 3 件事就能用
- 确保AI 后端服务在 8012 端口运行
- 运行代码 → 打开浏览器
http://127.0.0.1:7860 - 输入消息 → 发送 → 看 AI 回复