refactor(chat): refactor skill context deduplication and injection

- Extract push_unique_skill_context method to MessageBuilder
- Merge built-in skills with DB skills in passive injection
- Simplify code structure for both streaming/nonstreaming execution
This commit is contained in:
ZhenYi 2026-05-17 17:32:19 +08:00
parent 5c1b14c26a
commit 60100a025c
3 changed files with 53 additions and 32 deletions

View File

@ -7,7 +7,7 @@ use crate::client::types::ChatRequestMessage;
use crate::compact::CompactService; use crate::compact::CompactService;
use crate::embed::EmbedService; use crate::embed::EmbedService;
use crate::error::Result; use crate::error::Result;
use crate::perception::{PerceptionService, SkillEntry}; use crate::perception::{PerceptionService, SkillContext, SkillEntry};
#[derive(Clone)] #[derive(Clone)]
pub struct MessageBuilder { pub struct MessageBuilder {
@ -393,21 +393,25 @@ impl MessageBuilder {
.await; .await;
} }
} }
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new(); let mut result = Vec::new();
for ctx in vector_skills {
if seen.insert(ctx.label.clone()) {
result.push(ctx);
}
}
for ctx in keyword_skills { for ctx in keyword_skills {
if seen.insert(ctx.label.clone()) { Self::push_unique_skill_context(&mut result, ctx);
result.push(ctx); }
} for ctx in vector_skills {
Self::push_unique_skill_context(&mut result, ctx);
} }
result result
} }
pub fn push_unique_skill_context(result: &mut Vec<SkillContext>, ctx: SkillContext) {
let key = ctx.dedupe_key();
if result.iter().any(|existing| existing.dedupe_key() == key) {
return;
}
result.push(ctx);
result.sort_by_key(|ctx| ctx.activation.rank());
}
async fn build_memory_context( async fn build_memory_context(
&self, &self,
request: &AiRequest, request: &AiRequest,

View File

@ -94,22 +94,10 @@ pub async fn execute_process(
.collect(); .collect();
let tool_names: Vec<String> = calls.iter().map(|call| call.name.clone()).collect(); let tool_names: Vec<String> = calls.iter().map(|call| call.name.clone()).collect();
let tool_messages = execute_tools( let tool_messages =
&request, execute_tools(&request, &calls, session_id, tool_registry, message_builder).await;
&calls,
session_id,
tool_registry,
message_builder,
)
.await;
messages.extend(tool_messages); messages.extend(tool_messages);
inject_passive_skills( inject_passive_skills(&request, message_builder, &tool_names, &mut messages).await;
&request,
message_builder,
&tool_names,
&mut messages,
)
.await;
tool_depth += 1; tool_depth += 1;
if tool_depth >= max_tool_depth { if tool_depth >= max_tool_depth {
@ -261,7 +249,7 @@ async fn inject_passive_skills(
.all(&request.db) .all(&request.db)
.await .await
{ {
let skill_entries: Vec<SkillEntry> = skills let mut skill_entries: Vec<SkillEntry> = skills
.into_iter() .into_iter()
.map(|s| SkillEntry { .map(|s| SkillEntry {
slug: s.slug, slug: s.slug,
@ -270,6 +258,16 @@ async fn inject_passive_skills(
content: s.content, content: s.content,
}) })
.collect(); .collect();
for built_in in crate::skills::all_skills() {
if !skill_entries.iter().any(|s| s.slug == built_in.slug) {
skill_entries.push(SkillEntry {
slug: built_in.slug.to_string(),
name: built_in.name.to_string(),
description: Some(built_in.description.to_string()),
content: built_in.content.clone(),
});
}
}
let tool_events: Vec<ToolCallEvent> = tool_names let tool_events: Vec<ToolCallEvent> = tool_names
.iter() .iter()
.map(|name| ToolCallEvent { .map(|name| ToolCallEvent {
@ -277,14 +275,18 @@ async fn inject_passive_skills(
arguments: String::new(), arguments: String::new(),
}) })
.collect(); .collect();
let mut contexts = Vec::new();
for event in &tool_events { for event in &tool_events {
if let Some(ctx) = message_builder if let Some(ctx) = message_builder
.perception_service .perception_service
.passive .passive
.detect(event, &skill_entries) .detect(event, &skill_entries)
{ {
messages.push(ctx.to_system_message()); MessageBuilder::push_unique_skill_context(&mut contexts, ctx);
} }
} }
for ctx in contexts {
messages.push(ctx.to_system_message());
}
} }
} }

View File

@ -9,9 +9,9 @@ use super::message_builder::MessageBuilder;
use super::service::StreamResult; use super::service::StreamResult;
use super::session_recording::record_ai_session; use super::session_recording::record_ai_session;
use super::{AiChunkType, AiRequest, AiStreamChunk, StreamCallback}; use super::{AiChunkType, AiRequest, AiStreamChunk, StreamCallback};
use crate::client::types::{ChatRequestMessage, ToolCall};
use crate::client::AiClientConfig; use crate::client::AiClientConfig;
use crate::client::{call_stream, StreamChunk, StreamChunkType, StreamedToolCall}; use crate::client::types::{ChatRequestMessage, ToolCall};
use crate::client::{StreamChunk, StreamChunkType, StreamedToolCall, call_stream};
use crate::error::Result; use crate::error::Result;
use crate::perception::{SkillEntry, ToolCallEvent}; use crate::perception::{SkillEntry, ToolCallEvent};
use crate::tool::{ToolCall as AgentToolCall, ToolContext, ToolExecutor}; use crate::tool::{ToolCall as AgentToolCall, ToolContext, ToolExecutor};
@ -266,7 +266,8 @@ async fn drain_tool_call_notifications(
chunk_type: AiChunkType::ToolCall, chunk_type: AiChunkType::ToolCall,
metadata: Some(metadata), metadata: Some(metadata),
children_id: None, children_id: None,
}).await; })
.await;
all_chunks.push(StreamChunk { all_chunks.push(StreamChunk {
chunk_type: StreamChunkType::ToolCall, chunk_type: StreamChunkType::ToolCall,
content: tool_display, content: tool_display,
@ -467,7 +468,7 @@ async fn inject_passive_skills_stream(
.all(&request.db) .all(&request.db)
.await .await
{ {
let skill_entries: Vec<SkillEntry> = skills let mut skill_entries: Vec<SkillEntry> = skills
.into_iter() .into_iter()
.map(|s| SkillEntry { .map(|s| SkillEntry {
slug: s.slug, slug: s.slug,
@ -476,6 +477,16 @@ async fn inject_passive_skills_stream(
content: s.content, content: s.content,
}) })
.collect(); .collect();
for built_in in crate::skills::all_skills() {
if !skill_entries.iter().any(|s| s.slug == built_in.slug) {
skill_entries.push(SkillEntry {
slug: built_in.slug.to_string(),
name: built_in.name.to_string(),
description: Some(built_in.description.to_string()),
content: built_in.content.clone(),
});
}
}
let tool_events: Vec<ToolCallEvent> = tool_calls let tool_events: Vec<ToolCallEvent> = tool_calls
.iter() .iter()
.map(|tc| ToolCallEvent { .map(|tc| ToolCallEvent {
@ -483,14 +494,18 @@ async fn inject_passive_skills_stream(
arguments: tc.arguments.clone(), arguments: tc.arguments.clone(),
}) })
.collect(); .collect();
let mut contexts = Vec::new();
for event in &tool_events { for event in &tool_events {
if let Some(ctx) = message_builder if let Some(ctx) = message_builder
.perception_service .perception_service
.passive .passive
.detect(event, &skill_entries) .detect(event, &skill_entries)
{ {
messages.push(ctx.to_system_message()); MessageBuilder::push_unique_skill_context(&mut contexts, ctx);
} }
} }
for ctx in contexts {
messages.push(ctx.to_system_message());
}
} }
} }