diff --git a/neo/assistant/api.go b/neo/assistant/api.go index a900c49e..59a00e44 100644 --- a/neo/assistant/api.go +++ b/neo/assistant/api.go @@ -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 and 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" + - "\n" + `{"function":"","arguments":{"":""}}` + "\n", - })) - 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 and 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)) + } + } } diff --git a/neo/assistant/tool.go b/neo/assistant/tool.go new file mode 100644 index 00000000..60fc7579 --- /dev/null +++ b/neo/assistant/tool.go @@ -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("\n{\"function\":\"%s\",\"arguments\":%s}\n", + 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) + } +} diff --git a/neo/assistant/types.go b/neo/assistant/types.go index 712ccede..cad8fd16 100644 --- a/neo/assistant/types.go +++ b/neo/assistant/types.go @@ -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"`