mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-18 03:57:06 +08:00
v0.3.8
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
package modeladapter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// recordLLMRequestArtifact 记录一次模型调用的原始请求工件。
|
||||
func recordLLMRequestArtifact(req StreamRequest, provider string, model string, method string, url string, body any) {
|
||||
if req.Observer == nil {
|
||||
return
|
||||
}
|
||||
path, err := req.Observer.RecordLLMRequest(req.RequestID, req.RunID, req.ModelCallID, map[string]any{
|
||||
"request_id": req.RequestID,
|
||||
"run_id": req.RunID,
|
||||
"model_call_id": req.ModelCallID,
|
||||
"provider": strings.TrimSpace(provider),
|
||||
"openai_endpoint": strings.TrimSpace(req.OpenAIEndpoint),
|
||||
"model": firstNonEmptyString(model, req.ModelID),
|
||||
"runtime_model_id": strings.TrimSpace(req.ModelID),
|
||||
"resolved_channel_id": strings.TrimSpace(req.ResolvedChannelID),
|
||||
"resolved_channel_name": strings.TrimSpace(req.ResolvedChannelName),
|
||||
"resolved_context_window_tokens": req.ResolvedContextWindowTokens,
|
||||
"url": url,
|
||||
"method": method,
|
||||
"body": body,
|
||||
"request_knobs": req.RequestKnobs,
|
||||
"compile_summary": req.CompileSummary,
|
||||
"stable_message_count": req.StableMessageCount,
|
||||
"tools_summary": summarizeTools(req.Tools),
|
||||
"messages_summary": summarizeMessages(req.Messages),
|
||||
})
|
||||
if err == nil && req.ArtifactPaths != nil {
|
||||
req.ArtifactPaths.RequestPath = path
|
||||
}
|
||||
}
|
||||
|
||||
// buildLLMSummaryPayload 生成 LLM 调用摘要工件内容。
|
||||
func buildLLMSummaryPayload(
|
||||
req StreamRequest,
|
||||
provider string,
|
||||
model string,
|
||||
startedAt time.Time,
|
||||
firstEventAt time.Time,
|
||||
finishedAt time.Time,
|
||||
finishReason string,
|
||||
inputTokens int64,
|
||||
outputTokens int64,
|
||||
cacheReadTokens int64,
|
||||
cacheWriteTokens int64,
|
||||
err error,
|
||||
) map[string]any {
|
||||
finished := finishedAt
|
||||
if finished.IsZero() {
|
||||
finished = time.Now().UTC()
|
||||
}
|
||||
promptTokensTotal := inputTokens + cacheReadTokens + cacheWriteTokens
|
||||
requestTokensTotal := promptTokensTotal + outputTokens
|
||||
return map[string]any{
|
||||
"provider": strings.TrimSpace(provider),
|
||||
"model": strings.TrimSpace(model),
|
||||
"started_at": normalizeModelArtifactTime(startedAt),
|
||||
"first_event_at": normalizeModelArtifactTime(firstEventAt),
|
||||
"finished_at": normalizeModelArtifactTime(finished),
|
||||
"finish_reason": strings.TrimSpace(finishReason),
|
||||
"input_tokens": inputTokens,
|
||||
"output_tokens": outputTokens,
|
||||
"cache_read_tokens": cacheReadTokens,
|
||||
"cache_write_tokens": cacheWriteTokens,
|
||||
"prompt_tokens_total": promptTokensTotal,
|
||||
"request_tokens_total": requestTokensTotal,
|
||||
"error": summarizeModelArtifactError(err),
|
||||
"ttft_ms": computeTTFTMS(startedAt, firstEventAt),
|
||||
"duration_ms": computeDurationMS(startedAt, finished),
|
||||
}
|
||||
}
|
||||
|
||||
// appendLLMResponseArtifact 追加模型调用的原始响应文本。
|
||||
func appendLLMResponseArtifact(req StreamRequest, chunk string) (string, error) {
|
||||
if req.Observer == nil {
|
||||
return "", nil
|
||||
}
|
||||
path, err := req.Observer.AppendLLMResponseChunk(req.RequestID, req.RunID, req.ModelCallID, chunk)
|
||||
if err == nil && req.ArtifactPaths != nil {
|
||||
req.ArtifactPaths.ResponsePath = path
|
||||
}
|
||||
return path, err
|
||||
}
|
||||
|
||||
// recordLLMSummaryArtifact 记录模型调用摘要。
|
||||
func recordLLMSummaryArtifact(req StreamRequest, payload map[string]any) {
|
||||
if req.Observer == nil {
|
||||
return
|
||||
}
|
||||
path, err := req.Observer.RecordLLMSummary(req.RequestID, req.RunID, req.ModelCallID, payload)
|
||||
if err == nil && req.ArtifactPaths != nil {
|
||||
req.ArtifactPaths.SummaryPath = path
|
||||
}
|
||||
}
|
||||
|
||||
// summarizeTools 生成工具列表摘要。
|
||||
func summarizeTools(items []json.RawMessage) []string {
|
||||
result := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
var wrapper struct {
|
||||
Function struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"function"`
|
||||
}
|
||||
if err := json.Unmarshal(item, &wrapper); err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(wrapper.Function.Name) != "" {
|
||||
result = append(result, strings.TrimSpace(wrapper.Function.Name))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// summarizeMessages 生成消息列表摘要。
|
||||
func summarizeMessages(items []Message) []map[string]any {
|
||||
result := make([]map[string]any, 0, len(items))
|
||||
for _, item := range items {
|
||||
content := truncateArtifactText(item.Content, 120)
|
||||
imageCount := 0
|
||||
for _, part := range item.ContentParts {
|
||||
if normalizeContentPartType(part.Type) == contentPartTypeImage {
|
||||
imageCount++
|
||||
}
|
||||
}
|
||||
result = append(result, map[string]any{
|
||||
"role": item.Role,
|
||||
"content_preview": content,
|
||||
"content_length": len([]rune(content)),
|
||||
"image_count": imageCount,
|
||||
"tool_call_count": len(item.ToolCalls),
|
||||
"tool_call_id": item.ToolCallID,
|
||||
"name": item.Name,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func truncateArtifactText(text string, maxRunes int) string {
|
||||
trimmed := strings.TrimSpace(text)
|
||||
if trimmed == "" || maxRunes <= 0 {
|
||||
return ""
|
||||
}
|
||||
runes := []rune(trimmed)
|
||||
if len(runes) <= maxRunes {
|
||||
return trimmed
|
||||
}
|
||||
return string(runes[:maxRunes]) + "..."
|
||||
}
|
||||
|
||||
// normalizeModelArtifactTime 把时间格式化为 RFC3339Nano。
|
||||
func normalizeModelArtifactTime(value time.Time) string {
|
||||
if value.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return value.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// computeTTFTMS 计算首事件耗时。
|
||||
func computeTTFTMS(startedAt time.Time, firstEventAt time.Time) int64 {
|
||||
if startedAt.IsZero() || firstEventAt.IsZero() {
|
||||
return 0
|
||||
}
|
||||
value := firstEventAt.Sub(startedAt).Milliseconds()
|
||||
if value < 0 {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// computeDurationMS 计算调用总耗时。
|
||||
func computeDurationMS(startedAt time.Time, finishedAt time.Time) int64 {
|
||||
if startedAt.IsZero() || finishedAt.IsZero() {
|
||||
return 0
|
||||
}
|
||||
value := finishedAt.Sub(startedAt).Milliseconds()
|
||||
if value < 0 {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// summarizeModelArtifactError 返回可安全落盘的错误文本。
|
||||
func summarizeModelArtifactError(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(err.Error())
|
||||
}
|
||||
|
||||
// firstNonEmptyString 返回第一个非空字符串。
|
||||
func firstNonEmptyString(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user