feat: add code formatting guide and improve code readability across multiple files
This commit is contained in:
parent
375e64bba5
commit
aca03f5df7
3 changed files with 154 additions and 52 deletions
|
|
@ -50,6 +50,8 @@ const (
|
||||||
|
|
||||||
// modelTagDescriptions provides LLM-readable explanations of each known tag,
|
// modelTagDescriptions provides LLM-readable explanations of each known tag,
|
||||||
// injected at runtime into the tool description to guide model selection.
|
// injected at runtime into the tool description to guide model selection.
|
||||||
|
//
|
||||||
|
//nolint:unused // Reserved for future use in dynamic tool descriptions
|
||||||
var modelTagDescriptions = map[string]string{
|
var modelTagDescriptions = map[string]string{
|
||||||
ModelTagVision: "can analyze images and screenshots (multimodal input)",
|
ModelTagVision: "can analyze images and screenshots (multimodal input)",
|
||||||
ModelTagImageGen: "can generate images from text descriptions (e.g. DALL-E, Stable Diffusion)",
|
ModelTagImageGen: "can generate images from text descriptions (e.g. DALL-E, Stable Diffusion)",
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,9 @@ func (t *TeamTool) maybeRunAutoReviewer(
|
||||||
cnf, err := t.cfg.GetModelConfig(reviewerConfig.Model)
|
cnf, err := t.cfg.GetModelConfig(reviewerConfig.Model)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
provider, model, err := providers.CreateProviderFromConfig(cnf)
|
var provider providers.LLMProvider
|
||||||
|
var model string
|
||||||
|
provider, model, err = providers.CreateProviderFromConfig(cnf)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reviewerConfig.Model = model
|
reviewerConfig.Model = model
|
||||||
|
|
@ -216,9 +218,13 @@ func (t *TeamTool) maybeRunAutoReviewer(
|
||||||
providerName = reviewerConfig.Provider.GetDefaultModel()
|
providerName = reviewerConfig.Provider.GetDefaultModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.InfoCF("team", fmt.Sprintf("reviewer use provider: [%s] and model: [%s]", providerName, reviewerConfig.Model), map[string]any{
|
logger.InfoCF(
|
||||||
|
"team",
|
||||||
|
fmt.Sprintf("reviewer use provider: [%s] and model: [%s]", providerName, reviewerConfig.Model),
|
||||||
|
map[string]any{
|
||||||
"model": teamConfig.ReviewerModel,
|
"model": teamConfig.ReviewerModel,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
loopContent, _, err := t.spawnWorker(ctx, reviewerConfig, reviewerMessages, nil)
|
loopContent, _, err := t.spawnWorker(ctx, reviewerConfig, reviewerMessages, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -253,7 +259,8 @@ func (t *TeamTool) Execute(ctx context.Context, args map[string]any) *ToolResult
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Default allowed strategies if not configured
|
// Default allowed strategies if not configured
|
||||||
if strategy == "sequential" || strategy == "parallel" || strategy == "dag" || strategy == "evaluator_optimizer" {
|
if strategy == "sequential" || strategy == "parallel" || strategy == "dag" ||
|
||||||
|
strategy == "evaluator_optimizer" {
|
||||||
validStrategy = true
|
validStrategy = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +276,13 @@ func (t *TeamTool) Execute(ctx context.Context, args map[string]any) *ToolResult
|
||||||
|
|
||||||
// 2. Validate Max Members
|
// 2. Validate Max Members
|
||||||
if teamConfig.MaxMembers > 0 && len(membersRaw) > teamConfig.MaxMembers {
|
if teamConfig.MaxMembers > 0 && len(membersRaw) > teamConfig.MaxMembers {
|
||||||
return ErrorResult(fmt.Sprintf("Team exceeds maximum allowed members (%d). You requested %d members.", teamConfig.MaxMembers, len(membersRaw)))
|
return ErrorResult(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Team exceeds maximum allowed members (%d). You requested %d members.",
|
||||||
|
teamConfig.MaxMembers,
|
||||||
|
len(membersRaw),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
maxTokensFloat, ok := args["max_team_tokens"].(float64)
|
maxTokensFloat, ok := args["max_team_tokens"].(float64)
|
||||||
|
|
@ -286,7 +299,7 @@ func (t *TeamTool) Execute(ctx context.Context, args map[string]any) *ToolResult
|
||||||
// LLM requested more than config allows: clamp to the hard ceiling.
|
// LLM requested more than config allows: clamp to the hard ceiling.
|
||||||
// effectiveMaxTokens already holds the correct ceiling, no change needed.
|
// effectiveMaxTokens already holds the correct ceiling, no change needed.
|
||||||
} else if effectiveMaxTokens == 0 || requestedTokens < effectiveMaxTokens {
|
} else if effectiveMaxTokens == 0 || requestedTokens < effectiveMaxTokens {
|
||||||
// LLM asked for less, or there is no hard limit: honour the requested budget.
|
// LLM asked for less, or there is no hard limit: honor the requested budget.
|
||||||
effectiveMaxTokens = requestedTokens
|
effectiveMaxTokens = requestedTokens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -421,7 +434,12 @@ func upgradeRegistryForConcurrency(original *ToolRegistry) *ToolRegistry {
|
||||||
// spawnWorker executes a single team member's turn, routing through SubTurnSpawner when available.
|
// spawnWorker executes a single team member's turn, routing through SubTurnSpawner when available.
|
||||||
// Returns (content, messages, error). The messages slice is non-nil only for stateful workers
|
// Returns (content, messages, error). The messages slice is non-nil only for stateful workers
|
||||||
// (evaluator_optimizer) and can be passed as InitialMessages for the next iteration.
|
// (evaluator_optimizer) and can be passed as InitialMessages for the next iteration.
|
||||||
func (t *TeamTool) spawnWorker(ctx context.Context, cfg ToolLoopConfig, messages []providers.Message, budget *atomic.Int64) (string, []providers.Message, error) {
|
func (t *TeamTool) spawnWorker(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg ToolLoopConfig,
|
||||||
|
messages []providers.Message,
|
||||||
|
budget *atomic.Int64,
|
||||||
|
) (string, []providers.Message, error) {
|
||||||
if t.spawner == nil {
|
if t.spawner == nil {
|
||||||
// Fallback: direct RunToolLoop (no turnState integration)
|
// Fallback: direct RunToolLoop (no turnState integration)
|
||||||
res, err := RunToolLoop(ctx, cfg, messages, t.originChannel, t.originChatID)
|
res, err := RunToolLoop(ctx, cfg, messages, t.originChannel, t.originChatID)
|
||||||
|
|
@ -475,7 +493,11 @@ func (t *TeamTool) spawnWorker(ctx context.Context, cfg ToolLoopConfig, messages
|
||||||
|
|
||||||
// spawnWorkerEmptyTools is like spawnWorker but forces an empty tool registry on the sub-turn.
|
// spawnWorkerEmptyTools is like spawnWorker but forces an empty tool registry on the sub-turn.
|
||||||
// Used for the evaluator in evaluator_optimizer to prevent side effects.
|
// Used for the evaluator in evaluator_optimizer to prevent side effects.
|
||||||
func (t *TeamTool) spawnWorkerEmptyTools(ctx context.Context, cfg ToolLoopConfig, messages []providers.Message) (string, error) {
|
func (t *TeamTool) spawnWorkerEmptyTools(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg ToolLoopConfig,
|
||||||
|
messages []providers.Message,
|
||||||
|
) (string, error) {
|
||||||
if t.spawner == nil {
|
if t.spawner == nil {
|
||||||
// Fallback: direct RunToolLoop with empty registry
|
// Fallback: direct RunToolLoop with empty registry
|
||||||
emptyConfig := cfg
|
emptyConfig := cfg
|
||||||
|
|
@ -532,15 +554,21 @@ func getLLMOptionsFromConfig(cfg ToolLoopConfig) (int, float64) {
|
||||||
return maxTokens, temperature
|
return maxTokens, temperature
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// potentially overriding the model based on the member's definition.
|
// potentially overriding the model based on the member's definition.
|
||||||
func (t *TeamTool) buildWorkerConfig(baseConfig ToolLoopConfig, registry *ToolRegistry, m TeamMember) (ToolLoopConfig, error) {
|
func (t *TeamTool) buildWorkerConfig(
|
||||||
|
baseConfig ToolLoopConfig,
|
||||||
|
registry *ToolRegistry,
|
||||||
|
m TeamMember,
|
||||||
|
) (ToolLoopConfig, error) {
|
||||||
cfg := baseConfig
|
cfg := baseConfig
|
||||||
cfg.Tools = registry
|
cfg.Tools = registry
|
||||||
// Heterogeneous Agents: Override model if this team member requested a specific one
|
// Heterogeneous Agents: Override model if this team member requested a specific one
|
||||||
if m.Model != "" {
|
if m.Model != "" {
|
||||||
if !t.manager.IsModelAllowed(m.Model) {
|
if !t.manager.IsModelAllowed(m.Model) {
|
||||||
return cfg, fmt.Errorf("requested model '%s' is not in the allowed fallback candidates list for this agent workspace", m.Model)
|
return cfg, fmt.Errorf(
|
||||||
|
"requested model '%s' is not in the allowed fallback candidates list for this agent workspace",
|
||||||
|
m.Model,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// Resolve model name from model_list if it's an alias
|
// Resolve model name from model_list if it's an alias
|
||||||
//resolvedModel := m.Model
|
//resolvedModel := m.Model
|
||||||
|
|
@ -554,13 +582,11 @@ func (t *TeamTool) buildWorkerConfig(baseConfig ToolLoopConfig, registry *ToolRe
|
||||||
//}
|
//}
|
||||||
|
|
||||||
cnf, err := t.cfg.GetModelConfig(m.Model)
|
cnf, err := t.cfg.GetModelConfig(m.Model)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, model, err := providers.CreateProviderFromConfig(cnf)
|
provider, model, err := providers.CreateProviderFromConfig(cnf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ToolLoopConfig{}, err
|
return ToolLoopConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -574,15 +600,24 @@ func (t *TeamTool) buildWorkerConfig(baseConfig ToolLoopConfig, registry *ToolRe
|
||||||
providerName = cfg.Provider.GetDefaultModel()
|
providerName = cfg.Provider.GetDefaultModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.InfoCF("team", fmt.Sprintf("[%s] use provider: [%s] and model: [%s]", m.Role, providerName, cfg.Model), map[string]any{
|
logger.InfoCF(
|
||||||
|
"team",
|
||||||
|
fmt.Sprintf("[%s] use provider: [%s] and model: [%s]", m.Role, providerName, cfg.Model),
|
||||||
|
map[string]any{
|
||||||
"member_index": m.ID,
|
"member_index": m.ID,
|
||||||
"model": m.Model,
|
"model": m.Model,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TeamTool) executeSequential(ctx context.Context, baseConfig ToolLoopConfig, members []TeamMember, contextLimit int) *ToolResult {
|
func (t *TeamTool) executeSequential(
|
||||||
|
ctx context.Context,
|
||||||
|
baseConfig ToolLoopConfig,
|
||||||
|
members []TeamMember,
|
||||||
|
contextLimit int,
|
||||||
|
) *ToolResult {
|
||||||
var finalOutput strings.Builder
|
var finalOutput strings.Builder
|
||||||
finalOutput.WriteString("Team Execution Summary (Sequential):\n\n")
|
finalOutput.WriteString("Team Execution Summary (Sequential):\n\n")
|
||||||
|
|
||||||
|
|
@ -592,7 +627,11 @@ func (t *TeamTool) executeSequential(ctx context.Context, baseConfig ToolLoopCon
|
||||||
// If there is a previous result, we append it to the task so the new agent sees it.
|
// If there is a previous result, we append it to the task so the new agent sees it.
|
||||||
actualTask := m.Task
|
actualTask := m.Task
|
||||||
if i > 0 && previousResult != "" {
|
if i > 0 && previousResult != "" {
|
||||||
actualTask = fmt.Sprintf("%s\n\n--- Context from previous phase ---\n%s", m.Task, truncateContextN(previousResult, contextLimit))
|
actualTask = fmt.Sprintf(
|
||||||
|
"%s\n\n--- Context from previous phase ---\n%s",
|
||||||
|
m.Task,
|
||||||
|
truncateContextN(previousResult, contextLimit),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := []providers.Message{
|
messages := []providers.Message{
|
||||||
|
|
@ -616,7 +655,9 @@ func (t *TeamTool) executeSequential(ctx context.Context, baseConfig ToolLoopCon
|
||||||
|
|
||||||
previousResult = content
|
previousResult = content
|
||||||
|
|
||||||
finalOutput.WriteString(fmt.Sprintf("### Phase %d completed by Role: [%s]\n%s\n\n", i+1, m.Role, previousResult))
|
finalOutput.WriteString(
|
||||||
|
fmt.Sprintf("### Phase %d completed by Role: [%s]\n%s\n\n", i+1, m.Role, previousResult),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ToolResult{
|
return &ToolResult{
|
||||||
|
|
@ -658,7 +699,6 @@ func (t *TeamTool) executeParallel(ctx context.Context, baseConfig ToolLoopConfi
|
||||||
}
|
}
|
||||||
|
|
||||||
content, _, err := t.spawnWorker(ctx, workerConfig, messages, baseConfig.RemainingTokenBudget)
|
content, _, err := t.spawnWorker(ctx, workerConfig, messages, baseConfig.RemainingTokenBudget)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultsChan <- workResult{index: index, role: member.Role, err: err}
|
resultsChan <- workResult{index: index, role: member.Role, err: err}
|
||||||
return
|
return
|
||||||
|
|
@ -709,18 +749,32 @@ func (t *TeamTool) executeParallel(ctx context.Context, baseConfig ToolLoopConfi
|
||||||
fullOutput := successOutput.String()
|
fullOutput := successOutput.String()
|
||||||
if failureCount > 0 {
|
if failureCount > 0 {
|
||||||
fullOutput += "---\n## ⚠️ Partial Failures\n\n" + failureOutput.String() +
|
fullOutput += "---\n## ⚠️ Partial Failures\n\n" + failureOutput.String() +
|
||||||
fmt.Sprintf("\n%d/%d workers succeeded. %d worker(s) failed. The successful results above may still be usable.",
|
fmt.Sprintf(
|
||||||
successCount, len(members), failureCount)
|
"\n%d/%d workers succeeded. %d worker(s) failed. The successful results above may still be usable.",
|
||||||
|
successCount,
|
||||||
|
len(members),
|
||||||
|
failureCount,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ToolResult{
|
return &ToolResult{
|
||||||
ForLLM: fullOutput,
|
ForLLM: fullOutput,
|
||||||
ForUser: fmt.Sprintf("⚠️ Parallel execution: %d/%d workers succeeded. %d failed.", successCount, len(members), failureCount),
|
ForUser: fmt.Sprintf(
|
||||||
|
"⚠️ Parallel execution: %d/%d workers succeeded. %d failed.",
|
||||||
|
successCount,
|
||||||
|
len(members),
|
||||||
|
failureCount,
|
||||||
|
),
|
||||||
IsError: failureCount == len(members),
|
IsError: failureCount == len(members),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TeamTool) executeEvaluatorOptimizer(ctx context.Context, baseConfig ToolLoopConfig, members []TeamMember, contextLimit int) *ToolResult {
|
func (t *TeamTool) executeEvaluatorOptimizer(
|
||||||
|
ctx context.Context,
|
||||||
|
baseConfig ToolLoopConfig,
|
||||||
|
members []TeamMember,
|
||||||
|
contextLimit int,
|
||||||
|
) *ToolResult {
|
||||||
if len(members) != 2 {
|
if len(members) != 2 {
|
||||||
return ErrorResult("The evaluator_optimizer strategy requires exactly two members: [0] Worker, [1] Evaluator.")
|
return ErrorResult("The evaluator_optimizer strategy requires exactly two members: [0] Worker, [1] Evaluator.")
|
||||||
}
|
}
|
||||||
|
|
@ -768,7 +822,12 @@ func (t *TeamTool) executeEvaluatorOptimizer(ctx context.Context, baseConfig Too
|
||||||
logger.InfoCF("team", fmt.Sprintf("Evaluator-Optimizer attempt %d/%d", attempt, maxLoops), map[string]any{})
|
logger.InfoCF("team", fmt.Sprintf("Evaluator-Optimizer attempt %d/%d", attempt, maxLoops), map[string]any{})
|
||||||
|
|
||||||
// 2. Trigger Worker (resumes from its exact previous state!)
|
// 2. Trigger Worker (resumes from its exact previous state!)
|
||||||
workerContent, workerMsgs, err := t.spawnWorker(ctx, workerConfig, workerMessages, baseConfig.RemainingTokenBudget)
|
workerContent, workerMsgs, err := t.spawnWorker(
|
||||||
|
ctx,
|
||||||
|
workerConfig,
|
||||||
|
workerMessages,
|
||||||
|
baseConfig.RemainingTokenBudget,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStr := fmt.Sprintf("Worker failed on attempt %d: %v", attempt, err)
|
errStr := fmt.Sprintf("Worker failed on attempt %d: %v", attempt, err)
|
||||||
finalOutput.WriteString(errStr + "\n")
|
finalOutput.WriteString(errStr + "\n")
|
||||||
|
|
@ -785,7 +844,11 @@ func (t *TeamTool) executeEvaluatorOptimizer(ctx context.Context, baseConfig Too
|
||||||
// 3. Trigger Evaluator (Ephemeral, stateless evaluation)
|
// 3. Trigger Evaluator (Ephemeral, stateless evaluation)
|
||||||
// The evaluator only needs to reason about text — give it no tools to avoid
|
// The evaluator only needs to reason about text — give it no tools to avoid
|
||||||
// unnecessary tool calls, wasted tokens, and potential side effects.
|
// unnecessary tool calls, wasted tokens, and potential side effects.
|
||||||
evalContext := fmt.Sprintf("%s\n\n--- Worker's Output to Evaluate ---\n%s\n\nIf the output is completely correct and fulfills the task, you MUST reply starting with strictly '[PASS]'. Otherwise, explain the issues in detail.", evaluator.Task, truncateContextN(workerContent, contextLimit))
|
evalContext := fmt.Sprintf(
|
||||||
|
"%s\n\n--- Worker's Output to Evaluate ---\n%s\n\nIf the output is completely correct and fulfills the task, you MUST reply starting with strictly '[PASS]'. Otherwise, explain the issues in detail.",
|
||||||
|
evaluator.Task,
|
||||||
|
truncateContextN(workerContent, contextLimit),
|
||||||
|
)
|
||||||
|
|
||||||
evalMessages := []providers.Message{
|
evalMessages := []providers.Message{
|
||||||
{Role: "system", Content: evaluator.Role},
|
{Role: "system", Content: evaluator.Role},
|
||||||
|
|
@ -807,15 +870,27 @@ func (t *TeamTool) executeEvaluatorOptimizer(ctx context.Context, baseConfig Too
|
||||||
logger.InfoCF("team", "Evaluator-Optimizer passed", map[string]any{"attempt": attempt})
|
logger.InfoCF("team", "Evaluator-Optimizer passed", map[string]any{"attempt": attempt})
|
||||||
return &ToolResult{
|
return &ToolResult{
|
||||||
ForLLM: finalOutput.String(),
|
ForLLM: finalOutput.String(),
|
||||||
ForUser: fmt.Sprintf("✅ Evaluator-Optimizer passed on attempt %d/%d (worker: %s).", attempt, maxLoops, worker.Role),
|
ForUser: fmt.Sprintf(
|
||||||
|
"✅ Evaluator-Optimizer passed on attempt %d/%d (worker: %s).",
|
||||||
|
attempt,
|
||||||
|
maxLoops,
|
||||||
|
worker.Role,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.InfoCF("team", "Evaluator-Optimizer did not pass, retrying", map[string]any{"attempt": attempt, "max_loops": maxLoops})
|
logger.InfoCF(
|
||||||
|
"team",
|
||||||
|
"Evaluator-Optimizer did not pass, retrying",
|
||||||
|
map[string]any{"attempt": attempt, "max_loops": maxLoops},
|
||||||
|
)
|
||||||
|
|
||||||
// 5. If not passed, and not the last attempt, inject feedback into Worker's stateful memory
|
// 5. If not passed, and not the last attempt, inject feedback into Worker's stateful memory
|
||||||
if attempt < maxLoops {
|
if attempt < maxLoops {
|
||||||
injection := fmt.Sprintf("The evaluator rejected your previous attempt. Please fix the issues based on this feedback:\n\n%s", evalContent)
|
injection := fmt.Sprintf(
|
||||||
|
"The evaluator rejected your previous attempt. Please fix the issues based on this feedback:\n\n%s",
|
||||||
|
evalContent,
|
||||||
|
)
|
||||||
workerMessages = append(workerMessages, providers.Message{
|
workerMessages = append(workerMessages, providers.Message{
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Content: injection,
|
Content: injection,
|
||||||
|
|
@ -831,7 +906,13 @@ func (t *TeamTool) executeEvaluatorOptimizer(ctx context.Context, baseConfig Too
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TeamTool) executeDAG(ctx context.Context, cancel context.CancelFunc, baseConfig ToolLoopConfig, members []TeamMember, contextLimit int) *ToolResult {
|
func (t *TeamTool) executeDAG(
|
||||||
|
ctx context.Context,
|
||||||
|
cancel context.CancelFunc,
|
||||||
|
baseConfig ToolLoopConfig,
|
||||||
|
members []TeamMember,
|
||||||
|
contextLimit int,
|
||||||
|
) *ToolResult {
|
||||||
logger.InfoCF("team", "DAG execution starting", map[string]any{"member_count": len(members)})
|
logger.InfoCF("team", "DAG execution starting", map[string]any{"member_count": len(members)})
|
||||||
// 1. Build and VALIDATE dependency graph
|
// 1. Build and VALIDATE dependency graph
|
||||||
memberMap := make(map[string]TeamMember)
|
memberMap := make(map[string]TeamMember)
|
||||||
|
|
@ -849,7 +930,9 @@ func (t *TeamTool) executeDAG(ctx context.Context, cancel context.CancelFunc, ba
|
||||||
for _, m := range members {
|
for _, m := range members {
|
||||||
for _, dep := range m.DependsOn {
|
for _, dep := range m.DependsOn {
|
||||||
if _, exists := memberMap[dep]; !exists {
|
if _, exists := memberMap[dep]; !exists {
|
||||||
return ErrorResult(fmt.Sprintf("DAG Validation Error: Member [%s] depends on undefined member [%s]", m.ID, dep))
|
return ErrorResult(
|
||||||
|
fmt.Sprintf("DAG Validation Error: Member [%s] depends on undefined member [%s]", m.ID, dep),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
graph[dep] = append(graph[dep], m.ID)
|
graph[dep] = append(graph[dep], m.ID)
|
||||||
inDegree[m.ID]++
|
inDegree[m.ID]++
|
||||||
|
|
@ -881,7 +964,9 @@ func (t *TeamTool) executeDAG(ctx context.Context, cancel context.CancelFunc, ba
|
||||||
}
|
}
|
||||||
|
|
||||||
if processedCount != len(members) {
|
if processedCount != len(members) {
|
||||||
return ErrorResult("DAG Validation Error: Circular dependency (cycle) detected in the team layout. Please fix your 'depends_on' definitions.")
|
return ErrorResult(
|
||||||
|
"DAG Validation Error: Circular dependency (cycle) detected in the team layout. Please fix your 'depends_on' definitions.",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Channels for coordination
|
// 2. Channels for coordination
|
||||||
|
|
@ -919,7 +1004,7 @@ func (t *TeamTool) executeDAG(ctx context.Context, cancel context.CancelFunc, ba
|
||||||
for completedNodes < nodesToProcess {
|
for completedNodes < nodesToProcess {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ErrorResult("DAG execution timed out or cancelled")
|
return ErrorResult("DAG execution timed out or canceled")
|
||||||
|
|
||||||
case memberID := <-readyChan:
|
case memberID := <-readyChan:
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
@ -939,7 +1024,11 @@ func (t *TeamTool) executeDAG(ctx context.Context, cancel context.CancelFunc, ba
|
||||||
contextMu.Unlock()
|
contextMu.Unlock()
|
||||||
|
|
||||||
if depsContext != "" {
|
if depsContext != "" {
|
||||||
actualTask = fmt.Sprintf("%s\n\n--- Context from dependencies ---\n%s", m.Task, truncateContextN(depsContext, contextLimit))
|
actualTask = fmt.Sprintf(
|
||||||
|
"%s\n\n--- Context from dependencies ---\n%s",
|
||||||
|
m.Task,
|
||||||
|
truncateContextN(depsContext, contextLimit),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := []providers.Message{
|
messages := []providers.Message{
|
||||||
|
|
@ -959,7 +1048,6 @@ func (t *TeamTool) executeDAG(ctx context.Context, cancel context.CancelFunc, ba
|
||||||
}
|
}
|
||||||
|
|
||||||
content, _, err := t.spawnWorker(ctx, workerConfig, messages, baseConfig.RemainingTokenBudget)
|
content, _, err := t.spawnWorker(ctx, workerConfig, messages, baseConfig.RemainingTokenBudget)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
masterErrMu.Lock()
|
masterErrMu.Lock()
|
||||||
if masterErr == nil {
|
if masterErr == nil {
|
||||||
|
|
@ -1046,12 +1134,6 @@ func truncateContextN(ctx string, maxRunes int) string {
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// truncateContext is the default wrapper using 8000 runes (≈6000 words).
|
|
||||||
// Call truncateContextN directly when a configurable limit is needed.
|
|
||||||
func truncateContext(ctx string) string {
|
|
||||||
return truncateContextN(ctx, 8000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildUserSummary produces a concise human-readable summary for the ForUser field,
|
// buildUserSummary produces a concise human-readable summary for the ForUser field,
|
||||||
// listing each member's role. errors (if any) are appended as a separate section.
|
// listing each member's role. errors (if any) are appended as a separate section.
|
||||||
func buildUserSummary(strategy string, members []TeamMember, errors []string) string {
|
func buildUserSummary(strategy string, members []TeamMember, errors []string) string {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/providers"
|
"github.com/sipeed/picoclaw/pkg/providers"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpgradeRegistryForConcurrency(t *testing.T) {
|
func TestUpgradeRegistryForConcurrency(t *testing.T) {
|
||||||
|
|
@ -27,7 +28,12 @@ func TestUpgradeRegistryForConcurrency(t *testing.T) {
|
||||||
upgraded := upgradeRegistryForConcurrency(original)
|
upgraded := upgradeRegistryForConcurrency(original)
|
||||||
|
|
||||||
// Verify count matches
|
// Verify count matches
|
||||||
assert.Equal(t, len(original.ListTools()), len(upgraded.ListTools()), "Upgraded registry should have same number of tools")
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
len(original.ListTools()),
|
||||||
|
len(upgraded.ListTools()),
|
||||||
|
"Upgraded registry should have same number of tools",
|
||||||
|
)
|
||||||
|
|
||||||
// Verify ReadFileTool got upgraded
|
// Verify ReadFileTool got upgraded
|
||||||
actualReadTool, ok := upgraded.Get("read_file")
|
actualReadTool, ok := upgraded.Get("read_file")
|
||||||
|
|
@ -180,7 +186,13 @@ type mockProvider struct {
|
||||||
callCount int
|
callCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, options map[string]any) (*providers.LLMResponse, error) {
|
func (m *mockProvider) Chat(
|
||||||
|
ctx context.Context,
|
||||||
|
messages []providers.Message,
|
||||||
|
tools []providers.ToolDefinition,
|
||||||
|
model string,
|
||||||
|
options map[string]any,
|
||||||
|
) (*providers.LLMResponse, error) {
|
||||||
if m.callCount >= len(m.responses) {
|
if m.callCount >= len(m.responses) {
|
||||||
return &providers.LLMResponse{Content: "Default response"}, nil
|
return &providers.LLMResponse{Content: "Default response"}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +213,13 @@ type mockProviderWithID struct {
|
||||||
callCount int
|
callCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockProviderWithID) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, options map[string]any) (*providers.LLMResponse, error) {
|
func (m *mockProviderWithID) Chat(
|
||||||
|
ctx context.Context,
|
||||||
|
messages []providers.Message,
|
||||||
|
tools []providers.ToolDefinition,
|
||||||
|
model string,
|
||||||
|
options map[string]any,
|
||||||
|
) (*providers.LLMResponse, error) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
m.callCount++
|
m.callCount++
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue