brunch

package module
v0.0.0-...-7487a0d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 29, 2025 License: Apache-2.0 Imports: 13 Imported by: 0

README

brunch

Install

Requires:

  • ANTHROPIC_API_KEY environment variable set
make
./brucli

Example Usage

Then, we can start submitting statements to do things like "make a new chat session," and "derive alternative provider configurations."

Here is an overview of the initial statement options that help define chats and providers:

1. `\new-provider "name"`
   - Creates a new provider
   - Required properties:
     - `:host` (string)
     - `:base-url` (string)
     - `:max-tokens` (integer)
     - `:temperature` (real/float)
     - `:system-prompt` (string)

2. `\new-chat "name"`
   - Creates a new chat
   - Required properties:
     - `:provider` (string)

3. `\chat "name"`
   - Interacts with an existing chat
   - Optional properties:
     - `:hash` (string) [loads a specified hash, defaults to latest]

4. `\new-ctx "name"`
   - Creates a new context for knowledge/data access
   - Optional properties (at least one required):
     - `:dir` (string) [directory path for file access]
     - `:database` (string) [database connection string]
     - `:web` (string) [web endpoint]

Example of the creating a chat, and using the chat REPL:

 ~/workspace/brunch/ [main] ./brucli
time=2025-01-26T12:26:35.102-05:00 level=INFO msg="installing core" dir=/tmp/brunch
>\new-chat "mychat" :provider "anthropic"
>\chat "mychat"


                        W E L C O M E

                Send a message to the assistant by
                typing the message and pressing "enter"
                twice.

                To see a list of commands type '\?'
                To quit, type '\q'

Chat started. Press Ctrl+C to exit and view conversation tree.
Enter your messages (press Enter twice to send):

Now the chat is loaded, so lets see what the chat REPL commands are:

[9809d4c7]>  \?
Commands:
        \l: List chat history [current branch of chat]
        \t: List chat tree [all branches]
        \i: Queue image [import image file into chat for inquiry]
        \s: Save snapshot [save a snapshot of the current tree to disk]
        \p: Go to parent [traverse up the tree]
        \c: Go to child [traverse down the tree to the nth child]
        \r: Go to root [traverse to the root of the tree]
        \g: Go to node [traverse to a specific node by hash]
        \.: List children [list all children of the current node]
        \x: Toggle chat [toggle chat mode on/off - chat on by default press enter twice to send with no command leading]
        \a: List artifacts [display artifacts from current node] or [write artifacts to disk if followed by a directory path]
        \q: Quit [save and quit]
        \new-k: Attach new knowledge-context [attach a non-existing knowledge-context to the chat]
        \attach-k: Attach existing knowledge-context [attach an existing knowledge-context to the chat]

Image analysis:

[9809d4c7]>  \i
Enter image path:
assets/test_image.jpg

[9809d4c7]>  can you describe this image please ?

assistant>  This image appears to be a stylized, geometric portrait illustration of a man with distinct facial features. The man is portrayed with angular lines and shapes that create a cubist or faceted effect, reminiscent of pop art styles.

The illustration uses bold, contrasting colors like orange, yellow, green, and shades of blue. The man's face is composed of geometric shapes and planes that give it a three-dimensional, almost sculptural quality.

His eyes are highlighted with bright blue shapes, making them stand out prominently. The overall style suggests a modern, graphic design approach, potentially used for a magazine cover, poster, or some kind of promotional material.

While I cannot identify the specific individual depicted, the striking visual style and use of vibrant colors create an eye-catching and memorable portrait illustration.

[06113e43]>

Now, lets add another message and see the "tree":

[06113e43]>  Thanks, that was spot-on. Tell me some cool facts about programming, but keep it brief

assistant>  Sure, here are some cool brief facts about programming:

1. The first computer programmer was a woman named Ada Lovelace, who wrote the first algorithm for a computing machine in the 1840s.

2. The first computer bug was a real bug - a moth stuck in the Harvard Mark II computer in 1947, which led to the term "debugging."

3. The programming language Python was named after the British comedy group Monty Python, not the snake.

4. The "Hello, World!" program is a traditional way to introduce new programmers to a programming language and is often the first program written.

5. The most popular programming language in the world is JavaScript, which is used to add interactivity and dynamic effects to websites.

6. The first computer game, "Spacewar!," was created in 1962 by a group of programmers at MIT.

7. The term "hacker" originally referred to someone who was a skilled programmer and enjoyed exploring the details of programming systems.

[27377479]>  \t
[ROOT] Provider: mychat, Model: claude-3-sonnet-20240229
├── Temperature: 0.70
├── MaxTokens: 4000
└── Hash: 9809d4c779c7a9aef9cfa2f287219d42829beee5b62c26ad5ef8e3def83e1f88
    └── [MESSAGE_PAIR] Time: 2025-01-26 12:31:35
        ├── User (user): can you describe this ima...
        ├── User Images: assets/test_image.jpg
        ├── Assistant (assistant): This image appears to be ...
        └── Hash: 06113e434abff4380d3c4761033b2b2ecb09b08558fc9a1dfaa2f33c224a7db1
        └── [MESSAGE_PAIR] Time: 2025-01-26 12:32:41
            ├── User (user): Thanks, that was spot-on....
            ├── Assistant (assistant): Sure, here are some cool ...
            └── Hash: 273774790c3a95e035305ed707f992e44a6253412411ef778aeb7b41f29db0b6


[27377479]>

Hmm, that was a dumb question and I don't want to start all over! The context is now polluted GAHH DARN.. oh waitt

[06113e43]>  \g 9809d4c779c7a9aef9cfa2f287219d42829beee5b62c26ad5ef8e3def83e1f88

[9809d4c7]>  \t
[ROOT] Provider: mychat, Model: claude-3-sonnet-20240229
├── Temperature: 0.70
├── MaxTokens: 4000
└── Hash: 9809d4c779c7a9aef9cfa2f287219d42829beee5b62c26ad5ef8e3def83e1f88
    └── [MESSAGE_PAIR] Time: 2025-01-26 12:31:35
        ├── User (user): can you describe this ima...
        ├── User Images: assets/test_image.jpg
        ├── Assistant (assistant): This image appears to be ...
        └── Hash: 06113e434abff4380d3c4761033b2b2ecb09b08558fc9a1dfaa2f33c224a7db1
        └── [MESSAGE_PAIR] Time: 2025-01-26 12:32:41
            ├── User (user): Thanks, that was spot-on....
            ├── Assistant (assistant): Sure, here are some cool ...
            └── Hash: 273774790c3a95e035305ed707f992e44a6253412411ef778aeb7b41f29db0b6


[9809d4c7]>  \l

[9809d4c7]>  Howdy partner! lets try some more!

assistant>  Howdy! Im ready to continue our conversation. Feel free to ask me anything or share your thoughts.

[dbe4cb25]>  \t
[ROOT] Provider: mychat, Model: claude-3-sonnet-20240229
├── Temperature: 0.70
├── MaxTokens: 4000
└── Hash: 9809d4c779c7a9aef9cfa2f287219d42829beee5b62c26ad5ef8e3def83e1f88
    │├── [MESSAGE_PAIR] Time: 2025-01-26 12:31:35
    │    ├── User (user): can you describe this ima...
    │    ├── User Images: assets/test_image.jpg
    │    ├── Assistant (assistant): This image appears to be ...
    │    └── Hash: 06113e434abff4380d3c4761033b2b2ecb09b08558fc9a1dfaa2f33c224a7db1
    │    └── [MESSAGE_PAIR] Time: 2025-01-26 12:32:41
    │        ├── User (user): Thanks, that was spot-on....
    │        ├── Assistant (assistant): Sure, here are some cool ...
    │        └── Hash: 273774790c3a95e035305ed707f992e44a6253412411ef778aeb7b41f29db0b6
    └── [MESSAGE_PAIR] Time: 2025-01-26 12:34:37
        ├── User (user): Howdy partner! lets try s...
        ├── Assistant (assistant): Howdy! Im ready to conti...
        └── Hash: dbe4cb25ca1bd5b9a30e8cb1e0ae1af9ed9745c604246f44d796d8ff429a1cd2

Nice, now we have both and we can hop betweeen these two contrived examples!

Producing Artifacts

Here is an example of retrieving artifacts from a node:

[9809d4c7]>  please generate me some Haskell code!

... omitted for bevity ...


[988f36fc]>  \a
Artifacts in current node:
        0: Text: Sure, here's some Haskell code that defines a simp...
        1: File [haskell] Name: <unnamed artifact>
           Preview: -- Define a function to find the sum of a list of ...
        2: Text: Here's how the code works:

1. The function `sumLi...

[988f36fc]>

Now lets see what we can get:

[988f36fc]>  \a /tmp/go-is-better

[988f36fc]>  \q
saving back to loaded snapshot
>\q
 ~/workspace/brunch/ [main*] tree /tmp/go-is-better
/tmp/go-is-better
├── 32b260ec.artifact
├── 91e68199.artifact
└── file_113.artifact.haskell

1 directory, 3 files
 ~/workspace/brunch/ [main*]

 ~/workspace/brunch/ [main*] cat /tmp/go-is-better/file_113.artifact.haskell
-- Define a function to find the sum of a list of integers
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs

-- Example usage
main = do
  print (sumList [1, 2, 3, 4, 5]) -- Output: 15
  print (sumList [])              -- Output: 0

Documentation

Index

Constants

View Source
const (
	TokenTypeNewProviderCmd tokenType = iota
	TokenTypeNewChatCmd
	TokenTypeChatCmd
	TokenTypeNewContextCmd
	TokenTypePropertyTag
	TokenTypeString
	TokenTypeInteger
	TokenTypeReal
	TokenTypeDelChatCmd
	TokenTypeDelContextCmd
	TokenTypeListContextCmd
	TokenTypeListChatCmd
	TokenTypeDescribeContextCmd
	TokenTypeDescribeChatCmd
	TokenTypeListProviderCmd
	TokenTypeDelProviderCmd
)
View Source
const (
	PropertyTypeString propertyType = iota
	PropertyTypeInteger
	PropertyTypeReal
)

Variables

This section is empty.

Functions

func MapTree

func MapTree(node Node) map[string]Node

todo: make this not so bad

func PrettyPrint

func PrettyPrint(node Node, indent string, isLastChild bool) string

func PrintTree

func PrintTree(node Node) string

Types

type Artifact

type Artifact interface {
	Type() ArtifactType
	Write(dir string, name string) error
}

An artifact is some data that was generated by the provider and encoded as some understandable format. Whenever "code blocks" are present in the provider's response we encode them as artifacts and save them to disk. We also save non-code block artifacts to ensure that every individual piece of information within the message is saved for finer grain contextual analysis

func ParseArtifactsFrom

func ParseArtifactsFrom(msg *MessageData) ([]Artifact, error)

ParseArtifactsFrom takes a message and parses it for artifacts This is done by looking for code blocks in the message and then parsing them into artifacts if any exist

type ArtifactType

type ArtifactType int
const (
	ArtifactTypeFile ArtifactType = iota
	ArtifactTypeNonFile
)

type ContextSettings

type ContextSettings struct {
	Name  string      `json:"name"`
	Type  ContextType `json:"type"`
	Value string      `json:"value"`
}

type ContextType

type ContextType string

A context type is a type of knowledge that can be attached to a conversation This could be a directory, a database, a web page, etc. HOW the knowledge is incorperated into the conversation is up to the provider and if the provider doesn't support knowledge contexts, this should return an error

const (
	ContextTypeDirectory ContextType = "directory"
	ContextTypeDatabase  ContextType = "database"
	ContextTypeWeb       ContextType = "web"
)

type Conversation

type Conversation interface {

	// Print the entire tree of the conversation, which includes all branches
	PrintTree() string

	// Print the history of the conversation, on the current branch back to the root
	PrintHistory() string

	// Queue images to be sent to the provider
	QueueImages(paths []string) error

	// Snapshot the current state of the conversation
	Snapshot() (*Snapshot, error)

	// Get the artifacts from the current node (not the entire conversation)
	Artifacts() []Artifact

	// Attach a context to the conversation that the chat provider _may_ use
	// within the conversation that is ongoing
	CreateContext(ctx *ContextSettings) error

	// Attach an existing context to the conversation
	AttachContext(ctxName string) error

	// Goto a specific node in the conversation via hash (use PrintTree of History to see hashes)
	Goto(nodeHash string) error

	// Navigate to the parent node of the current node
	Parent() error

	// Navigate to the nth child of the current node
	Child(idx int) error

	// Navigate to the root node of the conversation
	Root() error

	// List the children of the current node
	ListChildren() []string

	// Check if the current node has a parent
	HasParent() bool

	// Toggle the chat on or off (soft disable)
	ToggleChat(enabled bool)

	// Get info about the current state of the chat
	Info() string

	// Get the current node of the conversation
	CurrentNode() Node

	// Submit a message to the chat provider
	SubmitMessage(message string) (string, error)

	// List the knowledge contexts that are attached to the conversation
	ListKnowledgeContexts() []string
}

The panel is an interface for the user of brunch to interact with our chat instance in a way that is easy to understand and use

type Core

type Core struct {
	// contains filtered or unexported fields
}

The brunch core handles the installes of and managment of chats and their related llm provider info. The core is what executes the statements and is used to load/store branchable chats

func NewCore

func NewCore(opts CoreOpts) *Core

Create a new core instance with a set of providers that can be selected from. We are attempting to be entirely removed from the actual "chat" that the external user is doing, and instead we are just providing a way to manage instances of them, and add composability to the system through branching and traversal of a session forest

func (*Core) AddProvider

func (c *Core) AddProvider(name string, p Provider) error

Here we clone the provider handed to us and store in the provider map under a new name given to us by the user so they can reference that particular incarnation of the provider in their chat sessions (host: is the base provider like "anthropic" or "openai" etc whatever is setup by hand from config oin core init)

func (*Core) AddToChatStore

func (c *Core) AddToChatStore(filename string, content string) error

func (*Core) AddToContextStore

func (c *Core) AddToContextStore(filename string, content string) error

func (*Core) AddToDataStore

func (c *Core) AddToDataStore(filename string, content string) error

func (*Core) EndSession

func (c *Core) EndSession(sessionId string) error

func (*Core) ExecuteStatement

func (c *Core) ExecuteStatement(sessionId string, stmt *Statement) error

func (*Core) GetActiveChat

func (c *Core) GetActiveChat(name string) (*chatInstance, error)

func (*Core) Install

func (c *Core) Install() error

Sets up the core into the given install directory. It can be called multiple times and it wont overwrite the existing data store or chat store. It just makes sure that the directories exist that we rely on

func (*Core) IsInstalled

func (c *Core) IsInstalled() bool

func (*Core) ListContexts

func (c *Core) ListContexts() []string

func (*Core) LoadContexts

func (c *Core) LoadContexts() error

func (*Core) LoadFromChatStore

func (c *Core) LoadFromChatStore(filename string) (string, error)

func (*Core) LoadFromContextStore

func (c *Core) LoadFromContextStore(filename string) (string, error)

func (*Core) LoadFromDataStore

func (c *Core) LoadFromDataStore(filename string) (string, error)

func (*Core) LoadProviders

func (c *Core) LoadProviders() error

Load all available providers from the provider store directory

func (*Core) NewChat

func (c *Core) NewChat(name string, providerName string) error

This creates a chat instance, but it does not load it. It defines it so that the user can load it later (think of it like making a db table)

func (*Core) SaveActiveChat

func (c *Core) SaveActiveChat(sessionName string) error

func (*Core) SessionList

func (c *Core) SessionList() []string

Retrive a list of all sessions This locks the session map and returns a list of all session ids, so best not call this in a hot spot

func (*Core) SetAvailableProviders

func (c *Core) SetAvailableProviders(providers map[string]Provider)

type CoreChatStartHandler

type CoreChatStartHandler func(req Conversation) error

type CoreDescription

type CoreDescription struct {
	Chats     []string
	Providers []string
	Contexts  []string
}

type CoreInfo

type CoreInfo struct {
	Chats     []string
	Providers []string
	Contexts  []string
}

type CoreOpts

type CoreOpts struct {
	InstallDirectory string
	BaseProviders    map[string]Provider
	ChatStartHandler CoreChatStartHandler
	InfoHandler      InformationCallback
}

type FileArtifact

type FileArtifact struct {
	Id       string
	Data     string
	Name     string
	FileType *string
}

A FileArtifact is a code block that was encoded as a file If a code block is returned without the standard "```language:name.ext ... ```" format then we use a hash of the contents to name the file

func (*FileArtifact) Type

func (a *FileArtifact) Type() ArtifactType

func (*FileArtifact) Write

func (a *FileArtifact) Write(dir string, name string) error

type InformationCallback

type InformationCallback struct {
	OnListChats       func(chats []string)
	OnListProviders   func(providers []string)
	OnListContexts    func(contexts []string)
	OnDescribeContext func(data string)
	OnDescribeChat    func(data string)
}

Informational callbacks are given to the core so that the user of the core can institute the display of information requested from a query regardless if the implementation is one of a CLI app, server, etc. This way the "backend" doesn't make any assumptions about how the hell the information is supposed to get out

type MessageCreator

type MessageCreator func(userMessage string) (*MessagePairNode, error)

Provider must create a function that the user can call to create a new message pair node

type MessageData

type MessageData struct {
	Role              string   `json:"role"`
	B64EncodedContent string   `json:"-"`
	RawContent        string   `json:"content"`
	Images            []string `json:"images,omitempty"`
}

func NewMessageData

func NewMessageData(role string, unencodedContent string) *MessageData

NewMessageData creates a new message data object and ensures that the content is base64 encoded as when we save things we don't want messages to bonk our json, and it helps keep the data clean

func (*MessageData) MarshalJSON

func (m *MessageData) MarshalJSON() ([]byte, error)

func (*MessageData) UnencodedContent

func (m *MessageData) UnencodedContent() string

UnencodedContent returns the raw content of the message if the message is not base64 encoded, it will return the base64 encoded content

func (*MessageData) UnmarshalJSON

func (m *MessageData) UnmarshalJSON(data []byte) error

type MessagePairNode

type MessagePairNode struct {
	Assistant *MessageData `json:"assistant"`
	User      *MessageData `json:"user"`
	Time      time.Time    `json:"time"`
	// contains filtered or unexported fields
}

func NewMessagePairNode

func NewMessagePairNode(parent Node) *MessagePairNode

func (*MessagePairNode) AddChild

func (n *MessagePairNode) AddChild(child Node)

func (*MessagePairNode) Hash

func (m *MessagePairNode) Hash() string

func (*MessagePairNode) History

func (m *MessagePairNode) History() []string

func (*MessagePairNode) ToMap

func (n *MessagePairNode) ToMap() map[string]Node

func (*MessagePairNode) ToString

func (m *MessagePairNode) ToString() string

func (*MessagePairNode) Type

func (m *MessagePairNode) Type() NodeTyppe

type Node

type Node interface {
	Type() NodeTyppe
	Hash() string
	ToString() string
	History() []string
	ToMap() map[string]Node
}

A Node is either a root, or a PAIR of messages (user and provider) We seperate into pairs of messages to constrain the conversation as a request->generation || failure

type NodeTyppe

type NodeTyppe string
const (
	NT_ROOT         NodeTyppe = "root"
	NT_MESSAGE_PAIR NodeTyppe = "message_pair"
)

type NonFileArtifact

type NonFileArtifact struct {
	Data string
}

A NonFileArtifact is a piece of information that was not encoded as a file but rather as a string. This could be a table, a list, a markdown block, etc. These are always stored as a hash of the contents (name)

func (*NonFileArtifact) Type

func (a *NonFileArtifact) Type() ArtifactType

func (*NonFileArtifact) Write

func (a *NonFileArtifact) Write(dir string, name string) error

type OperationalCallback

type OperationalCallback struct {
	OnLoadChat       func(name string, hash *string) error
	OnNewChat        func(name string, provider string) error
	OnNewProvider    func(name string, host string, baseUrl string, maxTokens int, temperature float64, systemPrompt string) error
	OnNewContext     func(name string, dir *string, database *string, web *string) error
	OnDeleteChat     func(name string) error
	OnDeleteContext  func(name string) error
	OnDeleteProvider func(name string) error

	// These operational callbacks may be user to get information and forward to the InformationCallback,
	// BUT not NECESARILY. The InformationCallback is offered as a means to pipe informational data to a user
	// regardless of their connection to the server. However its not mandatory for the implementation to do so
	OnListChats       func() error
	OnListProviders   func() error
	OnListContexts    func() error
	OnDescribeContext func(name string) error
	OnDescribeChat    func(name string) error
}

An operational callback is used when a session with a user (pre-chat interface) is in process. When they submit a commmand via the core, it will use these callbacks to receive instructions based on the command when `execucte` is called (below)

type Provider

type Provider interface {

	// NewConversationRoot creates a new root node for a new conversation
	// from which all messages will be derived
	NewConversationRoot() RootNode

	// ExtendFrom takes a node and returns a function that can be used to create a new message pair node
	// This means that this is the function we call in order to get a function to send a message,
	// and then receive a response
	ExtendFrom(Node) MessageCreator

	// GetRoot takes a node and returns the root node
	GetRoot(Node) RootNode

	// GetHistory takes a node and returns the history of the conversation
	GetHistory(Node) []map[string]string

	// QueueImages takes a list of image urls and queues them for attachment to the next message
	// In this way we can ask about images in the conversation and do analysis on them
	// If the provider doesn't support images, this should return an error
	QueueImages([]string) error

	// Settings returns the settings for the provider
	Settings() ProviderSettings

	// CloneWithSettings returns a new provider with the given settings
	// This is so we can derive providers from existing providers at runtime
	// and have them be available to the user
	CloneWithSettings(ProviderSettings) Provider

	// AttachKnowledgeContext attaches a knowledge context to the provider
	// A knowledge context could be a directory, a database, a web page, etc.
	// HOW the knowledge is incorperated into the conversation is up to the provider
	// and if the provider doesn't support knowledge contexts, this should return an error
	AttachKnowledgeContext(ContextSettings) error
}

A provider is an abstraction of some (presumably LLM) message generation service though it could be anything that generates messages i guess

type ProviderSettings

type ProviderSettings struct {
	Name         string  `json:"name"`
	Host         string  `json:"host"`
	BaseUrl      string  `json:"base_url"`
	MaxTokens    int     `json:"max_tokens"`
	Temperature  float64 `json:"temperature"`
	SystemPrompt string  `json:"system_prompt"`
}

type RootNode

type RootNode struct {
	Provider    string  `json:"provider"`
	Model       string  `json:"model"`
	Prompt      string  `json:"prompt"`
	Temperature float64 `json:"temperature"`
	MaxTokens   int     `json:"max_tokens"`
	// contains filtered or unexported fields
}

func NewRootNode

func NewRootNode(opts RootOpt) *RootNode

func (*RootNode) AddChild

func (n *RootNode) AddChild(child Node)

func (*RootNode) Hash

func (r *RootNode) Hash() string

func (*RootNode) History

func (m *RootNode) History() []string

func (*RootNode) ToMap

func (n *RootNode) ToMap() map[string]Node

func (*RootNode) ToString

func (m *RootNode) ToString() string

func (*RootNode) Type

func (r *RootNode) Type() NodeTyppe

type RootOpt

type RootOpt struct {
	Provider    string
	Model       string
	Prompt      string
	Temperature float64
	MaxTokens   int
}

type Snapshot

type Snapshot struct {
	ProviderName string   `json:"provider_name"`
	ActiveBranch string   `json:"active_branch"`
	Contents     []byte   `json:"contents"`
	Contexts     []string `json:"contexts"`
}

The snapshot is a hollistic snapshot of the current state of the chat It includes the provider name, the active branch, the contents of the conversation, and the contexts that are attached to the conversation. If a chat is saved with contexts then then all of the contextual configuration information MUST be available at the time of load. Snapshots save references to internal brunch resources on disk so they must be persisent and available at the time of load.

func SnapshotFromJSON

func SnapshotFromJSON(data []byte) (*Snapshot, error)

func (*Snapshot) Marshal

func (s *Snapshot) Marshal() ([]byte, error)

type Statement

type Statement struct {
	// contains filtered or unexported fields
}

func NewStatement

func NewStatement(content string) *Statement

func (*Statement) IsPrepared

func (p *Statement) IsPrepared() bool

func (*Statement) Prepare

func (p *Statement) Prepare() error

func (*Statement) Reset

func (p *Statement) Reset()

Directories

Path Synopsis
cmd
brucli command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL