Matt Low
3fde58b77d
- More emphasis on `api` package. It now holds database model structs from `lmcli/models` (which is now gone) as well as the tool spec, call, and result types. `tools.Tool` is now `api.ToolSpec`. `api.ChatCompletionClient` was renamed to `api.ChatCompletionProvider`. - Change ChatCompletion interface and implementations to no longer do automatic tool call recursion - they simply return a ToolCall message which the caller can decide what to do with (e.g. prompt for user confirmation before executing) - `api.ChatCompletionProvider` functions have had their ReplyCallback parameter removed, as now they only return a single reply. - Added a top-level `agent` package, moved the current built-in tools implementations under `agent/toolbox`. `tools.ExecuteToolCalls` is now `agent.ExecuteToolCalls`. - Fixed request context handling in openai, google, ollama (use `NewRequestWithContext`), cleaned up request cancellation in TUI - Fix tool call tui persistence bug (we were skipping message with empty content) - Now handle tool calling from TUI layer TODO: - Prompt users before executing tool calls - Automatically send tool results to the model (or make this toggleable)
99 lines
2.2 KiB
Go
99 lines
2.2 KiB
Go
package api
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
type ToolSpec struct {
|
|
Name string
|
|
Description string
|
|
Parameters []ToolParameter
|
|
Impl func(*ToolSpec, map[string]interface{}) (string, error)
|
|
}
|
|
|
|
type ToolParameter struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"` // "string", "integer", "boolean"
|
|
Required bool `json:"required"`
|
|
Description string `json:"description"`
|
|
Enum []string `json:"enum,omitempty"`
|
|
}
|
|
|
|
type ToolCall struct {
|
|
ID string `json:"id" yaml:"-"`
|
|
Name string `json:"name" yaml:"tool"`
|
|
Parameters map[string]interface{} `json:"parameters" yaml:"parameters"`
|
|
}
|
|
|
|
type ToolResult struct {
|
|
ToolCallID string `json:"toolCallID" yaml:"-"`
|
|
ToolName string `json:"toolName,omitempty" yaml:"tool"`
|
|
Result string `json:"result,omitempty" yaml:"result"`
|
|
}
|
|
|
|
type ToolCalls []ToolCall
|
|
|
|
func (tc *ToolCalls) Scan(value any) (err error) {
|
|
s := value.(string)
|
|
if value == nil || s == "" {
|
|
*tc = nil
|
|
return
|
|
}
|
|
err = json.Unmarshal([]byte(s), tc)
|
|
return
|
|
}
|
|
|
|
func (tc ToolCalls) Value() (driver.Value, error) {
|
|
if len(tc) == 0 {
|
|
return "", nil
|
|
}
|
|
jsonBytes, err := json.Marshal(tc)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Could not marshal ToolCalls to JSON: %v\n", err)
|
|
}
|
|
return string(jsonBytes), nil
|
|
}
|
|
|
|
type ToolResults []ToolResult
|
|
|
|
func (tr *ToolResults) Scan(value any) (err error) {
|
|
s := value.(string)
|
|
if value == nil || s == "" {
|
|
*tr = nil
|
|
return
|
|
}
|
|
err = json.Unmarshal([]byte(s), tr)
|
|
return
|
|
}
|
|
|
|
func (tr ToolResults) Value() (driver.Value, error) {
|
|
if len(tr) == 0 {
|
|
return "", nil
|
|
}
|
|
jsonBytes, err := json.Marshal([]ToolResult(tr))
|
|
if err != nil {
|
|
return "", fmt.Errorf("Could not marshal ToolResults to JSON: %v\n", err)
|
|
}
|
|
return string(jsonBytes), nil
|
|
}
|
|
|
|
type CallResult struct {
|
|
Message string `json:"message"`
|
|
Result any `json:"result,omitempty"`
|
|
}
|
|
|
|
func (r CallResult) ToJson() (string, error) {
|
|
if r.Message == "" {
|
|
// When message not supplied, assume success
|
|
r.Message = "success"
|
|
}
|
|
|
|
jsonBytes, err := json.Marshal(r)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Could not marshal CallResult to JSON: %v\n", err)
|
|
}
|
|
return string(jsonBytes), nil
|
|
}
|