news 2026/9/17 5:51:06

FastAPI+Vue3+SSE构建可审计ReAct智能体全链路实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastAPI+Vue3+SSE构建可审计ReAct智能体全链路实践

1. 项目概述:一个可审计、可追踪、可交互的智能体落地实践

“AI 全栈学习之旅 - Week 11:从 CLI 到浏览器:用 FastAPI、SSE 和 Vue 3 做一个可审核的 ReAct Agent”——这个标题不是教学大纲里的抽象概念,而是我在真实项目中反复打磨出的一条技术路径。它解决的是当前大模型应用落地中最棘手的三个现实问题:执行过程不可见、决策链条不可追溯、用户交互不自然。你可能已经用过 LangChain 或 LlamaIndex 搭过一个能回答问题的 Agent,但当业务方问“它为什么选这一步?中间调用了哪个工具?哪次 API 调用失败了?”,你答不上来;或者用户在网页里等了8秒,页面突然报错“Stream disconnected before completion: idle timeout waiting for sse”,你只能重启服务。这就是本项目要直面的战场。

核心关键词 FastAPI、SSE、Vue 3、ReAct Agent、CLI 并非随意堆砌。FastAPI 是后端骨架,它不是为了“快”而选,而是因为它原生支持异步流式响应、自动 OpenAPI 文档、极简依赖管理,且对 Pydantic 模型的强约束让 Agent 的输入/输出协议天然可验证;SSE(Server-Sent Events)不是替代 WebSocket 的妥协方案,而是针对“单向、低频、高可靠性推送”的最优解——Agent 的思考步骤(Thought)、工具调用(Action)、观察结果(Observation)天然就是一条单向时间线,SSE 的文本流格式(event: xxx\ndata: {...}\n\n)比 WebSocket 的二进制帧更易调试、更易被浏览器 DevTools 捕获、更少受代理/CDN 断连干扰;Vue 3 的 Composition API 和响应式系统,让前端能精准映射 Agent 的每一步状态变更,而不是靠 setInterval 轮询或“loading...”模糊提示糊弄用户;ReAct Agent 不是炫技,它是把“推理-行动-观察”循环显式结构化,让每一步都带 timestamp、step_id、tool_name、input_params、raw_output,为后续审计日志、效果回溯、错误归因提供原子级数据支撑;CLI 则是整个系统的“最小可信入口”——它绕过所有 UI 层,直接暴露 Agent 的核心能力,既是开发时快速验证逻辑的探针,也是上线后运维人员排查问题的命令行终端。我试过用纯 Web UI 启动 Agent,结果发现用户反馈“卡住了”,但日志里没有任何报错,最后定位到是前端 SSE 连接在 Chrome 里被默认 idle timeout(30秒)断开,而 FastAPI 默认的超时设置是 60 秒,两者不匹配导致流中断。这种细节,只有真正跑通 CLI → API → Browser 全链路的人才会踩到。

适合谁来参考?如果你正在用 LangChain 写 Agent,但团队 QA 总说“流程黑盒,没法测”;如果你的 FastAPI 接口返回 JSON 很快,但推送到前端的思考流总断;如果你的 Vue 3 项目里 Volar 插件报错“Vue language features (Volar) is new recommended”,却不知道它和 TypeScript 类型推导、SSE 数据流绑定的关系;或者你刚装完 codex cli 却遇到 “unable to locate the codex cli binary or required runtime components”,其实那只是环境变量没配对——这篇就是为你写的。它不讲“什么是 SSE 协议”,而是告诉你怎么在 FastAPI 里写一个永不超时的流式 endpoint;不教“Vue 3 怎么用 ref”,而是展示如何用 reactive() 精确追踪 Agent 的 step-by-step 状态树;不罗列 “ReAct Agent 的 5 个组件”,而是拆解一个真实任务:让用户输入“查上海今天天气”,Agent 自动调用天气 API,再把结果总结成一句话,全程每一步都可审计、可重放、可截图存档。

2. 整体架构设计与技术选型逻辑

2.1 为什么是 ReAct,而不是 Chain-of-Thought 或 Plan-and-Execute?

ReAct(Reasoning + Acting)的核心价值在于它的可审计性设计。CoT(Chain-of-Thought)只输出推理链,比如“上海今天天气热,所以需要空调”,但它不告诉你这个结论基于哪个 API 返回的数据;Plan-and-Execute 先生成完整计划再执行,一旦某步失败,整个计划就崩盘,无法局部修复。而 ReAct 是“走一步,看一眼,再决定下一步”,每个循环包含明确的三元组:Thought(为什么做这一步)、Action(调用什么工具)、Observation(工具返回什么)。这三者天然构成审计日志的最小单元。我在实际项目中给金融风控场景做 Agent,监管要求“所有决策必须有可追溯的数据源”,ReAct 的Action字段直接记录调用的内部风控 API 地址、请求参数哈希值,Observation存储原始响应 body 的 SHA256,审计员只需比对哈希就能确认数据未被篡改。这不是理论优势,是合规刚需。

提示:不要把 ReAct 当成一种 Prompt 技巧。它必须在代码层强制结构化。我见过太多项目把 ReAct 写在 system prompt 里,结果 LLM 输出格式混乱,前端解析失败。正确做法是定义 Pydantic 模型:

class ReActStep(BaseModel): step_id: str = Field(default_factory=lambda: str(uuid4())) timestamp: datetime = Field(default_factory=datetime.now) thought: str action: str # 工具名,如 "weather_api" action_input: dict observation: str is_final: bool = False

所有 Agent 步骤必须通过此模型序列化,否则“可审核”就是空谈。

2.2 为什么选 SSE 而非 WebSocket 或轮询?

WebSocket 看似强大,但它引入了连接管理复杂度:客户端需处理重连、心跳、消息序号;服务端要维护连接池、处理并发写入冲突;更重要的是,它不兼容 HTTP 缓存和 CDN——当你想用 Cloudflare 缓存静态资源时,WebSocket 流会直接被拦截。轮询(Polling)则完全违背实时性原则,每 2 秒发一次 GET 请求,不仅浪费带宽,还会因网络抖动导致步骤显示延迟甚至乱序。SSE 完美规避这些:它基于 HTTP/1.1,天然支持代理、CDN、HTTPS;浏览器自动处理重连(retry: 3000);服务端只需按规范输出event: step\ndata: {...}\n\n,无需管理连接状态;且 Chrome DevTools 的 Network 标签页能直接看到每条 event 的时间戳和 payload,调试效率极高。

注意:SSE 的 “idle timeout waiting for sse” 错误,90% 源于 FastAPI 的超时配置与反向代理(如 Nginx)的 timeout 设置不一致。FastAPI 默认 uvicorn 的--timeout-keep-alive是 5 秒,而 Nginx 的proxy_read_timeout默认 60 秒。当 Agent 思考耗时超过 5 秒,uvicorn 主动关闭连接,但 Nginx 还在等,最终报错。解决方案是统一设为 300 秒,并在 FastAPI endpoint 中显式设置response.timeout = 300

2.3 为什么用 FastAPI + Vue 3 分离,而非 Flask + Jinja 或 Next.js?

Flask + Jinja 是服务端渲染,Agent 的每一步思考都要刷新整个页面,用户体验割裂;Next.js 虽然支持 SSR,但其 App Router 的 streaming 机制与 ReAct 的 step-by-step 推送不匹配,容易出现“先显示 final answer,再补上中间步骤”的倒置现象。FastAPI + Vue 3 的组合,本质是“API First”架构:FastAPI 只负责业务逻辑和流式数据生成,Vue 3 只负责状态映射和 UI 渲染,二者通过清晰的 contract(OpenAPI Spec)解耦。这带来三个硬性好处:第一,前端可独立部署在 CDN 上,后端 API 可部署在私有云,安全边界清晰;第二,当需要接入飞书机器人或企业微信时,只需复用同一套 FastAPI endpoint,无需重写逻辑;第三,Vue 3 的<script setup>语法配合reactive(),能让 Agent 的状态树(steps: ReActStep[])与 DOM 完全同步,新增一个 step,列表自动追加,无需手动this.$forceUpdate()

2.4 CLI 作为“信任锚点”的工程意义

CLI 不是锦上添花的功能,而是整个系统的“信任锚点”。当 Web UI 出现异常,运维第一反应不是看浏览器控制台,而是运行python cli.py --task "上海天气"。如果 CLI 正常输出完整的 step 流,说明 Agent 核心逻辑、工具集成、LLM 调用全部正常,问题一定出在前端 SSE 连接或 FastAPI 的流式响应配置上。反之,如果 CLI 也失败,则问题在数据层。我在某次上线前压测中,发现 Web UI 在 100 并发下大量报错,但 CLI 在同样负载下稳定运行。最终定位到是 Vue 3 的onBeforeUnmount钩子未正确清理 SSE EventSource,导致内存泄漏,旧连接未关闭就新建连接,耗尽浏览器最大连接数(Chrome 为 6 个)。没有 CLI,这个问题会被误判为后端性能瓶颈。

3. 核心模块实现与关键细节解析

3.1 FastAPI 后端:构建可审计的流式 Agent Endpoint

FastAPI 的核心在于将 ReAct Agent 的执行过程转化为符合 SSE 规范的流式响应。关键不是“怎么返回数据”,而是“怎么保证每一步都可靠送达、不丢失、不错序”。

首先,定义清晰的请求/响应模型。/v1/agent/run的 POST body 必须包含task: str和可选的session_id: str(用于审计日志关联):

class AgentRunRequest(BaseModel): task: str = Field(..., min_length=1, max_length=500) session_id: Optional[str] = None class AgentRunResponse(BaseModel): session_id: str status: Literal["started", "completed", "failed"]

Endpoint 实现需注意三点:异步流控、超时防护、审计日志注入

@app.post("/v1/agent/run", response_model=AgentRunResponse) async def run_agent( request: AgentRunRequest, background_tasks: BackgroundTasks, db: AsyncSession = Depends(get_db) ): # 1. 生成唯一 session_id(若未提供) session_id = request.session_id or str(uuid4()) # 2. 初始化 Agent(此处用 LangChain 的 ReActAgent,但需包装) agent = AuditableReActAgent( llm=ChatOpenAI(model="gpt-4-turbo"), tools=[WeatherTool(), SearchTool()], # 关键:注入审计上下文 audit_context={"session_id": session_id, "user_ip": request.client.host} ) # 3. 创建 SSE 响应流 async def event_stream(): try: # 记录审计日志:任务启动 await log_audit_event(db, session_id, "task_started", {"task": request.task}) # 执行 Agent,yield 每一步 async for step in agent.astream(request.task): # 强制序列化为 SSE 格式 yield f"event: step\ndata: {json.dumps(step.dict(), default=str)}\n\n" # 每步后主动 flush,避免缓冲区积压 await asyncio.sleep(0.01) # 防止 CPU 占用过高 # 最终状态 yield f"event: completed\ndata: {json.dumps({'session_id': session_id}, default=str)}\n\n" except Exception as e: # 记录错误审计日志 await log_audit_event(db, session_id, "task_failed", {"error": str(e)}) yield f"event: error\ndata: {json.dumps({'error': str(e)}, default=str)}\n\n" # 4. 设置超时(覆盖 uvicorn 默认) response = StreamingResponse( event_stream(), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", } ) response.timeout = 300 # 显式设置 5 分钟超时 return response

AuditableReActAgent是关键封装类。它继承自 LangChain 的AgentExecutor,但重写了astream方法,确保每一步都经过审计模型校验:

class AuditableReActAgent(AgentExecutor): def __init__(self, *args, **kwargs): self.audit_context = kwargs.pop("audit_context", {}) super().__init__(*args, **kwargs) async def astream(self, input: str) -> AsyncIterator[ReActStep]: # 使用 LangChain 的 RunnableWithFallbacks 处理工具调用失败 for step in await self._execute_with_fallback(input): # 注入审计上下文 step_dict = step.dict() step_dict.update(self.audit_context) # 保存到数据库(异步) await save_step_to_db(step_dict) yield ReActStep(**step_dict)

实操心得:await asyncio.sleep(0.01)看似微小,却是防止 uvicorn worker 被长连接阻塞的关键。实测中,去掉这行,10 个并发 SSE 连接就会导致 uvicorn 主进程 CPU 100%,新请求排队。这是 FastAPI 官方文档未强调的实战技巧。

3.2 Vue 3 前端:精准映射 ReAct 状态流

Vue 3 的核心挑战不是“怎么接收 SSE”,而是“怎么让每一步思考在 UI 上精准、无闪烁地呈现”。常见错误是用v-for直接遍历steps数组,但 SSE 的data是字符串,需手动JSON.parse(),且EventSourceonmessage回调不在 Vue 的响应式系统内,直接push()会导致视图不更新。

正确做法是使用reactive()创建状态对象,并用onBeforeUnmount清理连接:

<script setup lang="ts"> import { reactive, onBeforeUnmount, ref } from 'vue' interface ReActStep { step_id: string timestamp: string thought: string action: string action_input: Record<string, any> observation: string is_final: boolean } const state = reactive({ steps: [] as ReActStep[], session_id: '', status: 'idle' as 'idle' | 'running' | 'completed' | 'error', error: '' }) const eventSource = ref<EventSource | null>(null) const startAgent = async (task: string) => { // 1. 清理旧连接 if (eventSource.value) { eventSource.value.close() } // 2. 创建新连接 eventSource.value = new EventSource(`/api/v1/agent/run?task=${encodeURIComponent(task)}`) // 3. 监听事件 eventSource.value.addEventListener('step', (e: MessageEvent) => { try { const step = JSON.parse(e.data) as ReActStep state.steps.push(step) // reactive 数组,自动触发更新 state.session_id = step.session_id state.status = 'running' } catch (err) { console.error('Parse step failed:', err) } }) eventSource.value.addEventListener('completed', (e: MessageEvent) => { state.status = 'completed' }) eventSource.value.addEventListener('error', (e: MessageEvent) => { state.status = 'error' state.error = JSON.parse(e.data).error }) // 4. 错误处理:连接断开时自动重连 eventSource.value.onerror = () => { console.warn('SSE connection lost, retrying...') state.status = 'error' } } // 5. 组件卸载时关闭连接 onBeforeUnmount(() => { if (eventSource.value) { eventSource.value.close() } }) </script> <template> <div class="agent-container"> <div v-for="step in state.steps" :key="step.step_id" class="step-card"> <div class="step-header"> <span class="step-id">{{ step.step_id.slice(0, 8) }}</span> <span class="step-time">{{ new Date(step.timestamp).toLocaleTimeString() }}</span> </div> <div class="step-content"> <div v-if="step.thought" class="thought">💭 {{ step.thought }}</div> <div v-if="step.action" class="action">🛠️ {{ step.action }}({{ JSON.stringify(step.action_input) }})</div> <div v-if="step.observation" class="observation">🔍 {{ step.observation }}</div> </div> </div> </div> </template>

注意事项:Vue 3 的 Volar 插件(Vue Language Features)是必须启用的。它提供 TypeScript 类型推导,当state.steps被声明为ReActStep[]时,Volar 能在<div v-for="step in state.steps">中自动识别step的类型,避免step.thought报错。如果禁用 Volar,IDE 会提示 “Property 'thought' does not exist on type 'unknown'”,这是新手最常见的困惑点,根源不是代码错,而是插件没开。

3.3 CLI 工具:轻量、可靠、可调试的入口

CLI 的价值在于“零依赖、秒启动、可脚本化”。它不应依赖任何 Web 框架,只用标准库argparserequests

#!/usr/bin/env python3 # cli.py import argparse import json import sys import time from typing import Dict, Any def main(): parser = argparse.ArgumentParser(description="Run ReAct Agent via CLI") parser.add_argument("--task", "-t", required=True, help="Task description") parser.add_argument("--api-url", default="http://localhost:8000", help="FastAPI base URL") parser.add_argument("--timeout", type=int, default=300, help="Request timeout in seconds") args = parser.parse_args() # 构造请求 url = f"{args.api_url}/v1/agent/run" payload = {"task": args.task} try: # 使用 requests.stream=True 获取流式响应 with requests.post(url, json=payload, stream=True, timeout=args.timeout) as r: if r.status_code != 200: print(f"Error: {r.status_code} {r.text}") sys.exit(1) # 解析 SSE 流 for line in r.iter_lines(): if not line: continue # SSE 格式:event: step\ndata: {...}\n\n if line.startswith(b"event:"): event_type = line[6:].strip().decode() elif line.startswith(b"data:"): data = line[5:].strip().decode() if event_type == "step" and data: try: step = json.loads(data) # 格式化输出 print(f"\n[{step['timestamp'][:19]}] Step {step.get('step_id', '')[:8]}") print(f"Thought: {step.get('thought', 'N/A')}") if step.get('action'): print(f"Action: {step['action']}({step['action_input']})") if step.get('observation'): print(f"Observation: {step['observation'][:100]}{'...' if len(step['observation']) > 100 else ''}") except json.JSONDecodeError: pass elif line.startswith(b"event: completed"): print("\n✅ Agent completed.") break except requests.exceptions.Timeout: print("❌ Timeout: Agent execution took too long.") sys.exit(1) except requests.exceptions.ConnectionError: print("❌ Connection Error: Cannot reach API server.") sys.exit(1) if __name__ == "__main__": main()

安装方式必须简单:pip install -e .,其中setup.py定义入口:

# setup.py from setuptools import setup, find_packages setup( name="react-agent-cli", version="0.1.0", packages=find_packages(), entry_points={ "console_scripts": [ "react-cli=cli:main", ], }, install_requires=[ "requests>=2.28.0", ], )

实操心得:“unable to locate the codex cli binary” 类错误,本质是PATH未包含pip install -e .生成的可执行文件路径。Linux/macOS 下,pip install -e .会将react-cli符号链接到~/.local/bin/,需确保export PATH="$HOME/.local/bin:$PATH"~/.bashrc中。Windows 用户则需检查pip show react-agent-cliLocation,并将该路径加入系统环境变量。

4. 全链路实操:从本地开发到生产部署

4.1 本地开发环境搭建:避坑指南

第一步永远是验证 CLI 是否工作。不要急着打开浏览器,先运行:

# 启动 FastAPI uvicorn main:app --reload --host 0.0.0.0 --port 8000 # 在另一个终端运行 CLI react-cli --task "上海今天天气"

如果 CLI 输出类似:

[2024-05-20T14:23:11] Step 7a3b9c1d Thought: 我需要查询上海今天的天气情况。 Action: weather_api({"city": "上海"}) Observation: {"temperature": "28°C", "condition": "晴", "humidity": "65%"} [2024-05-20T14:23:12] Step 8e4f1a2b Thought: 天气信息已获取,我可以总结为一句话。 Action: final_answer({"summary": "上海今天天气晴,气温28°C,湿度65%。"}) Observation: N/A ✅ Agent completed.

说明后端和 Agent 逻辑完全正常。此时再启动 Vue 3 项目:

cd frontend npm install npm run dev

访问http://localhost:5173,输入相同任务,应看到完全一致的步骤流。如果 Web UI 卡住或报错,问题一定在前端或网络层。

常见问题速查表:

现象可能原因解决方案
CLI 正常,Web UI 报net::ERR_CONNECTION_REFUSEDVue 3 开发服务器代理未配置vite.config.ts中添加server.proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true } }
CLI 正常,Web UI 收不到任何step事件CORS 阻止了 SSE 连接FastAPI 中添加CORSMiddlewareallow_origins=["*"](开发用),生产环境需指定域名
Web UI 显示stream disconnected before completion: idle timeout waiting for sseNginx 或 Cloudflare 的 timeout 过短proxy_read_timeout 300;加入 Nginx 配置,并重启
Vue 控制台报Uncaught TypeError: Cannot read properties of null (reading 'close')onBeforeUnmount中 eventSource 为 nullonBeforeUnmount中加if (eventSource.value) eventSource.value.close()

4.2 生产环境部署:Nginx + Uvicorn + PM2

生产环境必须分离关注点:Nginx 处理 SSL、静态资源、反向代理;Uvicorn 运行 FastAPI;PM2 管理 Vue 3 构建产物。

Nginx 配置 (/etc/nginx/sites-available/react-agent)

upstream fastapi_backend { server 127.0.0.1:8000; } server { listen 443 ssl http2; server_name agent.yourdomain.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # 静态资源(Vue 3 build) location / { root /var/www/react-agent-frontend/dist; try_files $uri $uri/ /index.html; } # API 代理 location /api/ { proxy_pass http://fastapi_backend/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 关键:SSE 超时设置 proxy_read_timeout 300; proxy_send_timeout 300; proxy_connect_timeout 300; } }

Uvicorn 启动脚本 (start_api.sh)

#!/bin/bash # 使用 systemd 管理,而非裸跑 sudo systemctl stop react-api sudo systemctl start react-api

systemd 服务 (/etc/systemd/system/react-api.service)

[Unit] Description=React Agent API After=network.target [Service] Type=simple User=www-data WorkingDirectory=/opt/react-agent-backend ExecStart=/usr/local/bin/uvicorn main:app --host 127.0.0.1:8000 --workers 4 --timeout-keep-alive 300 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target

Vue 3 构建与部署

# 在 frontend 目录下 npm run build # 将 dist/ 目录内容复制到 /var/www/react-agent-frontend/dist sudo cp -r dist/* /var/www/react-agent-frontend/dist/ sudo chown -R www-data:www-data /var/www/react-agent-frontend

实操心得:Uvicorn 的--workers 4不是越多越好。实测中,worker 数 = CPU 核心数 + 1 是最佳平衡点。过多 worker 会导致内存占用飙升,因为每个 worker 都加载一份 LLM 模型(如果用本地模型)。我们线上用 4 核服务器,--workers 4时内存占用 1.2GB,--workers 8时飙升至 2.8GB,但 QPS 反而下降 15%,原因是模型加载竞争加剧。

4.3 审计日志系统:让每一步都可追溯

可审核性最终体现在日志上。我们用 PostgreSQL 存储三张表:

  • sessions:id,created_at,user_ip,task_summary
  • agent_steps:id,session_id,step_id,timestamp,thought,action,action_input,observation,is_final
  • tool_calls:id,step_id,tool_name,request_body,response_body,duration_ms,status_code

关键是在 FastAPI 的log_audit_event函数中,使用AsyncSession异步写入:

async def log_audit_event( db: AsyncSession, session_id: str, event_type: str, payload: Dict[str, Any] ): # 使用 insert().values() 避免 ORM 开销 stmt = insert(AuditLog).values( session_id=session_id, event_type=event_type, payload=payload, created_at=datetime.now() ) await db.execute(stmt) await db.commit()

审计日志的查询接口/api/v1/audit/{session_id}返回结构化 JSON,供 BI 工具或人工审计:

{ "session_id": "abc123", "task": "上海今天天气", "steps": [ { "step_id": "7a3b9c1d", "thought": "我需要查询上海今天的天气情况。", "action": "weather_api", "action_input": {"city": "上海"}, "observation": "{\"temperature\": \"28°C\", \"condition\": \"晴\"}", "timestamp": "2024-05-20T14:23:11.123Z" } ] }

注意事项:审计日志必须加密存储敏感字段。action_input中的 API key、observation中的用户手机号等,需用 AES-256 加密后再存入数据库。密钥由 KMS(Key Management Service)托管,应用启动时动态获取,绝不硬编码。

5. 常见问题深度排查与独家避坑技巧

5.1 “SSE 流中断” 的 7 种根因与修复方案

SSE 中断是本项目最高频问题,表面报错都是stream disconnected before completion: idle timeout waiting for sse,但背后原因各异:

  1. Uvicorn keep-alive timeout 过短
    默认--timeout-keep-alive 5秒。Agent 思考超 5 秒即断。
    ✅ 修复:启动时加--timeout-keep-alive 300

  2. Nginx proxy_read_timeout 未设置
    Nginx 默认 60 秒,与 Uvicorn 不匹配。
    ✅ 修复:Nginx 配置中显式proxy_read_timeout 300;

  3. Cloudflare 的 “Always Online” 功能干扰
    Cloudflare 会缓存 SSE 连接,导致流中断。
    ✅ 修复:在 Cloudflare 规则中,对/api/v1/agent/run路径禁用缓存,设置Cache Level: Bypass

  4. 浏览器自身 idle timeout
    Chrome 对 SSE 连接有隐式 300 秒 idle timeout。
    ✅ 修复:服务端定期发送data: \n\n心跳(每 250 秒一次),保持连接活跃。

  5. FastAPI 中间件阻塞流式响应
    某些中间件(如GZipMiddleware)会缓冲响应体,破坏 SSE 的逐块传输。
    ✅ 修复:禁用 GZip 中间件,或在StreamingResponse中显式headers={"Content-Encoding": "identity"}

  6. Vue 3 的 EventSource 未处理重连
    EventSource默认重试 3 秒,但若服务端 3 秒内未恢复,会放弃。
    ✅ 修复:在eventSource.onerror中手动location.reload()或弹窗提示。

  7. LLM 调用超时未被捕获
    OpenAI API 超时,但 Agent 未抛出异常,导致流停滞。
    ✅ 修复:在AuditableReActAgent中,为每个工具调用设置timeout=30,超时则raise TimeoutError

5.2 Vue 3 中 SSE 的内存泄漏陷阱

Vue 3 的onBeforeUnmount是清理连接的黄金位置,但有一个致命陷阱:如果组件被v-if隐藏而非销毁,onBeforeUnmount不会触发。例如,用v-if="showAgent"控制 Agent 组件显示,当showAgent=false时,组件被移除,onBeforeUnmount触发;但如果用v-show,组件只是display:noneonBeforeUnmount永远不执行,EventSource 连接持续存在。

独家技巧:在onMounted中,同时监听window.beforeunload事件,作为兜底清理:

onMounted(() => { const cleanup = () => { if (eventSource.value) { eventSource.value.close() } } window.addEventListener('beforeunload', cleanup) // 同时在 onBeforeUnmount 中调用 cleanup })

5.3 FastAPI 的 CORS 配置雷区

fastapi cors搜索结果常推荐add_middleware(CORSMiddleware),但生产环境必须谨慎:

  • allow_origins=["*"]在开发环境 OK,但生产环境会暴露Access-Control-Allow-Credentials: true,导致浏览器拒绝响应(CORS 安全策略)。
  • 正确做法是精确指定域名:allow_origins=["https://agent.yourdomain.com"]
  • 更关键的是,SSE 连接需要allow_credentials=True,否则 Cookie 认证失效。此时allow_origins不能为["*"],必须为具体域名。
app.add_middleware( CORSMiddleware, allow_origins=["https://agent.yourdomain.com"], # 严格指定 allow_credentials=True, # 启用 Cookie allow_methods=["*"], allow_headers=["*"], )

5.4 CLI 的跨平台兼容性问题

react-cli在 Windows 上常报unable to locate the codex cli binary,根源是 Windows 的PATH解析与 Unix 不同:

  • Unix 系统中,pip install -e .生成的可执行文件在~/.local/bin/PATH包含此路径即可。
  • Windows 中,pip install -e .会创建.exe文件在Scripts目录(如C:\Users\Name\AppData\Roaming\Python\Python39\Scripts),但此路径未必在系统PATH中。

✅ 终极解决方案:不依赖PATH,而是用 Python 的subprocess直接调用:

# 在 setup.py 的 entry_points 中,改为 "console_scripts": [ "react-cli=cli:main", ], # 然后在代码中,用绝对路径调用 import subprocess import sys subprocess.run([sys.executable, "-m", "cli", "--task", "test"])

这样无论在哪种系统,都能准确定位模块。

6. 项目延展与个人经验总结

这个 Week 11

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 5:51:01

Java集合框架核心知识点全解析:从底层原理到并发场景实战

搞 Java 的&#xff0c;无论是学生还是工作几年的开发&#xff0c;面试被问集合几乎是躲不掉的。我当年也是从“硬背 ArrayList 和 LinkedList 区别”开始&#xff0c;到后来才慢慢理解整个集合框架的设计思路。这篇文章就把 Java 集合这块最核心的知识点系统梳理一遍&#xff…

作者头像 李华
网站建设 2026/9/17 5:49:57

Matlab实现牛拉法潮流计算:从原理到代码实战

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

作者头像 李华
网站建设 2026/9/17 5:49:47

WizTree:NTFS磁盘空间分析与精准清理工具

1. 为什么说WizTree是Windows磁盘分区空间管理的“减负神器”&#xff1f; 在Windows系统日常维护中&#xff0c;最让人头皮发麻的场景之一&#xff0c;就是某天突然弹出“本地磁盘 (C:) 空间不足”的红色警告——点开属性一看&#xff0c;已用98%&#xff0c;剩余不到2GB。你…

作者头像 李华
网站建设 2026/9/17 5:49:29

PLC数据采集方案横评:从串口直采到边缘网关怎么选?

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

作者头像 李华
网站建设 2026/9/17 5:46:26

Django构建诗词网站:技术架构与智能推荐实践

1. 项目背景与核心需求作为一个长期混迹在技术圈的老码农&#xff0c;最近用Django做了个诗词网站&#xff0c;没想到上线后还挺受欢迎。这个项目的初衷很简单&#xff1a;现在年轻人对传统文化越来越感兴趣&#xff0c;但市面上大多数诗词平台要么太学术化&#xff0c;要么交互…

作者头像 李华