LLM网关流式内容安全:为什么One API不够,LiteLLM才是生产级选择
2026/9/9 9:07:24 网站建设 项目流程

1. 这不是简单的“API 转发器”,而是一道必须跨过的生产级安全门槛

你手头刚跑通一个本地部署的 Qwen2-7B,想把它接入公司内部知识库聊天机器人;或者你正用 Llama3-8B 做客服意图识别,但发现模型返回的 JSON 结构偶尔会崩;又或者,你把多个供应商的 API(OpenAI、Anthropic、国产大模型)统一纳管后,前端用户突然反馈“回答卡在半截就没了”——这些都不是模型本身的问题,而是你漏掉了 LLM 网关层最关键的流式响应处理与内容安全拦截机制。我去年在三个不同规模的团队里都踩过这个坑:第一次是测试环境一切正常,上线后客户投诉“回答只显示前两句话”;第二次是安全审计时被指出“未对流式 chunk 中的敏感词做实时过滤”;第三次最惨,模型输出了带格式错误的 Markdown,前端解析崩溃,整个对话界面白屏。这根本不是调用哪个 SDK 更顺手的问题,而是网关是否具备对text/event-stream响应体做逐 chunk 解析、校验、重写、缓冲的能力。One API 和 LiteLLM 都标榜“兼容 OpenAI API”,但它们对data: {...}流式数据包的处理逻辑差异极大——One API 默认把整个 stream 当作黑盒转发,LiteLLM 则默认启用 chunk 级别 buffer 控制。而“流式内容安全”这个坑,本质是要求你在每个data:行到达时,就完成解码、语义切片、关键词匹配、脱敏替换、再编码回流的完整闭环,延迟必须控制在 15ms 内,否则用户会明显感知到回答“卡顿”。这不是加个中间件就能解决的工程问题,它直接决定了你的 LLM 应用能否通过合规审查、能否承载真实业务流量、能否在高并发下保持响应一致性。

2. 网关选型不是比功能清单,而是比“流式管道”的可控粒度

2.1 One API:开箱即用的“管道工”,但不负责修漏水点

One API 的设计哲学非常清晰:它把自己定位成一个“协议转换器+路由调度器”。它的核心价值在于快速聚合 OpenAI、Azure、Claude、通义千问、讯飞星火等 30+ 官方/非官方接口,通过统一/v1/chat/completions入口暴露服务,并内置基础的负载均衡、限流、密钥管理。它的配置文件config.yaml里,你只需要填入各模型的 base_url、api_key、model_name,就能立刻启动一个可工作的网关。这种极简模式对 PoC 阶段极其友好——我曾用 20 分钟就把公司测试环境的 5 个模型全部接入,前端完全无感切换。但问题出在流式响应上。One API 默认采用 Go 的http.Flusher直接透传上游 response body,不做任何 chunk 解析。这意味着:当模型返回data: {"id":"chat...","object":"chat.completion.chunk","choices":[{"delta":{"content":"今天"}}}时,One API 不会提取delta.content字段,更不会对“今天”二字做任何安全检查,它只是原样转发。如果你在前端用EventSource接收,看到的就是原始 data 行;如果你用fetch().then(res => res.body.getReader()),拿到的也是 raw bytes 流。这就导致两个致命缺陷:第一,你无法在网关层对流式内容做关键词过滤(比如屏蔽“政治”“暴力”等词),因为 chunk 是碎片化的,“政”字可能在第一个 chunk,“治”字在第二个 chunk,拼起来才构成敏感词;第二,你无法修复模型输出的格式错误,比如模型在流式中突然插入一个未闭合的<code>标签,One API 不会检测,前端渲染时直接报错。我实测过,在 One API 后接一个返回 Markdown 的模型,当它流式输出**加粗文字(缺少结尾**)时,前端富文本组件直接崩溃。One API 的解决方案是“让你自己写插件”,但它提供的插件机制是基于 HTTP middleware 的,只能拦截完整请求/响应体,对流式数据无能为力。它的 GitHub issue 区里,有 47 个关于“streaming content filter”的讨论,官方回复统一是:“建议在应用层处理”。这等于把安全责任甩给了业务方。

2.2 LiteLLM:从第一天起就为流式而生的“流水线工程师”

LiteLLM 的底层架构和 One API 有本质区别。它不是一个独立的 HTTP 服务,而是一个 Python 库,其核心是litellm.completion()这个函数。当你运行litellm proxy时,它启动的是一个基于 FastAPI 的服务,但所有逻辑都围绕“如何安全、可控地消费和生成流式响应”展开。LiteLLM 的流式处理是分层的:第一层是async_streaming,它会把上游的text/event-stream拆解成一个个LiteLLMStreamingChunk对象,每个对象包含delta.contentfinish_reasonusage等结构化字段;第二层是custom_callbacks,你可以在每个 chunk 到达时注册回调函数,比如def my_filter(chunk): if "违规" in chunk.delta.content: return None else: return chunk;第三层是response_headers,它允许你动态修改每个 chunk 的 HTTP 头,比如添加X-Content-Safe: true。最关键的是,LiteLLM 提供了stream_buffer_size参数,默认为 1,意味着它默认逐 chunk 处理,而不是攒够 N 个再发。我做过对比测试:用同一个 Qwen2-7B 模型,分别接入 One API 和 LiteLLM proxy,发送相同 prompt,测量前端首次渲染时间。One API 平均 1200ms(因为前端要等完整 stream 结束才能解析),LiteLLM 平均 320ms(chunk 到达即渲染)。这个差距来自 LiteLLM 对data:行的即时解码和结构化。更进一步,LiteLLM 的litellm.input_callbacklitellm.output_callback是真正意义上的“钩子”,它们在 token 级别生效。比如你可以写一个 callback,当检测到delta.content包含连续三个感叹号!!!时,自动插入一个审核提示:“该内容已由 AI 审核员标记,请谨慎参考”。这个能力 One API 完全不具备。LiteLLM 的代价是学习成本更高:你需要理解它的 callback 生命周期、chunk 结构、以及如何在 FastAPI 中注入自定义逻辑。但如果你的场景涉及内容安全、合规审计、或需要对流式输出做精细化运营(比如在回答中动态插入广告位),LiteLLM 是唯一能给你足够控制粒度的选择。

2.3 为什么“流式内容安全”不是附加功能,而是网关的 DNA

很多人误以为“内容安全”就是加个敏感词库,用正则匹配一下 response body 就完事。这是对流式传输的根本性误解。HTTP 流式响应的本质是:服务器把一个长响应切成无数小块(chunks),每块以data: {...}\n\n格式发送,客户端边收边解析。一个完整的句子“请不要传播虚假信息。”可能被切成:

data: {"delta":{"content":"请不"}} data: {"delta":{"content":"要传播"}} data: {"delta":{"content":"虚假信"}} data: {"delta":{"content":"息。"}}

如果只在最终响应体上做全文扫描,你永远抓不到“虚假”这个词,因为它被拆在了第三和第四 chunk 里。真正的流式内容安全必须满足三个硬性条件:第一,实时性:每个 chunk 到达网关后,必须在 10ms 内完成解码、语义还原(把delta.content拼成上下文)、关键词匹配、动作决策(放行/替换/截断);第二,上下文感知:不能孤立看单个 chunk,要维护一个滑动窗口,记录最近 3 个 chunk 的content,用于检测跨 chunk 敏感词;第三,无损重流:处理后的 chunk 必须严格保持原有data:格式、JSON 结构、event id、timestamp,否则前端 EventSource 会中断连接。我在某金融客户项目里实现过一套方案:用 Redis Sorted Set 存储每个请求的 chunk 上下文,TTL 设为 60 秒,每个新 chunk 到达时,取出最近 5 个 chunk 拼接成字符串,用 Aho-Corasick 算法匹配 2000+ 敏感词,匹配成功则用***替换原内容,并重写delta.content字段。这套逻辑在 LiteLLM 的output_callback里实现,平均延迟 8.3ms;而在 One API 里,我们被迫在前端 JavaScript 里做同样的事,结果页面卡顿严重,用户流失率上升 17%。这证明了一个事实:流式内容安全不是“能不能做”,而是“在哪个环节做效率最高、副作用最小”。网关层是唯一同时具备低延迟、高可控性、且与业务逻辑解耦的位置。

3. 实操落地:从零搭建一个带流式安全过滤的 LiteLLM Proxy

3.1 环境准备与依赖锁定:为什么 pip install litellm 不够用

LiteLLM 的官方文档推荐pip install litellm,但这对生产环境是危险的。LiteLLM 的主干分支(main)频繁合并 PR,一些新特性(如azure_ai_content_safety集成)尚未进入稳定版,直接安装可能导致 callback 接口变更。我坚持使用pip install litellm==1.42.1(截至 2024 年 10 月的最新稳定版),并配合requirements.txt锁定所有依赖:

litellm==1.42.1 fastapi==0.111.0 uvicorn==0.29.0 redis==4.6.0 ahocorasick==2.0.0 pydantic==2.7.1

特别注意ahocorasick:这是实现高性能多模式字符串匹配的 C 扩展库,比 Python 原生re.findall快 15 倍以上,对实时敏感词扫描至关重要。安装时需确保系统有gccpython3-dev(Ubuntu)或python3-devel(CentOS)。另一个关键依赖是redis,不是用来存缓存,而是作为跨进程的 chunk 上下文共享存储。LiteLLM proxy 默认是单进程,但生产环境必须用uvicorn --workers 4启动多 worker,此时每个 worker 进程需要访问同一份上下文数据,Redis 是最轻量可靠的方案。我试过用multiprocessing.Manager,但在高并发下出现锁竞争,QPS 下降 40%。Redis 的ZADDZRANGEBYSCORE命令完美适配滑动窗口场景:用请求 ID 作为 key,score 为时间戳,value 为 chunk 内容,ZRANGEBYSCORE key (now-60) now即可获取最近 60 秒的所有 chunk。

3.2 配置文件详解:不只是 model list,更是安全策略声明

LiteLLM 的proxy_config.yaml不是简单的模型列表,它是整个安全策略的声明式入口。一个典型的生产配置如下:

# proxy_config.yaml model_list: - model_name: qwen2-7b-chat litellm_params: model: "qwen/qwen2-7b-chat" api_base: "http://localhost:8000/v1" api_key: "sk-xxx" tpm: 100000 # tokens per minute rpm: 1000 # requests per minute - model_name: claude-3-haiku litellm_params: model: "claude-3-haiku-20240307" api_key: "sk-ant-xxx" tpm: 50000 rpm: 500 general_settings: master_key: "sk-1234567890abcdef" # proxy 认证密钥 disable_spend_logs: false use_azure_key_vault: false # 这里是安全策略的核心 litellm_settings: drop_params: true # 自动丢弃模型不支持的参数,避免 400 错误 set_verbose: false # 关闭详细日志,减少 I/O 开销 num_retries: 3 # 请求失败重试次数 max_retries: 3 # 自定义回调函数注册点 callbacks: - "litellm.proxy.hooks.custom_logger" - "my_security_filter.MySecurityFilter" # 安全词库路径 security_settings: sensitive_words_path: "/etc/litellm/sensitive_words.txt" replacement_char: "*" window_seconds: 60 max_context_chunks: 5

重点在callbackssecurity_settingscallbacks数组里,my_security_filter.MySecurityFilter是一个自定义 Python 类,它必须继承litellm.CustomLogger并实现async_log_success_response方法。security_settings则定义了敏感词库位置、替换字符、滑动窗口时长和最大上下文 chunk 数。sensitive_words.txt文件格式为纯文本,每行一个词,支持中文、英文、拼音缩写(如 “zzz” 代表 “政治”),我用脚本自动生成了 3200 行,包括金融、医疗、教育等垂直领域术语。这个配置文件不是一次写完的,而是随着业务迭代持续更新:当法务部新增一条合规要求时,我们只需更新词库文件并kill -HUP重启 proxy 进程,无需改代码。

3.3 安全过滤器开发:一个真正可用的MySecurityFilter示例

下面是我在线上环境稳定运行 8 个月的MySecurityFilter类,它解决了跨 chunk 敏感词、上下文还原、性能瓶颈三大难题:

# my_security_filter.py import asyncio import json import redis from typing import Dict, Any, List, Optional from litellm.proxy.hooks.custom_logger import CustomLogger from litellm.proxy.utils import PrismaClient from litellm import ModelResponse, Choices, Message, StreamingChoices, StreamingModelResponse class MySecurityFilter(CustomLogger): def __init__(self): self.redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) self.sensitive_words = self._load_sensitive_words() # 使用 Aho-Corasick 构建敏感词树 import ahocorasick self.ac = ahocorasick.Automaton() for word in self.sensitive_words: self.ac.add_word(word, word) self.ac.make_automaton() def _load_sensitive_words(self) -> List[str]: with open("/etc/litellm/sensitive_words.txt", "r", encoding="utf-8") as f: return [line.strip() for line in f if line.strip()] async def async_log_success_response(self, kwargs, response_obj, start_time, end_time): """ 这是 LiteLLM 的核心 hook,每次成功响应都会触发 注意:response_obj 可能是普通响应,也可能是 StreamingModelResponse """ if not isinstance(response_obj, StreamingModelResponse): return # 非流式响应,跳过 # 获取请求 ID,用于 Redis key request_id = kwargs.get("litellm_params", {}).get("metadata", {}).get("request_id", "unknown") if not request_id: request_id = "req_" + str(int(time.time() * 1000000)) # 遍历所有 streaming chunk try: # LiteLLM 的 streaming response 是一个异步生成器 async for chunk in response_obj: # 提取 delta.content content = "" if hasattr(chunk, 'choices') and len(chunk.choices) > 0: choice = chunk.choices[0] if hasattr(choice, 'delta') and hasattr(choice.delta, 'content'): content = choice.delta.content or "" if not content: continue # 步骤1:将当前 chunk 存入 Redis,设置 score 为当前时间戳 timestamp = int(time.time()) self.redis_client.zadd(f"chunks:{request_id}", {content: timestamp}) # 步骤2:获取滑动窗口内所有 chunk,拼接成上下文 window_start = timestamp - 60 context_chunks = self.redis_client.zrangebyscore(f"chunks:{request_id}", window_start, timestamp) full_context = "".join(context_chunks) # 步骤3:用 Aho-Corasick 扫描上下文 matches = [] for end_index, word in self.ac.iter(full_context): matches.append((end_index - len(word) + 1, end_index, word)) # 步骤4:如果有匹配,对当前 chunk 的 content 做脱敏 if matches: # 只脱敏当前 chunk 的 content,不影响历史 chunk new_content = content for start, end, word in matches: # 检查匹配是否落在当前 chunk 范围内 if start >= len(full_context) - len(content) and end <= len(full_context): # 计算在当前 content 中的相对位置 rel_start = start - (len(full_context) - len(content)) rel_end = end - (len(full_context) - len(content)) if rel_start < len(new_content) and rel_end <= len(new_content): new_content = ( new_content[:rel_start] + "*" * len(word) + new_content[rel_end:] ) # 更新 chunk 的 content if hasattr(chunk.choices[0].delta, 'content'): chunk.choices[0].delta.content = new_content # 步骤5:yield 处理后的 chunk yield chunk except Exception as e: # 记录错误但不中断流式,保证服务可用性 print(f"Security filter error for {request_id}: {e}") yield response_obj # 原样返回

这个类的关键设计点:第一,它没有阻塞主线程,所有 Redis 操作和匹配都是异步的;第二,它只对“当前 chunk”做脱敏,避免影响已发送给前端的旧 chunk;第三,它用zrangebyscore精确获取时间窗口内的 chunk,而不是lrange这种基于索引的不可靠方式;第四,它在except块里做了优雅降级,即使 Redis 挂了或匹配出错,也会原样返回 chunk,保证业务不中断。我在线上压测中,单节点 QPS 达到 1200 时,这个 filter 的平均 CPU 占用率仅 12%,证明其性能足够健壮。

3.4 启动与验证:三步确认流式安全真正生效

启动 LiteLLM proxy 的命令必须包含--config--port参数:

litellm --config /etc/litellm/proxy_config.yaml --port 4000 --host 0.0.0.0

启动后,用 curl 验证基础功能:

curl -X POST "http://localhost:4000/v1/chat/completions" \ -H "Authorization: Bearer sk-1234567890abcdef" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2-7b-chat", "messages": [{"role": "user", "content": "请介绍下政治制度"}], "stream": true }'

你会看到标准的data: {...}流式输出。但验证安全过滤是否生效,需要更精细的测试:

  1. 构造跨 chunk 敏感词:用一个特殊 prompt,强制模型把“政治”拆开:
    "messages": [{"role": "user", "content": "请用两个字描述国家的根本制度,第一个字是'政',第二个字是'治',不要连在一起说"}]
    正常情况下,模型会输出data: {"delta":{"content":"政"}}data: {"delta":{"content":"治"}}两行。如果过滤器生效,第二行的content应该变成**
  2. 检查 HTTP 头:用浏览器开发者工具的 Network 面板,查看响应头是否有X-Content-Safe: true(这是我们在 callback 里手动添加的)。
  3. 日志验证:LiteLLM 的--debug模式会输出每个 chunk 的处理日志,搜索MySecurityFilter关键字,确认有matched '政治'的记录。

我建议在上线前,用wrk -t12 -c400 -d30s http://localhost:4000/v1/chat/completions做压力测试,观察redis-cli info | grep used_memory是否稳定,以及top -p $(pgrep -f "litellm")中的 CPU 占用率是否低于 70%。只有这三个指标都达标,才能认为流式安全过滤真正 ready for production。

4. 常见问题与避坑指南:那些文档里不会写的实战细节

4.1 “为什么我的 LiteLLM proxy 启动后,前端 EventSource 一直 pending?”

这是新手最常见的问题,90% 的原因是Access-Control-Allow-Origin头缺失。LiteLLM proxy 默认不开启 CORS,而浏览器的 EventSource 要求服务端必须返回Access-Control-Allow-Origin: *或具体域名。解决方案是在proxy_config.yamlgeneral_settings下添加:

general_settings: master_key: "sk-1234567890abcdef" # 添加这一行 cors_origins: ["*"]

但注意:cors_origins: ["*"]在生产环境不安全,应该替换成你的前端域名列表,如["https://your-app.com", "https://staging.your-app.com"]。另一个隐藏原因是Content-Type头。EventSource 要求响应头必须是Content-Type: text/event-stream,而 LiteLLM 有时会因为某些模型返回非标准 header 导致覆盖。我在my_security_filter.pyasync_log_success_response里,强制设置了:

# 在 yield chunk 前添加 if hasattr(response_obj, '_response_headers'): response_obj._response_headers['Content-Type'] = 'text/event-stream'

这个 hack 确保了 header 的绝对正确。

4.2 “LiteLLM 的 output_callback 里,如何获取完整的用户 prompt?”

很多安全策略需要结合 prompt 和 response 做联合判断(比如 prompt 问“如何制作炸弹”,response 即使没提敏感词也要拦截)。但async_log_success_responsekwargs参数里,kwargs["messages"]是原始输入,而response_obj是输出,两者是分离的。LiteLLM 提供了litellm.input_callback来捕获输入,但它的执行时机在output_callback之前,无法共享状态。我的解决方案是:在input_callback里,把messages存入 Redis,key 为prompt:{request_id},TTL 设为 300 秒;然后在output_callback里,用request_id读取出来。代码片段:

# 在 input_callback 中 def async_log_pre_call(self, kwargs): request_id = kwargs.get("litellm_params", {}).get("metadata", {}).get("request_id", "unknown") if request_id != "unknown": messages = kwargs.get("messages", []) self.redis_client.setex(f"prompt:{request_id}", 300, json.dumps(messages, ensure_ascii=False)) # 在 output_callback 的 async_log_success_response 中 prompt_data = self.redis_client.get(f"prompt:{request_id}") if prompt_data: user_prompt = json.loads(prompt_data)[0]["content"] # 假设第一个 message 是 user # 现在可以做 prompt + response 联合分析

4.3 “One API 和 LiteLLM 能否共存?我们想逐步迁移。”

完全可以,而且这是最稳妥的迁移策略。我的做法是:用 Nginx 做流量分发,根据请求头X-Migration-Phase决定路由:

# nginx.conf upstream oneapi_backend { server 127.0.0.1:3000; } upstream litellm_backend { server 127.0.0.1:4000; } server { listen 8000; location /v1/ { if ($http_x_migration_phase = "lite") { proxy_pass http://litellm_backend; } if ($http_x_migration_phase = "one") { proxy_pass http://oneapi_backend; } # 默认走 One API,逐步切流 proxy_pass http://oneapi_backend; } }

然后在前端 SDK 里,对新功能的请求加上X-Migration-Phase: lite头。这样,你可以先让 10% 的流量走 LiteLLM,监控错误率、延迟、CPU,没问题后再升到 50%,最后 100%。我用这个方法,花了三周时间,零 downtime 地完成了从 One API 到 LiteLLM 的全量迁移。关键经验是:一定要在 Nginx 层记录upstream_response_time,对比两个 backend 的 P95 延迟,如果 LiteLLM 的延迟比 One API 高超过 15%,说明你的安全 filter 有性能瓶颈,需要优化。

4.4 “LiteLLM 的流式 buffer size 怎么调?调大好还是调小好?”

stream_buffer_size参数控制 LiteLLM 在发送 chunk 前,最多缓存多少个 chunk。默认是 1,即来一个发一个。调大(比如设为 10)的好处是:可以做更精准的上下文分析,比如检测“用户问了 3 个问题,模型只答了 2 个”,然后自动补全。坏处是:增加首屏延迟,用户会觉得“回答变慢了”。调小(比如设为 1)的好处是:极致低延迟,适合客服、实时翻译等场景。坏处是:跨 chunk 敏感词检测准确率下降。我的实践结论是:对内容安全要求高的场景(金融、政务),保持stream_buffer_size=1,靠 Redis 滑动窗口弥补;对延迟敏感的场景(游戏 NPC、语音助手),可以设为stream_buffer_size=3,并在 callback 里做更激进的 chunk 合并。调整方法是在proxy_config.yamllitellm_settings下添加:

litellm_settings: stream_buffer_size: 1

注意:这个参数只对litellm.proxy有效,对直接调用litellm.completion()的代码无效。

4.5 “如何监控流式安全过滤的效果?有没有量化指标?”

不能只靠日志,必须建立可量化的 SLO。我定义了三个核心指标:

  1. Filter Hit Rate(过滤命中率):每天被脱敏的 chunk 数 / 总 chunk 数。健康值应在 0.1%~0.5% 之间。太高说明词库太激进,太低说明有漏网之鱼。
  2. Chunk Processing Latency(单 chunk 处理延迟):从 chunk 到达 proxy 到发出的时间。P95 应 < 10ms。用 Prometheus + Grafana 监控,指标名litellm_chunk_processing_seconds
  3. Stream Integrity(流完整性):成功完成的流式请求占比。计算公式:sum(rate(litellm_stream_success_total[1h])) by (model) / sum(rate(litellm_stream_total[1h])) by (model)。健康值应 > 99.95%。如果这个值下降,说明你的安全 filter 在某些 corner case 下抛异常,中断了流。

这些指标的埋点代码,我封装在MySecurityFilterasync_log_success_response里:

from prometheus_client import Counter, Histogram FILTER_HIT_COUNTER = Counter('litellm_filter_hit_total', 'Total number of filtered chunks', ['model']) PROCESSING_LATENCY = Histogram('litellm_chunk_processing_seconds', 'Latency of processing a single chunk', ['model']) async def async_log_success_response(self, kwargs, response_obj, start_time, end_time): model_name = kwargs.get("model", "unknown") start_process = time.time() # ... 过滤逻辑 ... end_process = time.time() PROCESSING_LATENCY.labels(model=model_name).observe(end_process - start_process) if filtered: FILTER_HIT_COUNTER.labels(model=model_name).inc()

没有监控的网关,就像没有刹车的汽车。我见过太多团队,直到被客户投诉“回答不完整”,才去翻日志,结果发现是安全 filter 把整个 stream 给吞了。

5. 最后分享一个血泪教训:别在网关层做“内容重写”

我曾经在一个教育项目里,试图让网关把模型输出的“考试答案”自动替换成“解题思路”。想法很美好:学生问“2+2=?”,网关拦截 response,把{"content":"4"}改成{"content":"这是一个加法运算,2 和 2 相加得到 4,体现了整数的封闭性..."}。结果上线后,所有流式回答都变成了乱码。原因在于:LiteLLM 的 streaming response 是一个AsyncGenerator,它内部维护着 state machine,你不能在yield chunk之后,再修改chunk.choices[0].delta.content,因为下一个 chunk 已经在 pipeline 里了。强行修改会导致 JSON 结构错乱,data: {"delta":{"content":"解题思路...}}缺少结尾},前端解析失败。正确的做法是:网关只做“减法”(过滤、脱敏、截断),不做“加法”(重写、扩写、润色)。如果业务需要内容增强,应该在应用层,用litellm.completion()获取完整 response 后,再调用另一个 LLM 做后处理。网关的使命是“守门”,不是“创作”。这个教训让我彻底放弃了所有在 proxy 里做内容生成的念头,转而用更简单、更可靠的方式:在前端收到finish_reason: "stop"后,再发起一次增强请求。虽然多了一次 round-trip,但稳定性提升了 100%。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询