Files
ragflow/internal/entity/models/llm_test.go
Haruko386 ff3d566a4f fix[go]: support retrieval kb in agent chat (#16944)
### Summary

As title
- [x] fix tool & component `Retrieval KB`
- [x] fix agent cannot use  `Retrieval KB` in agent chat

#### Main cause

Model provider do not set up:
```Go
if chatModelConfig.Tools != nil {
    reqBody["tools"] = chatModelConfig.Tools
}
```

#### Working Now
<img width="3774" height="2128" alt="image"
src="https://github.com/user-attachments/assets/400a349d-0211-43e5-a7ec-7a014acf77a6"
/>

```
 ____________________________________
< This PR takes me all day to do it. >
 ------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ                                                                                                                                                                                                    
```
2026-07-15 21:44:28 +08:00

197 lines
6.3 KiB
Go

package models
import (
"context"
"testing"
"github.com/cloudwego/eino/schema"
)
func TestEinoChatModelGenerateSendsBoundTools(t *testing.T) {
apiKey := "key"
modelName := "chat"
driver := &captureToolDriver{
resp: &ChatResponse{
ToolCalls: []map[string]interface{}{
{
"id": "call-1",
"type": "function",
"function": map[string]interface{}{
"name": "search_my_dateset",
"arguments": `{"query":"hello"}`,
},
},
},
},
}
base := NewChatModel(driver, &modelName, &APIConfig{ApiKey: &apiKey})
model := NewEinoChatModel(base, nil)
bound, err := model.WithTools([]*schema.ToolInfo{
{
Name: "search_my_dateset",
Desc: "Search datasets.",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"query": &schema.ParameterInfo{Type: schema.String, Required: true},
}),
},
})
if err != nil {
t.Fatalf("WithTools: %v", err)
}
msg, err := bound.Generate(context.Background(), []*schema.Message{schema.UserMessage("hello")})
if err != nil {
t.Fatalf("Generate: %v", err)
}
if driver.lastConfig == nil || driver.lastConfig.Tools == nil {
t.Fatal("Generate did not send tools to driver")
}
tools, ok := driver.lastConfig.Tools.([]map[string]any)
if !ok || len(tools) != 1 {
t.Fatalf("driver tools = %#v, want one OpenAI-style tool", driver.lastConfig.Tools)
}
fn, _ := tools[0]["function"].(map[string]any)
if fn["name"] != "search_my_dateset" {
t.Fatalf("tool function name = %#v, want search_my_dateset", fn["name"])
}
if driver.lastConfig.ToolChoice == nil || *driver.lastConfig.ToolChoice != "auto" {
t.Fatalf("ToolChoice = %#v, want auto", driver.lastConfig.ToolChoice)
}
if len(msg.ToolCalls) != 1 {
t.Fatalf("msg.ToolCalls len = %d, want 1", len(msg.ToolCalls))
}
if msg.ToolCalls[0].Function.Name != "search_my_dateset" || msg.ToolCalls[0].Function.Arguments != `{"query":"hello"}` {
t.Fatalf("tool call = %#v, want search_my_dateset query call", msg.ToolCalls[0])
}
}
func TestEinoChatModelStreamWithToolsYieldsToolCalls(t *testing.T) {
apiKey := "key"
modelName := "chat"
driver := &captureToolDriver{
resp: &ChatResponse{
ToolCalls: []map[string]interface{}{
{
"id": "call-1",
"type": "function",
"function": map[string]interface{}{
"name": "search_my_dateset",
"arguments": `{"query":"hello"}`,
},
},
},
},
}
base := NewChatModel(driver, &modelName, &APIConfig{ApiKey: &apiKey})
model := NewEinoChatModel(base, nil)
bound, err := model.WithTools([]*schema.ToolInfo{
{
Name: "search_my_dateset",
Desc: "Search datasets.",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"query": &schema.ParameterInfo{Type: schema.String, Required: true},
}),
},
})
if err != nil {
t.Fatalf("WithTools: %v", err)
}
stream, err := bound.Stream(context.Background(), []*schema.Message{schema.UserMessage("hello")})
if err != nil {
t.Fatalf("Stream: %v", err)
}
msg, err := stream.Recv()
if err != nil {
t.Fatalf("stream.Recv: %v", err)
}
if msg == nil {
t.Fatal("stream ended before yielding message")
}
if len(msg.ToolCalls) != 1 || msg.ToolCalls[0].Function.Name != "search_my_dateset" {
t.Fatalf("stream message tool calls = %#v, want search_my_dateset", msg.ToolCalls)
}
}
func TestToInternalMessagesPreservesToolMessages(t *testing.T) {
internal := toInternalMessages([]*schema.Message{
{
Role: schema.Assistant,
ToolCalls: []schema.ToolCall{{
ID: "call-1",
Type: "function",
Function: schema.FunctionCall{
Name: "search_my_dateset",
Arguments: `{"query":"hello"}`,
},
}},
},
{
Role: schema.Tool,
Content: `{"formalized_content":"answer"}`,
ToolCallID: "call-1",
},
})
if len(internal) != 2 {
t.Fatalf("len(internal) = %d, want 2", len(internal))
}
if len(internal[0].ToolCalls) != 1 {
t.Fatalf("assistant ToolCalls = %#v, want one tool call", internal[0].ToolCalls)
}
if internal[1].ToolCallID != "call-1" || internal[1].Role != "tool" {
t.Fatalf("tool message = %#v, want tool role with call id", internal[1])
}
}
type captureToolDriver struct {
resp *ChatResponse
lastConfig *ChatConfig
}
func (d *captureToolDriver) NewInstance(baseURL map[string]string) ModelDriver { return d }
func (d *captureToolDriver) Name() string { return "capture" }
func (d *captureToolDriver) ChatWithMessages(_ string, _ []Message, _ *APIConfig, cfg *ChatConfig) (*ChatResponse, error) {
d.lastConfig = cfg
return d.resp, nil
}
func (d *captureToolDriver) ChatStreamlyWithSender(_ string, _ []Message, _ *APIConfig, _ *ChatConfig, _ func(*string, *string) error) error {
return nil
}
func (d *captureToolDriver) Embed(_ *string, _ []string, _ *APIConfig, _ *EmbeddingConfig) ([]EmbeddingData, error) {
return nil, nil
}
func (d *captureToolDriver) Rerank(_ *string, _ string, _ []string, _ *APIConfig, _ *RerankConfig) (*RerankResponse, error) {
return nil, nil
}
func (d *captureToolDriver) TranscribeAudio(_ *string, _ *string, _ *APIConfig, _ *ASRConfig) (*ASRResponse, error) {
return nil, nil
}
func (d *captureToolDriver) TranscribeAudioWithSender(_ *string, _ *string, _ *APIConfig, _ *ASRConfig, _ func(*string, *string) error) error {
return nil
}
func (d *captureToolDriver) AudioSpeech(_ *string, _ *string, _ *APIConfig, _ *TTSConfig) (*TTSResponse, error) {
return nil, nil
}
func (d *captureToolDriver) AudioSpeechWithSender(_ *string, _ *string, _ *APIConfig, _ *TTSConfig, _ func(*string, *string) error) error {
return nil
}
func (d *captureToolDriver) OCRFile(_ *string, _ []byte, _ *string, _ *APIConfig, _ *OCRConfig) (*OCRFileResponse, error) {
return nil, nil
}
func (d *captureToolDriver) ParseFile(_ *string, _ []byte, _ *string, _ *APIConfig, _ *ParseFileConfig) (*ParseFileResponse, error) {
return nil, nil
}
func (d *captureToolDriver) ListModels(_ *APIConfig) ([]ListModelResponse, error) {
return nil, nil
}
func (d *captureToolDriver) Balance(_ *APIConfig) (map[string]interface{}, error) {
return nil, nil
}
func (d *captureToolDriver) CheckConnection(_ *APIConfig) error { return nil }
func (d *captureToolDriver) ListTasks(_ *APIConfig) ([]ListTaskStatus, error) {
return nil, nil
}
func (d *captureToolDriver) ShowTask(_ string, _ *APIConfig) (*TaskResponse, error) {
return nil, nil
}