从RAG到退货业务流:电商AI Agent客服框架实践

从RAG到退货业务流:电商AI Agent客服框架实践 简介这是一套面向中高级AI工程开发者与电商智能客服系统架构师的生产级AI Agent框架聚焦解决复杂业务场景下多步骤决策、状态持久化与RAG知识融合等核心难题。资源共76个文件含60个Python源码覆盖LangGraph工作流编排、FastAPI路由、RAG检索器、退货子图逻辑及工具函数、5张架构与流程图PNG、2个部署脚本start_worker.sh/start.sh、1个YAML定义智能体行为图谱以及测试用例、配置文件与完整README压缩包仅1.01MB轻量但结构严谨。包内采用清晰分层设计agents目录定义智能体行为tools封装外部系统调用retrievers实现多源知识融合routers组织RESTful接口tests提供全链路自动化验证用例便于快速理解、二次开发与企业集成。已有12人学习下载配套Swagger文档、Postman集合、Docker/K8s部署指南及CI/CD流水线配置开箱即用支持本地大模型或商业API灵活切换并内置审计日志、熔断机制与OAuth2.0安全体系。从 RAG 到退货业务流我用 LangGraph FastAPI 搭了一套电商 AI Agent 客服框架如果你最近在折腾 AI Agent应该和我一样有个明显的感觉LangChain 单独拎出来做 demo 很爽但一旦遇到真实业务场景——比如电商售后客户情绪、订单状态、退款规则纠缠在一起——链条式的东西根本不够用。我这次把整个项目重写了一遍核心骨架换成了 LangGraph接口层用 FastAPI 撑起来目标是做成一套能接真实业务的电商智能客服 Agent 框架。整个项目我打包成了一个压缩包解压后就是一套可以直接跑起来的东西从 RAG 知识库到复杂退货流程都有覆盖。这个框架解决的问题很具体客户上来先问“收货地址怎么改”再追问“已经发货了能不能拦截”最后可能直接甩一句“那我退货运费谁出”。这一类带上下文、带分支、带状态流转的对话传统意图识别加 FAQ 问答根本接不住。LangGraph 的图结构恰好能承载这种多轮、多分支、需要人工介入的业务流FastAPI 则负责把 Agent 能力暴露成标准 HTTP 接口方便前端、小程序或者别的后端服务接入。如果你正在做智能客服、售后自动化、或者任何需要“多步推理 工具调用 状态管理”的 Agent 应用这篇文章值得你完整看完。我走了不少弯路落盘了很多细节节点怎么设计、状态怎么传递、退货单工具该怎么拆、知识库检索怎么和业务分支结合以及 FastAPI 接口层有哪些坑。下面我把这套框架的完整拆解过程写出来。1. 整体架构设计为什么是 LangGraph 而不是 LangChain 或纯 FastAPI先把结论放在前面RAG 知识库是“能回答问题”业务流是“能解决问题”两者之间差了一个编排层。LangGraph 就是用来搭这个编排层的它让 AI 不再是一条直线走到底而是能根据用户意图在图里跳转、回退、挂起、恢复。FastAPI 则是给整个 Agent 穿上一件 HTTP 外衣让外部系统可以稳定地调用它。1.1 核心需求解析电商智能客服到底要解决什么问题电商智能客服和通用聊天机器人最大的区别在于它必须对业务的确定性负责。客户问“我的订单什么时候到”如果是自由发挥的生成式回答模型可能给你编一个物流状态出来。这就决定了系统架构不能只靠大模型的生成能力而要配合结构化数据查询、规则约束、状态机流转以及必要时的“人工接管”。我梳理了这个框架需要覆盖的核心场景大概是下面几类售前咨询商品参数、库存、优惠规则这类问题高度重复答案基本固定适合用 RAG 知识库去检索。订单查询物流轨迹、发货状态、价格明细这类问题需要调用订单系统的接口实时性要求高。售后处理退货申请、退款进度、运费争议、换货流程这类问题有明确的状态流转逻辑而且需要写操作比如生成退货单。多轮会话客户上面说的那句话要结合前面几轮的信息来理解。比如“那这个呢”得知道“这个”指的是哪个商品。情绪识别与升级客户反复追问、语言激烈时系统需要能识别出来并升级到人工客服。这些问题如果只用一个 LangChain 的 Sequential Chain 去串一定会很难收场。因为真实的对话是非线性的用户可能问一半突然转移话题也可能把两个问题混在一起。所以核心架构必须是一个有状态、有分支、能循环的图结构LangGraph 是目前最贴合这个需求的开源方案而且它天然支持状态暂存、断点恢复和人工介入。1.2 技术选型对比LangGraph、LangChain 与 FastAPI 的分工边界我见过很多朋友纠结“LangGraph 和 LangChain 到底有什么区别”说个最直白的理解LangChain 提供的是“积木”LangGraph 提供的是“拼积木的图纸和流程规则”。LangChain 的 Chain 默认是线性的A 接 B、B 接 CLangGraph 的核心抽象是 StateGraph它是一个有向图节点是处理逻辑边是控制流而且节点之间传递的是一个可变的共享状态对象。从代码层面来看LangChain 的链式调用是这样的chain prompt_a | llm | prompt_b | llm result chain.invoke({input: user_input})流程是固定的想加一个条件分支你就会不舒服。LangGraph 则不一样你可以给图添加节点和边还可以定义条件边——根据状态的某种属性决定下一步走哪个节点。FastAPI 在这套框架里的定位非常清楚它不参与业务决策只负责三件事接收 HTTP 请求、把参数转换成 Agent 的输入格式、把 Agent 的输出结果转成统一的 JSON 响应。之所以选 FastAPI 而不是 Flask除了性能因素我比较看重它原生的 OpenAPI 文档支持和基于 Pydantic 的参数校验。你可以打开http://localhost:8000/docs直接调试接口这对联调阶段的效率提升特别大。1.3 项目目录结构设计整个项目我按职责拆成了四个模块agentLangGraph 图定义、apiFastAPI 路由、retrieverRAG 检索层、tools工具函数集。第一步是把目录结构立起来这是后面所有代码的骨架ecommerce_agent/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── api/ │ │ ├── routes.py # 会话接口、流式接口 │ │ └── schemas.py # 请求/响应模型 │ ├── agent/ │ │ ├── graph.py # LangGraph 状态图定义 │ │ ├── state.py # Agent 状态数据结构 │ │ └── nodes.py # 各节点处理逻辑 │ ├── retriever/ │ │ ├── vector_store.py # 向量库初始化 │ │ └── rag_chain.py # 检索增强生成链路 │ ├── tools/ │ │ ├── order_tools.py # 订单查询工具 │ │ ├── return_tools.py # 退货流程工具 │ │ └── policy_tools.py # 政策查询工具 │ ├── models/ │ │ └── llm.py # LLM 统一封装 │ ├── memory/ │ │ └── store.py # 长时间记忆存储 │ └── config.py # 全局配置 ├── data/ │ ├── knowledge_base/ # 知识库原始文档 │ └── vector_db/ # 向量数据库持久化目录 └── requirements.txt这个结构的好处是职责单向依赖api层只依赖agent层的导出函数agent层只依赖tools和retriever不会出现循环引用的混乱局面。而且每个模块都能独立测试。2. RAG 知识库模块从文档清洗到检索增强生成很多人一提到 RAG 就默认是“把 PDF 塞进向量库然后检索”但真实的电商场景里文档格式五花八门——有 Markdown 格式的售后政策、有 Excel 格式的运费补贴表、甚至有客服聊天记录里沉淀出来的 FAQ。更麻烦的是一段文本里既有政策条款又有操作示例直接切块检索效果会很差。2.1 文档加载与切块策略为什么固定长度切块不够用我最初也图省事用固定 500 字符的 chunk 去切结果问题很典型一条退货政策跨了两个 chunk检索时把半截子内容喂给模型回答出来缺胳膊少腿。后来我把切块策略分成了三级第一级是按文档结构切。如果文档有明确的标题层级优先按标题分块——Markdown 文件用 Header 分割PDF 就先用解析库提取出标题大纲。第二级是按语义边界切。结构不明显的文本我按段落切段内用句号、问号、感叹号这些句子边界符号做软切尽量保证一个 chunk 是一个完整的意思单元。第三级是设定最大长度兜底。单个 chunk 最长不超过 800 token超过部分强制截断到下个句子边界。还有一个小技巧是“重叠窗口”相邻 chunk 之间重叠 50~100 个字符。别小看这个重叠它能让检索时上下文衔接连贯很多尤其是模型在回答时引用前文信息的情况下效果提升非常明显。2.2 向量化与混合检索覆盖“关键词精确匹配”和“语义近似”只做向量检索的 RAG 是“半残”的。电商售后里有大量商品型号、订单号、政策编号这类精确信息比如“HUAWEI P60 原装充电器”向量检索很可能把它跟别的型号混在一起。我的做法是混合检索BM25 关键词检索 向量相似度检索双通道并行然后用 RRFReciprocal Rank Fusion把两路结果合并重排。向量模型方面我建议用bge-m3或者text-embedding-3-small这类中英文都覆盖得比较好的模型不要用纯英文模型处理中文客服语料不然检索效果会打折扣。如果你在本地用 Ollama 部署bge-m3有 GGUF 量化版sentence-transformers加载也非常方便。检索流程的伪代码如下def hybrid_search(query: str, top_k: int 6) - list[str]: bm25_docs bm25_index.search(query, top_k10) vector_docs vector_store.similarity_search(query, top_k10) fused rrf_merge(bm25_docs, vector_docs) return [doc.page_content for doc in fused[:top_k]]关于召回之后要不要加重排模型我的建议是如果你的业务对准确性要求高比如售后政策条款的引用加一个cross-encoder重排层是值得的它能把最相关的 2~3 个文档排在前面。代价是会增加 50~100ms 的延迟自己权衡。2.3 知识库答案的“引用可追溯”这里我踩了一个大坑早期版本里 RAG 只输出答案文本客户问“运费谁出”模型答“平台承担”但客服没法验证对不对因为不知道这个答案依据的是哪条政策。后来我在 RAG 链路里强制返回来源文档 ID响应里带上了引用来源。具体做法是在返回结果里加一个sources字段包含命中的文档名和 chunk 索引。前端可以在界面上展示“参考了《退货政策》第 2 条”后端在人工接管时也能快速核对。3. 退货业务流用 LangGraph 编排一个有状态的 Agent终于到重点了。电商售后最复杂的场景就是退货它是“多步骤、多条件、可中断、需确认”的典型流程。我拿“退货申请”这个业务流来完整拆解一遍。3.1 退货流程的状态设计先把退货流程拆成几个关键节点并给出状态定义退货流程状态流转 USER_INPUT - ANALYZE_INTENT - CHECK_ORDER_ELIGIBLE - COLLECT_RETURN_REASON - CHECK_POLICY - CALCULATE_REFUND - GENERATE_RMA - CONFIRM_RETURN - END在这个流程里需要两个核心状态一个是订单快照数据包括订单号、商品列表、订单状态、物流状态另一个是用户在对话中提供的信息包括退货原因、期望处理方式、客户情绪等级。LangGraph 里用 TypedDict 定义状态对象节点本质上就是这个状态对象的“变换函数”。节点执行完之后返回一个 dictLangGraph 会把它合并进全局状态。下面是我在实际项目中使用的状态定义from typing_extensions import TypedDict from langgraph.graph import StateGraph, END class AgentState(TypedDict): messages: list intent: str order_id: str order_info: dict return_reason: str policy_check: dict refund_amount: float rma_number: str need_human: bool current_step: str这里messages是对话历史intent是当前用户意图的识别结果order_id是识别出的订单号order_info是从订单系统拉取的订单详情policy_check是退货政策校验结果refund_amount是退款金额rma_number是退货授权编号need_human是是否需要人工介入的标志current_step是当前流程步骤。3.2 节点逻辑拆解每一步都要可解释、可回退节点的核心逻辑我拆成四个步骤每个节点最好保证职责单一。以check_order_eligible订单资格校验为例它的活儿就是根据订单状态判断是否支持退货。已发货的订单不等于不能退货已签收但超出 7 天无理由期限的订单需要走“质量问题”通道。这些判断规则全部显式写在代码里而不是让模型自由发挥。def check_order_eligible(state: AgentState) - dict: order state[order_info] status order.get(status) days_since_delivery order.get(days_since_delivery, 0) if status in (pending, cancelled): return {policy_check: {eligible: False, reason: 订单未完成不满足退货条件}, current_step: CHECK_ORDER_ELIGIBLE} if status completed: if days_since_delivery 7: return {policy_check: {eligible: True, return_type: standard}, current_step: CHECK_ORDER_ELIGIBLE} return {policy_check: {eligible: True, return_type: quality_issue, need_evidence: True}, current_step: CHECK_ORDER_ELIGIBLE} return {policy_check: {eligible: False, reason: 当前订单状态不支持退货}, current_step: CHECK_ORDER_ELIGIBLE}这个节点的返回值会更新全局状态后面的collect_return_reason节点就可以根据policy_check来决定是否需要客户上传证据照片。3.3 条件边让 Agent 学会“看情况走流程”LangGraph 和普通函数调用最大的区别就在条件边上。比如用户在退货流程进行到一半时突然说“我不退了算了”系统需要能跳出流程而不是继续问“退货原因是什么”。这个能力用条件边实现def route_after_intent(state: AgentState) - str: intent state[intent] if intent return_request: return check_order_eligible elif intent order_query: return query_order_status elif intent human_handoff: return human_agent return END条件边的作用是决定“下一个节点是哪一个”而普通边是固定连接。有了条件边这套系统才能应对真实的、非线性的对话。3.4 图构建把节点和边串起来节点和条件边都定义好之后组装成 LangGraph 图非常快代码就长这样def build_graph(): g StateGraph(AgentState) g.add_node(analyze_intent, analyze_intent_node) g.add_node(check_order_eligible, check_order_eligible_node) g.add_node(collect_return_reason, collect_return_reason_node) g.add_node(check_policy, check_policy_node) g.add_node(calculate_refund, calculate_refund_node) g.add_node(generate_rma, generate_rma_node) g.add_node(query_order_status, query_order_status_node) g.add_node(human_agent, human_agent_node) g.set_entry_point(analyze_intent) g.add_conditional_edges(analyze_intent, route_after_intent, {return_request: check_order_eligible, order_query: query_order_status, human_handoff: human_agent}) g.add_edge(check_order_eligible, collect_return_reason) g.add_edge(collect_return_reason, check_policy) g.add_edge(check_policy, calculate_refund) g.add_edge(calculate_refund, generate_rma) g.add_edge(generate_rma, END) g.add_edge(query_order_status, END) g.add_edge(human_agent, END) return g.compile()注意analyze_intent节点是入口它负责把用户输入转成结构化意图。这里我直接用 LLM 做意图识别用结构化输出限制返回字段。intent_response llm.with_structured_output(IntentResult).invoke([ {role: system, content: 你是电商客服Agent的意图识别模块。只输出结构化的意图类型。}, *state[messages] ])我亲测下来LangGraph 的StateGraph处理这种流程非常顺手而且它天然支持断点恢复Checkpointer——这一点在“人工接管”场景里至关重要。3.5 工具调用Agent 不只能“说”还要能“做”退货流程只有对话显然不够系统要能做写操作创建退货单、调用物流接口、触发退款。这些动作在 LangGraph 里用tool装饰器实现。我封装了两个核心工具check_order_info和create_return_order。from langchain_core.tools import tool tool def check_order_info(order_id: str) - dict: 根据订单号查询订单基本信息、状态、物流信息。 try: order order_service.get(order_id) return {status: order.status, items: order.items, logistics: order.logistics} except OrderNotFound: return {error: f订单 {order_id} 不存在请让客户核对订单号。} tool def create_return_order(order_id: str, reason: str, evidence_images: list[str] []) - dict: 为满足退货条件的订单创建退货单并返回退货授权编号RMA。 rma return_service.create(order_idorder_id, reasonreason, imagesevidence_images) return {rma_number: rma.id, status: rma.status, next_step: 请将商品寄回至指定地址。}为什么要把工具调用粒度做成这么细我踩过教训。早期版本里工具是“万能函数”一个工具传各种参数结果模型经常漏参数、传错格式。拆成小工具后模型更容易理解“什么场景调什么工具”成功率明显上升。但要注意的是工具返回的数据结构一定要稳定不然模型会解析出幻觉字段。4. FastAPI 接口层把 Agent 包成一个标准服务Agent 图构建好了之后接下来的问题是怎么让外部系统网页、小程序、企微机器人调用它FastAPI 在这层的工作至关重要。4.1 路由设计与统一响应格式接口层我有两个核心设计约束第一任何接口的响应格式必须统一前端才能写死一套处理逻辑第二必须支持流式输出因为 LLM 生成答案有延迟不流式输出客户会以为系统卡死了。统一响应格式用了 Pydantic BaseModel 约束class ApiResponse(BaseModel): code: int 0 message: str ok data: dict | None None class ChatRequest(BaseModel): session_id: str message: str user_id: str | None None class ChatResponse(ApiResponse): data: dict | None None接口的返回统一包一层无论成功还是失败前端只需要检查code是否为 0业务数据都在data里。后端想改数据结构时只要保持data向下兼容前端代码完全不用动。4.2 同步接口与流式接口同步接口POST /chat适合一次性提问实现很简单router.post(/chat) async def chat(req: ChatRequest): result await agent_executor.ainvoke({ messages: [{role: user, content: req.message}], session_id: req.session_id, }, config{configurable: {session_id: req.session_id}}) return {code: 0, message: ok, data: {reply: result[messages][-1].content}}但如果你的客户在等一个带知识库引用的长回答流式交互体验会好很多。FastAPI 原生支持StreamingResponseLangGraph 的astream_events可以逐 token 返回。落地时我封装了一个事件生成器router.post(/chat/stream) async def chat_stream(req: ChatRequest): async def event_generator(): async for event in agent_executor.astream_events({messages: [{role: user, content: req.message}]}, versionv2): if event[event] on_chat_model_stream: token event[data][chunk].content if token: yield fdata: {json.dumps({type: token, content: token}, ensure_asciiFalse)}\n\n return StreamingResponse(event_generator(), media_typetext/event-stream)前端可以用标准的 EventSource 或者 fetch-stream 去接这个流。这里提醒一句SSE 流式接口在生产环境经过 Nginx 时必须关闭缓冲否则流会被 Nginx 攒起来一次性吐给前端体验直接回到“转圈等半天”。4.3 会话管理、长期记忆与人工接管FastAPI 是一个无状态的服务但客服场景天然有状态。每个session_id对应一段独立的对话历史。LangGraph 的 Checkpointer 提供了按 session_id 存储状态快照的能力这样 Agent 在处理到中途比如“已生成退货单等待用户确认”时可以停下用户隔几个小时回来还能继续跳转到确认节点。实现上在编译图时传入一个MemorySaverfrom langgraph.checkpoint.memory import MemorySaver checkpointer MemorySaver() agent build_graph().compile(checkpointercheckpointer)调用时传入config里的thread_id作为会话标识result agent.invoke(input, config{configurable: {thread_id: session_id}})这样 LangGraph 会持久化每一步的状态下次同一个thread_id发起请求时它会从上次的断点继续而不是重新跑一遍。这个机制对于退货这种需要多轮确认的流程太重要了。退货业务里还有一条铁律退款金额超过一定阈值或者用户情绪指数超过阈值系统必须转人工。这个逻辑不是模型来决定的是业务规则决定的。我写了一个独立判断节点放在退款计算之后def human_handoff_guard(state: AgentState) - dict: if state[refund_amount] 500 or state.get(user_sentiment_score, 0) 0.8: return {need_human: True, messages: [{role: assistant, content: 您的退款金额较大需要人工客服与您确认已为您转接。}]} return {need_human: False}一旦need_human为 True流程直接跳到human_agent节点系统会发送站内信通知人工客服并把完整对话上下文和订单快照作为工单推送过去。5. 长期记忆与知识沉淀这可能是最容易被忽略但价值最高的模块。LangGraph 的 Checkpointer 偏向会话级的短期记忆实现的是同一线程里的多轮延续。但电商客服场景里长期记忆同样关键——客户半年内退过三次货如果你的系统没有这个历史记录退货策略就会“一视同仁”风险很高。5.1 基于向量库的长期记忆存储我的做法是每一次完整的会话结束时把对话摘要、用户ID、订单ID、退货原因、处理结果写入一个独立的“客户记忆向量库”。下次这个用户再来咨询时系统会先做一次记忆召回把相关历史摘要注入到系统提示词里。def save_memory(session_data: dict): summary summarize_session(session_data) embedding embed_text(summary) memory_store.add( ids[session_data[user_id] _ str(time.time())], embeddings[embedding], metadatas[{user_id: session_data[user_id], ts: time.time()}], documents[summary] ) def recall_memory(user_id: str, top_k: int 5) - list[str]: query f用户 {user_id} 的历史服务记录 results memory_store.query(query_texts[query], n_resultstop_k) return [doc for doc in results[documents][0]]有了这个长期记忆系统对老客户的回答会明显更有人情味。比如客户说“上次那个充电器还是不行”系统能自动关联到他之前买过充电器、退过一次货然后优先引导他走换货而不是退货。这就是长期记忆的核心价值不是重复客户说过的话而是在隐含信息里提供决策依据。5.2 会话摘要避免长期记忆过载直接存全部历史对话会两个问题一是向量化成本高二是检索时噪声大。我在每个会话结束时用 LLM 生成一段结构化的会话摘要比如用户 USER_123 于 2025-06-11 咨询订单 ORD-9981商品为无线鼠标反馈滚轮失灵。 已生成退货单 RMA-20250611-001退款金额 129 元等待用户寄回。这段摘要是一个信息密度极高的压缩块检索效果好且省 token。顺便说一句摘要任务本身就是一个小的 RAG 应用——它把原始对话当作“知识库输入”把摘要格式模板当作 prompt模型的任务只是“忠实转述”。6. 常见问题与排查技巧实录写到最后这部分内容是老生常谈的“避坑指南”但每一个坑都是我在真实项目里踩过的值得单独说一下。6.1 LangGraph 状态持久化的类型陷阱LangGraph 的StateGraph有一个默认行为节点返回的 dict 键会与已有状态合并。如果你返回的键名拼错了比如return_reason写成了return_rason它不会报错而是静默地新增一个键整个图跑完你都不知道哪里出了问题。排查这种问题的经验是在关键节点后面加断言日志把state.keys()打印出来看。6.2 FastAPI 异步接口里的“阻塞陷阱”FastAPI 的async函数跑在事件循环里如果你的节点函数里用了同步的requests.get()去调订单系统会阻塞整个事件循环导致所有请求排队变慢。解决办法有两个要么在同步函数外面套run_in_executor让它去线程池跑要么把接口函数改成普通defFastAPI 会自动放到线程池中执行。我实测第二种方式最简单代码不用动性能还稳。6.3 退货流程中的“会话悬空”客户在“选择退货原因”节点突然关掉页面不回了。如果系统不处理这个会话的 Checkpointer 状态永远停在那个节点下次客户再来可能已经忘了之前聊到哪。我的做法是加一个超时机制每个会话超过 30 分钟没有新消息就把状态标记为expired下次发起请求时强制创建一个新会话并且提示客户“您有一个未完成的退货申请是否继续处理”。6.4 知识库检索结果不稳定同样是“退货政策”问题上午检索到的是 2.1 节内容下午检索到的却是 3.2 节内容。原因是向量检索本身有一定随机性和 embedding 模型对文本的敏感性有关。如果你对回答的稳定性有要求我的建议是给高优先级的业务文档增加“元数据过滤”比如售后政策类问题检索时直接限定category return_policy可以绕过语义匹配的不确定性。6.5 大模型返回内容的“业务约束”LLM 什么都好就是太爱自由发挥。我遇到最典型的问题是客户问“这笔订单能退款吗”政策里明明写着“超时订单需人工审核”模型却给出“可以退款”的肯定答案。后来把所有工具返回的约束字段都强制注入了生成提示词并在生成节点里加了规则校验policy_guard extract_rules(state[policy_check]) answer llm.invoke(f必须基于以下事实回答不得超出此范围{json.dumps(policy_guard, ensure_asciiFalse)})简单说让模型“带着镣铐跳舞”比反复改提示词靠谱得多。最后一点扩展思路这套框架目前跑在退货流程上但图结构本身是通用的。稍微扩展一下你可以把“换货”“补差价”“发票申请”都做成独立子图然后让analyze_intent根据意图路由到不同的子图入口LangGraph 的StateGraph是可以嵌套的。另一个值得探索的方向是把节点可视化——LangGraph 官方有 Studio也可以用echarts把节点的执行路径画出来对调试复杂流程会有很大帮助。我个人实操下来最深刻的体会是这套框架的价值不在于“用了多潮的技术”而在于它把 AI 的灵活性和业务的确定性做了一个很好的平衡。知识库负责“懂”流程图负责“稳”FastAPI 负责“通”——三者接在一起才是一个真正能上线的智能客服系统。希望对正在搭建 Agent 应用的朋友能有点启发也欢迎一起交流你的踩坑经验。本文还有配套的精品资源点击获取