Repository: neurocult/agency
Branch: main
Commit: a1e4f35c5bda
Files: 38
Total size: 70.2 KB
Directory structure:
gitextract_oea_xf51/
├── .gitignore
├── LICENSE
├── README.md
├── agency.go
├── examples/
│ ├── README.md
│ ├── chat/
│ │ └── main.go
│ ├── cli/
│ │ └── main.go
│ ├── custom_operation/
│ │ └── main.go
│ ├── func_call/
│ │ └── main.go
│ ├── image_to_stream/
│ │ └── main.go
│ ├── image_to_text/
│ │ └── main.go
│ ├── logging/
│ │ └── main.go
│ ├── prompt_template/
│ │ └── main.go
│ ├── rag_vector_database/
│ │ ├── data.go
│ │ ├── docker-compose.yaml
│ │ └── main.go
│ ├── speech_to_text/
│ │ └── main.go
│ ├── speech_to_text_multi_model/
│ │ └── main.go
│ ├── speech_to_text_to_image/
│ │ └── main.go
│ ├── text_to_image_dalle2/
│ │ └── main.go
│ ├── text_to_speech/
│ │ └── main.go
│ ├── text_to_stream/
│ │ └── main.go
│ └── translate_text/
│ └── main.go
├── go.mod
├── go.sum
├── messages.go
├── process.go
└── providers/
└── openai/
├── helpers.go
├── helpers_test.go
├── image_to_text.go
├── provider.go
├── speech_to_text.go
├── text_to_embedding.go
├── text_to_image.go
├── text_to_speech.go
├── text_to_stream.go
├── text_to_text.go
└── tools.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
# ide
.idea
.vscode
# env
.env
# generation artifacts
example.mp3
speech.ogg
example.png
speech.mp3
# mac os
.DS_Store
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Agency: The Go Way to AI
Library designed for developers eager to explore the potential of Large Language Models (LLMs) and other generative AI through a clean, effective, and Go-idiomatic approach.
**Welcome to the agency!** 🕵️♂️

## 💻 Quick Start
Install package:
```bash
go get github.com/neurocult/agency
```
Chat example:
```go
package main
import (
"bufio"
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
assistant := openai.
New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")}).
TextToText(openai.TextToTextParams{Model: "gpt-4o-mini"}).
SetPrompt("You are helpful assistant.")
messages := []agency.Message{}
reader := bufio.NewReader(os.Stdin)
ctx := context.Background()
for {
fmt.Print("User: ")
text, err := reader.ReadString('\n')
if err != nil {
panic(err)
}
input := agency.NewTextMessage(agency.UserRole, text)
answer, err := assistant.SetMessages(messages).Execute(ctx, input)
if err != nil {
panic(err)
}
fmt.Println("Assistant:", string(answer.Content()))
messages = append(messages, input, answer)
}
}
```
That's it!
See [examples](./examples/) to find out more complex usecases including RAGs and multimodal operations.
## 🚀 Features
✨ **Pure Go**: fast and lightweight, statically typed, no need to mess with Python or JavaScript
✨ Write **clean code** and follow **clean architecture** by separating business logic from concrete implementations
✨ Easily create **custom operations** by implementing simple interface
✨ **Compose operations** together into **processes** with the ability to observe each step via **interceptors**
✨ **OpenAI API bindings** (can be used for any openai-compatable API: text to text (completion), text to image, text to speech, speech to text
<!-- TODO v0.1.0
- [ ] Name the organization
- [ ] Reorganize folders and packages -->
## 🤔 Why need Agency?
At the heart of Agency is the ambition to empower users to build autonomous agents. While **perfect for all range of generative AI applications**, from chat interfaces to complex data analysis, our library's ultimate goal is to simplify the creation of autonomous AI systems. Whether you're building individual assistant or coordinating agent swarms, Agency provides the tools and flexibility needed to bring these advanced concepts to life with ease and efficiency.
In the generative AI landscape, Go-based libraries are rare. The most notable is [LangChainGo](https://github.com/tmc/langchaingo), a Go port of the Python LangChain. However, translating Python to Go can be clunky and may not fit well with Go's idiomatic style. Plus, some question LangChain's design, even in Python. This situation reveals a clear need for an idiomatic Go alternative.
Our goal is to fill this gap with a Go-centric library that emphasizes clean, simple code and avoids unnecessary complexities. Agency is designed with a small, robust core, easy to extend and perfectly suited to Go's strengths in static typing and performance. It's our answer to the lack of Go-native solutions in generative AI.
## Tutorial
- [Part 1](https://dev.to/emil14/agency-the-go-way-to-ai-part-1-1lhe) ([Russian translation](https://habr.com/ru/sandbox/204508/))
## 🛣 Roadmap
In the next versions:
- [x] Support for external function calls
- [ ] Metadata (tokens used, audio duration, etc)
- [ ] More provider-adapters, not only openai
- [x] Image to text operations
- [ ] Powerful API for autonomous agents
- [ ] Tagging and JSON output parser
================================================
FILE: agency.go
================================================
package agency
import (
"context"
"fmt"
)
// Operation is basic building block.
type Operation struct {
handler OperationHandler
config *OperationConfig
}
// OperationHandler is a function that implements operation's logic.
// It could be thought of as an interface that providers must implement.
type OperationHandler func(context.Context, Message, *OperationConfig) (Message, error)
// OperationConfig represents abstract operation configuration for all possible models.
type OperationConfig struct {
Prompt string
Messages []Message
}
func (p *Operation) Config() *OperationConfig {
return p.config
}
// NewOperation allows to create an operation from a function.
func NewOperation(handler OperationHandler) *Operation {
return &Operation{
handler: handler,
config: &OperationConfig{},
}
}
// Execute executes operation handler with input message and current configuration.
func (p *Operation) Execute(ctx context.Context, input Message) (Message, error) {
output, err := p.handler(ctx, input, p.config)
if err != nil {
return nil, err
}
return output, nil
}
func (p *Operation) SetPrompt(prompt string, args ...any) *Operation {
p.config.Prompt = fmt.Sprintf(prompt, args...)
return p
}
func (p *Operation) SetMessages(msgs []Message) *Operation {
p.config.Messages = msgs
return p
}
================================================
FILE: examples/README.md
================================================
To run an example:
```shell
export OPENAI_API_KEY="<your key here>"
go run ./example_name
```
================================================
FILE: examples/chat/main.go
================================================
package main
import (
"bufio"
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
assistant := openai.
New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")}).
TextToText(openai.TextToTextParams{Model: "gpt-4o-mini"}).
SetPrompt("You are helpful assistant.")
messages := []agency.Message{}
reader := bufio.NewReader(os.Stdin)
ctx := context.Background()
for {
fmt.Print("User: ")
text, err := reader.ReadString('\n')
if err != nil {
panic(err)
}
input := agency.NewTextMessage(agency.UserRole, text)
answer, err := assistant.SetMessages(messages).Execute(ctx, input)
if err != nil {
panic(err)
}
fmt.Println("Assistant:", string(answer.Content()))
messages = append(messages, input, answer)
}
}
================================================
FILE: examples/cli/main.go
================================================
package main
import (
"context"
"flag"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
// usage example: go to the repo root and execute
// go run examples/cli/main.go -prompt "You are professional translator, translate everything you see to Russian" -model "gpt-4o-mini" -maxTokens=1000 "I love winter"
func main() {
provider := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")})
temp := flag.Float64("temp", 0.0, "Temperature value")
maxTokens := flag.Int("maxTokens", 0, "Maximum number of tokens")
model := flag.String("model", "gpt-4o-mini", "Model name")
prompt := flag.String("prompt", "You are a helpful assistant", "System message")
flag.Parse()
args := flag.Args()
if len(args) < 1 {
fmt.Println("content argument is required")
os.Exit(1)
}
content := args[0]
result, err := provider.
TextToText(openai.TextToTextParams{
Model: *model,
Temperature: openai.Temperature(float32(*temp)),
MaxTokens: *maxTokens,
}).
SetPrompt(*prompt).
Execute(context.Background(), agency.NewMessage(agency.UserRole, agency.TextKind, []byte(content)))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(result.Content())
}
================================================
FILE: examples/custom_operation/main.go
================================================
package main
import (
"context"
"fmt"
"strconv"
"github.com/neurocult/agency"
)
func main() {
increment := agency.NewOperation(incrementFunc)
msg, err := agency.NewProcess(
increment,
increment,
increment,
).Execute(
context.Background(),
agency.NewMessage(
agency.UserRole,
agency.TextKind,
[]byte("0"),
),
)
if err != nil {
panic(err)
}
fmt.Println(string(msg.Content()))
}
func incrementFunc(ctx context.Context, msg agency.Message, _ *agency.OperationConfig) (agency.Message, error) {
i, err := strconv.ParseInt(string(msg.Content()), 10, 10)
if err != nil {
return nil, err
}
inc := strconv.Itoa(int(i) + 1)
return agency.NewMessage(agency.ToolRole, agency.TextKind, []byte(inc)), nil
}
================================================
FILE: examples/func_call/main.go
================================================
package main
import (
"context"
"encoding/json"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
go_openai "github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
t2tOp := openai.
New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")}).
TextToText(openai.TextToTextParams{
Model: go_openai.GPT4oMini,
FuncDefs: []openai.FuncDef{
// function without parameters
{
Name: "GetMeaningOfLife",
Description: "Answer questions about meaning of life",
Body: func(ctx context.Context, _ []byte) (agency.Message, error) {
// because we don't need any arguments
return agency.NewTextMessage(agency.ToolRole, "42"), nil
},
},
// function with parameters
{
Name: "ChangeNumbers",
Description: "Change given numbers when asked",
Parameters: &jsonschema.Definition{
Type: "object",
Properties: map[string]jsonschema.Definition{
"a": {Type: "integer"},
"b": {Type: "integer"},
},
},
Body: func(ctx context.Context, params []byte) (agency.Message, error) {
var pp struct{ A, B int }
if err := json.Unmarshal(params, &pp); err != nil {
return nil, err
}
return agency.NewTextMessage(
agency.ToolRole,
fmt.Sprintf("%d", (pp.A+pp.B)*10),
), nil // *10 is just to distinguish from normal response
},
},
},
}).
SetPrompt(`
Answer questions about meaning of life and summing numbers.
Always use GetMeaningOfLife and ChangeNumbers functions results as answers.
Examples:
- User: what is the meaning of life?
- Assistant: 42
- User: 1+1
- Assistant: 20
- User: 1+1 and what is the meaning of life?
- Assistant: 20 and 42`)
ctx := context.Background()
// test for first function call
answer, err := t2tOp.Execute(
ctx,
agency.NewMessage(agency.UserRole, agency.TextKind, []byte("what is the meaning of life?")),
)
if err != nil {
panic(err)
}
printAnswer(answer)
// test for second function call
answer, err = t2tOp.Execute(
ctx,
agency.NewMessage(agency.UserRole, agency.TextKind, []byte("1+1?")),
)
if err != nil {
panic(err)
}
printAnswer(answer)
// test for both function calls at the same time
answer, err = t2tOp.Execute(
ctx,
agency.NewMessage(agency.UserRole, agency.TextKind, []byte("1+1 and what is the meaning of life?")),
)
if err != nil {
panic(err)
}
printAnswer(answer)
}
func printAnswer(message agency.Message) {
fmt.Printf(
"Role: %s; Type: %s; Data: %s\n",
message.Role(),
message.Kind(),
string(message.Content()),
)
}
================================================
FILE: examples/image_to_stream/main.go
================================================
package main
import (
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
providers "github.com/neurocult/agency/providers/openai"
)
func main() {
imgBytes, err := os.ReadFile("example.png")
if err != nil {
panic(err)
}
_, err = providers.New(providers.Params{Key: os.Getenv("OPENAI_API_KEY")}).
TextToStream(providers.TextToStreamParams{
TextToTextParams: providers.TextToTextParams{MaxTokens: 300, Model: "gpt-4o"},
StreamHandler: func(delta, total string, isFirst, isLast bool) error {
fmt.Println(delta)
return nil
}}).
SetPrompt("describe what you see").
Execute(
context.Background(),
agency.NewMessage(agency.UserRole, agency.ImageKind, imgBytes),
)
if err != nil {
panic(err)
}
}
================================================
FILE: examples/image_to_text/main.go
================================================
package main
import (
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
openAIProvider "github.com/neurocult/agency/providers/openai"
"github.com/sashabaranov/go-openai"
)
func main() {
imgBytes, err := os.ReadFile("example.png")
if err != nil {
panic(err)
}
result, err := openAIProvider.New(openAIProvider.Params{Key: os.Getenv("OPENAI_API_KEY")}).
ImageToText(openAIProvider.ImageToTextParams{Model: openai.GPT4o, MaxTokens: 300}).
SetPrompt("describe what you see").
Execute(
context.Background(),
agency.NewMessage(agency.UserRole, agency.ImageKind, imgBytes),
)
if err != nil {
panic(err)
}
fmt.Println(string(result.Content()))
}
================================================
FILE: examples/logging/main.go
================================================
package main
import (
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
factory := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")})
params := openai.TextToTextParams{Model: "gpt-4o-mini"}
_, err := agency.NewProcess(
factory.TextToText(params).SetPrompt("explain what that means"),
factory.TextToText(params).SetPrompt("translate to russian"),
factory.TextToText(params).SetPrompt("replace all spaces with '_'"),
).
Execute(
context.Background(),
agency.NewMessage(agency.UserRole, agency.TextKind, []byte("Kazakhstan alga!")),
Logger,
)
if err != nil {
panic(err)
}
}
func Logger(input, output agency.Message, cfg *agency.OperationConfig) {
fmt.Printf(
"in: %v\nprompt: %v\nout: %v\n\n",
string(input.Content()),
cfg.Prompt,
string(output.Content()),
)
}
================================================
FILE: examples/prompt_template/main.go
================================================
package main
import (
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
factory := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")})
resultMsg, err := factory.
TextToText(openai.TextToTextParams{Model: "gpt-4o-mini"}).
SetPrompt(
"You are a helpful assistant that translates %s to %s",
"English", "French",
).
Execute(
context.Background(),
agency.NewMessage(agency.UserRole, agency.TextKind, []byte("I love programming.")),
)
if err != nil {
panic(err)
}
fmt.Println(string(resultMsg.Content()))
}
================================================
FILE: examples/rag_vector_database/data.go
================================================
package main
import "github.com/weaviate/weaviate/entities/models"
// first 5 objects contain something about programming without word 'programming' while other are random topics.
var data []*models.Object = []*models.Object{
{
Class: "Records",
Properties: map[string]string{"content": "Debugging Java applications can be challenging."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Writing code in Python is both fun and efficient."},
},
{
Class: "Records",
Properties: map[string]string{"content": "HTML and CSS are the building blocks of web development."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Using version control systems like Git is essential for collaborative coding."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Understanding algorithms is crucial for effective software development."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Swimming is a fun and beneficial form of exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Playing musical instruments can be both challenging and fulfilling."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Playing musical instruments can be both challenging and fulfilling."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Learning a new language opens up a world of opportunities."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Playing musical instruments can be both challenging and fulfilling."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Meditation helps in reducing stress and improving focus."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Playing musical instruments can be both challenging and fulfilling."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Gardening is a peaceful and rewarding hobby."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Running is a simple and effective way to stay fit."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Playing musical instruments can be both challenging and fulfilling."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "The art of cooking is a blend of flavors and techniques."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Watching movies is a popular form of entertainment."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Practicing yoga promotes physical and mental well-being."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Reading books is a journey through knowledge and imagination."},
},
{
Class: "Records",
Properties: map[string]string{"content": "The art of cooking is a blend of flavors and techniques."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Running is a simple and effective way to stay fit."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Practicing yoga promotes physical and mental well-being."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Photography captures moments and memories."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Gardening is a peaceful and rewarding hobby."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Swimming is a fun and beneficial form of exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Reading books is a journey through knowledge and imagination."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Painting allows for creative expression and relaxation."},
},
{
Class: "Records",
Properties: map[string]string{"content": "The art of cooking is a blend of flavors and techniques."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Photography captures moments and memories."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Reading books is a journey through knowledge and imagination."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Running is a simple and effective way to stay fit."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Playing musical instruments can be both challenging and fulfilling."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Learning a new language opens up a world of opportunities."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Painting allows for creative expression and relaxation."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "The art of cooking is a blend of flavors and techniques."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Meditation helps in reducing stress and improving focus."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Practicing yoga promotes physical and mental well-being."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Swimming is a fun and beneficial form of exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Photography captures moments and memories."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Practicing yoga promotes physical and mental well-being."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Running is a simple and effective way to stay fit."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Swimming is a fun and beneficial form of exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Running is a simple and effective way to stay fit."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Meditation helps in reducing stress and improving focus."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Photography captures moments and memories."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Running is a simple and effective way to stay fit."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Practicing yoga promotes physical and mental well-being."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Practicing yoga promotes physical and mental well-being."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Reading books is a journey through knowledge and imagination."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Watching movies is a popular form of entertainment."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Playing musical instruments can be both challenging and fulfilling."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Painting allows for creative expression and relaxation."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Hiking in nature is both invigorating and relaxing."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Meditation helps in reducing stress and improving focus."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Watching movies is a popular form of entertainment."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Photography captures moments and memories."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Learning a new language opens up a world of opportunities."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Painting allows for creative expression and relaxation."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Photography captures moments and memories."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Practicing yoga promotes physical and mental well-being."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Practicing yoga promotes physical and mental well-being."},
},
{
Class: "Records",
Properties: map[string]string{"content": "The art of cooking is a blend of flavors and techniques."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Running is a simple and effective way to stay fit."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Running is a simple and effective way to stay fit."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Photography captures moments and memories."},
},
{
Class: "Records",
Properties: map[string]string{"content": "The art of cooking is a blend of flavors and techniques."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Photography captures moments and memories."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Playing musical instruments can be both challenging and fulfilling."},
},
{
Class: "Records",
Properties: map[string]string{"content": "The art of cooking is a blend of flavors and techniques."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Swimming is a fun and beneficial form of exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Cycling is both an eco-friendly transport and a great way to exercise."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Traveling to new places is an adventure worth having."},
},
{
Class: "Records",
Properties: map[string]string{"content": "Painting allows for creative expression and relaxation."},
},
}
================================================
FILE: examples/rag_vector_database/docker-compose.yaml
================================================
---
version: "3.4"
services:
weaviate:
command:
- --host
- 0.0.0.0
- --port
- "8080"
- --scheme
- http
image: cr.weaviate.io/semitechnologies/weaviate:1.22.4
ports:
- 8080:8080
- 50051:50051
restart: on-failure:0
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: "true"
PERSISTENCE_DATA_PATH: "/var/lib/weaviate"
DEFAULT_VECTORIZER_MODULE: "none"
ENABLE_MODULES: "text2vec-cohere,text2vec-huggingface,text2vec-palm,text2vec-openai,generative-openai,generative-cohere,generative-palm,ref2vec-centroid,reranker-cohere,qna-openai"
CLUSTER_HOSTNAME: "node1"
volumes:
weaviate_data:
================================================
FILE: examples/rag_vector_database/main.go
================================================
package main
import (
"context"
"encoding/json"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
"github.com/weaviate/weaviate-go-client/v4/weaviate"
"github.com/weaviate/weaviate-go-client/v4/weaviate/graphql"
"github.com/weaviate/weaviate/entities/models"
"github.com/neurocult/agency/providers/openai"
)
// natural langauge query -> weaviate RAG -> speech
func main() {
openAPIKey := os.Getenv("OPENAI_API_KEY")
ctx := context.Background()
client, err := prepareDB(openAPIKey, ctx)
if err != nil {
panic(err)
}
factory := openai.New(openai.Params{Key: openAPIKey})
retrieve := RAGoperation(client)
summarize := factory.TextToText(openai.TextToTextParams{Model: "gpt-4o-mini"}).SetPrompt("summarize")
voice := factory.TextToSpeech(openai.TextToSpeechParams{
Model: "tts-1", ResponseFormat: "mp3", Speed: 1, Voice: "onyx",
})
result, err := agency.NewProcess(
retrieve,
summarize,
voice,
).Execute(ctx, agency.NewMessage(agency.UserRole, agency.TextKind, []byte("programming")))
if err != nil {
panic(err)
}
if err := saveToDisk(result); err != nil {
panic(err)
}
}
// RAGoperation retrieves relevant objects from vector store and builds a text message to pass further to the process
func RAGoperation(client *weaviate.Client) *agency.Operation {
return agency.NewOperation(func(ctx context.Context, msg agency.Message, po *agency.OperationConfig) (agency.Message, error) {
input := string(msg.Content())
result, err := client.GraphQL().Get().
WithClassName("Records").
WithFields(graphql.Field{Name: "content"}).
WithNearText(
client.GraphQL().
NearTextArgBuilder().
WithConcepts(
[]string{input},
),
).
WithLimit(10).
Do(ctx)
if err != nil {
panic(err)
}
var content string
for _, obj := range result.Data {
bb, err := json.Marshal(&obj)
if err != nil {
return nil, err
}
content += string(bb)
}
return agency.NewMessage(
agency.AssistantRole,
agency.TextKind,
[]byte(content),
), nil
})
}
func prepareDB(openAPIKey string, ctx context.Context) (*weaviate.Client, error) {
client, err := weaviate.NewClient(weaviate.Config{
Host: "localhost:8080",
Scheme: "http",
Headers: map[string]string{
"X-OpenAI-Api-Key": openAPIKey,
},
})
if err != nil {
return nil, err
}
if err := client.Schema().AllDeleter().Do(ctx); err != nil {
return nil, err
}
classObj := &models.Class{
Class: "Records",
Vectorizer: "text2vec-openai",
ModuleConfig: map[string]interface{}{
"text2vec-openai": map[string]interface{}{},
"generative-openai": map[string]interface{}{},
},
}
if err = client.Schema().ClassCreator().WithClass(classObj).Do(context.Background()); err != nil {
return nil, err
}
if _, err := client.Batch().ObjectsBatcher().WithObjects(data...).Do(ctx); err != nil {
return nil, err
}
return client, nil
}
func saveToDisk(msg agency.Message) error {
file, err := os.Create("speech.mp3")
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(msg.Content())
if err != nil {
return err
}
return nil
}
================================================
FILE: examples/speech_to_text/main.go
================================================
// To make this example work make sure you have speech.ogg file in the root of directory.
// You can use text to speech example to generate speech file.
package main
import (
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
goopenai "github.com/sashabaranov/go-openai"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
factory := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")})
data, err := os.ReadFile("speech.mp3")
if err != nil {
panic(err)
}
result, err := factory.SpeechToText(openai.SpeechToTextParams{
Model: goopenai.Whisper1,
}).Execute(
context.Background(),
agency.NewMessage(agency.UserRole, agency.VoiceKind, data),
)
if err != nil {
panic(err)
}
fmt.Println(string(result.Content()))
}
================================================
FILE: examples/speech_to_text_multi_model/main.go
================================================
// To make this example work make sure you have speech.ogg file in the root of directory
package main
import (
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
goopenai "github.com/sashabaranov/go-openai"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
type Saver []agency.Message
func (s *Saver) Save(input, output agency.Message, _ *agency.OperationConfig) {
*s = append(*s, output)
}
func main() {
factory := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")})
// step 1
hear := factory.
SpeechToText(openai.SpeechToTextParams{
Model: goopenai.Whisper1,
})
// step2
translate := factory.
TextToText(openai.TextToTextParams{
Model: "gpt-4o-mini",
Temperature: openai.Temperature(0.5),
}).
SetPrompt("translate to russian")
// step 3
uppercase := factory.
TextToText(openai.TextToTextParams{
Model: "gpt-4o-mini",
Temperature: openai.Temperature(1),
}).
SetPrompt("uppercase every letter of the text")
saver := Saver{}
sound, err := os.ReadFile("speech.mp3")
if err != nil {
panic(err)
}
ctx := context.Background()
speechMsg := agency.NewMessage(agency.UserRole, agency.VoiceKind, sound)
_, err = agency.NewProcess(
hear,
translate,
uppercase,
).Execute(ctx, speechMsg, saver.Save)
if err != nil {
panic(err)
}
for _, msg := range saver {
fmt.Println(string(msg.Content()))
}
}
================================================
FILE: examples/speech_to_text_to_image/main.go
================================================
// To make this example work make sure you have speech.ogg file in the root of directory
package main
import (
"bytes"
"context"
"image/png"
"os"
_ "github.com/joho/godotenv/autoload"
goopenai "github.com/sashabaranov/go-openai"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
factory := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")})
data, err := os.ReadFile("speech.mp3")
if err != nil {
panic(err)
}
msg, err := agency.NewProcess(
factory.SpeechToText(openai.SpeechToTextParams{Model: goopenai.Whisper1}),
factory.TextToImage(openai.TextToImageParams{
Model: goopenai.CreateImageModelDallE2,
ImageSize: goopenai.CreateImageSize256x256,
}),
).Execute(context.Background(), agency.NewMessage(agency.UserRole, agency.VoiceKind, data))
if err != nil {
panic(err)
}
if err := saveImgToDisk(msg); err != nil {
panic(err)
}
}
func saveImgToDisk(msg agency.Message) error {
r := bytes.NewReader(msg.Content())
imgData, err := png.Decode(r)
if err != nil {
return err
}
file, err := os.Create("example.png")
if err != nil {
return err
}
defer file.Close()
if err := png.Encode(file, imgData); err != nil {
return err
}
return nil
}
================================================
FILE: examples/text_to_image_dalle2/main.go
================================================
package main
import (
"bytes"
"context"
"fmt"
"image"
"image/png"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
provider := openai.New(openai.Params{
Key: os.Getenv("OPENAI_API_KEY"),
})
result, err := provider.TextToImage(openai.TextToImageParams{
Model: "dall-e-2",
ImageSize: "512x512",
Quality: "standard",
Style: "vivid",
}).Execute(
context.Background(),
agency.NewMessage(agency.UserRole, agency.TextKind, []byte("Halloween night at a haunted museum")),
)
if err != nil {
panic(err)
}
if err := saveToDisk(result); err != nil {
panic(err)
}
fmt.Println("Image has been saved!")
}
func saveToDisk(msg agency.Message) error {
r := bytes.NewReader(msg.Content())
// for dall-e-3 use third party libraries due to lack of webp support in go stdlib
imgData, format, err := image.Decode(r)
if err != nil {
return err
}
file, err := os.Create("example." + format)
if err != nil {
return err
}
defer file.Close()
if err := png.Encode(file, imgData); err != nil {
return err
}
return nil
}
================================================
FILE: examples/text_to_speech/main.go
================================================
package main
import (
"context"
"os"
_ "github.com/joho/godotenv/autoload"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
input := agency.NewMessage(
agency.UserRole,
agency.TextKind,
[]byte(`One does not simply walk into Mordor.
Its black gates are guarded by more than just Orcs.
There is evil there that does not sleep, and the Great Eye is ever watchful.`))
msg, err := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")}).
TextToSpeech(openai.TextToSpeechParams{
Model: "tts-1",
ResponseFormat: "mp3",
Speed: 1,
Voice: "alloy",
}).
Execute(context.Background(), input)
if err != nil {
panic(err)
}
if err := saveToDisk(msg); err != nil {
panic(err)
}
}
func saveToDisk(msg agency.Message) error {
file, err := os.Create("speech.mp3")
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(msg.Content())
if err != nil {
return err
}
return nil
}
================================================
FILE: examples/text_to_stream/main.go
================================================
package main
import (
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
goopenai "github.com/sashabaranov/go-openai"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
factory := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")})
result, err := factory.
TextToStream(openai.TextToStreamParams{
TextToTextParams: openai.TextToTextParams{Model: goopenai.GPT4oMini},
StreamHandler: func(delta, total string, isFirst, isLast bool) error {
if isFirst {
fmt.Println("====Start streaming====")
}
fmt.Print(delta)
if isLast {
fmt.Println("\n====Finish streaming====")
}
return nil
},
}).
SetPrompt("Write a few sentences about topic").
Execute(
context.Background(),
agency.NewMessage(
agency.UserRole,
agency.TextKind,
[]byte("I love programming."),
),
)
if err != nil {
panic(err)
}
fmt.Println("\nResult:", string(result.Content()))
}
================================================
FILE: examples/translate_text/main.go
================================================
package main
import (
"context"
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
goopenai "github.com/sashabaranov/go-openai"
"github.com/neurocult/agency"
"github.com/neurocult/agency/providers/openai"
)
func main() {
factory := openai.New(openai.Params{Key: os.Getenv("OPENAI_API_KEY")})
result, err := factory.
TextToText(openai.TextToTextParams{Model: goopenai.GPT4oMini}).
SetPrompt("You are a helpful assistant that translates English to French").
Execute(
context.Background(),
agency.NewMessage(
agency.UserRole,
agency.TextKind,
[]byte("I love programming."),
),
)
if err != nil {
panic(err)
}
fmt.Println(string(result.Content()))
}
================================================
FILE: go.mod
================================================
module github.com/neurocult/agency
go 1.21.0
require (
github.com/sashabaranov/go-openai v1.36.1
github.com/weaviate/weaviate v1.24.8
github.com/weaviate/weaviate-go-client/v4 v4.13.1
)
require (
github.com/google/uuid v1.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
)
require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/joho/godotenv v1.5.1
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
go.mongodb.org/mongo-driver v1.15.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56 // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/sashabaranov/go-openai v1.36.1 h1:EVfRXwIlW2rUzpx6vR+aeIKCK/xylSrVYAx1TMTSX3g=
github.com/sashabaranov/go-openai v1.36.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/weaviate/weaviate v1.24.8 h1:obeBOJuXScDvUlbTKuqPwJl/cUB5csRhCN6q4smcQiM=
github.com/weaviate/weaviate v1.24.8/go.mod h1:LkAk+xUwF8DKKRb9dEI9DrquwJ/tUzfgd2NN+KEDTYU=
github.com/weaviate/weaviate-go-client/v4 v4.13.1 h1:7PuK/hpy6Q0b9XaVGiUg5OD1MI/eF2ew9CJge9XdBEE=
github.com/weaviate/weaviate-go-client/v4 v4.13.1/go.mod h1:B2m6g77xWDskrCq1GlU6CdilS0RG2+YXEgzwXRADad0=
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56 h1:zviK8GX4VlMstrK3JkexM5UHjH1VOkRebH9y3jhSBGk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=
================================================
FILE: messages.go
================================================
package agency
import "encoding/json"
type Message interface {
Role() Role
Content() []byte
Kind() Kind
}
type Kind string
const (
TextKind Kind = "text"
ImageKind Kind = "image"
VoiceKind Kind = "voice"
EmbeddingKind Kind = "embedding"
)
type Role string
const (
UserRole Role = "user"
SystemRole Role = "system"
AssistantRole Role = "assistant"
ToolRole Role = "tool"
)
type BaseMessage struct {
content []byte
role Role
kind Kind
}
func (bm BaseMessage) Role() Role {
return bm.role
}
func (bm BaseMessage) Kind() Kind {
return bm.kind
}
func (bm BaseMessage) Content() []byte {
return bm.content
}
// NewMessage creates new `Message` with the specified `Role` and `Kind`
func NewMessage(role Role, kind Kind, content []byte) BaseMessage {
return BaseMessage{
content: content,
role: role,
kind: kind,
}
}
// NewTextMessage creates new `Message` with Text kind and the specified `Role`
func NewTextMessage(role Role, content string) BaseMessage {
return BaseMessage{
content: []byte(content),
role: role,
kind: TextKind,
}
}
// NewJsonMessage marshals content and creates new `Message` with text kind and the specified `Role`
func NewJsonMessage(role Role, content any) (BaseMessage, error) {
data, err := json.Marshal(content)
if err != nil {
return BaseMessage{}, err
}
return BaseMessage{
content: data,
role: role,
kind: TextKind,
}, nil
}
================================================
FILE: process.go
================================================
package agency
import (
"context"
)
// Process is a chain of operations that can be executed in sequence.
type Process struct {
operations []*Operation
}
// NewProcess creates a new Process with given operations.
func NewProcess(operations ...*Operation) *Process {
return &Process{
operations: operations,
}
}
// Interceptor is a function that is called by Process after one operation finished but before next one is started.
type Interceptor func(in Message, out Message, cfg *OperationConfig)
// Execute iterates over Process's operations and sequentially executes them.
// After first operation is executed it uses its output as an input to the second one and so on until the whole chain is finished.
// It also executes all given interceptors, if they are provided, so for every N operations and M interceptors it's N x M executions.
func (p *Process) Execute(ctx context.Context, input Message, interceptors ...Interceptor) (Message, error) {
for _, operation := range p.operations {
output, err := operation.Execute(ctx, input)
if err != nil {
return nil, err
}
// FIXME while these are called AFTER operation and not before it's impossible to modify configuration
for _, interceptor := range interceptors {
interceptor(input, output, operation.Config())
}
input = output
}
return input, nil
}
================================================
FILE: providers/openai/helpers.go
================================================
package openai
import (
"encoding/binary"
"fmt"
"math"
)
type Embedding []float32
func EmbeddingToBytes(dimensions int, embeddings []Embedding) ([]byte, error) {
if len(embeddings) == 0 {
return nil, fmt.Errorf("embeddings is empty")
}
buf := make([]byte, len(embeddings)*dimensions*4)
for i, embedding := range embeddings {
if len(embedding) != dimensions {
return nil, fmt.Errorf("invalid embedding length: %d, expected %d", len(embedding), dimensions)
}
for j, f := range embedding {
u := math.Float32bits(f)
binary.LittleEndian.PutUint32(buf[(i*dimensions+j)*4:], u)
}
}
return buf, nil
}
func BytesToEmbedding(dimensions int, buf []byte) ([]Embedding, error) {
if mltp := len(buf) % (dimensions * 4); mltp != 0 {
return nil, fmt.Errorf("invalid buffer length: got %d, but expected multiple of %d", len(buf), dimensions*4)
}
embeddings := make([]Embedding, len(buf)/dimensions/4)
for i := range embeddings {
embeddings[i] = make([]float32, dimensions)
for j := 0; j < dimensions; j++ {
index := (i*dimensions + j) * 4
if index+4 > len(buf) {
return nil, fmt.Errorf("buffer is too small for expected number of embeddings")
}
embeddings[i][j] = math.Float32frombits(binary.LittleEndian.Uint32(buf[index:]))
}
}
return embeddings, nil
}
// NullableFloat32 is a type that exists to distinguish between undefined values and real zeros.
// It fixes sashabaranov/go-openai issue with zero temp not included in api request due to how json unmarshal work.
type NullableFloat32 *float32
// Temperature is just a tiny helper to create nullable float32 value from regular float32
func Temperature(v float32) NullableFloat32 {
return &v
}
// nullableToFloat32 replaces nil with zero (in this case value won't be included in api request)
// and for real zeros it returns math.SmallestNonzeroFloat32 that is as close to zero as possible.
func nullableToFloat32(v NullableFloat32) float32 {
if v == nil {
return 0
}
if *v == 0 {
return math.SmallestNonzeroFloat32
}
return *v
}
================================================
FILE: providers/openai/helpers_test.go
================================================
package openai
import (
"reflect"
"testing"
)
func TestEmbeddingToBytes(t *testing.T) {
floats := []Embedding{{1.1, 2.2, 3.3}, {4.4, 5.5, 6.6}}
bytes, err := EmbeddingToBytes(3, floats)
if err != nil {
t.Errorf("EmbeddingToBytes error %v", err)
}
newFloats, err := BytesToEmbedding(3, bytes)
if err != nil {
t.Errorf("EmbeddingToBytes error %v", err)
}
if !reflect.DeepEqual(floats, newFloats) {
t.Errorf("floats and newFloats are not equal %v %v", floats, newFloats)
}
wrongFloats := []Embedding{{4.4, 5.5, 6.6, 7.7}}
_, err = EmbeddingToBytes(3, wrongFloats)
if err == nil {
t.Errorf("EmbeddingToBytes should has error")
}
bytes, err = EmbeddingToBytes(4, wrongFloats)
if err != nil {
t.Errorf("EmbeddingToBytes error %v", err)
}
_, err = BytesToEmbedding(3, bytes)
if err == nil {
t.Errorf("BytesToEmbedding should has error")
}
}
================================================
FILE: providers/openai/image_to_text.go
================================================
package openai
import (
"context"
"encoding/base64"
"errors"
"fmt"
"github.com/neurocult/agency"
"github.com/sashabaranov/go-openai"
)
type ImageToTextParams struct {
Model string
MaxTokens int
Temperature NullableFloat32
TopP NullableFloat32
FrequencyPenalty NullableFloat32
PresencePenalty NullableFloat32
}
// ImageToText is an operation builder that creates operation than can convert image to text.
func (f *Provider) ImageToText(params ImageToTextParams) *agency.Operation {
return agency.NewOperation(func(ctx context.Context, msg agency.Message, cfg *agency.OperationConfig) (agency.Message, error) {
openaiMsg := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
MultiContent: make([]openai.ChatMessagePart, 0, len(cfg.Messages)+2),
}
openaiMsg.MultiContent = append(openaiMsg.MultiContent, openai.ChatMessagePart{
Type: openai.ChatMessagePartTypeText,
Text: cfg.Prompt,
})
for _, cfgMsg := range cfg.Messages {
openaiMsg.MultiContent = append(
openaiMsg.MultiContent,
openAIBase64ImageMessage(cfgMsg.Content()),
)
}
openaiMsg.MultiContent = append(
openaiMsg.MultiContent,
openAIBase64ImageMessage(msg.Content()),
)
resp, err := f.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
MaxTokens: params.MaxTokens,
Model: params.Model,
Messages: []openai.ChatCompletionMessage{openaiMsg},
Temperature: nullableToFloat32(params.Temperature),
TopP: nullableToFloat32(params.TopP),
FrequencyPenalty: nullableToFloat32(params.FrequencyPenalty),
PresencePenalty: nullableToFloat32(params.PresencePenalty),
})
if err != nil {
return nil, err
}
if len(resp.Choices) < 1 {
return nil, errors.New("no choice")
}
choice := resp.Choices[0].Message
return agency.NewMessage(agency.AssistantRole, agency.TextKind, []byte(choice.Content)), nil
})
}
func openAIBase64ImageMessage(bb []byte) openai.ChatMessagePart {
imgBase64Str := base64.StdEncoding.EncodeToString(bb)
return openai.ChatMessagePart{
Type: openai.ChatMessagePartTypeImageURL,
ImageURL: &openai.ChatMessageImageURL{
URL: fmt.Sprintf("data:image/jpeg;base64,%s", imgBase64Str),
Detail: openai.ImageURLDetailAuto,
},
}
}
================================================
FILE: providers/openai/provider.go
================================================
package openai
import (
"github.com/sashabaranov/go-openai"
)
// Provider is a set of operation builders.
type Provider struct {
client *openai.Client
}
// Params is a set of parameters specific for creating this concrete provider.
// They are shared across all operation builders.
type Params struct {
Key string // Required if not using local LLM.
BaseURL string // Optional. If not set then default openai base url is used
}
// New creates a new Provider instance.
func New(params Params) *Provider {
cfg := openai.DefaultConfig(params.Key)
if params.BaseURL != "" {
cfg.BaseURL = params.BaseURL
}
return &Provider{
client: openai.NewClientWithConfig(cfg),
}
}
================================================
FILE: providers/openai/speech_to_text.go
================================================
package openai
import (
"bytes"
"context"
"github.com/neurocult/agency"
"github.com/sashabaranov/go-openai"
)
type SpeechToTextParams struct {
Model string
Temperature NullableFloat32
}
// SpeechToText is an operation builder that creates operation than can convert speech to text.
func (f Provider) SpeechToText(params SpeechToTextParams) *agency.Operation {
return agency.NewOperation(
func(ctx context.Context, msg agency.Message, cfg *agency.OperationConfig) (agency.Message, error) {
resp, err := f.client.CreateTranscription(ctx, openai.AudioRequest{
Model: params.Model,
Prompt: cfg.Prompt,
FilePath: "speech.ogg",
Reader: bytes.NewReader(msg.Content()),
Temperature: nullableToFloat32(params.Temperature),
})
if err != nil {
return nil, err
}
return agency.NewMessage(agency.AssistantRole, agency.TextKind, []byte(resp.Text)), nil
},
)
}
================================================
FILE: providers/openai/text_to_embedding.go
================================================
package openai
import (
"context"
"fmt"
"github.com/sashabaranov/go-openai"
"github.com/neurocult/agency"
)
type EmbeddingModel = openai.EmbeddingModel
const AdaEmbeddingV2 EmbeddingModel = openai.AdaEmbeddingV2
type TextToEmbeddingParams struct {
Model EmbeddingModel
Dimensions EmbeddingDimensions
}
type EmbeddingDimensions *int
func NewDimensions(v int) EmbeddingDimensions {
return &v
}
func (p Provider) TextToEmbedding(params TextToEmbeddingParams) *agency.Operation {
var dimensions int
if params.Dimensions != nil {
dimensions = *params.Dimensions
}
return agency.NewOperation(func(ctx context.Context, msg agency.Message, cfg *agency.OperationConfig) (agency.Message, error) {
// TODO: we have to convert string to model and then model to string. Can we optimize it?
messages := append(cfg.Messages, msg)
texts := make([]string, len(messages))
for i, m := range messages {
texts[i] = string(m.Content())
}
resp, err := p.client.CreateEmbeddings(
ctx,
openai.EmbeddingRequest{
Input: texts,
Model: params.Model,
Dimensions: dimensions,
},
)
if err != nil {
return nil, err
}
vectors := make([]Embedding, len(resp.Data))
for i, vector := range resp.Data {
vectors[i] = vector.Embedding
}
bytes, err := EmbeddingToBytes(1536, vectors)
if err != nil {
return nil, fmt.Errorf("failed to convert embedding to bytes: %w", err)
}
// TODO: we have to convert []float32 to []byte. Can we optimize it?
return agency.NewMessage(agency.AssistantRole, agency.EmbeddingKind, bytes), nil
})
}
================================================
FILE: providers/openai/text_to_image.go
================================================
package openai
import (
"context"
"encoding/base64"
"fmt"
"github.com/neurocult/agency"
"github.com/sashabaranov/go-openai"
)
type TextToImageParams struct {
Model string
ImageSize string
Quality string
Style string
}
// TextToImage is an operation builder that creates operation than can convert text to image.
func (p Provider) TextToImage(params TextToImageParams) *agency.Operation {
return agency.NewOperation(
func(ctx context.Context, msg agency.Message, cfg *agency.OperationConfig) (agency.Message, error) {
reqBase64 := openai.ImageRequest{
Prompt: fmt.Sprintf("%s\n\n%s", cfg.Prompt, string(msg.Content())),
Size: params.ImageSize,
ResponseFormat: openai.CreateImageResponseFormatB64JSON,
N: 1, // DALL·E-3 only support n=1, for other models support needed
Model: params.Model,
Quality: params.Quality,
Style: params.Style,
}
respBase64, err := p.client.CreateImage(ctx, reqBase64)
if err != nil {
return nil, err
}
imgBytes, err := base64.StdEncoding.DecodeString(respBase64.Data[0].B64JSON)
if err != nil {
return nil, err
}
return agency.NewMessage(agency.AssistantRole, agency.ImageKind, imgBytes), nil
},
)
}
================================================
FILE: providers/openai/text_to_speech.go
================================================
package openai
import (
"context"
"io"
"github.com/neurocult/agency"
"github.com/sashabaranov/go-openai"
)
type TextToSpeechParams struct {
Model string
ResponseFormat string
Speed float64
Voice string
}
// TextToSpeech is an operation builder that creates operation than can convert text to speech.
func (f Provider) TextToSpeech(params TextToSpeechParams) *agency.Operation {
return agency.NewOperation(
func(ctx context.Context, msg agency.Message, cfg *agency.OperationConfig) (agency.Message, error) {
resp, err := f.client.CreateSpeech(ctx, openai.CreateSpeechRequest{
Model: openai.SpeechModel(params.Model),
Input: string(msg.Content()),
Voice: openai.SpeechVoice(params.Voice),
ResponseFormat: openai.SpeechResponseFormat(params.ResponseFormat),
Speed: params.Speed,
})
if err != nil {
return nil, err
}
bb, err := io.ReadAll(resp)
if err != nil {
return nil, err
}
return agency.NewMessage(agency.AssistantRole, agency.VoiceKind, bb), nil
},
)
}
================================================
FILE: providers/openai/text_to_stream.go
================================================
package openai
import (
"context"
"errors"
"fmt"
"io"
"github.com/neurocult/agency"
"github.com/sashabaranov/go-openai"
)
type TextToStreamParams struct {
TextToTextParams
StreamHandler func(delta, total string, isFirst, isLast bool) error
}
func (p Provider) TextToStream(params TextToStreamParams) *agency.Operation {
openAITools := castFuncDefsToOpenAITools(params.FuncDefs)
return agency.NewOperation(
func(ctx context.Context, msg agency.Message, cfg *agency.OperationConfig) (agency.Message, error) {
openAIMessages, err := agencyToOpenaiMessages(cfg, msg)
if err != nil {
return nil, fmt.Errorf("text to stream: %w", err)
}
for { // streaming loop
openAIResponse, err := p.client.CreateChatCompletionStream(
ctx,
openai.ChatCompletionRequest{
Model: params.Model,
Temperature: nullableToFloat32(params.Temperature),
MaxTokens: params.MaxTokens,
Messages: openAIMessages,
Tools: openAITools,
Stream: params.StreamHandler != nil,
ToolChoice: params.ToolCallRequired(),
Seed: params.Seed,
ResponseFormat: params.Format,
},
)
if err != nil {
return nil, fmt.Errorf("create chat completion stream: %w", err)
}
var content string
var accumulatedStreamedFunctions = make([]openai.ToolCall, 0, len(openAITools))
var isFirstDelta = true
var isLastDelta = false
var lastDelta string
for {
recv, err := openAIResponse.Recv()
isLastDelta = errors.Is(err, io.EOF)
if len(lastDelta) > 0 || (isLastDelta && len(content) > 0) {
if err = params.StreamHandler(lastDelta, content, isFirstDelta, isLastDelta); err != nil {
return nil, fmt.Errorf("handing stream: %w", err)
}
isFirstDelta = false
}
if isLastDelta {
if len(accumulatedStreamedFunctions) == 0 {
return agency.NewTextMessage(
agency.AssistantRole,
content,
), nil
}
break
}
if err != nil {
return nil, err
}
if len(recv.Choices) < 1 {
return nil, errors.New("no choice")
}
firstChoice := recv.Choices[0]
if len(firstChoice.Delta.Content) > 0 {
lastDelta = firstChoice.Delta.Content
content += lastDelta
} else {
lastDelta = ""
}
for index, toolCall := range firstChoice.Delta.ToolCalls {
if len(accumulatedStreamedFunctions) < index+1 {
accumulatedStreamedFunctions = append(accumulatedStreamedFunctions, openai.ToolCall{
Index: toolCall.Index,
ID: toolCall.ID,
Type: toolCall.Type,
Function: openai.FunctionCall{
Name: toolCall.Function.Name,
Arguments: toolCall.Function.Arguments,
},
})
}
accumulatedStreamedFunctions[index].Function.Arguments += toolCall.Function.Arguments
}
if firstChoice.FinishReason != openai.FinishReasonToolCalls {
continue
}
// Saving tool call to history
openAIMessages = append(openAIMessages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleAssistant,
ToolCalls: accumulatedStreamedFunctions,
})
for _, call := range accumulatedStreamedFunctions {
toolResponse, err := callTool(ctx, call, params.FuncDefs)
if err != nil {
return nil, fmt.Errorf("text to text call tool: %w", err)
}
if toolResponse.Role() != agency.ToolRole {
return toolResponse, nil
}
openAIMessages = append(openAIMessages, toolMessageToOpenAI(toolResponse, call.ID))
}
}
openAIResponse.Close()
}
},
)
}
func messageToOpenAI(message agency.Message) openai.ChatCompletionMessage {
wrappedMessage := openai.ChatCompletionMessage{
Role: string(message.Role()),
}
switch message.Kind() {
case agency.ImageKind:
wrappedMessage.MultiContent = append(
wrappedMessage.MultiContent,
openAIBase64ImageMessage(message.Content()),
)
default:
wrappedMessage.Content = string(message.Content())
}
return wrappedMessage
}
func toolMessageToOpenAI(message agency.Message, toolID string) openai.ChatCompletionMessage {
wrappedMessage := messageToOpenAI(message)
wrappedMessage.ToolCallID = toolID
return wrappedMessage
}
================================================
FILE: providers/openai/text_to_text.go
================================================
package openai
import (
"context"
"errors"
"fmt"
"github.com/sashabaranov/go-openai"
"github.com/neurocult/agency"
)
// TextToTextParams represents parameters that are specific for this operation.
type TextToTextParams struct {
Model string
Temperature NullableFloat32
MaxTokens int
FuncDefs []FuncDef
Seed *int
IsToolsCallRequired bool
Format *openai.ChatCompletionResponseFormat
}
func (p TextToTextParams) ToolCallRequired() *string {
var toolChoice *string
if p.IsToolsCallRequired {
v := "required"
toolChoice = &v
}
return toolChoice
}
// TextToText is an operation builder that creates operation than can convert text to text.
// It can also call provided functions if needed, as many times as needed until the final answer is generated.
func (p Provider) TextToText(params TextToTextParams) *agency.Operation {
openAITools := castFuncDefsToOpenAITools(params.FuncDefs)
return agency.NewOperation(
func(ctx context.Context, msg agency.Message, cfg *agency.OperationConfig) (agency.Message, error) {
openAIMessages, err := agencyToOpenaiMessages(cfg, msg)
if err != nil {
return nil, fmt.Errorf("text to stream: %w", err)
}
for {
openAIResponse, err := p.client.CreateChatCompletion(
ctx,
openai.ChatCompletionRequest{
Model: params.Model,
Temperature: nullableToFloat32(params.Temperature),
MaxTokens: params.MaxTokens,
Messages: openAIMessages,
Tools: openAITools,
Seed: params.Seed,
ToolChoice: params.ToolCallRequired(),
ResponseFormat: params.Format,
},
)
if err != nil {
return nil, err
}
if len(openAIResponse.Choices) == 0 {
return nil, errors.New("get text to text response: no choice")
}
responseMessage := openAIResponse.Choices[0].Message
if len(responseMessage.ToolCalls) == 0 {
return OpenaiToAgencyMessage(responseMessage), nil
}
openAIMessages = append(openAIMessages, responseMessage)
for _, call := range responseMessage.ToolCalls {
toolResponse, err := callTool(ctx, call, params.FuncDefs)
if err != nil {
return nil, fmt.Errorf("text to text call tool: %w", err)
}
if toolResponse.Role() != agency.ToolRole {
return toolResponse, nil
}
openAIMessages = append(openAIMessages, toolMessageToOpenAI(toolResponse, call.ID))
}
}
},
)
}
// === Helpers ===
func castFuncDefsToOpenAITools(funcDefs []FuncDef) []openai.Tool {
tools := make([]openai.Tool, 0, len(funcDefs))
for _, f := range funcDefs {
tool := openai.Tool{
Type: openai.ToolTypeFunction,
Function: &openai.FunctionDefinition{
Name: f.Name,
Description: f.Description,
},
}
if f.Parameters != nil {
tool.Function.Parameters = f.Parameters
}
tools = append(tools, tool)
}
return tools
}
func agencyToOpenaiMessages(cfg *agency.OperationConfig, msg agency.Message) ([]openai.ChatCompletionMessage, error) {
openAIMessages := make([]openai.ChatCompletionMessage, 0, len(cfg.Messages)+2)
openAIMessages = append(openAIMessages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleSystem,
Content: cfg.Prompt,
})
for _, cfgMsg := range cfg.Messages {
openAIMessages = append(openAIMessages, messageToOpenAI(cfgMsg))
}
openaiMsg := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
}
switch msg.Kind() {
case agency.TextKind:
openaiMsg.Content = string(msg.Content())
case agency.ImageKind:
openaiMsg.MultiContent = append(
openaiMsg.MultiContent,
openAIBase64ImageMessage(msg.Content()),
)
default:
return nil, fmt.Errorf("operator doesn't support %s kind", msg.Kind())
}
openAIMessages = append(openAIMessages, openaiMsg)
return openAIMessages, nil
}
func callTool(
ctx context.Context,
call openai.ToolCall,
defs FuncDefs,
) (agency.Message, error) {
funcToCall := defs.getFuncDefByName(call.Function.Name)
if funcToCall == nil {
return nil, errors.New("function not found")
}
funcResult, err := funcToCall.Body(ctx, []byte(call.Function.Arguments))
if err != nil {
return funcResult, fmt.Errorf("call function %s: %w", funcToCall.Name, err)
}
return funcResult, nil
}
func OpenaiToAgencyMessage(msg openai.ChatCompletionMessage) agency.Message {
return agency.NewTextMessage(
agency.Role(msg.Role),
msg.Content,
)
}
================================================
FILE: providers/openai/tools.go
================================================
package openai
import (
"context"
"github.com/neurocult/agency"
"github.com/sashabaranov/go-openai/jsonschema"
)
type ToolResultMessage struct {
agency.Message
ToolID string
ToolName string
}
// FuncDef represents a function definition that can be called during the conversation.
type FuncDef struct {
Name string
Description string
// Parameters is an optional structure that defines the schema of the parameters that the function accepts.
Parameters *jsonschema.Definition
// Body is the actual function that get's called.
// Parameters passed are bytes that can be unmarshalled to type that implements provided json schema.
// Returned result must be anything that can be marshalled, including primitive values.
Body func(ctx context.Context, params []byte) (agency.Message, error)
}
type FuncDefs []FuncDef
func (ds FuncDefs) getFuncDefByName(name string) *FuncDef {
for _, f := range ds {
if f.Name == name {
return &f
}
}
return nil
}
gitextract_oea_xf51/
├── .gitignore
├── LICENSE
├── README.md
├── agency.go
├── examples/
│ ├── README.md
│ ├── chat/
│ │ └── main.go
│ ├── cli/
│ │ └── main.go
│ ├── custom_operation/
│ │ └── main.go
│ ├── func_call/
│ │ └── main.go
│ ├── image_to_stream/
│ │ └── main.go
│ ├── image_to_text/
│ │ └── main.go
│ ├── logging/
│ │ └── main.go
│ ├── prompt_template/
│ │ └── main.go
│ ├── rag_vector_database/
│ │ ├── data.go
│ │ ├── docker-compose.yaml
│ │ └── main.go
│ ├── speech_to_text/
│ │ └── main.go
│ ├── speech_to_text_multi_model/
│ │ └── main.go
│ ├── speech_to_text_to_image/
│ │ └── main.go
│ ├── text_to_image_dalle2/
│ │ └── main.go
│ ├── text_to_speech/
│ │ └── main.go
│ ├── text_to_stream/
│ │ └── main.go
│ └── translate_text/
│ └── main.go
├── go.mod
├── go.sum
├── messages.go
├── process.go
└── providers/
└── openai/
├── helpers.go
├── helpers_test.go
├── image_to_text.go
├── provider.go
├── speech_to_text.go
├── text_to_embedding.go
├── text_to_image.go
├── text_to_speech.go
├── text_to_stream.go
├── text_to_text.go
└── tools.go
SYMBOL INDEX (96 symbols across 30 files)
FILE: agency.go
type Operation (line 9) | type Operation struct
method Config (line 24) | func (p *Operation) Config() *OperationConfig {
method Execute (line 37) | func (p *Operation) Execute(ctx context.Context, input Message) (Messa...
method SetPrompt (line 45) | func (p *Operation) SetPrompt(prompt string, args ...any) *Operation {
method SetMessages (line 50) | func (p *Operation) SetMessages(msgs []Message) *Operation {
type OperationHandler (line 16) | type OperationHandler
type OperationConfig (line 19) | type OperationConfig struct
function NewOperation (line 29) | func NewOperation(handler OperationHandler) *Operation {
FILE: examples/chat/main.go
function main (line 15) | func main() {
FILE: examples/cli/main.go
function main (line 17) | func main() {
FILE: examples/custom_operation/main.go
function main (line 11) | func main() {
function incrementFunc (line 33) | func incrementFunc(ctx context.Context, msg agency.Message, _ *agency.Op...
FILE: examples/func_call/main.go
function main (line 17) | func main() {
function printAnswer (line 100) | func printAnswer(message agency.Message) {
FILE: examples/image_to_stream/main.go
function main (line 14) | func main() {
FILE: examples/image_to_text/main.go
function main (line 14) | func main() {
FILE: examples/logging/main.go
function main (line 14) | func main() {
function Logger (line 34) | func Logger(input, output agency.Message, cfg *agency.OperationConfig) {
FILE: examples/prompt_template/main.go
function main (line 14) | func main() {
FILE: examples/rag_vector_database/main.go
function main (line 18) | func main() {
function RAGoperation (line 50) | func RAGoperation(client *weaviate.Client) *agency.Operation {
function prepareDB (line 87) | func prepareDB(openAPIKey string, ctx context.Context) (*weaviate.Client...
function saveToDisk (line 122) | func saveToDisk(msg agency.Message) error {
FILE: examples/speech_to_text/main.go
function main (line 17) | func main() {
FILE: examples/speech_to_text_multi_model/main.go
type Saver (line 16) | type Saver
method Save (line 18) | func (s *Saver) Save(input, output agency.Message, _ *agency.Operation...
function main (line 22) | func main() {
FILE: examples/speech_to_text_to_image/main.go
function main (line 17) | func main() {
function saveImgToDisk (line 41) | func saveImgToDisk(msg agency.Message) error {
FILE: examples/text_to_image_dalle2/main.go
function main (line 17) | func main() {
function saveToDisk (line 42) | func saveToDisk(msg agency.Message) error {
FILE: examples/text_to_speech/main.go
function main (line 13) | func main() {
function saveToDisk (line 39) | func saveToDisk(msg agency.Message) error {
FILE: examples/text_to_stream/main.go
function main (line 15) | func main() {
FILE: examples/translate_text/main.go
function main (line 15) | func main() {
FILE: messages.go
type Message (line 5) | type Message interface
type Kind (line 11) | type Kind
constant TextKind (line 14) | TextKind Kind = "text"
constant ImageKind (line 15) | ImageKind Kind = "image"
constant VoiceKind (line 16) | VoiceKind Kind = "voice"
constant EmbeddingKind (line 17) | EmbeddingKind Kind = "embedding"
type Role (line 20) | type Role
constant UserRole (line 23) | UserRole Role = "user"
constant SystemRole (line 24) | SystemRole Role = "system"
constant AssistantRole (line 25) | AssistantRole Role = "assistant"
constant ToolRole (line 26) | ToolRole Role = "tool"
type BaseMessage (line 29) | type BaseMessage struct
method Role (line 35) | func (bm BaseMessage) Role() Role {
method Kind (line 39) | func (bm BaseMessage) Kind() Kind {
method Content (line 42) | func (bm BaseMessage) Content() []byte {
function NewMessage (line 47) | func NewMessage(role Role, kind Kind, content []byte) BaseMessage {
function NewTextMessage (line 56) | func NewTextMessage(role Role, content string) BaseMessage {
function NewJsonMessage (line 65) | func NewJsonMessage(role Role, content any) (BaseMessage, error) {
FILE: process.go
type Process (line 8) | type Process struct
method Execute (line 25) | func (p *Process) Execute(ctx context.Context, input Message, intercep...
function NewProcess (line 13) | func NewProcess(operations ...*Operation) *Process {
type Interceptor (line 20) | type Interceptor
FILE: providers/openai/helpers.go
type Embedding (line 9) | type Embedding
function EmbeddingToBytes (line 11) | func EmbeddingToBytes(dimensions int, embeddings []Embedding) ([]byte, e...
function BytesToEmbedding (line 32) | func BytesToEmbedding(dimensions int, buf []byte) ([]Embedding, error) {
type NullableFloat32 (line 56) | type NullableFloat32
function Temperature (line 59) | func Temperature(v float32) NullableFloat32 {
function nullableToFloat32 (line 65) | func nullableToFloat32(v NullableFloat32) float32 {
FILE: providers/openai/helpers_test.go
function TestEmbeddingToBytes (line 8) | func TestEmbeddingToBytes(t *testing.T) {
FILE: providers/openai/image_to_text.go
type ImageToTextParams (line 13) | type ImageToTextParams struct
method ImageToText (line 23) | func (f *Provider) ImageToText(params ImageToTextParams) *agency.Operati...
function openAIBase64ImageMessage (line 69) | func openAIBase64ImageMessage(bb []byte) openai.ChatMessagePart {
FILE: providers/openai/provider.go
type Provider (line 8) | type Provider struct
type Params (line 14) | type Params struct
function New (line 20) | func New(params Params) *Provider {
FILE: providers/openai/speech_to_text.go
type SpeechToTextParams (line 11) | type SpeechToTextParams struct
method SpeechToText (line 17) | func (f Provider) SpeechToText(params SpeechToTextParams) *agency.Operat...
FILE: providers/openai/text_to_embedding.go
constant AdaEmbeddingV2 (line 14) | AdaEmbeddingV2 EmbeddingModel = openai.AdaEmbeddingV2
type TextToEmbeddingParams (line 16) | type TextToEmbeddingParams struct
type EmbeddingDimensions (line 21) | type EmbeddingDimensions
function NewDimensions (line 23) | func NewDimensions(v int) EmbeddingDimensions {
method TextToEmbedding (line 27) | func (p Provider) TextToEmbedding(params TextToEmbeddingParams) *agency....
FILE: providers/openai/text_to_image.go
type TextToImageParams (line 12) | type TextToImageParams struct
method TextToImage (line 20) | func (p Provider) TextToImage(params TextToImageParams) *agency.Operation {
FILE: providers/openai/text_to_speech.go
type TextToSpeechParams (line 11) | type TextToSpeechParams struct
method TextToSpeech (line 19) | func (f Provider) TextToSpeech(params TextToSpeechParams) *agency.Operat...
FILE: providers/openai/text_to_stream.go
type TextToStreamParams (line 13) | type TextToStreamParams struct
method TextToStream (line 18) | func (p Provider) TextToStream(params TextToStreamParams) *agency.Operat...
function messageToOpenAI (line 138) | func messageToOpenAI(message agency.Message) openai.ChatCompletionMessage {
function toolMessageToOpenAI (line 157) | func toolMessageToOpenAI(message agency.Message, toolID string) openai.C...
FILE: providers/openai/text_to_text.go
type TextToTextParams (line 14) | type TextToTextParams struct
method ToolCallRequired (line 24) | func (p TextToTextParams) ToolCallRequired() *string {
method TextToText (line 36) | func (p Provider) TextToText(params TextToTextParams) *agency.Operation {
function castFuncDefsToOpenAITools (line 94) | func castFuncDefsToOpenAITools(funcDefs []FuncDef) []openai.Tool {
function agencyToOpenaiMessages (line 112) | func agencyToOpenaiMessages(cfg *agency.OperationConfig, msg agency.Mess...
function callTool (line 145) | func callTool(
function OpenaiToAgencyMessage (line 163) | func OpenaiToAgencyMessage(msg openai.ChatCompletionMessage) agency.Mess...
FILE: providers/openai/tools.go
type ToolResultMessage (line 10) | type ToolResultMessage struct
type FuncDef (line 18) | type FuncDef struct
type FuncDefs (line 29) | type FuncDefs
method getFuncDefByName (line 31) | func (ds FuncDefs) getFuncDefByName(name string) *FuncDef {
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (81K chars).
[
{
"path": ".gitignore",
"chars": 600,
"preview": "# If you prefer the allow list template instead of the deny list, see community template:\n# https://github.com/github/gi"
},
{
"path": "LICENSE",
"chars": 1087,
"preview": "MIT License\n\nCopyright (c) Meta Platforms, Inc. and affiliates.\n\nPermission is hereby granted, free of charge, to any pe"
},
{
"path": "README.md",
"chars": 3671,
"preview": "# Agency: The Go Way to AI\n\nLibrary designed for developers eager to explore the potential of Large Language Models (LLM"
},
{
"path": "agency.go",
"chars": 1324,
"preview": "package agency\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\n// Operation is basic building block.\ntype Operation struct {\n\thandler Ope"
},
{
"path": "examples/README.md",
"chars": 95,
"preview": "To run an example:\n\n```shell\nexport OPENAI_API_KEY=\"<your key here>\"\ngo run ./example_name\n```\n"
},
{
"path": "examples/chat/main.go",
"chars": 847,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\n\t\"github.com/neurocult/"
},
{
"path": "examples/cli/main.go",
"chars": 1273,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\n\t\"github.com/neurocult/a"
},
{
"path": "examples/custom_operation/main.go",
"chars": 739,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/neurocult/agency\"\n)\n\nfunc main() {\n\tincrement := agenc"
},
{
"path": "examples/func_call/main.go",
"chars": 2695,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\tgo_openai \"gith"
},
{
"path": "examples/image_to_stream/main.go",
"chars": 777,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\t\"github.com/neurocult/agency\"\n\n\t"
},
{
"path": "examples/image_to_text/main.go",
"chars": 714,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\t\"github.com/neurocult/agency\"\n\to"
},
{
"path": "examples/logging/main.go",
"chars": 919,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\n\t\"github.com/neurocult/agency\"\n\t"
},
{
"path": "examples/prompt_template/main.go",
"chars": 653,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\n\t\"github.com/neurocult/agency\"\n\t"
},
{
"path": "examples/rag_vector_database/data.go",
"chars": 13775,
"preview": "package main\n\nimport \"github.com/weaviate/weaviate/entities/models\"\n\n// first 5 objects contain something about programm"
},
{
"path": "examples/rag_vector_database/docker-compose.yaml",
"chars": 715,
"preview": "---\nversion: \"3.4\"\nservices:\n weaviate:\n command:\n - --host\n - 0.0.0.0\n - --port\n - \"8080\"\n "
},
{
"path": "examples/rag_vector_database/main.go",
"chars": 3149,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\t\"github.com/neurocult/"
},
{
"path": "examples/speech_to_text/main.go",
"chars": 800,
"preview": "// To make this example work make sure you have speech.ogg file in the root of directory.\n// You can use text to speech "
},
{
"path": "examples/speech_to_text_multi_model/main.go",
"chars": 1425,
"preview": "// To make this example work make sure you have speech.ogg file in the root of directory\npackage main\n\nimport (\n\t\"contex"
},
{
"path": "examples/speech_to_text_to_image/main.go",
"chars": 1253,
"preview": "// To make this example work make sure you have speech.ogg file in the root of directory\npackage main\n\nimport (\n\t\"bytes\""
},
{
"path": "examples/text_to_image_dalle2/main.go",
"chars": 1154,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"image\"\n\t\"image/png\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\n\t"
},
{
"path": "examples/text_to_speech/main.go",
"chars": 1005,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\n\t\"github.com/neurocult/agency\"\n\t\"github"
},
{
"path": "examples/text_to_stream/main.go",
"chars": 984,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\tgoopenai \"github.com/sashabarano"
},
{
"path": "examples/translate_text/main.go",
"chars": 693,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n\tgoopenai \"github.com/sashabarano"
},
{
"path": "go.mod",
"chars": 1493,
"preview": "module github.com/neurocult/agency\n\ngo 1.21.0\n\nrequire (\n\tgithub.com/sashabaranov/go-openai v1.36.1\n\tgithub.com/weaviate"
},
{
"path": "go.sum",
"chars": 6647,
"preview": "github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=\ngit"
},
{
"path": "messages.go",
"chars": 1456,
"preview": "package agency\n\nimport \"encoding/json\"\n\ntype Message interface {\n\tRole() Role\n\tContent() []byte\n\tKind() Kind\n}\n\ntype Kin"
},
{
"path": "process.go",
"chars": 1338,
"preview": "package agency\n\nimport (\n\t\"context\"\n)\n\n// Process is a chain of operations that can be executed in sequence.\ntype Proces"
},
{
"path": "providers/openai/helpers.go",
"chars": 2049,
"preview": "package openai\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math\"\n)\n\ntype Embedding []float32\n\nfunc EmbeddingToBytes(dimensions"
},
{
"path": "providers/openai/helpers_test.go",
"chars": 876,
"preview": "package openai\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestEmbeddingToBytes(t *testing.T) {\n\tfloats := []Embedding{{1.1,"
},
{
"path": "providers/openai/image_to_text.go",
"chars": 2324,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/neurocult/agency\"\n\t\"github.com/sas"
},
{
"path": "providers/openai/provider.go",
"chars": 685,
"preview": "package openai\n\nimport (\n\t\"github.com/sashabaranov/go-openai\"\n)\n\n// Provider is a set of operation builders.\ntype Provid"
},
{
"path": "providers/openai/speech_to_text.go",
"chars": 927,
"preview": "package openai\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\n\t\"github.com/neurocult/agency\"\n\t\"github.com/sashabaranov/go-openai\"\n)\n\ntyp"
},
{
"path": "providers/openai/text_to_embedding.go",
"chars": 1598,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\n\t\"github.com/neurocult/agency\"\n)\n\ntype"
},
{
"path": "providers/openai/text_to_image.go",
"chars": 1276,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\n\t\"github.com/neurocult/agency\"\n\t\"github.com/sashabaranov/"
},
{
"path": "providers/openai/text_to_speech.go",
"chars": 1091,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/neurocult/agency\"\n\t\"github.com/sashabaranov/go-openai\"\n)\n\ntype T"
},
{
"path": "providers/openai/text_to_stream.go",
"chars": 4317,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/neurocult/agency\"\n\t\"github.com/sashabaranov/go-"
},
{
"path": "providers/openai/text_to_text.go",
"chars": 4475,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\n\t\"github.com/neurocult/agenc"
},
{
"path": "providers/openai/tools.go",
"chars": 982,
"preview": "package openai\n\nimport (\n\t\"context\"\n\n\t\"github.com/neurocult/agency\"\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\nt"
}
]
About this extraction
This page contains the full source code of the neurocult/agency GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (70.2 KB), approximately 21.7k tokens, and a symbol index with 96 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.