//! Skill perception system for the AI agent. //! //! Provides three perception modes for injecting relevant skills into the agent's context: //! //! - **Auto (自动感知)**: Background awareness that scans conversation content for skill //! relevance based on keyword matching and semantic similarity. //! //! - **Active (主动感知)**: Proactive skill retrieval triggered by explicit user intent, //! such as mentioning a skill slug directly in the message. Both keyword and vector-based. //! //! - **Passive (被动感知)**: Reactive skill retrieval triggered by tool-call events, //! such as when the agent mentions a specific skill in its reasoning. Both keyword and //! vector-based. pub mod active; pub mod auto; pub mod passive; pub mod vector; pub use active::ActiveSkillAwareness; pub use auto::AutoSkillAwareness; pub use passive::PassiveSkillAwareness; pub use vector::{VectorActiveAwareness, VectorPassiveAwareness}; use async_openai::types::chat::ChatCompletionRequestMessage; /// A chunk of skill context ready to be injected into the message list. #[derive(Debug, Clone)] pub struct SkillContext { /// Human-readable label shown to the AI, e.g. "Active skill: code-review" pub label: String, /// The actual skill content to inject. pub content: String, } /// Converts skill context into a system message for injection. impl SkillContext { pub fn to_system_message(self) -> ChatCompletionRequestMessage { use async_openai::types::chat::{ ChatCompletionRequestSystemMessage, ChatCompletionRequestSystemMessageContent, }; ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { content: ChatCompletionRequestSystemMessageContent::Text(format!( "[{}]\n{}", self.label, self.content )), ..Default::default() }) } } /// Unified perception service combining all three modes. #[derive(Debug, Clone)] pub struct PerceptionService { pub auto: AutoSkillAwareness, pub active: ActiveSkillAwareness, pub passive: PassiveSkillAwareness, } impl Default for PerceptionService { fn default() -> Self { Self { auto: AutoSkillAwareness::default(), active: ActiveSkillAwareness::default(), passive: PassiveSkillAwareness::default(), } } } impl PerceptionService { /// Inject relevant skill context into the message list based on current conversation state. /// /// - **auto**: Scans the current input and conversation history for skill-relevant keywords /// and injects matching skills that are enabled. /// - **active**: Checks if the user explicitly invoked a skill by slug (e.g. "用 code-review") /// and injects it. /// - **passive**: Checks if any tool-call events or prior observations mention a skill /// slug and injects the matching skill. /// /// Returns a list of system messages to prepend to the conversation. pub async fn inject_skills( &self, input: &str, history: &[String], tool_calls: &[ToolCallEvent], enabled_skills: &[SkillEntry], ) -> Vec { let mut results = Vec::new(); // Active: explicit skill invocation (highest priority) if let Some(skill) = self.active.detect(input, enabled_skills) { results.push(skill); } // Passive: triggered by tool-call events for tc in tool_calls { if let Some(skill) = self.passive.detect(tc, enabled_skills) { if !results.iter().any(|r: &SkillContext| r.label == skill.label) { results.push(skill); } } } // Auto: keyword-based relevance matching let auto_results = self.auto.detect(input, history, enabled_skills).await; for skill in auto_results { if !results.iter().any(|r: &SkillContext| r.label == skill.label) { results.push(skill); } } results } } /// A tool-call event used for passive skill detection. #[derive(Debug, Clone)] pub struct ToolCallEvent { pub tool_name: String, pub arguments: String, } /// A skill entry from the database, used for matching. #[derive(Debug, Clone)] pub struct SkillEntry { pub slug: String, pub name: String, pub description: Option, pub content: String, }