gitdataai/libs/agent/perception/mod.rs
2026-04-14 19:02:01 +08:00

132 lines
4.4 KiB
Rust

//! 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<SkillContext> {
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<String>,
pub content: String,
}