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..3266cc8 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,56 @@ 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 `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 +const llmConfig = { + apiKey: process.env.OPENAI_API_KEY, + model: 'gpt-4o', + 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: { + apiKey: process.env.OPENAI_API_KEY, + 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({ + url: 'https://example.com/doc.pdf', + llmConfig: modelsConfig, +}); + +const response = await client.retrieval.query({ + namespace: 'support-center', + query: 'What is the refund policy?', + useAgentic: true, + llmConfig: splitLlmConfig, +}); +``` + ### 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..5dafb61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,9 @@ export type { ParsingModel, DocType, DocumentMetadata, + LlmConfig, + LlmModelsConfig, + 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..e0836c0 100644 --- a/src/types/params.ts +++ b/src/types/params.ts @@ -43,6 +43,50 @@ 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; +} + +/** + * 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. + * 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/models + baseUrl) */ + apiKey?: string; + /** 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) */ + vision?: LlmProviderConfig; +} + /** * Client-provided display metadata copied onto the published document. */ @@ -68,6 +112,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 +199,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; } /**