Compare commits
2 Commits
a43a91c6ff
...
8ddac2f820
Author | SHA1 | Date | |
---|---|---|---|
8ddac2f820 | |||
cea5118cac |
8
TODO.md
8
TODO.md
@ -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
|
||||||
- [ ] Agents - a name given to a system prompt + set of available tools +
|
- [x] 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
|
||||||
pair-programmer`, `lmcli chat -a financier`).
|
code-helper`, `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
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
|
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
|
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
|
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
|
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
|
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
toolutil "git.mlow.ca/mlow/lmcli/pkg/agent/toolbox/util"
|
toolutil "git.mlow.ca/mlow/lmcli/pkg/agents/toolbox/util"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
@ -1,9 +1,9 @@
|
|||||||
package agent
|
package agents
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/agent/toolbox"
|
"git.mlow.ca/mlow/lmcli/pkg/agents/toolbox"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
@ -51,6 +51,12 @@ 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
|
||||||
@ -65,14 +71,21 @@ 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 !slices.Contains(ctx.GetModels(), model) {
|
if model != "" && !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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,15 @@ 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)
|
||||||
}
|
}
|
||||||
@ -172,8 +181,11 @@ Example response:
|
|||||||
|
|
||||||
var msgs []msg
|
var msgs []msg
|
||||||
for _, m := range messages {
|
for _, m := range messages {
|
||||||
|
switch m.Role {
|
||||||
|
case api.MessageRoleAssistant, api.MessageRoleUser:
|
||||||
msgs = append(msgs, msg{string(m.Role), m.Content})
|
msgs = append(msgs, msg{string(m.Role), m.Content})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Serialize the conversation to JSON
|
// Serialize the conversation to JSON
|
||||||
conversation, err := json.Marshal(msgs)
|
conversation, err := json.Marshal(msgs)
|
||||||
|
@ -15,6 +15,8 @@ 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"`
|
||||||
@ -23,9 +25,11 @@ 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"`
|
||||||
Tools *struct {
|
Agents []*struct {
|
||||||
EnabledTools []string `yaml:"enabledTools"`
|
Name string `yaml:"name"`
|
||||||
} `yaml:"tools"`
|
SystemPrompt string `yaml:"systemPrompt"`
|
||||||
|
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"`
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/agent"
|
"git.mlow.ca/mlow/lmcli/pkg/agents"
|
||||||
"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,20 +18,24 @@ 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 {
|
||||||
Fatal("%v\n", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseFile := filepath.Join(dataDir(), "conversations.db")
|
databaseFile := filepath.Join(dataDir(), "conversations.db")
|
||||||
@ -43,20 +47,12 @@ func NewContext() (*Context, error) {
|
|||||||
}
|
}
|
||||||
store, err := NewSQLStore(db)
|
store, err := NewSQLStore(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatal("%v\n", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
chroma := tty.NewChromaHighlighter("markdown", *config.Chroma.Formatter, *config.Chroma.Style)
|
chroma := tty.NewChromaHighlighter("markdown", *config.Chroma.Formatter, *config.Chroma.Style)
|
||||||
|
|
||||||
var enabledTools []api.ToolSpec
|
return &Context{*config, store, chroma}, nil
|
||||||
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) {
|
||||||
@ -82,6 +78,40 @@ 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)
|
||||||
|
@ -144,6 +144,12 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/agent"
|
"git.mlow.ca/mlow/lmcli/pkg/agents"
|
||||||
"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,7 +244,12 @@ 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 {
|
||||||
results, err := agent.ExecuteToolCalls(toolCalls, m.Ctx.EnabledTools)
|
agent := m.Shared.Ctx.GetAgent(m.Shared.Ctx.Config.Defaults.Agent)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
@ -266,11 +271,15 @@ func (m *Model) promptLLM() tea.Cmd {
|
|||||||
return shared.MsgError(err)
|
return shared.MsgError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
requestParams := api.RequestParameters{
|
params := 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())
|
||||||
@ -283,7 +292,7 @@ func (m *Model) promptLLM() tea.Cmd {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
resp, err := provider.CreateChatCompletionStream(
|
resp, err := provider.CreateChatCompletionStream(
|
||||||
ctx, requestParams, m.messages, m.chatReplyChunks,
|
ctx, params, m.messages, m.chatReplyChunks,
|
||||||
)
|
)
|
||||||
|
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
|
Loading…
Reference in New Issue
Block a user