diff --git a/core/action/state.go b/core/action/state.go index 3e79a3b..5c9d990 100644 --- a/core/action/state.go +++ b/core/action/state.go @@ -25,7 +25,7 @@ type StateAction struct{} // TODO: A special action is then used to let the LLM itself update its memory // periodically during self-processing, and the same action is ALSO exposed // during the conversation to let the user put for example, a new goal to the agent. -type StateResult struct { +type AgentInternalState struct { NowDoing string `json:"doing_now"` DoingNext string `json:"doing_next"` DoneHistory []string `json:"done_history"` @@ -81,7 +81,7 @@ You have a short memory with: %+v ===================== ` -func (c StateResult) String() string { +func (c AgentInternalState) String() string { return fmt.Sprintf( fmtT, c.NowDoing, diff --git a/core/agent/actions.go b/core/agent/actions.go index aec695f..8d58794 100644 --- a/core/agent/actions.go +++ b/core/agent/actions.go @@ -12,7 +12,7 @@ import ( type ActionState struct { ActionCurrentState - Result string + action.ActionResult } type ActionCurrentState struct { diff --git a/core/agent/agent.go b/core/agent/agent.go index d27f48f..be99535 100644 --- a/core/agent/agent.go +++ b/core/agent/agent.go @@ -31,7 +31,7 @@ type Agent struct { context *action.ActionContext currentReasoning string - currentState *action.StateResult + currentState *action.AgentInternalState nextAction Action nextActionParams *action.ActionParams currentConversation Messages @@ -67,7 +67,7 @@ func New(opts ...Option) (*Agent, error) { options: options, client: client, Character: options.character, - currentState: &action.StateResult{}, + currentState: &action.AgentInternalState{}, context: action.NewContext(ctx, cancel), } @@ -239,15 +239,15 @@ func (a *Agent) Memory() RAGDB { return a.options.ragdb } -func (a *Agent) runAction(chosenAction Action, params action.ActionParams) (result string, err error) { - for _, action := range a.systemInternalActions() { - if action.Definition().Name == chosenAction.Definition().Name { - res, err := action.Run(a.actionContext, params) +func (a *Agent) runAction(chosenAction Action, params action.ActionParams) (result action.ActionResult, err error) { + for _, act := range a.systemInternalActions() { + if act.Definition().Name == chosenAction.Definition().Name { + res, err := act.Run(a.actionContext, params) if err != nil { - return "", fmt.Errorf("error running action: %w", err) + return action.ActionResult{}, fmt.Errorf("error running action: %w", err) } - result = res.Result + result = res } } @@ -255,11 +255,11 @@ func (a *Agent) runAction(chosenAction Action, params action.ActionParams) (resu if chosenAction.Definition().Name.Is(action.StateActionName) { // We need to store the result in the state - state := action.StateResult{} + state := action.AgentInternalState{} err = params.Unmarshal(&state) if err != nil { - return "", fmt.Errorf("error unmarshalling state of the agent: %w", err) + return action.ActionResult{}, fmt.Errorf("error unmarshalling state of the agent: %w", err) } // update the current state with the one we just got from the action a.currentState = &state @@ -267,7 +267,7 @@ func (a *Agent) runAction(chosenAction Action, params action.ActionParams) (resu // update the state file if a.options.statefile != "" { if err := a.SaveState(a.options.statefile); err != nil { - return "", err + return action.ActionResult{}, err } } } @@ -445,6 +445,7 @@ func (a *Agent) consumeJob(job *Job, role string) { Role: "assistant", Content: reasoning, }) + job.Result.Conversation = a.currentConversation job.Result.SetResponse(reasoning) job.Result.Finish(nil) return @@ -486,7 +487,8 @@ func (a *Agent) consumeJob(job *Job, role string) { } if !job.Callback(ActionCurrentState{chosenAction, actionParams, reasoning}) { - job.Result.SetResult(ActionState{ActionCurrentState{chosenAction, actionParams, reasoning}, "stopped by callback"}) + job.Result.SetResult(ActionState{ActionCurrentState{chosenAction, actionParams, reasoning}, action.ActionResult{Result: "stopped by callback"}}) + job.Result.Conversation = a.currentConversation job.Result.Finish(nil) return } @@ -512,6 +514,7 @@ func (a *Agent) consumeJob(job *Job, role string) { Content: message.Message, } }() + job.Result.Conversation = a.currentConversation job.Result.SetResponse("decided to initiate a new conversation") job.Result.Finish(nil) return @@ -524,7 +527,7 @@ func (a *Agent) consumeJob(job *Job, role string) { //job.Result.Finish(fmt.Errorf("error running action: %w", err)) //return // make the LLM aware of the error of running the action instead of stopping the job here - result = fmt.Sprintf("Error running tool: %v", err) + result.Result = fmt.Sprintf("Error running tool: %v", err) } stateResult := ActionState{ActionCurrentState{chosenAction, actionParams, reasoning}, result} @@ -549,7 +552,7 @@ func (a *Agent) consumeJob(job *Job, role string) { // result of calling the function a.currentConversation = append(a.currentConversation, openai.ChatCompletionMessage{ Role: openai.ChatMessageRoleTool, - Content: result, + Content: result.Result, Name: chosenAction.Definition().Name.String(), ToolCallID: chosenAction.Definition().Name.String(), }) @@ -561,6 +564,7 @@ func (a *Agent) consumeJob(job *Job, role string) { // to continue using another tool given the result followingAction, followingParams, reasoning, err := a.pickAction(ctx, reEvaluationTemplate, a.currentConversation) if err != nil { + job.Result.Conversation = a.currentConversation job.Result.Finish(fmt.Errorf("error picking action: %w", err)) return } @@ -592,12 +596,15 @@ func (a *Agent) consumeJob(job *Job, role string) { a.currentConversation = append(a.currentConversation, msg) job.Result.SetResponse(msg.Content) + job.Result.Conversation = a.currentConversation job.Result.Finish(nil) return } } } + job.Result.Conversation = a.currentConversation + // At this point can only be a reply action xlog.Info("Computing reply", "agent", a.Character.Name) @@ -605,6 +612,7 @@ func (a *Agent) consumeJob(job *Job, role string) { replyResponse := action.ReplyResponse{} if err := actionParams.Unmarshal(&replyResponse); err != nil { + job.Result.Conversation = a.currentConversation job.Result.Finish(fmt.Errorf("error unmarshalling reply response: %w", err)) return } @@ -628,6 +636,7 @@ func (a *Agent) consumeJob(job *Job, role string) { if a.options.enableHUD { prompt, err := renderTemplate(hudTemplate, a.prepareHUD(), a.systemInternalActions(), reasoning) if err != nil { + job.Result.Conversation = a.currentConversation job.Result.Finish(fmt.Errorf("error renderTemplate: %w", err)) return } @@ -663,6 +672,7 @@ func (a *Agent) consumeJob(job *Job, role string) { } a.currentConversation = append(a.currentConversation, msg) + job.Result.Conversation = a.currentConversation job.Result.SetResponse(msg.Content) job.Result.Finish(nil) return @@ -672,6 +682,7 @@ func (a *Agent) consumeJob(job *Job, role string) { xlog.Debug("Conversation", "conversation", fmt.Sprintf("%+v", a.currentConversation)) msg, err := a.askLLM(ctx, a.currentConversation) if err != nil { + job.Result.Conversation = a.currentConversation job.Result.Finish(err) return } @@ -690,7 +701,7 @@ func (a *Agent) consumeJob(job *Job, role string) { a.currentConversation = append(a.currentConversation, msg) job.Result.SetResponse(msg.Content) xlog.Info("Response from LLM", "response", msg.Content, "agent", a.Character.Name) - + job.Result.Conversation = a.currentConversation job.Result.Finish(nil) } diff --git a/core/agent/jobs.go b/core/agent/jobs.go index 89e3871..5d5c4df 100644 --- a/core/agent/jobs.go +++ b/core/agent/jobs.go @@ -23,10 +23,11 @@ type Job struct { type JobResult struct { sync.Mutex // The result of a job - State []ActionState - Response string - Error error - ready chan bool + State []ActionState + Conversation []openai.ChatCompletionMessage + Response string + Error error + ready chan bool } type JobOption func(*Job) diff --git a/core/agent/state.go b/core/agent/state.go index e0c6389..51e011b 100644 --- a/core/agent/state.go +++ b/core/agent/state.go @@ -14,10 +14,10 @@ import ( // all information that should be displayed to the LLM // in the prompts type PromptHUD struct { - Character Character `json:"character"` - CurrentState action.StateResult `json:"current_state"` - PermanentGoal string `json:"permanent_goal"` - ShowCharacter bool `json:"show_character"` + Character Character `json:"character"` + CurrentState action.AgentInternalState `json:"current_state"` + PermanentGoal string `json:"permanent_goal"` + ShowCharacter bool `json:"show_character"` } type Character struct { @@ -42,7 +42,7 @@ func Load(path string) (*Character, error) { return &c, nil } -func (a *Agent) State() action.StateResult { +func (a *Agent) State() action.AgentInternalState { return *a.currentState } diff --git a/go.mod b/go.mod index c2851b5..b4d128a 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,13 @@ require ( github.com/chasefleming/elem-go v0.25.0 github.com/donseba/go-htmx v1.8.0 github.com/dslipak/pdf v0.0.2 + github.com/eritikass/githubmarkdownconvertergo v0.1.10 github.com/go-telegram/bot v1.2.1 github.com/gofiber/fiber/v2 v2.52.4 github.com/gofiber/template/html/v2 v2.1.1 github.com/google/go-github/v61 v61.0.0 github.com/onsi/ginkgo/v2 v2.15.0 github.com/onsi/gomega v1.31.1 - github.com/oxffaa/gopher-parse-sitemap v0.0.0-20191021113419-005d2eb1def4 github.com/philippgille/chromem-go v0.5.0 github.com/sashabaranov/go-openai v1.18.3 github.com/slack-go/slack v0.12.5 @@ -23,6 +23,7 @@ require ( github.com/traefik/yaegi v0.16.1 github.com/valyala/fasthttp v1.52.0 jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 + mvdan.cc/xurls/v2 v2.6.0 ) require ( @@ -33,7 +34,6 @@ require ( github.com/antchfx/xmlquery v1.3.17 // indirect github.com/antchfx/xpath v1.2.4 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect - github.com/eritikass/githubmarkdownconvertergo v0.1.10 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -61,11 +61,11 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.16.1 // indirect + golang.org/x/crypto v0.30.0 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.28.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index d4f488f..44384cd 100644 --- a/go.sum +++ b/go.sum @@ -112,8 +112,6 @@ github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= -github.com/oxffaa/gopher-parse-sitemap v0.0.0-20191021113419-005d2eb1def4 h1:2vmb32OdDhjZf2ETGDlr9n8RYXx7c+jXPxMiPbwnA+8= -github.com/oxffaa/gopher-parse-sitemap v0.0.0-20191021113419-005d2eb1def4/go.mod h1:2JQx4jDHmWrbABvpOayg/+OTU6ehN0IyK2EHzceXpJo= github.com/philippgille/chromem-go v0.5.0 h1:bryX0F3N6jnN/21iBd8i2/k9EzPTZn3nyiqAti19si8= github.com/philippgille/chromem-go v0.5.0/go.mod h1:hTd+wGEm/fFPQl7ilfCwQXkgEUxceYh86iIdoKMolPo= github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw= @@ -155,8 +153,8 @@ go.starlark.net v0.0.0-20230302034142-4b1e35fe2254/go.mod h1:jxU+3+j+71eXOW14274 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -175,8 +173,8 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -197,8 +195,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -213,8 +211,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -223,8 +221,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -259,3 +257,5 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 h1:6YFJoB+0fUH6X3xU/G2tQqCYg+PkGtnZ5nMR5rpw72g= jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:OxvTsCwKosqQ1q7B+8FwXqg4rKZ/UG9dUW+g/VL2xH4= +mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI= +mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk= diff --git a/services/actions/genimage.go b/services/actions/genimage.go index f17ea87..13fe360 100644 --- a/services/actions/genimage.go +++ b/services/actions/genimage.go @@ -9,6 +9,10 @@ import ( "github.com/sashabaranov/go-openai/jsonschema" ) +const ( + MetadataImages = "images_url" +) + func NewGenImage(config map[string]string) *GenImageAction { defaultConfig := openai.DefaultConfig(config["apiKey"]) defaultConfig.BaseURL = config["apiURL"] @@ -65,7 +69,7 @@ func (a *GenImageAction) Run(ctx context.Context, params action.ActionParams) (a return action.ActionResult{ Result: fmt.Sprintf("The image was generated and available at: %s", resp.Data[0].URL), Metadata: map[string]interface{}{ - "url": resp.Data[0].URL, + MetadataImages: []string{resp.Data[0].URL}, }}, nil } diff --git a/services/actions/search.go b/services/actions/search.go index 15d136b..3fe963e 100644 --- a/services/actions/search.go +++ b/services/actions/search.go @@ -8,6 +8,11 @@ import ( "github.com/mudler/LocalAgent/core/action" "github.com/sashabaranov/go-openai/jsonschema" "github.com/tmc/langchaingo/tools/duckduckgo" + "mvdan.cc/xurls/v2" +) + +const ( + MetadataUrls = "urls" ) func NewSearch(config map[string]string) *SearchAction { @@ -50,7 +55,11 @@ func (a *SearchAction) Run(ctx context.Context, params action.ActionParams) (act return action.ActionResult{}, err } - return action.ActionResult{Result: res}, nil + + rxStrict := xurls.Strict() + urls := rxStrict.FindAllString(res, -1) + + return action.ActionResult{Result: res, Metadata: map[string]interface{}{MetadataUrls: urls}}, nil } func (a *SearchAction) Definition() action.ActionDefinition { diff --git a/services/connectors/slack.go b/services/connectors/slack.go index a524cb2..73cf741 100644 --- a/services/connectors/slack.go +++ b/services/connectors/slack.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/mudler/LocalAgent/pkg/xlog" + "github.com/mudler/LocalAgent/services/actions" "github.com/sashabaranov/go-openai" "github.com/mudler/LocalAgent/core/agent" @@ -55,6 +56,35 @@ func cleanUpUsernameFromMessage(message string, b *slack.AuthTestResponse) strin return cleaned } +func generateAttachmentsFromJobResponse(j *agent.JobResult) (attachments []slack.Attachment) { + for _, state := range j.State { + // coming from the search action + if urls, exists := state.Metadata[actions.MetadataUrls]; exists { + for _, url := range urls.([]string) { + attachment := slack.Attachment{ + Title: "URL", + TitleLink: url, + Text: url, + } + attachments = append(attachments, attachment) + } + } + + // coming from the gen image actions + if imagesUrls, exists := state.Metadata[actions.MetadataImages]; exists { + for _, url := range imagesUrls.([]string) { + attachment := slack.Attachment{ + Title: "Image", + TitleLink: url, + ImageURL: url, + } + attachments = append(attachments, attachment) + } + } + } + return +} + func (t *Slack) Start(a *agent.Agent) { api := slack.New( t.botToken, @@ -129,6 +159,7 @@ func (t *Slack) Start(a *agent.Agent) { _, _, err = api.PostMessage(ev.Channel, slack.MsgOptionText(res.Response, true), slack.MsgOptionPostMessageParameters(postMessageParams), + slack.MsgOptionAttachments(generateAttachmentsFromJobResponse(res)...), // slack.MsgOptionTS(ts), ) if err != nil { @@ -194,10 +225,12 @@ func (t *Slack) Start(a *agent.Agent) { slack.MsgOptionPostMessageParameters( postMessageParams, ), + slack.MsgOptionAttachments(generateAttachmentsFromJobResponse(res)...), slack.MsgOptionTS(ts)) } else { _, _, err = api.PostMessage(ev.Channel, slack.MsgOptionText(res.Response, true), + slack.MsgOptionAttachments(generateAttachmentsFromJobResponse(res)...), slack.MsgOptionPostMessageParameters( postMessageParams, ),