新增 internal/logging,slog、标准库 log、GORM 与 Gin 访问日志共用同一 Handler,由 CANVAS_LOG_LEVEL / CANVAS_LOG_FORMAT 控制。info 级别不再输出成功的查询类请求、成功 SQL、message_end 等 Agent 流式事件和常规画布自动保存;4xx/5xx、慢请求、慢 SQL、SQL 失败与 panic 仍会记录,SQL 不内联参数值。
124 lines
3.5 KiB
Go
124 lines
3.5 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log/slog"
|
|
|
|
"infinite-canvas/backend/internal/model"
|
|
)
|
|
|
|
func cloudAgentCallSnapshotHash(call cloudAgentCall) string {
|
|
var args struct {
|
|
SnapshotHash string `json:"snapshotHash"`
|
|
}
|
|
if err := json.Unmarshal([]byte(call.Function.Arguments), &args); err != nil {
|
|
return ""
|
|
}
|
|
return args.SnapshotHash
|
|
}
|
|
|
|
func cloudAgentCaptureStepSnapshotHash(calls []cloudAgentCall) string {
|
|
for _, call := range calls {
|
|
if hash := cloudAgentCallSnapshotHash(call); hash != "" {
|
|
return hash
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func cloudAgentRewriteCallSnapshotHash(call cloudAgentCall, hash string) cloudAgentCall {
|
|
args := map[string]any{}
|
|
if err := json.Unmarshal([]byte(call.Function.Arguments), &args); err != nil {
|
|
return call
|
|
}
|
|
args["snapshotHash"] = hash
|
|
raw, err := json.Marshal(args)
|
|
if err != nil {
|
|
return call
|
|
}
|
|
call.Function.Arguments = string(raw)
|
|
return call
|
|
}
|
|
|
|
// cloudAgentRefreshStepSnapshotHash 把同一轮里后续写操作的 snapshotHash 接到当前画布上。
|
|
// 只放宽同一轮内、且哈希仍等于本轮读时基线的调用;跨轮和模型自己换过哈希的调用仍走原校验。
|
|
// generate_media 漏传快照时补当前媒体内容哈希,避免写画布后还要再读一轮。
|
|
func (s *Service) cloudAgentRefreshStepSnapshotHash(run *model.CloudAgentExecution, state *cloudAgentRuntime, call cloudAgentCall) cloudAgentCall {
|
|
if state == nil || state.CallIndex <= 0 || len(state.Calls) == 0 || !cloudAgentWrite(call.Function.Name) {
|
|
return call
|
|
}
|
|
var current struct {
|
|
SnapshotHash string `json:"snapshotHash"`
|
|
}
|
|
if err := json.Unmarshal([]byte(call.Function.Arguments), ¤t); err != nil {
|
|
return call
|
|
}
|
|
canvas, err := s.repo.CanvasProjectForUser(run.UserID, state.Request.CanvasID)
|
|
if err != nil {
|
|
return call
|
|
}
|
|
doc, err := creationDocument(canvas.PayloadJSON)
|
|
if err != nil {
|
|
return call
|
|
}
|
|
if current.SnapshotHash == "" {
|
|
if call.Function.Name != "generate_media" {
|
|
return call
|
|
}
|
|
latest := cloudAgentMediaContentHash(doc)
|
|
if latest == "" {
|
|
return call
|
|
}
|
|
call = cloudAgentRewriteCallSnapshotHash(call, latest)
|
|
slog.Debug("agent step hash filled", "run", run.ID, "step", state.Step, "index", state.CallIndex, "tool", call.Function.Name)
|
|
return call
|
|
}
|
|
baseline := state.StepSnapshotHash
|
|
if baseline == "" || current.SnapshotHash != baseline {
|
|
return call
|
|
}
|
|
latest := cloudAgentCanvasHash(doc)
|
|
if latest == "" || latest == current.SnapshotHash {
|
|
return call
|
|
}
|
|
latestMutation, err := s.repo.LatestCloudAgentCanvasMutationForCanvas(run.UserID, state.Request.CanvasID)
|
|
if err != nil || latestMutation.RunID != run.ID || latestMutation.AfterSnapshotHash != latest {
|
|
return call
|
|
}
|
|
chain, err := s.repo.CloudAgentCanvasMutationChain(run.UserID, run.ID, state.Request.CanvasID)
|
|
if err != nil {
|
|
return call
|
|
}
|
|
stepIDs := map[string]bool{}
|
|
for _, candidate := range state.Calls {
|
|
if candidate.ID != "" {
|
|
stepIDs[candidate.ID] = true
|
|
}
|
|
}
|
|
expected := baseline
|
|
advanced := false
|
|
for _, mutation := range chain {
|
|
if !stepIDs[mutation.StepID] {
|
|
continue
|
|
}
|
|
if !advanced {
|
|
if mutation.BeforeSnapshotHash != expected {
|
|
continue
|
|
}
|
|
advanced = true
|
|
} else if mutation.BeforeSnapshotHash != expected {
|
|
return call
|
|
}
|
|
expected = mutation.AfterSnapshotHash
|
|
if expected == latest {
|
|
break
|
|
}
|
|
}
|
|
if !advanced || expected != latest {
|
|
return call
|
|
}
|
|
call = cloudAgentRewriteCallSnapshotHash(call, latest)
|
|
slog.Debug("agent step hash refreshed", "run", run.ID, "step", state.Step, "index", state.CallIndex, "tool", call.Function.Name)
|
|
return call
|
|
}
|