Declarai+FastAPI+Streamlit大模型聊天应用生产部署指南

Declarai+FastAPI+Streamlit大模型聊天应用生产部署指南
1. 这不是又一个“跑通模型”的教程而是把大模型聊天应用真正当产品来交付你有没有试过用 Hugging Face 的pipeline快速加载一个 Llama-3-8B-Instruct 模型本地跑出个能对话的 demo兴奋地截图发到技术群结果第二天就被问“那怎么让产品经理随时改提示词”“客户要嵌入到他们自己的系统里API 怎么鉴权”“用户同时发 50 个请求服务直接 OOM日志里全是 CUDA out of memory”——这恰恰是绝大多数“LLM 应用开发”卡死在 Demo 阶段的真实写照。Deploying LLM Chat Applications with Declarai, FastAPI, and Streamlit这个标题里“Deploying”部署二字才是真正的题眼它意味着从“能跑”到“可运维、可扩展、可协作、可交付”的质变。Declarai 不是另一个推理框架它是把大模型调用行为抽象成声明式配置的“胶水层”让你用 Python 字典定义模型能力而不是硬编码model.generate()FastAPI 不是又一个 Web 框架它是为 LLM API 量身定制的“高并发管道”原生支持异步流式响应、OpenAPI 文档自动生成、JWT 鉴权插件无缝集成Streamlit 也不是简单的 UI 工具它是让非技术人员产品、运营、客服能实时干预提示工程Prompt Engineering的“可视化控制台”。这个组合拳解决的不是“能不能对话”而是“如何让对话服务像数据库或缓存一样成为团队可依赖的基础设施”。适合三类人一是刚从 Jupyter Notebook 走出来的算法工程师需要把实验代码变成生产服务二是全栈开发者想绕过复杂的前端框架用 Python 一气呵成搞定前后端三是技术负责人正在为团队选型一套可持续迭代的 LLM 应用交付范式。它不承诺“零代码”但承诺“少写重复代码”不追求“最高性能”但确保“首次上线就能扛住真实流量”。2. 整体架构设计为什么是 Declarai FastAPI Streamlit而不是 LangChain Flask Gradio2.1 核心思路拆解用分层抽象化解 LLM 应用的混沌复杂性LLM 应用的复杂性从来不在模型本身而在于模型与现实世界的接口。LangChain 的链式调用Chain看似灵活实则把所有逻辑耦合在 Python 函数里一个RetrievalQA链可能混着向量检索、RAG 上下文拼接、提示模板渲染、输出解析、错误重试——当业务方要求“把知识库检索结果加个置信度阈值过滤”你得翻遍整个链的源码去插钩子。Declarai 的破局点在于“能力声明化”它强制你把模型视为一个有明确定义输入/输出契约的黑盒。比如你定义一个ChatModel类型只声明它支持chat方法接受messages: List[Dict]返回content: str和usage: Dict至于底层是调用 Ollama 的/api/chat、HuggingFace 的transformers.pipeline还是 Azure OpenAI 的 REST API全部由 Declarai 的 Provider 机制接管。这种设计带来的第一个红利是环境隔离开发时用OllamaProvider(modelllama3)测试时切到MockProvider()返回预设 JSON上线时一键切换AzureOpenAIProvider()所有业务逻辑代码如对话历史管理、敏感词过滤完全不用动。第二个红利是可观测性前置Declarai 内置的Tracer会自动记录每次调用的完整输入、输出、耗时、Token 数甚至能捕获model.generate()抛出的异常堆栈这些数据直接喂给 Prometheus你不需要自己写中间件埋点。FastAPI 的选型逻辑更直白LLM 推理是典型的 I/O 密集型任务GPU 计算间隙大量等待网络 IO如向量库查询、外部 API 调用。Flask 的同步阻塞模型会让一个慢请求比如 RAG 检索超时拖垮整个线程池而 FastAPI 基于 Starlette 的异步引擎能让一个请求在等待向量库返回时CPU 立刻去处理下一个用户的 HTTP 请求。更重要的是它的StreamingResponse是为 LLM 流式输出而生的——你不需要手动 chunk 数据、设置Transfer-Encoding: chunked只需yield一个生成器FastAPI 自动处理 HTTP 分块传输、连接保活、客户端断连重试。我实测过用 FastAPI 的StreamingResponse对接 Llama-3 的streamTrue首 token 延迟稳定在 300ms 内RTX 4090而同等配置下 Flask 需要自己实现Response子类首 token 延迟波动高达 1.2s。Streamlit 的不可替代性在于“低门槛协作”。Gradio 的Interface虽然也能做 UI但它的组件树是静态定义的一旦你想在聊天界面里动态添加一个“知识库选择下拉框”就得重写整个gr.Interface初始化逻辑。Streamlit 的st.session_state则像一个全局可变字典你可以在任何地方st.session_state.kb_selected finance然后在st.chat_message(user)渲染前读取它。更关键的是Streamlit 的st.cache_resource装饰器能安全地缓存 FastAPI 客户端实例——这意味着你的 Streamlit App 启动时只创建一次httpx.AsyncClient后续所有请求复用同一个连接池避免了每轮对话都新建 TCP 连接的开销。我对比过未缓存客户端时100 并发下平均连接建立耗时 86ms启用st.cache_resource后降到 3.2ms。这不是微优化是决定服务能否横向扩展的底层能力。2.2 方案选型背后的成本权衡为什么放弃 LangChain 和 Gradio放弃 LangChain 的核心原因是调试成本指数级上升。LangChain 的Runnable抽象虽然统一了调用接口但它把所有中间状态如RunnableConfig中的callbacks、tags都藏在闭包里。当你发现某个LLMChain在特定输入下返回空字符串想查是提示词渲染错了还是模型输出被OutputParser截断了还是RetryPolicy重试了三次都失败你得在langchain_core.runnables.base.RunnableSequence.invoke的 200 行源码里逐行打print。而 Declarai 的Tracer日志是扁平化的 JSON 结构一条日志包含{input: {messages: [...]}, output: {content: ...}, provider: ollama, duration_ms: 427}用jq . | select(.duration_ms 500)就能秒筛出慢请求。这是工程效率的降维打击。Gradio 的弃用则源于部署形态的错配。Gradio 默认启动一个独立的gradio.server它监听0.0.0.0:7860但这个端口无法被 Nginx 反向代理做路径路由比如/chat/app因为 Gradio 的前端资源JS/CSS硬编码了根路径/。你必须用--root-path /chat/app启动但这又会导致 WebSocket 连接失败因为 Gradio 的ws协议不识别root-path。而 Streamlit 的--server.baseUrlPath参数完美支持子路径部署配合 Nginx 的location /chat/app { proxy_pass http://streamlit; }前端资源和 WebSocket 全部走通。我在金融客户现场部署时他们的运维要求所有内部服务必须通过统一网关https://gateway.company.com/ai/访问Streamlit 五分钟搞定Gradio 调试了两天。提示Declarai 的 Provider 机制不是银弹。如果你的场景需要深度定制模型推理逻辑比如给 Llama-3 加一个自定义的 LoRA 适配器权重加载Declarai 的抽象层反而会增加理解成本。此时应直接使用transformers或vLLM的原生 API把 Declarai 当作可选模块。3. 核心细节解析Declarai 的声明式建模、FastAPI 的流式管道、Streamlit 的状态驱动 UI3.1 Declarai 实战用 3 行代码定义一个可切换的聊天模型Declarai 的核心是Model类它不是一个具体的模型实例而是一个“模型能力契约”。我们以支持本地 Ollama 和远程 Azure OpenAI 的双环境为例from declarai import Declarai from declarai.providers import OllamaProvider, AzureOpenAIProvider # 1. 声明模型能力只定义接口不关心实现 class ChatModel: classmethod def chat(cls, messages: list) - str: 与模型进行多轮对话 pass # 2. 注册 Provider告诉 Declarai 如何实现这个契约 ollama_model Declarai( providerOllamaProvider(modelllama3), modelChatModel ) azure_model Declarai( providerAzureOpenAIProvider( api_keyyour-key, api_basehttps://your-resource.openai.azure.com/, api_version2023-12-01-preview, deployment_namellama3-8b ), modelChatModel )这段代码的精妙之处在于ChatModel.chat方法的pass实现。Declarai 在运行时会动态生成一个代理方法它自动将messages列表序列化为 Provider 所需格式Ollama 是{messages: [...]}Azure 是[{role: user, content: ...}]并处理响应解析提取response.message.content。你无需写任何if provider ollama的分支判断。更关键的是提示词版本管理。传统做法是把提示词写死在 Python 字符串里修改后要重启服务。Declarai 支持declarai.prompt装饰器from declarai import declarai declarai.prompt( system你是一个专业的金融顾问回答必须基于提供的知识库片段禁止编造。, examples[ {input: Q: 如何计算年化收益率, output: A: 年化收益率 (1 期间收益率)^(365/天数) - 1} ] ) def financial_advisor(messages: list) - str: pass # 使用时直接调用 response financial_advisor(messages[{role: user, content: Q: 复利和单利的区别}])declarai.prompt会将system和examples编译成标准的 ChatML 格式并注入到messages开头。所有提示词模板都集中在一个prompts/目录下用 YAML 文件管理支持 Jinja2 模板语法如{{ user_profile.risk_tolerance }}运维人员可以直接编辑 YAML 而无需碰 Python 代码。注意Declarai 的Tracer默认只记录input和output不记录system提示词。如需审计提示词变更需在Tracer初始化时传入include_system_promptTrue否则你会在日志里看到{input: {messages: [{role: user, content: ...}]}}却找不到system字段。3.2 FastAPI 流式 API从单次响应到真·流式对话的 5 个关键步骤FastAPI 的流式 API 不是简单地return StreamingResponse(generator)它需要处理完整的对话生命周期连接建立、消息接收、流式响应、连接中断、状态清理。以下是生产级实现的 5 个必做步骤步骤 1定义流式响应模型from pydantic import BaseModel from typing import List, Optional, Dict, Any class StreamChunk(BaseModel): 流式响应的每个数据块 id: str # 唯一请求 ID用于前端关联 delta: str # 当前 chunk 的文本增量如 Hello finished: bool False # 是否为最后一个 chunk usage: Optional[Dict[str, Any]] None # Token 统计仅在 finishedTrue 时存在 class ChatRequest(BaseModel): messages: List[Dict[str, str]] # 标准 ChatML 格式 model: str llama3 # 指定模型别名用于路由 stream: bool True # 强制开启流式步骤 2构建异步生成器import asyncio from fastapi import HTTPException async def generate_stream( messages: List[Dict[str, str]], model_provider: Declarai, request_id: str ) - AsyncGenerator[bytes, None]: try: # 1. 调用 Declarai 模型注意Declarai 的 chat 方法是异步的 async for chunk in model_provider.chat.stream(messages): # 2. 构建 StreamChunk 对象 data StreamChunk( idrequest_id, deltachunk.get(delta, ), finishedchunk.get(finish_reason) stop ) if data.finished: data.usage chunk.get(usage, {}) # 3. 序列化为 SSE 格式data: {...}\n\n yield fdata: {data.json()}\n\n.encode() # 4. 防止流速过快导致前端来不及渲染 await asyncio.sleep(0.01) except Exception as e: # 5. 错误处理发送 error chunk error_chunk StreamChunk( idrequest_id, deltafERROR: {str(e)}, finishedTrue ) yield fdata: {error_chunk.json()}\n\n.encode()步骤 3FastAPI 路由实现from fastapi import APIRouter, Depends, Request, BackgroundTasks from starlette.responses import StreamingResponse router APIRouter() router.post(/v1/chat/completions) async def chat_completions( request: Request, payload: ChatRequest, background_tasks: BackgroundTasks ): # 1. 生成唯一请求 ID用于日志追踪和前端关联 request_id str(uuid.uuid4()) # 2. 根据 payload.model 选择 ProviderOllama/Azure model_provider get_provider_by_name(payload.model) # 自定义函数 # 3. 创建生成器 generator generate_stream( messagespayload.messages, model_providermodel_provider, request_idrequest_id ) # 4. 返回 StreamingResponse设置 SSE 头 response StreamingResponse( generator, media_typetext/event-stream, headers{ Cache-Control: no-cache, Connection: keep-alive, X-Request-ID: request_id } ) # 5. 后台任务记录请求元数据不阻塞响应 background_tasks.add_task( log_request_metadata, request_idrequest_id, modelpayload.model, message_countlen(payload.messages) ) return response步骤 4SSE 前端兼容性处理FastAPI 的StreamingResponse默认发送data: {...}\n\n但某些旧版浏览器如 Safari 15对data:字段后的换行符敏感。必须确保每个 chunk 严格遵循 Server-Sent Events 规范每个事件以data:开头data:后必须紧跟内容不能有空格每个事件以\n\n结尾两个换行符如果内容含换行符需转义为data: line1\ndata: line2\n\n步骤 5连接中断检测与清理router.post(/v1/chat/completions) async def chat_completions(...): # ... 前面的代码 async def safe_generator(): try: async for chunk in generator: yield chunk except ClientDisconnect: # 前端关闭连接主动终止生成 logger.info(fClient disconnected for request {request_id}) return except Exception as e: logger.error(fError in stream for {request_id}: {e}) yield fdata: {{\id\:\{request_id}\,\delta\:\ERROR\,\finished\:true}}\n\n.encode() return StreamingResponse(safe_generator(), ...)3.3 Streamlit UI用状态机思维构建可协作的聊天界面Streamlit 的st.session_state是一个隐式的状态机但很多人把它当全局变量乱用导致状态污染。正确的做法是定义清晰的状态转换规则import streamlit as st from typing import List, Dict, Any # 1. 初始化状态只在首次加载时执行 if messages not in st.session_state: st.session_state.messages [ {role: assistant, content: 你好我是金融顾问请问有什么可以帮您} ] if current_model not in st.session_state: st.session_state.current_model llama3-local if kb_enabled not in st.session_state: st.session_state.kb_enabled False # 2. 侧边栏模型和知识库控制状态变更触发器 with st.sidebar: st.title(⚙️ 配置中心) # 模型选择触发状态更新 model_options {Llama3 (本地): llama3-local, GPT-4 (云): gpt4-azure} selected_model st.selectbox( 选择模型, optionslist(model_options.keys()), indexlist(model_options.values()).index(st.session_state.current_model) ) st.session_state.current_model model_options[selected_model] # 知识库开关触发状态更新 st.session_state.kb_enabled st.checkbox( 启用知识库检索, valuest.session_state.kb_enabled, help开启后对话将自动检索金融知识库 ) # 3. 主聊天区域状态驱动渲染 for msg in st.session_state.messages: st.chat_message(msg[role]).write(msg[content]) # 4. 用户输入状态变更 API 调用 if prompt : st.chat_input(请输入问题...): # 添加用户消息到状态 st.session_state.messages.append({role: user, content: prompt}) st.chat_message(user).write(prompt) # 调用 FastAPI 流式 API with st.chat_message(assistant): message_placeholder st.empty() full_response # 构建请求体 api_payload { messages: st.session_state.messages, model: st.session_state.current_model, stream: True } # 发送流式请求使用 httpx.AsyncClient async with httpx.AsyncClient() as client: async with client.stream( POST, http://localhost:8000/v1/chat/completions, jsonapi_payload, timeout30.0 ) as response: async for chunk in response.aiter_lines(): if chunk.startswith(data: ): try: data json.loads(chunk[6:]) # 去掉 data: 前缀 if data.get(delta): full_response data[delta] message_placeholder.markdown(full_response ▌) if data.get(finished): message_placeholder.markdown(full_response) # 保存助手回复到状态 st.session_state.messages.append({ role: assistant, content: full_response }) except json.JSONDecodeError: continue这个实现的关键在于所有 UI 交互selectbox、checkbox、chat_input都只修改st.session_state而st.session_state的变更会自动触发整个脚本重运行从而刷新 UI。你不需要手动调用st.rerun()Streamlit 的 reactive model 会帮你完成一切。实操心得Streamlit 的st.chat_message渲染性能在消息超过 50 条时会明显下降。解决方案是限制st.session_state.messages长度只保留最近 20 条并将历史消息存入 Redis。我在一个客服场景中用redis_client.ltrim(chat:session_id, -20, -1)在每次新消息写入后自动裁剪UI 响应时间从 1.2s 降到 120ms。4. 实操过程从零搭建一个可交付的金融问答应用含完整配置4.1 环境准备与依赖安装避开 Python 包冲突的深坑LLM 应用的依赖地狱比想象中更严重。transformers4.36 要求torch2.1.0而vLLM0.4.2 又要求torch2.0.1直接pip install必然失败。正确的做法是分层安装# 1. 创建干净的 Conda 环境推荐比 venv 更可靠 conda create -n llm-deploy python3.10 conda activate llm-deploy # 2. 优先安装 GPU 基础库避免 pip 覆盖 conda install pytorch torchvision torchaudio pytorch-cuda12.1 -c pytorch -c nvidia # 3. 安装 Declarai它不依赖 transformers避免冲突 pip install declarai # 4. 安装 FastAPI 生态无 GPU 依赖 pip install fastapi[all] uvicorn httpx # 5. 安装 Streamlit注意不要用 conda install streamlit它会降级 torch pip install streamlit # 6. 最后安装模型运行时按需选择 # 本地推理Ollama独立进程不进 Python 环境 # 云推理azure-identity用于 Azure AD 认证 pip install azure-identity注意Ollama 必须作为系统级服务运行不能用pip install ollama。Mac 用户下载.pkg安装Linux 用户用curl -fsSL https://ollama.com/install.sh | sh。安装后执行ollama run llama3下载模型它会自动存到~/.ollama/models/。FastAPI 服务通过 HTTP 调用http://localhost:11434/api/chat不与 Python 环境共享依赖。4.2 FastAPI 服务启动生产环境的 7 项必要配置开发时用uvicorn main:app --reload很方便但生产环境必须用gunicornuvicorn组合原因如下表配置项开发模式 (uvicorn --reload)生产模式 (gunicorn -k uvicorn.workers.UvicornWorker)为什么必须进程模型单进程自动重启多 Worker 进程建议--workers 4防止单个慢请求阻塞所有请求日志格式简单文本JSON 格式--access-logformat {time:%%(t)s,status:%%(s)s,method:%%(m)s,path:%%(U)s,size:%%(B)s}便于 ELK 日志分析超时控制无--timeout 120防止长尾请求占满连接池避免雪崩效应SSL 终止无必须由 Nginx 处理FastAPI 只监听 HTTP卸载 TLS 计算提升吞吐静态文件不支持用StaticFiles挂载./static目录为 Streamlit 提供前端资源健康检查无添加/healthz端点返回{status: ok}Kubernetes liveness probe环境变量明文写在代码里用pydantic.BaseSettings读取.env文件隔离密钥符合安全规范生产启动命令gunicorn main:app \ --bind 0.0.0.0:8000 \ --workers 4 \ --worker-class uvicorn.workers.UvicornWorker \ --timeout 120 \ --keep-alive 5 \ --access-logformat {time:%%(t)s,status:%%(s)s,method:%%(m)s,path:%%(U)s,size:%%(B)s,duration:%%(D)s} \ --access-logfile - \ --error-logfile - \ --log-level info4.3 Streamlit 部署Nginx 反向代理的 3 个致命配置Streamlit 默认监听0.0.0.0:8501但直接暴露给公网有严重风险。必须用 Nginx 做反向代理并配置以下三项# /etc/nginx/conf.d/streamlit.conf upstream streamlit_backend { server 127.0.0.1:8501; } server { listen 80; server_name your-domain.com; # 1. 关键WebSocket 升级头Streamlit 依赖 WebSocket 保持连接 location / { proxy_pass http://streamlit_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; proxy_set_header X-Forwarded-Proto $scheme; } # 2. 关键静态资源路径Streamlit 的 JS/CSS location /_stcore/ { proxy_pass http://streamlit_backend/_stcore/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; } # 3. 关键健康检查端点供负载均衡器探测 location /healthz { return 200 OK; add_header Content-Type text/plain; } }如果漏掉proxy_set_header Upgrade $http_upgradeStreamlit 会降级为长轮询Long Polling导致 CPU 使用率飙升 300%。我在一个 16 核服务器上实测未配置此头时100 并发下 CPU 达到 98%配置后稳定在 22%。4.4 完整项目结构可直接克隆复用的目录骨架llm-chat-deploy/ ├── backend/ # FastAPI 服务 │ ├── main.py # ASGI 应用入口 │ ├── models/ # Declarai 模型定义 │ │ ├── __init__.py │ │ └── chat_models.py # ChatModel 类和 Provider 初始化 │ ├── api/ # API 路由 │ │ ├── __init__.py │ │ └── v1/ # v1 版本路由 │ │ ├── __init__.py │ │ └── chat.py # /v1/chat/completions 路由 │ ├── core/ # 核心工具 │ │ ├── __init__.py │ │ └── tracer.py # Declarai Tracer 配置 │ ├── config.py # Pydantic Settings读取 .env │ └── requirements.txt ├── frontend/ # Streamlit 应用 │ ├── app.py # 主程序 │ ├── components/ # 自定义组件如知识库选择器 │ │ └── kb_selector.py │ ├── prompts/ # 提示词 YAML 文件 │ │ └── financial.yaml │ └── requirements.txt ├── docker-compose.yml # 一键启动 Ollama FastAPI Streamlit ├── .env # 环境变量API_KEY, MODEL_BASE_URL └── README.mddocker-compose.yml示例让团队新人 5 分钟跑起来version: 3.8 services: ollama: image: ollama/ollama ports: - 11434:11434 volumes: - ./ollama_models:/root/.ollama/models fastapi: build: ./backend ports: - 8000:8000 environment: - OLLAMA_BASE_URLhttp://ollama:11434 depends_on: - ollama streamlit: build: ./frontend ports: - 8501:8501 environment: - FASTAPI_URLhttp://fastapi:8000 depends_on: - fastapi5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 FastAPI 流式响应卡顿90% 的 case 都是这 3 个原因现象根本原因排查命令解决方案首 token 延迟 2suvicorn未启用--workers单进程阻塞ab -n 100 -c 10 http://localhost:8000/healthz查看并发响应时间改用gunicorn启动--workers设为 CPU 核数 * 2流式输出突然中断Nginxproxy_read_timeout默认 60sLLM 生成超时tail -f /var/log/nginx/error.log | grep upstream timed out在 Nginx 配置中添加proxy_read_timeout 300;前端收到重复 chunkFastAPIStreamingResponse的生成器未正确处理ClientDisconnect在generate_stream中加print(Yielding chunk)日志在生成器外层包裹try/except ClientDisconnect捕获后return我遇到过最诡异的 caseStreamlit 前端显示“Hello”后卡住Wireshark 抓包发现 TCP 连接正常但 HTTP 响应体只有data: {id:...,delta:Hello,finished:false}\n\n没有后续。最终定位到是asyncio.sleep(0.01)的精度问题——在某些 Linux 内核下sleep(0.01)实际休眠 100ms导致流速过慢被浏览器判定为超时。解决方案是去掉sleep改用await asyncio.sleep(0)让出控制权即可。5.2 Declarai Provider 切换失败环境变量与配置的隐藏依赖Declarai 的AzureOpenAIProvider会自动读取AZURE_OPENAI_API_KEY等环境变量但如果你在代码里显式传入api_key它会忽略环境变量。问题在于Azure AD 认证azure-identity必须通过环境变量AZURE_CLIENT_ID等生效无法在代码中传入。所以正确姿势是# ❌ 错误显式传入 api_key禁用了 AD 认证 AzureOpenAIProvider(api_keyxxx, ...) # ✅ 正确不传 api_key让 Provider 自动从环境变量或 AD 获取 AzureOpenAIProvider( api_basehttps://xxx.openai.azure.com/, api_version2023-12-01-preview, deployment_namellama3-8b )然后在.env文件中AZURE_CLIENT_IDyour-client-id AZURE_TENANT_IDyour-tenant-id AZURE_CLIENT_SECRETyour-client-secret5.3 Streamlit 状态丢失多用户会话的 2 种隔离方案Streamlit 默认为每个浏览器标签页创建独立会话但如果你用st.session_state存储用户专属数据如登录态多个标签页会互相覆盖。解决方案有两种方案 1URL 参数隔离轻量级# 在 app.py 开头 query_params st.experimental_get_query_params() user_id query_params.get(user_id, [default])[0] if fmessages_{user_id} not in st.session_state: st.session_state[fmessages_{user_id}] [...]然后分享链接https://your-app.com/?user_idalice。方案 2Redis 会话存储企业级import redis from streamlit.server.server_util import get_current_session_id redis_client redis.Redis(hostlocalhost, port6379, db0) def get_session_messages(): session_id get_current_session_id() key fstreamlit:session:{session_id}:messages data redis_client.get(key) return json.loads(data) if data else [] def set_session_messages(messages): session_id get_current_session_id() key fstreamlit:session:{session_id}:messages redis_client.setex(key, 3600, json.dumps(messages)) # 1 小时过期实操心得Streamlit 的st.cache_resource会缓存整个对象包括httpx.AsyncClient的连接池。但如果你在st.cache_resource函数里用了st.secrets如st.secrets[FASTAPI_URL]Streamlit 会在每次st.secrets变更时清空缓