Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ ADMIN_PASSWORD=admin123
DB_PATH=./data/customer-service.db
DOCUMENT_UPLOAD_DIR=./data/uploads

# Optional vector backend. Memory remains the fresh-clone default. When Qdrant
# is selected, restart the app after changing these deployment values.
VECTOR_STORE_PROVIDER=memory
QDRANT_URL=
QDRANT_API_KEY=
QDRANT_COLLECTION_PREFIX=resolveweave_knowledge
QDRANT_COLLECTION_ALIAS=resolveweave_knowledge_active
QDRANT_TIMEOUT_MS=5000
RETRIEVAL_TRACE_RETENTION_DAYS=30

# Optional local PaddleOCR/PP-StructureV3 worker. Existing FAQ/text document
# features keep working when this is empty.
OCR_SERVICE_URL=
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,33 @@ jobs:
run: EMBED_PROVIDER=other npm run build
env:
JWT_SECRET: test-secret-123

qdrant-integration:
runs-on: ubuntu-latest
services:
qdrant:
image: qdrant/qdrant:v1.18.2
ports:
- 6333:6333

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Wait for Qdrant
run: curl --fail --retry 20 --retry-delay 1 --retry-connrefused http://127.0.0.1:6333/readyz

- name: Run Qdrant integration
run: npm run test:qdrant
env:
QDRANT_URL: http://127.0.0.1:6333
JWT_SECRET: test-secret-123
115 changes: 86 additions & 29 deletions README.md

Large diffs are not rendered by default.

95 changes: 69 additions & 26 deletions README_CN.md

Large diffs are not rendered by default.

24 changes: 14 additions & 10 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ This roadmap describes the product direction rather than fixed delivery dates. T
| v0.2.8 | Released | RAG Quality Lab | 用版本化评测集比较检索与 Grounding 策略,并通过质量门禁安全发布和回滚。 |
| v0.2.9 | Released | Structured Escalation & Triage | 以结构化交接包、确定性优先级和只读双语分流页承接转人工流程。 |
| v0.3.0 | Released | Structure-Aware Ingestion Foundation | 统一结构表示、质量门禁、结构切片、处理时间线和显式影子重处理已公开发布。 |
| v0.3.1 | Current | Multimodal Knowledge Review | PNG/JPEG/WebP/扫描 PDF 经持久化 PaddleOCR 队列进入可编辑复核草稿;可选 DeepSeek 影子对照,发布后保留引擎、页码和 Block 来源。 |
| v0.3.1 | Released | Multimodal Knowledge Review | PNG/JPEG/WebP/扫描 PDF 经持久化 PaddleOCR 队列进入可编辑复核草稿;可选 DeepSeek 影子对照,发布后保留引擎、页码和 Block 来源。 |
| v0.3.2 | Current | Qdrant & Retrieval Observability | 可选 Qdrant、可恢复索引任务、Quality Lab 后端影子评测、alias 原子激活/回滚和八阶段检索 Trace 形成独立运维闭环。 |

v0.2.9 已经形成可运行且带回答边界、结构化人工交接的小规模客服产品基线:用户聊天、匿名会话历史、FAQ
与文档知识、混合检索、转人工记录、满意度、知识审核、会话分析、双语后台、
v0.3.2 已经形成可运行且带回答边界、结构化人工交接和检索运维的小规模客服产品基线:用户聊天、匿名会话历史、FAQ
与文档知识、混合检索、可选 Qdrant、可恢复索引、检索 Trace、转人工记录、满意度、知识审核、会话分析、双语后台、
可信回答决策、来源持久化、接口幂等、防重复提交、Docker、检索评测和 Playwright 回归在同一工程内闭环。后续版本不再以增加
“另一个聊天 Demo”为目标,而是先补齐企业知识工程与 Agentic Retrieval,再扩展业务处理和人工协作。

## Planned Sequence / 计划顺序

| Version | Theme | Intended outcome |
| --- | --- | --- |
| v0.3.2 | Qdrant & Retrieval Observability | 将 Qdrant 作为可选生产向量后端,保留本地回退,并提供迁移、混合检索、检索预算和全链路 Trace。 |
| v0.3.3 | Bounded Agentic Retrieval | LLM 在预算内选择、组合和重试检索工具;确定性 Grounding Gate 决定引用、拒答、转人工和答案放行。 |
| v0.3.4 | Enterprise Knowledge Operations | 增加可观测入库任务、文档版本、重建索引、失败恢复、白名单远程来源和定时刷新。 |
| v0.3.5 | Read-Only Customer Service Tools | 以 mock 订单/物流查询验证类型化工具和外部订单系统接口,不执行业务写操作。 |
Expand Down Expand Up @@ -90,12 +90,16 @@ v0.2.9 已经形成可运行且带回答边界、结构化人工交接的小规

### v0.3.2 — Qdrant & Retrieval Observability

- Qdrant 作为 `VectorStore` 后的第一类生产后端;内存实现继续服务
fresh-clone 和无基础设施演示。
- 保留关键词/结构化检索,明确向量、关键词、融合、重排和最终上下文预算。
- 为已有 FAQ/文档向量提供可恢复迁移、幂等重建、健康检查和回滚。
- 持久化来源、各阶段得分、延迟、失败原因和最终证据集,并通过 Quality Lab
做影子对比后再切换默认生产路径。
- Qdrant 已作为异步 `VectorStore` 后的可选生产后端;内存实现继续服务
fresh-clone 和无基础设施演示,SQLite 始终是知识权威源。
- 保留关键词/结构化检索;Qdrant 超时或不可用会记录 `degraded` Trace,
不静默重建内存向量。
- 版本化 collection 通过固定批次、知识指纹和检查点支持中断恢复;就绪前
校验 profile、维度、点数和当前知识指纹。
- Quality Lab 可用同一数据集和策略影子对比 memory/Qdrant;质量门禁通过后
原子切换 alias,并可回滚到上一已验证 collection。
- 独立双语检索运维页展示健康、索引任务和固定八阶段 Trace;Trace 只保存
有界安全元数据,默认保留 30 天。

### v0.3.3 — Bounded Agentic Retrieval

Expand Down
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const ModelConfigPage = lazy(() => import('./pages/admin/ModelConfigPage'));
const KnowledgeReviewPage = lazy(() => import('./pages/admin/KnowledgeReviewPage'));
const DocumentManagementPage = lazy(() => import('./pages/admin/DocumentManagementPage'));
const QualityLabPage = lazy(() => import('./pages/admin/QualityLabPage'));
const RetrievalOpsPage = lazy(() => import('./pages/admin/RetrievalOpsPage'));
const EscalationTriagePage = lazy(() => import('./pages/admin/EscalationTriagePage'));
const AuthGuard = lazy(() => import('./components/common/AuthGuard'));

Expand Down Expand Up @@ -53,6 +54,7 @@ export function App(): React.ReactElement {
<Route path="knowledge-review" element={<KnowledgeReviewPage />} />
<Route path="documents" element={<DocumentManagementPage />} />
<Route path="quality-lab" element={<QualityLabPage />} />
<Route path="retrieval-ops" element={<RetrievalOpsPage />} />
<Route path="config" element={<ModelConfigPage />} />
</Route>
</Routes>
Expand Down
85 changes: 85 additions & 0 deletions client/src/api/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ import type {
QualityCase,
QualityDatasetVersion,
QualityRun,
QualityBackendTarget,
RetrievalActivationCheck,
RetrievalIndexJob,
RetrievalStatus,
RetrievalTrace,
RetrievalTraceDetail,
RetrievalTraceStatus,
RetrievalPolicy,
RetrievalPolicyEvent,
RetrievalPolicyConfig,
Expand Down Expand Up @@ -77,6 +84,12 @@ export type {
QualityCase,
QualityDatasetVersion,
QualityRun,
RetrievalActivationCheck,
RetrievalIndexJob,
RetrievalStatus,
RetrievalTrace,
RetrievalTraceDetail,
RetrievalTraceStatus,
RetrievalPolicy,
RetrievalPolicyEvent,
RetrievalPolicyConfig,
Expand Down Expand Up @@ -448,6 +461,7 @@ export async function getQualityRun(runId: string): Promise<QualityRun> {
export async function createQualityRun(data: {
datasetVersionIds: string[];
policies: RetrievalPolicyConfig[];
backendTargets?: QualityBackendTarget[];
}): Promise<QualityRun> {
return post('/admin/quality/runs', data, idempotentRequest());
}
Expand Down Expand Up @@ -487,3 +501,74 @@ export async function rollbackQualityPolicy(data: {
}): Promise<RetrievalPolicy> {
return post('/admin/quality/policies/rollback', data, idempotentRequest());
}

// ── Retrieval Operations ──────────────────────────

export async function getRetrievalStatus(): Promise<RetrievalStatus> {
return get('/admin/retrieval/status');
}

export async function listRetrievalIndexJobs(
page: number = 1,
pageSize: number = 50,
): Promise<PaginationResponse<RetrievalIndexJob>> {
return get('/admin/retrieval/index-jobs', { page, pageSize });
}

export async function createRetrievalIndexJob(): Promise<RetrievalIndexJob> {
return post('/admin/retrieval/index-jobs', {}, idempotentRequest());
}

export async function getRetrievalActivationCheck(
id: string,
): Promise<RetrievalActivationCheck> {
return get(`/admin/retrieval/index-jobs/${id}/activation-check`);
}

export async function activateRetrievalIndexJob(data: {
id: string;
expectedCurrentCollection: string | null;
confirmLatencyWarning: boolean;
}): Promise<RetrievalIndexJob> {
return post(
`/admin/retrieval/index-jobs/${data.id}/activate`,
{
expectedCurrentCollection: data.expectedCurrentCollection,
confirmed: true,
confirmLatencyWarning: data.confirmLatencyWarning,
},
idempotentRequest(),
);
}

export async function rollbackRetrievalIndexJob(data: {
id: string;
expectedCurrentCollection: string;
}): Promise<RetrievalIndexJob> {
return post(
`/admin/retrieval/index-jobs/${data.id}/rollback`,
{
expectedCurrentCollection: data.expectedCurrentCollection,
confirmed: true,
},
idempotentRequest(),
);
}

export async function listRetrievalTraces(params?: {
page?: number;
pageSize?: number;
status?: RetrievalTraceStatus;
backend?: 'memory' | 'qdrant';
sessionId?: string;
createdFrom?: string;
createdTo?: string;
}): Promise<PaginationResponse<RetrievalTrace>> {
return get('/admin/retrieval/traces', params);
}

export async function getRetrievalTrace(
traceId: string,
): Promise<RetrievalTraceDetail> {
return get(`/admin/retrieval/traces/${traceId}`);
}
Loading
Loading