@astack-tech/integrations 0.2.0-beta.7 → 0.2.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +22 -1
- package/dist/index.cjs +27 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +27 -9
- package/dist/index.js.map +1 -1
- package/dist/model-provider/index.cjs +29 -10
- package/dist/model-provider/index.cjs.map +1 -1
- package/dist/model-provider/index.d.cts +85 -14
- package/dist/model-provider/index.d.ts +85 -14
- package/dist/model-provider/index.js +27 -9
- package/dist/model-provider/index.js.map +1 -1
- package/package.json +6 -4
- package/src/index.ts +7 -1
- package/src/model-provider/deepseek/index.ts +27 -433
- package/src/model-provider/index.ts +14 -0
- package/src/model-provider/openai-compatible/index.ts +499 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/model-provider/deepseek/index.ts"],"sourcesContent":["import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for the DeepSeek model provider.\n */\nexport interface DeepseekConfig {\n /** DeepSeek API key. */\n apiKey: string;\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n name: string;\n description: string;\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n tool_name: string;\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n tool_name: string;\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n completion_tokens: number;\n prompt_tokens: number;\n prompt_cache_hit_tokens?: number | undefined;\n prompt_cache_miss_tokens?: number | undefined;\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n role: 'assistant';\n content: string;\n tool_calls?: ResponseToolCall[] | undefined;\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n role: 'assistant';\n content: string;\n tool_calls?: StreamToolCall[] | undefined;\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface DeepseekUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n if (isRecord(tool) && tool['type'] === 'function') {\n return tool as unknown as ChatTool;\n }\n const raw = isRecord(tool) ? tool : {};\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: DeepseekUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * DeepSeek model provider. It calls the DeepSeek API through the OpenAI-compatible client.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n\n constructor(config: DeepseekConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model ?? DEFAULT_MODEL;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n if (delta?.content) {\n yield { role: 'assistant', content: delta.content };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive((prompt: string) => {\n this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive((messages: Message[]) => {\n this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt ? [{ role: 'system', content: this.systemPrompt }] : [];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [\n ...this.systemMessages(),\n ...messages.map(\n (message): ChatMessageParam =>\n ({\n role: message.role,\n content: message.content,\n ...(message.role === 'tool' && message.tool_call_id\n ? { tool_call_id: message.tool_call_id }\n : {}),\n }) as ChatMessageParam\n ),\n ];\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error('DeepSeek returned no choices.');\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error('[Deepseek] API call failed:', error);\n }\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/model-provider/openai-compatible/index.ts","../../src/model-provider/deepseek/index.ts"],"sourcesContent":["import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n if (isRecord(tool) && tool['type'] === 'function') {\n return tool as unknown as ChatTool;\n }\n const raw = isRecord(tool) ? tool : {};\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n if (delta?.content) {\n yield { role: 'assistant', content: delta.content };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive((prompt: string) => {\n this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive((messages: Message[]) => {\n this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt ? [{ role: 'system', content: this.systemPrompt }] : [];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [\n ...this.systemMessages(),\n ...messages.map(\n (message): ChatMessageParam =>\n ({\n role: message.role,\n content: message.content,\n ...(message.role === 'tool' && message.tool_call_id\n ? { tool_call_id: message.tool_call_id }\n : {}),\n }) as ChatMessageParam\n ),\n ];\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["DEFAULT_BASE_URL"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astack-tech/integrations",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.8",
|
|
4
4
|
"description": "Model provider integrations for AStack, starting with DeepSeek through the OpenAI-compatible API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astack",
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"openai": "^6.10.0",
|
|
62
|
-
"@astack-tech/core": "0.2.0-beta.
|
|
62
|
+
"@astack-tech/core": "0.2.0-beta.3"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/node": "^22.20.5",
|
|
66
66
|
"@vitest/coverage-v8": "^5.0.5",
|
|
67
67
|
"tsup": "^9.0.0",
|
|
68
|
-
"typescript": "^
|
|
68
|
+
"typescript": "^6.0.3",
|
|
69
69
|
"vitest": "^5.0.5"
|
|
70
70
|
},
|
|
71
71
|
"publishConfig": {
|
|
@@ -77,6 +77,8 @@
|
|
|
77
77
|
"check-types": "tsc -p tsconfig.json",
|
|
78
78
|
"test": "vitest run --coverage",
|
|
79
79
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
80
|
-
"check:package": "publint --strict && attw --pack . --profile node16"
|
|
80
|
+
"check:package": "publint --strict && attw --pack . --profile node16",
|
|
81
|
+
"check:api": "tsc -p tsconfig.json --noEmit false --emitDeclarationOnly --declarationMap false --outDir temp/dts && api-extractor run",
|
|
82
|
+
"update:api": "tsc -p tsconfig.json --noEmit false --emitDeclarationOnly --declarationMap false --outDir temp/dts && api-extractor run --local"
|
|
81
83
|
}
|
|
82
84
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Model provider integrations for AStack. `ModelProvider.OpenAICompatible` calls any API
|
|
3
|
+
* that speaks the OpenAI chat completions protocol, and `ModelProvider.Deepseek` is that
|
|
4
|
+
* provider set up for DeepSeek. Both satisfy the `ModelProvider` interface of
|
|
5
|
+
* `@astack-tech/components`.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
3
8
|
*/
|
|
9
|
+
|
|
4
10
|
export * as ModelProvider from './model-provider';
|
|
@@ -1,455 +1,49 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
|
|
1
|
+
import OpenAICompatible from '../openai-compatible';
|
|
2
|
+
import type { OpenAICompatibleConfig } from '../openai-compatible';
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
AssistantMessage,
|
|
6
|
+
ChatCompletionOptions,
|
|
7
|
+
Message,
|
|
8
|
+
MessageRole,
|
|
9
|
+
RawTool,
|
|
10
|
+
ResponseToolCall,
|
|
11
|
+
StreamChunk,
|
|
12
|
+
StreamToolCall,
|
|
13
|
+
TokenUsage,
|
|
14
|
+
ToolCall,
|
|
15
|
+
} from '../openai-compatible';
|
|
16
|
+
|
|
17
|
+
const DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';
|
|
18
|
+
const DEFAULT_MODEL = 'deepseek-chat';
|
|
4
19
|
|
|
5
20
|
/**
|
|
6
|
-
* Options for the DeepSeek model provider.
|
|
21
|
+
* Options for the DeepSeek model provider. They are the options of
|
|
22
|
+
* `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.
|
|
7
23
|
*/
|
|
8
|
-
export interface DeepseekConfig {
|
|
9
|
-
/** DeepSeek API key. */
|
|
10
|
-
apiKey: string;
|
|
24
|
+
export interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {
|
|
11
25
|
/** Model name. Defaults to `deepseek-chat`. */
|
|
12
26
|
model?: string | undefined;
|
|
13
27
|
/** API base URL. Defaults to `https://api.deepseek.com/v1`. */
|
|
14
28
|
baseURL?: string | undefined;
|
|
15
|
-
/** Sampling temperature. Defaults to `0.7`. */
|
|
16
|
-
temperature?: number | undefined;
|
|
17
|
-
/** Maximum number of generated tokens. */
|
|
18
|
-
maxTokens?: number | undefined;
|
|
19
|
-
/** Nucleus sampling probability. */
|
|
20
|
-
topP?: number | undefined;
|
|
21
|
-
/** System prompt added before every request. */
|
|
22
|
-
systemPrompt?: string | undefined;
|
|
23
|
-
/**
|
|
24
|
-
* Tools given as `{ name, description, parameters }` objects.
|
|
25
|
-
* When this list is not empty, it takes precedence over `tools`.
|
|
26
|
-
*/
|
|
27
|
-
rawTools?: RawTool[] | undefined;
|
|
28
|
-
/** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */
|
|
29
|
-
tools?: unknown[] | undefined;
|
|
30
|
-
/** Allows the OpenAI client to run in a browser. Defaults to `false`. */
|
|
31
|
-
dangerouslyAllowBrowser?: boolean | undefined;
|
|
32
|
-
/** Logs API errors to the console. Defaults to `false`. */
|
|
33
|
-
verbose?: boolean | undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Tool description accepted by `rawTools` and `temporaryTools`.
|
|
38
|
-
*/
|
|
39
|
-
export interface RawTool {
|
|
40
|
-
name: string;
|
|
41
|
-
description: string;
|
|
42
|
-
parameters?: Record<string, unknown> | undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Role of a chat message.
|
|
47
|
-
*/
|
|
48
|
-
export type MessageRole = 'system' | 'user' | 'assistant' | 'tool';
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Tool call in the internal format.
|
|
52
|
-
*/
|
|
53
|
-
export interface ToolCall {
|
|
54
|
-
/** Identifier assigned by the API. */
|
|
55
|
-
id?: string | undefined;
|
|
56
|
-
/** Name of the tool. */
|
|
57
|
-
tool_name: string;
|
|
58
|
-
/** Parsed arguments. */
|
|
59
|
-
arguments: Record<string, unknown>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Tool call returned by `chatCompletion`. It carries both the OpenAI shape
|
|
64
|
-
* (`type`, `function`) and the internal shape (`tool_name`, `arguments`).
|
|
65
|
-
*/
|
|
66
|
-
export interface ResponseToolCall {
|
|
67
|
-
id: string;
|
|
68
|
-
type: 'function';
|
|
69
|
-
function: { name: string; arguments: string };
|
|
70
|
-
tool_name: string;
|
|
71
|
-
arguments: Record<string, unknown>;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.
|
|
76
|
-
*/
|
|
77
|
-
export interface StreamToolCall {
|
|
78
|
-
id: string;
|
|
79
|
-
type: 'function';
|
|
80
|
-
function: { name: string; arguments: string };
|
|
81
|
-
tool_name: string;
|
|
82
|
-
arguments: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Token usage reported by the API.
|
|
87
|
-
*/
|
|
88
|
-
export interface TokenUsage {
|
|
89
|
-
completion_tokens: number;
|
|
90
|
-
prompt_tokens: number;
|
|
91
|
-
prompt_cache_hit_tokens?: number | undefined;
|
|
92
|
-
prompt_cache_miss_tokens?: number | undefined;
|
|
93
|
-
total_tokens: number;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Chat message accepted as input. It matches the agent message contract in
|
|
98
|
-
* `@astack-tech/components`, so agent histories can be passed directly.
|
|
99
|
-
*/
|
|
100
|
-
export interface Message {
|
|
101
|
-
/** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */
|
|
102
|
-
role: string;
|
|
103
|
-
content?: string | undefined;
|
|
104
|
-
/** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */
|
|
105
|
-
tool_calls?: readonly unknown[] | undefined;
|
|
106
|
-
/** Identifier of the call that a `tool` message answers. */
|
|
107
|
-
tool_call_id?: string | undefined;
|
|
108
|
-
usage?: TokenUsage | undefined;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Assistant message returned by `chatCompletion`.
|
|
113
|
-
*/
|
|
114
|
-
export interface AssistantMessage {
|
|
115
|
-
role: 'assistant';
|
|
116
|
-
content: string;
|
|
117
|
-
tool_calls?: ResponseToolCall[] | undefined;
|
|
118
|
-
usage?: TokenUsage | undefined;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Chunk yielded by `streamChatCompletion`.
|
|
123
|
-
*/
|
|
124
|
-
export interface StreamChunk {
|
|
125
|
-
role: 'assistant';
|
|
126
|
-
content: string;
|
|
127
|
-
tool_calls?: StreamToolCall[] | undefined;
|
|
128
|
-
usage?: TokenUsage | undefined;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Per-call options.
|
|
133
|
-
*/
|
|
134
|
-
export interface ChatCompletionOptions {
|
|
135
|
-
/** Tools for this call only. They replace the configured tools. */
|
|
136
|
-
temporaryTools?: unknown[] | undefined;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
type ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;
|
|
140
|
-
type ChatTool = OpenAI.Chat.ChatCompletionTool;
|
|
141
|
-
|
|
142
|
-
interface DeepseekUsage {
|
|
143
|
-
completion_tokens?: number;
|
|
144
|
-
prompt_tokens?: number;
|
|
145
|
-
total_tokens?: number;
|
|
146
|
-
prompt_cache_hit_tokens?: number;
|
|
147
|
-
prompt_cache_miss_tokens?: number;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';
|
|
151
|
-
const DEFAULT_MODEL = 'deepseek-chat';
|
|
152
|
-
const DEFAULT_TEMPERATURE = 0.7;
|
|
153
|
-
const FALLBACK_TEXT = 'API call failed';
|
|
154
|
-
|
|
155
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
156
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Converts a `{ name, description, parameters }` object to the OpenAI tool format.
|
|
161
|
-
* Objects already in the OpenAI format are returned unchanged.
|
|
162
|
-
*/
|
|
163
|
-
function toApiTool(tool: unknown): ChatTool {
|
|
164
|
-
if (isRecord(tool) && tool['type'] === 'function') {
|
|
165
|
-
return tool as unknown as ChatTool;
|
|
166
|
-
}
|
|
167
|
-
const raw = isRecord(tool) ? tool : {};
|
|
168
|
-
const { name, description, parameters } = raw;
|
|
169
|
-
return {
|
|
170
|
-
type: 'function',
|
|
171
|
-
function: {
|
|
172
|
-
name: typeof name === 'string' ? name : '',
|
|
173
|
-
description: typeof description === 'string' ? description : '',
|
|
174
|
-
parameters: isRecord(parameters) ? parameters : {},
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
29
|
}
|
|
178
30
|
|
|
179
31
|
/**
|
|
180
|
-
*
|
|
181
|
-
*/
|
|
182
|
-
function parseArguments(raw: string): Record<string, unknown> {
|
|
183
|
-
if (raw.trim().length === 0) {
|
|
184
|
-
return {};
|
|
185
|
-
}
|
|
186
|
-
try {
|
|
187
|
-
const parsed: unknown = JSON.parse(raw);
|
|
188
|
-
return isRecord(parsed) ? parsed : {};
|
|
189
|
-
} catch {
|
|
190
|
-
return {};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function mapUsage(usage: DeepseekUsage): TokenUsage {
|
|
195
|
-
return {
|
|
196
|
-
completion_tokens: usage.completion_tokens ?? 0,
|
|
197
|
-
prompt_tokens: usage.prompt_tokens ?? 0,
|
|
198
|
-
total_tokens: usage.total_tokens ?? 0,
|
|
199
|
-
...(usage.prompt_cache_hit_tokens === undefined
|
|
200
|
-
? {}
|
|
201
|
-
: { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),
|
|
202
|
-
...(usage.prompt_cache_miss_tokens === undefined
|
|
203
|
-
? {}
|
|
204
|
-
: { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* DeepSeek model provider. It calls the DeepSeek API through the OpenAI-compatible client.
|
|
32
|
+
* DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.
|
|
210
33
|
*
|
|
211
34
|
* Ports:
|
|
212
35
|
* - `prompt` (in): a text prompt. The answer is sent on `completion`.
|
|
213
36
|
* - `messages` (in): a list of chat messages. The reply is sent on `message`.
|
|
214
37
|
*/
|
|
215
|
-
class Deepseek extends
|
|
216
|
-
private readonly client: OpenAI;
|
|
217
|
-
private readonly model: string;
|
|
218
|
-
private readonly temperature: number;
|
|
219
|
-
private readonly maxTokens: number | undefined;
|
|
220
|
-
private readonly topP: number | undefined;
|
|
221
|
-
private readonly systemPrompt: string | undefined;
|
|
222
|
-
private readonly tools: unknown[] | undefined;
|
|
223
|
-
private readonly verbose: boolean;
|
|
224
|
-
|
|
38
|
+
class Deepseek extends OpenAICompatible {
|
|
225
39
|
constructor(config: DeepseekConfig) {
|
|
226
|
-
super({
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
apiKey: config.apiKey,
|
|
40
|
+
super({
|
|
41
|
+
...config,
|
|
42
|
+
model: config.model ?? DEFAULT_MODEL,
|
|
230
43
|
baseURL: config.baseURL ?? DEFAULT_BASE_URL,
|
|
231
|
-
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
this.model = config.model ?? DEFAULT_MODEL;
|
|
235
|
-
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
236
|
-
this.maxTokens = config.maxTokens;
|
|
237
|
-
this.topP = config.topP;
|
|
238
|
-
this.systemPrompt = config.systemPrompt;
|
|
239
|
-
this.verbose = config.verbose ?? false;
|
|
240
|
-
this.tools =
|
|
241
|
-
config.rawTools !== undefined && config.rawTools.length > 0
|
|
242
|
-
? config.rawTools.map(toApiTool)
|
|
243
|
-
: config.tools;
|
|
244
|
-
|
|
245
|
-
Component.Port.I('prompt').attach(this);
|
|
246
|
-
Component.Port.I('messages').attach(this);
|
|
247
|
-
Component.Port.O('completion').attach(this);
|
|
248
|
-
Component.Port.O('message').attach(this);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Generates a completion for a single prompt.
|
|
253
|
-
*
|
|
254
|
-
* @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.
|
|
255
|
-
*/
|
|
256
|
-
async generateCompletion(prompt: string): Promise<string> {
|
|
257
|
-
const response = await this.client.chat.completions.create({
|
|
258
|
-
...this.baseRequest(),
|
|
259
|
-
messages: [...this.systemMessages(), { role: 'user', content: prompt }],
|
|
260
|
-
...this.toolsParam(this.tools),
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const message = this.firstMessage(response);
|
|
264
|
-
if (message.tool_calls !== undefined && message.tool_calls.length > 0) {
|
|
265
|
-
return JSON.stringify(message);
|
|
266
|
-
}
|
|
267
|
-
return message.content ?? '';
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Sends a conversation and returns the assistant reply.
|
|
272
|
-
*/
|
|
273
|
-
async chatCompletion(
|
|
274
|
-
messages: Message[],
|
|
275
|
-
options?: ChatCompletionOptions
|
|
276
|
-
): Promise<AssistantMessage> {
|
|
277
|
-
const response = await this.client.chat.completions.create({
|
|
278
|
-
...this.baseRequest(),
|
|
279
|
-
messages: this.formatMessages(messages),
|
|
280
|
-
...this.toolsParam(options?.temporaryTools ?? this.tools),
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
const message = this.firstMessage(response);
|
|
284
|
-
const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };
|
|
285
|
-
|
|
286
|
-
if (response.usage !== undefined) {
|
|
287
|
-
result.usage = mapUsage(response.usage);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (message.tool_calls !== undefined && message.tool_calls.length > 0) {
|
|
291
|
-
result.tool_calls = message.tool_calls.map(toolCall => {
|
|
292
|
-
if (toolCall.type !== 'function') {
|
|
293
|
-
throw new Error(`Unsupported tool call type: ${toolCall.type}`);
|
|
294
|
-
}
|
|
295
|
-
const rawArguments = toolCall.function.arguments;
|
|
296
|
-
return {
|
|
297
|
-
id: toolCall.id,
|
|
298
|
-
type: 'function',
|
|
299
|
-
function: {
|
|
300
|
-
name: toolCall.function.name,
|
|
301
|
-
arguments: rawArguments.length > 0 ? rawArguments : '{}',
|
|
302
|
-
},
|
|
303
|
-
tool_name: toolCall.function.name,
|
|
304
|
-
arguments: parseArguments(rawArguments),
|
|
305
|
-
};
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return result;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.
|
|
314
|
-
*/
|
|
315
|
-
async *streamChatCompletion(
|
|
316
|
-
messages: Message[],
|
|
317
|
-
options?: ChatCompletionOptions
|
|
318
|
-
): AsyncGenerator<StreamChunk> {
|
|
319
|
-
const stream = await this.client.chat.completions.create({
|
|
320
|
-
...this.baseRequest(),
|
|
321
|
-
messages: this.formatMessages(messages),
|
|
322
|
-
...this.toolsParam(options?.temporaryTools ?? this.tools),
|
|
323
|
-
stream: true,
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
for await (const chunk of stream) {
|
|
327
|
-
const delta = chunk.choices[0]?.delta;
|
|
328
|
-
|
|
329
|
-
if (delta?.content) {
|
|
330
|
-
yield { role: 'assistant', content: delta.content };
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (delta?.tool_calls) {
|
|
334
|
-
yield {
|
|
335
|
-
role: 'assistant',
|
|
336
|
-
content: '',
|
|
337
|
-
tool_calls: delta.tool_calls.map(toolCall => {
|
|
338
|
-
const name = toolCall.function?.name ?? '';
|
|
339
|
-
const args = toolCall.function?.arguments ?? '';
|
|
340
|
-
return {
|
|
341
|
-
id: toolCall.id ?? '',
|
|
342
|
-
type: 'function',
|
|
343
|
-
function: { name, arguments: args },
|
|
344
|
-
tool_name: name,
|
|
345
|
-
arguments: args,
|
|
346
|
-
};
|
|
347
|
-
}),
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (chunk.usage) {
|
|
352
|
-
yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Runs the model outside a pipeline.
|
|
359
|
-
*
|
|
360
|
-
* @param input A prompt string or a list of messages.
|
|
361
|
-
*/
|
|
362
|
-
override run(input: string | Message[]): Promise<string | AssistantMessage> {
|
|
363
|
-
return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Connects the `prompt` and `messages` input ports to the model.
|
|
368
|
-
* API errors produce a fallback value so the pipeline keeps flowing.
|
|
369
|
-
*/
|
|
370
|
-
override _transform($i: Port, $o: Port): void {
|
|
371
|
-
$i('prompt').receive((prompt: string) => {
|
|
372
|
-
this.generateCompletion(prompt).then(
|
|
373
|
-
completion => {
|
|
374
|
-
$o('completion').send(completion);
|
|
375
|
-
},
|
|
376
|
-
(error: unknown) => {
|
|
377
|
-
this.logError(error);
|
|
378
|
-
$o('completion').send(FALLBACK_TEXT);
|
|
379
|
-
}
|
|
380
|
-
);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
$i('messages').receive((messages: Message[]) => {
|
|
384
|
-
this.chatCompletion(messages).then(
|
|
385
|
-
message => {
|
|
386
|
-
$o('message').send(message);
|
|
387
|
-
},
|
|
388
|
-
(error: unknown) => {
|
|
389
|
-
this.logError(error);
|
|
390
|
-
$o('message').send({ role: 'assistant', content: FALLBACK_TEXT });
|
|
391
|
-
}
|
|
392
|
-
);
|
|
44
|
+
name: 'DeepSeek',
|
|
393
45
|
});
|
|
394
46
|
}
|
|
395
|
-
|
|
396
|
-
private baseRequest(): {
|
|
397
|
-
model: string;
|
|
398
|
-
temperature: number;
|
|
399
|
-
max_tokens?: number;
|
|
400
|
-
top_p?: number;
|
|
401
|
-
} {
|
|
402
|
-
return {
|
|
403
|
-
model: this.model,
|
|
404
|
-
temperature: this.temperature,
|
|
405
|
-
...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),
|
|
406
|
-
...(this.topP === undefined ? {} : { top_p: this.topP }),
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {
|
|
411
|
-
return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
private systemMessages(): ChatMessageParam[] {
|
|
415
|
-
return this.systemPrompt ? [{ role: 'system', content: this.systemPrompt }] : [];
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Converts input messages to API messages. Assistant `tool_calls` are not forwarded:
|
|
420
|
-
* agents send tool results back as user messages, and the API rejects assistant tool
|
|
421
|
-
* calls that are not followed by tool messages.
|
|
422
|
-
*/
|
|
423
|
-
private formatMessages(messages: Message[]): ChatMessageParam[] {
|
|
424
|
-
return [
|
|
425
|
-
...this.systemMessages(),
|
|
426
|
-
...messages.map(
|
|
427
|
-
(message): ChatMessageParam =>
|
|
428
|
-
({
|
|
429
|
-
role: message.role,
|
|
430
|
-
content: message.content,
|
|
431
|
-
...(message.role === 'tool' && message.tool_call_id
|
|
432
|
-
? { tool_call_id: message.tool_call_id }
|
|
433
|
-
: {}),
|
|
434
|
-
}) as ChatMessageParam
|
|
435
|
-
),
|
|
436
|
-
];
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {
|
|
440
|
-
const choice = response.choices[0];
|
|
441
|
-
if (choice === undefined) {
|
|
442
|
-
throw new Error('DeepSeek returned no choices.');
|
|
443
|
-
}
|
|
444
|
-
return choice.message;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
private logError(error: unknown): void {
|
|
448
|
-
if (this.verbose) {
|
|
449
|
-
// eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.
|
|
450
|
-
console.error('[Deepseek] API call failed:', error);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
47
|
}
|
|
454
48
|
|
|
455
49
|
export default Deepseek;
|
|
@@ -12,3 +12,17 @@ export {
|
|
|
12
12
|
type TokenUsage as DeepseekTokenUsage,
|
|
13
13
|
type ToolCall as DeepseekToolCall,
|
|
14
14
|
} from './deepseek';
|
|
15
|
+
export {
|
|
16
|
+
default as OpenAICompatible,
|
|
17
|
+
type AssistantMessage,
|
|
18
|
+
type ChatCompletionOptions,
|
|
19
|
+
type Message,
|
|
20
|
+
type MessageRole,
|
|
21
|
+
type OpenAICompatibleConfig,
|
|
22
|
+
type RawTool,
|
|
23
|
+
type ResponseToolCall,
|
|
24
|
+
type StreamChunk,
|
|
25
|
+
type StreamToolCall,
|
|
26
|
+
type TokenUsage,
|
|
27
|
+
type ToolCall,
|
|
28
|
+
} from './openai-compatible';
|