Compare commits
27 Commits
function
...
c73c63a8aa
Author | SHA1 | Date | |
---|---|---|---|
c73c63a8aa | |||
101291abd9 | |||
e9baf7910e | |||
39ffb82237 | |||
916f07be18 | |||
3093b988f8 | |||
2ae583e9f3 | |||
5ba0d5df7e | |||
58552ee226 | |||
14961bfbc6 | |||
7c9eb08cb4 | |||
ff5e4ca7b0 | |||
82feb7d8b4 | |||
5ba42056ad | |||
52533238d3 | |||
88fbf89a63 | |||
e5a046a70b | |||
2737a5b2be | |||
7f5e34e437 | |||
0d909edd44 | |||
388a44fa79 | |||
e7b7aab62e | |||
0d70ec46de | |||
6e2b5a33c0 | |||
dfb768d966 | |||
46a526fd5a | |||
0b06fd965e |
141
anthropic.go
141
anthropic.go
@@ -1,11 +1,21 @@
|
|||||||
package go_llm
|
package go_llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
anth "github.com/liushuangls/go-anthropic/v2"
|
"image"
|
||||||
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
anth "github.com/liushuangls/go-anthropic/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type anthropic struct {
|
type anthropic struct {
|
||||||
@@ -22,6 +32,13 @@ func (a anthropic) ModelVersion(modelVersion string) (ChatCompletion, error) {
|
|||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deferClose(c io.Closer) {
|
||||||
|
err := c.Close()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error closing", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
||||||
res := anth.MessagesRequest{
|
res := anth.MessagesRequest{
|
||||||
Model: anth.Model(a.model),
|
Model: anth.Model(a.model),
|
||||||
@@ -60,24 +77,103 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, img := range msg.Images {
|
for _, img := range msg.Images {
|
||||||
|
// anthropic doesn't allow the assistant to send images, so we need to say it's from the user
|
||||||
|
if m.Role == anth.RoleAssistant {
|
||||||
|
m.Role = anth.RoleUser
|
||||||
|
}
|
||||||
|
|
||||||
if img.Base64 != "" {
|
if img.Base64 != "" {
|
||||||
m.Content = append(m.Content, anth.NewImageMessageContent(anth.MessageContentImageSource{
|
|
||||||
Type: "base64",
|
// Anthropic models expect images to be < 5MiB in size
|
||||||
MediaType: img.ContentType,
|
raw, err := base64.StdEncoding.DecodeString(img.Base64)
|
||||||
Data: img.Base64,
|
|
||||||
}))
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if image size exceeds 5MiB (5242880 bytes)
|
||||||
|
if len(raw) >= 5242880 {
|
||||||
|
// Decode the image
|
||||||
|
imgData, format, err := image.Decode(bytes.NewReader(raw))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("failed to decode image", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case "jpeg", "jpg":
|
||||||
|
err = jpeg.Encode(&buf, imgData, &jpeg.Options{Quality: 60})
|
||||||
|
case "png":
|
||||||
|
// For PNG, use a higher compression level
|
||||||
|
enc := &png.Encoder{
|
||||||
|
CompressionLevel: png.BestCompression,
|
||||||
|
}
|
||||||
|
err = enc.Encode(&buf, imgData)
|
||||||
|
case "gif":
|
||||||
|
err = gif.Encode(&buf, imgData, &gif.Options{
|
||||||
|
NumColors: 128,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("failed to encode image", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the base64 string
|
||||||
|
img.Base64 = base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Content = append(m.Content, anth.NewImageMessageContent(
|
||||||
|
anth.NewMessageContentSource(
|
||||||
|
anth.MessagesContentSourceTypeBase64,
|
||||||
|
img.ContentType,
|
||||||
|
img.Base64,
|
||||||
|
)))
|
||||||
} else if img.Url != "" {
|
} else if img.Url != "" {
|
||||||
m.Content = append(m.Content, anth.NewImageMessageContent(anth.MessageContentImageSource{
|
|
||||||
Type: "url",
|
// download the image
|
||||||
MediaType: img.ContentType,
|
cl, err := http.NewRequest(http.MethodGet, img.Url, nil)
|
||||||
Data: img.Url,
|
if err != nil {
|
||||||
}))
|
log.Println("failed to create request", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(cl)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("failed to download image", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
defer deferClose(resp.Body)
|
||||||
|
|
||||||
|
img.ContentType = resp.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
// read the image
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("failed to read image", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// base64 encode the image
|
||||||
|
img.Base64 = string(b)
|
||||||
|
|
||||||
|
m.Content = append(m.Content, anth.NewImageMessageContent(
|
||||||
|
anth.NewMessageContentSource(
|
||||||
|
anth.MessagesContentSourceTypeBase64,
|
||||||
|
img.ContentType,
|
||||||
|
img.Base64,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this has the same role as the previous message, we can append it to the previous message
|
// if this has the same role as the previous message, we can append it to the previous message
|
||||||
// as anthropic expects alternating assistant and user roles
|
// as anthropic expects alternating assistant and user roles
|
||||||
|
|
||||||
if len(msgs) > 0 && msgs[len(msgs)-1].Role == role {
|
if len(msgs) > 0 && msgs[len(msgs)-1].Role == role {
|
||||||
m2 := &msgs[len(msgs)-1]
|
m2 := &msgs[len(msgs)-1]
|
||||||
|
|
||||||
@@ -88,18 +184,19 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tool := range req.Toolbox.funcs {
|
for _, tool := range req.Toolbox.functions {
|
||||||
res.Tools = append(res.Tools, anth.ToolDefinition{
|
res.Tools = append(res.Tools, anth.ToolDefinition{
|
||||||
Name: tool.Name,
|
Name: tool.Name,
|
||||||
Description: tool.Description,
|
Description: tool.Description,
|
||||||
InputSchema: tool.Parameters,
|
InputSchema: tool.Parameters.AnthropicInputSchema(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Messages = msgs
|
res.Messages = msgs
|
||||||
|
|
||||||
if req.Temperature != nil {
|
if req.Temperature != nil {
|
||||||
res.Temperature = req.Temperature
|
var f = float32(*req.Temperature)
|
||||||
|
res.Temperature = &f
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("llm request to anthropic request", res)
|
log.Println("llm request to anthropic request", res)
|
||||||
@@ -108,15 +205,13 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a anthropic) responseToLLMResponse(in anth.MessagesResponse) Response {
|
func (a anthropic) responseToLLMResponse(in anth.MessagesResponse) Response {
|
||||||
res := Response{}
|
choice := ResponseChoice{}
|
||||||
|
|
||||||
for _, msg := range in.Content {
|
for _, msg := range in.Content {
|
||||||
choice := ResponseChoice{}
|
|
||||||
|
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case anth.MessagesContentTypeText:
|
case anth.MessagesContentTypeText:
|
||||||
if msg.Text != nil {
|
if msg.Text != nil {
|
||||||
choice.Content = *msg.Text
|
choice.Content += *msg.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
case anth.MessagesContentTypeToolUse:
|
case anth.MessagesContentTypeToolUse:
|
||||||
@@ -135,13 +230,13 @@ func (a anthropic) responseToLLMResponse(in anth.MessagesResponse) Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Choices = append(res.Choices, choice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("anthropic response to llm response", res)
|
log.Println("anthropic response to llm response", choice)
|
||||||
|
|
||||||
return res
|
return Response{
|
||||||
|
Choices: []ResponseChoice{choice},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a anthropic) ChatComplete(ctx context.Context, req Request) (Response, error) {
|
func (a anthropic) ChatComplete(ctx context.Context, req Request) (Response, error) {
|
||||||
|
120
context.go
Normal file
120
context.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package go_llm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
context.Context
|
||||||
|
request Request
|
||||||
|
response *ResponseChoice
|
||||||
|
toolcall *ToolCall
|
||||||
|
syntheticFields map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ToNewRequest(toolResults ...ToolCallResponse) Request {
|
||||||
|
var res Request
|
||||||
|
|
||||||
|
res.Toolbox = c.request.Toolbox
|
||||||
|
res.Temperature = c.request.Temperature
|
||||||
|
|
||||||
|
res.Conversation = make([]Input, len(c.request.Conversation))
|
||||||
|
copy(res.Conversation, c.request.Conversation)
|
||||||
|
|
||||||
|
// now for every input message, convert those to an Input to add to the conversation
|
||||||
|
for _, msg := range c.request.Messages {
|
||||||
|
res.Conversation = append(res.Conversation, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are tool calls, then we need to add those to the conversation
|
||||||
|
if c.response != nil {
|
||||||
|
res.Conversation = append(res.Conversation, *c.response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are tool results, then we need to add those to the conversation
|
||||||
|
for _, result := range toolResults {
|
||||||
|
res.Conversation = append(res.Conversation, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContext(ctx context.Context, request Request, response *ResponseChoice, toolcall *ToolCall) *Context {
|
||||||
|
return &Context{Context: ctx, request: request, response: response, toolcall: toolcall}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Request() Request {
|
||||||
|
return c.request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Response() *ResponseChoice {
|
||||||
|
return c.response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ToolCall() *ToolCall {
|
||||||
|
return c.toolcall
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) SyntheticFields() map[string]string {
|
||||||
|
if c.syntheticFields == nil {
|
||||||
|
c.syntheticFields = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.syntheticFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) WithContext(ctx context.Context) *Context {
|
||||||
|
return &Context{Context: ctx, request: c.request, response: c.response, toolcall: c.toolcall, syntheticFields: c.syntheticFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) WithRequest(request Request) *Context {
|
||||||
|
return &Context{Context: c.Context, request: request, response: c.response, toolcall: c.toolcall, syntheticFields: c.syntheticFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) WithResponse(response *ResponseChoice) *Context {
|
||||||
|
return &Context{Context: c.Context, request: c.request, response: response, toolcall: c.toolcall, syntheticFields: c.syntheticFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) WithToolCall(toolcall *ToolCall) *Context {
|
||||||
|
return &Context{Context: c.Context, request: c.request, response: c.response, toolcall: toolcall, syntheticFields: c.syntheticFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) WithSyntheticFields(syntheticFields map[string]string) *Context {
|
||||||
|
return &Context{Context: c.Context, request: c.request, response: c.response, toolcall: c.toolcall, syntheticFields: syntheticFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return c.Context.Deadline()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Done() <-chan struct{} {
|
||||||
|
return c.Context.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Err() error {
|
||||||
|
return c.Context.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Value(key any) any {
|
||||||
|
switch key {
|
||||||
|
case "request":
|
||||||
|
return c.request
|
||||||
|
|
||||||
|
case "response":
|
||||||
|
return c.response
|
||||||
|
|
||||||
|
case "toolcall":
|
||||||
|
return c.toolcall
|
||||||
|
|
||||||
|
case "syntheticFields":
|
||||||
|
return c.syntheticFields
|
||||||
|
|
||||||
|
}
|
||||||
|
return c.Context.Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) WithTimeout(timeout time.Duration) (*Context, context.CancelFunc) {
|
||||||
|
ctx, cancel := context.WithTimeout(c.Context, timeout)
|
||||||
|
return c.WithContext(ctx), cancel
|
||||||
|
}
|
97
function.go
97
function.go
@@ -4,11 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gitea.stevedudenhoeffer.com/steve/go-llm/schema"
|
"log/slog"
|
||||||
"github.com/sashabaranov/go-openai"
|
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/go-llm/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Function struct {
|
type Function struct {
|
||||||
@@ -26,27 +26,71 @@ type Function struct {
|
|||||||
fn reflect.Value
|
fn reflect.Value
|
||||||
|
|
||||||
paramType reflect.Type
|
paramType reflect.Type
|
||||||
|
|
||||||
// definition is a cache of the openaiImpl jsonschema definition
|
|
||||||
definition *jsonschema.Definition
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) Execute(ctx context.Context, input string) (string, error) {
|
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() {
|
if !f.fn.IsValid() {
|
||||||
return "", fmt.Errorf("function %s is not implemented", f.Name)
|
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
|
// first, we need to parse the input into the struct
|
||||||
p := reflect.New(f.paramType)
|
p := reflect.New(f.paramType)
|
||||||
fmt.Println("Function.Execute", f.Name, "input:", input)
|
fmt.Println("Function.Execute", f.Name, "input:", input)
|
||||||
//m := map[string]any{}
|
|
||||||
err := json.Unmarshal([]byte(input), p.Interface())
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to unmarshal input: %w (input: %s)", err, input)
|
return "", fmt.Errorf("failed to unmarshal input: %w (input: %s)", err, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we can call the function
|
// now we can call the function
|
||||||
exec := func(ctx context.Context) (string, error) {
|
exec := func(ctx *Context) (any, error) {
|
||||||
out := f.fn.Call([]reflect.Value{reflect.ValueOf(ctx), p.Elem()})
|
out := f.fn.Call([]reflect.Value{reflect.ValueOf(ctx), p.Elem()})
|
||||||
|
|
||||||
if len(out) != 2 {
|
if len(out) != 2 {
|
||||||
@@ -54,7 +98,7 @@ func (f *Function) Execute(ctx context.Context, input string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if out[1].IsNil() {
|
if out[1].IsNil() {
|
||||||
return out[0].String(), nil
|
return out[0].Interface(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", out[1].Interface().(error)
|
return "", out[1].Interface().(error)
|
||||||
@@ -62,31 +106,26 @@ func (f *Function) Execute(ctx context.Context, input string) (string, error) {
|
|||||||
|
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
if f.Timeout > 0 {
|
if f.Timeout > 0 {
|
||||||
ctx, cancel = context.WithTimeout(ctx, f.Timeout)
|
ctx, cancel = ctx.WithTimeout(f.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
return exec(ctx)
|
return exec(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) toOpenAIFunction() *openai.FunctionDefinition {
|
|
||||||
return &openai.FunctionDefinition{
|
|
||||||
Name: f.Name,
|
|
||||||
Description: f.Description,
|
|
||||||
Strict: f.Strict,
|
|
||||||
Parameters: f.Parameters,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (f *Function) toOpenAIDefinition() jsonschema.Definition {
|
|
||||||
if f.definition == nil {
|
|
||||||
def := f.Parameters.Definition()
|
|
||||||
f.definition = &def
|
|
||||||
}
|
|
||||||
|
|
||||||
return *f.definition
|
|
||||||
}
|
|
||||||
|
|
||||||
type FunctionCall struct {
|
type FunctionCall struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Arguments string `json:"arguments,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
|
||||||
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package go_llm
|
package go_llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/go-llm/schema"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/go-llm/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parse takes a function pointer and returns a function object.
|
// Parse takes a function pointer and returns a function object.
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
// The struct parameters can have the following tags:
|
// The struct parameters can have the following tags:
|
||||||
// - Description: a string that describes the parameter, passed to openaiImpl to tell it what the parameter is for
|
// - Description: a string that describes the parameter, passed to openaiImpl to tell it what the parameter is for
|
||||||
|
|
||||||
func NewFunction[T any](name string, description string, fn func(context.Context, T) (string, error)) *Function {
|
func NewFunction[T any](name string, description string, fn func(*Context, T) (any, error)) Function {
|
||||||
var o T
|
var o T
|
||||||
|
|
||||||
res := Function{
|
res := Function{
|
||||||
@@ -31,5 +31,5 @@ func NewFunction[T any](name string, description string, fn func(context.Context
|
|||||||
panic("function parameter must be a struct")
|
panic("function parameter must be a struct")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &res
|
return res
|
||||||
}
|
}
|
||||||
|
69
go.mod
69
go.mod
@@ -3,44 +3,45 @@ module gitea.stevedudenhoeffer.com/steve/go-llm
|
|||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/generative-ai-go v0.18.0
|
github.com/google/generative-ai-go v0.19.0
|
||||||
github.com/liushuangls/go-anthropic/v2 v2.8.0
|
github.com/liushuangls/go-anthropic/v2 v2.15.0
|
||||||
github.com/sashabaranov/go-openai v1.31.0
|
github.com/openai/openai-go v0.1.0-beta.9
|
||||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
|
google.golang.org/api v0.228.0
|
||||||
google.golang.org/api v0.186.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.115.0 // indirect
|
cloud.google.com/go v0.120.0 // indirect
|
||||||
cloud.google.com/go/ai v0.8.0 // indirect
|
cloud.google.com/go/ai v0.10.1 // indirect
|
||||||
cloud.google.com/go/auth v0.6.0 // indirect
|
cloud.google.com/go/auth v0.15.0 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||||
cloud.google.com/go/longrunning v0.5.7 // indirect
|
cloud.google.com/go/longrunning v0.6.6 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||||
golang.org/x/crypto v0.24.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||||
golang.org/x/net v0.26.0 // indirect
|
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||||
golang.org/x/oauth2 v0.21.0 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
golang.org/x/sync v0.9.0 // indirect
|
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||||
golang.org/x/sys v0.21.0 // indirect
|
golang.org/x/crypto v0.37.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/net v0.39.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/oauth2 v0.29.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
|
golang.org/x/sync v0.13.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
google.golang.org/grpc v1.64.1 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a // indirect
|
||||||
|
google.golang.org/grpc v1.71.1 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
)
|
)
|
||||||
|
237
go.sum
237
go.sum
@@ -1,172 +1,95 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
|
||||||
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
|
||||||
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
cloud.google.com/go/ai v0.10.1 h1:EU93KqYmMeOKgaBXAz2DshH2C/BzAT1P+iJORksLIic=
|
||||||
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
|
cloud.google.com/go/ai v0.10.1/go.mod h1:sWWHZvmJ83BjuxAQtYEiA0SFTpijtbH+SXWFO14ri5A=
|
||||||
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
|
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
||||||
cloud.google.com/go/auth v0.6.0 h1:5x+d6b5zdezZ7gmLWD1m/xNjnaQ2YDhmIz/HH3doy1g=
|
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||||
cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw=
|
||||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw=
|
||||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/generative-ai-go v0.18.0 h1:6ybg9vOCLcI/UpBBYXOTVgvKmcUKFRNj+2Cj3GnebSo=
|
github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
|
||||||
github.com/google/generative-ai-go v0.18.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
|
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
|
||||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
|
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
|
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||||
github.com/liushuangls/go-anthropic/v2 v2.8.0 h1:0zH2jDNycbrlszxnLrG+Gx8vVT0yJAPWU4s3ZTkWzgI=
|
github.com/liushuangls/go-anthropic/v2 v2.15.0 h1:zpplg7BRV/9FlMmeMPI0eDwhViB0l9SkNrF8ErYlRoQ=
|
||||||
github.com/liushuangls/go-anthropic/v2 v2.8.0/go.mod h1:8BKv/fkeTaL5R9R9bGkaknYBueyw2WxY20o7bImbOek=
|
github.com/liushuangls/go-anthropic/v2 v2.15.0/go.mod h1:kq2yW3JVy1/rph8u5KzX7F3q95CEpCT2RXp/2nfCmb4=
|
||||||
|
github.com/openai/openai-go v0.1.0-beta.9 h1:ABpubc5yU/3ejee2GgRrbFta81SG/d7bQbB8mIdP0Xo=
|
||||||
|
github.com/openai/openai-go v0.1.0-beta.9/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/sashabaranov/go-openai v1.31.0 h1:rGe77x7zUeCjtS2IS7NCY6Tp4bQviXNMhkQM6hz/UC4=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/sashabaranov/go-openai v1.31.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
|
||||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a h1:OQ7sHVzkx6L57dQpzUS4ckfWJ51KDH74XHTDe23xWAs=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a/go.mod h1:2R6XrVC8Oc08GlNh8ujEpc7HkLiEZ16QeY7FxIs20ac=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a h1:GIqLhp/cYUkuGuiT+vJk8vhOP86L4+SP5j8yXgeVpvI=
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.186.0 h1:n2OPp+PPXX0Axh4GuSsL5QL8xQCTb2oDwyzPnQvqUug=
|
|
||||||
google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
|
||||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
|
135
google.go
135
google.go
@@ -2,8 +2,12 @@ package go_llm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/generative-ai-go/genai"
|
"github.com/google/generative-ai-go/genai"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
)
|
)
|
||||||
@@ -19,51 +23,117 @@ func (g google) ModelVersion(modelVersion string) (ChatCompletion, error) {
|
|||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g google) requestToGoogleRequest(in Request, model *genai.GenerativeModel) []genai.Part {
|
func (g google) requestToChatHistory(in Request, model *genai.GenerativeModel) (*genai.GenerativeModel, *genai.ChatSession, []genai.Part) {
|
||||||
|
res := *model
|
||||||
|
|
||||||
if in.Temperature != nil {
|
for _, tool := range in.Toolbox.functions {
|
||||||
model.GenerationConfig.Temperature = in.Temperature
|
res.Tools = append(res.Tools, &genai.Tool{
|
||||||
|
FunctionDeclarations: []*genai.FunctionDeclaration{
|
||||||
|
{
|
||||||
|
Name: tool.Name,
|
||||||
|
Description: tool.Description,
|
||||||
|
Parameters: tool.Parameters.GoogleParameters(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
res := []genai.Part{}
|
if !in.Toolbox.RequiresTool() {
|
||||||
|
res.ToolConfig = &genai.ToolConfig{FunctionCallingConfig: &genai.FunctionCallingConfig{
|
||||||
for _, c := range in.Messages {
|
Mode: genai.FunctionCallingAny,
|
||||||
res = append(res, genai.Text(c.Text))
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tool := range in.Toolbox.funcs {
|
cs := res.StartChat()
|
||||||
panic("google ToolBox is todo" + tool.Name)
|
|
||||||
|
|
||||||
/*
|
for i, c := range in.Messages {
|
||||||
t := genai.Tool{}
|
content := genai.NewUserContent(genai.Text(c.Text))
|
||||||
t.FunctionDeclarations = append(t.FunctionDeclarations, &genai.FunctionDeclaration{
|
|
||||||
Name: tool.Name,
|
switch c.Role {
|
||||||
Description: tool.Description,
|
case RoleAssistant, RoleSystem:
|
||||||
Parameters: nil, //tool.Parameters,
|
content.Role = "model"
|
||||||
})
|
|
||||||
*/
|
case RoleUser:
|
||||||
|
content.Role = "user"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, img := range c.Images {
|
||||||
|
if img.Url != "" {
|
||||||
|
// gemini does not support URLs, so we need to download the image and convert it to a blob
|
||||||
|
|
||||||
|
// Download the image from the URL
|
||||||
|
resp, err := http.Get(img.Url)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error downloading image: %v", err))
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check the Content-Length to ensure it's not over 20MB
|
||||||
|
if resp.ContentLength > 20*1024*1024 {
|
||||||
|
panic(fmt.Sprintf("image size exceeds 20MB: %d bytes", resp.ContentLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the content into a byte slice
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error reading image data: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the MIME type is appropriate
|
||||||
|
mimeType := http.DetectContentType(data)
|
||||||
|
switch mimeType {
|
||||||
|
case "image/jpeg", "image/png", "image/gif":
|
||||||
|
// MIME type is valid
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported image MIME type: %s", mimeType))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a genai.Blob using the validated image data
|
||||||
|
content.Parts = append(content.Parts, genai.Blob{
|
||||||
|
MIMEType: mimeType,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// convert base64 to blob
|
||||||
|
b, e := base64.StdEncoding.DecodeString(img.Base64)
|
||||||
|
if e != nil {
|
||||||
|
panic(fmt.Sprintf("error decoding base64: %v", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
content.Parts = append(content.Parts, genai.Blob{
|
||||||
|
MIMEType: img.ContentType,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is the last message, we want to add to history, we want it to be the parts
|
||||||
|
if i == len(in.Messages)-1 {
|
||||||
|
return &res, cs, content.Parts
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.History = append(cs.History, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return &res, cs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g google) responseToLLMResponse(in *genai.GenerateContentResponse) (Response, error) {
|
func (g google) responseToLLMResponse(in *genai.GenerateContentResponse) (Response, error) {
|
||||||
res := Response{}
|
res := Response{}
|
||||||
|
|
||||||
for _, c := range in.Candidates {
|
for _, c := range in.Candidates {
|
||||||
|
var choice ResponseChoice
|
||||||
|
var set = false
|
||||||
if c.Content != nil {
|
if c.Content != nil {
|
||||||
for _, p := range c.Content.Parts {
|
for _, p := range c.Content.Parts {
|
||||||
switch p.(type) {
|
switch p.(type) {
|
||||||
case genai.Text:
|
case genai.Text:
|
||||||
res.Choices = append(res.Choices, ResponseChoice{
|
choice.Content = string(p.(genai.Text))
|
||||||
Content: string(p.(genai.Text)),
|
set = true
|
||||||
})
|
|
||||||
|
|
||||||
case genai.FunctionCall:
|
case genai.FunctionCall:
|
||||||
v := p.(genai.FunctionCall)
|
v := p.(genai.FunctionCall)
|
||||||
choice := ResponseChoice{}
|
|
||||||
|
|
||||||
choice.Content = v.Name
|
|
||||||
b, e := json.Marshal(v.Args)
|
b, e := json.Marshal(v.Args)
|
||||||
|
|
||||||
if e != nil {
|
if e != nil {
|
||||||
@@ -79,14 +149,17 @@ func (g google) responseToLLMResponse(in *genai.GenerateContentResponse) (Respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
choice.Calls = append(choice.Calls, call)
|
choice.Calls = append(choice.Calls, call)
|
||||||
|
set = true
|
||||||
res.Choices = append(res.Choices, choice)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Response{}, fmt.Errorf("unknown part type: %T", p)
|
return Response{}, fmt.Errorf("unknown part type: %T", p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if set {
|
||||||
|
choice.Role = RoleAssistant
|
||||||
|
res.Choices = append(res.Choices, choice)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
@@ -101,9 +174,13 @@ func (g google) ChatComplete(ctx context.Context, req Request) (Response, error)
|
|||||||
|
|
||||||
model := cl.GenerativeModel(g.model)
|
model := cl.GenerativeModel(g.model)
|
||||||
|
|
||||||
parts := g.requestToGoogleRequest(req, model)
|
_, cs, parts := g.requestToChatHistory(req, model)
|
||||||
|
|
||||||
resp, err := model.GenerateContent(ctx, parts...)
|
resp, err := cs.SendMessage(ctx, parts...)
|
||||||
|
|
||||||
|
//parts := g.requestToGoogleRequest(req, model)
|
||||||
|
|
||||||
|
//resp, err := model.GenerateContent(ctx, parts...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Response{}, fmt.Errorf("error generating content: %w", err)
|
return Response{}, fmt.Errorf("error generating content: %w", err)
|
||||||
|
243
llm.go
243
llm.go
@@ -2,6 +2,11 @@ package go_llm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/openai/openai-go"
|
||||||
|
"github.com/openai/openai-go/packages/param"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Role string
|
type Role string
|
||||||
@@ -18,6 +23,26 @@ type Image struct {
|
|||||||
Url string
|
Url string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i Image) toRaw() map[string]any {
|
||||||
|
res := map[string]any{
|
||||||
|
"base64": i.Base64,
|
||||||
|
"contenttype": i.ContentType,
|
||||||
|
"url": i.Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) fromRaw(raw map[string]any) Image {
|
||||||
|
var res Image
|
||||||
|
|
||||||
|
res.Base64 = raw["base64"].(string)
|
||||||
|
res.ContentType = raw["contenttype"].(string)
|
||||||
|
res.Url = raw["url"].(string)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Role Role
|
Role Role
|
||||||
Name string
|
Name string
|
||||||
@@ -25,10 +50,145 @@ type Message struct {
|
|||||||
Images []Image
|
Images []Image
|
||||||
}
|
}
|
||||||
|
|
||||||
type Request struct {
|
func (m Message) toRaw() map[string]any {
|
||||||
Messages []Message
|
res := map[string]any{
|
||||||
Toolbox *ToolBox
|
"role": m.Role,
|
||||||
Temperature *float32
|
"name": m.Name,
|
||||||
|
"text": m.Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
images := make([]map[string]any, 0, len(m.Images))
|
||||||
|
for _, img := range m.Images {
|
||||||
|
images = append(images, img.toRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
res["images"] = images
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) fromRaw(raw map[string]any) Message {
|
||||||
|
var res Message
|
||||||
|
|
||||||
|
res.Role = Role(raw["role"].(string))
|
||||||
|
res.Name = raw["name"].(string)
|
||||||
|
res.Text = raw["text"].(string)
|
||||||
|
|
||||||
|
images := raw["images"].([]map[string]any)
|
||||||
|
for _, img := range images {
|
||||||
|
var i Image
|
||||||
|
|
||||||
|
res.Images = append(res.Images, i.fromRaw(img))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Message) toChatCompletionMessages(model string) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
var res openai.ChatCompletionMessageParamUnion
|
||||||
|
|
||||||
|
var arrayOfContentParts []openai.ChatCompletionContentPartUnionParam
|
||||||
|
var textContent param.Opt[string]
|
||||||
|
|
||||||
|
for _, img := range m.Images {
|
||||||
|
if img.Base64 != "" {
|
||||||
|
arrayOfContentParts = append(arrayOfContentParts,
|
||||||
|
openai.ChatCompletionContentPartUnionParam{
|
||||||
|
OfImageURL: &openai.ChatCompletionContentPartImageParam{
|
||||||
|
ImageURL: openai.ChatCompletionContentPartImageImageURLParam{
|
||||||
|
URL: "data:" + img.ContentType + ";base64," + img.Base64,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else if img.Url != "" {
|
||||||
|
arrayOfContentParts = append(arrayOfContentParts,
|
||||||
|
openai.ChatCompletionContentPartUnionParam{
|
||||||
|
OfImageURL: &openai.ChatCompletionContentPartImageParam{
|
||||||
|
ImageURL: openai.ChatCompletionContentPartImageImageURLParam{
|
||||||
|
URL: img.Url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Text != "" {
|
||||||
|
if len(arrayOfContentParts) > 0 {
|
||||||
|
arrayOfContentParts = append(arrayOfContentParts,
|
||||||
|
openai.ChatCompletionContentPartUnionParam{
|
||||||
|
OfText: &openai.ChatCompletionContentPartTextParam{
|
||||||
|
Text: "\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
textContent = openai.String(m.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a := strings.Split(model, "-")
|
||||||
|
|
||||||
|
useSystemInsteadOfDeveloper := true
|
||||||
|
if len(a) > 1 && a[0][0] == 'o' {
|
||||||
|
useSystemInsteadOfDeveloper = false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m.Role {
|
||||||
|
case RoleSystem:
|
||||||
|
if useSystemInsteadOfDeveloper {
|
||||||
|
res = openai.ChatCompletionMessageParamUnion{
|
||||||
|
OfSystem: &openai.ChatCompletionSystemMessageParam{
|
||||||
|
Content: openai.ChatCompletionSystemMessageParamContentUnion{
|
||||||
|
OfString: textContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = openai.ChatCompletionMessageParamUnion{
|
||||||
|
OfDeveloper: &openai.ChatCompletionDeveloperMessageParam{
|
||||||
|
Content: openai.ChatCompletionDeveloperMessageParamContentUnion{
|
||||||
|
OfString: textContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case RoleUser:
|
||||||
|
var name param.Opt[string]
|
||||||
|
if m.Name != "" {
|
||||||
|
name = openai.String(m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
res = openai.ChatCompletionMessageParamUnion{
|
||||||
|
OfUser: &openai.ChatCompletionUserMessageParam{
|
||||||
|
Name: name,
|
||||||
|
Content: openai.ChatCompletionUserMessageParamContentUnion{
|
||||||
|
OfString: textContent,
|
||||||
|
OfArrayOfContentParts: arrayOfContentParts,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
case RoleAssistant:
|
||||||
|
var name param.Opt[string]
|
||||||
|
if m.Name != "" {
|
||||||
|
name = openai.String(m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
res = openai.ChatCompletionMessageParamUnion{
|
||||||
|
OfAssistant: &openai.ChatCompletionAssistantMessageParam{
|
||||||
|
Name: name,
|
||||||
|
Content: openai.ChatCompletionAssistantMessageParamContentUnion{
|
||||||
|
OfString: textContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return []openai.ChatCompletionMessageParamUnion{res}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolCall struct {
|
type ToolCall struct {
|
||||||
@@ -36,16 +196,73 @@ type ToolCall struct {
|
|||||||
FunctionCall FunctionCall
|
FunctionCall FunctionCall
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseChoice struct {
|
func (t ToolCall) toRaw() map[string]any {
|
||||||
Index int
|
res := map[string]any{
|
||||||
Role Role
|
"id": t.ID,
|
||||||
Content string
|
}
|
||||||
Refusal string
|
|
||||||
Name string
|
res["function"] = t.FunctionCall.toRaw()
|
||||||
Calls []ToolCall
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
type Response struct {
|
|
||||||
Choices []ResponseChoice
|
func (t ToolCall) toChatCompletionMessages(_ string) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
return []openai.ChatCompletionMessageParamUnion{{
|
||||||
|
OfAssistant: &openai.ChatCompletionAssistantMessageParam{
|
||||||
|
ToolCalls: []openai.ChatCompletionMessageToolCallParam{
|
||||||
|
{
|
||||||
|
ID: t.ID,
|
||||||
|
Function: openai.ChatCompletionMessageToolCallFunctionParam{
|
||||||
|
Name: t.FunctionCall.Name,
|
||||||
|
Arguments: t.FunctionCall.Arguments,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolCallResponse struct {
|
||||||
|
ID string
|
||||||
|
Result any
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolCallResponse) toRaw() map[string]any {
|
||||||
|
res := map[string]any{
|
||||||
|
"id": t.ID,
|
||||||
|
"result": t.Result,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Error != nil {
|
||||||
|
res["error"] = t.Error.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolCallResponse) toChatCompletionMessages(_ string) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
var refusal string
|
||||||
|
if t.Error != nil {
|
||||||
|
refusal = t.Error.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if refusal != "" {
|
||||||
|
if t.Result != "" {
|
||||||
|
t.Result = fmt.Sprint(t.Result) + " (error in execution: " + refusal + ")"
|
||||||
|
} else {
|
||||||
|
t.Result = "error in execution:" + refusal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []openai.ChatCompletionMessageParamUnion{{
|
||||||
|
OfTool: &openai.ChatCompletionToolMessageParam{
|
||||||
|
ToolCallID: t.ID,
|
||||||
|
Content: openai.ChatCompletionToolMessageParamContentUnion{
|
||||||
|
OfString: openai.String(fmt.Sprint(t.Result)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatCompletion interface {
|
type ChatCompletion interface {
|
||||||
|
123
openai.go
123
openai.go
@@ -3,119 +3,87 @@ package go_llm
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
oai "github.com/sashabaranov/go-openai"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/openai/openai-go"
|
||||||
|
"github.com/openai/openai-go/option"
|
||||||
|
"github.com/openai/openai-go/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
type openaiImpl struct {
|
type openaiImpl struct {
|
||||||
key string
|
key string
|
||||||
model string
|
model string
|
||||||
|
baseUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ LLM = openaiImpl{}
|
var _ LLM = openaiImpl{}
|
||||||
|
|
||||||
func (o openaiImpl) requestToOpenAIRequest(request Request) oai.ChatCompletionRequest {
|
func (o openaiImpl) newRequestToOpenAIRequest(request Request) openai.ChatCompletionNewParams {
|
||||||
res := oai.ChatCompletionRequest{
|
res := openai.ChatCompletionNewParams{
|
||||||
Model: o.model,
|
Model: o.model,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, msg := range request.Messages {
|
for _, i := range request.Conversation {
|
||||||
m := oai.ChatCompletionMessage{
|
res.Messages = append(res.Messages, i.toChatCompletionMessages(o.model)...)
|
||||||
Content: msg.Text,
|
|
||||||
Role: string(msg.Role),
|
|
||||||
Name: msg.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, img := range msg.Images {
|
|
||||||
if img.Base64 != "" {
|
|
||||||
m.MultiContent = append(m.MultiContent, oai.ChatMessagePart{
|
|
||||||
Type: "image_url",
|
|
||||||
ImageURL: &oai.ChatMessageImageURL{
|
|
||||||
URL: fmt.Sprintf("data:%s;base64,%s", img.ContentType, img.Base64),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if img.Url != "" {
|
|
||||||
m.MultiContent = append(m.MultiContent, oai.ChatMessagePart{
|
|
||||||
Type: "image_url",
|
|
||||||
ImageURL: &oai.ChatMessageImageURL{
|
|
||||||
URL: img.Url,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// openai does not allow Content and MultiContent to be set at the same time, so we need to check
|
|
||||||
if len(m.MultiContent) > 0 && m.Content != "" {
|
|
||||||
m.MultiContent = append([]oai.ChatMessagePart{{
|
|
||||||
Type: "text",
|
|
||||||
Text: m.Content,
|
|
||||||
}}, m.MultiContent...)
|
|
||||||
|
|
||||||
m.Content = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Messages = append(res.Messages, m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tool := range request.Toolbox.funcs {
|
for _, msg := range request.Messages {
|
||||||
res.Tools = append(res.Tools, oai.Tool{
|
res.Messages = append(res.Messages, msg.toChatCompletionMessages(o.model)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tool := range request.Toolbox.functions {
|
||||||
|
res.Tools = append(res.Tools, openai.ChatCompletionToolParam{
|
||||||
Type: "function",
|
Type: "function",
|
||||||
Function: &oai.FunctionDefinition{
|
Function: shared.FunctionDefinitionParam{
|
||||||
Name: tool.Name,
|
Name: tool.Name,
|
||||||
Description: tool.Description,
|
Description: openai.String(tool.Description),
|
||||||
Strict: tool.Strict,
|
Strict: openai.Bool(tool.Strict),
|
||||||
Parameters: tool.Parameters.Definition(),
|
Parameters: tool.Parameters.OpenAIParameters(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("tool:", tool.Name, tool.Description, tool.Strict, tool.Parameters.Definition())
|
if request.Toolbox.RequiresTool() {
|
||||||
|
res.ToolChoice = openai.ChatCompletionToolChoiceOptionUnionParam{
|
||||||
|
OfAuto: openai.String("required"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.Temperature != nil {
|
if request.Temperature != nil {
|
||||||
res.Temperature = *request.Temperature
|
res.Temperature = openai.Float(*request.Temperature)
|
||||||
}
|
|
||||||
|
|
||||||
// is this an o1-* model?
|
|
||||||
isO1 := strings.Split(o.model, "-")[0] == "o1"
|
|
||||||
|
|
||||||
if isO1 {
|
|
||||||
// o1 models do not support system messages, so if any messages are system messages, we need to convert them to
|
|
||||||
// user messages
|
|
||||||
|
|
||||||
for i, msg := range res.Messages {
|
|
||||||
if msg.Role == "system" {
|
|
||||||
res.Messages[i].Role = "user"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o openaiImpl) responseToLLMResponse(response oai.ChatCompletionResponse) Response {
|
func (o openaiImpl) responseToLLMResponse(response *openai.ChatCompletion) Response {
|
||||||
res := Response{}
|
var res Response
|
||||||
|
|
||||||
|
if response == nil {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Choices) == 0 {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
for _, choice := range response.Choices {
|
for _, choice := range response.Choices {
|
||||||
var toolCalls []ToolCall
|
var toolCalls []ToolCall
|
||||||
for _, call := range choice.Message.ToolCalls {
|
for _, call := range choice.Message.ToolCalls {
|
||||||
fmt.Println("responseToLLMResponse: call:", call.Function.Arguments)
|
|
||||||
toolCall := ToolCall{
|
toolCall := ToolCall{
|
||||||
ID: call.ID,
|
ID: call.ID,
|
||||||
FunctionCall: FunctionCall{
|
FunctionCall: FunctionCall{
|
||||||
Name: call.Function.Name,
|
Name: call.Function.Name,
|
||||||
Arguments: call.Function.Arguments,
|
Arguments: strings.TrimSpace(call.Function.Arguments),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("toolCall.FunctionCall.Arguments:", toolCall.FunctionCall.Arguments)
|
|
||||||
|
|
||||||
toolCalls = append(toolCalls, toolCall)
|
toolCalls = append(toolCalls, toolCall)
|
||||||
|
|
||||||
}
|
}
|
||||||
res.Choices = append(res.Choices, ResponseChoice{
|
res.Choices = append(res.Choices, ResponseChoice{
|
||||||
Content: choice.Message.Content,
|
Content: choice.Message.Content,
|
||||||
Role: Role(choice.Message.Role),
|
Role: Role(choice.Message.Role),
|
||||||
Name: choice.Message.Name,
|
|
||||||
Refusal: choice.Message.Refusal,
|
Refusal: choice.Message.Refusal,
|
||||||
Calls: toolCalls,
|
Calls: toolCalls,
|
||||||
})
|
})
|
||||||
@@ -125,13 +93,20 @@ func (o openaiImpl) responseToLLMResponse(response oai.ChatCompletionResponse) R
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o openaiImpl) ChatComplete(ctx context.Context, request Request) (Response, error) {
|
func (o openaiImpl) ChatComplete(ctx context.Context, request Request) (Response, error) {
|
||||||
cl := oai.NewClient(o.key)
|
var opts = []option.RequestOption{
|
||||||
|
option.WithAPIKey(o.key),
|
||||||
|
}
|
||||||
|
|
||||||
req := o.requestToOpenAIRequest(request)
|
if o.baseUrl != "" {
|
||||||
|
opts = append(opts, option.WithBaseURL(o.baseUrl))
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := cl.CreateChatCompletion(ctx, req)
|
cl := openai.NewClient(opts...)
|
||||||
|
|
||||||
fmt.Println("resp:", fmt.Sprintf("%#v", resp))
|
req := o.newRequestToOpenAIRequest(request)
|
||||||
|
|
||||||
|
resp, err := cl.Chat.Completions.New(ctx, req)
|
||||||
|
//resp, err := cl.CreateChatCompletion(ctx, req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Response{}, fmt.Errorf("unhandled openaiImpl error: %w", err)
|
return Response{}, fmt.Errorf("unhandled openaiImpl error: %w", err)
|
||||||
|
50
parse.go
Normal file
50
parse.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package go_llm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Providers are the allowed shortcuts in the providers, e.g.: if you set { "openai": OpenAI("key") } that'll allow
|
||||||
|
// for the "openai" provider to be used when parsed.
|
||||||
|
type Providers map[string]LLM
|
||||||
|
|
||||||
|
// Parse will parse the provided input and attempt to return a LLM chat completion interface.
|
||||||
|
// Input should be in the provided format:
|
||||||
|
// - provider/modelname
|
||||||
|
//
|
||||||
|
// where provider is a key inside Providers, and the modelname being passed to the LLM interface's GetModel
|
||||||
|
func (providers Providers) Parse(input string) ChatCompletion {
|
||||||
|
sections := strings.Split(input, "/")
|
||||||
|
|
||||||
|
var provider LLM
|
||||||
|
var ok bool
|
||||||
|
var modelVersion string
|
||||||
|
|
||||||
|
if len(sections) < 2 {
|
||||||
|
// is there a default provider?
|
||||||
|
provider, ok = providers["default"]
|
||||||
|
if !ok {
|
||||||
|
panic("expected format: \"provider/model\" or provide a \"default\" provider to the Parse callback")
|
||||||
|
}
|
||||||
|
|
||||||
|
modelVersion = sections[0]
|
||||||
|
} else {
|
||||||
|
provider, ok = providers[sections[0]]
|
||||||
|
modelVersion = sections[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
panic("expected format: \"provider/model\" or provide a \"default\" provider to the Parse callback")
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider == nil {
|
||||||
|
panic("unknown provider: " + sections[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := provider.ModelVersion(modelVersion)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
48
request.go
Normal file
48
request.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package go_llm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/openai/openai-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rawAble interface {
|
||||||
|
toRaw() map[string]any
|
||||||
|
fromRaw(raw map[string]any) Input
|
||||||
|
}
|
||||||
|
|
||||||
|
type Input interface {
|
||||||
|
toChatCompletionMessages(model string) []openai.ChatCompletionMessageParamUnion
|
||||||
|
}
|
||||||
|
type Request struct {
|
||||||
|
Conversation []Input
|
||||||
|
Messages []Message
|
||||||
|
Toolbox ToolBox
|
||||||
|
Temperature *float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextRequest will take the current request's conversation, messages, the response, and any tool results, and
|
||||||
|
// return a new request with the conversation updated to include the response and tool results.
|
||||||
|
func (req Request) NextRequest(resp ResponseChoice, toolResults []ToolCallResponse) Request {
|
||||||
|
var res Request
|
||||||
|
|
||||||
|
res.Toolbox = req.Toolbox
|
||||||
|
res.Temperature = req.Temperature
|
||||||
|
|
||||||
|
res.Conversation = make([]Input, len(req.Conversation))
|
||||||
|
copy(res.Conversation, req.Conversation)
|
||||||
|
|
||||||
|
// now for every input message, convert those to an Input to add to the conversation
|
||||||
|
for _, msg := range req.Messages {
|
||||||
|
res.Conversation = append(res.Conversation, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Content != "" || resp.Refusal != "" || len(resp.Calls) > 0 {
|
||||||
|
res.Conversation = append(res.Conversation, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are tool results, then we need to add those to the conversation
|
||||||
|
for _, result := range toolResults {
|
||||||
|
res.Conversation = append(res.Conversation, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
84
response.go
Normal file
84
response.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package go_llm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/openai/openai-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResponseChoice struct {
|
||||||
|
Index int
|
||||||
|
Role Role
|
||||||
|
Content string
|
||||||
|
Refusal string
|
||||||
|
Name string
|
||||||
|
Calls []ToolCall
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResponseChoice) toRaw() map[string]any {
|
||||||
|
res := map[string]any{
|
||||||
|
"index": r.Index,
|
||||||
|
"role": r.Role,
|
||||||
|
"content": r.Content,
|
||||||
|
"refusal": r.Refusal,
|
||||||
|
"name": r.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
calls := make([]map[string]any, 0, len(r.Calls))
|
||||||
|
for _, call := range r.Calls {
|
||||||
|
calls = append(calls, call.toRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
res["tool_calls"] = calls
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResponseChoice) toChatCompletionMessages(_ string) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
var as openai.ChatCompletionAssistantMessageParam
|
||||||
|
|
||||||
|
if r.Name != "" {
|
||||||
|
as.Name = openai.String(r.Name)
|
||||||
|
}
|
||||||
|
if r.Refusal != "" {
|
||||||
|
as.Refusal = openai.String(r.Refusal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Content != "" {
|
||||||
|
as.Content.OfString = openai.String(r.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, call := range r.Calls {
|
||||||
|
as.ToolCalls = append(as.ToolCalls, openai.ChatCompletionMessageToolCallParam{
|
||||||
|
ID: call.ID,
|
||||||
|
Function: openai.ChatCompletionMessageToolCallFunctionParam{
|
||||||
|
Name: call.FunctionCall.Name,
|
||||||
|
Arguments: call.FunctionCall.Arguments,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return []openai.ChatCompletionMessageParamUnion{
|
||||||
|
{
|
||||||
|
OfAssistant: &as,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResponseChoice) toInput() []Input {
|
||||||
|
var res []Input
|
||||||
|
|
||||||
|
for _, call := range r.Calls {
|
||||||
|
res = append(res, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Content != "" || r.Refusal != "" {
|
||||||
|
res = append(res, Message{
|
||||||
|
Role: RoleAssistant,
|
||||||
|
Text: r.Content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Choices []ResponseChoice
|
||||||
|
}
|
@@ -3,8 +3,6 @@ package schema
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetType will, given an interface{} that is a struct (NOT a pointer to a struct), return the Type of the struct that
|
// GetType will, given an interface{} that is a struct (NOT a pointer to a struct), return the Type of the struct that
|
||||||
@@ -27,23 +25,28 @@ func getFromType(t reflect.Type, b basic) Type {
|
|||||||
|
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
b.DataType = jsonschema.String
|
b.DataType = TypeString
|
||||||
|
b.typeName = "string"
|
||||||
return b
|
return b
|
||||||
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
b.DataType = jsonschema.Integer
|
b.DataType = TypeInteger
|
||||||
|
b.typeName = "integer"
|
||||||
return b
|
return b
|
||||||
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
b.DataType = jsonschema.Integer
|
b.DataType = TypeInteger
|
||||||
|
b.typeName = "integer"
|
||||||
return b
|
return b
|
||||||
|
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
b.DataType = jsonschema.Number
|
b.DataType = TypeNumber
|
||||||
|
b.typeName = "number"
|
||||||
return b
|
return b
|
||||||
|
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
b.DataType = jsonschema.Boolean
|
b.DataType = TypeBoolean
|
||||||
|
b.typeName = "boolean"
|
||||||
return b
|
return b
|
||||||
|
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
@@ -89,6 +92,8 @@ func getField(f reflect.StructField, index int) Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.DataType = TypeString
|
||||||
|
b.typeName = "string"
|
||||||
return enum{
|
return enum{
|
||||||
basic: b,
|
basic: b,
|
||||||
values: vals,
|
values: vals,
|
||||||
@@ -99,15 +104,26 @@ func getField(f reflect.StructField, index int) Type {
|
|||||||
return getFromType(t, b)
|
return getFromType(t, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getObject(t reflect.Type) object {
|
func getObject(t reflect.Type) Object {
|
||||||
fields := make(map[string]Type, t.NumField())
|
fields := make(map[string]Type, t.NumField())
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
field := t.Field(i)
|
field := t.Field(i)
|
||||||
fields[field.Name] = getField(field, i)
|
|
||||||
|
if field.Anonymous {
|
||||||
|
// if the field is anonymous, we need to get the fields of the anonymous struct
|
||||||
|
// and add them to the object
|
||||||
|
anon := getObject(field.Type)
|
||||||
|
for k, v := range anon.fields {
|
||||||
|
fields[k] = v
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
fields[field.Name] = getField(field, i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return object{
|
return Object{
|
||||||
basic: basic{DataType: jsonschema.Object},
|
basic: basic{DataType: TypeObject, typeName: "object"},
|
||||||
fields: fields,
|
fields: fields,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +131,8 @@ func getObject(t reflect.Type) object {
|
|||||||
func getArray(t reflect.Type) array {
|
func getArray(t reflect.Type) array {
|
||||||
res := array{
|
res := array{
|
||||||
basic: basic{
|
basic: basic{
|
||||||
DataType: jsonschema.Array,
|
DataType: TypeArray,
|
||||||
|
typeName: "array",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,7 +4,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
"github.com/google/generative-ai-go/genai"
|
||||||
|
"github.com/openai/openai-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type array struct {
|
type array struct {
|
||||||
@@ -14,17 +15,28 @@ type array struct {
|
|||||||
items Type
|
items Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a array) SchemaType() jsonschema.DataType {
|
func (a array) OpenAIParameters() openai.FunctionParameters {
|
||||||
return jsonschema.Array
|
return openai.FunctionParameters{
|
||||||
|
"type": "array",
|
||||||
|
"description": a.Description(),
|
||||||
|
"items": a.items.OpenAIParameters(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a array) Definition() jsonschema.Definition {
|
func (a array) GoogleParameters() *genai.Schema {
|
||||||
def := a.basic.Definition()
|
return &genai.Schema{
|
||||||
def.Type = jsonschema.Array
|
Type: genai.TypeArray,
|
||||||
i := a.items.Definition()
|
Description: a.Description(),
|
||||||
def.Items = &i
|
Items: a.items.GoogleParameters(),
|
||||||
def.AdditionalProperties = false
|
}
|
||||||
return def
|
}
|
||||||
|
|
||||||
|
func (a array) AnthropicInputSchema() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"type": "array",
|
||||||
|
"description": a.Description(),
|
||||||
|
"items": a.items.AnthropicInputSchema(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a array) FromAny(val any) (reflect.Value, error) {
|
func (a array) FromAny(val any) (reflect.Value, error) {
|
||||||
|
@@ -5,14 +5,27 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
"github.com/google/generative-ai-go/genai"
|
||||||
|
"github.com/openai/openai-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// just enforcing that basic implements Type
|
// just enforcing that basic implements Type
|
||||||
var _ Type = basic{}
|
var _ Type = basic{}
|
||||||
|
|
||||||
|
type DataType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeString DataType = "string"
|
||||||
|
TypeInteger DataType = "integer"
|
||||||
|
TypeNumber DataType = "number"
|
||||||
|
TypeBoolean DataType = "boolean"
|
||||||
|
TypeObject DataType = "object"
|
||||||
|
TypeArray DataType = "array"
|
||||||
|
)
|
||||||
|
|
||||||
type basic struct {
|
type basic struct {
|
||||||
jsonschema.DataType
|
DataType
|
||||||
|
typeName string
|
||||||
|
|
||||||
// index is the position of the parameter in the StructField of the function's parameter struct
|
// index is the position of the parameter in the StructField of the function's parameter struct
|
||||||
index int
|
index int
|
||||||
@@ -25,17 +38,64 @@ type basic struct {
|
|||||||
description string
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b basic) SchemaType() jsonschema.DataType {
|
func (b basic) OpenAIParameters() openai.FunctionParameters {
|
||||||
return b.DataType
|
return openai.FunctionParameters{
|
||||||
|
"type": b.typeName,
|
||||||
|
"description": b.description,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b basic) Definition() jsonschema.Definition {
|
func (b basic) GoogleParameters() *genai.Schema {
|
||||||
return jsonschema.Definition{
|
var t = genai.TypeUnspecified
|
||||||
Type: b.DataType,
|
|
||||||
|
switch b.DataType {
|
||||||
|
case TypeString:
|
||||||
|
t = genai.TypeString
|
||||||
|
case TypeInteger:
|
||||||
|
t = genai.TypeInteger
|
||||||
|
case TypeNumber:
|
||||||
|
t = genai.TypeNumber
|
||||||
|
case TypeBoolean:
|
||||||
|
t = genai.TypeBoolean
|
||||||
|
case TypeObject:
|
||||||
|
t = genai.TypeObject
|
||||||
|
case TypeArray:
|
||||||
|
t = genai.TypeArray
|
||||||
|
default:
|
||||||
|
t = genai.TypeUnspecified
|
||||||
|
}
|
||||||
|
return &genai.Schema{
|
||||||
|
Type: t,
|
||||||
Description: b.description,
|
Description: b.description,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b basic) AnthropicInputSchema() map[string]any {
|
||||||
|
var t = "string"
|
||||||
|
|
||||||
|
switch b.DataType {
|
||||||
|
case TypeString:
|
||||||
|
t = "string"
|
||||||
|
case TypeInteger:
|
||||||
|
t = "integer"
|
||||||
|
case TypeNumber:
|
||||||
|
t = "number"
|
||||||
|
case TypeBoolean:
|
||||||
|
t = "boolean"
|
||||||
|
case TypeObject:
|
||||||
|
t = "object"
|
||||||
|
case TypeArray:
|
||||||
|
t = "array"
|
||||||
|
default:
|
||||||
|
t = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]any{
|
||||||
|
"type": t,
|
||||||
|
"description": b.description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b basic) Required() bool {
|
func (b basic) Required() bool {
|
||||||
return b.required
|
return b.required
|
||||||
}
|
}
|
||||||
@@ -48,12 +108,12 @@ func (b basic) FromAny(val any) (reflect.Value, error) {
|
|||||||
v := reflect.ValueOf(val)
|
v := reflect.ValueOf(val)
|
||||||
|
|
||||||
switch b.DataType {
|
switch b.DataType {
|
||||||
case jsonschema.String:
|
case TypeString:
|
||||||
var val = v.String()
|
var val = v.String()
|
||||||
|
|
||||||
return reflect.ValueOf(val), nil
|
return reflect.ValueOf(val), nil
|
||||||
|
|
||||||
case jsonschema.Integer:
|
case TypeInteger:
|
||||||
if v.Kind() == reflect.Float64 {
|
if v.Kind() == reflect.Float64 {
|
||||||
return v.Convert(reflect.TypeOf(int(0))), nil
|
return v.Convert(reflect.TypeOf(int(0))), nil
|
||||||
} else if v.Kind() != reflect.Int {
|
} else if v.Kind() != reflect.Int {
|
||||||
@@ -62,7 +122,7 @@ func (b basic) FromAny(val any) (reflect.Value, error) {
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case jsonschema.Number:
|
case TypeNumber:
|
||||||
if v.Kind() == reflect.Float64 {
|
if v.Kind() == reflect.Float64 {
|
||||||
return v.Convert(reflect.TypeOf(float64(0))), nil
|
return v.Convert(reflect.TypeOf(float64(0))), nil
|
||||||
} else if v.Kind() != reflect.Float64 {
|
} else if v.Kind() != reflect.Float64 {
|
||||||
@@ -71,7 +131,7 @@ func (b basic) FromAny(val any) (reflect.Value, error) {
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case jsonschema.Boolean:
|
case TypeBoolean:
|
||||||
if v.Kind() == reflect.Bool {
|
if v.Kind() == reflect.Bool {
|
||||||
return v, nil
|
return v, nil
|
||||||
} else if v.Kind() == reflect.String {
|
} else if v.Kind() == reflect.String {
|
||||||
|
@@ -3,10 +3,10 @@ package schema
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"github.com/google/generative-ai-go/genai"
|
||||||
|
"github.com/openai/openai-go"
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type enum struct {
|
type enum struct {
|
||||||
@@ -15,14 +15,28 @@ type enum struct {
|
|||||||
values []string
|
values []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e enum) SchemaType() jsonschema.DataType {
|
func (e enum) FunctionParameters() openai.FunctionParameters {
|
||||||
return jsonschema.String
|
return openai.FunctionParameters{
|
||||||
|
"type": "string",
|
||||||
|
"description": e.Description(),
|
||||||
|
"enum": e.values,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e enum) Definition() jsonschema.Definition {
|
func (e enum) GoogleParameters() *genai.Schema {
|
||||||
def := e.basic.Definition()
|
return &genai.Schema{
|
||||||
def.Enum = e.values
|
Type: genai.TypeString,
|
||||||
return def
|
Description: e.Description(),
|
||||||
|
Enum: e.values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e enum) AnthropicInputSchema() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"type": "string",
|
||||||
|
"description": e.Description(),
|
||||||
|
"enum": e.values,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e enum) FromAny(val any) (reflect.Value, error) {
|
func (e enum) FromAny(val any) (reflect.Value, error) {
|
||||||
|
123
schema/object.go
123
schema/object.go
@@ -4,34 +4,125 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
"github.com/google/generative-ai-go/genai"
|
||||||
|
"github.com/openai/openai-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type object struct {
|
const (
|
||||||
|
// SyntheticFieldPrefix is any prefix that is added to any synthetic fields that are added to the object, to prevent
|
||||||
|
// collisions with the fields in the struct.
|
||||||
|
SyntheticFieldPrefix = "__"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Object struct {
|
||||||
basic
|
basic
|
||||||
|
|
||||||
ref reflect.Type
|
ref reflect.Type
|
||||||
|
|
||||||
fields map[string]Type
|
fields map[string]Type
|
||||||
|
|
||||||
|
// syntheticFields are fields that are not in the struct but are generated by a system.
|
||||||
|
synetheticFields map[string]Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o object) SchemaType() jsonschema.DataType {
|
func (o Object) WithSyntheticField(name string, description string) Object {
|
||||||
return jsonschema.Object
|
if o.synetheticFields == nil {
|
||||||
}
|
o.synetheticFields = map[string]Type{}
|
||||||
|
|
||||||
func (o object) Definition() jsonschema.Definition {
|
|
||||||
def := o.basic.Definition()
|
|
||||||
def.Type = jsonschema.Object
|
|
||||||
def.Properties = make(map[string]jsonschema.Definition)
|
|
||||||
for k, v := range o.fields {
|
|
||||||
def.Properties[k] = v.Definition()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def.AdditionalProperties = false
|
o.synetheticFields[name] = basic{
|
||||||
return def
|
DataType: TypeString,
|
||||||
|
typeName: "string",
|
||||||
|
index: -1,
|
||||||
|
required: false,
|
||||||
|
description: description,
|
||||||
|
}
|
||||||
|
|
||||||
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o object) FromAny(val any) (reflect.Value, error) {
|
func (o Object) SyntheticFields() map[string]Type {
|
||||||
|
return o.synetheticFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Object) OpenAIParameters() openai.FunctionParameters {
|
||||||
|
var properties = map[string]openai.FunctionParameters{}
|
||||||
|
var required []string
|
||||||
|
for k, v := range o.fields {
|
||||||
|
properties[k] = v.OpenAIParameters()
|
||||||
|
if v.Required() {
|
||||||
|
required = append(required, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range o.synetheticFields {
|
||||||
|
properties[SyntheticFieldPrefix+k] = v.OpenAIParameters()
|
||||||
|
if v.Required() {
|
||||||
|
required = append(required, SyntheticFieldPrefix+k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = openai.FunctionParameters{
|
||||||
|
"type": "object",
|
||||||
|
"description": o.Description(),
|
||||||
|
"properties": properties,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(required) > 0 {
|
||||||
|
res["required"] = required
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Object) GoogleParameters() *genai.Schema {
|
||||||
|
var properties = map[string]*genai.Schema{}
|
||||||
|
var required []string
|
||||||
|
for k, v := range o.fields {
|
||||||
|
properties[k] = v.GoogleParameters()
|
||||||
|
if v.Required() {
|
||||||
|
required = append(required, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = &genai.Schema{
|
||||||
|
Type: genai.TypeObject,
|
||||||
|
Description: o.Description(),
|
||||||
|
Properties: properties,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(required) > 0 {
|
||||||
|
res.Required = required
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Object) AnthropicInputSchema() map[string]any {
|
||||||
|
var properties = map[string]any{}
|
||||||
|
var required []string
|
||||||
|
for k, v := range o.fields {
|
||||||
|
properties[k] = v.AnthropicInputSchema()
|
||||||
|
if v.Required() {
|
||||||
|
required = append(required, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"description": o.Description(),
|
||||||
|
"properties": properties,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(required) > 0 {
|
||||||
|
res["required"] = required
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromAny converts the value from any to the correct type, returning the value, and an error if any
|
||||||
|
func (o Object) FromAny(val any) (reflect.Value, error) {
|
||||||
// if the value is nil, we can't do anything
|
// if the value is nil, we can't do anything
|
||||||
if val == nil {
|
if val == nil {
|
||||||
return reflect.Value{}, nil
|
return reflect.Value{}, nil
|
||||||
@@ -68,7 +159,7 @@ func (o object) FromAny(val any) (reflect.Value, error) {
|
|||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o object) SetValueOnField(obj reflect.Value, val reflect.Value) {
|
func (o Object) SetValueOnField(obj reflect.Value, val reflect.Value) {
|
||||||
// if this basic type is not required that means it's a pointer type so we need to set the value to the address of the value
|
// if this basic type is not required that means it's a pointer type so we need to set the value to the address of the value
|
||||||
if !o.required {
|
if !o.required {
|
||||||
val = val.Addr()
|
val = val.Addr()
|
||||||
|
@@ -3,12 +3,17 @@ package schema
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
"github.com/google/generative-ai-go/genai"
|
||||||
|
"github.com/openai/openai-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Type interface {
|
type Type interface {
|
||||||
SchemaType() jsonschema.DataType
|
OpenAIParameters() openai.FunctionParameters
|
||||||
Definition() jsonschema.Definition
|
GoogleParameters() *genai.Schema
|
||||||
|
AnthropicInputSchema() map[string]any
|
||||||
|
|
||||||
|
//SchemaType() jsonschema.DataType
|
||||||
|
//Definition() jsonschema.Definition
|
||||||
|
|
||||||
Required() bool
|
Required() bool
|
||||||
Description() string
|
Description() string
|
||||||
|
155
toolbox.go
155
toolbox.go
@@ -4,56 +4,82 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/sashabaranov/go-openai"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToolBox is a collection of tools that OpenAI can use to execute functions.
|
// ToolBox is a collection of tools that OpenAI can use to execute functions.
|
||||||
// It is a wrapper around a collection of functions, and provides a way to automatically call the correct function with
|
// It is a wrapper around a collection of functions, and provides a way to automatically call the correct function with
|
||||||
// the correct parameters.
|
// the correct parameters.
|
||||||
type ToolBox struct {
|
type ToolBox struct {
|
||||||
funcs []Function
|
functions map[string]Function
|
||||||
names map[string]Function
|
dontRequireTool bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewToolBox(fns ...*Function) *ToolBox {
|
func NewToolBox(fns ...Function) ToolBox {
|
||||||
res := ToolBox{
|
res := ToolBox{
|
||||||
funcs: []Function{},
|
functions: map[string]Function{},
|
||||||
names: map[string]Function{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range fns {
|
for _, f := range fns {
|
||||||
o := *f
|
res.functions[f.Name] = f
|
||||||
res.names[o.Name] = o
|
|
||||||
res.funcs = append(res.funcs, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ToolBox) WithFunction(f Function) *ToolBox {
|
|
||||||
t2 := *t
|
|
||||||
t2.names[f.Name] = f
|
|
||||||
t2.funcs = append(t2.funcs, f)
|
|
||||||
|
|
||||||
return &t2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToOpenAI will convert the current ToolBox to a slice of openai.Tool, which can be used to send to the OpenAI API.
|
|
||||||
func (t *ToolBox) toOpenAI() []openai.Tool {
|
|
||||||
var res []openai.Tool
|
|
||||||
|
|
||||||
for _, f := range t.funcs {
|
|
||||||
res = append(res, openai.Tool{
|
|
||||||
Type: "function",
|
|
||||||
Function: f.toOpenAIFunction(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ToolBox) ToToolChoice() any {
|
func (t ToolBox) Functions() []Function {
|
||||||
if len(t.funcs) == 0 {
|
var res []Function
|
||||||
|
|
||||||
|
for _, f := range t.functions {
|
||||||
|
res = append(res, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) WithFunction(f Function) ToolBox {
|
||||||
|
t.functions[f.Name] = f
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) WithFunctions(fns ...Function) ToolBox {
|
||||||
|
for _, f := range fns {
|
||||||
|
t.functions[f.Name] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) WithSyntheticFieldsAddedToAllFunctions(fieldsAndDescriptions map[string]string) ToolBox {
|
||||||
|
for k, v := range t.functions {
|
||||||
|
t.functions[k] = v.WithSyntheticFields(fieldsAndDescriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) ForEachFunction(fn func(f Function)) {
|
||||||
|
for _, f := range t.functions {
|
||||||
|
fn(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) WithFunctionRemoved(name string) ToolBox {
|
||||||
|
delete(t.functions, name)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) WithRequireTool(val bool) ToolBox {
|
||||||
|
t.dontRequireTool = !val
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) RequiresTool() bool {
|
||||||
|
return !t.dontRequireTool && len(t.functions) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) ToToolChoice() any {
|
||||||
|
if len(t.functions) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +90,8 @@ var (
|
|||||||
ErrFunctionNotFound = errors.New("function not found")
|
ErrFunctionNotFound = errors.New("function not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *ToolBox) ExecuteFunction(ctx context.Context, functionName string, params string) (string, error) {
|
func (t ToolBox) executeFunction(ctx *Context, functionName string, params string) (any, error) {
|
||||||
f, ok := t.names[functionName]
|
f, ok := t.functions[functionName]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", newError(ErrFunctionNotFound, fmt.Errorf("function \"%s\" not found", functionName))
|
return "", newError(ErrFunctionNotFound, fmt.Errorf("function \"%s\" not found", functionName))
|
||||||
@@ -74,6 +100,61 @@ func (t *ToolBox) ExecuteFunction(ctx context.Context, functionName string, para
|
|||||||
return f.Execute(ctx, params)
|
return f.Execute(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ToolBox) Execute(ctx context.Context, toolCall ToolCall) (string, error) {
|
func (t ToolBox) Execute(ctx *Context, toolCall ToolCall) (any, error) {
|
||||||
return t.ExecuteFunction(ctx, toolCall.FunctionCall.Name, toolCall.FunctionCall.Arguments)
|
return t.executeFunction(ctx.WithToolCall(&toolCall), toolCall.FunctionCall.Name, toolCall.FunctionCall.Arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolBox) GetSyntheticParametersFromFunctionContext(ctx context.Context) map[string]string {
|
||||||
|
val := ctx.Value("syntheticParameters")
|
||||||
|
|
||||||
|
if val == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
syntheticParameters, ok := val.(map[string]string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return syntheticParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteCallbacks will execute all the tool calls in the given list, and call the given callbacks when a new function is created, and when a function is finished.
|
||||||
|
// OnNewFunction is called when a new function is created
|
||||||
|
// OnFunctionFinished is called when a function is finished
|
||||||
|
func (t ToolBox) ExecuteCallbacks(ctx *Context, toolCalls []ToolCall, OnNewFunction func(ctx context.Context, funcName string, parameter string) (any, error), OnFunctionFinished func(ctx context.Context, funcName string, parameter string, result any, err error, newFunctionResult any) error) ([]ToolCallResponse, error) {
|
||||||
|
var res []ToolCallResponse
|
||||||
|
|
||||||
|
for _, call := range toolCalls {
|
||||||
|
ctx := ctx.WithToolCall(&call)
|
||||||
|
if call.FunctionCall.Name == "" {
|
||||||
|
return nil, newError(ErrFunctionNotFound, errors.New("function name is empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var arg any
|
||||||
|
if OnNewFunction != nil {
|
||||||
|
var err error
|
||||||
|
arg, err = OnNewFunction(ctx, call.FunctionCall.Name, call.FunctionCall.Arguments)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError(ErrFunctionNotFound, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out, err := t.Execute(ctx, call)
|
||||||
|
|
||||||
|
if OnFunctionFinished != nil {
|
||||||
|
err := OnFunctionFinished(ctx, call.FunctionCall.Name, call.FunctionCall.Arguments, out, err, arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError(ErrFunctionNotFound, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, ToolCallResponse{
|
||||||
|
ID: call.ID,
|
||||||
|
Result: out,
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user