From ff5a80352b53b3286f4bd4f068a8a4a826179292 Mon Sep 17 00:00:00 2001 From: suguanYang Date: Wed, 15 Jul 2026 19:53:53 +0800 Subject: [PATCH 1/5] feat: support llmConfig BYOK on jobs and retrieval Expose optional text/vision OpenAI-compatible credentials on job create, parse, and retrieval.query so callers can drive KNOWHERE with their own keys. Co-authored-by: Cursor --- .changeset/byok-llm-config.md | 5 +++ README.md | 32 +++++++++++++++++ src/__tests__/client.test.ts | 36 +++++++++++++++++++ src/client.ts | 1 + src/index.ts | 2 ++ src/resources/__tests__/jobs.test.ts | 42 ++++++++++++++++++++++ src/resources/__tests__/retrieval.test.ts | 43 +++++++++++++++++++++++ src/types/params.ts | 26 ++++++++++++++ src/types/retrieval.ts | 4 +++ 9 files changed, 191 insertions(+) create mode 100644 .changeset/byok-llm-config.md diff --git a/.changeset/byok-llm-config.md b/.changeset/byok-llm-config.md new file mode 100644 index 0000000..ea9ed8c --- /dev/null +++ b/.changeset/byok-llm-config.md @@ -0,0 +1,5 @@ +--- +"@ontos-ai/knowhere-sdk": minor +--- + +Add optional BYOK `llmConfig` on job create, parse, and retrieval query so callers can supply per-request text/vision provider credentials. diff --git a/README.md b/README.md index 9c9c700..d519ec2 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,38 @@ const result = await client.parse({ }); ``` +### Bring Your Own Key (BYOK) + +Pass per-request LLM credentials via `llmConfig` on parse/job create and +retrieval queries. Use camelCase in TypeScript; the HTTP client serializes to +snake_case on the wire. + +```typescript +const llmConfig = { + text: { + apiKey: process.env.OPENAI_API_KEY, + model: 'gpt-4o-mini', + baseUrl: 'https://api.openai.com/v1', + }, + vision: { + apiKey: process.env.OPENAI_API_KEY, + model: 'gpt-4o', + }, +}; + +const result = await client.parse({ + url: 'https://example.com/doc.pdf', + llmConfig, +}); + +const response = await client.retrieval.query({ + namespace: 'support-center', + query: 'What is the refund policy?', + useAgentic: true, + llmConfig, +}); +``` + ### Page Citation Assets Page citation assets are generated by Knowhere during page-memory parsing. The diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index fa78008..ca710b3 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -398,6 +398,42 @@ describe('Knowhere Client', () => { ); }); + it('should forward llmConfig from parse/startParse to job creation', async () => { + const llmConfig = { + text: { + apiKey: 'sk-text', + model: 'gpt-4o-mini', + baseUrl: 'https://api.openai.com/v1', + }, + vision: { + apiKey: 'sk-vision', + model: 'gpt-4o', + }, + }; + + await client.parse({ + url: 'https://example.com/doc.pdf', + llmConfig, + }); + + expect(client.jobs.create).toHaveBeenCalledWith( + expect.objectContaining({ + llmConfig, + }), + ); + + await client.startParse({ + url: 'https://example.com/doc.pdf', + llmConfig, + }); + + expect(client.jobs.create).toHaveBeenLastCalledWith( + expect.objectContaining({ + llmConfig, + }), + ); + }); + it('should handle upload progress callback', async () => { const progressUpdates: number[] = []; diff --git a/src/client.ts b/src/client.ts index 00ae169..10d0e04 100644 --- a/src/client.ts +++ b/src/client.ts @@ -193,6 +193,7 @@ export class Knowhere { documentId: params.documentId, documentMetadata: params.documentMetadata, parsingParams: buildParsingParams(params), + llmConfig: params.llmConfig, webhook, }); diff --git a/src/index.ts b/src/index.ts index 4ab1ef3..60be23e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,8 @@ export type { ParsingModel, DocType, DocumentMetadata, + LlmConfig, + LlmProviderConfig, WebhookConfig, CreateJobParams, UploadParams, diff --git a/src/resources/__tests__/jobs.test.ts b/src/resources/__tests__/jobs.test.ts index 62e3dd1..3d0d3a4 100644 --- a/src/resources/__tests__/jobs.test.ts +++ b/src/resources/__tests__/jobs.test.ts @@ -139,6 +139,48 @@ describe('Jobs Resource', () => { ); }); + it('should include llmConfig when provided', async () => { + mockHttpClient.post.mockResolvedValue({ + jobId: 'job-byok', + status: 'pending', + sourceType: 'url', + createdAt: new Date(), + }); + + await jobs.create({ + sourceType: 'url', + sourceUrl: 'https://example.com/doc.pdf', + llmConfig: { + text: { + apiKey: 'sk-text', + model: 'gpt-4o-mini', + baseUrl: 'https://api.openai.com/v1', + }, + vision: { + apiKey: 'sk-vision', + model: 'gpt-4o', + }, + }, + }); + + expect(mockHttpClient.post).toHaveBeenCalledWith( + '/v2/jobs', + expect.objectContaining({ + llmConfig: { + text: { + apiKey: 'sk-text', + model: 'gpt-4o-mini', + baseUrl: 'https://api.openai.com/v1', + }, + vision: { + apiKey: 'sk-vision', + model: 'gpt-4o', + }, + }, + }), + ); + }); + it('should include webhook configuration', async () => { mockHttpClient.post.mockResolvedValue({ jobId: 'job-abc', diff --git a/src/resources/__tests__/retrieval.test.ts b/src/resources/__tests__/retrieval.test.ts index e7fb787..cf10fbb 100644 --- a/src/resources/__tests__/retrieval.test.ts +++ b/src/resources/__tests__/retrieval.test.ts @@ -113,6 +113,49 @@ describe('Retrieval Resource', () => { }); }); + it('should send llmConfig parameter', async () => { + mockHttpClient.post.mockResolvedValue({ + namespace: 'default', + query: 'test', + routerUsed: 'workflow_single_step', + answerText: null, + referencedChunks: [], + results: [], + }); + + await retrieval.query({ + query: 'test', + useAgentic: true, + llmConfig: { + text: { + apiKey: 'sk-text', + model: 'gpt-4o-mini', + baseUrl: 'https://api.openai.com/v1', + }, + vision: { + apiKey: 'sk-vision', + model: 'gpt-4o', + }, + }, + }); + + expect(mockHttpClient.post).toHaveBeenCalledWith('/v2/retrieval/query', { + query: 'test', + useAgentic: true, + llmConfig: { + text: { + apiKey: 'sk-text', + model: 'gpt-4o-mini', + baseUrl: 'https://api.openai.com/v1', + }, + vision: { + apiKey: 'sk-vision', + model: 'gpt-4o', + }, + }, + }); + }); + it('should send useAgentic parameter', async () => { mockHttpClient.post.mockResolvedValue({ namespace: 'default', diff --git a/src/types/params.ts b/src/types/params.ts index 278af74..b75a70c 100644 --- a/src/types/params.ts +++ b/src/types/params.ts @@ -43,6 +43,28 @@ export interface WebhookConfig { url: string; } +/** + * Per-provider BYOK credentials and routing overrides. + */ +export interface LlmProviderConfig { + /** Provider API key */ + apiKey?: string; + /** Model identifier */ + model?: string; + /** Provider base URL override */ + baseUrl?: string; +} + +/** + * Bring-your-own-key LLM configuration for text and vision providers. + */ +export interface LlmConfig { + /** Text / chat provider credentials */ + text?: LlmProviderConfig; + /** Vision / multimodal provider credentials */ + vision?: LlmProviderConfig; +} + /** * Client-provided display metadata copied onto the published document. */ @@ -68,6 +90,8 @@ export interface CreateJobParams { documentMetadata?: DocumentMetadata; /** Parsing configuration */ parsingParams?: ParsingParams; + /** Bring-your-own-key LLM credentials for this job */ + llmConfig?: LlmConfig; /** Webhook configuration */ webhook?: WebhookConfig; } @@ -153,6 +177,8 @@ export interface ParseParams { * assets into application-owned storage before the result is returned. */ storageAdapter?: KnowhereAssetStorageOptions; + /** Bring-your-own-key LLM credentials for this parse */ + llmConfig?: LlmConfig; /** Webhook configuration */ webhook?: WebhookConfig; /** Upload progress callback */ diff --git a/src/types/retrieval.ts b/src/types/retrieval.ts index 0b120d7..6555254 100644 --- a/src/types/retrieval.ts +++ b/src/types/retrieval.ts @@ -1,3 +1,5 @@ +import type { LlmConfig } from './params.js'; + /** * Section exclusion for follow-up retrieval queries. */ @@ -60,6 +62,8 @@ export interface RetrievalQueryParams { excludeDocumentIds?: string[]; /** Document sections to exclude for this request only */ excludeSections?: RetrievalSectionExclusion[]; + /** Bring-your-own-key LLM credentials for this query */ + llmConfig?: LlmConfig; } /** From 28a524bae273cce5e73b8570deea866ce10a2b23 Mon Sep 17 00:00:00 2001 From: suguanYang Date: Wed, 15 Jul 2026 21:39:12 +0800 Subject: [PATCH 2/5] docs: clarify llmConfig partial per-channel override Document that missing text/vision slots keep server defaults instead of acting as a unified multimodal fallback. Co-authored-by: Cursor --- README.md | 4 +++- src/types/params.ts | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d519ec2..aa52e4c 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,9 @@ const result = await client.parse({ ### Bring Your Own Key (BYOK) Pass per-request LLM credentials via `llmConfig` on parse/job create and -retrieval queries. Use camelCase in TypeScript; the HTTP client serializes to +retrieval queries. Each of `text` / `vision` overrides only its own channel; +missing slots keep server defaults. For one multimodal model, set both slots to +the same credentials. Use camelCase in TypeScript; the HTTP client serializes to snake_case on the wire. ```typescript diff --git a/src/types/params.ts b/src/types/params.ts index b75a70c..54c1a9b 100644 --- a/src/types/params.ts +++ b/src/types/params.ts @@ -57,11 +57,14 @@ export interface LlmProviderConfig { /** * Bring-your-own-key LLM configuration for text and vision providers. + * + * Each slot overrides only its own channel; missing slots keep server defaults. + * For one multimodal model, set both slots to the same credentials. */ export interface LlmConfig { /** Text / chat provider credentials */ text?: LlmProviderConfig; - /** Vision / multimodal provider credentials */ + /** Vision / VLM provider credentials */ vision?: LlmProviderConfig; } From 543f6bebc40cebf5bd24a7d6ceca27115e560f8c Mon Sep 17 00:00:00 2001 From: suguanYang Date: Wed, 15 Jul 2026 21:44:59 +0800 Subject: [PATCH 3/5] feat: add llmConfig.provider multimodal shorthand Document and type the shared provider slot for single multimodal BYOK. Co-authored-by: Cursor --- README.md | 17 +++++++---------- src/types/params.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index aa52e4c..e07d134 100644 --- a/README.md +++ b/README.md @@ -148,21 +148,18 @@ const result = await client.parse({ ### Bring Your Own Key (BYOK) Pass per-request LLM credentials via `llmConfig` on parse/job create and -retrieval queries. Each of `text` / `vision` overrides only its own channel; -missing slots keep server defaults. For one multimodal model, set both slots to -the same credentials. Use camelCase in TypeScript; the HTTP client serializes to -snake_case on the wire. +retrieval queries. Use `provider` for a single multimodal model (both channels). +Use `text` / `vision` to override one channel; missing channels without +`provider` keep server defaults. Use camelCase in TypeScript; the HTTP client +serializes to snake_case on the wire. ```typescript +// Multimodal shorthand — one model for text + vision const llmConfig = { - text: { - apiKey: process.env.OPENAI_API_KEY, - model: 'gpt-4o-mini', - baseUrl: 'https://api.openai.com/v1', - }, - vision: { + provider: { apiKey: process.env.OPENAI_API_KEY, model: 'gpt-4o', + baseUrl: 'https://api.openai.com/v1', }, }; diff --git a/src/types/params.ts b/src/types/params.ts index 54c1a9b..743ee31 100644 --- a/src/types/params.ts +++ b/src/types/params.ts @@ -58,13 +58,16 @@ export interface LlmProviderConfig { /** * Bring-your-own-key LLM configuration for text and vision providers. * - * Each slot overrides only its own channel; missing slots keep server defaults. - * For one multimodal model, set both slots to the same credentials. + * - `provider`: shared multimodal credentials for both channels + * - `text` / `vision`: per-channel overrides (win over `provider`) + * - a channel with neither a slot nor `provider` keeps server defaults */ export interface LlmConfig { - /** Text / chat provider credentials */ + /** Shared multimodal credentials for both text and vision */ + provider?: LlmProviderConfig; + /** Text / chat provider credentials (overrides provider) */ text?: LlmProviderConfig; - /** Vision / VLM provider credentials */ + /** Vision / VLM provider credentials (overrides provider) */ vision?: LlmProviderConfig; } From b54344499aa79bc61935ec9c6b0c81bdcc91050d Mon Sep 17 00:00:00 2001 From: suguanYang Date: Wed, 15 Jul 2026 22:03:46 +0800 Subject: [PATCH 4/5] refactor: flatten llmConfig to OpenAI-style root fields Document flat multimodal shorthand and split text/vision endpoints. Co-authored-by: Cursor --- README.md | 23 +++++++++++++++++------ src/types/params.ts | 20 ++++++++++++-------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e07d134..95d4a76 100644 --- a/README.md +++ b/README.md @@ -148,19 +148,30 @@ const result = await client.parse({ ### Bring Your Own Key (BYOK) Pass per-request LLM credentials via `llmConfig` on parse/job create and -retrieval queries. Use `provider` for a single multimodal model (both channels). -Use `text` / `vision` to override one channel; missing channels without -`provider` keep server defaults. Use camelCase in TypeScript; the HTTP client +retrieval queries. Flat root applies to both channels; use `text` / `vision` +for different provider endpoints. Use camelCase in TypeScript; the HTTP client serializes to snake_case on the wire. ```typescript // Multimodal shorthand — one model for text + vision const llmConfig = { - provider: { + apiKey: process.env.OPENAI_API_KEY, + model: 'gpt-4o', + baseUrl: 'https://api.openai.com/v1', +}; + +// Or two different endpoints +const splitLlmConfig = { + text: { apiKey: process.env.OPENAI_API_KEY, - model: 'gpt-4o', + model: 'gpt-4o-mini', baseUrl: 'https://api.openai.com/v1', }, + vision: { + apiKey: process.env.DASHSCOPE_API_KEY, + model: 'qwen-vl-max', + baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + }, }; const result = await client.parse({ @@ -172,7 +183,7 @@ const response = await client.retrieval.query({ namespace: 'support-center', query: 'What is the refund policy?', useAgentic: true, - llmConfig, + llmConfig: splitLlmConfig, }); ``` diff --git a/src/types/params.ts b/src/types/params.ts index 743ee31..3a8be58 100644 --- a/src/types/params.ts +++ b/src/types/params.ts @@ -56,18 +56,22 @@ export interface LlmProviderConfig { } /** - * Bring-your-own-key LLM configuration for text and vision providers. + * Bring-your-own-key LLM configuration. * - * - `provider`: shared multimodal credentials for both channels - * - `text` / `vision`: per-channel overrides (win over `provider`) - * - a channel with neither a slot nor `provider` keeps server defaults + * Flat root (`apiKey` / `model` / `baseUrl`) applies to both channels. + * Optional `text` / `vision` fully replace the default for that channel — + * use both when text and vision use different provider endpoints. */ export interface LlmConfig { - /** Shared multimodal credentials for both text and vision */ - provider?: LlmProviderConfig; - /** Text / chat provider credentials (overrides provider) */ + /** Default provider API key (with model + baseUrl) */ + apiKey?: string; + /** Default model identifier */ + model?: string; + /** Default OpenAI-compatible base URL */ + baseUrl?: string; + /** Text / chat credentials (replaces root for text) */ text?: LlmProviderConfig; - /** Vision / VLM provider credentials (overrides provider) */ + /** Vision / VLM credentials (replaces root for vision) */ vision?: LlmProviderConfig; } From 8552761489b21cfb8325a6e853fc6c63478519b0 Mon Sep 17 00:00:00 2001 From: suguanYang Date: Wed, 15 Jul 2026 22:11:02 +0800 Subject: [PATCH 5/5] feat: add llmConfig.models per-channel model map Support shared auth with different text/vision model ids. Co-authored-by: Cursor --- README.md | 16 ++++++++++++---- src/index.ts | 1 + src/types/params.ts | 20 ++++++++++++++++---- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 95d4a76..3266cc8 100644 --- a/README.md +++ b/README.md @@ -148,9 +148,10 @@ const result = await client.parse({ ### Bring Your Own Key (BYOK) Pass per-request LLM credentials via `llmConfig` on parse/job create and -retrieval queries. Flat root applies to both channels; use `text` / `vision` -for different provider endpoints. Use camelCase in TypeScript; the HTTP client -serializes to snake_case on the wire. +retrieval queries. Flat root applies to both channels; use `models` for +different model ids on the same endpoint, or `text` / `vision` for different +provider endpoints. Use camelCase in TypeScript; the HTTP client serializes to +snake_case on the wire. ```typescript // Multimodal shorthand — one model for text + vision @@ -160,6 +161,13 @@ const llmConfig = { baseUrl: 'https://api.openai.com/v1', }; +// Same endpoint, different models per channel +const modelsConfig = { + apiKey: process.env.OPENAI_API_KEY, + baseUrl: 'https://api.openai.com/v1', + models: { text: 'gpt-4o-mini', vision: 'gpt-4o' }, +}; + // Or two different endpoints const splitLlmConfig = { text: { @@ -176,7 +184,7 @@ const splitLlmConfig = { const result = await client.parse({ url: 'https://example.com/doc.pdf', - llmConfig, + llmConfig: modelsConfig, }); const response = await client.retrieval.query({ diff --git a/src/index.ts b/src/index.ts index 60be23e..5dafb61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,6 +49,7 @@ export type { DocType, DocumentMetadata, LlmConfig, + LlmModelsConfig, LlmProviderConfig, WebhookConfig, CreateJobParams, diff --git a/src/types/params.ts b/src/types/params.ts index 3a8be58..e0836c0 100644 --- a/src/types/params.ts +++ b/src/types/params.ts @@ -55,20 +55,32 @@ export interface LlmProviderConfig { baseUrl?: string; } +/** + * Per-channel model ids that share root apiKey / baseUrl. + */ +export interface LlmModelsConfig { + /** Text / planning model id */ + text?: string; + /** Vision / VLM model id */ + vision?: string; +} + /** * Bring-your-own-key LLM configuration. * * Flat root (`apiKey` / `model` / `baseUrl`) applies to both channels. - * Optional `text` / `vision` fully replace the default for that channel — - * use both when text and vision use different provider endpoints. + * Use `models` for different model ids on the same endpoint, or `text` / + * `vision` objects for different provider endpoints. */ export interface LlmConfig { - /** Default provider API key (with model + baseUrl) */ + /** Default provider API key (with model/models + baseUrl) */ apiKey?: string; - /** Default model identifier */ + /** Default model for both channels (overridden by models.*) */ model?: string; /** Default OpenAI-compatible base URL */ baseUrl?: string; + /** Per-channel model ids sharing root apiKey / baseUrl */ + models?: LlmModelsConfig; /** Text / chat credentials (replaces root for text) */ text?: LlmProviderConfig; /** Vision / VLM credentials (replaces root for vision) */