use async_trait::async_trait; use crate::{ callbacks::CreativeAgentCallbacks, error::PlatformAgentError, function_agent::FunctionAgentLimits, }; #[derive(Clone, Debug, PartialEq, Eq)] pub struct PuzzlePhase1AgentInput { pub session_id: String, pub user_text: String, pub image_urls: Vec, pub limits: FunctionAgentLimits, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PuzzlePhase1AgentOutput { pub session_id: String, pub model: String, pub final_message: String, pub tool_call_count: usize, } #[async_trait] pub trait CreativeAgentExecutor: Send + Sync { async fn run_puzzle_phase1( &self, input: PuzzlePhase1AgentInput, callbacks: CreativeAgentCallbacks, ) -> Result; } #[derive(Clone, Debug, Default)] pub struct MockLangChainRustAgentExecutor; #[async_trait] impl CreativeAgentExecutor for MockLangChainRustAgentExecutor { async fn run_puzzle_phase1( &self, input: PuzzlePhase1AgentInput, callbacks: CreativeAgentCallbacks, ) -> Result { input.limits.validate()?; if input.session_id.trim().is_empty() { return Err(PlatformAgentError::InvalidInput( "creative session_id 不能为空".to_string(), )); } if input.user_text.trim().is_empty() && input.image_urls.is_empty() { return Err(PlatformAgentError::InvalidInput( "创意 Agent 输入文本和图片不能同时为空".to_string(), )); } callbacks.stage("perceiving"); callbacks.stage("thinking"); callbacks.stage("remembering"); callbacks.stage("selecting_puzzle_template"); callbacks.stage("waiting_template_confirmation"); Ok(PuzzlePhase1AgentOutput { session_id: input.session_id, model: crate::apimart_gpt5_adapter::CREATIVE_AGENT_GPT5_MODEL.to_string(), final_message: "已完成创意 Agent Phase 1 mock 执行,可进入模板确认。".to_string(), tool_call_count: 0, }) } } #[cfg(test)] mod tests { use std::sync::{Arc, Mutex}; use super::*; #[tokio::test] async fn mock_executor_keeps_gpt5_model_and_emits_stages() { let events = Arc::new(Mutex::new(Vec::new())); let events_clone = events.clone(); let callbacks = CreativeAgentCallbacks::new(move |event| { events_clone.lock().expect("events lock").push(event.label); }); let output = MockLangChainRustAgentExecutor .run_puzzle_phase1( PuzzlePhase1AgentInput { session_id: "creative-session-test".to_string(), user_text: "做一个家庭纪念拼图".to_string(), image_urls: vec!["https://example.com/ref.png".to_string()], limits: FunctionAgentLimits::default(), }, callbacks, ) .await .expect("mock executor should succeed"); assert_eq!( output.model, crate::apimart_gpt5_adapter::CREATIVE_AGENT_GPT5_MODEL ); assert_eq!( events.lock().expect("events lock").as_slice(), [ "perceiving", "thinking", "remembering", "selecting_puzzle_template", "waiting_template_confirmation", ] ); } }