Skip to content

Commit de4bbfc

Browse files
authored
Merge pull request #3 from mutablelogic/dev
Updated Ollama and Mistral implementations
2 parents 11ec895 + 9e30bd3 commit de4bbfc

Some content is hidden

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

47 files changed

+2657
-899
lines changed

README.md

+171-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
# go-llm
22

33
Large Language Model API interface. This is a simple API interface for large language models
4-
which run on [Ollama](https://github.com/ollama/ollama/blob/main/docs/api.md)
5-
and [Anthopic](https://docs.anthropic.com/en/api/getting-started).
4+
which run on [Ollama](https://github.com/ollama/ollama/blob/main/docs/api.md),
5+
[Anthopic](https://docs.anthropic.com/en/api/getting-started) and [Mistral](https://docs.mistral.ai/)
6+
(OpenAI might be added later).
67

78
The module includes the ability to utilize:
89

910
* Maintaining a session of messages
10-
* Tool calling support
11+
* Tool calling support, including using your own tools (aka Tool plugins)
12+
* Creating embedding vectors from text
1113
* Streaming responses
14+
* Multi-modal support (aka, Images and Attachments)
1215

1316
There is a command-line tool included in the module which can be used to interact with the API.
14-
For example,
17+
If you have docker installed, you can use the following command to run the tool, without
18+
installation:
1519

1620
```bash
1721
# Display help
@@ -20,15 +24,23 @@ docker run ghcr.io/mutablelogic/go-llm:latest --help
2024
# Interact with Claude to retrieve news headlines, assuming
2125
# you have an API key for Anthropic and NewsAPI
2226
docker run \
23-
--interactive -e ANTHROPIC_API_KEY -e NEWSAPI_KEY \
27+
--interactive -e MISTRAL_API_KEY -e NEWSAPI_KEY \
2428
ghcr.io/mutablelogic/go-llm:latest \
25-
chat claude-3-5-haiku-20241022
29+
chat claude-3-5-haiku-20241022 --prompt "What is the latest news?"
2630
```
2731

32+
See below for more information on how to use the command-line tool (or how to install it
33+
if you have a `go` compiler).
34+
2835
## Programmatic Usage
2936

3037
See the documentation [here](https://pkg.go.dev/github.com/mutablelogic/go-llm)
31-
for integration into your own Go programs. To create an
38+
for integration into your own Go programs.
39+
40+
### Agent Instantiation
41+
42+
For each LLM provider, you create an agent which can be used to interact with the API.
43+
To create an
3244
[Ollama](https://pkg.go.dev/github.com/mutablelogic/go-llm/pkg/anthropic)
3345
agent,
3446

@@ -38,7 +50,7 @@ import (
3850
)
3951

4052
func main() {
41-
// Create a new agent
53+
// Create a new agent - replace the URL with the one to your Ollama instance
4254
agent, err := ollama.New("https://ollama.com/api/v1/")
4355
if err != nil {
4456
panic(err)
@@ -49,7 +61,7 @@ func main() {
4961

5062
To create an
5163
[Anthropic](https://pkg.go.dev/github.com/mutablelogic/go-llm/pkg/anthropic)
52-
agent,
64+
agent with an API key stored as an environment variable,
5365

5466
```go
5567
import (
@@ -58,14 +70,58 @@ import (
5870

5971
func main() {
6072
// Create a new agent
61-
agent, err := anthropic.New(os.Getev("ANTHROPIC_API_KEY"))
73+
agent, err := anthropic.New(os.Getenv("ANTHROPIC_API_KEY"))
74+
if err != nil {
75+
panic(err)
76+
}
77+
// ...
78+
}
79+
```
80+
81+
For [Mistral](https://pkg.go.dev/github.com/mutablelogic/go-llm/pkg/mistral) models, you can use:
82+
83+
```go
84+
import (
85+
"github.com/mutablelogic/go-llm/pkg/mistral"
86+
)
87+
88+
func main() {
89+
// Create a new agent
90+
agent, err := mistral.New(os.Getenv("MISTRAL_API_KEY"))
91+
if err != nil {
92+
panic(err)
93+
}
94+
// ...
95+
}
96+
```
97+
98+
You can append options to the agent creation to set the client/server communication options,
99+
such as user agent strings, timeouts, debugging, rate limiting, adding custom headers, etc. See [here](https://pkg.go.dev/github.com/mutablelogic/go-client#readme-basic-usage) for more information.
100+
101+
There is also an _aggregated_ agent which can be used to interact with multiple providers at once. This is useful if you want
102+
to use models from different providers simultaneously.
103+
104+
```go
105+
import (
106+
"github.com/mutablelogic/go-llm/pkg/agent"
107+
)
108+
109+
func main() {
110+
// Create a new agent which aggregates multiple providers
111+
agent, err := agent.New(
112+
agent.WithAnthropic(os.Getenv("ANTHROPIC_API_KEY")),
113+
agent.WithMistral(os.Getenv("MISTRAL_API_KEY")),
114+
agent.WithOllama(os.Getenv("OLLAMA_URL")),
115+
)
62116
if err != nil {
63117
panic(err)
64118
}
65119
// ...
66120
}
67121
```
68122

123+
### Chat Sessions
124+
69125
You create a **chat session** with a model as follows,
70126

71127
```go
@@ -75,7 +131,7 @@ import (
75131

76132
func session(ctx context.Context, agent llm.Agent) error {
77133
// Create a new chat session
78-
session := agent.Model("claude-3-5-haiku-20241022").Context()
134+
session := agent.Model(context.TODO(), "claude-3-5-haiku-20241022").Context()
79135

80136
// Repeat forever
81137
for {
@@ -84,12 +140,114 @@ func session(ctx context.Context, agent llm.Agent) error {
84140
return err
85141
}
86142

87-
// Print the response
88-
fmt.Println(session.Text())
143+
// Print the response for the zero'th completion
144+
fmt.Println(session.Text(0))
89145
}
90146
}
91147
```
92148

149+
The `Context` object will continue to store the current session and options, and will
150+
ensure the session is maintained across multiple calls.
151+
152+
### Embedding Generation
153+
154+
TODO
155+
156+
### Attachments & Image Caption Generation
157+
158+
TODO
159+
160+
### Streaming
161+
162+
TODO
163+
164+
### Tool Support
165+
166+
TODO
167+
168+
## Options
169+
170+
You can add options to sessions, or to prompts. Different providers and models support
171+
different options.
172+
173+
```go
174+
type Model interface {
175+
// Set session-wide options
176+
Context(...Opt) Context
177+
178+
// Add attachments (images, PDF's) to a user prompt for completion
179+
UserPrompt(string, ...Opt) Context
180+
181+
// Create an embedding vector with embedding options
182+
Embedding(context.Context, string, ...Opt) ([]float64, error)
183+
}
184+
185+
type Context interface {
186+
// Add single-use options when calling the model, which override
187+
// session options. You can attach files to a user prompt.
188+
FromUser(context.Context, string, ...Opt) error
189+
}
190+
```
191+
192+
The options are as follows:
193+
194+
| Option | Ollama | Anthropic | Mistral | OpenAI | Description |
195+
|--------|--------|-----------|---------|--------|-------------|
196+
| `llm.WithTemperature(float64)` | Yes | Yes | Yes | - | What sampling temperature to use, between 0.0 and 1.0. Higher values like 0.7 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. |
197+
| `llm.WithTopP(float64)` | Yes | Yes | Yes | - | Nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. |
198+
| `llm.WithTopK(uint64)` | Yes | Yes | No | - | Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. |
199+
| `llm.WithMaxTokens(uint64)` | No | Yes | Yes | - | The maximum number of tokens to generate in the response. |
200+
| `llm.WithStream(func(llm.Completion))` | Can be enabled when tools are not used | Yes | Yes | - | Stream the response to a function. |
201+
| `llm.WithToolChoice(string, string, ...)` | No | Yes | Use `auto`, `any`, `none`, `required` or a function name. Only the first argument is used. | - | The tool to use for the model. |
202+
| `llm.WithToolKit(llm.ToolKit)` | Cannot be combined with streaming | Yes | Yes | - | The set of tools to use. |
203+
| `llm.WithStopSequence(string, string, ...)` | Yes | Yes | Yes | - | Stop generation if one of these tokens is detected. |
204+
| `llm.WithSystemPrompt(string)` | No | Yes | Yes | - | Set the system prompt for the model. |
205+
| `llm.WithSeed(uint64)` | Yes | Yes | Yes | - | The seed to use for random sampling. If set, different calls will generate deterministic results. |
206+
| `llm.WithFormat(string)` | Use `json` | Yes | Use `json_format` or `text` | - | The format of the response. For Mistral, you must also instruct the model to produce JSON yourself with a system or a user message. |
207+
| `llm.WithPresencePenalty(float64)` | Yes | No | Yes | - | Determines how much the model penalizes the repetition of words or phrases. A higher presence penalty encourages the model to use a wider variety of words and phrases, making the output more diverse and creative. |
208+
| `llm.WithFequencyPenalty(float64)` | Yes | No | Yes | - | Penalizes the repetition of words based on their frequency in the generated text. A higher frequency penalty discourages the model from repeating words that have already appeared frequently in the output, promoting diversity and reducing repetition. |
209+
| `mistral.WithPrediction(string)` | No | No | Yes | - | Enable users to specify expected results, optimizing response times by leveraging known or predictable content. This approach is especially effective for updating text documents or code files with minimal changes, reducing latency while maintaining high-quality results. |
210+
| `llm.WithSafePrompt()` | No | No | Yes | - | Whether to inject a safety prompt before all conversations. |
211+
| `llm.WithNumCompletions(uint64)` | No | No | Yes | - | Number of completions to return for each request. |
212+
| `llm.WithAttachment(io.Reader)` | Yes | Yes | Yes | - | Attach a file to a user prompt. It is the responsibility of the caller to close the reader. |
213+
| `antropic.WithEphemeral()` | No | Yes | No | - | Attachments should be cached server-side |
214+
| `antropic.WithCitations()` | No | Yes | No | - | Attachments should be used in citations |
215+
| `antropic.WithUser(string)` | No | Yes | No | - | Indicate the user name for the request, for debugging |
216+
217+
## The Command Line Tool
218+
219+
You can use the command-line tool to interact with the API. To build the tool, you can use the following command:
220+
221+
```bash
222+
go install github.com/mutablelogic/go-llm/cmd/llm@latest
223+
llm --help
224+
```
225+
226+
The output is something like:
227+
228+
```text
229+
Usage: llm <command> [flags]
230+
231+
LLM agent command line interface
232+
233+
Flags:
234+
-h, --help Show context-sensitive help.
235+
--debug Enable debug output
236+
--verbose Enable verbose output
237+
--ollama-endpoint=STRING Ollama endpoint ($OLLAMA_URL)
238+
--anthropic-key=STRING Anthropic API Key ($ANTHROPIC_API_KEY)
239+
--news-key=STRING News API Key ($NEWSAPI_KEY)
240+
241+
Commands:
242+
agents Return a list of agents
243+
models Return a list of models
244+
tools Return a list of tools
245+
download Download a model
246+
chat Start a chat session
247+
248+
Run "llm <command> --help" for more information on a command.
249+
```
250+
93251
## Contributing & Distribution
94252

95253
*This module is currently in development and subject to change*. Please do file

agent.go

+4
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ type Agent interface {
1111

1212
// Return the models
1313
Models(context.Context) ([]Model, error)
14+
15+
// Return a model by name, or nil if not found.
16+
// Panics on error.
17+
Model(context.Context, string) Model
1418
}

attachment.go

+38
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package llm
22

33
import (
4+
"encoding/base64"
5+
"encoding/json"
46
"io"
7+
"mime"
8+
"net/http"
59
"os"
10+
"path/filepath"
611
)
712

813
///////////////////////////////////////////////////////////////////////////////
@@ -31,6 +36,25 @@ func ReadAttachment(r io.Reader) (*Attachment, error) {
3136
return &Attachment{filename: filename, data: data}, nil
3237
}
3338

39+
////////////////////////////////////////////////////////////////////////////////
40+
// STRINGIFY
41+
42+
func (a *Attachment) String() string {
43+
var j struct {
44+
Filename string `json:"filename"`
45+
Type string `json:"type"`
46+
Bytes uint64 `json:"bytes"`
47+
}
48+
j.Filename = a.filename
49+
j.Type = a.Type()
50+
j.Bytes = uint64(len(a.data))
51+
data, err := json.MarshalIndent(j, "", " ")
52+
if err != nil {
53+
return err.Error()
54+
}
55+
return string(data)
56+
}
57+
3458
////////////////////////////////////////////////////////////////////////////////
3559
// PUBLIC METHODS
3660

@@ -41,3 +65,17 @@ func (a *Attachment) Filename() string {
4165
func (a *Attachment) Data() []byte {
4266
return a.data
4367
}
68+
69+
func (a *Attachment) Type() string {
70+
// Mimetype based on content
71+
mimetype := http.DetectContentType(a.data)
72+
if mimetype == "application/octet-stream" && a.filename != "" {
73+
// Detect mimetype from extension
74+
mimetype = mime.TypeByExtension(filepath.Ext(a.filename))
75+
}
76+
return mimetype
77+
}
78+
79+
func (a *Attachment) Url() string {
80+
return "data:" + a.Type() + ";base64," + base64.StdEncoding.EncodeToString(a.data)
81+
}

0 commit comments

Comments
 (0)