@astack-tech/integrations 0.1.1-beta.6 → 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 +50 -0
- package/README.md +96 -1
- package/dist/index.cjs +200 -245
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +200 -245
- package/dist/index.js.map +1 -1
- package/dist/model-provider/index.cjs +202 -246
- package/dist/model-provider/index.cjs.map +1 -1
- package/dist/model-provider/index.d.cts +218 -146
- package/dist/model-provider/index.d.ts +218 -146
- package/dist/model-provider/index.js +200 -245
- package/dist/model-provider/index.js.map +1 -1
- package/package.json +47 -26
- package/src/index.ts +5 -3
- package/src/model-provider/deepseek/index.ts +35 -596
- package/src/model-provider/index.ts +23 -2
- package/src/model-provider/openai-compatible/index.ts +499 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @astack-tech/integrations
|
|
2
|
+
|
|
3
|
+
## 0.2.0-beta.8
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9aab43a: Add `ModelProvider.OpenAICompatible`, a provider for any API that speaks the OpenAI chat completions protocol: OpenAI and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama, vLLM and others. Pass `model`, and `baseURL` for providers other than OpenAI; `name` labels errors and logs. `Deepseek` is now this provider with DeepSeek's defaults; its options, methods, ports and exported types are unchanged. The neutral message, tool and usage types are exported next to the `Deepseek*` names.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [1aba97f]
|
|
12
|
+
- @astack-tech/core@0.2.0-beta.3
|
|
13
|
+
|
|
14
|
+
## 0.2.0-beta.7
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- e71dd3b: Deepseek satisfies the `ModelProvider` interface of `@astack-tech/components` without a cast. Unset request options are omitted, an empty `choices` array fails with a clear error, non-object tool arguments become `{}`, and the pipeline fallback text is `API call failed`. API errors are logged only when `verbose` is set. Node.js 20 or later is required.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [e71dd3b]
|
|
23
|
+
- @astack-tech/core@0.2.0-beta.2
|
|
24
|
+
|
|
25
|
+
## 0.1.1-beta.6
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies
|
|
30
|
+
- @astack-tech/core@0.2.0-beta.1
|
|
31
|
+
|
|
32
|
+
## 0.1.1-beta.2
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- rebuild package
|
|
37
|
+
|
|
38
|
+
## 0.1.1-beta.1
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- fix agent tool finder
|
|
43
|
+
|
|
44
|
+
## 0.1.1-beta.0
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- beta release
|
|
49
|
+
- Updated dependencies
|
|
50
|
+
- @astack-tech/core@0.1.1-beta.0
|
package/README.md
CHANGED
|
@@ -1 +1,96 @@
|
|
|
1
|
-
|
|
1
|
+
# @astack-tech/integrations
|
|
2
|
+
|
|
3
|
+
<img src="https://raw.githubusercontent.com/astack-tech/astack/master/assets/brand/readme-banner.png" alt="AStack" width="100%">
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@astack-tech/integrations) [](https://github.com/astack-tech/astack/blob/master/LICENSE) [](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
Model providers for AStack. Each provider is a component: call it directly, use it in a pipeline, or pass it to an agent.
|
|
8
|
+
|
|
9
|
+
[Website](https://astack.tech) · [Docs](https://astack.tech/docs) · [Examples](https://astack.tech/examples) · [GitHub](https://github.com/astack-tech/astack)
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @astack-tech/integrations @astack-tech/core
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { ModelProvider } from '@astack-tech/integrations';
|
|
21
|
+
|
|
22
|
+
const model = new ModelProvider.Deepseek({ apiKey: process.env['DEEPSEEK_API_KEY'] ?? '' });
|
|
23
|
+
|
|
24
|
+
const text = await model.run('What is flow-based programming?');
|
|
25
|
+
const reply = await model.chatCompletion([{ role: 'user', content: 'Hello' }]);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`Deepseek` and `OpenAICompatible` are also available from the subpath: `import { Deepseek, OpenAICompatible } from '@astack-tech/integrations/model-provider'`.
|
|
29
|
+
|
|
30
|
+
## Deepseek
|
|
31
|
+
|
|
32
|
+
Uses the OpenAI-compatible DeepSeek API through the official `openai` client.
|
|
33
|
+
|
|
34
|
+
| Option | Default | Description |
|
|
35
|
+
| -------------------- | ----------------------------- | -------------------------------------------------------------------- |
|
|
36
|
+
| `apiKey` | — | DeepSeek API key |
|
|
37
|
+
| `model` | `deepseek-chat` | Model name |
|
|
38
|
+
| `baseURL` | `https://api.deepseek.com/v1` | API base URL |
|
|
39
|
+
| `temperature` | `0.7` | Sampling temperature |
|
|
40
|
+
| `maxTokens`, `topP` | not sent | Optional sampling limits |
|
|
41
|
+
| `systemPrompt` | none | Added before every request |
|
|
42
|
+
| `rawTools` / `tools` | none | Tools as `{ name, description, parameters }` or in the OpenAI format |
|
|
43
|
+
| `verbose` | `false` | Log API errors to the console |
|
|
44
|
+
|
|
45
|
+
| Method | Returns |
|
|
46
|
+
| --------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
47
|
+
| `run(prompt)` | Text |
|
|
48
|
+
| `run(messages)`, `chatCompletion(messages, { temporaryTools })` | Assistant message with `content`, `tool_calls` and `usage` |
|
|
49
|
+
| `streamChatCompletion(messages, options)` | Async generator of text, tool-call and usage chunks |
|
|
50
|
+
| `generateCompletion(prompt)` | Text, or the JSON assistant message when the model calls tools |
|
|
51
|
+
|
|
52
|
+
| Pipeline input port | Output port |
|
|
53
|
+
| ------------------- | ------------ |
|
|
54
|
+
| `prompt` | `completion` |
|
|
55
|
+
| `messages` | `message` |
|
|
56
|
+
|
|
57
|
+
`Deepseek` satisfies the `ModelProvider` interface of `@astack-tech/components`, so it can drive `Agent` and `StreamingAgent` directly.
|
|
58
|
+
|
|
59
|
+
## OpenAICompatible
|
|
60
|
+
|
|
61
|
+
Calls any API that speaks the OpenAI chat completions protocol: OpenAI and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama, vLLM and others. `Deepseek` is this provider with DeepSeek's defaults, so methods, ports and the other options are the same.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { ModelProvider } from '@astack-tech/integrations';
|
|
65
|
+
|
|
66
|
+
const model = new ModelProvider.OpenAICompatible({
|
|
67
|
+
apiKey: process.env['OPENAI_API_KEY'] ?? '',
|
|
68
|
+
model: 'gpt-4o-mini',
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Option | Default | Description |
|
|
73
|
+
| --------- | --------------------------- | -------------------------------- |
|
|
74
|
+
| `apiKey` | — | API key of the provider |
|
|
75
|
+
| `model` | — | Model name (required) |
|
|
76
|
+
| `baseURL` | `https://api.openai.com/v1` | API base URL |
|
|
77
|
+
| `name` | `The model API` | Provider name in errors and logs |
|
|
78
|
+
|
|
79
|
+
## Learn more
|
|
80
|
+
|
|
81
|
+
- [API reference](https://astack.tech/docs/api/integrations): every export, generated from the source
|
|
82
|
+
- [Model providers](https://astack.tech/docs/model-providers), including how to write your own
|
|
83
|
+
- Example: [chat](https://astack.tech/examples/chat)
|
|
84
|
+
|
|
85
|
+
## The AStack packages
|
|
86
|
+
|
|
87
|
+
| Package | Purpose |
|
|
88
|
+
| -------------------------------------------------------------------------------------- | ------------------------------------------ |
|
|
89
|
+
| [`@astack-tech/core`](https://www.npmjs.com/package/@astack-tech/core) | `Component`, `Pipeline` and ports |
|
|
90
|
+
| [`@astack-tech/components`](https://www.npmjs.com/package/@astack-tech/components) | Agents, text splitting and tool invocation |
|
|
91
|
+
| [`@astack-tech/tools`](https://www.npmjs.com/package/@astack-tech/tools) | Tool definitions for agents |
|
|
92
|
+
| [`@astack-tech/integrations`](https://www.npmjs.com/package/@astack-tech/integrations) | Model providers |
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
[MIT](./LICENSE). AStack is in public beta: APIs can change between beta releases. See [CHANGELOG.md](./CHANGELOG.md).
|
package/dist/index.cjs
CHANGED
|
@@ -37,310 +37,265 @@ module.exports = __toCommonJS(src_exports);
|
|
|
37
37
|
// src/model-provider/index.ts
|
|
38
38
|
var model_provider_exports = {};
|
|
39
39
|
__export(model_provider_exports, {
|
|
40
|
-
Deepseek: () => deepseek_default
|
|
40
|
+
Deepseek: () => deepseek_default,
|
|
41
|
+
OpenAICompatible: () => openai_compatible_default
|
|
41
42
|
});
|
|
42
43
|
|
|
43
|
-
// src/model-provider/
|
|
44
|
+
// src/model-provider/openai-compatible/index.ts
|
|
44
45
|
var import_core = require("@astack-tech/core");
|
|
45
46
|
var import_openai = __toESM(require("openai"), 1);
|
|
46
|
-
var
|
|
47
|
+
var DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
48
|
+
var DEFAULT_NAME = "The model API";
|
|
49
|
+
var DEFAULT_TEMPERATURE = 0.7;
|
|
50
|
+
var FALLBACK_TEXT = "API call failed";
|
|
51
|
+
function isRecord(value) {
|
|
52
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
function toApiTool(tool) {
|
|
55
|
+
if (isRecord(tool) && tool["type"] === "function") {
|
|
56
|
+
return tool;
|
|
57
|
+
}
|
|
58
|
+
const raw = isRecord(tool) ? tool : {};
|
|
59
|
+
const { name, description, parameters } = raw;
|
|
60
|
+
return {
|
|
61
|
+
type: "function",
|
|
62
|
+
function: {
|
|
63
|
+
name: typeof name === "string" ? name : "",
|
|
64
|
+
description: typeof description === "string" ? description : "",
|
|
65
|
+
parameters: isRecord(parameters) ? parameters : {}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function parseArguments(raw) {
|
|
70
|
+
if (raw.trim().length === 0) {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(raw);
|
|
75
|
+
return isRecord(parsed) ? parsed : {};
|
|
76
|
+
} catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function mapUsage(usage) {
|
|
81
|
+
return {
|
|
82
|
+
completion_tokens: usage.completion_tokens ?? 0,
|
|
83
|
+
prompt_tokens: usage.prompt_tokens ?? 0,
|
|
84
|
+
total_tokens: usage.total_tokens ?? 0,
|
|
85
|
+
...usage.prompt_cache_hit_tokens === void 0 ? {} : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens },
|
|
86
|
+
...usage.prompt_cache_miss_tokens === void 0 ? {} : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
var OpenAICompatible = class extends import_core.Component {
|
|
90
|
+
client;
|
|
91
|
+
model;
|
|
92
|
+
temperature;
|
|
93
|
+
maxTokens;
|
|
94
|
+
topP;
|
|
95
|
+
systemPrompt;
|
|
96
|
+
tools;
|
|
97
|
+
verbose;
|
|
98
|
+
name;
|
|
47
99
|
constructor(config) {
|
|
48
100
|
super({});
|
|
49
101
|
this.client = new import_openai.default({
|
|
50
102
|
apiKey: config.apiKey,
|
|
51
|
-
baseURL: config.baseURL
|
|
103
|
+
baseURL: config.baseURL ?? DEFAULT_BASE_URL,
|
|
52
104
|
dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false
|
|
53
105
|
});
|
|
54
|
-
this.model = config.model
|
|
55
|
-
this.
|
|
106
|
+
this.model = config.model;
|
|
107
|
+
this.name = config.name ?? DEFAULT_NAME;
|
|
108
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
56
109
|
this.maxTokens = config.maxTokens;
|
|
57
110
|
this.topP = config.topP;
|
|
58
111
|
this.systemPrompt = config.systemPrompt;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
type: "function",
|
|
62
|
-
function: {
|
|
63
|
-
name: tool.name,
|
|
64
|
-
description: tool.description,
|
|
65
|
-
parameters: tool.parameters || {}
|
|
66
|
-
}
|
|
67
|
-
}));
|
|
68
|
-
} else {
|
|
69
|
-
this.tools = config.tools;
|
|
70
|
-
}
|
|
112
|
+
this.verbose = config.verbose ?? false;
|
|
113
|
+
this.tools = config.rawTools !== void 0 && config.rawTools.length > 0 ? config.rawTools.map(toApiTool) : config.tools;
|
|
71
114
|
import_core.Component.Port.I("prompt").attach(this);
|
|
72
115
|
import_core.Component.Port.I("messages").attach(this);
|
|
73
116
|
import_core.Component.Port.O("completion").attach(this);
|
|
74
117
|
import_core.Component.Port.O("message").attach(this);
|
|
75
118
|
}
|
|
76
119
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* @returns
|
|
120
|
+
* Generates a completion for a single prompt.
|
|
121
|
+
*
|
|
122
|
+
* @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.
|
|
80
123
|
*/
|
|
81
124
|
async generateCompletion(prompt) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
messages.
|
|
85
|
-
|
|
86
|
-
content: this.systemPrompt
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
messages.push({
|
|
90
|
-
role: "user",
|
|
91
|
-
content: prompt
|
|
125
|
+
const response = await this.client.chat.completions.create({
|
|
126
|
+
...this.baseRequest(),
|
|
127
|
+
messages: [...this.systemMessages(), { role: "user", content: prompt }],
|
|
128
|
+
...this.toolsParam(this.tools)
|
|
92
129
|
});
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
temperature: this.temperature,
|
|
97
|
-
max_tokens: this.maxTokens,
|
|
98
|
-
top_p: this.topP
|
|
99
|
-
};
|
|
100
|
-
if (this.tools && this.tools.length > 0) {
|
|
101
|
-
requestParams.tools = this.tools;
|
|
130
|
+
const message = this.firstMessage(response);
|
|
131
|
+
if (message.tool_calls !== void 0 && message.tool_calls.length > 0) {
|
|
132
|
+
return JSON.stringify(message);
|
|
102
133
|
}
|
|
103
|
-
|
|
104
|
-
if (response.choices[0].message.tool_calls && response.choices[0].message.tool_calls.length > 0) {
|
|
105
|
-
return JSON.stringify(response.choices[0].message);
|
|
106
|
-
}
|
|
107
|
-
return response.choices[0].message.content || "";
|
|
134
|
+
return message.content ?? "";
|
|
108
135
|
}
|
|
109
136
|
/**
|
|
110
|
-
*
|
|
111
|
-
* @param messages 对话消息数组
|
|
112
|
-
* @param options 可选的调用选项,包含临时工具列表
|
|
113
|
-
* @returns 生成的流式响应消息异步生成器
|
|
137
|
+
* Sends a conversation and returns the assistant reply.
|
|
114
138
|
*/
|
|
115
|
-
async
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
139
|
+
async chatCompletion(messages, options) {
|
|
140
|
+
const response = await this.client.chat.completions.create({
|
|
141
|
+
...this.baseRequest(),
|
|
142
|
+
messages: this.formatMessages(messages),
|
|
143
|
+
...this.toolsParam(options?.temporaryTools ?? this.tools)
|
|
144
|
+
});
|
|
145
|
+
const message = this.firstMessage(response);
|
|
146
|
+
const result = { role: "assistant", content: message.content ?? "" };
|
|
147
|
+
if (response.usage !== void 0) {
|
|
148
|
+
result.usage = mapUsage(response.usage);
|
|
122
149
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
content: msg.content
|
|
128
|
-
};
|
|
129
|
-
if (msg.role === "tool" && msg.tool_call_id) {
|
|
130
|
-
formattedMsg.tool_call_id = msg.tool_call_id;
|
|
131
|
-
}
|
|
132
|
-
return formattedMsg;
|
|
133
|
-
})
|
|
134
|
-
);
|
|
135
|
-
const requestParams = {
|
|
136
|
-
model: this.model,
|
|
137
|
-
messages: formattedMessages,
|
|
138
|
-
temperature: this.temperature,
|
|
139
|
-
max_tokens: this.maxTokens,
|
|
140
|
-
top_p: this.topP,
|
|
141
|
-
stream: true
|
|
142
|
-
// 开启流式模式
|
|
143
|
-
};
|
|
144
|
-
const toolsToUse = options?.temporaryTools || this.tools;
|
|
145
|
-
if (toolsToUse && toolsToUse.length > 0) {
|
|
146
|
-
const formattedTools = toolsToUse.map((tool) => {
|
|
147
|
-
const typedTool = tool;
|
|
148
|
-
if (typedTool.type === "function") {
|
|
149
|
-
return tool;
|
|
150
|
+
if (message.tool_calls !== void 0 && message.tool_calls.length > 0) {
|
|
151
|
+
result.tool_calls = message.tool_calls.map((toolCall) => {
|
|
152
|
+
if (toolCall.type !== "function") {
|
|
153
|
+
throw new Error(`Unsupported tool call type: ${toolCall.type}`);
|
|
150
154
|
}
|
|
155
|
+
const rawArguments = toolCall.function.arguments;
|
|
151
156
|
return {
|
|
157
|
+
id: toolCall.id,
|
|
152
158
|
type: "function",
|
|
153
159
|
function: {
|
|
154
|
-
name:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
name: toolCall.function.name,
|
|
161
|
+
arguments: rawArguments.length > 0 ? rawArguments : "{}"
|
|
162
|
+
},
|
|
163
|
+
tool_name: toolCall.function.name,
|
|
164
|
+
arguments: parseArguments(rawArguments)
|
|
158
165
|
};
|
|
159
166
|
});
|
|
160
|
-
requestParams.tools = formattedTools;
|
|
161
167
|
}
|
|
162
|
-
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.
|
|
172
|
+
*/
|
|
173
|
+
async *streamChatCompletion(messages, options) {
|
|
174
|
+
const stream = await this.client.chat.completions.create({
|
|
175
|
+
...this.baseRequest(),
|
|
176
|
+
messages: this.formatMessages(messages),
|
|
177
|
+
...this.toolsParam(options?.temporaryTools ?? this.tools),
|
|
178
|
+
stream: true
|
|
179
|
+
});
|
|
163
180
|
for await (const chunk of stream) {
|
|
164
181
|
const delta = chunk.choices[0]?.delta;
|
|
165
182
|
if (delta?.content) {
|
|
166
|
-
yield {
|
|
167
|
-
role: "assistant",
|
|
168
|
-
content: delta.content
|
|
169
|
-
};
|
|
183
|
+
yield { role: "assistant", content: delta.content };
|
|
170
184
|
}
|
|
171
185
|
if (delta?.tool_calls) {
|
|
172
186
|
yield {
|
|
173
187
|
role: "assistant",
|
|
174
188
|
content: "",
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
})
|
|
189
|
+
tool_calls: delta.tool_calls.map((toolCall) => {
|
|
190
|
+
const name = toolCall.function?.name ?? "";
|
|
191
|
+
const args = toolCall.function?.arguments ?? "";
|
|
192
|
+
return {
|
|
193
|
+
id: toolCall.id ?? "",
|
|
194
|
+
type: "function",
|
|
195
|
+
function: { name, arguments: args },
|
|
196
|
+
tool_name: name,
|
|
197
|
+
arguments: args
|
|
198
|
+
};
|
|
199
|
+
})
|
|
186
200
|
};
|
|
187
201
|
}
|
|
188
202
|
if (chunk.usage) {
|
|
189
|
-
yield {
|
|
190
|
-
role: "assistant",
|
|
191
|
-
content: "",
|
|
192
|
-
usage: {
|
|
193
|
-
completion_tokens: chunk.usage.completion_tokens || 0,
|
|
194
|
-
prompt_tokens: chunk.usage.prompt_tokens || 0,
|
|
195
|
-
prompt_cache_hit_tokens: chunk.usage.prompt_cache_hit_tokens,
|
|
196
|
-
prompt_cache_miss_tokens: chunk.usage.prompt_cache_miss_tokens,
|
|
197
|
-
total_tokens: chunk.usage.total_tokens || 0
|
|
198
|
-
}
|
|
199
|
-
};
|
|
203
|
+
yield { role: "assistant", content: "", usage: mapUsage(chunk.usage) };
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
}
|
|
203
207
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* @param
|
|
207
|
-
* @returns 生成的响应消息
|
|
208
|
+
* Runs the model outside a pipeline.
|
|
209
|
+
*
|
|
210
|
+
* @param input - A prompt string or a list of messages.
|
|
208
211
|
*/
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
212
|
+
run(input) {
|
|
213
|
+
return typeof input === "string" ? this.generateCompletion(input) : this.chatCompletion(input);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Connects the `prompt` and `messages` input ports to the model.
|
|
217
|
+
* API errors produce a fallback value so the pipeline keeps flowing.
|
|
218
|
+
*/
|
|
219
|
+
_transform($i, $o) {
|
|
220
|
+
$i("prompt").receive((prompt) => {
|
|
221
|
+
this.generateCompletion(prompt).then(
|
|
222
|
+
(completion) => {
|
|
223
|
+
$o("completion").send(completion);
|
|
224
|
+
},
|
|
225
|
+
(error) => {
|
|
226
|
+
this.logError(error);
|
|
227
|
+
$o("completion").send(FALLBACK_TEXT);
|
|
225
228
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
)
|
|
229
|
-
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
$i("messages").receive((messages) => {
|
|
232
|
+
this.chatCompletion(messages).then(
|
|
233
|
+
(message) => {
|
|
234
|
+
$o("message").send(message);
|
|
235
|
+
},
|
|
236
|
+
(error) => {
|
|
237
|
+
this.logError(error);
|
|
238
|
+
$o("message").send({ role: "assistant", content: FALLBACK_TEXT });
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
baseRequest() {
|
|
244
|
+
return {
|
|
230
245
|
model: this.model,
|
|
231
|
-
messages: formattedMessages,
|
|
232
246
|
temperature: this.temperature,
|
|
233
|
-
max_tokens: this.maxTokens,
|
|
234
|
-
top_p: this.topP
|
|
247
|
+
...this.maxTokens === void 0 ? {} : { max_tokens: this.maxTokens },
|
|
248
|
+
...this.topP === void 0 ? {} : { top_p: this.topP }
|
|
235
249
|
};
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
return {
|
|
244
|
-
type: "function",
|
|
245
|
-
function: {
|
|
246
|
-
name: typedTool.name,
|
|
247
|
-
description: typedTool.description,
|
|
248
|
-
parameters: typedTool.parameters || {}
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
});
|
|
252
|
-
requestParams.tools = formattedTools;
|
|
253
|
-
}
|
|
254
|
-
const response = await this.client.chat.completions.create(requestParams);
|
|
255
|
-
const responseMessage = response.choices[0].message;
|
|
256
|
-
const result = {
|
|
257
|
-
role: "assistant",
|
|
258
|
-
content: responseMessage.content || ""
|
|
259
|
-
};
|
|
260
|
-
if (response.usage) {
|
|
261
|
-
const usage = response.usage;
|
|
262
|
-
result.usage = {
|
|
263
|
-
completion_tokens: usage.completion_tokens || 0,
|
|
264
|
-
prompt_tokens: usage.prompt_tokens || 0,
|
|
265
|
-
prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens,
|
|
266
|
-
prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens,
|
|
267
|
-
total_tokens: usage.total_tokens || 0
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
|
|
271
|
-
result.tool_calls = responseMessage.tool_calls.map((toolCall) => {
|
|
272
|
-
if (toolCall.type !== "function" || !("function" in toolCall)) {
|
|
273
|
-
throw new Error(`Unsupported tool call type: ${toolCall.type}`);
|
|
274
|
-
}
|
|
275
|
-
let args;
|
|
276
|
-
const rawArgs = toolCall.function.arguments;
|
|
277
|
-
if (typeof rawArgs === "string") {
|
|
278
|
-
const raw = rawArgs;
|
|
279
|
-
try {
|
|
280
|
-
args = raw && raw.trim().length > 0 ? JSON.parse(raw) : {};
|
|
281
|
-
} catch {
|
|
282
|
-
args = {};
|
|
283
|
-
}
|
|
284
|
-
} else if (rawArgs && typeof rawArgs === "object") {
|
|
285
|
-
args = rawArgs;
|
|
286
|
-
} else {
|
|
287
|
-
args = {};
|
|
288
|
-
}
|
|
289
|
-
return {
|
|
290
|
-
id: toolCall.id,
|
|
291
|
-
// 满足 Agent 组件期望的格式
|
|
292
|
-
function: {
|
|
293
|
-
name: toolCall.function.name,
|
|
294
|
-
arguments: toolCall.function.arguments || "{}"
|
|
295
|
-
},
|
|
296
|
-
type: "function",
|
|
297
|
-
// 满足内部 ToolCall 接口
|
|
298
|
-
tool_name: toolCall.function.name,
|
|
299
|
-
arguments: args
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
return result;
|
|
250
|
+
}
|
|
251
|
+
toolsParam(tools) {
|
|
252
|
+
return tools !== void 0 && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};
|
|
253
|
+
}
|
|
254
|
+
systemMessages() {
|
|
255
|
+
return this.systemPrompt ? [{ role: "system", content: this.systemPrompt }] : [];
|
|
304
256
|
}
|
|
305
257
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
258
|
+
* Converts input messages to API messages. Assistant `tool_calls` are not forwarded:
|
|
259
|
+
* agents send tool results back as user messages, and the API rejects assistant tool
|
|
260
|
+
* calls that are not followed by tool messages.
|
|
309
261
|
*/
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
262
|
+
formatMessages(messages) {
|
|
263
|
+
return [
|
|
264
|
+
...this.systemMessages(),
|
|
265
|
+
...messages.map(
|
|
266
|
+
(message) => ({
|
|
267
|
+
role: message.role,
|
|
268
|
+
content: message.content,
|
|
269
|
+
...message.role === "tool" && message.tool_call_id ? { tool_call_id: message.tool_call_id } : {}
|
|
270
|
+
})
|
|
271
|
+
)
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
firstMessage(response) {
|
|
275
|
+
const choice = response.choices[0];
|
|
276
|
+
if (choice === void 0) {
|
|
277
|
+
throw new Error(`${this.name} returned no choices.`);
|
|
315
278
|
}
|
|
279
|
+
return choice.message;
|
|
316
280
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const responseMessage = await this.chatCompletion(messages);
|
|
336
|
-
$o("message").send(responseMessage);
|
|
337
|
-
} catch (error) {
|
|
338
|
-
console.error("Deepseek API \u8C03\u7528\u9519\u8BEF:", error);
|
|
339
|
-
$o("message").send({
|
|
340
|
-
role: "assistant",
|
|
341
|
-
content: "API \u8C03\u7528\u9519\u8BEF"
|
|
342
|
-
});
|
|
343
|
-
}
|
|
281
|
+
logError(error) {
|
|
282
|
+
if (this.verbose) {
|
|
283
|
+
console.error(`[${this.name}] API call failed:`, error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
var openai_compatible_default = OpenAICompatible;
|
|
288
|
+
|
|
289
|
+
// src/model-provider/deepseek/index.ts
|
|
290
|
+
var DEFAULT_BASE_URL2 = "https://api.deepseek.com/v1";
|
|
291
|
+
var DEFAULT_MODEL = "deepseek-chat";
|
|
292
|
+
var Deepseek = class extends openai_compatible_default {
|
|
293
|
+
constructor(config) {
|
|
294
|
+
super({
|
|
295
|
+
...config,
|
|
296
|
+
model: config.model ?? DEFAULT_MODEL,
|
|
297
|
+
baseURL: config.baseURL ?? DEFAULT_BASE_URL2,
|
|
298
|
+
name: "DeepSeek"
|
|
344
299
|
});
|
|
345
300
|
}
|
|
346
301
|
};
|