-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathautoimprove.go
More file actions
177 lines (157 loc) · 6.02 KB
/
Copy pathautoimprove.go
File metadata and controls
177 lines (157 loc) · 6.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package cogito
import (
"fmt"
"strings"
"github.com/mudler/cogito/prompt"
"github.com/mudler/xlog"
)
// AutoImproveState holds the state for the autoimproving feature.
// The caller owns this struct and passes a pointer to ExecuteTools via WithAutoImproveState.
// The state is mutated in-place through the pointer across executions.
type AutoImproveState struct {
SystemPrompt string `json:"system_prompt"`
ReviewCount int `json:"review_count"`
}
// editSystemPromptArgs captures the result from the edit_system_prompt tool.
type editSystemPromptArgs struct {
NewSystemPrompt string `json:"new_system_prompt"`
Reasoning string `json:"reasoning"`
}
// editSystemPromptRunner captures the tool result via a pointer.
type editSystemPromptRunner struct {
captured *editSystemPromptArgs
}
func (r *editSystemPromptRunner) Run(args editSystemPromptArgs) (string, any, error) {
*r.captured = args
return "System prompt updated.", nil, nil
}
// newEditSystemPromptTool creates the edit_system_prompt tool with pointer capture.
func newEditSystemPromptTool(captured *editSystemPromptArgs) ToolDefinitionInterface {
return &ToolDefinition[editSystemPromptArgs]{
ToolRunner: &editSystemPromptRunner{captured: captured},
InputArguments: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"new_system_prompt": map[string]interface{}{
"type": "string",
"description": "The new or updated system prompt that will guide the agent in future executions. This should incorporate lessons learned from the current conversation.",
},
"reasoning": map[string]interface{}{
"type": "string",
"description": "Your reasoning for why the system prompt should be changed.",
},
},
"required": []string{"new_system_prompt", "reasoning"},
},
Name: "edit_system_prompt",
Description: "Edit the system prompt that guides the agent's behavior. Use this to improve the agent based on the current conversation.",
}
}
// executeAutoImproveReview runs the review step after the main tool loop.
// It builds a review fragment that includes the actual conversation messages
// so the reviewer LLM has full context, then calls ExecuteTools with the
// edit_system_prompt tool. Mutates state in-place. Non-fatal on failure.
func executeAutoImproveReview(llm LLM, f Fragment, state *AutoImproveState, o *Options) {
reviewerLLM := llm
if o.autoImproveReviewerLLM != nil {
reviewerLLM = o.autoImproveReviewerLLM
}
// Render the review system prompt via the prompt system
systemPrompter := o.prompts.GetPrompt(prompt.PromptAutoImproveReviewSystemType)
reviewSystemPrompt, err := systemPrompter.Render(struct {
CurrentPrompt string
}{
CurrentPrompt: state.SystemPrompt,
})
if err != nil {
xlog.Warn("[autoimprove] Failed to render review system prompt", "error", err)
return
}
// Render the review user prompt via the prompt system
userPrompter := o.prompts.GetPrompt(prompt.PromptAutoImproveReviewUserType)
reviewUserPrompt, err := userPrompter.Render(struct {
ReviewNumber int
Conversation string
ToolResults string
}{
ReviewNumber: state.ReviewCount + 1,
Conversation: formatConversation(f),
ToolResults: formatToolResults(f),
})
if err != nil {
xlog.Warn("[autoimprove] Failed to render review user prompt", "error", err)
return
}
// Build the review fragment: system prompt, then the actual conversation
// messages, then a user message asking for the review.
// This gives the reviewer LLM the full conversation context.
reviewFragment := NewEmptyFragment().
AddMessage(SystemMessageRole, reviewSystemPrompt)
// Append all conversation messages so the reviewer sees the real exchange
reviewFragment.Messages = append(reviewFragment.Messages, f.Messages...)
// Append the review instruction as the final user message
reviewFragment = reviewFragment.AddMessage(UserMessageRole, reviewUserPrompt)
// Create the edit tool with pointer capture
var captured editSystemPromptArgs
editTool := newEditSystemPromptTool(&captured)
// Build safe options allowlist — no callbacks, no guidelines, no autoimprove
reviewOpts := []Option{
WithContext(o.context),
WithMaxRetries(o.maxRetries),
WithTools(editTool),
WithIterations(1),
DisableSinkState,
}
_, err = ExecuteTools(reviewerLLM, reviewFragment, reviewOpts...)
if err != nil {
xlog.Warn("[autoimprove] Review step failed, state unchanged", "error", err)
return
}
// Increment review count regardless of whether the tool was called
state.ReviewCount++
// If the tool was called, update the system prompt
if captured.NewSystemPrompt != "" {
state.SystemPrompt = captured.NewSystemPrompt
xlog.Debug("[autoimprove] System prompt updated",
"reasoning", captured.Reasoning,
"reviewCount", state.ReviewCount)
} else {
xlog.Debug("[autoimprove] Reviewer did not call edit_system_prompt tool, prompt unchanged",
"reviewCount", state.ReviewCount)
}
}
// formatConversation renders conversation messages into a readable text format
// for inclusion in the review prompt.
func formatConversation(f Fragment) string {
var sb strings.Builder
for _, msg := range f.Messages {
switch msg.Role {
case "user":
sb.WriteString(fmt.Sprintf("**User**: %s\n\n", msg.Content))
case "assistant":
sb.WriteString(fmt.Sprintf("**Assistant**: %s\n\n", msg.Content))
case "tool":
sb.WriteString(fmt.Sprintf("**Tool Result**: %s\n\n", msg.Content))
case "system":
// Skip system messages in the conversation rendering
}
if len(msg.ToolCalls) > 0 {
for _, tc := range msg.ToolCalls {
sb.WriteString(fmt.Sprintf("**Tool Call**: %s(%s)\n\n", tc.Function.Name, tc.Function.Arguments))
}
}
}
return sb.String()
}
// formatToolResults renders tool execution results from the fragment status.
func formatToolResults(f Fragment) string {
if f.Status == nil || len(f.Status.ToolResults) == 0 {
return ""
}
var sb strings.Builder
for _, tr := range f.Status.ToolResults {
sb.WriteString(fmt.Sprintf("- **%s**: executed=%v, result=%s\n",
tr.Name, tr.Executed, tr.Result))
}
return sb.String()
}