132 lines
3.1 KiB
Go
132 lines
3.1 KiB
Go
package go_llm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"reflect"
|
|
"time"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/go-llm/schema"
|
|
)
|
|
|
|
type Function struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
Strict bool `json:"strict,omitempty"`
|
|
Parameters schema.Type `json:"parameters"`
|
|
|
|
Forced bool `json:"forced,omitempty"`
|
|
|
|
// Timeout is the maximum time to wait for the function to complete
|
|
Timeout time.Duration `json:"-"`
|
|
|
|
// fn is the function to call, only set if this is constructed with NewFunction
|
|
fn reflect.Value
|
|
|
|
paramType reflect.Type
|
|
}
|
|
|
|
func (f Function) WithSyntheticField(name string, description string) Function {
|
|
if obj, o := f.Parameters.(schema.Object); o {
|
|
f.Parameters = obj.WithSyntheticField(name, description)
|
|
}
|
|
|
|
return f
|
|
}
|
|
|
|
func (f Function) WithSyntheticFields(fieldsAndDescriptions map[string]string) Function {
|
|
if obj, o := f.Parameters.(schema.Object); o {
|
|
for k, v := range fieldsAndDescriptions {
|
|
obj = obj.WithSyntheticField(k, v)
|
|
}
|
|
f.Parameters = obj
|
|
}
|
|
|
|
return f
|
|
}
|
|
|
|
func (f Function) Execute(ctx *Context, input string) (any, error) {
|
|
if !f.fn.IsValid() {
|
|
return "", fmt.Errorf("function %s is not implemented", f.Name)
|
|
}
|
|
|
|
slog.Info("Function.Execute", "name", f.Name, "input", input, "f", f.paramType)
|
|
// first, we need to parse the input into the struct
|
|
p := reflect.New(f.paramType)
|
|
fmt.Println("Function.Execute", f.Name, "input:", input)
|
|
|
|
var vals map[string]any
|
|
err := json.Unmarshal([]byte(input), &vals)
|
|
|
|
var syntheticFields map[string]string
|
|
|
|
// first eat up any synthetic fields
|
|
if obj, o := f.Parameters.(schema.Object); o {
|
|
for k := range obj.SyntheticFields() {
|
|
key := schema.SyntheticFieldPrefix + k
|
|
if val, ok := vals[key]; ok {
|
|
if syntheticFields == nil {
|
|
syntheticFields = map[string]string{}
|
|
}
|
|
|
|
syntheticFields[k] = fmt.Sprint(val)
|
|
delete(vals, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// now for any remaining fields, re-marshal them into json and then unmarshal into the struct
|
|
b, err := json.Marshal(vals)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to marshal input: %w (input: %s)", err, input)
|
|
}
|
|
|
|
// now we can unmarshal the input into the struct
|
|
err = json.Unmarshal(b, p.Interface())
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to unmarshal input: %w (input: %s)", err, input)
|
|
}
|
|
|
|
// now we can call the function
|
|
exec := func(ctx *Context) (any, error) {
|
|
out := f.fn.Call([]reflect.Value{reflect.ValueOf(ctx), p.Elem()})
|
|
|
|
if len(out) != 2 {
|
|
return "", fmt.Errorf("function %s must return two values, got %d", f.Name, len(out))
|
|
}
|
|
|
|
if out[1].IsNil() {
|
|
return out[0].Interface(), nil
|
|
}
|
|
|
|
return "", out[1].Interface().(error)
|
|
}
|
|
|
|
var cancel context.CancelFunc
|
|
if f.Timeout > 0 {
|
|
ctx, cancel = ctx.WithTimeout(f.Timeout)
|
|
defer cancel()
|
|
}
|
|
|
|
return exec(ctx)
|
|
}
|
|
|
|
type FunctionCall struct {
|
|
Name string `json:"name,omitempty"`
|
|
Arguments string `json:"arguments,omitempty"`
|
|
}
|
|
|
|
func (fc *FunctionCall) toRaw() map[string]any {
|
|
res := map[string]interface{}{
|
|
"name": fc.Name,
|
|
}
|
|
|
|
if fc.Arguments != "" {
|
|
res["arguments"] = fc.Arguments
|
|
}
|
|
|
|
return res
|
|
}
|