Skip to content

Commit 54292c0

Browse files
committed
Added samantha which can retrieve the weather
1 parent 1c8ae92 commit 54292c0

File tree

3 files changed

+171
-4
lines changed

3 files changed

+171
-4
lines changed

cmd/api/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func main() {
2222
anthropicRegister(flags)
2323
newsapiRegister(flags)
2424
weatherapiRegister(flags)
25+
samRegister(flags)
2526

2627
// Parse command line and return function to run
2728
fn, args, err := flags.Parse(os.Args[1:])

cmd/api/samantha.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"os"
9+
"strings"
10+
11+
// Packages
12+
"github.com/djthorpe/go-tablewriter"
13+
"github.com/mutablelogic/go-client"
14+
"github.com/mutablelogic/go-client/pkg/anthropic"
15+
"github.com/mutablelogic/go-client/pkg/openai/schema"
16+
)
17+
18+
///////////////////////////////////////////////////////////////////////////////
19+
// GLOBALS
20+
21+
var (
22+
samName = "sam"
23+
samWeatherTool = schema.NewTool("get_weather", "Get the weather for a location")
24+
samSystemPrompt = "Your name is Samantha, you are a friendly AI assistant, here to help you with anything you need. Your responses should be short and to the point, and you should always be polite."
25+
)
26+
27+
///////////////////////////////////////////////////////////////////////////////
28+
// LIFECYCLE
29+
30+
func samRegister(flags *Flags) {
31+
flags.Register(Cmd{
32+
Name: samName,
33+
Description: "Interact with Samantha, a friendly AI assistant",
34+
Parse: samParse,
35+
Fn: []Fn{
36+
{Name: "chat", Call: samChat, Description: "Chat with Sam"},
37+
},
38+
})
39+
}
40+
41+
func samParse(flags *Flags, opts ...client.ClientOpt) error {
42+
// Initialize weather
43+
if err := weatherapiParse(flags, opts...); err != nil {
44+
return err
45+
}
46+
47+
// Initialize anthropic
48+
opts = append(opts, client.OptHeader("Anthropic-Beta", "tools-2024-04-04"))
49+
if err := anthropicParse(flags, opts...); err != nil {
50+
return err
51+
}
52+
53+
// Add tool parameters
54+
if err := samWeatherTool.AddParameter("location", "The city to get the weather for", true); err != nil {
55+
return err
56+
}
57+
58+
// Return success
59+
return nil
60+
}
61+
62+
///////////////////////////////////////////////////////////////////////////////
63+
// METHODS
64+
65+
func samChat(ctx context.Context, w *tablewriter.Writer, _ []string) error {
66+
var toolResult bool
67+
68+
messages := []*schema.Message{}
69+
for {
70+
if ctx.Err() != nil {
71+
return nil
72+
}
73+
74+
// Read if there hasn't been any tool results yet
75+
if !toolResult {
76+
reader := bufio.NewReader(os.Stdin)
77+
fmt.Print("Chat: ")
78+
text, err := reader.ReadString('\n')
79+
if err != nil {
80+
return err
81+
}
82+
messages = append(messages, schema.NewMessage("user", schema.Text(strings.TrimSpace(text))))
83+
}
84+
85+
// Request -> Response
86+
responses, err := anthropicClient.Messages(ctx, messages, anthropic.OptSystem(samSystemPrompt), anthropic.OptTool(samWeatherTool))
87+
if err != nil {
88+
return err
89+
}
90+
toolResult = false
91+
92+
for _, response := range responses {
93+
switch response.Type {
94+
case "text":
95+
messages = samAppend(messages, schema.NewMessage("assistant", schema.Text(response.Text)))
96+
fmt.Println(response.Text)
97+
fmt.Println("")
98+
case "tool_use":
99+
messages = samAppend(messages, schema.NewMessage("assistant", response))
100+
result := samCall(ctx, response)
101+
messages = samAppend(messages, schema.NewMessage("user", result))
102+
toolResult = true
103+
}
104+
}
105+
}
106+
}
107+
108+
func samCall(_ context.Context, content schema.Content) *schema.Content {
109+
if content.Type != "tool_use" {
110+
return schema.ToolResult(content.Id, fmt.Sprint("unexpected content type:", content.Type))
111+
}
112+
switch content.Name {
113+
case samWeatherTool.Name:
114+
if location, exists := content.GetString(content.Name, "location"); exists {
115+
if weather, err := weatherapiClient.Current(location); err != nil {
116+
return schema.ToolResult(content.Id, fmt.Sprint("Unable to get the current weather, the error is ", err))
117+
} else if data, err := json.MarshalIndent(weather, "", " "); err != nil {
118+
return schema.ToolResult(content.Id, fmt.Sprint("Unable to marshal the weather data, the error is ", err))
119+
} else {
120+
return schema.ToolResult(content.Id, string(data))
121+
}
122+
}
123+
}
124+
return schema.ToolResult(content.Id, fmt.Sprint("unable to call:", content.Name))
125+
}
126+
127+
func samAppend(messages []*schema.Message, message *schema.Message) []*schema.Message {
128+
// if the previous message was of the same role, then append the new message to the previous one
129+
if len(messages) > 0 && messages[len(messages)-1].Role == message.Role {
130+
messages[len(messages)-1].Add(message.Content)
131+
return messages
132+
} else {
133+
return append(messages, message)
134+
}
135+
}

pkg/openai/schema/message.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ type Content struct {
4646
Text string `json:"text,omitempty,wrap,width:60"`
4747
Source *contentSource `json:"source,omitempty"`
4848
toolUse
49+
50+
ToolId string `json:"tool_use_id,omitempty"`
51+
Result string `json:"content,omitempty"`
4952
}
5053

5154
// Content Source
@@ -55,12 +58,18 @@ type contentSource struct {
5558
Data string `json:"data,omitempty"`
5659
}
5760

58-
// Tool arguments
61+
// Tool call
5962
type toolUse struct {
6063
Name string `json:"name,omitempty"`
6164
Input map[string]any `json:"input,omitempty"`
6265
}
6366

67+
// Tool result
68+
type toolResult struct {
69+
ToolId string `json:"tool_use_id,omitempty"`
70+
Result string `json:"content,omitempty"`
71+
}
72+
6473
///////////////////////////////////////////////////////////////////////////////
6574
// LIFECYCLE
6675

@@ -112,6 +121,11 @@ func ImageData(path string) (*Content, error) {
112121
return Image(r)
113122
}
114123

124+
// Return a tool result
125+
func ToolResult(id string, result string) *Content {
126+
return &Content{Type: "tool_result", ToolId: id, Result: result}
127+
}
128+
115129
///////////////////////////////////////////////////////////////////////////////
116130
// STRINGIFY
117131

@@ -148,6 +162,19 @@ func (m *Message) Add(content ...any) *Message {
148162
return m
149163
}
150164

165+
// Return an input parameter as a string, returns false if the name
166+
// is incorrect or the input doesn't exist
167+
func (c Content) GetString(name, input string) (string, bool) {
168+
if c.Name == name {
169+
if value, exists := c.Input[input]; exists {
170+
if value, ok := value.(string); ok {
171+
return value, true
172+
}
173+
}
174+
}
175+
return "", false
176+
}
177+
151178
///////////////////////////////////////////////////////////////////////////////
152179
// PRIVATE METHODS
153180

@@ -210,6 +237,10 @@ func (m *Message) append(v any) error {
210237
// []Content, Content => []Content
211238
m.Content = append(m.Content.([]Content), v)
212239
return nil
240+
case []Content:
241+
// []Content, []Content => []Content
242+
m.Content = append(m.Content.([]Content), v...)
243+
return nil
213244
}
214245
}
215246
return ErrBadParameter.With("append: not implemented for ", reflect.TypeOf(m.Content), ",", reflect.TypeOf(v))
@@ -218,16 +249,16 @@ func (m *Message) append(v any) error {
218249
// Set the message content
219250
func (m *Message) set(v any) error {
220251
// Append content to messages,
221-
// m.Content will be of type string, []string, Content or []Content
252+
// m.Content will be of type string, []string or []Content
222253
switch v := v.(type) {
223254
case string:
224255
m.Content = v
225256
case []string:
226257
m.Content = v
227258
case *Content:
228-
m.Content = *v
259+
m.Content = []Content{*v}
229260
case Content:
230-
m.Content = v
261+
m.Content = []Content{v}
231262
case []*Content:
232263
if len(v) > 0 {
233264
m.Content = make([]Content, 0, len(v))

0 commit comments

Comments
 (0)