Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
mod common;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::{MockLlmProvider, test_config};
|
||||
use nomi_agent::spawner::{AgentSpawner, SubAgentConfig};
|
||||
use nomi_types::llm::LlmEvent;
|
||||
use nomi_types::message::{StopReason, TokenUsage};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: build a minimal SubAgentConfig for testing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn make_sub_config(name: &str) -> SubAgentConfig {
|
||||
SubAgentConfig {
|
||||
name: name.to_string(),
|
||||
prompt: format!("Task for {}", name),
|
||||
max_turns: 5,
|
||||
max_tokens: 1024,
|
||||
system_prompt: None,
|
||||
allowed_tools: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Single sub-agent executes and returns the expected text result.
|
||||
#[tokio::test]
|
||||
async fn test_spawn_single_agent() {
|
||||
let provider = Arc::new(MockLlmProvider::with_text_response("Sub-agent done"));
|
||||
let spawner = AgentSpawner::new(provider, test_config(), std::env::temp_dir());
|
||||
|
||||
let result = spawner.spawn_one(make_sub_config("agent-1")).await;
|
||||
|
||||
assert_eq!(result.text, "Sub-agent done");
|
||||
assert!(!result.is_error, "expected no error, got: {}", result.text);
|
||||
assert_eq!(result.turns, 1);
|
||||
assert_eq!(result.name, "agent-1");
|
||||
}
|
||||
|
||||
/// Parallel sub-agents all complete successfully and return distinct results.
|
||||
#[tokio::test]
|
||||
async fn test_spawn_parallel_agents() {
|
||||
// Provide one turn sequence per sub-agent; each stream() call pops one entry.
|
||||
let make_turn = |text: &str| {
|
||||
vec![
|
||||
LlmEvent::TextDelta(text.to_string()),
|
||||
LlmEvent::Done {
|
||||
stop_reason: StopReason::EndTurn,
|
||||
usage: TokenUsage {
|
||||
input_tokens: 10,
|
||||
output_tokens: 5,
|
||||
cache_creation_tokens: 0,
|
||||
cache_read_tokens: 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
let provider = Arc::new(MockLlmProvider::with_turns(vec![
|
||||
make_turn("result-A"),
|
||||
make_turn("result-B"),
|
||||
make_turn("result-C"),
|
||||
]));
|
||||
|
||||
let spawner = AgentSpawner::new(provider, test_config(), std::env::temp_dir());
|
||||
|
||||
let sub_configs = vec![
|
||||
make_sub_config("agent-A"),
|
||||
make_sub_config("agent-B"),
|
||||
make_sub_config("agent-C"),
|
||||
];
|
||||
|
||||
let results = spawner.spawn_parallel(sub_configs).await;
|
||||
|
||||
assert_eq!(results.len(), 3, "expected 3 results from 3 sub-agents");
|
||||
|
||||
for result in &results {
|
||||
assert!(
|
||||
!result.is_error,
|
||||
"sub-agent '{}' returned an error: {}",
|
||||
result.name, result.text
|
||||
);
|
||||
}
|
||||
|
||||
// Each result should contain one of the expected texts (order may vary due
|
||||
// to concurrent scheduling, so we just verify the full set is covered).
|
||||
let texts: std::collections::HashSet<&str> = results.iter().map(|r| r.text.as_str()).collect();
|
||||
assert!(texts.contains("result-A"), "missing result-A");
|
||||
assert!(texts.contains("result-B"), "missing result-B");
|
||||
assert!(texts.contains("result-C"), "missing result-C");
|
||||
}
|
||||
|
||||
/// The same provider Arc is reused across sequentially spawned sub-agents.
|
||||
#[tokio::test]
|
||||
async fn test_spawn_shares_provider() {
|
||||
// Two turns: one for each sequential sub-agent call.
|
||||
let provider = Arc::new(MockLlmProvider::with_turns(vec![
|
||||
vec![
|
||||
LlmEvent::TextDelta("first".to_string()),
|
||||
LlmEvent::Done {
|
||||
stop_reason: StopReason::EndTurn,
|
||||
usage: TokenUsage {
|
||||
input_tokens: 10,
|
||||
output_tokens: 5,
|
||||
cache_creation_tokens: 0,
|
||||
cache_read_tokens: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
vec![
|
||||
LlmEvent::TextDelta("second".to_string()),
|
||||
LlmEvent::Done {
|
||||
stop_reason: StopReason::EndTurn,
|
||||
usage: TokenUsage {
|
||||
input_tokens: 10,
|
||||
output_tokens: 5,
|
||||
cache_creation_tokens: 0,
|
||||
cache_read_tokens: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
]));
|
||||
|
||||
// Both sub-agents share the same underlying provider via Arc.
|
||||
let provider_dyn: Arc<dyn nomi_providers::LlmProvider> = provider;
|
||||
let spawner = AgentSpawner::new(
|
||||
Arc::clone(&provider_dyn),
|
||||
test_config(),
|
||||
std::env::temp_dir(),
|
||||
);
|
||||
|
||||
let result1 = spawner.spawn_one(make_sub_config("seq-1")).await;
|
||||
let result2 = spawner.spawn_one(make_sub_config("seq-2")).await;
|
||||
|
||||
assert!(!result1.is_error, "seq-1 errored: {}", result1.text);
|
||||
assert!(!result2.is_error, "seq-2 errored: {}", result2.text);
|
||||
assert_eq!(result1.text, "first");
|
||||
assert_eq!(result2.text, "second");
|
||||
}
|
||||
|
||||
/// An LLM error event causes the sub-agent result to be marked as an error.
|
||||
#[tokio::test]
|
||||
async fn test_spawn_agent_error_captured() {
|
||||
// Emit an Error event — the engine converts this to AgentError::ApiError,
|
||||
// which spawner catches and stores in SubAgentResult::is_error.
|
||||
let provider = Arc::new(MockLlmProvider::with_events(vec![LlmEvent::Error(
|
||||
"provider failed".to_string(),
|
||||
)]));
|
||||
|
||||
let spawner = AgentSpawner::new(provider, test_config(), std::env::temp_dir());
|
||||
|
||||
let result = spawner.spawn_one(make_sub_config("error-agent")).await;
|
||||
|
||||
assert!(result.is_error, "expected is_error=true");
|
||||
assert!(
|
||||
result.text.to_lowercase().contains("error"),
|
||||
"expected error message to contain 'error', got: {}",
|
||||
result.text
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user