Files
Joel Alan b43ddb6543 feat: add context service (#101)
* feat: skill use context service (#97)

* feat: retrieve document as context with `zvec` (#87)

* feat: 召回策略升级到 zvec

* chore: 删除不必要的 createContext

---------

Co-authored-by: 福晋 <liufu.lf@antgroup.com>

* fix: 修复 playground 图表渲染异常 (#88)

Co-authored-by: 福晋 <liufu.lf@antgroup.com>

* refactor: antv skill refactor (#90)

* refactor: antv skill refactor

* chore: remove reference data from the eval process

* chore: update api path

* chore: remove mistakes content

* chore: includeInfo → includeConstraints

---------

Co-authored-by: 福晋 <liufu.lf@antgroup.com>

* chore: skill content update (#93)

* chore: skill content update

* chore: update skill content

---------

Co-authored-by: 福晋 <liufu.lf@antgroup.com>

* chore: skill rename to doc (#92)

* chore: skill rename to doc

* chore: code opt

---------

Co-authored-by: 福晋 <liufu.lf@antgroup.com>

* refactor: use context for retrieval (#94)

* refactor: use context for retrieval

* chore: update dependence

* chore: code opt

* chore: update eval results

* chore: cut down redundent code

* chore: reduce external dependence

* chore: update dependence version

* fix: build

* fix: utils

* fix: test

* chore: update node version

---------

Co-authored-by: 福晋 <liufu.lf@antgroup.com>

* refactor: 简化代码 (#96)

* refactor: 简化代码

* chore: remove command

* chore: update timeout

* feat: add cli and format output

* feat: add maxTokens

* docs: add publish action

* chore: update tc

* feat: use exist zvec

* chore: 0.1.4

---------

Co-authored-by: 逍为 <xiaowei.wzw@antgroup.com>

* chore: merge master

* chore: test should be first

---------

Co-authored-by: Joel Alan <31396322+lxfu1@users.noreply.github.com>
Co-authored-by: 福晋 <liufu.lf@antgroup.com>
Co-authored-by: 逍为 <xiaowei.wzw@antgroup.com>

* chore: add postinstall

* chore: improvement of retrieval quality (#99)

* chore: improvement of retrieval quality

* chore: remove content from dist

* chore: remove default ftsFields

* chore: update version

---------

Co-authored-by: 福晋 <liufu.lf@antgroup.com>

* chore: update retrieve host (#100)

Co-authored-by: 福晋 <liufu.lf@antgroup.com>

---------

Co-authored-by: hustcc <i@hust.cc>
Co-authored-by: 福晋 <liufu.lf@antgroup.com>
Co-authored-by: 逍为 <xiaowei.wzw@antgroup.com>
2026-07-31 11:46:18 +08:00

126 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env tsx
/**
* AntV Skills 召回率评估
*
* 使用核心 retrieve() APIzvec hybrid search评估召回率。
*/
import 'dotenv/config';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { inferCategory } from './utils/category-inference.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// ── Retrieve via core (zvec hybrid) ─────────────────────────────────────────────
interface SkillEntry {
id: string;
title?: string;
category?: string;
}
async function retrieveSkillsViaCore(
query: string,
library: string,
topK = 5
): Promise<SkillEntry[]> {
try {
const mod = (await import('../src/api.js')) as {
retrieve: (
q: string,
opts: {
library?: string;
topK?: number;
content?: boolean;
}
) => Promise<Array<{ id: string; title: string; category: string }>>;
};
return await mod.retrieve(query, { library, topK, content: false });
} catch (err) {
console.warn(`检索失败 (${library}): ${(err as Error).message}`);
return [];
}
}
// ── 评估函数 ───────────────────────────────────────────────────────────────────
async function evaluateRecall() {
const datasetPath = path.join(__dirname, 'data', 'g2-dataset-174.json');
const dataset: Array<{ id: string; description: string }> = JSON.parse(
fs.readFileSync(datasetPath, 'utf-8')
);
console.log('\n' + '='.repeat(60));
console.log('📊 AntV Skills 召回率评估 (zvec hybrid)');
console.log('='.repeat(60));
console.log(`📋 测试用例数: ${dataset.length}`);
const categoryStats: Record<string, { total: number; hit: number }> = {};
let totalHit = 0;
let totalWithResults = 0;
for (const { description } of dataset) {
const library =
description.includes('X6') || description.includes('@antv/x6')
? 'x6'
: description.includes('G6') || description.includes('图分析')
? 'g6'
: 'g2';
const results = await retrieveSkillsViaCore(description, library, 5);
if (results.length === 0) continue;
const expectedCategory = inferCategory(description);
const hit = results.some((s) => s.category === expectedCategory);
if (!categoryStats[expectedCategory]) {
categoryStats[expectedCategory] = { total: 0, hit: 0 };
}
categoryStats[expectedCategory].total++;
if (hit) {
categoryStats[expectedCategory].hit++;
totalHit++;
}
totalWithResults++;
}
console.log('\n📈 总体结果');
console.log('─'.repeat(40));
console.log(`有检索结果的用例: ${totalWithResults}/${dataset.length}`);
console.log(
`类别命中率: ${totalHit}/${totalWithResults} (${((totalHit / totalWithResults) * 100).toFixed(1)}%)`
);
console.log('\n📊 分类别统计');
console.log('─'.repeat(40));
for (const [category, stats] of Object.entries(categoryStats).sort(
(a, b) => b[1].hit - a[1].hit
)) {
const hitRate = (stats.hit / stats.total) * 100;
console.log(
`${category.padEnd(20)} 命中率: ${hitRate.toFixed(1).padStart(5)}% (${stats.hit}/${stats.total})`
);
}
console.log('\n📝 检索示例');
console.log('─'.repeat(40));
for (const { id, description } of dataset.slice(0, 5)) {
const library =
description.includes('X6') || description.includes('@antv/x6')
? 'x6'
: description.includes('G6') || description.includes('图分析')
? 'g6'
: 'g2';
const results = await retrieveSkillsViaCore(description, library, 3);
console.log(`\n[${id}] ${description.substring(0, 50)}...`);
console.log(` 检索结果: ${results.map((s) => s.id).join(', ')}`);
}
console.log('\n' + '='.repeat(60) + '\n');
}
evaluateRecall();