Skip to content

Commit

Permalink
Merge pull request #864 from trheyi/main
Browse files Browse the repository at this point in the history
Refactor tool call prompts and message handling
  • Loading branch information
trheyi authored Feb 11, 2025
2 parents 07f95cb + 8bc7f6d commit 6d1a118
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 49 deletions.
102 changes: 63 additions & 39 deletions neo/assistant/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
Expand All @@ -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",
}))

// Add tool_calls prompts

examples := []string{}
for _, tool := range ast.Tools.Tools {
example := tool.Example()
examples = append(examples, example)
}

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{}{
Expand All @@ -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))
}

}
}

Expand Down
73 changes: 73 additions & 0 deletions neo/assistant/tool.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
10 changes: 0 additions & 10 deletions neo/assistant/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down

0 comments on commit 6d1a118

Please sign in to comment.