新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展 - 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通 - 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦 - AI planner/plan_hint/intent:aichat B 路线并行多轮基础 - patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环 - 压缩超时兜底(F-15 卡死根治) - F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本 - 知识注入 DRY/skills/audit 扩展 清理: - aichat 技术债(误报 allow/死导入/过时注释 30 项) - URGENT.md 删除(11 项加急全解决/迁 todo) - 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
@@ -17,22 +17,38 @@ use super::{now_millis_str, storage_err, validate_column_name};
|
||||
// 知识库 SELECT 列清单(防 COLS 漂移)
|
||||
// ============================================================
|
||||
|
||||
/// `knowledges` 表对应 `KnowledgeRecord` 14 个字段的列名(顺序与结构体一致)。
|
||||
/// `knowledges` 表对应 `KnowledgeRecord` 15 个字段的列名(顺序与结构体一致)。
|
||||
///
|
||||
/// 多处 `search`/`search_vector` 内联 COLS 串的 DRY 收口(CR-260615-03):集中一处定义,
|
||||
/// 配合下方 `KNOWLEDGE_COL_COUNT` 断言,任一处加列漏改会被测试 `test_knowledge_cols_matches_record`
|
||||
/// 立即捕获(`knowledge_from_row` 按 name 取列,SELECT 漏列会运行时 rusqlite 报错,故提前断言)。
|
||||
const KNOWLEDGE_COLS: &str = "id,kind,title,content,tags,status,confidence,reuse_count,verified,source_project,source_ref,reasoning,created_at,updated_at";
|
||||
///
|
||||
/// V23 新增 embedding_status 列(嵌入失败可补偿重试),已纳入列清单 + 计数。
|
||||
const KNOWLEDGE_COLS: &str = "id,kind,title,content,tags,status,confidence,reuse_count,verified,source_project,source_ref,reasoning,embedding_status,created_at,updated_at";
|
||||
/// `KnowledgeRecord` 字段数(与上面列清单的逗号分隔项数一致,被测试断言)。
|
||||
/// 仅测试期消费(列漂移断言);保留为非 `cfg(test)` 以便测试外的阅读者一眼看到字段数。
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
const KNOWLEDGE_COL_COUNT: usize = 14;
|
||||
const KNOWLEDGE_COL_COUNT: usize = 15;
|
||||
/// `search_vector` 用的列清单:KNOWLEDGE_COLS + embedding(余弦计算用,不入 KnowledgeRecord)。
|
||||
const KNOWLEDGE_COLS_WITH_EMBEDDING: &str = concat!(
|
||||
"id,kind,title,content,tags,status,confidence,reuse_count,verified,",
|
||||
"source_project,source_ref,reasoning,created_at,updated_at,embedding"
|
||||
"source_project,source_ref,reasoning,embedding_status,created_at,updated_at,embedding"
|
||||
);
|
||||
|
||||
/// `ideas` 表对应 `IdeaRecord` 14 个字段的列名(顺序与结构体一致)。
|
||||
///
|
||||
/// 同 KNOWLEDGE_COLS 的列漂移防护(CR-260615-03):idea 表 INSERT/UPDATE/from_row 三处
|
||||
/// 各写一份列名串,加列须三处同步(如 V24 加 related_ids 即三处齐改),漏一处
|
||||
/// 只在运行时 rusqlite 报错(INSERT 列数与参数数不匹配 / from_row 取不到列)。集中一处
|
||||
/// 定义 + 配合 `IDEA_COL_COUNT` 断言 + 测试 `test_idea_cols_matches_record`,加列漏改即捕获。
|
||||
///
|
||||
/// V24 新增 related_ids 列(灵感间关联关系持久化打底),已纳入列清单 + 计数。
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
const IDEA_COLS: &str = "id,title,description,status,priority,score,tags,source,promoted_to,ai_analysis,scores,related_ids,created_at,updated_at";
|
||||
/// `IdeaRecord` 字段数(与上面列清单的逗号分隔项数一致,被测试断言)。
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
const IDEA_COL_COUNT: usize = 14;
|
||||
|
||||
// ============================================================
|
||||
// 向量工具 — embedding BLOB 序列化 + 余弦相似度
|
||||
// ============================================================
|
||||
@@ -74,6 +90,7 @@ fn idea_from_row(row: &Row<'_>) -> std::result::Result<IdeaRecord, rusqlite::Err
|
||||
promoted_to: row.get("promoted_to")?,
|
||||
ai_analysis: row.get("ai_analysis")?,
|
||||
scores: row.get("scores")?,
|
||||
related_ids: row.get("related_ids")?,
|
||||
created_at: row.get("created_at")?,
|
||||
updated_at: row.get("updated_at")?,
|
||||
})
|
||||
@@ -93,6 +110,7 @@ fn knowledge_from_row(row: &Row<'_>) -> std::result::Result<KnowledgeRecord, rus
|
||||
source_project: row.get("source_project")?,
|
||||
source_ref: row.get("source_ref")?,
|
||||
reasoning: row.get("reasoning")?,
|
||||
embedding_status: row.get("embedding_status")?,
|
||||
created_at: row.get("created_at")?,
|
||||
updated_at: row.get("updated_at")?,
|
||||
})
|
||||
@@ -121,22 +139,22 @@ impl_repo!(
|
||||
from_row => |row| idea_from_row(row),
|
||||
insert => |conn, rec| {
|
||||
conn.execute(
|
||||
"INSERT INTO ideas (id, title, description, status, priority, score, tags, source, promoted_to, ai_analysis, scores, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
|
||||
"INSERT INTO ideas (id, title, description, status, priority, score, tags, source, promoted_to, ai_analysis, scores, related_ids, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
|
||||
params![
|
||||
rec.id, rec.title, rec.description, rec.status, rec.priority,
|
||||
rec.score, rec.tags, rec.source, rec.promoted_to, rec.ai_analysis,
|
||||
rec.scores, rec.created_at, rec.updated_at
|
||||
rec.scores, rec.related_ids, rec.created_at, rec.updated_at
|
||||
],
|
||||
)
|
||||
},
|
||||
update => |conn, rec| {
|
||||
conn.execute(
|
||||
"UPDATE ideas SET title = ?1, description = ?2, status = ?3, priority = ?4, score = ?5, tags = ?6, source = ?7, promoted_to = ?8, ai_analysis = ?9, scores = ?10, updated_at = ?11 WHERE id = ?12",
|
||||
"UPDATE ideas SET title = ?1, description = ?2, status = ?3, priority = ?4, score = ?5, tags = ?6, source = ?7, promoted_to = ?8, ai_analysis = ?9, scores = ?10, related_ids = ?11, updated_at = ?12 WHERE id = ?13",
|
||||
params![
|
||||
rec.title, rec.description, rec.status, rec.priority,
|
||||
rec.score, rec.tags, rec.source, rec.promoted_to, rec.ai_analysis,
|
||||
rec.scores, rec.updated_at, rec.id
|
||||
rec.scores, rec.related_ids, rec.updated_at, rec.id
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -151,23 +169,23 @@ impl_repo!(
|
||||
insert => |conn, rec| {
|
||||
let verified = if rec.verified { 1i32 } else { 0i32 };
|
||||
conn.execute(
|
||||
"INSERT INTO knowledges (id, kind, title, content, tags, status, confidence, reuse_count, verified, source_project, source_ref, reasoning, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
|
||||
"INSERT INTO knowledges (id, kind, title, content, tags, status, confidence, reuse_count, verified, source_project, source_ref, reasoning, embedding_status, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)",
|
||||
params![
|
||||
rec.id, rec.kind, rec.title, rec.content, rec.tags, rec.status, rec.confidence,
|
||||
rec.reuse_count, verified, rec.source_project, rec.source_ref, rec.reasoning,
|
||||
rec.created_at, rec.updated_at
|
||||
rec.embedding_status, rec.created_at, rec.updated_at
|
||||
],
|
||||
)
|
||||
},
|
||||
update => |conn, rec| {
|
||||
let verified = if rec.verified { 1i32 } else { 0i32 };
|
||||
conn.execute(
|
||||
"UPDATE knowledges SET kind = ?1, title = ?2, content = ?3, tags = ?4, status = ?5, confidence = ?6, reuse_count = ?7, verified = ?8, source_project = ?9, source_ref = ?10, reasoning = ?11, updated_at = ?12 WHERE id = ?13",
|
||||
"UPDATE knowledges SET kind = ?1, title = ?2, content = ?3, tags = ?4, status = ?5, confidence = ?6, reuse_count = ?7, verified = ?8, source_project = ?9, source_ref = ?10, reasoning = ?11, embedding_status = ?12, updated_at = ?13 WHERE id = ?14",
|
||||
params![
|
||||
rec.kind, rec.title, rec.content, rec.tags, rec.status, rec.confidence,
|
||||
rec.reuse_count, verified, rec.source_project, rec.source_ref, rec.reasoning,
|
||||
rec.updated_at, rec.id
|
||||
rec.embedding_status, rec.updated_at, rec.id
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -223,17 +241,15 @@ impl KnowledgeRepo {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let guard = conn.blocking_lock();
|
||||
let mut stmt = guard
|
||||
.prepare(
|
||||
"SELECT id,kind,title,content,tags,status,confidence,reuse_count,verified,\
|
||||
source_project,source_ref,reasoning,created_at,updated_at \
|
||||
FROM knowledges WHERE status = ?1
|
||||
.prepare(&format!(
|
||||
"SELECT {KNOWLEDGE_COLS} FROM knowledges WHERE status = ?1
|
||||
ORDER BY CASE confidence
|
||||
WHEN 'high' THEN 3
|
||||
WHEN 'medium' THEN 2
|
||||
WHEN 'low' THEN 1
|
||||
ELSE 0
|
||||
END DESC, created_at DESC",
|
||||
)
|
||||
))
|
||||
.map_err(storage_err)?;
|
||||
let rows = stmt
|
||||
.query_map(params![status], |row| knowledge_from_row(row))
|
||||
@@ -270,6 +286,7 @@ impl KnowledgeRepo {
|
||||
/// 写入向量嵌入(BLOB = Vec<f32> 小端字节序列化)
|
||||
///
|
||||
/// embedding 列不进 KnowledgeRecord(IPC 不需要传向量给前端),专用方法读写。
|
||||
/// V23:同时把 embedding_status 置 'done'(成功标记),供补偿重试逻辑判别。
|
||||
pub async fn set_embedding(&self, id: &str, embedding: &[f32]) -> Result<bool> {
|
||||
let conn = self.conn.clone();
|
||||
let id = id.to_owned();
|
||||
@@ -278,7 +295,7 @@ impl KnowledgeRepo {
|
||||
let guard = conn.blocking_lock();
|
||||
let affected = guard
|
||||
.execute(
|
||||
"UPDATE knowledges SET embedding = ?1 WHERE id = ?2",
|
||||
"UPDATE knowledges SET embedding = ?1, embedding_status = 'done' WHERE id = ?2",
|
||||
params![blob, id],
|
||||
)
|
||||
.map_err(storage_err)?;
|
||||
@@ -288,6 +305,54 @@ impl KnowledgeRepo {
|
||||
.map_err(storage_err)?
|
||||
}
|
||||
|
||||
/// 标记嵌入生成失败(embedding_status = 'failed'),供补偿重试逻辑定位。
|
||||
///
|
||||
/// 失败时不写 embedding(保持 NULL,检索侧 `embedding IS NOT NULL` 自然跳过该条走 LIKE)。
|
||||
/// 幂等:重复标记 failed 无副作用(同值覆写)。
|
||||
pub async fn mark_embedding_failed(&self, id: &str) -> Result<bool> {
|
||||
let conn = self.conn.clone();
|
||||
let id = id.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let guard = conn.blocking_lock();
|
||||
let affected = guard
|
||||
.execute(
|
||||
"UPDATE knowledges SET embedding_status = 'failed' WHERE id = ?1",
|
||||
params![id],
|
||||
)
|
||||
.map_err(storage_err)?;
|
||||
Ok(affected > 0)
|
||||
})
|
||||
.await
|
||||
.map_err(storage_err)?
|
||||
}
|
||||
|
||||
/// 列出 embedding_status = 'failed' 的已发布知识(补偿重试入口用)。
|
||||
///
|
||||
/// 仅返回 published(候选/归档不参与检索,重试无意义),按 created_at 升序(老条目优先补)。
|
||||
pub async fn list_failed_embeddings(&self) -> Result<Vec<KnowledgeRecord>> {
|
||||
let conn = self.conn.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let guard = conn.blocking_lock();
|
||||
let mut stmt = guard
|
||||
.prepare(&format!(
|
||||
"SELECT {KNOWLEDGE_COLS} FROM knowledges \
|
||||
WHERE status = 'published' AND embedding_status = 'failed' \
|
||||
ORDER BY created_at ASC"
|
||||
))
|
||||
.map_err(storage_err)?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| knowledge_from_row(row))
|
||||
.map_err(storage_err)?;
|
||||
let mut results = Vec::new();
|
||||
for r in rows {
|
||||
results.push(r.map_err(storage_err)?);
|
||||
}
|
||||
Ok(results)
|
||||
})
|
||||
.await
|
||||
.map_err(storage_err)?
|
||||
}
|
||||
|
||||
/// 向量检索: 加载全部 published 且有 embedding 的记录,纯 Rust 余弦相似度取 top-N
|
||||
///
|
||||
/// 返回 (记录, 相似度分数)。数据量 <50k 时暴力遍历 <50ms,够用;
|
||||
@@ -308,7 +373,7 @@ impl KnowledgeRepo {
|
||||
let query_vec = query_vec.to_vec();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let guard = conn.blocking_lock();
|
||||
// 显式列: 14 个 KnowledgeRecord 字段 + embedding(余弦计算用,不入 KnowledgeRecord)
|
||||
// 显式列: 15 个 KnowledgeRecord 字段(含 embedding_status)+ embedding(余弦计算用,不入 KnowledgeRecord)
|
||||
let mut stmt = guard
|
||||
.prepare(&format!(
|
||||
"SELECT {KNOWLEDGE_COLS_WITH_EMBEDDING} FROM knowledges WHERE status = 'published' AND embedding IS NOT NULL"
|
||||
@@ -347,17 +412,15 @@ impl KnowledgeRepo {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let guard = conn.blocking_lock();
|
||||
let mut stmt = guard
|
||||
.prepare(
|
||||
"SELECT id,kind,title,content,tags,status,confidence,reuse_count,verified,\
|
||||
source_project,source_ref,reasoning,created_at,updated_at \
|
||||
FROM knowledges WHERE status != 'archived'
|
||||
.prepare(&format!(
|
||||
"SELECT {KNOWLEDGE_COLS} FROM knowledges WHERE status != 'archived'
|
||||
ORDER BY CASE confidence
|
||||
WHEN 'high' THEN 3
|
||||
WHEN 'medium' THEN 2
|
||||
WHEN 'low' THEN 1
|
||||
ELSE 0
|
||||
END DESC, created_at DESC",
|
||||
)
|
||||
))
|
||||
.map_err(storage_err)?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| knowledge_from_row(row))
|
||||
@@ -379,7 +442,9 @@ impl KnowledgeRepo {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let guard = conn.blocking_lock();
|
||||
let mut stmt = guard
|
||||
.prepare("SELECT id,kind,title,content,tags,status,confidence,reuse_count,verified,source_project,source_ref,reasoning,created_at,updated_at FROM knowledges WHERE status = 'published' ORDER BY reuse_count DESC LIMIT ?1")
|
||||
.prepare(&format!(
|
||||
"SELECT {KNOWLEDGE_COLS} FROM knowledges WHERE status = 'published' ORDER BY reuse_count DESC LIMIT ?1"
|
||||
))
|
||||
.map_err(storage_err)?;
|
||||
let rows = stmt
|
||||
.query_map(params![limit_i], |row| knowledge_from_row(row))
|
||||
@@ -539,6 +604,23 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// IDEA_COLS 列数须等于 IDEA_COL_COUNT(任一处漂移:加列漏改 INSERT/UPDATE/from_row
|
||||
/// 三处之一 → 立即失败)。`idea_from_row` 按 name 取列,SELECT/INSERT 漏列会在运行时被
|
||||
/// rusqlite 报错;此断言提前到测试期捕获(本次 V24 加 related_ids 已改 3 处的回归保险)。
|
||||
#[test]
|
||||
fn test_idea_cols_matches_record() {
|
||||
let count = IDEA_COLS.split(',').count();
|
||||
assert_eq!(
|
||||
count, IDEA_COL_COUNT,
|
||||
"IDEA_COLS({count}列) ≠ IDEA_COL_COUNT({IDEA_COL_COUNT}); \
|
||||
修改一处须同步另一处(INSERT/UPDATE/from_row/IDEA_COLS/IDEA_COL_COUNT 五处)"
|
||||
);
|
||||
// 每个列名须能被 split 出来(防末尾多逗号 / 空段)
|
||||
for col in IDEA_COLS.split(',') {
|
||||
assert!(!col.is_empty(), "IDEA_COLS 含空列名段");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 向量纯函数 ----------
|
||||
|
||||
#[test]
|
||||
@@ -631,6 +713,7 @@ mod tests {
|
||||
source_project: Some("proj-1".to_string()),
|
||||
source_ref: Some("conv:c1".to_string()),
|
||||
reasoning: None,
|
||||
embedding_status: None,
|
||||
created_at: "1700000000000".to_string(),
|
||||
updated_at: "1700000000000".to_string(),
|
||||
}
|
||||
@@ -868,4 +951,105 @@ mod tests {
|
||||
let results = repo.search_vector(&[1.0, 0.0], 10).await.unwrap();
|
||||
assert!(results.is_empty());
|
||||
}
|
||||
|
||||
// ---------- V23 embedding_status 补偿重试 ----------
|
||||
|
||||
#[tokio::test]
|
||||
async fn set_embedding_marks_status_done() {
|
||||
// V23:set_embedding 成功写入时应同步置 embedding_status='done'
|
||||
let repo = setup_repo().await;
|
||||
repo.insert(krec("k1", "t", "c", "lesson", "published", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
repo.set_embedding("k1", &[1.0, 0.0]).await.unwrap();
|
||||
let rec = repo.get_by_id("k1").await.unwrap().expect("记录存在");
|
||||
assert_eq!(rec.embedding_status.as_deref(), Some("done"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mark_embedding_failed_sets_status() {
|
||||
let repo = setup_repo().await;
|
||||
repo.insert(krec("k1", "t", "c", "lesson", "published", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(repo.mark_embedding_failed("k1").await.unwrap());
|
||||
let rec = repo.get_by_id("k1").await.unwrap().expect("记录存在");
|
||||
assert_eq!(rec.embedding_status.as_deref(), Some("failed"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mark_embedding_failed_idempotent() {
|
||||
// 重复标记 failed 无副作用
|
||||
let repo = setup_repo().await;
|
||||
repo.insert(krec("k1", "t", "c", "lesson", "published", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
repo.mark_embedding_failed("k1").await.unwrap();
|
||||
assert!(repo.mark_embedding_failed("k1").await.unwrap());
|
||||
let rec = repo.get_by_id("k1").await.unwrap().unwrap();
|
||||
assert_eq!(rec.embedding_status.as_deref(), Some("failed"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mark_embedding_failed_missing_row_returns_false() {
|
||||
let repo = setup_repo().await;
|
||||
// 不存在的 id → affected=0
|
||||
let ok = repo.mark_embedding_failed("ghost").await.unwrap();
|
||||
assert!(!ok);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_failed_embeddings_only_returns_published_failed() {
|
||||
let repo = setup_repo().await;
|
||||
// k1:published + failed → 应列出
|
||||
repo.insert(krec("k1", "t", "c", "lesson", "published", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
repo.mark_embedding_failed("k1").await.unwrap();
|
||||
// k2:candidate + failed → 不应列出(候选不参与检索,重试无意义)
|
||||
repo.insert(krec("k2", "t", "c", "lesson", "candidate", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
repo.mark_embedding_failed("k2").await.unwrap();
|
||||
// k3:published + done → 不应列出
|
||||
repo.insert(krec("k3", "t", "c", "lesson", "published", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
repo.set_embedding("k3", &[1.0, 0.0]).await.unwrap();
|
||||
// k4:published + 未标记(NULL)→ 不应列出
|
||||
repo.insert(krec("k4", "t", "c", "lesson", "published", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let failed = repo.list_failed_embeddings().await.unwrap();
|
||||
let ids: Vec<_> = failed.iter().map(|r| r.id.as_str()).collect();
|
||||
assert_eq!(ids, vec!["k1"], "只应返回 published + failed 的条目");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_failed_embeddings_empty_when_none_failed() {
|
||||
let repo = setup_repo().await;
|
||||
repo.insert(krec("k1", "t", "c", "lesson", "published", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
repo.set_embedding("k1", &[1.0, 0.0]).await.unwrap();
|
||||
let failed = repo.list_failed_embeddings().await.unwrap();
|
||||
assert!(failed.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn failed_retry_flow_done_after_set_embedding() {
|
||||
// 补偿重试完整流程:failed → set_embedding 成功 → done(不再出现在 list_failed)
|
||||
let repo = setup_repo().await;
|
||||
repo.insert(krec("k1", "t", "c", "lesson", "published", None, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
repo.mark_embedding_failed("k1").await.unwrap();
|
||||
assert_eq!(repo.list_failed_embeddings().await.unwrap().len(), 1);
|
||||
// 重试成功
|
||||
repo.set_embedding("k1", &[0.5, 0.5]).await.unwrap();
|
||||
assert!(repo.list_failed_embeddings().await.unwrap().is_empty());
|
||||
let rec = repo.get_by_id("k1").await.unwrap().unwrap();
|
||||
assert_eq!(rec.embedding_status.as_deref(), Some("done"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user