@cloudbase/ai 3.6.4-alpha.1 → 3.6.5-alpha.2
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/.eslintignore +2 -0
- package/.eslintrc.js +16 -0
- package/package.json +4 -8
- package/src/AI.ts +191 -0
- package/src/bot/index.ts +278 -0
- package/src/bot/types.ts +165 -0
- package/src/errors.ts +23 -0
- package/src/eventsource_parser/index.ts +2 -0
- package/src/eventsource_parser/parse.ts +169 -0
- package/src/eventsource_parser/stream.ts +35 -0
- package/src/eventsource_parser/types.ts +90 -0
- package/src/index.ts +165 -0
- package/src/models/Ark/index.ts +94 -0
- package/src/models/Ark/type.ts +82 -0
- package/src/models/DashScope/index.ts +97 -0
- package/src/models/DashScope/type.ts +76 -0
- package/src/models/DeepSeek/index.ts +80 -0
- package/src/models/Default/index.ts +70 -0
- package/src/models/HunYuan/index.ts +94 -0
- package/src/models/HunYuan/type.ts +85 -0
- package/src/models/HunYuan/util.ts +60 -0
- package/src/models/HunYuanBeta/index.ts +83 -0
- package/src/models/HunYuanExp/index.ts +80 -0
- package/src/models/HunYuanOpen/index.ts +83 -0
- package/src/models/Moonshot/index.ts +95 -0
- package/src/models/Moonshot/type.ts +82 -0
- package/src/models/Yi/index.ts +100 -0
- package/src/models/Yi/type.ts +77 -0
- package/src/models/ZhiPu/index.ts +104 -0
- package/src/models/ZhiPu/type.ts +71 -0
- package/src/models/image-model.ts +100 -0
- package/src/models/index.ts +41 -0
- package/src/models/model.ts +425 -0
- package/src/type.ts +362 -0
- package/src/utils.ts +198 -0
- package/tsconfig.esm.json +16 -0
- package/tsconfig.json +16 -0
- package/webpack/web.prod.js +100 -0
- package/webpack/webpack.miniprogram.js +24 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModelReq,
|
|
3
|
+
// ImageEditInput,
|
|
4
|
+
HunyuanGenerateImageInput,
|
|
5
|
+
HunyuanGenerateImageOutput,
|
|
6
|
+
HunyuanARGenerateImageInput,
|
|
7
|
+
HunyuanARGenerateImageOutput,
|
|
8
|
+
// HunyuanEditImageInput,
|
|
9
|
+
// HunyuanEditImageOutput,
|
|
10
|
+
} from '../type'
|
|
11
|
+
import { InvalidParamsError } from '../errors'
|
|
12
|
+
|
|
13
|
+
interface HunyuanImageGenerateFn {
|
|
14
|
+
(input: HunyuanARGenerateImageInput): Promise<HunyuanARGenerateImageOutput>
|
|
15
|
+
(input: HunyuanGenerateImageInput): Promise<HunyuanGenerateImageOutput>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type GenerateImageFn<TProvider> = TProvider extends 'hunyuan-image'
|
|
19
|
+
? HunyuanImageGenerateFn
|
|
20
|
+
: (input: unknown) => Promise<unknown>
|
|
21
|
+
|
|
22
|
+
export class DefaultImageModel<TProvider extends string> {
|
|
23
|
+
public defaultGenerateImageSubUrl = 'images/ar/generations'
|
|
24
|
+
|
|
25
|
+
public generateImageSubUrlConfig: Record<string, Array<[RegExp, string]>> = {
|
|
26
|
+
'hunyuan-image': [
|
|
27
|
+
[/^hunyuan-image(-v[12](\..*)?)?$/, 'images/generations'],
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 生成图片 */
|
|
32
|
+
public generateImage: GenerateImageFn<TProvider>
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
private req: ModelReq,
|
|
36
|
+
public baseUrl: string,
|
|
37
|
+
public provider: TProvider,
|
|
38
|
+
) {
|
|
39
|
+
this.generateImage = this.generateImageImpl.bind(this) as GenerateImageFn<TProvider>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** generateImage 的实际实现 */
|
|
43
|
+
private async generateImageImpl(input: HunyuanARGenerateImageInput | HunyuanGenerateImageInput,): Promise<HunyuanARGenerateImageOutput | HunyuanGenerateImageOutput> {
|
|
44
|
+
// 当模型为 HY-Image-v3.0-I2I-ToB-v1.0.1 且 provider 为空或为 hunyuan-image 时校验参数
|
|
45
|
+
if (
|
|
46
|
+
typeof input === 'object' && input && 'model' in input && input.model === 'HY-Image-v3.0-I2I-ToB-v1.0.1' && (this.provider === '' || this.provider === 'hunyuan-image')
|
|
47
|
+
) {
|
|
48
|
+
const typedInput = input
|
|
49
|
+
const imageUrls = typedInput.image_urls as string[] | undefined
|
|
50
|
+
const images = typedInput.images as string[] | undefined
|
|
51
|
+
|
|
52
|
+
if (imageUrls && imageUrls.length > 1) {
|
|
53
|
+
throw new InvalidParamsError(
|
|
54
|
+
'当模型为 HY-Image-v3.0-I2I-ToB-v1.0.1 时,image_urls 最多只能有 1 个元素',
|
|
55
|
+
'image_urls',
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
if (images && images.length > 1) {
|
|
59
|
+
throw new InvalidParamsError(
|
|
60
|
+
'当模型为 HY-Image-v3.0-I2I-ToB-v1.0.1 时,images 最多只能有 1 个元素',
|
|
61
|
+
'images',
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let subUrl = this.defaultGenerateImageSubUrl
|
|
67
|
+
|
|
68
|
+
if (typeof input === 'object' && input && 'model' in input && typeof input.model === 'string') {
|
|
69
|
+
const newSubUrl = this.getGenerateImageSubUrl(this.provider, input.model)
|
|
70
|
+
if (newSubUrl) subUrl = newSubUrl
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const res = (await this.req({
|
|
74
|
+
url: this.getGenerateUrl(subUrl),
|
|
75
|
+
data: input,
|
|
76
|
+
stream: false,
|
|
77
|
+
})) as HunyuanARGenerateImageOutput | HunyuanGenerateImageOutput
|
|
78
|
+
|
|
79
|
+
return res
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private getGenerateUrl(subUrl: string) {
|
|
83
|
+
return `${this.baseUrl}/${this.provider}/${subUrl}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 根据 provider 与 model 解析生成图片的子路径。
|
|
88
|
+
* 按配置顺序用 RegExp 匹配,命中即返回对应 subUrl。
|
|
89
|
+
*/
|
|
90
|
+
private getGenerateImageSubUrl(provider: string, model: string): string | undefined {
|
|
91
|
+
const rules = this.generateImageSubUrlConfig[provider]
|
|
92
|
+
if (!rules) return undefined
|
|
93
|
+
|
|
94
|
+
for (const [pattern, subUrl] of rules) {
|
|
95
|
+
if (pattern.test(model)) return subUrl
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return undefined
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ZhiPuSimpleModel } from './ZhiPu/index'
|
|
2
|
+
import { HunYuanBetaSimpleModel } from './HunYuanBeta/index'
|
|
3
|
+
import { HunYuanSimpleModel } from './HunYuan/index'
|
|
4
|
+
import { ArkSimpleModel } from './Ark/index'
|
|
5
|
+
import { DSSimpleModel } from './DashScope/index'
|
|
6
|
+
import { YiSimpleModel } from './Yi/index'
|
|
7
|
+
import { MoonshotSimpleModel } from './Moonshot/index'
|
|
8
|
+
import { HunYuanExpSimpleModel } from './HunYuanExp/index'
|
|
9
|
+
import { HunYuanOpenSimpleModel } from './HunYuanOpen/index'
|
|
10
|
+
import { DeepSeekSimpleModel } from './DeepSeek'
|
|
11
|
+
import { DefaultSimpleModel } from './Default'
|
|
12
|
+
import { DefaultImageModel } from './image-model'
|
|
13
|
+
|
|
14
|
+
export const MODELS = {
|
|
15
|
+
hunyuan: HunYuanSimpleModel,
|
|
16
|
+
'hunyuan-beta': HunYuanBetaSimpleModel,
|
|
17
|
+
ark: ArkSimpleModel,
|
|
18
|
+
dashscope: DSSimpleModel,
|
|
19
|
+
'01-ai': YiSimpleModel,
|
|
20
|
+
moonshot: MoonshotSimpleModel,
|
|
21
|
+
zhipu: ZhiPuSimpleModel,
|
|
22
|
+
'hunyuan-exp': HunYuanExpSimpleModel,
|
|
23
|
+
'hunyuan-open': HunYuanOpenSimpleModel,
|
|
24
|
+
deepseek: DeepSeekSimpleModel,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
ZhiPuSimpleModel,
|
|
29
|
+
HunYuanBetaSimpleModel,
|
|
30
|
+
HunYuanSimpleModel,
|
|
31
|
+
ArkSimpleModel,
|
|
32
|
+
DSSimpleModel,
|
|
33
|
+
YiSimpleModel,
|
|
34
|
+
MoonshotSimpleModel,
|
|
35
|
+
HunYuanExpSimpleModel,
|
|
36
|
+
HunYuanOpenSimpleModel,
|
|
37
|
+
DefaultSimpleModel,
|
|
38
|
+
DefaultImageModel,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { ReactModel, toolMap } from './model'
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
+
/* eslint-disable eqeqeq */
|
|
3
|
+
import {
|
|
4
|
+
createAsyncIterable,
|
|
5
|
+
TransformStream,
|
|
6
|
+
createPromise,
|
|
7
|
+
isToolCallAssistantMessage,
|
|
8
|
+
functionToolToModelTool,
|
|
9
|
+
} from '../utils'
|
|
10
|
+
import {
|
|
11
|
+
BaseChatModelInput,
|
|
12
|
+
BaseDoStreamOutputChunk,
|
|
13
|
+
DoStreamOutput,
|
|
14
|
+
DoGenerateOutput,
|
|
15
|
+
SimpleChatModel,
|
|
16
|
+
ToolCall,
|
|
17
|
+
ChatModelMessage,
|
|
18
|
+
AsyncIterableReadableStream,
|
|
19
|
+
Usage,
|
|
20
|
+
ToolCallAssistantMessage,
|
|
21
|
+
ModelTool,
|
|
22
|
+
FunctionTool,
|
|
23
|
+
ReqOptions,
|
|
24
|
+
} from '../type'
|
|
25
|
+
|
|
26
|
+
type ReactModelInput = ReactProps &
|
|
27
|
+
Omit<BaseChatModelInput, 'tools'> & {
|
|
28
|
+
tools?: Array<ModelTool | FunctionTool>
|
|
29
|
+
topP?: number
|
|
30
|
+
toolChoice?: 'none' | 'auto' | 'custom'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface IOnStepFinish {
|
|
34
|
+
messages: Array<ChatModelMessage>
|
|
35
|
+
text?: string
|
|
36
|
+
toolCall?: ToolCall
|
|
37
|
+
toolResult?: unknown
|
|
38
|
+
finishReason?: string
|
|
39
|
+
stepUsage?: Usage
|
|
40
|
+
totalUsage?: Usage
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ReactProps {
|
|
44
|
+
maxSteps?: number
|
|
45
|
+
onStepFinish?: (prop: IOnStepFinish) => unknown
|
|
46
|
+
abortSignal?: AbortSignal // TODO: 实现 abortSignal
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function processInput(obj: ReactModelInput): [ReactProps, BaseChatModelInput] {
|
|
50
|
+
const { onStepFinish, abortSignal, maxSteps, topP, toolChoice, ...b } = obj
|
|
51
|
+
|
|
52
|
+
if (maxSteps != null && maxSteps < 1) {
|
|
53
|
+
throw new Error('`maxSteps` muse be greater than 0.')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
{ onStepFinish, abortSignal, maxSteps },
|
|
58
|
+
{
|
|
59
|
+
...b,
|
|
60
|
+
tools: b.tools?.map((tool) => {
|
|
61
|
+
if ('fn' in tool) {
|
|
62
|
+
return functionToolToModelTool(tool)
|
|
63
|
+
}
|
|
64
|
+
return tool
|
|
65
|
+
}),
|
|
66
|
+
top_p: topP != null ? topP : b.top_p,
|
|
67
|
+
tool_choice: toolChoice != null ? toolChoice : b.tool_choice,
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class ReactModel {
|
|
73
|
+
constructor(private model: SimpleChatModel) {}
|
|
74
|
+
|
|
75
|
+
public async generateText(_input: ReactModelInput, options?: ReqOptions): Promise<{
|
|
76
|
+
text: string
|
|
77
|
+
messages: Array<ChatModelMessage>
|
|
78
|
+
usage: Usage
|
|
79
|
+
rawResponses: Array<unknown>
|
|
80
|
+
error?: any
|
|
81
|
+
}> {
|
|
82
|
+
const rawResponses = []
|
|
83
|
+
const totalUsage: Usage = { completion_tokens: 0, prompt_tokens: 0, total_tokens: 0 }
|
|
84
|
+
|
|
85
|
+
const [{ onStepFinish, maxSteps = 10 }, input] = processInput(_input)
|
|
86
|
+
|
|
87
|
+
const doGenerate = () => this.model.doGenerate(input, options) // 后续代码会直接对 input.messages 原地修改,这里一直用同一个对象就行
|
|
88
|
+
let currentRes = await doGenerate()
|
|
89
|
+
let currentStep = 1
|
|
90
|
+
currentRes.rawResponse && rawResponses.push(currentRes.rawResponse)
|
|
91
|
+
|
|
92
|
+
let toolCall: ToolCall | null = null
|
|
93
|
+
|
|
94
|
+
// TODO: 一次对话有多个 tool call? 目前没有这种现象,暂时不处理
|
|
95
|
+
while (currentStep < maxSteps && (toolCall = getToolCallFromGenerate(currentRes)) != null) {
|
|
96
|
+
const stepUsage = createSolidUsage(currentRes.usage)
|
|
97
|
+
addToUsage(totalUsage, stepUsage)
|
|
98
|
+
|
|
99
|
+
// 当判断需要工具调用时
|
|
100
|
+
try {
|
|
101
|
+
const toolCallResult = await callTool(toolCall) // 调用
|
|
102
|
+
|
|
103
|
+
const choice = currentRes.choices[0] // getToolCallFromGenerate 保证了 choice 肯定存在
|
|
104
|
+
|
|
105
|
+
await onStepFinish?.({
|
|
106
|
+
finishReason: choice.finish_reason,
|
|
107
|
+
messages: input.messages.slice(),
|
|
108
|
+
text: choice.message.content,
|
|
109
|
+
toolCall,
|
|
110
|
+
toolResult: toolCallResult,
|
|
111
|
+
stepUsage,
|
|
112
|
+
totalUsage: Object.assign({}, totalUsage),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
pushNewMessages(input.messages, choice.message as ToolCallAssistantMessage, toolCallResult) // 用调用结果修改最新的消息
|
|
116
|
+
|
|
117
|
+
currentRes = await doGenerate() // 循环对话
|
|
118
|
+
currentRes.rawResponse && rawResponses.push(currentRes.rawResponse)
|
|
119
|
+
currentStep += 1
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return {
|
|
122
|
+
text: '',
|
|
123
|
+
messages: input.messages,
|
|
124
|
+
usage: totalUsage,
|
|
125
|
+
error: e,
|
|
126
|
+
rawResponses,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const lastChoice = currentRes?.choices?.[0]
|
|
132
|
+
const lastMessage = lastChoice?.message
|
|
133
|
+
|
|
134
|
+
const text = lastMessage?.content ?? ''
|
|
135
|
+
const messages = lastMessage ? [...input.messages, lastMessage] : input.messages
|
|
136
|
+
|
|
137
|
+
const stepUsage = createSolidUsage(currentRes.usage)
|
|
138
|
+
addToUsage(totalUsage, stepUsage)
|
|
139
|
+
|
|
140
|
+
await onStepFinish?.({
|
|
141
|
+
finishReason: lastChoice.finish_reason,
|
|
142
|
+
messages: messages.slice(),
|
|
143
|
+
text,
|
|
144
|
+
toolCall: getToolCallFromGenerate(currentRes),
|
|
145
|
+
toolResult: null,
|
|
146
|
+
stepUsage,
|
|
147
|
+
totalUsage: Object.assign({}, totalUsage),
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
text,
|
|
152
|
+
messages,
|
|
153
|
+
usage: totalUsage,
|
|
154
|
+
rawResponses,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public async streamText(_input: ReactModelInput, options?: ReqOptions): Promise<{
|
|
159
|
+
dataStream: DoStreamOutput
|
|
160
|
+
textStream: AsyncIterableReadableStream<string>
|
|
161
|
+
messages: Promise<Array<ChatModelMessage>>
|
|
162
|
+
usage: Promise<Usage>
|
|
163
|
+
error?: any
|
|
164
|
+
}> {
|
|
165
|
+
const totalUsage: Usage = { completion_tokens: 0, prompt_tokens: 0, total_tokens: 0 }
|
|
166
|
+
|
|
167
|
+
const [{ onStepFinish, maxSteps = 10 }, input] = processInput(_input)
|
|
168
|
+
const doStream = () => this.model.doStream(input, options) // 后续代码会直接对 input.messages 原地修改,这里一直用同一个对象就行
|
|
169
|
+
let currentRes = await doStream()
|
|
170
|
+
const currentStep = 1
|
|
171
|
+
let readResult: { message: ToolCallAssistantMessage; usage: Usage } | null = null
|
|
172
|
+
|
|
173
|
+
const readCurrentStream = () => {
|
|
174
|
+
const [oldStream, newStream] = currentRes.tee()
|
|
175
|
+
currentRes = createAsyncIterable(oldStream)
|
|
176
|
+
return readFunctionCallStream(newStream)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// TODO: 一次对话有多个 tool call? 目前没有这种现象,暂时不处理
|
|
180
|
+
// 这里和 generateText 不太一样,除了解析出 toolCall 外,还需要从流中构造出其他完整的信息
|
|
181
|
+
while (currentStep < maxSteps && (readResult = await readCurrentStream()) != null) {
|
|
182
|
+
const { message: assistantMessage, usage: stepUsage } = readResult
|
|
183
|
+
addToUsage(totalUsage, stepUsage)
|
|
184
|
+
|
|
185
|
+
// 当判断需要工具调用时
|
|
186
|
+
const toolCall = assistantMessage.tool_calls?.[0] // 这个 toolCall 一定存在
|
|
187
|
+
try {
|
|
188
|
+
const toolCallResult = await callTool(toolCall) // 调用
|
|
189
|
+
|
|
190
|
+
await onStepFinish?.({
|
|
191
|
+
finishReason: 'tool_calls',
|
|
192
|
+
messages: input.messages.slice(),
|
|
193
|
+
text: assistantMessage.content,
|
|
194
|
+
toolCall,
|
|
195
|
+
toolResult: toolCallResult,
|
|
196
|
+
stepUsage,
|
|
197
|
+
totalUsage: Object.assign({}, totalUsage),
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
pushNewMessages(input.messages, assistantMessage, toolCallResult) // 用调用结果修改最新的消息
|
|
201
|
+
currentRes = await doStream() // 循环对话
|
|
202
|
+
} catch (e) {
|
|
203
|
+
const [s1, s2] = currentRes.tee()
|
|
204
|
+
return {
|
|
205
|
+
messages: Promise.resolve(input.messages),
|
|
206
|
+
dataStream: createAsyncIterable(s1),
|
|
207
|
+
textStream: createAsyncIterable(s2.pipeThrough(new TransformStream({
|
|
208
|
+
transform(chunk, controller) {
|
|
209
|
+
const str = chunk?.choices?.[0]?.delta?.content
|
|
210
|
+
if (typeof str === 'string') controller.enqueue(str)
|
|
211
|
+
},
|
|
212
|
+
}),),),
|
|
213
|
+
usage: Promise.resolve(totalUsage),
|
|
214
|
+
error: e,
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 最后返回时,有几种情况:
|
|
221
|
+
* 1. 没超 maxStep 无工具调用
|
|
222
|
+
* 2. 超了 maxStep 有工具调用
|
|
223
|
+
* 3. 超了 maxStep 无工具调用
|
|
224
|
+
* never. 没超 maxStep,有工具调用,这时候会进到上面的 while 循环 block 中处理
|
|
225
|
+
*
|
|
226
|
+
* 其中 1. 3. 可以合并,没有工具调用就应该直接返回,无论 maxStep
|
|
227
|
+
*
|
|
228
|
+
* 所以合并为:
|
|
229
|
+
* 1. 无工具调用
|
|
230
|
+
* 2. 有工具调用,但是超过 maxStep
|
|
231
|
+
*
|
|
232
|
+
* 这两种情况都没进到 while 循环 block 中处理
|
|
233
|
+
* 我们需要 a. 塞 message b. 算 Usage c. 调用 onStepFinish
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
readResult = await readCurrentStream()
|
|
237
|
+
|
|
238
|
+
if (readResult) {
|
|
239
|
+
// 情况 2 有工具调用,但是超过 maxStep
|
|
240
|
+
const { message, usage } = readResult
|
|
241
|
+
addToUsage(totalUsage, usage)
|
|
242
|
+
|
|
243
|
+
const messages = [...input.messages, message]
|
|
244
|
+
|
|
245
|
+
onStepFinish({
|
|
246
|
+
messages: messages.slice(),
|
|
247
|
+
finishReason: 'tool_call',
|
|
248
|
+
stepUsage: usage,
|
|
249
|
+
text: message.content,
|
|
250
|
+
toolCall: message.tool_calls[0],
|
|
251
|
+
totalUsage: Object.assign({}, totalUsage),
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const [s1, s2] = currentRes.tee()
|
|
255
|
+
return {
|
|
256
|
+
messages: Promise.resolve([...input.messages, message]),
|
|
257
|
+
dataStream: createAsyncIterable(s1),
|
|
258
|
+
textStream: createAsyncIterable(s2.pipeThrough(new TransformStream({
|
|
259
|
+
transform(chunk, controller) {
|
|
260
|
+
const str = chunk?.choices?.[0]?.delta?.content
|
|
261
|
+
if (typeof str === 'string') controller.enqueue(str)
|
|
262
|
+
},
|
|
263
|
+
}),),),
|
|
264
|
+
usage: Promise.resolve(totalUsage),
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// 情况 1 无工具调用
|
|
268
|
+
const messagePromise = createPromise<Array<ChatModelMessage>>()
|
|
269
|
+
const usagePromise = createPromise<Usage>()
|
|
270
|
+
|
|
271
|
+
const message: ChatModelMessage = {
|
|
272
|
+
role: 'assistant',
|
|
273
|
+
content: '',
|
|
274
|
+
}
|
|
275
|
+
let finishReason = ''
|
|
276
|
+
const stepUsage: Usage = { completion_tokens: 0, prompt_tokens: 0, total_tokens: 0 }
|
|
277
|
+
|
|
278
|
+
const originStream = currentRes.pipeThrough(new TransformStream({
|
|
279
|
+
transform(chunk, controller) {
|
|
280
|
+
// 不改变 chunk 内容,只是拦截下内容拼最后的结果
|
|
281
|
+
const content = chunk?.choices?.[0]?.delta?.content
|
|
282
|
+
if (typeof content === 'string') {
|
|
283
|
+
message.content += content
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const reason = chunk?.choices?.[0]?.finish_reason
|
|
287
|
+
if (reason) finishReason = reason
|
|
288
|
+
|
|
289
|
+
// TODO: 不同大模型的 stream usage 格式不一样,后续可能要调整.
|
|
290
|
+
// hunyuan 每个 chunk 都会有 usage,逐步增加,以最后一个的为准;
|
|
291
|
+
// zhipu 最后 chunk 会有 usage;
|
|
292
|
+
if (chunk?.usage?.completion_tokens) stepUsage.completion_tokens = chunk.usage.completion_tokens
|
|
293
|
+
if (chunk?.usage?.prompt_tokens) stepUsage.prompt_tokens = chunk.usage.prompt_tokens
|
|
294
|
+
if (chunk?.usage?.total_tokens) stepUsage.total_tokens = chunk.usage.total_tokens
|
|
295
|
+
|
|
296
|
+
controller.enqueue(chunk)
|
|
297
|
+
},
|
|
298
|
+
flush() {
|
|
299
|
+
messagePromise.res([...input.messages, message])
|
|
300
|
+
addToUsage(totalUsage, stepUsage)
|
|
301
|
+
usagePromise.res(Object.assign({}, totalUsage))
|
|
302
|
+
onStepFinish?.({
|
|
303
|
+
messages: [...input.messages, message],
|
|
304
|
+
finishReason,
|
|
305
|
+
text: message.content,
|
|
306
|
+
stepUsage,
|
|
307
|
+
totalUsage: Object.assign({}, totalUsage),
|
|
308
|
+
})
|
|
309
|
+
},
|
|
310
|
+
}),)
|
|
311
|
+
|
|
312
|
+
const [s1, s2] = originStream.tee()
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
messages: messagePromise.promise,
|
|
316
|
+
dataStream: createAsyncIterable(s1),
|
|
317
|
+
textStream: createAsyncIterable(s2.pipeThrough(new TransformStream({
|
|
318
|
+
transform(chunk, controller) {
|
|
319
|
+
const content = chunk?.choices?.[0]?.delta?.content
|
|
320
|
+
if (typeof content === 'string') {
|
|
321
|
+
controller.enqueue(content)
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
}),),),
|
|
325
|
+
usage: usagePromise.promise,
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function getToolCallFromGenerate(output: DoGenerateOutput) {
|
|
331
|
+
const choice = output?.choices?.[0]
|
|
332
|
+
|
|
333
|
+
if (!choice) return null
|
|
334
|
+
|
|
335
|
+
const { finish_reason, message } = choice
|
|
336
|
+
|
|
337
|
+
if (finish_reason !== 'tool_calls') return null
|
|
338
|
+
if (!message) return null
|
|
339
|
+
if (!isToolCallAssistantMessage(message)) return null
|
|
340
|
+
|
|
341
|
+
return message.tool_calls[0]
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function pushNewMessages(
|
|
345
|
+
messages: Array<ChatModelMessage>,
|
|
346
|
+
assistantMessage: ToolCallAssistantMessage,
|
|
347
|
+
toolCallResult: unknown,
|
|
348
|
+
) {
|
|
349
|
+
messages.push(assistantMessage, {
|
|
350
|
+
role: 'tool',
|
|
351
|
+
tool_call_id: assistantMessage.tool_calls[0].id,
|
|
352
|
+
content: JSON.stringify(toolCallResult),
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function readFunctionCallStream(stream: ReadableStream<BaseDoStreamOutputChunk>,): Promise<{ message: ToolCallAssistantMessage; usage: Usage } | null> {
|
|
357
|
+
const stepUsage: Usage = { completion_tokens: 0, prompt_tokens: 0, total_tokens: 0 }
|
|
358
|
+
const aStream = createAsyncIterable(stream)
|
|
359
|
+
|
|
360
|
+
const retToolCall: ToolCall = {
|
|
361
|
+
id: '',
|
|
362
|
+
function: {
|
|
363
|
+
name: '',
|
|
364
|
+
arguments: '',
|
|
365
|
+
},
|
|
366
|
+
type: '',
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const retMessage: ToolCallAssistantMessage = {
|
|
370
|
+
role: 'assistant',
|
|
371
|
+
content: '',
|
|
372
|
+
tool_calls: [retToolCall],
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
for await (const chunk of aStream) {
|
|
376
|
+
const choice = chunk?.choices[0]
|
|
377
|
+
if (!choice) return null
|
|
378
|
+
|
|
379
|
+
const { finish_reason, delta } = choice
|
|
380
|
+
|
|
381
|
+
if (finish_reason !== 'tool_calls') return null
|
|
382
|
+
if (!delta) continue
|
|
383
|
+
|
|
384
|
+
if (delta.content) retMessage.content += delta.content
|
|
385
|
+
|
|
386
|
+
if (!('tool_calls' in delta)) continue
|
|
387
|
+
const toolCall = delta?.tool_calls?.[0]
|
|
388
|
+
if (toolCall?.id) retToolCall.id = toolCall.id
|
|
389
|
+
if (toolCall?.type) retToolCall.type = toolCall.type
|
|
390
|
+
if (toolCall?.function?.name) retToolCall.function.name = toolCall.function.name
|
|
391
|
+
if (toolCall?.function?.arguments) retToolCall.function.arguments += toolCall.function.arguments
|
|
392
|
+
|
|
393
|
+
// TODO: 不同大模型的 stream usage 格式不一样,后续可能要调整.
|
|
394
|
+
// hunyuan 每个 chunk 都会有 usage,逐步增加,以最后一个的为准;
|
|
395
|
+
// zhipu 最后 chunk 会有 usage;
|
|
396
|
+
if (chunk?.usage?.completion_tokens) stepUsage.completion_tokens = chunk.usage.completion_tokens
|
|
397
|
+
if (chunk?.usage?.prompt_tokens) stepUsage.prompt_tokens = chunk.usage.prompt_tokens
|
|
398
|
+
if (chunk?.usage?.total_tokens) stepUsage.total_tokens = chunk.usage.total_tokens
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
message: retMessage,
|
|
403
|
+
usage: stepUsage,
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export const toolMap = new Map<string, CallableFunction>()
|
|
408
|
+
|
|
409
|
+
function callTool(toolCall: ToolCall) {
|
|
410
|
+
return toolMap.get(toolCall.function.name)(JSON.parse(toolCall.function.arguments))
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function createSolidUsage(usage?: Partial<Usage>): Usage {
|
|
414
|
+
return {
|
|
415
|
+
completion_tokens: usage?.completion_tokens ?? 0,
|
|
416
|
+
prompt_tokens: usage?.prompt_tokens ?? 0,
|
|
417
|
+
total_tokens: usage?.total_tokens ?? 0,
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function addToUsage(targetUsage: Usage, sourceUsage: Usage) {
|
|
422
|
+
targetUsage.completion_tokens += sourceUsage.completion_tokens
|
|
423
|
+
targetUsage.prompt_tokens += sourceUsage.prompt_tokens
|
|
424
|
+
targetUsage.total_tokens += sourceUsage.total_tokens
|
|
425
|
+
}
|