Release v1.4.9 remote image routing
This commit is contained in:
@@ -3,8 +3,11 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"lingma-ipc-proxy/internal/toolemulation"
|
||||
)
|
||||
|
||||
func TestIsRecoverableIPCError(t *testing.T) {
|
||||
@@ -48,3 +51,126 @@ func TestContextWithOptionalTimeoutPositiveSetsDeadline(t *testing.T) {
|
||||
t.Fatal("positive timeout should set a deadline")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLingmaPromptOnlyInjectsToolingWhenEmulationEnabled(t *testing.T) {
|
||||
req := ChatRequest{
|
||||
Messages: []ChatMessage{{Role: "user", Text: "查看项目结构"}},
|
||||
Tools: []toolemulation.ToolDef{{
|
||||
Name: "Bash",
|
||||
InputSchema: map[string]any{
|
||||
"properties": map[string]any{
|
||||
"command": map[string]any{"type": "string"},
|
||||
},
|
||||
"required": []any{"command"},
|
||||
},
|
||||
}},
|
||||
ToolChoice: toolemulation.ToolChoice{Mode: "auto"},
|
||||
}
|
||||
|
||||
remotePrompt, err := buildLingmaPrompt(req, SessionModeFresh, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(remotePrompt, "```json action") || strings.Contains(remotePrompt, "DIRECT tool access") {
|
||||
t.Fatalf("remote prompt should not include tool emulation:\n%s", remotePrompt)
|
||||
}
|
||||
|
||||
ipcPrompt, err := buildLingmaPrompt(req, SessionModeFresh, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(ipcPrompt, "```json action") || !strings.Contains(ipcPrompt, "DIRECT tool access") {
|
||||
t.Fatalf("ipc prompt should include tool emulation:\n%s", ipcPrompt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldRetryRemoteNativeToolForContinuationText(t *testing.T) {
|
||||
req := ChatRequest{
|
||||
Tools: []toolemulation.ToolDef{{Name: "Bash"}},
|
||||
ToolChoice: toolemulation.ToolChoice{
|
||||
Mode: "auto",
|
||||
},
|
||||
}
|
||||
if !shouldRetryRemoteNativeTool(req, "让我查看一下项目的整体结构,特别是源代码目录:") {
|
||||
t.Fatal("expected continuation text to trigger native tool retry")
|
||||
}
|
||||
if shouldRetryRemoteNativeTool(req, "这是一个 uni-app 项目,核心目录是 src。") {
|
||||
t.Fatal("substantive answer should not trigger retry")
|
||||
}
|
||||
req.ToolChoice = toolemulation.ToolChoice{Mode: "none"}
|
||||
if shouldRetryRemoteNativeTool(req, "让我查看一下:") {
|
||||
t.Fatal("tool_choice none should not trigger retry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLingmaPromptKeepsToolResultsForIPC(t *testing.T) {
|
||||
req := ChatRequest{
|
||||
Messages: []ChatMessage{
|
||||
{Role: "user", Text: "查看项目"},
|
||||
{Role: "assistant", ToolCalls: []toolemulation.ToolCall{{ID: "call_1", Name: "Bash", Arguments: map[string]any{"command": "pwd"}}}},
|
||||
{Role: "tool", ToolCallID: "call_1", Text: "/tmp/project"},
|
||||
},
|
||||
Tools: []toolemulation.ToolDef{{Name: "Bash"}},
|
||||
ToolChoice: toolemulation.ToolChoice{Mode: "auto"},
|
||||
}
|
||||
prompt, err := buildLingmaPrompt(req, SessionModeFresh, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(prompt, "Tool result for call_1") || !strings.Contains(prompt, "/tmp/project") {
|
||||
t.Fatalf("ipc prompt should include tool result:\n%s", prompt)
|
||||
}
|
||||
if strings.Contains(prompt, "Assistant used tool") {
|
||||
t.Fatalf("ipc prompt should not include textualized assistant tool calls:\n%s", prompt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteImagesFromRequest(t *testing.T) {
|
||||
req := ChatRequest{Messages: []ChatMessage{{Role: "user", Text: "see", Images: []Image{{MediaType: "image/png", Data: "AAAA"}}}}}
|
||||
images := remoteImagesFromRequest(req)
|
||||
if len(images) != 1 {
|
||||
t.Fatalf("images = %#v", images)
|
||||
}
|
||||
if images[0].MediaType != "image/png" || images[0].Data != "AAAA" {
|
||||
t.Fatalf("unexpected image = %#v", images[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestHasImages(t *testing.T) {
|
||||
if requestHasImages(ChatRequest{Messages: []ChatMessage{{Role: "user", Text: "plain"}}}) {
|
||||
t.Fatal("plain request should not have images")
|
||||
}
|
||||
if !requestHasImages(ChatRequest{Messages: []ChatMessage{{Role: "user", Images: []Image{{URL: "file:///tmp/a.png"}}}}}) {
|
||||
t.Fatal("image URL request should have images")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractLastUserImagesFindsPreviousImageTurn(t *testing.T) {
|
||||
images := extractLastUserImages([]ChatMessage{
|
||||
{Role: "user", Text: "看这张图", Images: []Image{{URL: "file:///tmp/a.png"}}},
|
||||
{Role: "assistant", Text: "这是一张图片"},
|
||||
{Role: "user", Text: "继续基于上图分析"},
|
||||
})
|
||||
if len(images) != 1 || images[0].URL != "file:///tmp/a.png" {
|
||||
t.Fatalf("images = %#v", images)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestWithImageContextRemovesImagesAndAppendsContext(t *testing.T) {
|
||||
req := ChatRequest{
|
||||
Messages: []ChatMessage{
|
||||
{Role: "user", Text: "看图", Images: []Image{{URL: "file:///tmp/a.png"}}},
|
||||
{Role: "assistant", Text: "好的"},
|
||||
{Role: "user", Text: "继续分析"},
|
||||
},
|
||||
}
|
||||
out := requestWithImageContext(req, "海边礁石和海浪")
|
||||
for _, message := range out.Messages {
|
||||
if len(message.Images) > 0 {
|
||||
t.Fatalf("images should be removed: %#v", out.Messages)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(out.Messages[2].Text, "[图片上下文]") || !strings.Contains(out.Messages[2].Text, "海边礁石和海浪") {
|
||||
t.Fatalf("latest user message missing image context: %#v", out.Messages[2])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user