Files
Agent-System-Backend/internal/intentsem/intentsem_test.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:47:16 +00:00

150 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package intentsem
import (
"context"
"errors"
"strings"
"testing"
agentintent "git-ssh.u1680832.nyat.app/weichen/Agent-Intent.git/intent"
agentllm "git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime/llm"
)
// fakeEmbedder 返回脚本化向量:texts[i] → vecs[i](循环取模)。
type fakeEmbedder struct {
vecs [][]float32
err error
}
func (f fakeEmbedder) Embed(_ context.Context, texts []string) ([][]float32, error) {
if f.err != nil {
return nil, f.err
}
out := make([][]float32, len(texts))
for i := range texts {
out[i] = f.vecs[i%len(f.vecs)]
}
return out, nil
}
func TestEmbeddingScorerMaxCosine(t *testing.T) {
// input 与 example2 同向(相似度 1),与 example1 正交(0)。
s := EmbeddingScorer{Provider: fakeEmbedder{vecs: [][]float32{
{1, 0}, {0, 1}, {2, 0},
}}}
got, err := s.Score(context.Background(), "输入", []string{"例一", "例二"})
if err != nil {
t.Fatal(err)
}
if got != 1 {
t.Fatalf("期望最大余弦 1,得到 %v", got)
}
}
func TestEmbeddingScorerErrorPropagates(t *testing.T) {
s := EmbeddingScorer{Provider: fakeEmbedder{err: errors.New("boom")}}
if _, err := s.Score(context.Background(), "x", []string{"y"}); err == nil {
t.Fatal("期望错误上抛(库侧保持 Ambiguous)")
}
}
func TestEmbeddingScorerEmptyExamples(t *testing.T) {
s := EmbeddingScorer{Provider: fakeEmbedder{vecs: [][]float32{{1}}}}
got, err := s.Score(context.Background(), "x", nil)
if err != nil || got != 0 {
t.Fatalf("空示例应得 0 分无错误,得到 %v / %v", got, err)
}
}
// scriptedStream 回放固定文本增量后关闭通道。
func scriptedStream(text string, err error) agentllm.StreamFunc {
return func(_ context.Context, _ string, msgs []agentllm.Message, _ []agentllm.ToolDecl, _ agentllm.StreamOptions) <-chan agentllm.Event {
ch := make(chan agentllm.Event, 3)
if err != nil {
ch <- agentllm.Event{Kind: agentllm.KindError, Err: err}
} else {
ch <- agentllm.Event{Kind: agentllm.KindTextDelta, Text: text}
}
close(ch)
return ch
}
}
func catalogFixture() []agentintent.CatalogSpec {
return []agentintent.CatalogSpec{
{ID: "deploy.run", Description: "部署应用", Slots: []agentintent.CatalogSlot{{Key: "env", Required: true}}},
}
}
func TestLightGeneratorParsesCommands(t *testing.T) {
g := LightGenerator{
Stream: scriptedStream(" ```json\n[{\"kind\":\"start\",\"action\":{\"kind\":\"workflow\",\"id\":\"deploy.run\"},\"slots\":{\"env\":\"prod\"}}]\n``` ", nil),
Model: "lite",
}
cmds, err := g.Generate(context.Background(), "把应用部署到生产", catalogFixture())
if err != nil {
t.Fatal(err)
}
if len(cmds) != 1 || cmds[0].Kind != agentintent.CommandStart {
t.Fatalf("期望 1 条 start 命令,得到 %+v", cmds)
}
if cmds[0].Action.Kind != "workflow" || cmds[0].Action.ID != "deploy.run" {
t.Fatalf("action 不符:%+v", cmds[0].Action)
}
if cmds[0].Slots["env"] != "prod" {
t.Fatalf("槽位不符:%+v", cmds[0].Slots)
}
}
func TestLightGeneratorEmptyCatalogNoCall(t *testing.T) {
called := false
g := LightGenerator{Stream: func(_ context.Context, _ string, _ []agentllm.Message, _ []agentllm.ToolDecl, _ agentllm.StreamOptions) <-chan agentllm.Event {
called = true
ch := make(chan agentllm.Event)
close(ch)
return ch
}}
cmds, err := g.Generate(context.Background(), "你好", nil)
if err != nil || cmds != nil || called {
t.Fatalf("空目录不应调用模型:cmds=%v err=%v called=%v", cmds, err, called)
}
}
func TestLightGeneratorStreamError(t *testing.T) {
g := LightGenerator{Stream: scriptedStream("", errors.New("dial refused"))}
if _, err := g.Generate(context.Background(), "部署", catalogFixture()); err == nil ||
!strings.Contains(err.Error(), "dial refused") {
t.Fatalf("期望流错误上抛,得到 %v", err)
}
}
func TestLightGeneratorBadJSON(t *testing.T) {
g := LightGenerator{Stream: scriptedStream("我无法判断", nil)}
if _, err := g.Generate(context.Background(), "部署", catalogFixture()); err == nil {
t.Fatal("期望解析失败返回错误(库侧 fail-closed 回 fallback)")
}
}
func TestParseCommandsFencedAndChatty(t *testing.T) {
raw := "好的,命令如下:\n```json\n[{\"kind\":\"chitchat\"}]\n```\n以上。"
cmds, err := parseCommands(raw)
if err != nil {
t.Fatal(err)
}
if len(cmds) != 1 || cmds[0].Kind != agentintent.CommandChitchat {
t.Fatalf("期望 chitchat,得到 %+v", cmds)
}
}
func TestGeneratorPromptContainsCatalogOnly(t *testing.T) {
p := generatorSystemPrompt(catalogFixture())
for _, want := range []string{"deploy.run", "部署应用", "env"} {
if !strings.Contains(p, want) {
t.Fatalf("提示词缺少 %q", want)
}
}
if strings.Contains(p, "正例") || strings.Contains(p, "examples") {
t.Fatal("提示词不应包含示例文本(注入面)")
}
}