aichat

package module
v1.3.3 Latest Latest
Warning

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

Go to latest
Published: Apr 1, 2025 License: MIT Imports: 7 Imported by: 0

README

AI Chat Manager

Go Report Card codecov Go Go Reference

Simple Go package for managing AI chat sessions across LLM Providers with options for message history, tool calling, and S3-compatible session storage. Works with OpenRouter, OpenAI, Google GenAI, DeepSeek, and many others with a Chat Completion API.

The toolcalling example uses OpenRouter API with GPT-4o and OpenAI Function schema. The tool converter in the googlegenai subpackage provides support for Google GenAI SDK. Define tools once in YAML or JSON and reuse them across sessions, providers, and SDKs.

Features

  • Chat session management with message history and timestamps
  • Support for multiple message types (system, user, assistant, tool)
  • Comprehensive message operations:
    • Add, remove, pop, shift, and unshift messages
    • Query by role and content type
    • Message counting and iteration
    • Idempotent message handling
  • Tool/Function calling system:
    • Structured tool calls with ID tracking
    • JSON argument parsing
    • Pending tool call management
    • Tool response handling
  • S3-compatible storage backend:
    • Load/Save/Delete operations
    • Context-aware storage operations
    • Support for any S3-compatible service
  • Rich content support:
    • Text content
    • Structured content (JSON)
    • Multi-part content handling
  • Session metadata:
    • Unique session IDs
    • Creation and update timestamps
    • Custom metadata storage
  • JSON serialization with custom marshaling

Usage

The Chat, Message, and ToolCall structs are designed to be transparent - applications are welcome to access their members directly. For example, you can directly access chat.Messages, chat.Meta, or message.Role.

For convenience, the package provides several helper methods:

Chat Methods
  • AddMessage(msg *Message): Add a Message to the chat
  • AddMessageOnce(msg *Message): Add a Message to the chat (idempotent)
  • AddRoleContent(role string, content any) *Message: Add a message with any role and content, returns the created message
  • AddUserContent(content any) *Message: Add a user message, returns the created message
  • AddAssistantContent(content any) *Message: Add an assistant message, returns the created message
  • AddToolRawContent(name string, toolCallID string, content any) *Message: Add a tool message with raw content, returns the created message
  • AddToolContent(name string, toolCallID string, content any) error: Add a tool message with JSON-encoded content if needed, returns error if JSON marshaling fails
  • AddAssistantToolCall(toolCalls []ToolCall) *Message: Add an assistant message with tool calls, returns the created message
  • ClearMessages(): Remove all messages from the chat
  • LastMessage() *Message: Get the most recent message
  • LastMessageRole() string: Get the role of the most recent message
  • LastMessageByRole(role string) *Message: Get the last message with a specific role
  • LastMessageByType(contentType string) *Message: Get the last message with a specific content type
  • MessageCount() int: Get the total number of messages in the chat
  • MessageCountByRole(role string) int: Get the count of messages with a specific role
  • PopMessage() *Message: Remove and return the last message from the chat
  • PopMessageIfRole(role string) *Message: Remove and return the last message if it matches the specified role
  • Range(fn func(msg *Message) error) error: Iterate through messages with a callback function
  • RangeByRole(role string, fn func(msg *Message) error) error: Iterate through messages with a specific role
  • RemoveLastMessage() *Message: Remove and return the last message from the chat (alias for PopMessage)
  • SetSystemContent(content any) *Message: Set or update the system message content at the beginning of the chat, returns the system message
  • SetSystemMessage(msg *Message) *Message: Set or update the system message at the beginning of the chat, returns the system message
  • ShiftMessages() *Message: Remove and return the first message from the chat
  • UnshiftMessages(msg *Message): Insert a message at the beginning of the chat
Message Methods
  • Meta() *Meta: Get a Meta struct for working with message metadata
  • ContentString() string: Get the content as a string if it's a simple string
  • ContentParts() ([]*Part, error): Get the content as a slice of Part structs if it's a multipart message
Meta Methods
  • Set(key string, value any): Set a metadata value on a Message
  • Get(key string) any: Retrieve a metadata value from a Message
  • Keys() []string: Get all metadata keys for a Message
Function Methods
  • ArgumentsMap() map[string]any: Parse and return a map from a Function's Arguments JSON
Creating a New Chat
// Create new chat in-memory
chat := new(aichat.Chat)

// Or use persistent/S3-compatible storage wrapper
opts := aichat.Options{...}
storage := aichat.NewChatStorage(opts)
chat, err := storage.Load("chat-f00ba0ba0")
Working with Messages

The []*Message structure can be used to manage messages in a chat session in multiple ways:

// Add a message directly (idempotent)
chat.AddMessage(&aichat.Message{
    Role: "user",
    Content: "Hello!",
})

// Add user content (creates new message)
chat.AddUserContent("Hello!")

// Add assistant content (creates new message)
chat.AddAssistantContent("Hi there!")

// Set or update the system message
chat.SetSystemContent("Welcome to the chat!")

// Remove the last message if it's from the assistant
if msg := chat.PopMessageIfRole("assistant"); msg != nil {
    fmt.Println("Removed assistant's last message:", msg.Content)
}

// Get the last message
if last := chat.LastMessage(); last != nil {
    fmt.Println("Last message was from:", last.Role) // "assistant"
}

// Example of direct member access
fmt.Println(chat.ID, chat.LastUpdated)
for _, msg := range chat.Messages {
    fmt.Println(msg.Role, msg.Content)
}

// Add tool/function calls
toolCalls := []aichat.ToolCall{{
    ID:   "call-123",
    Type: "function",
        Function: aichat.Function{
        Name:      "get_weather",
        Arguments: `{"location": "Boston"}`,
        },
    },
}
chat.AddAssistantToolCall(toolCalls)
Working with Strings and Multi-Part Content

The Message struct provides a ContentString() method that returns the content as a string if it is a simple string. The ContentParts() method returns the content as a slice of Part structs if it is a multipart message.

// Handle text content
textMsg := message.ContentString()
fmt.Printf("Text: %s\n", textMsg)

// Handle rich content (text/images)
if parts, err := message.ContentParts(); err == nil {
    for _, part := range parts {
        switch part.Type {
        case "text":
            fmt.Println("Text:", part.Text)
        case "image_url":
            fmt.Println("Image:", part.ImageURL.URL)
        }
    }
}

// Working with message metadata
message.Meta().Set("timestamp", time.Now())
message.Meta().Set("processed", true)

timestamp := message.Meta().Get("timestamp")
keys := message.Meta().Keys() // Get all metadata keys
Handling Pending Tool Calls
// Iterate over pending tool calls
err := chat.RangePendingToolCalls(func(tcc *aichat.ToolCallContext) error {
    // Get the name of the tool/function
    name := tcc.Name()

    // Get the arguments of the tool call
    args, err := tcc.Arguments()
    if err != nil {
        return err
    }

    // Handle the tool call based on its name
    switch name {
    case "get_weather":
        // Implement the logic for the "get_weather" tool
        location, _ := args["location"].(string)
        weatherData := getWeatherData(location) // Replace with your implementation

        // Return the result back to the chat session
        return tcc.Return(map[string]any{
            "location": location,
            "weather":  weatherData,
        })
    default:
        return fmt.Errorf("unknown tool: %s", name)
    }
})

if err != nil {
    fmt.Println("Error processing tool calls:", err)
}
Chat Persistence via S3 Interface

The Chat struct provides methods for saving, loading, and deleting chat sessions. Pass a key (string) that will be used to lookup the chat in the storage backend. The S3 interface is used to abstract the storage backend. Official AWS S3, Minio, Tigris, and others are compatible.

userSessionKey := "user-123-chat-789"

// Save chat state
err := chat.Save(userSessionKey)

// Load existing chat
err = chat.Load(userSessionKey)

// Delete chat
err = chat.Delete(userSessionKey)

// Your S3 storage implementation should satisfy this interface:
type S3 interface {
	Get(ctx context.Context, key string) (io.ReadCloser, error)
	Put(ctx context.Context, key string, data io.Reader) error
	Delete(ctx context.Context, key string) error
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License

Copyright (c) 2025 Joe Presbrey

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Chat

type Chat struct {
	// ID is the unique identifier for the chat session
	ID string `json:"id,omitempty"`
	// Key is the storage key used for persistence
	Key string `json:"key,omitempty"`
	// Messages contains the chronological history of chat messages
	Messages []*Message `json:"messages"`
	// Created is the timestamp when the chat session was created
	Created time.Time `json:"created"`
	// LastUpdated is the timestamp of the most recent message or modification
	LastUpdated time.Time `json:"last_updated"`
	// Meta stores arbitrary session-related data
	Meta map[string]any `json:"meta,omitempty"`
	// Options contains the configuration for these chat sessions
	Options Options `json:"-"`
}

Chat represents a chat session with message history

func (*Chat) AddAssistantContent

func (chat *Chat) AddAssistantContent(content any) *Message

AddAssistantContent adds an assistant message to the chat

func (*Chat) AddAssistantToolCall

func (chat *Chat) AddAssistantToolCall(toolCalls []ToolCall) *Message

AddAssistantToolCall adds an assistant message with tool calls

func (*Chat) AddMessage added in v1.1.0

func (chat *Chat) AddMessage(message *Message)

AddMessage adds a message to the chat

func (*Chat) AddMessageOnce added in v1.3.0

func (chat *Chat) AddMessageOnce(message *Message)

AddMessageOnce adds a message to the chat (idempotent)

func (*Chat) AddRoleContent

func (chat *Chat) AddRoleContent(role string, content any) *Message

AddRoleContent adds a role and content to the chat

func (*Chat) AddToolContent

func (chat *Chat) AddToolContent(name string, toolCallID string, content any) error

AddToolContent adds a tool content to the chat

func (*Chat) AddToolRawContent

func (chat *Chat) AddToolRawContent(name string, toolCallID string, content any) *Message

AddToolRawContent adds a raw content to the chat

func (*Chat) AddUserContent

func (chat *Chat) AddUserContent(content any) *Message

AddUserContent adds a user message to the chat

func (*Chat) ClearMessages added in v1.0.1

func (chat *Chat) ClearMessages()

ClearMessages removes all messages from the chat

func (*Chat) Delete

func (chat *Chat) Delete(ctx context.Context, key string) error

Delete deletes the session from S3 storage

func (*Chat) LastMessage

func (chat *Chat) LastMessage() *Message

LastMessage returns the last message in the chat

func (*Chat) LastMessageByRole added in v1.0.1

func (chat *Chat) LastMessageByRole(role string) *Message

LastMessageByRole returns the last message in the chat by role

func (*Chat) LastMessageByType added in v1.0.1

func (chat *Chat) LastMessageByType(contentType string) *Message

LastMessageByType returns the last message in the chat with the given content type

func (*Chat) LastMessageRole

func (chat *Chat) LastMessageRole() string

LastMessageRole returns the role of the last message in the chat

func (*Chat) Load

func (chat *Chat) Load(ctx context.Context, key string) error

Load loads a chat from S3 storage

func (*Chat) MessageCount added in v1.0.1

func (chat *Chat) MessageCount() int

MessageCount returns the total number of messages in the chat

func (*Chat) MessageCountByRole added in v1.0.1

func (chat *Chat) MessageCountByRole(role string) int

MessageCountByRole returns the number of messages with a specific role

func (*Chat) PopMessage added in v1.1.2

func (chat *Chat) PopMessage() *Message

PopMessage removes and returns the last message from the chat

func (*Chat) PopMessageIfRole added in v1.1.2

func (chat *Chat) PopMessageIfRole(role string) *Message

PopMessageIfRole removes and returns the last message from the chat if it matches the role

func (*Chat) Range

func (chat *Chat) Range(fn func(msg *Message) error) error

Range iterates through messages

func (*Chat) RangeByRole added in v1.0.1

func (chat *Chat) RangeByRole(role string, fn func(msg *Message) error) error

RangeByRole iterates through messages with a specific role

func (*Chat) RangePendingToolCalls

func (chat *Chat) RangePendingToolCalls(fn func(toolCallContext *ToolCallContext) error) error

RangePendingToolCalls iterates through messages to find and process tool calls that haven't received a response. It performs two passes: first to identify which tool calls have responses, then to process pending calls. The provided function is called for each pending tool call.

func (*Chat) RemoveLastMessage added in v1.0.1

func (chat *Chat) RemoveLastMessage() *Message

RemoveLastMessage removes and returns the last message from the chat

func (*Chat) Save

func (chat *Chat) Save(ctx context.Context, key string) error

Save saves the session to S3 storage

func (*Chat) SetSystemContent added in v1.1.2

func (chat *Chat) SetSystemContent(content any) *Message

SetSystemContent sets or updates the system message at the beginning of the chat. If the first message is a system message, it updates its content. Otherwise, it inserts a new system message at the beginning.

func (*Chat) SetSystemMessage added in v1.1.2

func (chat *Chat) SetSystemMessage(msg *Message) *Message

SetSystemMessage sets the system message at the beginning of the chat

func (*Chat) ShiftMessages added in v1.1.2

func (chat *Chat) ShiftMessages() *Message

ShiftMessages shifts all messages to the left by one index

func (*Chat) UnshiftMessages added in v1.1.2

func (chat *Chat) UnshiftMessages(msg *Message)

UnshiftMessages unshifts all messages to the right by one index

type Function

type Function struct {
	Name        string `yaml:"name" json:"name"`
	Description string `yaml:"description,omitempty" json:"description,omitempty"`

	// Arguments field is used by LLM to call tools
	Arguments string `yaml:"arguments,omitempty" json:"arguments,omitempty"`

	// Parameters field is provides available tool calling schema to LLM
	Parameters Parameters `yaml:"parameters,omitempty" json:"parameters,omitempty"`
}

Function represents a function definition

func (*Function) ArgumentsMap

func (f *Function) ArgumentsMap() (map[string]interface{}, error)

ArgumentsMap parses the Arguments JSON string into a map[string]interface{}

type Message

type Message struct {
	Role       string     `json:"role"`
	Content    any        `json:"content"`
	ToolCalls  []ToolCall `json:"tool_calls,omitempty"`
	Name       string     `json:"name,omitempty"`         // For tool responses
	ToolCallID string     `json:"tool_call_id,omitempty"` // For tool responses
	// contains filtered or unexported fields
}

Message represents a chat message in the session

func (*Message) ContentParts

func (m *Message) ContentParts() ([]*Part, error)

ContentParts returns the parts of a multipart message content (text and images). Returns nil for both parts and error if the content is not multipart. Returns error if the content cannot be properly marshaled/unmarshaled.

func (*Message) ContentString

func (m *Message) ContentString() string

ContentString returns the content of the message as a string if the content is a simple string. Returns an empty string if the content is not a string type (e.g., multipart content).

func (*Message) Meta added in v1.3.2

func (m *Message) Meta() *Meta

Meta returns a Meta struct for the message.

type Meta added in v1.3.2

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

Meta represents metadata for a message.

func (*Meta) Get added in v1.3.2

func (meta *Meta) Get(key string) any

Get retrieves a metadata value for the message. Returns nil if the key does not exist.

func (*Meta) Keys added in v1.3.2

func (meta *Meta) Keys() []string

Keys returns a slice of all metadata keys in the message.

func (*Meta) MarshalJSON added in v1.3.3

func (meta *Meta) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface.

func (*Meta) Set added in v1.3.2

func (meta *Meta) Set(key string, value any)

Set sets a metadata value for the message. It initializes the underlying map if it's nil.

type Options

type Options struct {
	S3 S3
}

Options contains configuration options for Chat sessions. S3 provides storage capabilities for persisting chat sessions.

type Parameters added in v1.1.3

type Parameters struct {
	Type       string              `yaml:"type" json:"type"`
	Properties map[string]Property `yaml:"properties" json:"properties"`
	Required   []string            `yaml:"required" json:"required"`
}

Parameters defines the structure of function parameters

type Part

type Part struct {
	Type     string `json:"type"`
	Text     string `json:"text,omitempty"`
	ImageURL struct {
		URL    string `json:"url"`
		Detail string `json:"detail,omitempty"`
	} `json:"image_url,omitempty"`
}

Part represents a part of a message content that can be either text or image

type Property added in v1.1.3

type Property struct {
	Type        string `yaml:"type" json:"type"`
	Description string `yaml:"description" json:"description"`
}

Property contains the individual parameter definitions

type S3

type S3 interface {
	// Get retrieves data from storage
	Get(ctx context.Context, key string) (io.ReadCloser, error)
	// Put stores data
	Put(ctx context.Context, key string, data io.Reader) error
	// Delete deletes data from storage
	Delete(ctx context.Context, key string) error
}

S3 represents a storage interface for sessions

type Storage

type Storage struct {
	Options Options
}

Storage represents a storage interface for chat sessions

func NewStorage

func NewStorage(options Options) *Storage

NewStorage creates a new chat storage

func (*Storage) Load

func (s *Storage) Load(ctx context.Context, key string) (*Chat, error)

Load loads a chat from storage

type Tool added in v1.1.3

type Tool struct {
	Type     string   `yaml:"type" json:"type"`
	Function Function `yaml:"function" json:"function"`
}

Tool represents a single tool

type ToolCall

type ToolCall struct {
	ID   string `json:"id,omitempty"`
	Type string `json:"type"`

	Function Function `json:"function"`
}

ToolCall represents a call to an external tool or function

type ToolCallContext

type ToolCallContext struct {
	ToolCall *ToolCall
	Chat     *Chat
}

ToolCallContext represents a tool call within a chat context, managing the lifecycle of a single tool invocation including its execution and response handling.

func (*ToolCallContext) Arguments

func (tcc *ToolCallContext) Arguments() (map[string]any, error)

Arguments returns the arguments to the function as a map

func (*ToolCallContext) Name

func (tcc *ToolCallContext) Name() string

Name returns the name of the function

func (*ToolCallContext) Return

func (tcc *ToolCallContext) Return(result map[string]any) error

Return sends the result of the function call back to the chat

Directories

Path Synopsis
examples
deepseek command
schema

Jump to

Keyboard shortcuts

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