feat: 实现采购运行时规格解析 (#255)

This commit is contained in:
chengma
2026-08-17 17:20:07 +08:00
parent a699ec8132
commit de36cfd0b5
10 changed files with 1347 additions and 42 deletions
+16 -32
View File
@@ -153,55 +153,39 @@ func MatchSybSpecWithAI(ctx context.Context, db *sql.DB, actor *model.User, snap
}
bindings := bindAICandidates(eligible)
baseDecision.CandidatesJSON = candidateAuditJSON(bindings)
if snapshot.Client == nil || snapshot.Secret == "" || snapshot.Provider.ProviderID == "" {
return recordAIMatchWithoutSave(db, baseDecision, "failed", "AI 服务商运行快照不可用")
}
request := AIModelMatchRequest{ProductTitle: truncateRunes(orderContext.Order.Title, 300), SourceSpec: truncateRunes(orderContext.Order.ProductSpec, 300)}
for _, binding := range bindings {
request.Candidates = append(request.Candidates, AIModelCandidate{ID: binding.ID,
Label: truncateRunes(binding.Choice.Label, 300), Options: boundedAIOptions(binding.Choice.Options)})
}
response, modelErr := snapshot.Client.Match(ctx, snapshot.Provider, snapshot.Secret, request)
if modelErr != nil {
result, auditErr := recordAIMatchWithoutSave(db, baseDecision, "failed", safeAIError(modelErr))
result.ModelCalled = true
return result, auditErr
}
if err := validateAIModelMatchResponse(response); err != nil {
result, auditErr := recordAIMatchWithoutSave(db, baseDecision, "failed", err.Error())
result.ModelCalled = true
return result, auditErr
}
selection := selectAIWhitelistedCandidate(ctx, snapshot, request)
response := selection.Response
baseDecision.ChosenCandidateID = response.CandidateID
baseDecision.ConfidenceBPS = response.ConfidenceBPS
baseDecision.ConfidenceSet = true
baseDecision.Reason = response.Reason
baseDecision.ConfidenceSet = selection.ConfidenceSet
baseDecision.Reason = selection.Reason
baseDecision.ConflictDimensionsJSON = stringArrayJSON(response.ConflictDimensions)
baseDecision.MissingDimensionsJSON = stringArrayJSON(response.MissingDimensions)
if response.Conclusion != "match" {
result, auditErr := recordAIMatchWithoutSave(db, baseDecision, "rejected", response.Reason)
result.ModelCalled = true
if selection.Status != aiSelectionMatched {
outcome := "rejected"
if selection.Status == aiSelectionFailed {
outcome = "failed"
}
result, auditErr := recordAIMatchWithoutSave(db, baseDecision, outcome, selection.Reason)
result.ModelCalled = selection.ModelCalled
return result, auditErr
}
selected, found := bindingByID(bindings, response.CandidateID)
if !found {
result, auditErr := recordAIMatchWithoutSave(db, baseDecision, "rejected", "模型返回了不在候选白名单中的编号")
result.ModelCalled = true
// selectAIWhitelistedCandidate 已核对过同一 request;此分支只防以后
// 绑定构造被改坏时把错误候选写进真实映射。
result, auditErr := recordAIMatchWithoutSave(db, baseDecision, "rejected", "候选绑定已变化,未保存映射")
result.ModelCalled = selection.ModelCalled
return result, auditErr
}
baseDecision.ChosenOptionKey = selected.Choice.Key
if len(response.ConflictDimensions) > 0 || len(response.MissingDimensions) > 0 {
result, auditErr := recordAIMatchWithoutSave(db, baseDecision, "rejected", "模型报告仍有冲突或缺失维度")
result.ModelCalled = true
return result, auditErr
}
if response.ConfidenceBPS < snapshot.Provider.ConfidenceThresholdBPS {
result, auditErr := recordAIMatchWithoutSave(db, baseDecision, "rejected", "模型置信度低于管理员设置的自动写入阈值")
result.ModelCalled = true
return result, auditErr
}
result, err := saveAutomaticMapping(db, *actor, *orderContext, selected.Choice, "ai", baseDecision)
result.ModelCalled = true
result.ModelCalled = selection.ModelCalled
return result, err
}