Compare commits

..

No commits in common. "8ddac2f820a8d58a3f126348e1ed8d9745862a29" and "a43a91c6ffc835e39aba9e1f515bb145e3a4a9cd" have entirely different histories.

15 changed files with 38 additions and 112 deletions

View File

@ -4,15 +4,15 @@
when calling anthropic? when calling anthropic?
- [x] `dir_tree` tool - [x] `dir_tree` tool
- [x] Implement native Anthropic API tool calling - [x] Implement native Anthropic API tool calling
- [x] Agents - a name given to a system prompt + set of available tools + - [ ] Agents - a name given to a system prompt + set of available tools +
potentially other relevent data (e.g. external service credentials, files for potentially other relevent data (e.g. external service credentials, files for
RAG, etc), which the user explicitly selects (e.g. `lmcli chat --agent RAG, etc), which the user explicitly selects (e.g. `lmcli chat --agent
code-helper`, `lmcli chat -a financier`). pair-programmer`, `lmcli chat -a financier`).
- [ ] Specialized agents which have integrations beyond basic tool calling, - Specialized agents which have integrations beyond basic tool calling,
e.g. a coding agent which bakes in efficient code context management e.g. a coding agent which bakes in efficient code context management
(only the current state of relevant files get shown to the model in the (only the current state of relevant files get shown to the model in the
system prompt, rather than having them in the conversation messages) system prompt, rather than having them in the conversation messages)
- [ ] Agents may have some form of long term memory management (key-value? - Agents may have some form of long term memory management (key-value?
natural lang?). natural lang?).
- [ ] Support for arbitrary external script tools - [ ] Support for arbitrary external script tools
- [ ] Search - RAG driven search of existing conversation "hey, remind me of - [ ] Search - RAG driven search of existing conversation "hey, remind me of

View File

@ -7,7 +7,7 @@ import (
"strconv" "strconv"
"strings" "strings"
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util" toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
) )

View File

@ -5,7 +5,7 @@ import (
"os" "os"
"strings" "strings"
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util" toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
) )

View File

@ -5,7 +5,7 @@ import (
"os" "os"
"strings" "strings"
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util" toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
) )

View File

@ -6,7 +6,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util" toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
) )

View File

@ -5,7 +5,7 @@ import (
"os" "os"
"strings" "strings"
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util" toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
) )

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util" toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
) )

View File

@ -1,9 +1,9 @@
package agents package agent
import ( import (
"fmt" "fmt"
"git.mlow.ca/mlow/lmcli/pkg/agents/toolbox" "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
) )

View File

@ -51,12 +51,6 @@ func applyGenerationFlags(ctx *lmcli.Context, cmd *cobra.Command) {
return ctx.GetModels(), cobra.ShellCompDirectiveDefault return ctx.GetModels(), cobra.ShellCompDirectiveDefault
}) })
// -a, --agent
f.StringVarP(&ctx.Config.Defaults.Agent, "agent", "a", ctx.Config.Defaults.Agent, "Which agent to interact with")
cmd.RegisterFlagCompletionFunc("agent", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return ctx.GetAgents(), cobra.ShellCompDirectiveDefault
})
// --max-length // --max-length
f.IntVar(ctx.Config.Defaults.MaxTokens, "max-length", *ctx.Config.Defaults.MaxTokens, "Maximum response tokens") f.IntVar(ctx.Config.Defaults.MaxTokens, "max-length", *ctx.Config.Defaults.MaxTokens, "Maximum response tokens")
// --temperature // --temperature
@ -71,21 +65,14 @@ func applyGenerationFlags(ctx *lmcli.Context, cmd *cobra.Command) {
func validateGenerationFlags(ctx *lmcli.Context, cmd *cobra.Command) error { func validateGenerationFlags(ctx *lmcli.Context, cmd *cobra.Command) error {
f := cmd.Flags() f := cmd.Flags()
model, err := f.GetString("model") model, err := f.GetString("model")
if err != nil { if err != nil {
return fmt.Errorf("Error parsing --model: %w", err) return fmt.Errorf("Error parsing --model: %w", err)
} }
if model != "" && !slices.Contains(ctx.GetModels(), model) { if !slices.Contains(ctx.GetModels(), model) {
return fmt.Errorf("Unknown model: %s", model) return fmt.Errorf("Unknown model: %s", model)
} }
agent, err := f.GetString("agent")
if err != nil {
return fmt.Errorf("Error parsing --agent: %w", err)
}
if agent != "" && !slices.Contains(ctx.GetAgents(), agent) {
return fmt.Errorf("Unknown agent: %s", agent)
}
return nil return nil
} }

View File

@ -29,15 +29,6 @@ func Prompt(ctx *lmcli.Context, messages []api.Message, callback func(api.Messag
} }
system := ctx.DefaultSystemPrompt() system := ctx.DefaultSystemPrompt()
agent := ctx.GetAgent(ctx.Config.Defaults.Agent)
if agent != nil {
if agent.SystemPrompt != "" {
system = agent.SystemPrompt
}
params.ToolBag = agent.Toolbox
}
if system != "" { if system != "" {
messages = api.ApplySystemPrompt(messages, system, false) messages = api.ApplySystemPrompt(messages, system, false)
} }
@ -181,10 +172,7 @@ Example response:
var msgs []msg var msgs []msg
for _, m := range messages { for _, m := range messages {
switch m.Role { msgs = append(msgs, msg{string(m.Role), m.Content})
case api.MessageRoleAssistant, api.MessageRoleUser:
msgs = append(msgs, msg{string(m.Role), m.Content})
}
} }
// Serialize the conversation to JSON // Serialize the conversation to JSON

View File

@ -15,8 +15,6 @@ type Config struct {
Temperature *float32 `yaml:"temperature" default:"0.2"` Temperature *float32 `yaml:"temperature" default:"0.2"`
SystemPrompt string `yaml:"systemPrompt,omitempty"` SystemPrompt string `yaml:"systemPrompt,omitempty"`
SystemPromptFile string `yaml:"systemPromptFile,omitempty"` SystemPromptFile string `yaml:"systemPromptFile,omitempty"`
// CLI only
Agent string `yaml:"-"`
} `yaml:"defaults"` } `yaml:"defaults"`
Conversations *struct { Conversations *struct {
TitleGenerationModel *string `yaml:"titleGenerationModel" default:"gpt-3.5-turbo"` TitleGenerationModel *string `yaml:"titleGenerationModel" default:"gpt-3.5-turbo"`
@ -25,11 +23,9 @@ type Config struct {
Style *string `yaml:"style" default:"onedark"` Style *string `yaml:"style" default:"onedark"`
Formatter *string `yaml:"formatter" default:"terminal16m"` Formatter *string `yaml:"formatter" default:"terminal16m"`
} `yaml:"chroma"` } `yaml:"chroma"`
Agents []*struct { Tools *struct {
Name string `yaml:"name"` EnabledTools []string `yaml:"enabledTools"`
SystemPrompt string `yaml:"systemPrompt"` } `yaml:"tools"`
Tools []string `yaml:"tools"`
} `yaml:"agents"`
Providers []*struct { Providers []*struct {
Name string `yaml:"name,omitempty"` Name string `yaml:"name,omitempty"`
Kind string `yaml:"kind"` Kind string `yaml:"kind"`

View File

@ -6,7 +6,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"git.mlow.ca/mlow/lmcli/pkg/agents" "git.mlow.ca/mlow/lmcli/pkg/agent"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
"git.mlow.ca/mlow/lmcli/pkg/api/provider/anthropic" "git.mlow.ca/mlow/lmcli/pkg/api/provider/anthropic"
"git.mlow.ca/mlow/lmcli/pkg/api/provider/google" "git.mlow.ca/mlow/lmcli/pkg/api/provider/google"
@ -18,24 +18,20 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type Agent struct {
Name string
SystemPrompt string
Toolbox []api.ToolSpec
}
type Context struct { type Context struct {
// high level app configuration, may be mutated at runtime // high level app configuration, may be mutated at runtime
Config Config Config Config
Store ConversationStore Store ConversationStore
Chroma *tty.ChromaHighlighter
Chroma *tty.ChromaHighlighter
EnabledTools []api.ToolSpec
} }
func NewContext() (*Context, error) { func NewContext() (*Context, error) {
configFile := filepath.Join(configDir(), "config.yaml") configFile := filepath.Join(configDir(), "config.yaml")
config, err := NewConfig(configFile) config, err := NewConfig(configFile)
if err != nil { if err != nil {
return nil, err Fatal("%v\n", err)
} }
databaseFile := filepath.Join(dataDir(), "conversations.db") databaseFile := filepath.Join(dataDir(), "conversations.db")
@ -47,12 +43,20 @@ func NewContext() (*Context, error) {
} }
store, err := NewSQLStore(db) store, err := NewSQLStore(db)
if err != nil { if err != nil {
return nil, err Fatal("%v\n", err)
} }
chroma := tty.NewChromaHighlighter("markdown", *config.Chroma.Formatter, *config.Chroma.Style) chroma := tty.NewChromaHighlighter("markdown", *config.Chroma.Formatter, *config.Chroma.Style)
return &Context{*config, store, chroma}, nil var enabledTools []api.ToolSpec
for _, toolName := range config.Tools.EnabledTools {
tool, ok := agent.AvailableTools[toolName]
if ok {
enabledTools = append(enabledTools, tool)
}
}
return &Context{*config, store, chroma, enabledTools}, nil
} }
func (c *Context) GetModels() (models []string) { func (c *Context) GetModels() (models []string) {
@ -78,40 +82,6 @@ func (c *Context) GetModels() (models []string) {
return return
} }
func (c *Context) GetAgents() (agents []string) {
for _, p := range c.Config.Agents {
agents = append(agents, p.Name)
}
return
}
func (c *Context) GetAgent(name string) *Agent {
if name == "" {
return nil
}
for _, a := range c.Config.Agents {
if name != a.Name {
continue
}
var enabledTools []api.ToolSpec
for _, toolName := range a.Tools {
tool, ok := agents.AvailableTools[toolName]
if ok {
enabledTools = append(enabledTools, tool)
}
}
return &Agent{
Name: a.Name,
SystemPrompt: a.SystemPrompt,
Toolbox: enabledTools,
}
}
return nil
}
func (c *Context) DefaultSystemPrompt() string { func (c *Context) DefaultSystemPrompt() string {
if c.Config.Defaults.SystemPromptFile != "" { if c.Config.Defaults.SystemPromptFile != "" {
content, err := util.ReadFileContents(c.Config.Defaults.SystemPromptFile) content, err := util.ReadFileContents(c.Config.Defaults.SystemPromptFile)

View File

@ -144,12 +144,6 @@ func Chat(shared shared.Shared) Model {
m.replyCursor.Focus() m.replyCursor.Focus()
system := shared.Ctx.DefaultSystemPrompt() system := shared.Ctx.DefaultSystemPrompt()
agent := shared.Ctx.GetAgent(shared.Ctx.Config.Defaults.Agent)
if agent != nil && agent.SystemPrompt != "" {
system = agent.SystemPrompt
}
if system != "" { if system != "" {
m.messages = api.ApplySystemPrompt(m.messages, system, false) m.messages = api.ApplySystemPrompt(m.messages, system, false)
} }

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"git.mlow.ca/mlow/lmcli/pkg/agents" "git.mlow.ca/mlow/lmcli/pkg/agent"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
cmdutil "git.mlow.ca/mlow/lmcli/pkg/cmd/util" cmdutil "git.mlow.ca/mlow/lmcli/pkg/cmd/util"
"git.mlow.ca/mlow/lmcli/pkg/tui/shared" "git.mlow.ca/mlow/lmcli/pkg/tui/shared"
@ -244,12 +244,7 @@ func (m *Model) persistConversation() tea.Cmd {
func (m *Model) executeToolCalls(toolCalls []api.ToolCall) tea.Cmd { func (m *Model) executeToolCalls(toolCalls []api.ToolCall) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
agent := m.Shared.Ctx.GetAgent(m.Shared.Ctx.Config.Defaults.Agent) results, err := agent.ExecuteToolCalls(toolCalls, m.Ctx.EnabledTools)
if agent == nil {
return shared.MsgError(fmt.Errorf("Attempted to execute tool calls with no agent configured"))
}
results, err := agents.ExecuteToolCalls(toolCalls, agent.Toolbox)
if err != nil { if err != nil {
return shared.MsgError(err) return shared.MsgError(err)
} }
@ -271,15 +266,11 @@ func (m *Model) promptLLM() tea.Cmd {
return shared.MsgError(err) return shared.MsgError(err)
} }
params := api.RequestParameters{ requestParams := api.RequestParameters{
Model: model, Model: model,
MaxTokens: *m.Shared.Ctx.Config.Defaults.MaxTokens, MaxTokens: *m.Shared.Ctx.Config.Defaults.MaxTokens,
Temperature: *m.Shared.Ctx.Config.Defaults.Temperature, Temperature: *m.Shared.Ctx.Config.Defaults.Temperature,
} ToolBag: m.Shared.Ctx.EnabledTools,
agent := m.Shared.Ctx.GetAgent(m.Shared.Ctx.Config.Defaults.Agent)
if agent != nil {
params.ToolBag = agent.Toolbox
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -292,7 +283,7 @@ func (m *Model) promptLLM() tea.Cmd {
}() }()
resp, err := provider.CreateChatCompletionStream( resp, err := provider.CreateChatCompletionStream(
ctx, params, m.messages, m.chatReplyChunks, ctx, requestParams, m.messages, m.chatReplyChunks,
) )
if errors.Is(err, context.Canceled) { if errors.Is(err, context.Canceled) {