Skip to content

Commit 9b3931a

Browse files
authored
Merge pull request #7 from mutablelogic/dev
Added new agents
2 parents 31c3080 + 932b720 commit 9b3931a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+4976
-1548
lines changed

README.md

Lines changed: 315 additions & 59 deletions
Large diffs are not rendered by default.

attachment.go

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,31 @@ import (
1313
///////////////////////////////////////////////////////////////////////////////
1414
// TYPES
1515

16+
type AttachmentMeta struct {
17+
Id string `json:"id,omitempty"`
18+
Filename string `json:"filename,omitempty"`
19+
ExpiresAt uint64 `json:"expires_at,omitempty"`
20+
Caption string `json:"transcript,omitempty"`
21+
Data []byte `json:"data"`
22+
}
23+
1624
// Attachment for messages
1725
type Attachment struct {
18-
filename string
19-
data []byte
26+
meta AttachmentMeta
2027
}
2128

29+
const (
30+
defaultMimetype = "application/octet-stream"
31+
)
32+
2233
////////////////////////////////////////////////////////////////////////////////
2334
// LIFECYCLE
2435

36+
// NewAttachment creates a new, empty attachment
37+
func NewAttachment() *Attachment {
38+
return new(Attachment)
39+
}
40+
2541
// ReadAttachment returns an attachment from a reader object.
2642
// It is the responsibility of the caller to close the reader.
2743
func ReadAttachment(r io.Reader) (*Attachment, error) {
@@ -33,22 +49,43 @@ func ReadAttachment(r io.Reader) (*Attachment, error) {
3349
if f, ok := r.(*os.File); ok {
3450
filename = f.Name()
3551
}
36-
return &Attachment{filename: filename, data: data}, nil
52+
return &Attachment{
53+
meta: AttachmentMeta{
54+
Filename: filename,
55+
Data: data,
56+
},
57+
}, nil
3758
}
3859

3960
////////////////////////////////////////////////////////////////////////////////
4061
// STRINGIFY
4162

42-
func (a *Attachment) String() string {
63+
// Convert JSON into an attachment
64+
func (a *Attachment) UnmarshalJSON(data []byte) error {
65+
return json.Unmarshal(data, &a.meta)
66+
}
67+
68+
// Convert an attachment into JSON
69+
func (a *Attachment) MarshalJSON() ([]byte, error) {
70+
// Create a JSON representation
4371
var j struct {
44-
Filename string `json:"filename"`
72+
Id string `json:"id,omitempty"`
73+
Filename string `json:"filename,omitempty"`
4574
Type string `json:"type"`
4675
Bytes uint64 `json:"bytes"`
76+
Caption string `json:"transcript,omitempty"`
4777
}
48-
j.Filename = a.filename
78+
j.Id = a.meta.Id
79+
j.Filename = a.meta.Filename
4980
j.Type = a.Type()
50-
j.Bytes = uint64(len(a.data))
51-
data, err := json.MarshalIndent(j, "", " ")
81+
j.Bytes = uint64(len(a.meta.Data))
82+
j.Caption = a.meta.Caption
83+
return json.Marshal(j)
84+
}
85+
86+
// Stringify an attachment
87+
func (a *Attachment) String() string {
88+
data, err := json.MarshalIndent(a.meta, "", " ")
5289
if err != nil {
5390
return err.Error()
5491
}
@@ -58,24 +95,68 @@ func (a *Attachment) String() string {
5895
////////////////////////////////////////////////////////////////////////////////
5996
// PUBLIC METHODS
6097

98+
// Return the filename of an attachment
6199
func (a *Attachment) Filename() string {
62-
return a.filename
100+
return a.meta.Filename
63101
}
64102

103+
// Return the raw attachment data
65104
func (a *Attachment) Data() []byte {
66-
return a.data
105+
return a.meta.Data
106+
}
107+
108+
// Return the caption for the attachment
109+
func (a *Attachment) Caption() string {
110+
return a.meta.Caption
67111
}
68112

113+
// Return the mime media type for the attachment, based
114+
// on the data and/or filename extension. Returns an empty string if
115+
// there is no data or filename
69116
func (a *Attachment) Type() string {
117+
// If there's no data or filename, return empty
118+
if len(a.meta.Data) == 0 && a.meta.Filename == "" {
119+
return ""
120+
}
121+
70122
// Mimetype based on content
71-
mimetype := http.DetectContentType(a.data)
72-
if mimetype == "application/octet-stream" && a.filename != "" {
123+
mimetype := defaultMimetype
124+
if len(a.meta.Data) > 0 {
125+
mimetype = http.DetectContentType(a.meta.Data)
126+
if mimetype != defaultMimetype {
127+
return mimetype
128+
}
129+
}
130+
131+
// Mimetype based on filename
132+
if a.meta.Filename != "" {
73133
// Detect mimetype from extension
74-
mimetype = mime.TypeByExtension(filepath.Ext(a.filename))
134+
mimetype = mime.TypeByExtension(filepath.Ext(a.meta.Filename))
75135
}
136+
137+
// Return the default mimetype
76138
return mimetype
77139
}
78140

79141
func (a *Attachment) Url() string {
80-
return "data:" + a.Type() + ";base64," + base64.StdEncoding.EncodeToString(a.data)
142+
return "data:" + a.Type() + ";base64," + base64.StdEncoding.EncodeToString(a.meta.Data)
143+
}
144+
145+
// Streaming includes the ability to append data
146+
func (a *Attachment) Append(other *Attachment) {
147+
if other.meta.Id != "" {
148+
a.meta.Id = other.meta.Id
149+
}
150+
if other.meta.Filename != "" {
151+
a.meta.Filename = other.meta.Filename
152+
}
153+
if other.meta.ExpiresAt != 0 {
154+
a.meta.ExpiresAt = other.meta.ExpiresAt
155+
}
156+
if other.meta.Caption != "" {
157+
a.meta.Caption += other.meta.Caption
158+
}
159+
if len(other.meta.Data) > 0 {
160+
a.meta.Data = append(a.meta.Data, other.meta.Data...)
161+
}
81162
}

cmd/llm/chat.go

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
// Packages
1111
llm "github.com/mutablelogic/go-llm"
12-
agent "github.com/mutablelogic/go-llm/pkg/agent"
1312
)
1413

1514
////////////////////////////////////////////////////////////////////////////////
@@ -27,26 +26,17 @@ type ChatCmd struct {
2726
// PUBLIC METHODS
2827

2928
func (cmd *ChatCmd) Run(globals *Globals) error {
30-
return runagent(globals, func(ctx context.Context, client llm.Agent) error {
31-
// Get the model
32-
a, ok := client.(*agent.Agent)
33-
if !ok {
34-
return fmt.Errorf("No agents found")
35-
}
36-
model, err := a.GetModel(ctx, cmd.Model)
37-
if err != nil {
38-
return err
39-
}
29+
return run(globals, cmd.Model, func(ctx context.Context, model llm.Model) error {
30+
// Current buffer
31+
var buf string
4032

4133
// Set the options
4234
opts := []llm.Opt{}
4335
if !cmd.NoStream {
4436
opts = append(opts, llm.WithStream(func(cc llm.Completion) {
45-
if text := cc.Text(0); text != "" {
46-
count := strings.Count(text, "\n")
47-
fmt.Print(strings.Repeat("\033[F", count) + strings.Repeat(" ", count) + "\r")
48-
fmt.Print(text)
49-
}
37+
text := cc.Text(0)
38+
fmt.Print(strings.TrimPrefix(text, buf))
39+
buf = text
5040
}))
5141
}
5242
if cmd.System != "" {
@@ -66,6 +56,7 @@ func (cmd *ChatCmd) Run(globals *Globals) error {
6656
input = cmd.Prompt
6757
cmd.Prompt = ""
6858
} else {
59+
var err error
6960
input, err = globals.term.ReadLine(model.Name() + "> ")
7061
if errors.Is(err, io.EOF) {
7162
return nil
@@ -91,6 +82,7 @@ func (cmd *ChatCmd) Run(globals *Globals) error {
9182
if len(calls) == 0 {
9283
break
9384
}
85+
9486
if session.Text(0) != "" {
9587
globals.term.Println(session.Text(0))
9688
} else {
@@ -100,15 +92,20 @@ func (cmd *ChatCmd) Run(globals *Globals) error {
10092
}
10193
globals.term.Println("Calling ", strings.Join(names, ", "))
10294
}
95+
10396
if results, err := globals.toolkit.Run(ctx, calls...); err != nil {
10497
return err
10598
} else if err := session.FromTool(ctx, results...); err != nil {
10699
return err
107100
}
108101
}
109102

110-
// Print the response
111-
globals.term.Println("\n" + session.Text(0) + "\n")
103+
// Print the response, if not streaming
104+
if cmd.NoStream {
105+
globals.term.Println("\n" + session.Text(0) + "\n")
106+
} else {
107+
globals.term.Println()
108+
}
112109
}
113110
})
114111
}

cmd/llm/complete.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"os"
8+
"strings"
9+
10+
// Packages
11+
llm "github.com/mutablelogic/go-llm"
12+
)
13+
14+
////////////////////////////////////////////////////////////////////////////////
15+
// TYPES
16+
17+
type CompleteCmd struct {
18+
Model string `arg:"" help:"Model name"`
19+
Prompt string `arg:"" optional:"" help:"Prompt"`
20+
File []string `type:"file" short:"f" help:"Files to attach"`
21+
System string `flag:"system" help:"Set the system prompt"`
22+
NoStream bool `flag:"no-stream" help:"Do not stream output"`
23+
Format string `flag:"format" enum:"text,markdown,json" default:"text" help:"Output format"`
24+
Temperature *float64 `flag:"temperature" short:"t" help:"Temperature for sampling"`
25+
}
26+
27+
////////////////////////////////////////////////////////////////////////////////
28+
// PUBLIC METHODS
29+
30+
func (cmd *CompleteCmd) Run(globals *Globals) error {
31+
return run(globals, cmd.Model, func(ctx context.Context, model llm.Model) error {
32+
var prompt []byte
33+
34+
// If we are pipeline content in via stdin
35+
fileInfo, err := os.Stdin.Stat()
36+
if err != nil {
37+
return llm.ErrInternalServerError.Withf("Failed to get stdin stat: %v", err)
38+
}
39+
if (fileInfo.Mode() & os.ModeCharDevice) == 0 {
40+
if data, err := io.ReadAll(os.Stdin); err != nil {
41+
return err
42+
} else if len(data) > 0 {
43+
prompt = data
44+
}
45+
}
46+
47+
// Append any further prompt
48+
if len(cmd.Prompt) > 0 {
49+
prompt = append(prompt, []byte("\n\n")...)
50+
prompt = append(prompt, []byte(cmd.Prompt)...)
51+
}
52+
53+
opts := cmd.opts()
54+
if !cmd.NoStream {
55+
// Add streaming callback
56+
var buf string
57+
opts = append(opts, llm.WithStream(func(c llm.Completion) {
58+
fmt.Print(strings.TrimPrefix(c.Text(0), buf))
59+
buf = c.Text(0)
60+
}))
61+
}
62+
63+
// Add attachments
64+
for _, file := range cmd.File {
65+
f, err := os.Open(file)
66+
if err != nil {
67+
return err
68+
}
69+
defer f.Close()
70+
opts = append(opts, llm.WithAttachment(f))
71+
}
72+
73+
// Make the completion
74+
completion, err := model.Completion(ctx, string(prompt), opts...)
75+
if err != nil {
76+
return err
77+
}
78+
79+
// Print the completion
80+
if cmd.NoStream {
81+
fmt.Println(completion.Text(0))
82+
} else {
83+
fmt.Println("")
84+
}
85+
86+
// Return success
87+
return nil
88+
})
89+
}
90+
91+
func (cmd *CompleteCmd) opts() []llm.Opt {
92+
opts := []llm.Opt{}
93+
94+
// Set system prompt
95+
var system []string
96+
if cmd.Format == "markdown" {
97+
system = append(system, "Structure your output in markdown format.")
98+
} else if cmd.Format == "json" {
99+
system = append(system, "Structure your output in JSON format.")
100+
}
101+
if cmd.System != "" {
102+
system = append(system, cmd.System)
103+
}
104+
if len(system) > 0 {
105+
opts = append(opts, llm.WithSystemPrompt(strings.Join(system, "\n")))
106+
}
107+
108+
// Set format
109+
if cmd.Format == "json" {
110+
opts = append(opts, llm.WithFormat("json"))
111+
}
112+
113+
// Set temperature
114+
if cmd.Temperature != nil {
115+
opts = append(opts, llm.WithTemperature(*cmd.Temperature))
116+
}
117+
118+
return opts
119+
}

cmd/llm/embedding.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
// Packages
9+
llm "github.com/mutablelogic/go-llm"
10+
)
11+
12+
////////////////////////////////////////////////////////////////////////////////
13+
// TYPES
14+
15+
type EmbeddingCmd struct {
16+
Model string `arg:"" help:"Model name"`
17+
Prompt string `arg:"" help:"Prompt"`
18+
}
19+
20+
////////////////////////////////////////////////////////////////////////////////
21+
// PUBLIC METHODS
22+
23+
func (cmd *EmbeddingCmd) Run(globals *Globals) error {
24+
return run(globals, cmd.Model, func(ctx context.Context, model llm.Model) error {
25+
vector, err := model.Embedding(ctx, cmd.Prompt)
26+
if err != nil {
27+
return err
28+
}
29+
data, err := json.Marshal(vector)
30+
if err != nil {
31+
return err
32+
}
33+
fmt.Println(string(data))
34+
return nil
35+
})
36+
}

0 commit comments

Comments
 (0)