feat(agent): add architect, debugger, implementer, tester, security sub-agent roles
Extend delegation system with 5 new specialized roles alongside researcher/analyst/reviewer. Each role has curated tool access. Refactor profile lookup to use profile_for_role_name and update compact/summarizer and tool context accordingly.
This commit is contained in:
parent
b413edccaf
commit
8d144ac139
@ -15,6 +15,7 @@ fn shared_tools() -> Vec<String> {
|
|||||||
"git_file_content".into(),
|
"git_file_content".into(),
|
||||||
"git_blob_get".into(),
|
"git_blob_get".into(),
|
||||||
"git_blob_content".into(),
|
"git_blob_content".into(),
|
||||||
|
"git_status".into(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,8 +26,10 @@ fn researcher_tools() -> Vec<String> {
|
|||||||
"git_search_commits".into(),
|
"git_search_commits".into(),
|
||||||
"repo_search".into(),
|
"repo_search".into(),
|
||||||
"repo_doc_search".into(),
|
"repo_doc_search".into(),
|
||||||
|
"project_bing_search".into(),
|
||||||
"project_arxiv_search".into(),
|
"project_arxiv_search".into(),
|
||||||
"project_curl".into(),
|
"project_curl".into(),
|
||||||
|
"curl_exec".into(),
|
||||||
// Index / overview
|
// Index / overview
|
||||||
"repo_overview".into(),
|
"repo_overview".into(),
|
||||||
"repo_readme".into(),
|
"repo_readme".into(),
|
||||||
@ -45,6 +48,10 @@ fn researcher_tools() -> Vec<String> {
|
|||||||
"git_graph".into(),
|
"git_graph".into(),
|
||||||
"git_commit_info".into(),
|
"git_commit_info".into(),
|
||||||
"repo_commit_log".into(),
|
"repo_commit_log".into(),
|
||||||
|
"git_ref_list".into(),
|
||||||
|
"git_ref_info".into(),
|
||||||
|
"git_lfs_summary".into(),
|
||||||
|
"git_lfs_scan_tree".into(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +72,9 @@ fn analyst_tools() -> Vec<String> {
|
|||||||
"git_branch_list".into(),
|
"git_branch_list".into(),
|
||||||
"git_branch_info".into(),
|
"git_branch_info".into(),
|
||||||
"git_branch_diff".into(),
|
"git_branch_diff".into(),
|
||||||
|
"git_merge_analysis".into(),
|
||||||
|
"git_ref_list".into(),
|
||||||
|
"git_ref_info".into(),
|
||||||
// Project data
|
// Project data
|
||||||
"project_list_issues".into(),
|
"project_list_issues".into(),
|
||||||
"project_list_repos".into(),
|
"project_list_repos".into(),
|
||||||
@ -81,6 +91,7 @@ fn reviewer_tools() -> Vec<String> {
|
|||||||
// Merge status
|
// Merge status
|
||||||
"git_branches_merged".into(),
|
"git_branches_merged".into(),
|
||||||
"git_branch_info".into(),
|
"git_branch_info".into(),
|
||||||
|
"git_merge_analysis".into(),
|
||||||
// Tracking
|
// Tracking
|
||||||
"project_list_issues".into(),
|
"project_list_issues".into(),
|
||||||
"project_update_issue".into(),
|
"project_update_issue".into(),
|
||||||
@ -90,6 +101,108 @@ fn reviewer_tools() -> Vec<String> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Architect-specific tools (system design & dependency mapping).
|
||||||
|
fn architect_tools() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
// Repository structure
|
||||||
|
"repo_overview".into(),
|
||||||
|
"repo_readme".into(),
|
||||||
|
"repo_file_tree".into(),
|
||||||
|
"repo_languages".into(),
|
||||||
|
"repo_dependencies".into(),
|
||||||
|
"repo_test_discovery".into(),
|
||||||
|
// Change and branch context
|
||||||
|
"repo_diff_summary".into(),
|
||||||
|
"git_branch_list".into(),
|
||||||
|
"git_branch_info".into(),
|
||||||
|
"git_branch_diff".into(),
|
||||||
|
"git_merge_analysis".into(),
|
||||||
|
"git_ref_list".into(),
|
||||||
|
// Project context
|
||||||
|
"project_list_repos".into(),
|
||||||
|
"project_list_issues".into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debugger-specific tools (root-cause analysis & history tracing).
|
||||||
|
fn debugger_tools() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
// Failure and change inspection
|
||||||
|
"git_show".into(),
|
||||||
|
"git_diff".into(),
|
||||||
|
"git_diff_stats".into(),
|
||||||
|
"git_blame".into(),
|
||||||
|
"git_log".into(),
|
||||||
|
"git_commit_info".into(),
|
||||||
|
"git_ref_info".into(),
|
||||||
|
// Structural clues
|
||||||
|
"repo_file_tree".into(),
|
||||||
|
"repo_dependencies".into(),
|
||||||
|
"repo_test_discovery".into(),
|
||||||
|
"repo_doc_search".into(),
|
||||||
|
// Issue context
|
||||||
|
"project_list_issues".into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementer-specific tools (implementation planning & code navigation).
|
||||||
|
fn implementer_tools() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
// Code and documentation context
|
||||||
|
"repo_overview".into(),
|
||||||
|
"repo_readme".into(),
|
||||||
|
"repo_file_tree".into(),
|
||||||
|
"repo_doc_index".into(),
|
||||||
|
"repo_doc_read".into(),
|
||||||
|
"repo_dependencies".into(),
|
||||||
|
"repo_test_discovery".into(),
|
||||||
|
// Current change context
|
||||||
|
"git_diff".into(),
|
||||||
|
"git_diff_stats".into(),
|
||||||
|
"git_show".into(),
|
||||||
|
"git_branch_info".into(),
|
||||||
|
// Project context
|
||||||
|
"project_list_issues".into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tester-specific tools (coverage, regression, and validation planning).
|
||||||
|
fn tester_tools() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
// Test discovery and changed surface
|
||||||
|
"git_grep".into(),
|
||||||
|
"git_diff".into(),
|
||||||
|
"git_diff_stats".into(),
|
||||||
|
"git_show".into(),
|
||||||
|
"repo_file_tree".into(),
|
||||||
|
"repo_dependencies".into(),
|
||||||
|
"repo_test_discovery".into(),
|
||||||
|
// Project context
|
||||||
|
"project_list_issues".into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Security-specific tools (threat modeling & sensitive-code review).
|
||||||
|
fn security_tools() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
// Sensitive pattern discovery
|
||||||
|
"git_grep".into(),
|
||||||
|
"git_diff".into(),
|
||||||
|
"git_diff_stats".into(),
|
||||||
|
"git_blame".into(),
|
||||||
|
"git_log".into(),
|
||||||
|
// Dependency and surface review
|
||||||
|
"repo_file_tree".into(),
|
||||||
|
"repo_dependencies".into(),
|
||||||
|
"repo_doc_search".into(),
|
||||||
|
"git_lfs_summary".into(),
|
||||||
|
"git_lfs_pointer_info".into(),
|
||||||
|
"git_lfs_object_info".into(),
|
||||||
|
// Issue context
|
||||||
|
"project_list_issues".into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/// Supervisor-specific tools (delegation & synthesis).
|
/// Supervisor-specific tools (delegation & synthesis).
|
||||||
fn supervisor_tools() -> Vec<String> {
|
fn supervisor_tools() -> Vec<String> {
|
||||||
vec![
|
vec![
|
||||||
@ -105,12 +218,31 @@ pub fn tools_for_role(role: &AgentRole) -> Vec<String> {
|
|||||||
AgentRole::Researcher => tools.extend(researcher_tools()),
|
AgentRole::Researcher => tools.extend(researcher_tools()),
|
||||||
AgentRole::Analyst => tools.extend(analyst_tools()),
|
AgentRole::Analyst => tools.extend(analyst_tools()),
|
||||||
AgentRole::Reviewer => tools.extend(reviewer_tools()),
|
AgentRole::Reviewer => tools.extend(reviewer_tools()),
|
||||||
|
AgentRole::Architect => tools.extend(architect_tools()),
|
||||||
|
AgentRole::Debugger => tools.extend(debugger_tools()),
|
||||||
|
AgentRole::Implementer => tools.extend(implementer_tools()),
|
||||||
|
AgentRole::Tester => tools.extend(tester_tools()),
|
||||||
|
AgentRole::Security => tools.extend(security_tools()),
|
||||||
AgentRole::Supervisor => tools.extend(supervisor_tools()),
|
AgentRole::Supervisor => tools.extend(supervisor_tools()),
|
||||||
AgentRole::Default => {} // Default role gets only shared tools
|
AgentRole::Default => {} // Default role gets only shared tools
|
||||||
}
|
}
|
||||||
tools
|
tools
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn profile_for_role_name(role: &str) -> AgentExecutionProfile {
|
||||||
|
match role {
|
||||||
|
"researcher" => researcher_profile(),
|
||||||
|
"analyst" => analyst_profile(),
|
||||||
|
"reviewer" => reviewer_profile(),
|
||||||
|
"architect" => architect_profile(),
|
||||||
|
"debugger" => debugger_profile(),
|
||||||
|
"implementer" => implementer_profile(),
|
||||||
|
"tester" => tester_profile(),
|
||||||
|
"security" => security_profile(),
|
||||||
|
_ => researcher_profile(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn supervisor_profile() -> AgentExecutionProfile {
|
pub fn supervisor_profile() -> AgentExecutionProfile {
|
||||||
AgentExecutionProfile {
|
AgentExecutionProfile {
|
||||||
role: AgentRole::Supervisor,
|
role: AgentRole::Supervisor,
|
||||||
@ -123,6 +255,11 @@ pub fn supervisor_profile() -> AgentExecutionProfile {
|
|||||||
- **researcher**: Gathers concrete facts, evidence, and data from tools and context. Best for finding information, searching code, and discovering evidence.\n\
|
- **researcher**: Gathers concrete facts, evidence, and data from tools and context. Best for finding information, searching code, and discovering evidence.\n\
|
||||||
- **analyst**: Builds coherent explanations, highlights causal links, edge cases, and tradeoffs. Best for explaining findings and reasoning about implications.\n\
|
- **analyst**: Builds coherent explanations, highlights causal links, edge cases, and tradeoffs. Best for explaining findings and reasoning about implications.\n\
|
||||||
- **reviewer**: Stress-tests proposals, identifies contradictions, missing assumptions, regressions, and risks. Best for quality checks and risk assessment.\n\
|
- **reviewer**: Stress-tests proposals, identifies contradictions, missing assumptions, regressions, and risks. Best for quality checks and risk assessment.\n\
|
||||||
|
- **architect**: Maps systems, boundaries, dependencies, and long-term design tradeoffs. Best for architecture decisions and refactor strategy.\n\
|
||||||
|
- **debugger**: Finds likely root causes, reproduction gaps, and suspect changes. Best for failures, regressions, and confusing behavior.\n\
|
||||||
|
- **implementer**: Turns requirements into concrete implementation steps, affected files, and integration concerns. Best for execution planning.\n\
|
||||||
|
- **tester**: Designs validation strategy, regression coverage, and edge-case test plans. Best for test planning and release confidence.\n\
|
||||||
|
- **security**: Reviews auth, data exposure, injection, dependency, and abuse risks. Best for threat modeling and sensitive changes.\n\
|
||||||
- Provide a clear, focused task description for each sub-agent.\n\
|
- Provide a clear, focused task description for each sub-agent.\n\
|
||||||
- You may call multiple sub-agents in sequence (call one, review its output, then decide to call another).\n\
|
- You may call multiple sub-agents in sequence (call one, review its output, then decide to call another).\n\
|
||||||
- You may also call the same role twice with different tasks if needed.\n\
|
- You may also call the same role twice with different tasks if needed.\n\
|
||||||
@ -130,7 +267,10 @@ pub fn supervisor_profile() -> AgentExecutionProfile {
|
|||||||
## Decision Guide\n\
|
## Decision Guide\n\
|
||||||
- Simple factual questions: call researcher only.\n\
|
- Simple factual questions: call researcher only.\n\
|
||||||
- Questions requiring explanation: call researcher then analyst.\n\
|
- Questions requiring explanation: call researcher then analyst.\n\
|
||||||
- Design/architecture reviews: call researcher, analyst, then reviewer.\n\
|
- Design/architecture reviews: call researcher, architect, then reviewer.\n\
|
||||||
|
- Bug or regression diagnosis: call debugger, then tester if validation is needed.\n\
|
||||||
|
- Implementation requests: call implementer, then reviewer for risk checks.\n\
|
||||||
|
- Security-sensitive changes: call security, then reviewer.\n\
|
||||||
- If a sub-agent's output is insufficient, call another sub-agent for clarification.\n\
|
- If a sub-agent's output is insufficient, call another sub-agent for clarification.\n\
|
||||||
\n\
|
\n\
|
||||||
## Output Rules\n\
|
## Output Rules\n\
|
||||||
@ -201,6 +341,91 @@ pub fn reviewer_profile() -> AgentExecutionProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn architect_profile() -> AgentExecutionProfile {
|
||||||
|
AgentExecutionProfile {
|
||||||
|
role: AgentRole::Architect,
|
||||||
|
system_prompt: Some(
|
||||||
|
"You are the architect agent. Map system boundaries, dependencies, data flow, and design tradeoffs. Prefer practical architecture guidance tied to repository evidence. Call out migration risks and long-term maintainability concerns.".to_string(),
|
||||||
|
),
|
||||||
|
temperature: Some(0.2),
|
||||||
|
max_tokens: Some(2000),
|
||||||
|
top_p: Some(1.0),
|
||||||
|
frequency_penalty: Some(0.0),
|
||||||
|
presence_penalty: Some(0.0),
|
||||||
|
max_tool_depth: Some(5),
|
||||||
|
allowed_tools: Some(tools_for_role(&AgentRole::Architect)),
|
||||||
|
disable_orchestration: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debugger_profile() -> AgentExecutionProfile {
|
||||||
|
AgentExecutionProfile {
|
||||||
|
role: AgentRole::Debugger,
|
||||||
|
system_prompt: Some(
|
||||||
|
"You are the debugger agent. Identify likely root causes, suspect files or commits, missing reproduction details, and the shortest validation path. Separate evidence from hypotheses and rank hypotheses by plausibility.".to_string(),
|
||||||
|
),
|
||||||
|
temperature: Some(0.1),
|
||||||
|
max_tokens: Some(1800),
|
||||||
|
top_p: Some(1.0),
|
||||||
|
frequency_penalty: Some(0.0),
|
||||||
|
presence_penalty: Some(0.0),
|
||||||
|
max_tool_depth: Some(6),
|
||||||
|
allowed_tools: Some(tools_for_role(&AgentRole::Debugger)),
|
||||||
|
disable_orchestration: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn implementer_profile() -> AgentExecutionProfile {
|
||||||
|
AgentExecutionProfile {
|
||||||
|
role: AgentRole::Implementer,
|
||||||
|
system_prompt: Some(
|
||||||
|
"You are the implementer agent. Convert requirements into a concrete execution plan: files to touch, sequencing, integration points, and risks. Keep recommendations actionable and avoid broad rewrites unless justified.".to_string(),
|
||||||
|
),
|
||||||
|
temperature: Some(0.15),
|
||||||
|
max_tokens: Some(1800),
|
||||||
|
top_p: Some(1.0),
|
||||||
|
frequency_penalty: Some(0.0),
|
||||||
|
presence_penalty: Some(0.0),
|
||||||
|
max_tool_depth: Some(5),
|
||||||
|
allowed_tools: Some(tools_for_role(&AgentRole::Implementer)),
|
||||||
|
disable_orchestration: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tester_profile() -> AgentExecutionProfile {
|
||||||
|
AgentExecutionProfile {
|
||||||
|
role: AgentRole::Tester,
|
||||||
|
system_prompt: Some(
|
||||||
|
"You are the tester agent. Design high-signal validation: unit, integration, regression, and edge-case coverage. Identify what must be tested, what can be skipped, and the fastest commands or checks to build confidence.".to_string(),
|
||||||
|
),
|
||||||
|
temperature: Some(0.1),
|
||||||
|
max_tokens: Some(1600),
|
||||||
|
top_p: Some(1.0),
|
||||||
|
frequency_penalty: Some(0.0),
|
||||||
|
presence_penalty: Some(0.0),
|
||||||
|
max_tool_depth: Some(4),
|
||||||
|
allowed_tools: Some(tools_for_role(&AgentRole::Tester)),
|
||||||
|
disable_orchestration: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn security_profile() -> AgentExecutionProfile {
|
||||||
|
AgentExecutionProfile {
|
||||||
|
role: AgentRole::Security,
|
||||||
|
system_prompt: Some(
|
||||||
|
"You are the security agent. Review authentication, authorization, data exposure, injection, dependency, secret-handling, and abuse-case risks. Prioritize exploitable issues and concrete mitigations over generic advice.".to_string(),
|
||||||
|
),
|
||||||
|
temperature: Some(0.1),
|
||||||
|
max_tokens: Some(1800),
|
||||||
|
top_p: Some(1.0),
|
||||||
|
frequency_penalty: Some(0.0),
|
||||||
|
presence_penalty: Some(0.0),
|
||||||
|
max_tool_depth: Some(5),
|
||||||
|
allowed_tools: Some(tools_for_role(&AgentRole::Security)),
|
||||||
|
disable_orchestration: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether to enable multi-agent delegation for this request.
|
/// Whether to enable multi-agent delegation for this request.
|
||||||
/// Simplified from keyword-based gating: delegation is enabled when tools are available.
|
/// Simplified from keyword-based gating: delegation is enabled when tools are available.
|
||||||
pub fn should_enable_delegation(_input: &str, tools_available: bool) -> bool {
|
pub fn should_enable_delegation(_input: &str, tools_available: bool) -> bool {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::pin::Pin;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::agent_profile::{analyst_profile, researcher_profile, reviewer_profile};
|
use super::agent_profile::profile_for_role_name;
|
||||||
use crate::client::AiClientConfig;
|
use crate::client::AiClientConfig;
|
||||||
use crate::client::types::{ChatRequestMessage, ToolCall};
|
use crate::client::types::{ChatRequestMessage, ToolCall};
|
||||||
use crate::client::{StreamChunk, StreamChunkType, StreamedToolCall, call_stream};
|
use crate::client::{StreamChunk, StreamChunkType, StreamedToolCall, call_stream};
|
||||||
@ -629,13 +629,13 @@ pub async fn execute_chat_stream(
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "call_sub_agent",
|
"name": "call_sub_agent",
|
||||||
"description": "Delegate a task to a specialist sub-agent and receive its output.\nAvailable roles:\n- researcher: Gathers facts, evidence, and data. Best for finding information and searching code.\n- analyst: Builds explanations, highlights causal links and tradeoffs. Best for reasoning about implications.\n- reviewer: Stress-tests proposals, identifies risks and contradictions. Best for quality checks.\nProvide a clear, focused task description so the sub-agent knows exactly what to investigate.",
|
"description": "Delegate a task to a specialist sub-agent and receive its output.\nAvailable roles:\n- researcher: Gathers facts, evidence, and data. Best for finding information and searching code.\n- analyst: Builds explanations, highlights causal links and tradeoffs. Best for reasoning about implications.\n- reviewer: Stress-tests proposals, identifies risks and contradictions. Best for quality checks.\n- architect: Maps systems, dependencies, boundaries, and design tradeoffs. Best for architecture decisions.\n- debugger: Finds root causes, suspect changes, and validation paths. Best for bugs and regressions.\n- implementer: Converts requirements into concrete implementation steps. Best for execution planning.\n- tester: Designs validation and regression coverage. Best for test strategy.\n- security: Reviews auth, data exposure, injection, dependency, and abuse risks. Best for sensitive changes.\nProvide a clear, focused task description so the sub-agent knows exactly what to investigate.",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"role": {
|
"role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The sub-agent role to delegate to: researcher, analyst, or reviewer."
|
"description": "The sub-agent role to delegate to: researcher, analyst, reviewer, architect, debugger, implementer, tester, or security."
|
||||||
},
|
},
|
||||||
"task": {
|
"task": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -805,11 +805,7 @@ pub async fn execute_chat_stream(
|
|||||||
.unwrap_or("researcher");
|
.unwrap_or("researcher");
|
||||||
let task = args.get("task").and_then(|v| v.as_str()).unwrap_or("");
|
let task = args.get("task").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
|
||||||
let profile = match role {
|
let profile = profile_for_role_name(role);
|
||||||
"analyst" => analyst_profile(),
|
|
||||||
"reviewer" => reviewer_profile(),
|
|
||||||
_ => researcher_profile(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate children_id BEFORE starting sub-agent execution
|
// Generate children_id BEFORE starting sub-agent execution
|
||||||
let sub_agent_id = format!("sub-agent-{}", Uuid::now_v7());
|
let sub_agent_id = format!("sub-agent-{}", Uuid::now_v7());
|
||||||
|
|||||||
@ -87,6 +87,11 @@ pub enum AgentRole {
|
|||||||
Researcher,
|
Researcher,
|
||||||
Analyst,
|
Analyst,
|
||||||
Reviewer,
|
Reviewer,
|
||||||
|
Architect,
|
||||||
|
Debugger,
|
||||||
|
Implementer,
|
||||||
|
Tester,
|
||||||
|
Security,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::agent_profile::{
|
use super::agent_profile::{profile_for_role_name, should_enable_delegation, supervisor_profile};
|
||||||
analyst_profile, researcher_profile, reviewer_profile, should_enable_delegation,
|
|
||||||
supervisor_profile,
|
|
||||||
};
|
|
||||||
use super::message_builder::MessageBuilder;
|
use super::message_builder::MessageBuilder;
|
||||||
use super::nonstreaming_execution::execute_process;
|
use super::nonstreaming_execution::execute_process;
|
||||||
use super::service::{ProcessResult, StreamResult};
|
use super::service::{ProcessResult, StreamResult};
|
||||||
@ -67,9 +64,8 @@ pub async fn execute_orchestrated_process(
|
|||||||
supervisor_request.frequency_penalty = profile
|
supervisor_request.frequency_penalty = profile
|
||||||
.frequency_penalty
|
.frequency_penalty
|
||||||
.unwrap_or(request.frequency_penalty);
|
.unwrap_or(request.frequency_penalty);
|
||||||
supervisor_request.presence_penalty = profile
|
supervisor_request.presence_penalty =
|
||||||
.presence_penalty
|
profile.presence_penalty.unwrap_or(request.presence_penalty);
|
||||||
.unwrap_or(request.presence_penalty);
|
|
||||||
|
|
||||||
execute_process(
|
execute_process(
|
||||||
supervisor_request,
|
supervisor_request,
|
||||||
@ -138,9 +134,8 @@ pub async fn execute_orchestrated_stream(
|
|||||||
supervisor_request.frequency_penalty = profile
|
supervisor_request.frequency_penalty = profile
|
||||||
.frequency_penalty
|
.frequency_penalty
|
||||||
.unwrap_or(request.frequency_penalty);
|
.unwrap_or(request.frequency_penalty);
|
||||||
supervisor_request.presence_penalty = profile
|
supervisor_request.presence_penalty =
|
||||||
.presence_penalty
|
profile.presence_penalty.unwrap_or(request.presence_penalty);
|
||||||
.unwrap_or(request.presence_penalty);
|
|
||||||
|
|
||||||
super::streaming_execution::execute_process_stream(
|
super::streaming_execution::execute_process_stream(
|
||||||
supervisor_request,
|
supervisor_request,
|
||||||
@ -175,6 +170,11 @@ fn register_call_sub_agent_tool(
|
|||||||
- researcher: Gathers facts, evidence, and data. Best for finding information and searching code.\n\
|
- researcher: Gathers facts, evidence, and data. Best for finding information and searching code.\n\
|
||||||
- analyst: Builds explanations, highlights causal links and tradeoffs. Best for reasoning about implications.\n\
|
- analyst: Builds explanations, highlights causal links and tradeoffs. Best for reasoning about implications.\n\
|
||||||
- reviewer: Stress-tests proposals, identifies risks and contradictions. Best for quality checks.\n\
|
- reviewer: Stress-tests proposals, identifies risks and contradictions. Best for quality checks.\n\
|
||||||
|
- architect: Maps systems, dependencies, boundaries, and design tradeoffs. Best for architecture decisions.\n\
|
||||||
|
- debugger: Finds root causes, suspect changes, and validation paths. Best for bugs and regressions.\n\
|
||||||
|
- implementer: Converts requirements into concrete implementation steps. Best for execution planning.\n\
|
||||||
|
- tester: Designs validation and regression coverage. Best for test strategy.\n\
|
||||||
|
- security: Reviews auth, data exposure, injection, dependency, and abuse risks. Best for sensitive changes.\n\
|
||||||
Provide a clear, focused task description so the sub-agent knows exactly what to investigate.",
|
Provide a clear, focused task description so the sub-agent knows exactly what to investigate.",
|
||||||
)
|
)
|
||||||
.parameters(ToolSchema {
|
.parameters(ToolSchema {
|
||||||
@ -187,7 +187,7 @@ fn register_call_sub_agent_tool(
|
|||||||
name: "role".into(),
|
name: "role".into(),
|
||||||
param_type: "string".into(),
|
param_type: "string".into(),
|
||||||
description: Some(
|
description: Some(
|
||||||
"The sub-agent role to delegate to: researcher, analyst, or reviewer.".into(),
|
"The sub-agent role to delegate to: researcher, analyst, reviewer, architect, debugger, implementer, tester, or security.".into(),
|
||||||
),
|
),
|
||||||
required: true,
|
required: true,
|
||||||
properties: None,
|
properties: None,
|
||||||
@ -224,12 +224,7 @@ fn register_call_sub_agent_tool(
|
|||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let profile = match role.as_str() {
|
let profile = profile_for_role_name(role.as_str());
|
||||||
"researcher" => researcher_profile(),
|
|
||||||
"analyst" => analyst_profile(),
|
|
||||||
"reviewer" => reviewer_profile(),
|
|
||||||
_ => researcher_profile(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut sub_request = captured_request.clone();
|
let mut sub_request = captured_request.clone();
|
||||||
sub_request.input = format!(
|
sub_request.input = format!(
|
||||||
@ -288,9 +283,12 @@ fn filter_tools_for_sub_agent(
|
|||||||
let Some(tools) = original_tools else {
|
let Some(tools) = original_tools else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let allowed = allowed_tools
|
let allowed = allowed_tools.as_ref().map(|list| {
|
||||||
.as_ref()
|
list.iter()
|
||||||
.map(|list| list.iter().filter(|n| *n != "call_sub_agent").cloned().collect::<Vec<String>>());
|
.filter(|n| *n != "call_sub_agent")
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
});
|
||||||
|
|
||||||
match allowed {
|
match allowed {
|
||||||
Some(allowed_list) if !allowed_list.is_empty() => tools
|
Some(allowed_list) if !allowed_list.is_empty() => tools
|
||||||
@ -308,8 +306,7 @@ fn filter_tools_for_sub_agent(
|
|||||||
_ => tools
|
_ => tools
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|tool| {
|
.filter(|tool| {
|
||||||
tool
|
tool.get("function")
|
||||||
.get("function")
|
|
||||||
.and_then(|f| f.get("name"))
|
.and_then(|f| f.get("name"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.is_some_and(|name| name != "call_sub_agent")
|
.is_some_and(|name| name != "call_sub_agent")
|
||||||
|
|||||||
@ -165,10 +165,7 @@ impl ChatService {
|
|||||||
request: AiRequest,
|
request: AiRequest,
|
||||||
room_tools: ToolRegistry,
|
room_tools: ToolRegistry,
|
||||||
) -> Result<ProcessResult> {
|
) -> Result<ProcessResult> {
|
||||||
let mut merged = self
|
let mut merged = self.tool_registry.clone().unwrap_or_default();
|
||||||
.tool_registry
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_default();
|
|
||||||
merged.merge(room_tools);
|
merged.merge(room_tools);
|
||||||
|
|
||||||
super::nonstreaming_execution::execute_process(
|
super::nonstreaming_execution::execute_process(
|
||||||
@ -191,10 +188,7 @@ impl ChatService {
|
|||||||
on_chunk: StreamCallback,
|
on_chunk: StreamCallback,
|
||||||
room_tools: ToolRegistry,
|
room_tools: ToolRegistry,
|
||||||
) -> Result<StreamResult> {
|
) -> Result<StreamResult> {
|
||||||
let mut merged = self
|
let mut merged = self.tool_registry.clone().unwrap_or_default();
|
||||||
.tool_registry
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_default();
|
|
||||||
merged.merge(room_tools);
|
merged.merge(room_tools);
|
||||||
|
|
||||||
super::streaming_execution::execute_process_stream(
|
super::streaming_execution::execute_process_stream(
|
||||||
|
|||||||
@ -198,9 +198,7 @@ impl super::CompactService {
|
|||||||
.unwrap_or_else(|_| estimate_input.len() / 4);
|
.unwrap_or_else(|_| estimate_input.len() / 4);
|
||||||
|
|
||||||
let retain_count = Self::resolve_retain_count(config, estimated_tokens);
|
let retain_count = Self::resolve_retain_count(config, estimated_tokens);
|
||||||
if estimated_tokens >= config.token_threshold
|
if estimated_tokens >= config.token_threshold && messages.len() > retain_count {
|
||||||
&& messages.len() > retain_count
|
|
||||||
{
|
|
||||||
let split_index = messages.len().saturating_sub(retain_count);
|
let split_index = messages.len().saturating_sub(retain_count);
|
||||||
let (to_summarize, retained_messages) = messages.split_at(split_index);
|
let (to_summarize, retained_messages) = messages.split_at(split_index);
|
||||||
let from_seq = to_summarize
|
let from_seq = to_summarize
|
||||||
|
|||||||
@ -2,11 +2,11 @@ use models::rooms::room_message::Model as RoomMessageModel;
|
|||||||
use models::users::user::{Column as UserCol, Entity as User};
|
use models::users::user::{Column as UserCol, Entity as User};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||||
|
|
||||||
|
use crate::AgentError;
|
||||||
use crate::client::call_with_params;
|
use crate::client::call_with_params;
|
||||||
use crate::client::types::ChatRequestMessage;
|
use crate::client::types::ChatRequestMessage;
|
||||||
use crate::compact::types::{CompactConfig, MessageSummary};
|
use crate::compact::types::{CompactConfig, MessageSummary};
|
||||||
use crate::tokent::{TokenUsage, count_message_text};
|
use crate::tokent::{TokenUsage, count_message_text};
|
||||||
use crate::AgentError;
|
|
||||||
|
|
||||||
const DEFAULT_MODEL_CONTEXT_LIMIT: usize = 128_000;
|
const DEFAULT_MODEL_CONTEXT_LIMIT: usize = 128_000;
|
||||||
const MODEL_INPUT_RATIO_NUMERATOR: usize = 85;
|
const MODEL_INPUT_RATIO_NUMERATOR: usize = 85;
|
||||||
@ -193,7 +193,10 @@ impl super::CompactService {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((final_summary, if has_usage { Some(total_usage) } else { None }))
|
Ok((
|
||||||
|
final_summary,
|
||||||
|
if has_usage { Some(total_usage) } else { None },
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn merge_summary_rounds(
|
async fn merge_summary_rounds(
|
||||||
@ -230,11 +233,7 @@ impl super::CompactService {
|
|||||||
for pair_text in fitted_pairs {
|
for pair_text in fitted_pairs {
|
||||||
let prompt = self.build_prompt(kind, true, &pair_text, current_budget);
|
let prompt = self.build_prompt(kind, true, &pair_text, current_budget);
|
||||||
let (summary, usage) = self
|
let (summary, usage) = self
|
||||||
.invoke_summary_prompt(
|
.invoke_summary_prompt(&prompt, current_budget, Self::temperature_for(kind))
|
||||||
&prompt,
|
|
||||||
current_budget,
|
|
||||||
Self::temperature_for(kind),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
Self::accumulate_usage(total_usage, has_usage, usage);
|
Self::accumulate_usage(total_usage, has_usage, usage);
|
||||||
next_round.push(summary);
|
next_round.push(summary);
|
||||||
@ -474,18 +473,16 @@ impl super::CompactService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn safe_model_input_budget_from_limit(model_context_limit: Option<usize>) -> usize {
|
fn safe_model_input_budget_from_limit(model_context_limit: Option<usize>) -> usize {
|
||||||
let context_limit = model_context_limit.unwrap_or(DEFAULT_MODEL_CONTEXT_LIMIT).max(1);
|
let context_limit = model_context_limit
|
||||||
|
.unwrap_or(DEFAULT_MODEL_CONTEXT_LIMIT)
|
||||||
|
.max(1);
|
||||||
context_limit
|
context_limit
|
||||||
.saturating_mul(MODEL_INPUT_RATIO_NUMERATOR)
|
.saturating_mul(MODEL_INPUT_RATIO_NUMERATOR)
|
||||||
.saturating_div(MODEL_INPUT_RATIO_DENOMINATOR)
|
.saturating_div(MODEL_INPUT_RATIO_DENOMINATOR)
|
||||||
.max(1)
|
.max(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accumulate_usage(
|
fn accumulate_usage(total: &mut TokenUsage, has_usage: &mut bool, usage: Option<TokenUsage>) {
|
||||||
total: &mut TokenUsage,
|
|
||||||
has_usage: &mut bool,
|
|
||||||
usage: Option<TokenUsage>,
|
|
||||||
) {
|
|
||||||
if let Some(usage) = usage {
|
if let Some(usage) = usage {
|
||||||
total.input_tokens += usage.input_tokens;
|
total.input_tokens += usage.input_tokens;
|
||||||
total.output_tokens += usage.output_tokens;
|
total.output_tokens += usage.output_tokens;
|
||||||
@ -500,7 +497,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn room_summary_uses_eighty_five_percent_input_budget() {
|
fn room_summary_uses_eighty_five_percent_input_budget() {
|
||||||
assert_eq!(CompactService::safe_model_input_budget_from_limit(Some(1000)), 850);
|
assert_eq!(
|
||||||
|
CompactService::safe_model_input_budget_from_limit(Some(1000)),
|
||||||
|
850
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -19,8 +19,8 @@ pub use billing::{
|
|||||||
initialize_user_billing, persist_billing_error, record_ai_usage, record_user_ai_usage,
|
initialize_user_billing, persist_billing_error, record_ai_usage, record_user_ai_usage,
|
||||||
};
|
};
|
||||||
pub use chat::{
|
pub use chat::{
|
||||||
AgentExecutionProfile, AgentRole, AiContextSenderType, AiRequest, AiStreamChunk,
|
AgentExecutionProfile, AgentRole, AiContextSenderType, AiRequest, AiStreamChunk, ChatService,
|
||||||
ChatService, Mention, RoomMessageContext, StreamCallback,
|
Mention, RoomMessageContext, StreamCallback,
|
||||||
};
|
};
|
||||||
pub use client::types::ChatRequestMessage;
|
pub use client::types::ChatRequestMessage;
|
||||||
pub use client::{AiCallResponse, AiClientConfig, call_with_params, call_with_retry};
|
pub use client::{AiCallResponse, AiClientConfig, call_with_params, call_with_retry};
|
||||||
|
|||||||
@ -177,12 +177,7 @@ impl ToolContext {
|
|||||||
return Err(existing);
|
return Err(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
match current.compare_exchange(
|
match current.compare_exchange(existing, next, Ordering::AcqRel, Ordering::Relaxed) {
|
||||||
existing,
|
|
||||||
next,
|
|
||||||
Ordering::AcqRel,
|
|
||||||
Ordering::Relaxed,
|
|
||||||
) {
|
|
||||||
Ok(_) => return Ok(()),
|
Ok(_) => return Ok(()),
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user