模块介绍 / 今日规划 / 下班日报 / 团队摘要 生成成功后没有上报,也几乎不写「AI」日志。
流结束时的 usage 用了非阻塞发送,通道一满就被丢掉;很多服务商默认还不在流里带 usage。 现在每次真正打到模型的调用都会: 在本地日志(分类 AI,消息 AI 调用完成)写下 scene / prompt / completion / cached 登录后同步上报到后台日汇总
This commit is contained in:
25
ai.go
25
ai.go
@@ -258,9 +258,7 @@ func (a *App) runAIStream(ctx context.Context, provider ai.Provider, convID int6
|
||||
a.emit("ai:stream", aiStreamEvent{ConversationID: convID, Error: e.Error(), Done: true})
|
||||
return
|
||||
}
|
||||
if usage != nil {
|
||||
go a.reportAIUsage(provider.Name(), usage.PromptTokens, usage.CompletionTokens, usage.Estimated)
|
||||
}
|
||||
a.recordAIUsage(provider.Name(), "chat", usage, msgs, sb.String())
|
||||
a.store.TouchAIConversation(convID, provider.Name())
|
||||
a.emit("ai:stream", aiStreamEvent{ConversationID: convID, Done: true, MessageID: m.ID})
|
||||
}
|
||||
@@ -339,6 +337,21 @@ func (a *App) aiProvider() (ai.Provider, error) {
|
||||
return ai.New(st.AIProvider, key)
|
||||
}
|
||||
|
||||
// recordAIUsage 写入本地「AI」生成记录并上报云端日汇总。usage 为空时按消息粗估,避免生成成功却没有记录。
|
||||
func (a *App) recordAIUsage(provider, scene string, usage *ai.Usage, msgs []ai.Message, completion string) {
|
||||
if usage == nil {
|
||||
usage = ai.EstimateUsage(msgs, completion)
|
||||
}
|
||||
cache := "none"
|
||||
if usage.CachedTokens > 0 {
|
||||
cache = fmt.Sprintf("hit:%d", usage.CachedTokens)
|
||||
}
|
||||
a.store.Log("info", "AI", "AI 调用完成", fmt.Sprintf(
|
||||
"scene=%s provider=%s prompt=%d completion=%d cached=%d cache=%s estimated=%v",
|
||||
scene, provider, usage.PromptTokens, usage.CompletionTokens, usage.CachedTokens, cache, usage.Estimated))
|
||||
go a.reportAIUsage(provider, usage.PromptTokens, usage.CompletionTokens, usage.CachedTokens, usage.Estimated)
|
||||
}
|
||||
|
||||
// aiSummarySem 全局串行生成,避免批量分析后并发打爆免费额度接口。
|
||||
var aiSummarySem = make(chan struct{}, 1)
|
||||
|
||||
@@ -501,6 +514,7 @@ func (a *App) generateAISummaries(projectID int64, kinds ...string) {
|
||||
// 无数据的全局简报直接落固定文案,不浪费一次注定编造的模型调用。
|
||||
if content, ok := a.staticDayBrief(kind, st.Locale); ok {
|
||||
if e := a.store.SaveAISummary(projectID, kind, "system", content); e == nil {
|
||||
a.store.Log("info", "AI", "AI 调用完成", fmt.Sprintf("scene=summary:%s cache=local provider=system", kind))
|
||||
a.emit("ai:summary", aiSummaryEvent{ProjectID: projectID, Kind: kind})
|
||||
}
|
||||
continue
|
||||
@@ -518,8 +532,12 @@ func (a *App) generateAISummaries(projectID int64, kinds ...string) {
|
||||
continue
|
||||
}
|
||||
var sb strings.Builder
|
||||
var usage *ai.Usage
|
||||
var streamErr error
|
||||
for chunk := range stream {
|
||||
if chunk.Usage != nil {
|
||||
usage = chunk.Usage
|
||||
}
|
||||
if chunk.Err != nil {
|
||||
streamErr = chunk.Err
|
||||
break
|
||||
@@ -536,6 +554,7 @@ func (a *App) generateAISummaries(projectID int64, kinds ...string) {
|
||||
a.emit("ai:summary", aiSummaryEvent{ProjectID: projectID, Kind: kind, Error: msg})
|
||||
continue
|
||||
}
|
||||
a.recordAIUsage(provider.Name(), "summary:"+kind, usage, msgs, sb.String())
|
||||
if e := a.store.SaveAISummary(projectID, kind, provider.Name(), dedentCommon(stripFence(strings.TrimSpace(sb.String())))); e != nil {
|
||||
a.store.Log("warning", "AI", "AI 模块介绍保存失败", e.Error())
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user