Webman需通过环境变量注入DEEPSEEK_API_KEY并配置Guzzle超时,在config/app.php设安全代理入口,禁用明文密钥;401因Authorization头格式错误,429因未限流触发QPS限制;流式响应须手动解析SSE并分块推送。

Webman怎么配DeepSeek API的请求地址和密钥
Webman默认不内置大模型适配层,必须手动配置HTTP客户端指向DeepSeek真实接口,且不能直接暴露API_KEY到前端或配置文件明文里。
正确做法是:在config/app.php中定义安全代理入口,所有AI请求走后端中转;密钥通过环境变量注入,比如DEEPSEEK_API_KEY,再由config/autoload/dependencies.php注入到HTTP客户端实例。
- 不要把
https://api.deepseek.com/v1/chat/completions硬编码进控制器——改接口版本或域名时得全局搜替换 - 不要在
.env里写DEEPSEEK_API_KEY=sk-xxx——应使用系统级密钥管理(如Vault)或云平台Secrets Manager - Webman的
GuzzleHttp\Client需显式设置timeout和connect_timeout,否则默认无限等待,容易拖垮整个服务
为什么Webman调用DeepSeek总返回401或429
这两个错误几乎总是配置或调用方式不对导致的,不是模型本身问题。
401 Unauthorized:常见于Authorization头格式错误。DeepSeek要求Bearer {API_KEY},注意中间是空格,不是冒号或等号;另外确认用的是APP_ID还是API_KEY——Webman集成文档里混用了这两个字段,但实际只认API_KEY。
429 Too Many Requests:Webman默认没做请求节流,高频调试时容易触发。DeepSeek免费版QPS限制是5,超出即封禁1分钟。建议在app/Controller/AiController.php里加一层内存缓存+计数器,或直接用webman/plugin-rate-limit插件。
- 别用
curl_setopt($ch, CURLOPT_HEADER, true)调试——会把响应头当响应体解析,导致JSON decode失败 - 别在
onWorkerStart里初始化Client——每个worker都建一个连接池,可能耗尽fd - 别复用同一个
Client实例跨协程——Guzzle不保证协程安全,要用make()按需创建
如何让Webman支持DeepSeek的流式响应(stream=true)
Webman原生不支持SSE或chunked transfer encoding的流式返回,强行设stream => true只会等到超时才吐出整段JSON。
可行解法是:用Workerman\Connection\TcpConnection::send()手动分块推送。核心逻辑是监听DeepSeek的text/event-stream响应,逐行解析data: {...},再用$connection->send("event: message\n data: ...\n\n")转发给前端。
- 必须关闭
output_buffering,否则数据攒在PHP缓冲区不出去 - 前端不能用
fetch——得用EventSource或WebSocket接收 - DeepSeek的流式接口路径是
/v1/chat/completions(不是/completions),且body里必须带"stream": true
本地部署DeepSeek-R1后怎么对接Webman
如果用Ollama跑deepseek-r1:32b,默认监听http://localhost:11434,和官方API不兼容——Webman直接调会报404或invalid request。
需要加一层适配器:用ollama serve启动服务后,写个简单的路由转发中间件,把Webman发来的POST /v1/chat/completions请求,转换成Ollama能认的POST /api/chat格式(字段名、嵌套结构、messages数组转messages对象等)。
- Ollama不支持
system角色——得在适配器里把messages[0].role === 'system'的内容合并进prompt字段 - Webman的
json_decode($request->rawBody(), true)要提前判断是否为空,Ollama某些错误响应没body,直接decode会warning - 本地模型
max_tokens实际受显存限制,Webman传了4096进去,Ollama可能只返回前512个token,得在适配器里补"finish_reason": "length"字段,否则前端收不到结束信号



















