diff --git a/conversation/go/sdk/README.md b/conversation/go/sdk/README.md index 429feab41..e0c4a3f5c 100644 --- a/conversation/go/sdk/README.md +++ b/conversation/go/sdk/README.md @@ -21,8 +21,11 @@ Open a new terminal window and run the multi app run template: diff --git a/conversation/go/sdk/conversation/conversation.go b/conversation/go/sdk/conversation/conversation.go index 05dba71b4..41eb2b92e 100644 --- a/conversation/go/sdk/conversation/conversation.go +++ b/conversation/go/sdk/conversation/conversation.go @@ -16,34 +16,154 @@ package main import ( "context" + "encoding/json" "fmt" "log" + "strings" + + "github.com/invopop/jsonschema" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" dapr "github.com/dapr/go-sdk/client" ) +// createMapOfArgsForEcho is a helper function to deal with the issue with the echo component not returning args as a map but in csv format +func createMapOfArgsForEcho(s string) ([]byte, error) { + m := map[string]any{} + for _, p := range strings.Split(s, ",") { + m[p] = p + } + return json.Marshal(m) +} + +// getWeatherInLocation is an example function to use as a tool call +func getWeatherInLocation(request GetDegreesWeatherRequest, defaultValues GetDegreesWeatherRequest) string { + location := request.Location + unit := request.Unit + if location == "location" { + location = defaultValues.Location + } + if unit == "unit" { + unit = defaultValues.Unit + } + return fmt.Sprintf("The weather in %s is 25 degrees %s", location, unit) +} + +type GetDegreesWeatherRequest struct { + Location string `json:"location" jsonschema:"title=Location,description=The location to look up the weather for"` + Unit string `json:"unit" jsonschema:"enum=celsius,enum=fahrenheit,description=Unit"` +} + +// GenerateFunctionTool helper method to create jsonschema input +func GenerateFunctionTool[T any](name, description string) (*dapr.ConversationToolsAlpha2, error) { + reflector := jsonschema.Reflector{ + AllowAdditionalProperties: false, + DoNotReference: true, + } + var v T + + schema := reflector.Reflect(v) + + schemaBytes, err := schema.MarshalJSON() + if err != nil { + return nil, err + } + + var protoStruct structpb.Struct + if err := protojson.Unmarshal(schemaBytes, &protoStruct); err != nil { + return nil, fmt.Errorf("converting jsonschema to proto Struct: %w", err) + } + + return (*dapr.ConversationToolsAlpha2)(&dapr.ConversationToolsFunctionAlpha2{ + Name: name, + Description: &description, + Parameters: &protoStruct, + }), nil +} + +// createUserMessageInput is a helper method to create user messages in expected proto format +func createUserMessageInput(msg string) *dapr.ConversationInputAlpha2 { + return &dapr.ConversationInputAlpha2{ + Messages: []*dapr.ConversationMessageAlpha2{ + { + ConversationMessageOfUser: &dapr.ConversationMessageOfUserAlpha2{ + Content: []*dapr.ConversationMessageContentAlpha2{ + { + Text: &msg, + }, + }, + }, + }, + }, + } +} + func main() { client, err := dapr.NewClient() if err != nil { panic(err) } - input := dapr.ConversationInput{ - Content: "What is dapr?", - // Role: nil, // Optional - // ScrubPII: nil, // Optional + inputMsg := "What is dapr?" + conversationComponent := "echo" + + request := dapr.ConversationRequestAlpha2{ + Name: conversationComponent, + Inputs: []*dapr.ConversationInputAlpha2{createUserMessageInput(inputMsg)}, + } + + fmt.Println("Input sent:", inputMsg) + + resp, err := client.ConverseAlpha2(context.Background(), request) + if err != nil { + log.Fatalf("err: %v", err) } - fmt.Println("Input sent:", input.Content) + fmt.Println("Output response:", resp.Outputs[0].Choices[0].Message.Content) - var conversationComponent = "echo" + tool, err := GenerateFunctionTool[GetDegreesWeatherRequest]("getWeather", "get weather from a location in the given unit") + if err != nil { + log.Fatalf("err: %v", err) + } - request := dapr.NewConversationRequest(conversationComponent, []dapr.ConversationInput{input}) + weatherMessage := "Tool calling input sent: What is the weather like in San Francisco in celsius?'" + requestWithTool := dapr.ConversationRequestAlpha2{ + Name: conversationComponent, + Inputs: []*dapr.ConversationInputAlpha2{createUserMessageInput(weatherMessage)}, + Tools: []*dapr.ConversationToolsAlpha2{tool}, + } - resp, err := client.ConverseAlpha1(context.Background(), request) + resp, err = client.ConverseAlpha2(context.Background(), requestWithTool) if err != nil { log.Fatalf("err: %v", err) } - fmt.Println("Output response:", resp.Outputs[0].Result) + fmt.Println(resp.Outputs[0].Choices[0].Message.Content) + for _, toolCalls := range resp.Outputs[0].Choices[0].Message.ToolCalls { + fmt.Printf("Tool Call: Name: %s - Arguments: %v\n", toolCalls.ToolTypes.Name, toolCalls.ToolTypes.Arguments) + + // parse the arguments and execute tool + args := []byte(toolCalls.ToolTypes.Arguments) + if conversationComponent == "echo" { + // The echo component does not return a compliant tool calling response in json format but rather returns a csv + args, err = createMapOfArgsForEcho(toolCalls.ToolTypes.Arguments) + if err != nil { + log.Fatalf("err: %v", err) + } + } + + // find the tool (only one in this case) and execute + for _, toolInfo := range requestWithTool.Tools { + if toolInfo.Name == toolCalls.ToolTypes.Name && toolInfo.Name == "getWeather" { + var reqArgs GetDegreesWeatherRequest + if err = json.Unmarshal(args, &reqArgs); err != nil { + log.Fatalf("err: %v", err) + } + // execute tool + toolExecutionOutput := getWeatherInLocation(reqArgs, GetDegreesWeatherRequest{Location: "San Francisco", Unit: "Celsius"}) + fmt.Printf("Tool Call Output: %s\n", toolExecutionOutput) + } + } + } } diff --git a/conversation/go/sdk/conversation/go.mod b/conversation/go/sdk/conversation/go.mod index 9f6488cfd..3b63da02e 100644 --- a/conversation/go/sdk/conversation/go.mod +++ b/conversation/go/sdk/conversation/go.mod @@ -1,21 +1,33 @@ module conversation -go 1.24.4 +go 1.24.6 -toolchain go1.24.5 - -require github.com/dapr/go-sdk v1.13.0-rc.1 +require ( + github.com/dapr/go-sdk v1.13.0 + github.com/invopop/jsonschema v0.13.0 + google.golang.org/protobuf v1.36.6 +) require ( - github.com/dapr/dapr v1.16.0-rc.3 // indirect - github.com/dapr/kit v0.15.4 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/dapr/dapr v1.16.0 // indirect + github.com/dapr/durabletask-go v0.10.0 // indirect + github.com/dapr/kit v0.16.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/conversation/go/sdk/conversation/go.sum b/conversation/go/sdk/conversation/go.sum index 4e3c1988b..310f31707 100644 --- a/conversation/go/sdk/conversation/go.sum +++ b/conversation/go/sdk/conversation/go.sum @@ -1,11 +1,20 @@ -github.com/dapr/dapr v1.16.0-rc.3 h1:D99V20GOhb+bZXH1PngME+wgzIZCcBFOvmaP7DOZxGo= -github.com/dapr/dapr v1.16.0-rc.3/go.mod h1:uyKnxMohSg87LSFzZ/oyuiGSo0+qkzeR0eXncPyIV9c= -github.com/dapr/go-sdk v1.13.0-rc.1 h1:GKvTl38EhxQ3VHuQngMMm8hEaAVHn9gu63CI2HTbI+U= -github.com/dapr/go-sdk v1.13.0-rc.1/go.mod h1:Klfst183A5pb2YZ0KHUCRwdeQuL8RFKX649ILW9K3h4= -github.com/dapr/kit v0.15.4 h1:29DezCR22OuZhXX4yPEc+lqcOf/PNaeAuIEx9nGv394= -github.com/dapr/kit v0.15.4/go.mod h1:HwFsBKEbcyLanWlDZE7u/jnaDCD/tU+n3pkFNUctQNw= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/dapr/dapr v1.16.0 h1:la2WLZM8Myr2Pq3cyrFjHKWDSPYLzGZCs3p502TwBjI= +github.com/dapr/dapr v1.16.0/go.mod h1:ln/mxvNOeqklaDmic4ppsxmnjl2D/oZGKaJy24IwaEY= +github.com/dapr/durabletask-go v0.10.0 h1:vfIivPl4JYd55xZTslDwhA6p6F8ipcNxBtMaupxArr8= +github.com/dapr/durabletask-go v0.10.0/go.mod h1:0Ts4rXp74JyG19gDWPcwNo5V6NBZzhARzHF5XynmA7Q= +github.com/dapr/go-sdk v1.13.0 h1:Qw2BmUonClQ9yK/rrEEaFL1PyDgq616RrvYj0CT67Lk= +github.com/dapr/go-sdk v1.13.0/go.mod h1:RsffVNZitDApmQqoS68tNKGMXDZUjTviAbKZupJSzts= +github.com/dapr/kit v0.16.1 h1:MqLAhHVg8trPy2WJChMZFU7ToeondvxcNHYVvMDiVf4= +github.com/dapr/kit v0.16.1/go.mod h1:40ZWs5P6xfYf7O59XgwqZkIyDldTIXlhTQhGop8QoSM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -16,10 +25,23 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= @@ -44,7 +66,8 @@ google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/conversation/go/sdk/dapr.yaml b/conversation/go/sdk/dapr.yaml index 6a6c0e0fc..c22870b90 100644 --- a/conversation/go/sdk/dapr.yaml +++ b/conversation/go/sdk/dapr.yaml @@ -3,5 +3,5 @@ common: resourcesPath: ../../components/ apps: - appDirPath: ./conversation/ - appID: conversation + appID: conversation-sdk command: ["go", "run", "."]