feat: 补齐三个方向的流式工具调用;Anthropic 上游走 x-api-key;禁用模型真正下架
- 流式转换补齐 chat→responses / responses→chat / messages→responses 的工具调用(此前只转文本,SDK 客户端拿不到 tool_calls);completed 事件携带完整 output 数组 - Anthropic 协议上游同时发 x-api-key(官方 API 不接受 API key 走 Bearer)并透传 anthropic-beta - 请求侧校验模型存在且启用:禁用模型 404 model_not_found,不再只是从 /v1/models 消失、实际照发照扣费;空模型名 400 - 模型在请求中途被删时按 0 记账留痕,不再按硬编码示例价扣用户的钱 - finishUsage 新增一行式请求日志(trace/user/model/channel/tokens/cost/status/latency),记账是异步的,日志是唯一即时排障依据
This commit is contained in:
1 parent
984a31cbc5
commit
d6db0f8162
6 files changed
+485
-31
No files matched your search
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -305,7 +306,13 @@ func (g *Gateway) proxyOne(c *gin.Context, ch *store.Channel, plan *upstreamPlan
|
||||
req.Header.Set("User-Agent", ua)
|
||||
}
|
||||
if plan.path == "/messages" {
|
||||
// Anthropic 官方 API 只认 x-api-key(Bearer 是 OAuth token 的用法),
|
||||
// 兼容网关两种都收;带上 anthropic-version 与客户端声明的 beta 头。
|
||||
req.Header.Set("x-api-key", upKey)
|
||||
req.Header.Set("anthropic-version", "2023-06-01")
|
||||
if beta := c.GetHeader("anthropic-beta"); beta != "" {
|
||||
req.Header.Set("anthropic-beta", beta)
|
||||
}
|
||||
}
|
||||
for _, h := range []string{"OpenAI-Organization", "OpenAI-Project", "OpenAI-Beta"} {
|
||||
if v := c.GetHeader(h); v != "" {
|
||||
@@ -722,7 +729,9 @@ func (g *Gateway) finishUsage(c *gin.Context, ch *store.Channel, start time.Time
|
||||
float64(cacheRead)/1e6*model.CacheReadPrice +
|
||||
float64(cacheCreate)/1e6*model.CacheWritePriceOr()
|
||||
} else {
|
||||
cost = float64(in+cacheRead+cacheCreate)/1e6*0.15 + float64(out)/1e6*0.60 // 无定价模型时按示例价
|
||||
// 模型在记账时已被删除(请求进行中被下架):无法定价,按 0 记账留痕。
|
||||
// 虚构单价扣用户的钱既不可审计也无法对账。
|
||||
log.Printf("proxy: trace=%s model %q not found at billing time, cost recorded as 0", trace, mn)
|
||||
}
|
||||
// 落库的 input_tokens 存输入总量(含缓存读/写),与上游 message_start 口径一致,便于对账展示。
|
||||
in += cacheRead + cacheCreate
|
||||
@@ -793,6 +802,11 @@ func (g *Gateway) finishUsage(c *gin.Context, ch *store.Channel, start time.Time
|
||||
RawResponse: rawResp,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
})
|
||||
|
||||
// 请求级一行日志:usage_logs 落库是异步的且可能丢,排障时日志是唯一即时依据
|
||||
log.Printf("proxy: trace=%s user=%d key=%d model=%q channel=%d proto=%s in=%d out=%d cache_r=%d cache_w=%d cost=%.6f status=%s http=%d latency=%dms err=%s",
|
||||
traceStr, uidVal, kidVal, mn, chID, p, in, out, cacheRead, cacheCreate, cost, status,
|
||||
c.Writer.Status(), latency, errCode)
|
||||
}
|
||||
|
||||
// recordError 失败请求的记账(不产生扣费,status=error)。
|
||||
|
||||
Reference in new issue
Block a user