fix(core): 修复 v0.3.0 审查发现的 6 项警告问题
- InMemoryGraph 的 10 处 lock().unwrap() 改为错误传播,避免 Mutex poison 时 panic - SubTaskResult 添加 Serialize/Deserialize,支持结果序列化 - dispatch_all 移除多余的 task.clone() - truncate_total_chars 补充字节切片安全性注释 - SessionManager::children 添加 ponytail 注释标记 O(N) 扫描现状 - SessionMemoryEntry.created_at 添加 i64 类型说明注释
This commit is contained in:
@@ -124,7 +124,9 @@ fn truncate_total_chars(s: &str, max_chars: usize) -> String {
|
|||||||
let dropped: String = s.chars().take(skip).collect();
|
let dropped: String = s.chars().take(skip).collect();
|
||||||
let mut kept = String::with_capacity(max_chars + 8);
|
let mut kept = String::with_capacity(max_chars + 8);
|
||||||
kept.push_str("[... earlier messages truncated ...]\n");
|
kept.push_str("[... earlier messages truncated ...]\n");
|
||||||
kept.push_str(&s[dropped.len()..]); // 字节切:dropped.len() 字节一定在 char 边界
|
// 字节切安全:dropped 由 s.chars().take(skip).collect() 构建,
|
||||||
|
// 每个 char 的 UTF-8 字节序列完整保留,故 dropped.len() 恰好是 s 的某个 char 边界字节偏移。
|
||||||
|
kept.push_str(&s[dropped.len()..]);
|
||||||
kept
|
kept
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -446,6 +446,8 @@ impl SessionManager {
|
|||||||
/// 查询某 parent 的所有直接子 session 的 ID 列表。
|
/// 查询某 parent 的所有直接子 session 的 ID 列表。
|
||||||
///
|
///
|
||||||
/// 实现:prefix 查询所有 `session:*:meta`,过滤 `parent_id == parent_id`。
|
/// 实现:prefix 查询所有 `session:*:meta`,过滤 `parent_id == parent_id`。
|
||||||
|
// ponytail: O(N) 全表扫描,当前规模(≤10K session)可接受。
|
||||||
|
// 如有性能需求,可维护 `parent:{parent_id}:children` 索引 key 替代扫描。
|
||||||
pub async fn children(&self, parent_id: &str) -> Result<Vec<String>, EngineError> {
|
pub async fn children(&self, parent_id: &str) -> Result<Vec<String>, EngineError> {
|
||||||
let filter = MemoryFilter {
|
let filter = MemoryFilter {
|
||||||
prefix: Some("session:".to_string()),
|
prefix: Some("session:".to_string()),
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ pub struct SessionMemoryEntry {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub metadata: serde_json::Value,
|
pub metadata: serde_json::Value,
|
||||||
/// 创建时间(Unix 时间戳秒;`None` 兼容旧快照)。
|
/// 创建时间(Unix 时间戳秒;`None` 兼容旧快照)。
|
||||||
|
///
|
||||||
|
/// 类型为 `i64` 而非 `u64`:`time` crate 的 `OffsetDateTime::from_unix_timestamp` 接收 `i64`,
|
||||||
|
/// 这里保持与 `SessionMemory::set_with_meta` 签名一致,避免来回转换。
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub created_at: Option<i64>,
|
pub created_at: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
//! - 可增加 `cancel_all(parent_id)` 中止正在运行的子 session
|
//! - 可增加 `cancel_all(parent_id)` 中止正在运行的子 session
|
||||||
//! - 可暴露 `child_session_tree` API 支持会话树查询
|
//! - 可暴露 `child_session_tree` API 支持会话树查询
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::llm::stream::StreamEvent;
|
use crate::llm::stream::StreamEvent;
|
||||||
use crate::llm::types::response_v2::MessageResponse;
|
use crate::llm::types::response_v2::MessageResponse;
|
||||||
use crate::llm::types::usage::CostTracker;
|
use crate::llm::types::usage::CostTracker;
|
||||||
@@ -53,7 +55,7 @@ impl Default for DispatchConfig {
|
|||||||
/// dispatch 成功后子 session **保留在 SessionManager 中**,调用方可
|
/// dispatch 成功后子 session **保留在 SessionManager 中**,调用方可
|
||||||
/// 通过 `sm.get(&result.child_id)` 获取子 session 引用,进而通过
|
/// 通过 `sm.get(&result.child_id)` 获取子 session 引用,进而通过
|
||||||
/// `session_memory()` 读取子 SessionMemory(如 `"result_summary"`)。
|
/// `session_memory()` 读取子 SessionMemory(如 `"result_summary"`)。
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct SubTaskResult {
|
pub struct SubTaskResult {
|
||||||
/// 子 session ID。可通过此 ID 在 SessionManager 中读取子 session。
|
/// 子 session ID。可通过此 ID 在 SessionManager 中读取子 session。
|
||||||
pub child_id: String,
|
pub child_id: String,
|
||||||
@@ -273,7 +275,6 @@ impl SessionManager {
|
|||||||
let sm = self.clone();
|
let sm = self.clone();
|
||||||
let parent_id = parent_id_owned.clone();
|
let parent_id = parent_id_owned.clone();
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
let task = task.clone();
|
|
||||||
|
|
||||||
handles.push(tokio::spawn(async move {
|
handles.push(tokio::spawn(async move {
|
||||||
let _permit = sem.acquire_owned().await.expect("Semaphore closed");
|
let _permit = sem.acquire_owned().await.expect("Semaphore closed");
|
||||||
|
|||||||
+30
-10
@@ -253,7 +253,9 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
if entity.id.is_empty() {
|
if entity.id.is_empty() {
|
||||||
return Err(MemoryError::InvalidInput("entity id is empty".into()));
|
return Err(MemoryError::InvalidInput("entity id is empty".into()));
|
||||||
}
|
}
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
// upsert:若已存在,先收集旧标签用于清理反向引用(避免同时借用 entities 和 tag_index)
|
// upsert:若已存在,先收集旧标签用于清理反向引用(避免同时借用 entities 和 tag_index)
|
||||||
let old_tags: Vec<String> = inner
|
let old_tags: Vec<String> = inner
|
||||||
.entities
|
.entities
|
||||||
@@ -281,12 +283,16 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_entity(&self, id: &str) -> Result<Option<GraphEntity>, MemoryError> {
|
async fn get_entity(&self, id: &str) -> Result<Option<GraphEntity>, MemoryError> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
Ok(inner.entities.get(id).cloned())
|
Ok(inner.entities.get(id).cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_entity(&self, id: &str) -> Result<(), MemoryError> {
|
async fn remove_entity(&self, id: &str) -> Result<(), MemoryError> {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
// 移除实体并清理其标签反向引用
|
// 移除实体并清理其标签反向引用
|
||||||
if let Some(entity) = inner.entities.remove(id) {
|
if let Some(entity) = inner.entities.remove(id) {
|
||||||
for tag in &entity.tags {
|
for tag in &entity.tags {
|
||||||
@@ -306,7 +312,9 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn add_relation(&self, relation: GraphRelation) -> Result<(), MemoryError> {
|
async fn add_relation(&self, relation: GraphRelation) -> Result<(), MemoryError> {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
// 校验两端实体存在
|
// 校验两端实体存在
|
||||||
if !inner.entities.contains_key(&relation.source_id) {
|
if !inner.entities.contains_key(&relation.source_id) {
|
||||||
return Err(MemoryError::InvalidInput(format!(
|
return Err(MemoryError::InvalidInput(format!(
|
||||||
@@ -340,7 +348,9 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
target_id: &str,
|
target_id: &str,
|
||||||
relation_type: &str,
|
relation_type: &str,
|
||||||
) -> Result<(), MemoryError> {
|
) -> Result<(), MemoryError> {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
inner.relations.retain(|r| {
|
inner.relations.retain(|r| {
|
||||||
!(r.source_id == source_id && r.target_id == target_id && r.relation_type == relation_type)
|
!(r.source_id == source_id && r.target_id == target_id && r.relation_type == relation_type)
|
||||||
});
|
});
|
||||||
@@ -354,7 +364,9 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
direction: RelationDirection,
|
direction: RelationDirection,
|
||||||
relation_types: Option<&[&str]>,
|
relation_types: Option<&[&str]>,
|
||||||
) -> Result<Vec<ScoredEntity>, MemoryError> {
|
) -> Result<Vec<ScoredEntity>, MemoryError> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
// 1. 验证起点存在
|
// 1. 验证起点存在
|
||||||
if !inner.entities.contains_key(entity_id) {
|
if !inner.entities.contains_key(entity_id) {
|
||||||
@@ -458,7 +470,9 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn find_by_keywords(&self, keywords: &[String]) -> Result<Vec<GraphEntity>, MemoryError> {
|
async fn find_by_keywords(&self, keywords: &[String]) -> Result<Vec<GraphEntity>, MemoryError> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
if keywords.is_empty() {
|
if keywords.is_empty() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
@@ -481,7 +495,9 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn find_tags(&self, prefix: &str) -> Result<Vec<String>, MemoryError> {
|
async fn find_tags(&self, prefix: &str) -> Result<Vec<String>, MemoryError> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
let prefix_l = prefix.to_lowercase();
|
let prefix_l = prefix.to_lowercase();
|
||||||
let mut tags: Vec<String> = inner
|
let mut tags: Vec<String> = inner
|
||||||
.tag_index
|
.tag_index
|
||||||
@@ -498,7 +514,9 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
entity_id: &str,
|
entity_id: &str,
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
) -> Result<usize, MemoryError> {
|
) -> Result<usize, MemoryError> {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
// 先收集旧标签(避免同时借用 entities 和 tag_index)
|
// 先收集旧标签(避免同时借用 entities 和 tag_index)
|
||||||
let old_tags: Vec<String> = inner
|
let old_tags: Vec<String> = inner
|
||||||
@@ -536,7 +554,9 @@ impl KnowledgeGraph for InMemoryGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn entity_count_by_tag(&self, tag: &str) -> Result<usize, MemoryError> {
|
async fn entity_count_by_tag(&self, tag: &str) -> Result<usize, MemoryError> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().map_err(|e| {
|
||||||
|
MemoryError::RetrievalError(format!("lock poisoned: {e}"))
|
||||||
|
})?;
|
||||||
Ok(inner
|
Ok(inner
|
||||||
.tag_index
|
.tag_index
|
||||||
.get(tag)
|
.get(tag)
|
||||||
|
|||||||
Reference in New Issue
Block a user