picoclaw/pkg/agent/evolution_bridge.go

177 lines
4.7 KiB
Go

package agent
import (
"context"
"sync"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/evolution"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers"
)
const evolutionObserverHookName = "evolution-observer"
type evolutionBridge struct {
cfg config.EvolutionConfig
registry *AgentRegistry
runtime *evolution.Runtime
coldPathRunner *evolution.ColdPathRunner
bgCtx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
func newEvolutionBridge(registry *AgentRegistry, cfg *config.Config, provider providers.LLMProvider) (*evolutionBridge, error) {
if cfg == nil {
return nil, nil
}
modelID := ""
if provider != nil {
modelID = provider.GetDefaultModel()
}
runtime, err := evolution.NewRuntime(evolution.RuntimeOptions{
Config: cfg.Evolution,
GeneratorFactory: func(workspace string) evolution.DraftGenerator {
return evolution.NewDraftGeneratorForWorkspace(workspace, provider, modelID)
},
SuccessJudgeFactory: func(workspace string) evolution.SuccessJudge {
return evolution.NewLLMTaskSuccessJudge(provider, modelID, &evolution.HeuristicSuccessJudge{})
},
ApplierFactory: func(workspace string) *evolution.Applier {
return evolution.NewApplier(evolution.NewPaths(workspace, cfg.Evolution.StateDir), nil)
},
})
if err != nil {
return nil, err
}
bgCtx, cancel := context.WithCancel(context.Background())
bridge := &evolutionBridge{
cfg: cfg.Evolution,
registry: registry,
runtime: runtime,
bgCtx: bgCtx,
cancel: cancel,
}
if cfg.Evolution.RunsColdPathAutomatically() {
bridge.coldPathRunner = evolution.NewColdPathRunnerWithErrorHandler(runtime, func(err error) {
logger.WarnCF("agent", "Cold path run failed", map[string]any{
"error": err.Error(),
})
})
}
return bridge, nil
}
func (b *evolutionBridge) Close() error {
if b == nil {
return nil
}
if b.cancel != nil {
b.cancel()
}
var closeErr error
if b.coldPathRunner != nil {
closeErr = b.coldPathRunner.Close()
}
b.wg.Wait()
return closeErr
}
func (b *evolutionBridge) OnEvent(_ context.Context, evt Event) error {
if b == nil || !b.cfg.Enabled || b.runtime == nil {
return nil
}
switch evt.Kind {
case EventKindTurnEnd:
payload, ok := evt.Payload.(TurnEndPayload)
if !ok {
return nil
}
b.handleTurnEndAsync(evt.Meta, payload)
return nil
}
return nil
}
func (b *evolutionBridge) handleTurnEndAsync(meta EventMeta, payload TurnEndPayload) {
if b == nil || b.runtime == nil {
return
}
input := evolution.TurnCaseInput{
Workspace: payload.Workspace,
WorkspaceID: payload.Workspace,
TurnID: meta.TurnID,
SessionKey: meta.SessionKey,
AgentID: meta.AgentID,
Status: string(payload.Status),
UserMessage: payload.UserMessage,
FinalContent: payload.FinalContent,
ToolKinds: append([]string(nil), payload.ToolKinds...),
ToolExecutions: toEvolutionToolExecutions(payload.ToolExecutions),
ActiveSkillNames: append([]string(nil), payload.ActiveSkills...),
AttemptedSkillNames: append([]string(nil), payload.AttemptedSkills...),
FinalSuccessfulPath: append([]string(nil), payload.FinalSuccessfulPath...),
SkillContextSnapshots: toEvolutionSkillContextSnapshots(payload.SkillContextSnapshots),
}
ctx := b.bgCtx
if ctx == nil {
ctx = context.Background()
}
b.wg.Add(1)
go func() {
defer b.wg.Done()
if err := b.runtime.FinalizeTurn(ctx, input); err != nil {
logger.WarnCF("agent", "Evolution finalize turn failed", map[string]any{
"error": err.Error(),
"turn_id": input.TurnID,
"workspace": input.Workspace,
})
return
}
if b.coldPathRunner != nil {
b.coldPathRunner.Trigger(input.Workspace)
}
}()
}
func toEvolutionSkillContextSnapshots(input []SkillContextSnapshot) []evolution.SkillContextSnapshot {
if len(input) == 0 {
return nil
}
out := make([]evolution.SkillContextSnapshot, 0, len(input))
for _, snapshot := range input {
out = append(out, evolution.SkillContextSnapshot{
Sequence: snapshot.Sequence,
Trigger: snapshot.Trigger,
SkillNames: append([]string(nil), snapshot.SkillNames...),
})
}
return out
}
func toEvolutionToolExecutions(input []ToolExecutionRecord) []evolution.ToolExecutionRecord {
if len(input) == 0 {
return nil
}
out := make([]evolution.ToolExecutionRecord, 0, len(input))
for _, record := range input {
out = append(out, evolution.ToolExecutionRecord{
Name: record.Name,
Success: record.Success,
ErrorSummary: record.ErrorSummary,
SkillNames: append([]string(nil), record.SkillNames...),
})
}
return out
}