533db535f1
build / build (push) Successful in 1m12s
按已确认需求提供独立安全模型配置、真实探测、正式字典字段评估与原问题译中,持久批次支持只补空白、暂停恢复、有限重试、幂等和逐字段依据。覆盖固定规则、本地语言、组织关联与主表有效看板共用治理侧读模型,保留源生产和冻结历史。 验证:完整 make verify 退出0,702项Go与329项前端通过;默认118项外部/隔离集成明确跳过,另跑完整镜像及组织隔离PG竞态。12并发、输入漂移、过期租约、暂停/恢复及有限重试通过。用户授权的百炼 deepseek-v4.1-flash 完成真实后台与浏览器评估/翻译;552行、16列、11块看板及完整PNG、独立配置页测试/保存、重开恢复和零JS异常通过。仅非业务样本外发,自动处理关闭,原payload不变。 上线前治理库已做导出快照原生备份并真实恢复,60表行数与17个QA表内容哈希一致;不修改DobotAI生产或隧道。本提交时尚未部署,线上验收须独立完成。
141 lines
5.5 KiB
Go
141 lines
5.5 KiB
Go
package qaopsai
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
)
|
|
|
|
type WorkState struct {
|
|
ID string `json:"id"`
|
|
RecordID string `json:"record_id"`
|
|
Kind string `json:"kind"`
|
|
State string `json:"state"`
|
|
Stage string `json:"stage"`
|
|
Attempt int `json:"attempt"`
|
|
ErrorCode string `json:"error_code"`
|
|
SkipReason string `json:"skip_reason"`
|
|
StartedAt *time.Time `json:"started_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
type QueueCount struct {
|
|
Kind string `json:"kind"`
|
|
Queued int `json:"queued"`
|
|
Running int `json:"running"`
|
|
Retry int `json:"retry"`
|
|
Failed int `json:"failed"`
|
|
Paused int `json:"paused"`
|
|
Succeeded int `json:"succeeded"`
|
|
Skipped int `json:"skipped"`
|
|
}
|
|
type Status struct {
|
|
Settings Settings `json:"settings"`
|
|
WorkerReady bool `json:"worker_ready"`
|
|
Queues []QueueCount `json:"queues"`
|
|
Batches []Batch `json:"batches"`
|
|
RecentTasks []WorkState `json:"recent_tasks"`
|
|
RecentTasksLimited bool `json:"recent_tasks_limited"`
|
|
}
|
|
|
|
func (s *Store) GetStatus(ctx context.Context) (Status, error) {
|
|
out := Status{Queues: []QueueCount{}, Batches: []Batch{}, RecentTasks: []WorkState{}}
|
|
cfg, err := s.GetSettings(ctx)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
out.Settings = cfg
|
|
if s.pool.QueryRow(ctx, `SELECT COALESCE((SELECT heartbeat_at>clock_timestamp()-interval '30 seconds' OR lease_until>clock_timestamp() FROM qa_ops_ai_runtime WHERE tenant_key=$1),false)`, s.tenant).Scan(&out.WorkerReady) != nil {
|
|
return out, ErrUnavailable
|
|
}
|
|
for _, kind := range []string{"evaluation", "translation"} {
|
|
q := QueueCount{Kind: kind}
|
|
if s.pool.QueryRow(ctx, `SELECT count(*)FILTER(WHERE state='queued'),count(*)FILTER(WHERE state='running'),count(*)FILTER(WHERE state='retry'),count(*)FILTER(WHERE state='failed'),count(*)FILTER(WHERE state='paused'),count(*)FILTER(WHERE state='succeeded'),count(*)FILTER(WHERE state='skipped') FROM qa_ops_ai_work WHERE tenant_key=$1 AND kind=$2`, s.tenant, kind).Scan(&q.Queued, &q.Running, &q.Retry, &q.Failed, &q.Paused, &q.Succeeded, &q.Skipped) != nil {
|
|
return out, ErrUnavailable
|
|
}
|
|
out.Queues = append(out.Queues, q)
|
|
}
|
|
rows, err := s.pool.Query(ctx, `SELECT id FROM qa_ops_ai_batches WHERE tenant_key=$1 ORDER BY created_at DESC,id DESC LIMIT 10`, s.tenant)
|
|
if err != nil {
|
|
return out, ErrUnavailable
|
|
}
|
|
ids := []string{}
|
|
for rows.Next() {
|
|
var id string
|
|
if rows.Scan(&id) != nil {
|
|
rows.Close()
|
|
return out, ErrUnavailable
|
|
}
|
|
ids = append(ids, id)
|
|
}
|
|
err = rows.Err()
|
|
rows.Close()
|
|
if err != nil {
|
|
return out, ErrUnavailable
|
|
}
|
|
for _, id := range ids {
|
|
batch, e := s.GetBatch(ctx, id)
|
|
if e != nil {
|
|
return out, e
|
|
}
|
|
out.Batches = append(out.Batches, batch)
|
|
}
|
|
rows, err = s.pool.Query(ctx, `SELECT id,record_id,kind,state,stage,attempt,error_code,skip_reason,started_at,updated_at FROM qa_ops_ai_work WHERE tenant_key=$1 ORDER BY updated_at DESC,id DESC LIMIT 1001`, s.tenant)
|
|
if err != nil {
|
|
return out, ErrUnavailable
|
|
}
|
|
for rows.Next() {
|
|
var work WorkState
|
|
if rows.Scan(&work.ID, &work.RecordID, &work.Kind, &work.State, &work.Stage, &work.Attempt, &work.ErrorCode, &work.SkipReason, &work.StartedAt, &work.UpdatedAt) != nil {
|
|
rows.Close()
|
|
return out, ErrUnavailable
|
|
}
|
|
out.RecentTasks = append(out.RecentTasks, work)
|
|
}
|
|
err = rows.Err()
|
|
rows.Close()
|
|
if err != nil {
|
|
return out, ErrUnavailable
|
|
}
|
|
if len(out.RecentTasks) > 1000 {
|
|
out.RecentTasks = out.RecentTasks[:1000]
|
|
out.RecentTasksLimited = true
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
type WorkEvidence struct {
|
|
WorkState
|
|
InputHash string `json:"input_hash"`
|
|
ConfigRevision int64 `json:"config_revision"`
|
|
PromptVersion string `json:"prompt_version"`
|
|
DictionaryVersion string `json:"dictionary_version"`
|
|
ActualModel string `json:"actual_model"`
|
|
Result json.RawMessage `json:"result"`
|
|
AppliedFields map[string]string `json:"applied_fields"`
|
|
SkippedFields map[string]string `json:"skipped_fields"`
|
|
}
|
|
|
|
func (s *Store) RecordEvidence(ctx context.Context, recordID string) ([]WorkEvidence, error) {
|
|
if !s.valid() {
|
|
return nil, ErrUnavailable
|
|
}
|
|
rows, err := s.pool.Query(ctx, `SELECT w.id,w.record_id,w.kind,w.state,w.stage,w.attempt,w.error_code,w.skip_reason,w.started_at,w.updated_at,w.input_hash,w.config_revision,w.prompt_version,w.dictionary_version,COALESCE(o.actual_model,''),COALESCE(o.payload,'{}'::jsonb),COALESCE(o.applied_fields,'{}'::jsonb),COALESCE(o.skipped_fields,'{}'::jsonb) FROM qa_ops_ai_work w LEFT JOIN qa_ops_ai_outputs o ON o.tenant_key=w.tenant_key AND o.work_id=w.id WHERE w.tenant_key=$1 AND w.record_id=$2 ORDER BY w.created_at DESC,w.id DESC LIMIT 50`, s.tenant, recordID)
|
|
if err != nil {
|
|
return nil, ErrUnavailable
|
|
}
|
|
defer rows.Close()
|
|
out := []WorkEvidence{}
|
|
for rows.Next() {
|
|
var work WorkEvidence
|
|
var applied, skipped []byte
|
|
if rows.Scan(&work.ID, &work.RecordID, &work.Kind, &work.State, &work.Stage, &work.Attempt, &work.ErrorCode, &work.SkipReason, &work.StartedAt, &work.UpdatedAt, &work.InputHash, &work.ConfigRevision, &work.PromptVersion, &work.DictionaryVersion, &work.ActualModel, &work.Result, &applied, &skipped) != nil || json.Unmarshal(applied, &work.AppliedFields) != nil || json.Unmarshal(skipped, &work.SkippedFields) != nil {
|
|
return nil, ErrUnavailable
|
|
}
|
|
out = append(out, work)
|
|
}
|
|
if rows.Err() != nil {
|
|
return nil, ErrUnavailable
|
|
}
|
|
return out, nil
|
|
}
|