mirror of
https://github.com/mudler/cogito.git
synced 2026-07-24 02:45:53 -04:00
589ba31b5c
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
244 lines
7.0 KiB
Go
244 lines
7.0 KiB
Go
package cogito
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/mudler/cogito/pkg/xlog"
|
|
"github.com/mudler/cogito/prompt"
|
|
"github.com/mudler/cogito/structures"
|
|
"github.com/sashabaranov/go-openai"
|
|
)
|
|
|
|
type PlanStatus struct {
|
|
Plan structures.Plan
|
|
Tools []ToolStatus
|
|
}
|
|
|
|
var (
|
|
ErrGoalNotAchieved error = errors.New("goal not achieved")
|
|
)
|
|
|
|
// ExtractPlan extracts a plan from a conversation
|
|
// To override the prompt, define a PromptPlanType, PromptReEvaluatePlanType and PromptSubtaskExtractionType
|
|
func ExtractPlan(llm LLM, f Fragment, goal *structures.Goal, opts ...Option) (*structures.Plan, error) {
|
|
o := defaultOptions()
|
|
o.Apply(opts...)
|
|
|
|
// First we ask the LLM to organize subtasks
|
|
prompter := o.prompts.GetPrompt(prompt.PromptPlanType)
|
|
|
|
toolDefs := o.tools.Definitions()
|
|
planOptions := struct {
|
|
Context string
|
|
AdditionalContext string
|
|
Goal *structures.Goal
|
|
Tools []*openai.FunctionDefinition
|
|
FeedbackConversation string
|
|
}{
|
|
Context: f.String(),
|
|
Goal: goal,
|
|
Tools: toolDefs,
|
|
}
|
|
if o.deepContext && f.ParentFragment != nil {
|
|
planOptions.AdditionalContext = f.ParentFragment.AllFragmentsStrings()
|
|
}
|
|
|
|
var feedbackConv *Fragment
|
|
if o.feedbackCallback != nil {
|
|
feedbackConv = o.feedbackCallback()
|
|
planOptions.FeedbackConversation = feedbackConv.String()
|
|
}
|
|
|
|
prompt, err := prompter.Render(planOptions)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to render tool reasoner prompt: %w", err)
|
|
}
|
|
|
|
return applyPlanFromPrompt(llm, o, prompt, feedbackConv)
|
|
}
|
|
|
|
// ExtractPlan extracts a plan from a conversation
|
|
// to override the prompt, define a PromptReEvaluatePlanType and PromptSubtaskExtractionType
|
|
func ReEvaluatePlan(llm LLM, f, subtaskFragment Fragment, goal *structures.Goal, toolStatuses []ToolStatus, subtask string, opts ...Option) (*structures.Plan, error) {
|
|
o := defaultOptions()
|
|
o.Apply(opts...)
|
|
|
|
// First we ask the LLM to organize subtasks
|
|
prompter := o.prompts.GetPrompt(prompt.PromptReEvaluatePlanType)
|
|
|
|
toolDefs := o.tools.Definitions()
|
|
planOptions := struct {
|
|
Context string
|
|
AdditionalContext string
|
|
Subtask string
|
|
SubtaskConversation string
|
|
Goal string
|
|
Tools []*openai.FunctionDefinition
|
|
PastActionHistory []ToolStatus
|
|
FeedbackConversation string
|
|
}{
|
|
Context: f.String(),
|
|
Goal: goal.Goal,
|
|
Subtask: subtask,
|
|
Tools: toolDefs,
|
|
PastActionHistory: toolStatuses,
|
|
SubtaskConversation: subtaskFragment.String(),
|
|
}
|
|
if o.deepContext && f.ParentFragment != nil {
|
|
planOptions.AdditionalContext = f.ParentFragment.AllFragmentsStrings()
|
|
}
|
|
|
|
var feedbackConv *Fragment
|
|
if o.feedbackCallback != nil {
|
|
feedbackConv = o.feedbackCallback()
|
|
planOptions.FeedbackConversation = feedbackConv.String()
|
|
}
|
|
|
|
prompt, err := prompter.Render(planOptions)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to render tool reasoner prompt: %w", err)
|
|
}
|
|
|
|
return applyPlanFromPrompt(llm, o, prompt, feedbackConv)
|
|
}
|
|
|
|
func applyPlanFromPrompt(llm LLM, o *Options, planPrompt string, feedbackConv *Fragment) (*structures.Plan, error) {
|
|
multimedias := []Multimedia{}
|
|
if feedbackConv != nil {
|
|
multimedias = feedbackConv.Multimedia
|
|
}
|
|
planConv := NewEmptyFragment().AddMessage("user", planPrompt, multimedias...)
|
|
reasoningPlan, err := llm.Ask(o.context, planConv)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to ask LLM for plan identification: %w", err)
|
|
}
|
|
|
|
identifiedPlan := reasoningPlan.LastMessage()
|
|
|
|
structure, plan := structures.StructurePlan()
|
|
|
|
prompter := o.prompts.GetPrompt(prompt.PromptSubtaskExtractionType)
|
|
|
|
planOptions := struct {
|
|
Context string
|
|
}{
|
|
Context: identifiedPlan.Content,
|
|
}
|
|
|
|
prompt, err := prompter.Render(planOptions)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to render tool reasoner prompt: %w", err)
|
|
}
|
|
|
|
planConv = NewEmptyFragment().AddMessage("user", prompt)
|
|
|
|
err = planConv.ExtractStructure(o.context, llm, structure)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to extract structure: %w", err)
|
|
}
|
|
|
|
plan.Description = identifiedPlan.Content
|
|
return plan, err
|
|
}
|
|
|
|
// ExecutePlan Executes an already-defined plan with a set of options.
|
|
// To override its prompt, configure PromptPlanExecutionType, PromptPlanType, PromptReEvaluatePlanType and PromptSubtaskExtractionType
|
|
func ExecutePlan(llm LLM, conv Fragment, plan *structures.Plan, goal *structures.Goal, opts ...Option) (Fragment, error) {
|
|
o := defaultOptions()
|
|
o.Apply(opts...)
|
|
|
|
if len(plan.Subtasks) == 0 {
|
|
return NewEmptyFragment(), fmt.Errorf("no subtasks found in plan")
|
|
}
|
|
|
|
xlog.Debug("Executing plan for conversation", "length", len(conv.Messages), "plan", plan.Description, "subtasks", plan.Subtasks)
|
|
|
|
var toolStatuses []ToolStatus
|
|
|
|
conversation := &conv
|
|
|
|
defer func(conversation *Fragment) {
|
|
conversation.Status.Plans = append(conversation.Status.Plans, PlanStatus{
|
|
Plan: *plan,
|
|
Tools: toolStatuses,
|
|
})
|
|
}(conversation)
|
|
|
|
index := 0
|
|
attempts := 1
|
|
for {
|
|
subtask := plan.Subtasks[index]
|
|
|
|
xlog.Debug("Executing subtask", "goal", goal.Goal, "subtask", subtask)
|
|
|
|
prompter := o.prompts.GetPrompt(prompt.PromptPlanExecutionType)
|
|
|
|
subtaskOption := struct {
|
|
Goal string
|
|
Subtask string
|
|
}{
|
|
Goal: goal.Goal,
|
|
Subtask: subtask,
|
|
}
|
|
|
|
prompt, err := prompter.Render(subtaskOption)
|
|
if err != nil {
|
|
return NewEmptyFragment(), fmt.Errorf("failed to render tool reasoner prompt: %w", err)
|
|
}
|
|
|
|
subtaskConv := NewEmptyFragment().AddMessage("user", prompt)
|
|
|
|
subtaskConvResult, err := ExecuteTools(llm, subtaskConv, opts...)
|
|
if err != nil {
|
|
return *conversation, err
|
|
}
|
|
|
|
conversation.Messages = append(conversation.Messages, subtaskConvResult.LastAssistantAndToolMessages()...)
|
|
conversation.Status.Iterations = conversation.Status.Iterations + 1
|
|
conversation.Status.ToolsCalled = append(conversation.Status.ToolsCalled, subtaskConvResult.Status.ToolsCalled...)
|
|
conversation.Status.ToolResults = append(conversation.Status.ToolResults, subtaskConvResult.Status.ToolResults...)
|
|
toolStatuses = append(toolStatuses, subtaskConvResult.Status.ToolResults...)
|
|
|
|
boolean, err := IsGoalAchieved(llm, subtaskConvResult, nil, opts...)
|
|
if err != nil {
|
|
return *conversation, err
|
|
}
|
|
|
|
toolStatuses := []ToolStatus{}
|
|
for i := range conversation.Status.ToolsCalled {
|
|
toolStatuses = append(toolStatuses, conversation.Status.ToolResults[i])
|
|
}
|
|
|
|
if !boolean.Boolean {
|
|
if attempts >= o.maxAttempts {
|
|
if !o.planReEvaluator {
|
|
return *conversation, ErrGoalNotAchieved
|
|
}
|
|
xlog.Debug("All attempts failed, re-evaluating plan")
|
|
plan, err = ReEvaluatePlan(llm, *conversation, subtaskConv, goal, toolStatuses, subtask, opts...)
|
|
if err != nil {
|
|
return *conversation, err
|
|
}
|
|
|
|
// Start again
|
|
index = 0
|
|
attempts = 1
|
|
} else {
|
|
xlog.Debug("Attempt failed to achieve goal, retrying")
|
|
attempts++
|
|
}
|
|
} else {
|
|
xlog.Debug("Goal correctly achieved")
|
|
attempts = 1 // reset attempts
|
|
if len(plan.Subtasks)-1 > index {
|
|
index++
|
|
} else if !(o.infiniteExecution) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return *conversation, nil
|
|
}
|