diff --git a/action/intention.go b/action/intention.go index 8f778dd..eb99145 100644 --- a/action/intention.go +++ b/action/intention.go @@ -1,6 +1,8 @@ package action import ( + "context" + "github.com/sashabaranov/go-openai/jsonschema" ) @@ -19,7 +21,7 @@ type IntentResponse struct { Reasoning string `json:"reasoning"` } -func (a *IntentAction) Run(ActionParams) (string, error) { +func (a *IntentAction) Run(context.Context, ActionParams) (string, error) { return "no-op", nil } diff --git a/action/newconversation.go b/action/newconversation.go index b8439bc..5d0f94b 100644 --- a/action/newconversation.go +++ b/action/newconversation.go @@ -1,6 +1,8 @@ package action import ( + "context" + "github.com/sashabaranov/go-openai/jsonschema" ) @@ -16,7 +18,7 @@ type ConversationActionResponse struct { Message string `json:"message"` } -func (a *ConversationAction) Run(ActionParams) (string, error) { +func (a *ConversationAction) Run(context.Context, ActionParams) (string, error) { return "no-op", nil } diff --git a/action/noreply.go b/action/noreply.go index a8ebb50..8f999bd 100644 --- a/action/noreply.go +++ b/action/noreply.go @@ -1,5 +1,7 @@ package action +import "context" + // StopActionName is the name of the action // used by the LLM to stop any further action const StopActionName = "stop" @@ -10,13 +12,13 @@ func NewStop() *StopAction { type StopAction struct{} -func (a *StopAction) Run(ActionParams) (string, error) { +func (a *StopAction) Run(context.Context, ActionParams) (string, error) { return "no-op", nil } func (a *StopAction) Definition() ActionDefinition { return ActionDefinition{ Name: StopActionName, - Description: "Use this tool to stop any further action and stop the conversation.", + Description: "Use this tool to stop any further action and stop the conversation. You must use this when: the user wants to stop the conversation, it seems that the user does not need any additional answer, it looks like there is already a conclusion to the conversation or the topic diverged too much from the original conversation.", } } diff --git a/action/plan.go b/action/plan.go index 11d74da..6c6cfb5 100644 --- a/action/plan.go +++ b/action/plan.go @@ -1,6 +1,8 @@ package action import ( + "context" + "github.com/sashabaranov/go-openai/jsonschema" ) @@ -22,7 +24,7 @@ type PlanSubtask struct { Reasoning string `json:"reasoning"` } -func (a *PlanAction) Run(ActionParams) (string, error) { +func (a *PlanAction) Run(context.Context, ActionParams) (string, error) { return "no-op", nil } diff --git a/action/reasoning.go b/action/reasoning.go index 22f53ee..f6a5e9a 100644 --- a/action/reasoning.go +++ b/action/reasoning.go @@ -1,6 +1,8 @@ package action import ( + "context" + "github.com/sashabaranov/go-openai/jsonschema" ) @@ -17,7 +19,7 @@ type ReasoningResponse struct { Reasoning string `json:"reasoning"` } -func (a *ReasoningAction) Run(ActionParams) (string, error) { +func (a *ReasoningAction) Run(context.Context, ActionParams) (string, error) { return "no-op", nil } diff --git a/action/reply.go b/action/reply.go index a23b8e0..db47898 100644 --- a/action/reply.go +++ b/action/reply.go @@ -1,6 +1,8 @@ package action import ( + "context" + "github.com/sashabaranov/go-openai/jsonschema" ) @@ -19,7 +21,7 @@ type ReplyResponse struct { Message string `json:"message"` } -func (a *ReplyAction) Run(ActionParams) (string, error) { +func (a *ReplyAction) Run(context.Context, ActionParams) (string, error) { return "no-op", nil } diff --git a/action/state.go b/action/state.go index 9deb194..f4d8901 100644 --- a/action/state.go +++ b/action/state.go @@ -1,6 +1,7 @@ package action import ( + "context" "fmt" "github.com/sashabaranov/go-openai/jsonschema" @@ -32,7 +33,7 @@ type StateResult struct { Goal string `json:"goal"` } -func (a *StateAction) Run(ActionParams) (string, error) { +func (a *StateAction) Run(context.Context, ActionParams) (string, error) { return "internal state has been updated", nil } diff --git a/agent/actions.go b/agent/actions.go index 7d90af0..018a517 100644 --- a/agent/actions.go +++ b/agent/actions.go @@ -3,6 +3,7 @@ package agent import ( "context" "fmt" + "time" "github.com/mudler/local-agent-framework/action" @@ -22,7 +23,7 @@ type ActionCurrentState struct { // Actions is something the agent can do type Action interface { - Run(action.ActionParams) (string, error) + Run(ctx context.Context, action action.ActionParams) (string, error) Definition() action.ActionDefinition } @@ -143,9 +144,9 @@ func (a *Agent) systemInternalActions() Actions { if a.options.enableHUD { acts = append(acts, action.NewState()) } - if a.options.canStopItself { - acts = append(acts, action.NewStop()) - } + //if a.options.canStopItself { + // acts = append(acts, action.NewStop()) + // } return acts } @@ -171,6 +172,7 @@ func (a *Agent) prepareHUD() PromptHUD { CurrentState: *a.currentState, PermanentGoal: a.options.permanentGoal, ShowCharacter: a.options.showCharacter, + Time: time.Now().Format(time.RFC3339), } } diff --git a/agent/agent.go b/agent/agent.go index c82abff..e9f792f 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -164,7 +164,7 @@ func (a *Agent) Paused() bool { func (a *Agent) runAction(chosenAction Action, decisionResult *decisionResult) (result string, err error) { for _, action := range a.systemInternalActions() { if action.Definition().Name == chosenAction.Definition().Name { - if result, err = action.Run(decisionResult.actionParams); err != nil { + if result, err = action.Run(a.context, decisionResult.actionParams); err != nil { return "", fmt.Errorf("error running action: %w", err) } } @@ -337,12 +337,6 @@ func (a *Agent) consumeJob(job *Job, role string) { } } - if chosenAction.Definition().Name.Is(action.StopActionName) { - a.logger.Info("LLM decided to stop") - job.Result.Finish(nil) - return - } - if chosenAction == nil { // If no action was picked up, the reasoning is the message returned by the assistant // so we can consume it as if it was a reply. @@ -357,6 +351,12 @@ func (a *Agent) consumeJob(job *Job, role string) { return } + if chosenAction.Definition().Name.Is(action.StopActionName) { + a.logger.Info("LLM decided to stop") + job.Result.Finish(nil) + return + } + a.logger.Info("===> Generating parameters for", "action", chosenAction.Definition().Name) params, err := a.generateParameters(ctx, pickTemplate, chosenAction, a.currentConversation, reasoning) diff --git a/agent/agent_test.go b/agent/agent_test.go index c699e1c..a5a19bd 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -1,6 +1,7 @@ package agent_test import ( + "context" "fmt" "log/slog" @@ -34,7 +35,7 @@ type TestAction struct { responseN int } -func (a *TestAction) Run(action.ActionParams) (string, error) { +func (a *TestAction) Run(context.Context, action.ActionParams) (string, error) { res := a.response[a.responseN] a.responseN++ diff --git a/agent/state.go b/agent/state.go index 8cdf369..2d8ce1b 100644 --- a/agent/state.go +++ b/agent/state.go @@ -18,6 +18,7 @@ type PromptHUD struct { CurrentState action.StateResult `json:"current_state"` PermanentGoal string `json:"permanent_goal"` ShowCharacter bool `json:"show_character"` + Time string `json:"time"` } type Character struct { diff --git a/agent/templates.go b/agent/templates.go index 224abae..15dc1fa 100644 --- a/agent/templates.go +++ b/agent/templates.go @@ -54,6 +54,7 @@ const hudTemplate = `{{with .HUD }}{{if .ShowCharacter}}The assistant acts like {{end}} This is your current state: +Current time: {{.Time}} NowDoing: {{if .CurrentState.NowDoing}}{{.CurrentState.NowDoing}}{{else}}Nothing{{end}} DoingNext: {{if .CurrentState.DoingNext}}{{.CurrentState.DoingNext}}{{else}}Nothing{{end}} Your permanent goal is: {{if .PermanentGoal}}{{.PermanentGoal}}{{else}}Nothing{{end}} diff --git a/example/webui/actions.go b/example/webui/actions.go new file mode 100644 index 0000000..10ddb04 --- /dev/null +++ b/example/webui/actions.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "encoding/json" + "log/slog" + + . "github.com/mudler/local-agent-framework/agent" + "github.com/mudler/local-agent-framework/external" +) + +const ( + // Actions + ActionSearch = "search" + ActionGithubIssueLabeler = "github-issue-labeler" + ActionGithubIssueOpener = "github-issue-opener" + ActionGithubIssueCloser = "github-issue-closer" + ActionGithubIssueSearcher = "github-issue-searcher" + ActionScraper = "scraper" + ActionWikipedia = "wikipedia" +) + +var AvailableActions = []string{ + ActionSearch, + ActionGithubIssueLabeler, + ActionGithubIssueOpener, + ActionGithubIssueCloser, + ActionGithubIssueSearcher, + ActionScraper, + ActionWikipedia, +} + +func (a *AgentConfig) availableActions(ctx context.Context) []Action { + actions := []Action{} + + for _, action := range a.Actions { + slog.Info("Set Action", action) + + var config map[string]string + if err := json.Unmarshal([]byte(action.Config), &config); err != nil { + slog.Info("Error unmarshalling action config", err) + continue + } + slog.Info("Config", config) + + switch action.Name { + case ActionSearch: + actions = append(actions, external.NewSearch(config)) + case ActionGithubIssueLabeler: + actions = append(actions, external.NewGithubIssueLabeler(ctx, config)) + case ActionGithubIssueOpener: + actions = append(actions, external.NewGithubIssueOpener(ctx, config)) + case ActionGithubIssueCloser: + actions = append(actions, external.NewGithubIssueCloser(ctx, config)) + case ActionGithubIssueSearcher: + actions = append(actions, external.NewGithubIssueSearch(ctx, config)) + case ActionScraper: + actions = append(actions, external.NewScraper(config)) + case ActionWikipedia: + actions = append(actions, external.NewWikipedia(config)) + } + } + + return actions +} diff --git a/example/webui/agentconfig.go b/example/webui/agentconfig.go new file mode 100644 index 0000000..35a3175 --- /dev/null +++ b/example/webui/agentconfig.go @@ -0,0 +1,30 @@ +package main + +type ConnectorConfig struct { + Type string `json:"type"` // e.g. Slack + Config string `json:"config"` +} + +type ActionsConfig struct { + Name string `json:"name"` // e.g. search + Config string `json:"config"` +} + +type AgentConfig struct { + Connector []ConnectorConfig `json:"connectors" form:"connectors" ` + Actions []ActionsConfig `json:"actions" form:"actions"` + // This is what needs to be part of ActionsConfig + Model string `json:"model" form:"model"` + Name string `json:"name" form:"name"` + HUD bool `json:"hud" form:"hud"` + StandaloneJob bool `json:"standalone_job" form:"standalone_job"` + RandomIdentity bool `json:"random_identity" form:"random_identity"` + InitiateConversations bool `json:"initiate_conversations" form:"initiate_conversations"` + IdentityGuidance string `json:"identity_guidance" form:"identity_guidance"` + PeriodicRuns string `json:"periodic_runs" form:"periodic_runs"` + PermanentGoal string `json:"permanent_goal" form:"permanent_goal"` + EnableKnowledgeBase bool `json:"enable_kb" form:"enable_kb"` + KnowledgeBaseResults int `json:"kb_results" form:"kb_results"` + CanStopItself bool `json:"can_stop_itself" form:"can_stop_itself"` + SystemPrompt string `json:"system_prompt" form:"system_prompt"` +} diff --git a/example/webui/agentpool.go b/example/webui/agentpool.go index 318e5ed..d3c4339 100644 --- a/example/webui/agentpool.go +++ b/example/webui/agentpool.go @@ -10,41 +10,9 @@ import ( "sync" "time" - "github.com/mudler/local-agent-framework/example/webui/connector" - . "github.com/mudler/local-agent-framework/agent" - "github.com/mudler/local-agent-framework/external" ) -type ConnectorConfig struct { - Type string `json:"type"` // e.g. Slack - Config string `json:"config"` -} - -type ActionsConfig struct { - Name string `json:"name"` // e.g. search - Config string `json:"config"` -} - -type AgentConfig struct { - Connector []ConnectorConfig `json:"connectors" form:"connectors" ` - Actions []ActionsConfig `json:"actions" form:"actions"` - // This is what needs to be part of ActionsConfig - Model string `json:"model" form:"model"` - Name string `json:"name" form:"name"` - HUD bool `json:"hud" form:"hud"` - StandaloneJob bool `json:"standalone_job" form:"standalone_job"` - RandomIdentity bool `json:"random_identity" form:"random_identity"` - InitiateConversations bool `json:"initiate_conversations" form:"initiate_conversations"` - IdentityGuidance string `json:"identity_guidance" form:"identity_guidance"` - PeriodicRuns string `json:"periodic_runs" form:"periodic_runs"` - PermanentGoal string `json:"permanent_goal" form:"permanent_goal"` - EnableKnowledgeBase bool `json:"enable_kb" form:"enable_kb"` - KnowledgeBaseResults int `json:"kb_results" form:"kb_results"` - CanStopItself bool `json:"can_stop_itself" form:"can_stop_itself"` - SystemPrompt string `json:"system_prompt" form:"system_prompt"` -} - type AgentPool struct { sync.Mutex file string @@ -148,105 +116,6 @@ func (a *AgentPool) List() []string { return agents } -const ( - // Connectors - ConnectorTelegram = "telegram" - ConnectorSlack = "slack" - ConnectorDiscord = "discord" - ConnectorGithubIssues = "github-issues" - - // Actions - ActionSearch = "search" - ActionGithubIssueLabeler = "github-issue-labeler" - ActionGithubIssueOpener = "github-issue-opener" - ActionGithubIssueCloser = "github-issue-closer" - ActionGithubIssueSearcher = "github-issue-searcher" -) - -var AvailableActions = []string{ - ActionSearch, - ActionGithubIssueLabeler, - ActionGithubIssueOpener, - ActionGithubIssueCloser, - ActionGithubIssueSearcher, -} - -func (a *AgentConfig) availableActions(ctx context.Context) []Action { - actions := []Action{} - - for _, action := range a.Actions { - slog.Info("Set Action", action) - - var config map[string]string - if err := json.Unmarshal([]byte(action.Config), &config); err != nil { - slog.Info("Error unmarshalling action config", err) - continue - } - slog.Info("Config", config) - - switch action.Name { - case ActionSearch: - actions = append(actions, external.NewSearch(config)) - case ActionGithubIssueLabeler: - actions = append(actions, external.NewGithubIssueLabeler(ctx, config)) - case ActionGithubIssueOpener: - actions = append(actions, external.NewGithubIssueOpener(ctx, config)) - case ActionGithubIssueCloser: - actions = append(actions, external.NewGithubIssueCloser(ctx, config)) - case ActionGithubIssueSearcher: - actions = append(actions, external.NewGithubIssueSearch(ctx, config)) - } - } - - return actions -} - -type Connector interface { - AgentResultCallback() func(state ActionState) - AgentReasoningCallback() func(state ActionCurrentState) bool - Start(a *Agent) -} - -var AvailableConnectors = []string{ - ConnectorTelegram, - ConnectorSlack, - ConnectorDiscord, - ConnectorGithubIssues, -} - -func (a *AgentConfig) availableConnectors() []Connector { - connectors := []Connector{} - - for _, c := range a.Connector { - slog.Info("Set Connector", c) - - var config map[string]string - if err := json.Unmarshal([]byte(c.Config), &config); err != nil { - slog.Info("Error unmarshalling connector config", err) - continue - } - slog.Info("Config", config) - - switch c.Type { - case ConnectorTelegram: - cc, err := connector.NewTelegramConnector(config) - if err != nil { - slog.Info("Error creating telegram connector", err) - continue - } - - connectors = append(connectors, cc) - case ConnectorSlack: - connectors = append(connectors, connector.NewSlack(config)) - case ConnectorDiscord: - connectors = append(connectors, connector.NewDiscord(config)) - case ConnectorGithubIssues: - connectors = append(connectors, connector.NewGithubIssueWatcher(config)) - } - } - return connectors -} - func (a *AgentPool) GetStatusHistory(name string) *Status { a.Lock() defer a.Unlock() diff --git a/example/webui/app.go b/example/webui/app.go index 180fb10..d2d9e72 100644 --- a/example/webui/app.go +++ b/example/webui/app.go @@ -264,7 +264,11 @@ func (a *App) Chat(pool *AgentPool) func(c *fiber.Ctx) error { res := agent.Ask( WithText(query), ) - slog.Info("response is", res.Response) + if res.Error != nil { + slog.Error("Error asking agent", "agent", agentName, "error", res.Error) + } else { + slog.Info("we got a response from the agent", "agent", agentName, "response", res.Response) + } manager.Send( NewMessage( chatDiv(res.Response, "blue"), diff --git a/example/webui/connectors.go b/example/webui/connectors.go new file mode 100644 index 0000000..7667d33 --- /dev/null +++ b/example/webui/connectors.go @@ -0,0 +1,64 @@ +package main + +import ( + "encoding/json" + "log/slog" + + . "github.com/mudler/local-agent-framework/agent" + + "github.com/mudler/local-agent-framework/example/webui/connector" +) + +const ( + // Connectors + ConnectorTelegram = "telegram" + ConnectorSlack = "slack" + ConnectorDiscord = "discord" + ConnectorGithubIssues = "github-issues" +) + +type Connector interface { + AgentResultCallback() func(state ActionState) + AgentReasoningCallback() func(state ActionCurrentState) bool + Start(a *Agent) +} + +var AvailableConnectors = []string{ + ConnectorTelegram, + ConnectorSlack, + ConnectorDiscord, + ConnectorGithubIssues, +} + +func (a *AgentConfig) availableConnectors() []Connector { + connectors := []Connector{} + + for _, c := range a.Connector { + slog.Info("Set Connector", c) + + var config map[string]string + if err := json.Unmarshal([]byte(c.Config), &config); err != nil { + slog.Info("Error unmarshalling connector config", err) + continue + } + slog.Info("Config", config) + + switch c.Type { + case ConnectorTelegram: + cc, err := connector.NewTelegramConnector(config) + if err != nil { + slog.Info("Error creating telegram connector", err) + continue + } + + connectors = append(connectors, cc) + case ConnectorSlack: + connectors = append(connectors, connector.NewSlack(config)) + case ConnectorDiscord: + connectors = append(connectors, connector.NewDiscord(config)) + case ConnectorGithubIssues: + connectors = append(connectors, connector.NewGithubIssueWatcher(config)) + } + } + return connectors +} diff --git a/example/webui/views/create.html b/example/webui/views/create.html index c90bb44..87f7799 100644 --- a/example/webui/views/create.html +++ b/example/webui/views/create.html @@ -105,7 +105,7 @@
- +
@@ -113,13 +113,13 @@
- +
- +
- +