@@ -16,34 +16,154 @@ package main
1616
1717import (
1818 "context"
19+ "encoding/json"
1920 "fmt"
2021 "log"
22+ "strings"
23+
24+ "github.com/invopop/jsonschema"
25+ "google.golang.org/protobuf/encoding/protojson"
26+ "google.golang.org/protobuf/types/known/structpb"
2127
2228 dapr "github.com/dapr/go-sdk/client"
2329)
2430
31+ // createMapOfArgsForEcho is a helper function to deal with the issue with the echo component not returning args as a map but in csv format
32+ func createMapOfArgsForEcho (s string ) ([]byte , error ) {
33+ m := map [string ]any {}
34+ for _ , p := range strings .Split (s , "," ) {
35+ m [p ] = p
36+ }
37+ return json .Marshal (m )
38+ }
39+
40+ // getWeatherInLocation is an example function to use as a tool call
41+ func getWeatherInLocation (request GetDegreesWeatherRequest , defaultValues GetDegreesWeatherRequest ) string {
42+ location := request .Location
43+ unit := request .Unit
44+ if location == "location" {
45+ location = defaultValues .Location
46+ }
47+ if unit == "unit" {
48+ unit = defaultValues .Unit
49+ }
50+ return fmt .Sprintf ("The weather in %s is 25 degrees %s" , location , unit )
51+ }
52+
53+ type GetDegreesWeatherRequest struct {
54+ Location string `json:"location" jsonschema:"title=Location,description=The location to look up the weather for"`
55+ Unit string `json:"unit" jsonschema:"enum=celsius,enum=fahrenheit,description=Unit"`
56+ }
57+
58+ // GenerateFunctionTool helper method to create jsonschema input
59+ func GenerateFunctionTool [T any ](name , description string ) (* dapr.ConversationToolsAlpha2 , error ) {
60+ reflector := jsonschema.Reflector {
61+ AllowAdditionalProperties : false ,
62+ DoNotReference : true ,
63+ }
64+ var v T
65+
66+ schema := reflector .Reflect (v )
67+
68+ schemaBytes , err := schema .MarshalJSON ()
69+ if err != nil {
70+ return nil , err
71+ }
72+
73+ var protoStruct structpb.Struct
74+ if err := protojson .Unmarshal (schemaBytes , & protoStruct ); err != nil {
75+ return nil , fmt .Errorf ("converting jsonschema to proto Struct: %w" , err )
76+ }
77+
78+ return (* dapr .ConversationToolsAlpha2 )(& dapr.ConversationToolsFunctionAlpha2 {
79+ Name : name ,
80+ Description : & description ,
81+ Parameters : & protoStruct ,
82+ }), nil
83+ }
84+
85+ // createUserMessageInput is a helper method to create user messages in expected proto format
86+ func createUserMessageInput (msg string ) * dapr.ConversationInputAlpha2 {
87+ return & dapr.ConversationInputAlpha2 {
88+ Messages : []* dapr.ConversationMessageAlpha2 {
89+ {
90+ ConversationMessageOfUser : & dapr.ConversationMessageOfUserAlpha2 {
91+ Content : []* dapr.ConversationMessageContentAlpha2 {
92+ {
93+ Text : & msg ,
94+ },
95+ },
96+ },
97+ },
98+ },
99+ }
100+ }
101+
25102func main () {
26103 client , err := dapr .NewClient ()
27104 if err != nil {
28105 panic (err )
29106 }
30107
31- input := dapr.ConversationInput {
32- Content : "What is dapr?" ,
33- // Role: nil, // Optional
34- // ScrubPII: nil, // Optional
108+ inputMsg := "What is dapr?"
109+ conversationComponent := "echo"
110+
111+ request := dapr.ConversationRequestAlpha2 {
112+ Name : conversationComponent ,
113+ Inputs : []* dapr.ConversationInputAlpha2 {createUserMessageInput (inputMsg )},
114+ }
115+
116+ fmt .Println ("Input sent:" , inputMsg )
117+
118+ resp , err := client .ConverseAlpha2 (context .Background (), request )
119+ if err != nil {
120+ log .Fatalf ("err: %v" , err )
35121 }
36122
37- fmt .Println ("Input sent :" , input .Content )
123+ fmt .Println ("Output response :" , resp . Outputs [ 0 ]. Choices [ 0 ]. Message .Content )
38124
39- var conversationComponent = "echo"
125+ tool , err := GenerateFunctionTool [GetDegreesWeatherRequest ]("getWeather" , "get weather from a location in the given unit" )
126+ if err != nil {
127+ log .Fatalf ("err: %v" , err )
128+ }
40129
41- request := dapr .NewConversationRequest (conversationComponent , []dapr.ConversationInput {input })
130+ weatherMessage := "Tool calling input sent: What is the weather like in San Francisco in celsius?'"
131+ requestWithTool := dapr.ConversationRequestAlpha2 {
132+ Name : conversationComponent ,
133+ Inputs : []* dapr.ConversationInputAlpha2 {createUserMessageInput (weatherMessage )},
134+ Tools : []* dapr.ConversationToolsAlpha2 {tool },
135+ }
42136
43- resp , err : = client .ConverseAlpha1 (context .Background (), request )
137+ resp , err = client .ConverseAlpha2 (context .Background (), requestWithTool )
44138 if err != nil {
45139 log .Fatalf ("err: %v" , err )
46140 }
47141
48- fmt .Println ("Output response:" , resp .Outputs [0 ].Result )
142+ fmt .Println (resp .Outputs [0 ].Choices [0 ].Message .Content )
143+ for _ , toolCalls := range resp .Outputs [0 ].Choices [0 ].Message .ToolCalls {
144+ fmt .Printf ("Tool Call: Name: %s - Arguments: %v\n " , toolCalls .ToolTypes .Name , toolCalls .ToolTypes .Arguments )
145+
146+ // parse the arguments and execute tool
147+ args := []byte (toolCalls .ToolTypes .Arguments )
148+ if conversationComponent == "echo" {
149+ // The echo component does not return a compliant tool calling response in json format but rather returns a csv
150+ args , err = createMapOfArgsForEcho (toolCalls .ToolTypes .Arguments )
151+ if err != nil {
152+ log .Fatalf ("err: %v" , err )
153+ }
154+ }
155+
156+ // find the tool (only one in this case) and execute
157+ for _ , toolInfo := range requestWithTool .Tools {
158+ if toolInfo .Name == toolCalls .ToolTypes .Name && toolInfo .Name == "getWeather" {
159+ var reqArgs GetDegreesWeatherRequest
160+ if err = json .Unmarshal (args , & reqArgs ); err != nil {
161+ log .Fatalf ("err: %v" , err )
162+ }
163+ // execute tool
164+ toolExecutionOutput := getWeatherInLocation (reqArgs , GetDegreesWeatherRequest {Location : "San Francisco" , Unit : "Celsius" })
165+ fmt .Printf ("Tool Call Output: %s\n " , toolExecutionOutput )
166+ }
167+ }
168+ }
49169}
0 commit comments