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