ci / vet-and-test (pull_request) Successful in 40s
Why: User rulings 4/5 plus the P2-P4 live evidence. Keyword scoring dimensions (readability, actionability) kept failing valid documents - normal Chinese section headings flagged as column titles, loops of 4 corrections - because they grade writing preferences, not verifiable facts. Unverified numbers (~10-13 per run) remained the largest quality leak through every phase. And the verifier runs on the same model as the worker (lightweight falls back to chat), so "independent" verification was self-review. Separately, svc seeding after boot never registered the tavily tools until a restart because keys were snapshotted at startup. Changes: quality.HardFacts (lint actually ran + zero errors) replaces the full ScoreBytes projection in CheckArtifacts - keyword dimensions remain in ScoreBytes for eval visibility only. New fact-consistency check (runtime.factCheck, default on): extract percentages/dates/large numbers from deliverable text, trace each against the material corpus (attachment text + tool evidence); misses get exactly one correction round (fix, drop, or mark as target/estimate), never a loop. runVerifier skips same-source model verification unless runtime.verifierSameModel is set (hard facts + fact check still run). Search tools now register lazily, reading tavily keys per call (settings/seeding take effect immediately). Evidence: SELF - TestHardFactsIgnoresKeywordDimensions, TestHardFactsKeepsLintUnavailableBlocking, TestUnverifiedFactsAndNotice, TestFactCheckContinuationOnce (fact note fires, corrected run passes); go vet + go test ./... pass; live before/after on the golden task set recorded on the phase05 task card per the policy clause. Impact: Gate stops rejecting valid documents on stylistic grounds; invented figures get one forced correction round; same-model verification no longer pretends to be independent; tavily config is hot. No contract or storage changes.
99 lines
3.2 KiB
Go
99 lines
3.2 KiB
Go
package squadadapter
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
osexec "os/exec"
|
|
"strings"
|
|
"testing"
|
|
|
|
nativetools "agent-native/runtime/tools"
|
|
|
|
agentllm "agent-native/runtime/llm"
|
|
nativeadp "agent-squad/adapters/native"
|
|
"agent-system-backend/internal/skills"
|
|
core "agent-system-core"
|
|
)
|
|
|
|
type orchFakeRc struct {
|
|
subagentFakeRc
|
|
dir string
|
|
}
|
|
|
|
func (f *orchFakeRc) AskHITL(card core.HITLCard) (core.HITLResponse, error) {
|
|
if len(card.Choices) > 0 {
|
|
return core.HITLResponse{ChoiceID: card.Choices[0].ID}, nil
|
|
}
|
|
return core.HITLResponse{ChoiceID: "approve"}, nil
|
|
}
|
|
|
|
// TestOrchestrateRunsOnKernelStub Q3 WP2 行为面:agent-system 退役后,
|
|
// Orchestrate 的四阶段骨架经 agent-native 内核(桩 Stream + write_file 落盘
|
|
// 交付物 + 种子技能 + Exec 白名单)跑通并产出交付物工件。
|
|
func TestOrchestrateRunsOnKernelStub(t *testing.T) {
|
|
skillsRoot := t.TempDir()
|
|
if _, serr := skills.Seed(skillsRoot); serr != nil {
|
|
t.Fatal(serr)
|
|
}
|
|
saved := AgentNativeOptions
|
|
AgentNativeOptions = func(Ctx) (nativeadp.Config, bool) {
|
|
py, _ := osexec.LookPath("python3")
|
|
return nativeadp.Config{DefaultModel: "stub", SkillsRoots: []string{skillsRoot},
|
|
Exec: &nativetools.ExecConfig{Bash: "/bin/bash", Python: py}, Stream: stubStream("# 交付\n\n这是内核桩后端产出的步骤交付物正文,长度超过五十个字符以满足最小质检要求。")}, true
|
|
}
|
|
defer func() { AgentNativeOptions = saved }()
|
|
rc := &orchFakeRc{dir: t.TempDir()}
|
|
dataDir := t.TempDir()
|
|
summary, err := Orchestrate(rc, dataDir, "生成一份示例目标简报")
|
|
t.Logf("summary = %q", summary)
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
if !strings.Contains(summary, "completed") || !strings.Contains(summary, "完成 4") {
|
|
t.Fatalf("summary = %q", summary)
|
|
}
|
|
}
|
|
|
|
func stubStream(text string) agentllm.StreamFunc {
|
|
return func(_ context.Context, _ string, msgs []agentllm.Message, _ []agentllm.ToolDecl, _ agentllm.StreamOptions) <-chan agentllm.Event {
|
|
ch := make(chan agentllm.Event, 8)
|
|
go func() {
|
|
defer close(ch)
|
|
finish := func() {
|
|
ch <- agentllm.Event{Kind: agentllm.KindTextDelta, Text: text}
|
|
ch <- agentllm.Event{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, Content: text}}
|
|
}
|
|
prompt := ""
|
|
for _, m := range msgs {
|
|
if m.Role == agentllm.RoleTool {
|
|
finish()
|
|
return
|
|
}
|
|
if m.Role == agentllm.RoleUser {
|
|
prompt = m.Content
|
|
}
|
|
}
|
|
i := strings.Index(prompt, "期望产出:[")
|
|
if i < 0 {
|
|
finish()
|
|
return
|
|
}
|
|
j := strings.Index(prompt[i:], "]")
|
|
if j < 0 {
|
|
finish()
|
|
return
|
|
}
|
|
targets := strings.Fields(prompt[i+len("期望产出:[") : i+j])
|
|
var calls []agentllm.ToolCall
|
|
for k, tgt := range targets {
|
|
args := fmt.Sprintf(`{"path":%q,"content":%q}`, tgt, text)
|
|
id := fmt.Sprintf("call_stub_%d", k+1)
|
|
ch <- agentllm.Event{Kind: agentllm.KindToolCallDelta, ToolIndex: k, ToolID: id, ToolName: "write_file", ArgumentsDelta: args}
|
|
calls = append(calls, agentllm.ToolCall{ID: id, Name: "write_file", Arguments: args})
|
|
}
|
|
ch <- agentllm.Event{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, ToolCalls: calls, StopReason: agentllm.StopToolCalls}}
|
|
}()
|
|
return ch
|
|
}
|
|
}
|