173 lines
9.1 KiB
Go
173 lines
9.1 KiB
Go
package extraction
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"data-terminal-cloud/internal/outbox"
|
|
)
|
|
|
|
func TestArkGeneratorProducesValidatedContent(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("Authorization") != "Bearer test-key" || r.URL.Path != "/api/v3/chat/completions" {
|
|
t.Fatalf("unexpected Ark request path or authorization")
|
|
}
|
|
var body map[string]any
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body["model"] != "doubao-test" {
|
|
t.Fatalf("request body=%v err=%v", body, err)
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"message": map[string]any{"content": `{"meaningful":true,"story_worthy":true,"decision_reason":"包含具体的童年求学回忆","summary":"谈到河边小学","keywords":["河边小学","童年"],"open_topic":"下次可以聊聊同学","story_title":"河边小学的回忆","story_content":"小时候在河边小学读书。","fact_content":"曾在河边小学读书","confidence":0.82}`}}}})
|
|
}))
|
|
defer server.Close()
|
|
generator, err := NewArkGenerator(server.URL+"/api/v3", "test-key", "doubao-test", time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := generator.Generate(context.Background(), GenerationInput{Content: "小时候在河边小学读书。"})
|
|
if err != nil || result.ModelRef != "ark:doubao-test" || result.Confidence != 0.82 || !result.AllowStory || len(result.Keywords) != 2 {
|
|
t.Fatalf("result=%+v err=%v", result, err)
|
|
}
|
|
}
|
|
|
|
func TestCompatibleGeneratorDiscardsSmallTalk(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"message": map[string]any{"content": `{"meaningful":false,"story_worthy":false,"decision_reason":"只有问候和确认","summary":"","keywords":[],"open_topic":"","story_title":"","story_content":"","fact_content":"","confidence":0.96}`}}}})
|
|
}))
|
|
defer server.Close()
|
|
generator, err := NewCompatibleGenerator("openai_compatible", server.URL+"/v1", "local-key", "qwen-test", 0.1, 512, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := generator.Generate(context.Background(), GenerationInput{Content: "豆包:您好。\n长者:嗯,你好。\n豆包:今天怎么样?\n长者:还行。"})
|
|
if err != nil || result.Meaningful || result.StoryWorthy || result.AllowStory || result.DecisionReason == "" {
|
|
t.Fatalf("result=%+v err=%v", result, err)
|
|
}
|
|
}
|
|
|
|
func TestCompatibleGeneratorKeepsMeaningfulNonStoryWithoutDraft(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"message": map[string]any{"content": `{"meaningful":true,"story_worthy":false,"decision_reason":"有明确近况但没有事件脉络","summary":"长者今天胃口不错","keywords":["胃口","近况"],"open_topic":"下次可以聊聊最近喜欢的饭菜","story_title":"","story_content":"","fact_content":"","confidence":0.87}`}}}})
|
|
}))
|
|
defer server.Close()
|
|
generator, err := NewCompatibleGenerator("openai_compatible", server.URL+"/v1", "local-key", "qwen-test", 0.1, 512, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := generator.Generate(context.Background(), GenerationInput{Content: "长者:我今天胃口挺好的,中午吃了面条。"})
|
|
if err != nil || !result.Meaningful || result.StoryWorthy || result.AllowStory || result.Summary == "" {
|
|
t.Fatalf("result=%+v err=%v", result, err)
|
|
}
|
|
}
|
|
|
|
func TestCompatibleGeneratorAllowsMeaningfulContentWithoutForcedOpenTopic(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"message": map[string]any{"content": `{"meaningful":true,"story_worthy":false,"decision_reason":"记录了明确的生活偏好,但没有自然的未完话题","summary":"长者喜欢安静地听越剧","keywords":["越剧","偏好"],"open_topic":"","story_title":"","story_content":"","fact_content":"","confidence":0.88}`}}}})
|
|
}))
|
|
defer server.Close()
|
|
generator, err := NewCompatibleGenerator("openai_compatible", server.URL+"/v1", "local-key", "qwen-test", 0.1, 512, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := generator.Generate(context.Background(), GenerationInput{Content: "长者:我平时喜欢安静地听越剧。"})
|
|
if err != nil || !result.Meaningful || result.OpenTopic != "" {
|
|
t.Fatalf("result=%+v err=%v", result, err)
|
|
}
|
|
}
|
|
|
|
func TestCompatibleGeneratorClearsContradictoryStoryFields(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"message": map[string]any{"content": `{"meaningful":true,"story_worthy":false,"decision_reason":"只是近况","summary":"今天胃口不错","keywords":["胃口"],"open_topic":"最近喜欢吃什么?","story_title":"模型误填标题","story_content":"模型误填故事","fact_content":"模型误填事实","confidence":0.8}`}}}})
|
|
}))
|
|
defer server.Close()
|
|
generator, err := NewCompatibleGenerator("openai_compatible", server.URL+"/v1", "local-key", "qwen-test", 0.1, 512, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := generator.Generate(context.Background(), GenerationInput{Content: "长者:今天胃口不错。"})
|
|
if err != nil || result.AllowStory || result.StoryTitle != "" || result.StoryContent != "" || result.FactContent != "" {
|
|
t.Fatalf("result=%+v err=%v", result, err)
|
|
}
|
|
}
|
|
|
|
func TestCompatibleGeneratorHandlesThinkingWrapper(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"message": map[string]any{"content": "<think>先判断内容价值</think>\n```json\n{\"meaningful\":false,\"story_worthy\":false,\"decision_reason\":\"只有问候\",\"summary\":\"\",\"keywords\":[],\"open_topic\":\"\",\"story_title\":\"\",\"story_content\":\"\",\"fact_content\":\"\",\"confidence\":0.95}\n```"}}}})
|
|
}))
|
|
defer server.Close()
|
|
generator, err := NewCompatibleGenerator("minimax", server.URL+"/v1", "local-key", "MiniMax-test", 0.1, 512, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := generator.Generate(context.Background(), GenerationInput{Content: "您好。"})
|
|
if err != nil || result.Meaningful || result.ModelRef != "minimax:MiniMax-test" {
|
|
t.Fatalf("result=%+v err=%v", result, err)
|
|
}
|
|
}
|
|
|
|
func TestCompatibleGeneratorOmitsUndocumentedJSONMode(t *testing.T) {
|
|
for _, provider := range []string{"moonshot", "zhipu", "qianfan", "hunyuan", "minimax", "baichuan"} {
|
|
t.Run(provider, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var body map[string]any
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, exists := body["response_format"]; exists {
|
|
t.Fatalf("%s request unexpectedly enabled JSON mode", provider)
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"message": map[string]any{"content": `{"meaningful":false,"story_worthy":false,"decision_reason":"测试内容","summary":"","keywords":[],"open_topic":"","story_title":"","story_content":"","fact_content":"","confidence":0.9}`}}}})
|
|
}))
|
|
defer server.Close()
|
|
generator, err := NewCompatibleGenerator(provider, server.URL, "test-key", "test-model", 0.2, 512, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := generator.Generate(context.Background(), GenerationInput{Content: "模型连接测试"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArkGeneratorRetriesTemporaryFailure(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
http.Error(w, "busy", http.StatusTooManyRequests)
|
|
}))
|
|
defer server.Close()
|
|
generator, err := NewArkGenerator(server.URL, "test-key", "doubao-test", time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = generator.Generate(context.Background(), GenerationInput{Content: "测试内容"})
|
|
if !outbox.IsRetryable(err) {
|
|
t.Fatalf("429 should be retryable: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeterministicGeneratorIncludesKeywords(t *testing.T) {
|
|
result, err := (DeterministicGenerator{}).Generate(context.Background(), GenerationInput{Content: "小时候在河边小学读书,最喜欢和同学一起踢球。"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(result.Keywords) < 1 || len(result.Keywords) > 8 {
|
|
t.Fatalf("keywords=%v", result.Keywords)
|
|
}
|
|
formatted := summaryWithKeywords(result.Summary, result.Keywords)
|
|
if formatted == result.Summary {
|
|
t.Fatalf("summary did not include keywords: %q", formatted)
|
|
}
|
|
}
|
|
|
|
func TestRequestedGenerationVersion(t *testing.T) {
|
|
if got := requestedGenerationVersion(json.RawMessage(`{"generation_version":7}`)); got != 7 {
|
|
t.Fatalf("generation version=%d", got)
|
|
}
|
|
if got := requestedGenerationVersion(json.RawMessage(`{}`)); got != 1 {
|
|
t.Fatalf("default generation version=%d", got)
|
|
}
|
|
}
|