Merge pull request #864 from trheyi/main

Refactor tool call prompts and message handling
This commit is contained in:
Max 2025-02-11 16:32:08 +08:00 committed by GitHub
commit 6d1a118236
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 134 additions and 47 deletions

View file

@ -561,7 +561,7 @@ func (ast *Assistant) saveChatHistory(ctx chatctx.Context, messages []chatMessag
{
"role": "assistant",
"content": contents.JSON(),
"name": ctx.Sid,
"name": ast.ID,
"assistant_id": ast.ID,
"assistant_name": ast.Name,
"assistant_avatar": ast.Avatar,
@ -605,7 +605,7 @@ func (ast *Assistant) withOptions(options map[string]interface{}) map[string]int
func (ast *Assistant) withPrompts(messages []chatMessage.Message) []chatMessage.Message {
if ast.Prompts != nil {
for _, prompt := range ast.Prompts {
name := ast.Name
name := strings.ReplaceAll(ast.ID, ".", "_") // OpenAI only supports underscore in the name
if prompt.Name != "" {
name = prompt.Name
}
@ -618,43 +618,61 @@ func (ast *Assistant) withPrompts(messages []chatMessage.Message) []chatMessage.
settings, has := connectorSettings[ast.Connector]
if !has || !settings.Tools {
raw, _ := jsoniter.MarshalToString(ast.Tools.Tools)
messages = append(messages, *chatMessage.New().Map(map[string]interface{}{
"role": "system",
"name": "TOOL_CALLS_SCHEMA",
"content": raw,
}))
messages = append(messages, *chatMessage.New().Map(map[string]interface{}{
"role": "system",
"name": "TOOL_CALLS",
"content": "## Tool Response Format\n" +
"1. If no matching function exists in TOOL_CALLS_SCHEMA, respond normally without using tool calls\n" +
"2. When using tools, wrap function calls in <tool> and </tool> tags\n" +
"3. The tool call must be a valid JSON object\n" +
"4. Follow the JSON Schema defined in TOOL_CALLS_SCHEMA\n" +
"5. One complete tool call per response\n" +
"6. Parameter values MUST strictly follow the descriptions and validation rules defined in properties\n" +
"7. For each parameter, carefully check and comply with:\n" +
" - Data type requirements\n" +
" - Format restrictions\n" +
" - Value range limitations\n" +
" - Pattern matching rules\n" +
" - Required field validations\n\n" +
"Example:\n" +
"<tool>\n" + `{"function":"<FunctionName>","arguments":{"<ArgumentName>":"<ArgumentValue>"}}` + "\n</tool>",
}))
messages = append(messages, *chatMessage.New().Map(map[string]interface{}{
"role": "system",
"name": "TOOL_CALLS",
"content": "## Tool Usage Guidelines\n" +
"1. Use functions defined in TOOL_CALLS_SCHEMA only when they match your needs\n" +
"2. If no matching function exists, respond normally as a helpful assistant\n" +
"3. When using tools, arguments must match the schema definition exactly\n" +
"4. All parameter values must strictly adhere to the validation rules specified in properties\n" +
"5. Never skip or ignore any validation requirements defined in the schema",
}))
examples := []string{}
for _, tool := range ast.Tools.Tools {
example := tool.Example()
examples = append(examples, example)
}
// Add tool_calls prompts
examplesStr := ""
if len(examples) > 0 {
examplesStr = "Examples:\n" + strings.Join(examples, "\n\n")
}
prompts := []map[string]interface{}{
{
"role": "system",
"name": "TOOL_CALLS_SCHEMA",
"content": raw,
},
{
"role": "system",
"name": "TOOL_CALLS_SCHEMA",
"content": "## Tool Calls Schema Definition\n" +
"Each tool call is defined with:\n" +
" - type: always 'function'\n" +
" - function:\n" +
" - name: function name\n" +
" - description: function description\n" +
" - parameters: function parameters with type and validation rules\n",
},
{
"role": "system",
"name": "TOOL_CALLS",
"content": "## Tool Response Format\n" +
"1. Only use tool calls when a function matches your task exactly\n" +
"2. Each tool call must be wrapped in <tool> and </tool> tags\n" +
"3. Tool call must be a valid JSON with:\n" +
" {\"function\": \"function_name\", \"arguments\": {parameters}}\n" +
"4. Return the function's result as your response\n" +
"5. One tool call per response\n" +
"6. Arguments must match parameter types, rules and description\n\n" +
examplesStr,
},
{
"role": "system",
"name": "TOOL_CALLS",
"content": "## Tool Usage Guidelines\n" +
"1. Use functions defined in TOOL_CALLS_SCHEMA only when they match your needs\n" +
"2. If no matching function exists, respond normally as a helpful assistant\n" +
"3. When using tools, arguments must match the schema definition exactly\n" +
"4. All parameter values must strictly adhere to the validation rules specified in properties\n" +
"5. Never skip or ignore any validation requirements defined in the schema",
},
}
// Add tool_calls developer prompts
if ast.Tools.Prompts != nil && len(ast.Tools.Prompts) > 0 {
for _, prompt := range ast.Tools.Prompts {
messages = append(messages, *chatMessage.New().Map(map[string]interface{}{
@ -664,6 +682,12 @@ func (ast *Assistant) withPrompts(messages []chatMessage.Message) []chatMessage.
}))
}
}
// Add the prompts
for _, prompt := range prompts {
messages = append(messages, *chatMessage.New().Map(prompt))
}
}
}

73
neo/assistant/tool.go Normal file
View file

@ -0,0 +1,73 @@
package assistant
import (
"fmt"
jsoniter "github.com/json-iterator/go"
)
// Tool represents a tool
type Tool struct {
Type string `json:"type"`
Function struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters Parameter `json:"parameters"`
Strict bool `json:"strict,omitempty"`
} `json:"function"`
}
// SchemaProperty represents a JSON Schema property
type SchemaProperty struct {
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
}
// Parameter represents the parameters field in function calling format
type Parameter struct {
Type string `json:"type"`
Properties map[string]SchemaProperty `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
AdditionalProperties bool `json:"additionalProperties"`
}
// Example returns a formatted example of how to use this tool
func (tool Tool) Example() string {
return fmt.Sprintf("<tool>\n{\"function\":\"%s\",\"arguments\":%s}\n</tool>",
tool.Function.Name,
jsoniter.Wrap(tool.ExampleArguments()).ToString())
}
// ExampleArguments generates example arguments for the tool based on parameter types
func (tool Tool) ExampleArguments() map[string]interface{} {
args := map[string]interface{}{}
// Handle the root parameter object
if tool.Function.Parameters.Type == "object" && tool.Function.Parameters.Properties != nil {
for name, prop := range tool.Function.Parameters.Properties {
args[name] = generateExampleValue(name, prop)
}
}
return args
}
// generateExampleValue creates an example value for a parameter
func generateExampleValue(name string, prop SchemaProperty) interface{} {
switch prop.Type {
case "string":
return fmt.Sprintf("<%s:string>", name)
case "number":
return fmt.Sprintf("<%s:number>", name)
case "integer":
return fmt.Sprintf("<%s:integer>", name)
case "boolean":
return fmt.Sprintf("<%s:boolean>", name)
case "object":
return fmt.Sprintf("<%s:object>", name)
case "array":
return fmt.Sprintf("<%s:array>", name)
default:
return fmt.Sprintf("<%s>", name)
}
}

View file

@ -94,16 +94,6 @@ type Prompt struct {
Name string `json:"name,omitempty"`
}
// Tool represents a tool
type Tool struct {
Type string `json:"type"`
Function struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
} `json:"function"`
}
// QueryParam the assistant query param
type QueryParam struct {
Limit uint `json:"limit"`