Compare commits
1 Commits
obs-detail
...
feat/brows
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a1004e459 |
5
main.go
5
main.go
@@ -22,6 +22,7 @@ var withLogs = os.Getenv("LOCALAGI_ENABLE_CONVERSATIONS_LOGGING") == "true"
|
|||||||
var apiKeysEnv = os.Getenv("LOCALAGI_API_KEYS")
|
var apiKeysEnv = os.Getenv("LOCALAGI_API_KEYS")
|
||||||
var imageModel = os.Getenv("LOCALAGI_IMAGE_MODEL")
|
var imageModel = os.Getenv("LOCALAGI_IMAGE_MODEL")
|
||||||
var conversationDuration = os.Getenv("LOCALAGI_CONVERSATION_DURATION")
|
var conversationDuration = os.Getenv("LOCALAGI_CONVERSATION_DURATION")
|
||||||
|
var localOperatorBaseURL = os.Getenv("LOCALOPERATOR_BASE_URL")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if baseModel == "" {
|
if baseModel == "" {
|
||||||
@@ -61,7 +62,9 @@ func main() {
|
|||||||
apiKey,
|
apiKey,
|
||||||
stateDir,
|
stateDir,
|
||||||
localRAG,
|
localRAG,
|
||||||
services.Actions,
|
services.Actions(map[string]string{
|
||||||
|
"browser-agent-runner-base-url": localOperatorBaseURL,
|
||||||
|
}),
|
||||||
services.Connectors,
|
services.Connectors,
|
||||||
services.DynamicPrompts,
|
services.DynamicPrompts,
|
||||||
timeout,
|
timeout,
|
||||||
|
|||||||
70
pkg/localoperator/client.go
Normal file
70
pkg/localoperator/client.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client represents a client for interacting with the LocalOperator API
|
||||||
|
type Client struct {
|
||||||
|
baseURL string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new API client
|
||||||
|
func NewClient(baseURL string) *Client {
|
||||||
|
return &Client{
|
||||||
|
baseURL: baseURL,
|
||||||
|
httpClient: &http.Client{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentRequest represents the request body for running an agent
|
||||||
|
type AgentRequest struct {
|
||||||
|
Goal string `json:"goal"`
|
||||||
|
MaxAttempts int `json:"max_attempts,omitempty"`
|
||||||
|
MaxNoActionAttempts int `json:"max_no_action_attempts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateDescription represents a single state in the agent's history
|
||||||
|
type StateDescription struct {
|
||||||
|
CurrentURL string `json:"current_url"`
|
||||||
|
PageTitle string `json:"page_title"`
|
||||||
|
PageContentDescription string `json:"page_content_description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateHistory represents the complete history of states during agent execution
|
||||||
|
type StateHistory struct {
|
||||||
|
States []StateDescription `json:"states"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunAgent sends a request to run an agent with the given goal
|
||||||
|
func (c *Client) RunBrowserAgent(req AgentRequest) (*StateHistory, error) {
|
||||||
|
body, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Post(
|
||||||
|
fmt.Sprintf("%s/api/browser/run", c.baseURL),
|
||||||
|
"application/json",
|
||||||
|
bytes.NewBuffer(body),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var state StateHistory
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&state); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &state, nil
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ const (
|
|||||||
// Actions
|
// Actions
|
||||||
ActionSearch = "search"
|
ActionSearch = "search"
|
||||||
ActionCustom = "custom"
|
ActionCustom = "custom"
|
||||||
|
ActionBrowserAgentRunner = "browser-agent-runner"
|
||||||
ActionGithubIssueLabeler = "github-issue-labeler"
|
ActionGithubIssueLabeler = "github-issue-labeler"
|
||||||
ActionGithubIssueOpener = "github-issue-opener"
|
ActionGithubIssueOpener = "github-issue-opener"
|
||||||
ActionGithubIssueCloser = "github-issue-closer"
|
ActionGithubIssueCloser = "github-issue-closer"
|
||||||
@@ -52,6 +53,7 @@ var AvailableActions = []string{
|
|||||||
ActionGithubIssueSearcher,
|
ActionGithubIssueSearcher,
|
||||||
ActionGithubRepositoryGet,
|
ActionGithubRepositoryGet,
|
||||||
ActionGithubGetAllContent,
|
ActionGithubGetAllContent,
|
||||||
|
ActionBrowserAgentRunner,
|
||||||
ActionGithubRepositoryCreateOrUpdate,
|
ActionGithubRepositoryCreateOrUpdate,
|
||||||
ActionGithubIssueReader,
|
ActionGithubIssueReader,
|
||||||
ActionGithubIssueCommenter,
|
ActionGithubIssueCommenter,
|
||||||
@@ -71,31 +73,34 @@ var AvailableActions = []string{
|
|||||||
ActionShellcommand,
|
ActionShellcommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Actions(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
func Actions(actionsConfigs map[string]string) func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||||
return func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
return func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||||
allActions := []types.Action{}
|
return func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||||
|
allActions := []types.Action{}
|
||||||
|
|
||||||
agentName := a.Name
|
agentName := a.Name
|
||||||
|
|
||||||
for _, a := range a.Actions {
|
for _, a := range a.Actions {
|
||||||
var config map[string]string
|
var config map[string]string
|
||||||
if err := json.Unmarshal([]byte(a.Config), &config); err != nil {
|
if err := json.Unmarshal([]byte(a.Config), &config); err != nil {
|
||||||
xlog.Error("Error unmarshalling action config", "error", err)
|
xlog.Error("Error unmarshalling action config", "error", err)
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := Action(a.Name, agentName, config, pool, actionsConfigs)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allActions = append(allActions, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
a, err := Action(a.Name, agentName, config, pool)
|
return allActions
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
allActions = append(allActions, a)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return allActions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Action(name, agentName string, config map[string]string, pool *state.AgentPool) (types.Action, error) {
|
func Action(name, agentName string, config map[string]string, pool *state.AgentPool, actionsConfigs map[string]string) (types.Action, error) {
|
||||||
var a types.Action
|
var a types.Action
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -114,6 +119,8 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP
|
|||||||
a = actions.NewGithubIssueCloser(config)
|
a = actions.NewGithubIssueCloser(config)
|
||||||
case ActionGithubIssueSearcher:
|
case ActionGithubIssueSearcher:
|
||||||
a = actions.NewGithubIssueSearch(config)
|
a = actions.NewGithubIssueSearch(config)
|
||||||
|
case ActionBrowserAgentRunner:
|
||||||
|
a = actions.NewBrowserAgentRunner(config, actionsConfigs["browser-agent-runner-base-url"])
|
||||||
case ActionGithubIssueReader:
|
case ActionGithubIssueReader:
|
||||||
a = actions.NewGithubIssueReader(config)
|
a = actions.NewGithubIssueReader(config)
|
||||||
case ActionGithubPRReader:
|
case ActionGithubPRReader:
|
||||||
@@ -169,6 +176,11 @@ func ActionsConfigMeta() []config.FieldGroup {
|
|||||||
Label: "Search",
|
Label: "Search",
|
||||||
Fields: actions.SearchConfigMeta(),
|
Fields: actions.SearchConfigMeta(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "browser-agent-runner",
|
||||||
|
Label: "Browser Agent Runner",
|
||||||
|
Fields: actions.BrowserAgentRunnerConfigMeta(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "generate_image",
|
Name: "generate_image",
|
||||||
Label: "Generate Image",
|
Label: "Generate Image",
|
||||||
|
|||||||
116
services/actions/browseragentrunner.go
Normal file
116
services/actions/browseragentrunner.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mudler/LocalAGI/core/types"
|
||||||
|
"github.com/mudler/LocalAGI/pkg/config"
|
||||||
|
api "github.com/mudler/LocalAGI/pkg/localoperator"
|
||||||
|
"github.com/sashabaranov/go-openai/jsonschema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BrowserAgentRunner struct {
|
||||||
|
baseURL, customActionName string
|
||||||
|
client *api.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBrowserAgentRunner(config map[string]string, defaultURL string) *BrowserAgentRunner {
|
||||||
|
if config["baseURL"] == "" {
|
||||||
|
config["baseURL"] = defaultURL
|
||||||
|
}
|
||||||
|
|
||||||
|
client := api.NewClient(config["baseURL"])
|
||||||
|
|
||||||
|
return &BrowserAgentRunner{
|
||||||
|
client: client,
|
||||||
|
baseURL: config["baseURL"],
|
||||||
|
customActionName: config["customActionName"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrowserAgentRunner) Run(ctx context.Context, params types.ActionParams) (types.ActionResult, error) {
|
||||||
|
result := api.AgentRequest{}
|
||||||
|
err := params.Unmarshal(&result)
|
||||||
|
if err != nil {
|
||||||
|
return types.ActionResult{}, fmt.Errorf("failed to unmarshal params: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := api.AgentRequest{
|
||||||
|
Goal: result.Goal,
|
||||||
|
MaxAttempts: result.MaxAttempts,
|
||||||
|
MaxNoActionAttempts: result.MaxNoActionAttempts,
|
||||||
|
}
|
||||||
|
|
||||||
|
stateHistory, err := b.client.RunBrowserAgent(req)
|
||||||
|
if err != nil {
|
||||||
|
return types.ActionResult{}, fmt.Errorf("failed to run browser agent: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the state history into a readable string
|
||||||
|
var historyStr string
|
||||||
|
// for i, state := range stateHistory.States {
|
||||||
|
// historyStr += fmt.Sprintf("State %d:\n", i+1)
|
||||||
|
// historyStr += fmt.Sprintf(" URL: %s\n", state.CurrentURL)
|
||||||
|
// historyStr += fmt.Sprintf(" Title: %s\n", state.PageTitle)
|
||||||
|
// historyStr += fmt.Sprintf(" Description: %s\n\n", state.PageContentDescription)
|
||||||
|
// }
|
||||||
|
|
||||||
|
historyStr += fmt.Sprintf(" URL: %s\n", stateHistory.States[len(stateHistory.States)-1].CurrentURL)
|
||||||
|
historyStr += fmt.Sprintf(" Title: %s\n", stateHistory.States[len(stateHistory.States)-1].PageTitle)
|
||||||
|
historyStr += fmt.Sprintf(" Description: %s\n\n", stateHistory.States[len(stateHistory.States)-1].PageContentDescription)
|
||||||
|
|
||||||
|
return types.ActionResult{
|
||||||
|
Result: fmt.Sprintf("Browser agent completed successfully. History:\n%s", historyStr),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrowserAgentRunner) Definition() types.ActionDefinition {
|
||||||
|
actionName := "run_browser_agent"
|
||||||
|
if b.customActionName != "" {
|
||||||
|
actionName = b.customActionName
|
||||||
|
}
|
||||||
|
description := "Run a browser agent to achieve a specific goal, for example: 'Go to https://www.google.com and search for 'LocalAI', and tell me what's on the first page'"
|
||||||
|
return types.ActionDefinition{
|
||||||
|
Name: types.ActionDefinitionName(actionName),
|
||||||
|
Description: description,
|
||||||
|
Properties: map[string]jsonschema.Definition{
|
||||||
|
"goal": {
|
||||||
|
Type: jsonschema.String,
|
||||||
|
Description: "The goal for the browser agent to achieve",
|
||||||
|
},
|
||||||
|
"max_attempts": {
|
||||||
|
Type: jsonschema.Number,
|
||||||
|
Description: "Maximum number of attempts the agent can make (optional)",
|
||||||
|
},
|
||||||
|
"max_no_action_attempts": {
|
||||||
|
Type: jsonschema.Number,
|
||||||
|
Description: "Maximum number of attempts without taking an action (optional)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Required: []string{"goal"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BrowserAgentRunner) Plannable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// BrowserAgentRunnerConfigMeta returns the metadata for Browser Agent Runner action configuration fields
|
||||||
|
func BrowserAgentRunnerConfigMeta() []config.Field {
|
||||||
|
return []config.Field{
|
||||||
|
{
|
||||||
|
Name: "baseURL",
|
||||||
|
Label: "Base URL",
|
||||||
|
Type: config.FieldTypeText,
|
||||||
|
Required: false,
|
||||||
|
HelpText: "Base URL of the LocalOperator API",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "customActionName",
|
||||||
|
Label: "Custom Action Name",
|
||||||
|
Type: config.FieldTypeText,
|
||||||
|
HelpText: "Custom name for this action",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -444,7 +444,7 @@ func (a *App) ExecuteAction(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
|||||||
actionName := c.Params("name")
|
actionName := c.Params("name")
|
||||||
|
|
||||||
xlog.Debug("Executing action", "action", actionName, "config", payload.Config, "params", payload.Params)
|
xlog.Debug("Executing action", "action", actionName, "config", payload.Config, "params", payload.Params)
|
||||||
a, err := services.Action(actionName, "", payload.Config, pool)
|
a, err := services.Action(actionName, "", payload.Config, pool, map[string]string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Error("Error creating action", "error", err)
|
xlog.Error("Error creating action", "error", err)
|
||||||
return errorJSONMessage(c, err.Error())
|
return errorJSONMessage(c, err.Error())
|
||||||
|
|||||||
Reference in New Issue
Block a user