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
+72
View File
@@ -39,6 +39,78 @@ type AIModelClient interface {
Match(context.Context, model.AIProviderConfig, string, AIModelMatchRequest) (AIModelMatchResponse, error)
}
type aiWhitelistedSelectionStatus string
const (
aiSelectionMatched aiWhitelistedSelectionStatus = "matched"
aiSelectionUncertain aiWhitelistedSelectionStatus = "uncertain"
aiSelectionConflict aiWhitelistedSelectionStatus = "conflict"
aiSelectionFailed aiWhitelistedSelectionStatus = "failed"
aiSelectionCandidateRejected aiWhitelistedSelectionStatus = "candidate_rejected"
aiSelectionDimensionsUnresolved aiWhitelistedSelectionStatus = "dimensions_unresolved"
aiSelectionBelowThreshold aiWhitelistedSelectionStatus = "below_threshold"
)
// aiWhitelistedSelection 是 SYB 批量匹配和 Client 真机匹配共用的模型安全结论。
// 业务编排可以把“不确定”显示成不同文案,但不能绕过这里的候选、阈值和维度门禁。
type aiWhitelistedSelection struct {
Status aiWhitelistedSelectionStatus
Response AIModelMatchResponse
Reason string
ConfidenceSet bool
ModelCalled bool
}
func selectAIWhitelistedCandidate(ctx context.Context, snapshot AIMatchSnapshot,
request AIModelMatchRequest) aiWhitelistedSelection {
if snapshot.Client == nil || snapshot.Secret == "" || snapshot.Provider.ProviderID == "" {
return aiWhitelistedSelection{Status: aiSelectionFailed, Reason: "AI 服务商运行快照不可用"}
}
response, err := snapshot.Client.Match(ctx, snapshot.Provider, snapshot.Secret, request)
if err != nil {
reason := safeAIError(err)
if snapshot.Secret != "" {
reason = strings.ReplaceAll(reason, snapshot.Secret, "[REDACTED]")
}
return aiWhitelistedSelection{Status: aiSelectionFailed, Reason: reason, ModelCalled: true}
}
result := aiWhitelistedSelection{Response: response, Reason: response.Reason,
ConfidenceSet: true, ModelCalled: true}
if err := validateAIModelMatchResponse(response); err != nil {
result.Status, result.Reason, result.ConfidenceSet = aiSelectionFailed, err.Error(), false
return result
}
if response.Conclusion == "uncertain" {
result.Status = aiSelectionUncertain
return result
}
if response.Conclusion == "conflict" {
result.Status = aiSelectionConflict
return result
}
allowed := false
for _, candidate := range request.Candidates {
if candidate.ID == response.CandidateID {
allowed = true
break
}
}
if !allowed {
result.Status, result.Reason = aiSelectionCandidateRejected, "模型返回了不在候选白名单中的编号"
return result
}
if len(response.ConflictDimensions) > 0 || len(response.MissingDimensions) > 0 {
result.Status, result.Reason = aiSelectionDimensionsUnresolved, "模型报告仍有冲突或缺失维度"
return result
}
if response.ConfidenceBPS < snapshot.Provider.ConfidenceThresholdBPS {
result.Status, result.Reason = aiSelectionBelowThreshold, "模型置信度低于管理员设置的自动选择阈值"
return result
}
result.Status = aiSelectionMatched
return result
}
type OpenAICompatibleModelClient struct{ doer AIHTTPDoer }
func NewOpenAICompatibleModelClient(doer AIHTTPDoer) *OpenAICompatibleModelClient {