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::embed::EmbedService;
use crate::error::Result;
use crate::perception::{PerceptionService, SkillEntry};
use crate::perception::{PerceptionService, SkillContext, SkillEntry};
#[derive(Clone)]
pub struct MessageBuilder {
@ -393,21 +393,25 @@ impl MessageBuilder {
.await;
}
}
let mut seen = std::collections::HashSet::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 {
if seen.insert(ctx.label.clone()) {
result.push(ctx);
Self::push_unique_skill_context(&mut result, ctx);
}
for ctx in vector_skills {
Self::push_unique_skill_context(&mut result, ctx);
}
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(
&self,
request: &AiRequest,

View File

@ -94,22 +94,10 @@ pub async fn execute_process(
.collect();
let tool_names: Vec<String> = calls.iter().map(|call| call.name.clone()).collect();
let tool_messages = execute_tools(
&request,
&calls,
session_id,
tool_registry,
message_builder,
)
.await;
let tool_messages =
execute_tools(&request, &calls, session_id, tool_registry, message_builder).await;
messages.extend(tool_messages);
inject_passive_skills(
&request,
message_builder,
&tool_names,
&mut messages,
)
.await;
inject_passive_skills(&request, message_builder, &tool_names, &mut messages).await;
tool_depth += 1;
if tool_depth >= max_tool_depth {
@ -261,7 +249,7 @@ async fn inject_passive_skills(
.all(&request.db)
.await
{
let skill_entries: Vec<SkillEntry> = skills
let mut skill_entries: Vec<SkillEntry> = skills
.into_iter()
.map(|s| SkillEntry {
slug: s.slug,
@ -270,6 +258,16 @@ async fn inject_passive_skills(
content: s.content,
})
.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
.iter()
.map(|name| ToolCallEvent {
@ -277,14 +275,18 @@ async fn inject_passive_skills(
arguments: String::new(),
})
.collect();
let mut contexts = Vec::new();
for event in &tool_events {
if let Some(ctx) = message_builder
.perception_service
.passive
.detect(event, &skill_entries)
{
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::session_recording::record_ai_session;
use super::{AiChunkType, AiRequest, AiStreamChunk, StreamCallback};
use crate::client::types::{ChatRequestMessage, ToolCall};
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::perception::{SkillEntry, ToolCallEvent};
use crate::tool::{ToolCall as AgentToolCall, ToolContext, ToolExecutor};
@ -266,7 +266,8 @@ async fn drain_tool_call_notifications(
chunk_type: AiChunkType::ToolCall,
metadata: Some(metadata),
children_id: None,
}).await;
})
.await;
all_chunks.push(StreamChunk {
chunk_type: StreamChunkType::ToolCall,
content: tool_display,
@ -467,7 +468,7 @@ async fn inject_passive_skills_stream(
.all(&request.db)
.await
{
let skill_entries: Vec<SkillEntry> = skills
let mut skill_entries: Vec<SkillEntry> = skills
.into_iter()
.map(|s| SkillEntry {
slug: s.slug,
@ -476,6 +477,16 @@ async fn inject_passive_skills_stream(
content: s.content,
})
.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
.iter()
.map(|tc| ToolCallEvent {
@ -483,14 +494,18 @@ async fn inject_passive_skills_stream(
arguments: tc.arguments.clone(),
})
.collect();
let mut contexts = Vec::new();
for event in &tool_events {
if let Some(ctx) = message_builder
.perception_service
.passive
.detect(event, &skill_entries)
{
MessageBuilder::push_unique_skill_context(&mut contexts, ctx);
}
}
for ctx in contexts {
messages.push(ctx.to_system_message());
}
}
}
}