Skip to content

Commit fbe91d0

Browse files
committed
fix(knowledge-ragflow): read source.adapterConfig.datasetId, the declared key
`extractRagflowOptions` cast the source to a shape carrying `options` and read `options.datasetId`. `KnowledgeSourceSchema` declares `adapterConfig` and is a plain `z.object`, so any path that parses a source drops `options` entirely — the adapter worked only because no path parses. The cast is removed, the adapter reads the declared key, and the refusal names `adapterConfig.datasetId` so a host on the old spelling is told what to write. The published README example moves with it. Ruled: batch #160 item 2, letter A (maintainer 2026-09-18). Claude-Session: https://claude.ai/code/session_01AhQASwqJr2Z7XfGWUdvnbF Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1b82c51 commit fbe91d0

3 files changed

Lines changed: 40 additions & 15 deletions

File tree

‎packages/plugins/knowledge-ragflow/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ kernel.use(new KnowledgeServicePlugin({
2525
label: 'Product documentation',
2626
adapter: 'ragflow',
2727
source: { kind: 'http', urls: ['https://docs.example.com/sitemap.xml'] },
28-
options: { datasetId: 'rgf_doc_dataset_id' }, // RAGFlow dataset to bind
28+
adapterConfig: { datasetId: 'rgf_doc_dataset_id' }, // RAGFlow dataset to bind
2929
}],
3030
}));
3131
kernel.use(new KnowledgeRagflowPlugin({
@@ -36,7 +36,7 @@ kernel.use(new KnowledgeRagflowPlugin({
3636

3737
## Source binding
3838

39-
Each `KnowledgeSource` must include `options.datasetId` pointing to a pre-created RAGFlow dataset. The adapter doesn't create datasets — operators do that once in the RAGFlow UI, where they pick the chunking method, embedding model, and rerank policy.
39+
Each `KnowledgeSource` must include `adapterConfig.datasetId` pointing to a pre-created RAGFlow dataset. `adapterConfig` is the key `KnowledgeSourceSchema` declares for adapter-specific configuration; a source that spells it anything else loses it on any parsing path, and the adapter refuses it by name. The adapter doesn't create datasets — operators do that once in the RAGFlow UI, where they pick the chunking method, embedding model, and rerank policy.
4040

4141
## What the adapter does
4242

‎packages/plugins/knowledge-ragflow/src/__tests__/ragflow-adapter.test.ts‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const source: KnowledgeSource = {
99
label: 'Docs',
1010
adapter: 'ragflow',
1111
source: { kind: 'http', urls: ['https://docs.example.com'] } as KnowledgeSource['source'],
12-
options: { datasetId: 'ds_42' },
12+
adapterConfig: { datasetId: 'ds_42' },
1313
};
1414

1515
function fakeFetch(handler: (url: string, init?: any) => unknown): { fetch: FetchLike; calls: Array<{ url: string; init: any }> } {
@@ -30,11 +30,30 @@ function fakeFetch(handler: (url: string, init?: any) => unknown): { fetch: Fetc
3030
}
3131

3232
describe('KnowledgeRagflowAdapter', () => {
33-
it('rejects sources without datasetId', async () => {
33+
it('rejects sources without datasetId, naming the declared key', async () => {
3434
const { fetch } = fakeFetch(() => ({}));
3535
const a = new KnowledgeRagflowAdapter({ endpoint: 'http://x', apiKey: 'k', fetch });
36-
const bad: KnowledgeSource = { ...source, options: {} as Record<string, unknown> };
37-
await expect(a.search('q', { source: bad, topK: 1 })).rejects.toThrow(/datasetId/);
36+
const bad: KnowledgeSource = { ...source, adapterConfig: {} };
37+
// The refusal text is the migration notice a host reads, so it is pinned:
38+
// it must name `adapterConfig.datasetId`, the key the schema declares.
39+
await expect(a.search('q', { source: bad, topK: 1 })).rejects.toThrow(
40+
/source\.adapterConfig\.datasetId/,
41+
);
42+
});
43+
44+
it('does not read the undeclared `options` spelling', async () => {
45+
// `KnowledgeSourceSchema` is a plain `z.object`: it declares `adapterConfig`
46+
// and drops `options` on any parsing path. The adapter reads the declared
47+
// key only — no lenient fallback (Prime Directive #12). A host still on the
48+
// old spelling is refused loudly rather than served with silence.
49+
const { fetch, calls } = fakeFetch(() => ({}));
50+
const a = new KnowledgeRagflowAdapter({ endpoint: 'http://x', apiKey: 'k', fetch });
51+
const { adapterConfig: _dropped, ...rest } = source;
52+
const legacy = { ...rest, options: { datasetId: 'ds_42' } } as unknown as KnowledgeSource;
53+
await expect(a.search('q', { source: legacy, topK: 1 })).rejects.toThrow(
54+
/source\.adapterConfig\.datasetId/,
55+
);
56+
expect(calls).toHaveLength(0);
3857
});
3958

4059
it('upsert deletes-then-creates chunks and stamps objectstack metadata', async () => {
@@ -106,7 +125,7 @@ describe('KnowledgeRagflowAdapter', () => {
106125
const a = new KnowledgeRagflowAdapter({ endpoint: 'http://r', apiKey: 'k', fetch });
107126
const s: KnowledgeSource = {
108127
...source,
109-
options: { datasetId: 'ds_42', rerankModel: 'bge-reranker', similarityThreshold: 0.6 },
128+
adapterConfig: { datasetId: 'ds_42', rerankModel: 'bge-reranker', similarityThreshold: 0.6 },
110129
};
111130
await a.search('q', { source: s, topK: 3, filter: { tag: 'a' } });
112131
const body = JSON.parse(calls[0].init.body);

‎packages/plugins/knowledge-ragflow/src/index.ts‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,23 +57,29 @@ interface RagflowSourceOptions {
5757
vectorSimilarityWeight?: number;
5858
}
5959

60+
/**
61+
* Reads this source's RAGFlow binding from `adapterConfig` — the key
62+
* `KnowledgeSourceSchema` declares for adapter-specific configuration.
63+
* That schema is a plain `z.object`, so any parsing path drops keys it
64+
* does not declare; the adapter therefore reads the declared key and no
65+
* other spelling (Prime Directive #12 — no lenient consumer).
66+
*/
6067
function extractRagflowOptions(source: KnowledgeSource): RagflowSourceOptions {
61-
const opts = ((source as unknown as { options?: Record<string, unknown> }).options ?? {}) as
62-
Record<string, unknown>;
63-
const datasetId = opts.datasetId;
68+
const cfg = source.adapterConfig ?? {};
69+
const datasetId = cfg.datasetId;
6470
if (typeof datasetId !== 'string' || !datasetId) {
6571
throw new Error(
66-
`RAGFlow adapter requires source.options.datasetId on source '${source.id}'`,
72+
`RAGFlow adapter requires source.adapterConfig.datasetId on source '${source.id}'`,
6773
);
6874
}
6975
return {
7076
datasetId,
71-
rerankModel: typeof opts.rerankModel === 'string' ? opts.rerankModel : undefined,
77+
rerankModel: typeof cfg.rerankModel === 'string' ? cfg.rerankModel : undefined,
7278
similarityThreshold:
73-
typeof opts.similarityThreshold === 'number' ? opts.similarityThreshold : undefined,
79+
typeof cfg.similarityThreshold === 'number' ? cfg.similarityThreshold : undefined,
7480
vectorSimilarityWeight:
75-
typeof opts.vectorSimilarityWeight === 'number'
76-
? opts.vectorSimilarityWeight
81+
typeof cfg.vectorSimilarityWeight === 'number'
82+
? cfg.vectorSimilarityWeight
7783
: undefined,
7884
};
7985
}

0 commit comments

Comments
 (0)