control show of character,global callbacks, re-add replies during internal runs to self-stop
This commit is contained in:
@@ -4,6 +4,9 @@ import (
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// NewIntention creates a new intention action
|
||||
// The inention action is special as it tries to identify
|
||||
// a tool to use and a reasoning over to use it
|
||||
func NewIntention(s ...string) *IntentAction {
|
||||
return &IntentAction{tools: s}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import (
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// NewReasoning creates a new reasoning action
|
||||
// The reasoning action is special as it tries to force the LLM
|
||||
// to think about what to do next
|
||||
func NewReasoning() *ReasoningAction {
|
||||
return &ReasoningAction{}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ type decisionResult struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// decision forces the agent to take on of the available actions
|
||||
// decision forces the agent to take one of the available actions
|
||||
func (a *Agent) decision(
|
||||
ctx context.Context,
|
||||
conversation []openai.ChatCompletionMessage,
|
||||
@@ -145,20 +145,20 @@ func (a *Agent) generateParameters(ctx context.Context, pickTemplate string, act
|
||||
|
||||
return a.decision(ctx,
|
||||
conversation,
|
||||
a.systemActions().ToTools(),
|
||||
act.Definition().Name)
|
||||
a.systemInternalActions().ToTools(),
|
||||
openai.ToolChoice{
|
||||
Type: openai.ToolTypeFunction,
|
||||
Function: openai.ToolFunction{Name: act.Definition().Name.String()},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (a *Agent) systemInternalActions() Actions {
|
||||
if a.options.enableHUD {
|
||||
return append(a.options.userActions, action.NewState())
|
||||
return append(a.options.userActions, action.NewState(), action.NewReply())
|
||||
}
|
||||
|
||||
return append(a.options.userActions)
|
||||
}
|
||||
|
||||
func (a *Agent) systemActions() Actions {
|
||||
return append(a.systemInternalActions(), action.NewReply())
|
||||
return append(a.options.userActions, action.NewReply())
|
||||
}
|
||||
|
||||
func (a *Agent) prepareHUD() PromptHUD {
|
||||
@@ -166,10 +166,11 @@ func (a *Agent) prepareHUD() PromptHUD {
|
||||
Character: a.Character,
|
||||
CurrentState: *a.currentState,
|
||||
PermanentGoal: a.options.permanentGoal,
|
||||
ShowCharacter: a.options.showCharacter,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) prepareConversationParse(templ string, messages []openai.ChatCompletionMessage, canReply bool, reasoning string) ([]openai.ChatCompletionMessage, Actions, error) {
|
||||
func (a *Agent) prepareConversationParse(templ string, messages []openai.ChatCompletionMessage, reasoning string) ([]openai.ChatCompletionMessage, Actions, error) {
|
||||
// prepare the prompt
|
||||
prompt := bytes.NewBuffer([]byte{})
|
||||
|
||||
@@ -178,10 +179,7 @@ func (a *Agent) prepareConversationParse(templ string, messages []openai.ChatCom
|
||||
return nil, []Action{}, err
|
||||
}
|
||||
|
||||
actions := a.systemActions()
|
||||
if !canReply {
|
||||
actions = a.systemInternalActions()
|
||||
}
|
||||
actions := a.systemInternalActions()
|
||||
|
||||
// Get all the actions definitions
|
||||
definitions := []action.ActionDefinition{}
|
||||
@@ -225,7 +223,7 @@ func (a *Agent) prepareConversationParse(templ string, messages []openai.ChatCom
|
||||
}
|
||||
|
||||
// pickAction picks an action based on the conversation
|
||||
func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.ChatCompletionMessage, canReply bool) (Action, string, error) {
|
||||
func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.ChatCompletionMessage) (Action, string, error) {
|
||||
c := messages
|
||||
|
||||
// prepare the prompt
|
||||
@@ -236,10 +234,7 @@ func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
actions := a.systemActions()
|
||||
if !canReply {
|
||||
actions = a.systemInternalActions()
|
||||
}
|
||||
actions := a.systemInternalActions()
|
||||
|
||||
// Get all the actions definitions
|
||||
definitions := []action.ActionDefinition{}
|
||||
|
||||
@@ -111,7 +111,7 @@ func (a *Agent) StopAction() {
|
||||
// It discards any other computation.
|
||||
func (a *Agent) Ask(opts ...JobOption) *JobResult {
|
||||
a.StopAction()
|
||||
j := NewJob(opts...)
|
||||
j := NewJob(append(opts, WithReasoningCallback(a.options.reasoningCallback), WithResultCallback(a.options.resultCallback))...)
|
||||
// fmt.Println("Job created", text)
|
||||
a.jobQueue <- j
|
||||
return j.Result.WaitResult()
|
||||
@@ -138,7 +138,7 @@ func (a *Agent) Stop() {
|
||||
}
|
||||
|
||||
func (a *Agent) runAction(chosenAction Action, decisionResult *decisionResult) (result string, err error) {
|
||||
for _, action := range a.systemActions() {
|
||||
for _, action := range a.systemInternalActions() {
|
||||
if action.Definition().Name == chosenAction.Definition().Name {
|
||||
if result, err = action.Run(decisionResult.actionParams); err != nil {
|
||||
return "", fmt.Errorf("error running action: %w", err)
|
||||
@@ -218,7 +218,7 @@ func (a *Agent) consumeJob(job *Job, role string) {
|
||||
a.nextAction = nil
|
||||
} else {
|
||||
var err error
|
||||
chosenAction, reasoning, err = a.pickAction(ctx, pickTemplate, a.currentConversation, true)
|
||||
chosenAction, reasoning, err = a.pickAction(ctx, pickTemplate, a.currentConversation)
|
||||
if err != nil {
|
||||
job.Result.Finish(err)
|
||||
return
|
||||
@@ -231,12 +231,21 @@ func (a *Agent) consumeJob(job *Job, role string) {
|
||||
return
|
||||
}
|
||||
|
||||
if a.options.debugMode {
|
||||
fmt.Println("===> Generating parameters for", chosenAction.Definition().Name)
|
||||
}
|
||||
|
||||
params, err := a.generateParameters(ctx, pickTemplate, chosenAction, a.currentConversation, reasoning)
|
||||
if err != nil {
|
||||
job.Result.Finish(fmt.Errorf("error generating action's parameters: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if a.options.debugMode {
|
||||
fmt.Println("===> Generated parameters for", chosenAction.Definition().Name)
|
||||
fmt.Println(params.actionParams.String())
|
||||
}
|
||||
|
||||
if params.actionParams == nil {
|
||||
job.Result.Finish(fmt.Errorf("no parameters"))
|
||||
return
|
||||
@@ -282,7 +291,7 @@ func (a *Agent) consumeJob(job *Job, role string) {
|
||||
|
||||
// given the result, we can now ask OpenAI to complete the conversation or
|
||||
// to continue using another tool given the result
|
||||
followingAction, reasoning, err := a.pickAction(ctx, reEvaluationTemplate, a.currentConversation, !selfEvaluation)
|
||||
followingAction, reasoning, err := a.pickAction(ctx, reEvaluationTemplate, a.currentConversation)
|
||||
if err != nil {
|
||||
job.Result.Finish(fmt.Errorf("error picking action: %w", err))
|
||||
return
|
||||
@@ -324,16 +333,25 @@ func (a *Agent) consumeJob(job *Job, role string) {
|
||||
msg := resp.Choices[0].Message
|
||||
|
||||
a.currentConversation = append(a.currentConversation, msg)
|
||||
job.Result.SetResponse(msg.Content)
|
||||
job.Result.Finish(nil)
|
||||
}
|
||||
|
||||
func (a *Agent) periodicallyRun() {
|
||||
|
||||
if a.options.debugMode {
|
||||
fmt.Println("START -- Periodically run is starting")
|
||||
}
|
||||
|
||||
if len(a.CurrentConversation()) != 0 {
|
||||
// Here the LLM could decide to store some part of the conversation too in the memory
|
||||
evaluateMemory := NewJob(
|
||||
WithText(
|
||||
`Evaluate the current conversation and decide if we need to store some relevant informations from it`,
|
||||
))
|
||||
),
|
||||
WithReasoningCallback(a.options.reasoningCallback),
|
||||
WithResultCallback(a.options.resultCallback),
|
||||
)
|
||||
a.consumeJob(evaluateMemory, SystemRole)
|
||||
|
||||
a.ResetConversation()
|
||||
@@ -345,8 +363,18 @@ func (a *Agent) periodicallyRun() {
|
||||
// - asking the agent to do something else based on the result
|
||||
|
||||
// whatNext := NewJob(WithText("Decide what to do based on the state"))
|
||||
whatNext := NewJob(WithText("Decide what to based on the goal and the persistent goal."))
|
||||
whatNext := NewJob(
|
||||
WithText("Decide what to based on the goal and the persistent goal."),
|
||||
WithReasoningCallback(a.options.reasoningCallback),
|
||||
WithResultCallback(a.options.resultCallback),
|
||||
)
|
||||
a.consumeJob(whatNext, SystemRole)
|
||||
a.ResetConversation()
|
||||
|
||||
if a.options.debugMode {
|
||||
fmt.Println("STOP -- Periodically run is done")
|
||||
}
|
||||
// Save results from state
|
||||
|
||||
// a.ResetConversation()
|
||||
|
||||
|
||||
@@ -63,6 +63,25 @@ func (a *TestAction) Definition() action.ActionDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
type FakeStoreResultAction struct {
|
||||
TestAction
|
||||
}
|
||||
|
||||
func (a *FakeStoreResultAction) Definition() action.ActionDefinition {
|
||||
return action.ActionDefinition{
|
||||
Name: "store_results",
|
||||
Description: "store results permanently. Use this tool after you have a result you want to keep.",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"term": {
|
||||
Type: jsonschema.String,
|
||||
Description: "What to store permanently",
|
||||
},
|
||||
},
|
||||
|
||||
Required: []string{"term"},
|
||||
}
|
||||
}
|
||||
|
||||
type FakeInternetAction struct {
|
||||
TestAction
|
||||
}
|
||||
@@ -167,16 +186,43 @@ var _ = Describe("Agent test", func() {
|
||||
Expect(agent.State().Goal).To(ContainSubstring("guitar"), fmt.Sprint(agent.State()))
|
||||
})
|
||||
|
||||
It("it automatically performs things in the background", func() {
|
||||
FIt("it automatically performs things in the background", func() {
|
||||
agent, err := New(
|
||||
WithLLMAPIURL(apiModel),
|
||||
WithModel(testModel),
|
||||
EnableHUD,
|
||||
DebugMode,
|
||||
EnableStandaloneJob,
|
||||
WithActions(&FakeInternetAction{TestAction{response: []string{"Roma, Venice, Milan"}}}),
|
||||
WithAgentReasoningCallback(func(state ActionCurrentState) bool {
|
||||
fmt.Println("Reasoning", state)
|
||||
return true
|
||||
}),
|
||||
WithAgentResultCallback(func(state ActionState) {
|
||||
fmt.Println("Reasoning", state.Reasoning)
|
||||
fmt.Println("Action", state.Action)
|
||||
fmt.Println("Result", state.Result)
|
||||
}),
|
||||
WithActions(
|
||||
&FakeInternetAction{
|
||||
TestAction{
|
||||
response: []string{
|
||||
"Major cities in italy: Roma, Venice, Milan",
|
||||
"In rome it's 30C today, it's sunny, and humidity is at 98%",
|
||||
"In venice it's very hot today, it is 45C and the humidity is at 200%",
|
||||
"In milan it's very cold today, it is 2C and the humidity is at 10%",
|
||||
},
|
||||
},
|
||||
},
|
||||
&FakeStoreResultAction{
|
||||
TestAction{
|
||||
response: []string{
|
||||
"Result permanently stored",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
WithRandomIdentity(),
|
||||
WithPermanentGoal("get the weather of all the cities in italy"),
|
||||
WithPermanentGoal("get the weather of all the cities in italy and store the results"),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
go agent.Run()
|
||||
@@ -184,8 +230,17 @@ var _ = Describe("Agent test", func() {
|
||||
|
||||
Eventually(func() string {
|
||||
fmt.Println(agent.State())
|
||||
return agent.State().NowDoing
|
||||
}, "4m", "10s").Should(ContainSubstring("weather"), fmt.Sprint(agent.State()))
|
||||
return agent.State().Goal
|
||||
}, "10m", "10s").Should(ContainSubstring("weather"), fmt.Sprint(agent.State()))
|
||||
|
||||
Eventually(func() string {
|
||||
fmt.Println(agent.State())
|
||||
return agent.State().String()
|
||||
}, "10m", "10s").Should(ContainSubstring("store"), fmt.Sprint(agent.State()))
|
||||
Eventually(func() string {
|
||||
fmt.Println(agent.State())
|
||||
return agent.State().String()
|
||||
}, "10m", "10s").Should(ContainSubstring("inform"), fmt.Sprint(agent.State()))
|
||||
|
||||
// result := agent.Ask(
|
||||
// WithText("Update your goals such as you want to learn to play the guitar"),
|
||||
|
||||
@@ -21,6 +21,7 @@ type JobResult struct {
|
||||
sync.Mutex
|
||||
// The result of a job
|
||||
State []ActionState
|
||||
Response string
|
||||
Error error
|
||||
ready chan bool
|
||||
}
|
||||
@@ -104,6 +105,14 @@ func (j *JobResult) Finish(e error) {
|
||||
close(j.ready)
|
||||
}
|
||||
|
||||
// SetResult sets the result of a job
|
||||
func (j *JobResult) SetResponse(response string) {
|
||||
j.Lock()
|
||||
defer j.Unlock()
|
||||
|
||||
j.Response = response
|
||||
}
|
||||
|
||||
// WaitResult waits for the result of a job
|
||||
func (j *JobResult) WaitResult() *JobResult {
|
||||
<-j.ready
|
||||
|
||||
@@ -18,12 +18,16 @@ type options struct {
|
||||
randomIdentityGuidance string
|
||||
randomIdentity bool
|
||||
userActions Actions
|
||||
enableHUD, standaloneJob bool
|
||||
enableHUD, standaloneJob, showCharacter bool
|
||||
debugMode bool
|
||||
characterfile string
|
||||
statefile string
|
||||
context context.Context
|
||||
permanentGoal string
|
||||
|
||||
// callbacks
|
||||
reasoningCallback func(ActionCurrentState) bool
|
||||
resultCallback func(ActionState)
|
||||
}
|
||||
|
||||
func defaultOptions() *options {
|
||||
@@ -69,6 +73,11 @@ var EnableStandaloneJob = func(o *options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var EnableCharacter = func(o *options) error {
|
||||
o.showCharacter = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func WithLLMAPIURL(url string) Option {
|
||||
return func(o *options) error {
|
||||
o.LLMAPI.APIURL = url
|
||||
@@ -97,6 +106,20 @@ func WithContext(ctx context.Context) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithAgentReasoningCallback(cb func(ActionCurrentState) bool) Option {
|
||||
return func(o *options) error {
|
||||
o.reasoningCallback = cb
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithAgentResultCallback(cb func(ActionState)) Option {
|
||||
return func(o *options) error {
|
||||
o.resultCallback = cb
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithModel(model string) Option {
|
||||
return func(o *options) error {
|
||||
o.LLMAPI.Model = model
|
||||
|
||||
@@ -17,6 +17,7 @@ type PromptHUD struct {
|
||||
Character Character `json:"character"`
|
||||
CurrentState action.StateResult `json:"current_state"`
|
||||
PermanentGoal string `json:"permanent_goal"`
|
||||
ShowCharacter bool `json:"show_character"`
|
||||
}
|
||||
|
||||
type Character struct {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package agent
|
||||
|
||||
const hudTemplate = `{{with .HUD }}You have a character and your replies and actions might be influenced by it.
|
||||
const hudTemplate = `{{with .HUD }}{{if .ShowCharacter}}You have a character and your replies and actions might be influenced by it.
|
||||
{{if .Character.Name}}Name: {{.Character.Name}}
|
||||
{{end}}{{if .Character.Age}}Age: {{.Character.Age}}
|
||||
{{end}}{{if .Character.Occupation}}Occupation: {{.Character.Occupation}}
|
||||
{{end}}{{if .Character.Hobbies}}Hobbies: {{.Character.Hobbies}}
|
||||
{{end}}{{if .Character.MusicTaste}}Music taste: {{.Character.MusicTaste}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
This is your current state:
|
||||
NowDoing: {{if .CurrentState.NowDoing}}{{.CurrentState.NowDoing}}{{else}}Nothing{{end}}
|
||||
@@ -16,20 +17,13 @@ Your current goal is: {{if .CurrentState.Goal}}{{.CurrentState.Goal}}{{else}}Not
|
||||
You have done: {{range .CurrentState.DoneHistory}}{{.}} {{end}}
|
||||
You have a short memory with: {{range .CurrentState.Memories}}{{.}} {{end}}{{end}}`
|
||||
|
||||
const pickSelfTemplate = `
|
||||
You can take any of the following tools:
|
||||
const pickSelfTemplate = `You can take any of the following tools:
|
||||
|
||||
{{range .Actions -}}
|
||||
- {{.Name}}: {{.Description }}
|
||||
{{ end }}
|
||||
|
||||
{{if .Messages}}
|
||||
Consider the text below, decide which action to take and explain the detailed reasoning behind it.
|
||||
|
||||
{{range .Messages -}}
|
||||
{{.Role}}{{if .FunctionCall}}(tool_call){{.FunctionCall}}{{end}}: {{if .FunctionCall}}{{.FunctionCall}}{{else if .ToolCalls -}}{{range .ToolCalls -}}{{.Name}} called with {{.Arguments}}{{end}}{{ else }}{{.Content -}}{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
To finish your session, use the "reply" tool with your answer.
|
||||
|
||||
Act like as a fully autonomous smart AI agent having a character, the character and your state is defined in the message above.
|
||||
You are now self-evaluating what to do next based on the state in the previous message.
|
||||
@@ -53,12 +47,6 @@ You can take any of the following tools:
|
||||
To answer back to the user, use the "reply" tool.
|
||||
Given the text below, decide which action to take and explain the detailed reasoning behind it. For answering without picking a choice, reply with 'none'.
|
||||
|
||||
{{if .Messages}}
|
||||
{{range .Messages -}}
|
||||
{{.Role}}{{if .FunctionCall}}(tool_call){{.FunctionCall}}{{end}}: {{if .FunctionCall}}{{.FunctionCall}}{{else if .ToolCalls -}}{{range .ToolCalls -}}{{.Name}} called with {{.Arguments}}{{end}}{{ else }}{{.Content -}}{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .Reasoning}}Reasoning: {{.Reasoning}}{{end}}
|
||||
`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user