@baishuyun/coze-provider 0.0.17 → 0.1.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/.turbo/turbo-build.log +23 -22
- package/CHANGELOG.md +13 -0
- package/dist/index.js +121 -119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +121 -119
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/coze-language-model.ts +67 -23
- package/src/stream-trasnformer/fields-json-transform-stream.ts +11 -19
- package/src/utils/accumulate-sse-response.ts +83 -0
|
@@ -14,6 +14,7 @@ import { CozeChatConfig, IGenerateResult } from './types';
|
|
|
14
14
|
import { ServerSentEventTransformStream } from 'parse-sse';
|
|
15
15
|
import { TextDecoderStream } from 'stream/web';
|
|
16
16
|
import { convertPromptToMessages } from './utils/prompt-to-model-msg';
|
|
17
|
+
import { accumulateSSEResponse } from './utils/accumulate-sse-response';
|
|
17
18
|
import { createErrorStream } from './utils/create-error-stream';
|
|
18
19
|
import { LLMChunkInjectorTransformStream } from './stream-trasnformer/llm-chunk-injector-transform-stream';
|
|
19
20
|
import MCPChunkFilterTransformStream from './stream-trasnformer/mcp-chunk-filter-transform-stream';
|
|
@@ -39,26 +40,74 @@ export class CozeLanguageModel implements LanguageModelV2 {
|
|
|
39
40
|
this.config = config;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
// @not-support
|
|
43
|
-
// coze 目前不支持非流式接口,为了兼容接口规范,仍然实现了 doGenerate 方法
|
|
44
43
|
async doGenerate(options: LanguageModelV2CallOptions): Promise<IGenerateResult> {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
const messages = convertPromptToMessages(options.prompt);
|
|
45
|
+
const warnings: LanguageModelV2CallWarning[] = [];
|
|
46
|
+
|
|
47
|
+
const url = this.config.url({ path: '/chat' });
|
|
48
|
+
|
|
49
|
+
const user_id = options.headers ? options.headers['x-user-id'] : '';
|
|
50
|
+
const user_var = options.headers ? options.headers['x-user-var'] : '';
|
|
51
|
+
|
|
52
|
+
let customVar;
|
|
53
|
+
if (user_var) {
|
|
54
|
+
try {
|
|
55
|
+
customVar = JSON.parse(user_var);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.warn('x-user-var parse error', e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
48
60
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
const body = {
|
|
62
|
+
bot_id: this.config.botId,
|
|
63
|
+
auto_save_history: false,
|
|
64
|
+
user_id: user_id || '7543191937730215936',
|
|
65
|
+
stream: true,
|
|
66
|
+
additional_messages: messages,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (customVar) {
|
|
70
|
+
(body as any).custom_variables = customVar;
|
|
52
71
|
}
|
|
53
72
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
options,
|
|
57
|
-
this.config
|
|
58
|
-
);
|
|
73
|
+
const headers = this.config.headers();
|
|
74
|
+
const abortSignal = options.abortSignal;
|
|
59
75
|
|
|
60
|
-
const
|
|
61
|
-
|
|
76
|
+
const response = await fetch(url, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: headers,
|
|
79
|
+
credentials: 'include',
|
|
80
|
+
body: JSON.stringify(body),
|
|
81
|
+
signal: abortSignal,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!response.ok || !response.body) {
|
|
85
|
+
const rawError = await response.text();
|
|
86
|
+
let errorDetail;
|
|
87
|
+
try {
|
|
88
|
+
errorDetail = JSON.parse(rawError);
|
|
89
|
+
} catch {
|
|
90
|
+
errorDetail = rawError;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const errorMessage =
|
|
94
|
+
typeof errorDetail === 'object' && errorDetail?.msg
|
|
95
|
+
? errorDetail.msg
|
|
96
|
+
: `Coze API error: ${response.status} ${response.statusText}`;
|
|
97
|
+
|
|
98
|
+
return BuildWarningGenerateResult(errorMessage);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { content, finishReason, usage } = await accumulateSSEResponse(response.body);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
content,
|
|
105
|
+
finishReason,
|
|
106
|
+
usage,
|
|
107
|
+
request: { body },
|
|
108
|
+
response: { body: null },
|
|
109
|
+
warnings,
|
|
110
|
+
};
|
|
62
111
|
}
|
|
63
112
|
|
|
64
113
|
isVisibleMsgPart(evtData: any) {
|
|
@@ -81,7 +130,7 @@ export class CozeLanguageModel implements LanguageModelV2 {
|
|
|
81
130
|
path: '/chat',
|
|
82
131
|
});
|
|
83
132
|
|
|
84
|
-
const user_id =
|
|
133
|
+
const user_id = options.headers ? options.headers['x-user-id'] : '';
|
|
85
134
|
const user_var = options.headers ? options.headers['x-user-var'] : '';
|
|
86
135
|
// const user_token = options.headers ? options.headers['x-user-token'] : '';
|
|
87
136
|
|
|
@@ -97,22 +146,17 @@ export class CozeLanguageModel implements LanguageModelV2 {
|
|
|
97
146
|
const body = {
|
|
98
147
|
bot_id: this.config.botId,
|
|
99
148
|
auto_save_history: false,
|
|
100
|
-
user_id: '7543191937730215936',
|
|
149
|
+
user_id: user_id || '7543191937730215936',
|
|
101
150
|
stream: true,
|
|
102
151
|
additional_messages: messages,
|
|
103
152
|
};
|
|
104
153
|
|
|
105
154
|
if (customVar) {
|
|
106
|
-
(body as any).
|
|
155
|
+
(body as any).custom_variables = customVar;
|
|
107
156
|
}
|
|
108
157
|
|
|
109
158
|
const headers = this.config.headers();
|
|
110
159
|
|
|
111
|
-
// console.log('Request headers for Coze API:', JSON.stringify(headers));
|
|
112
|
-
// console.log('Request body for Coze API:', JSON.stringify(body));
|
|
113
|
-
// console.log('Request URL for Coze API:', url);
|
|
114
|
-
|
|
115
|
-
// Support abort signal for proper cancellation when clients disconnect
|
|
116
160
|
const abortSignal = options.abortSignal;
|
|
117
161
|
|
|
118
162
|
const response = await fetch(url, {
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { LanguageModelV2StreamPart } from '@ai-sdk/provider';
|
|
2
2
|
import { JSONParser } from '@streamparser/json';
|
|
3
|
-
import { createTextInfoEnqueuer } from '../utils/enqueue-text-info';
|
|
3
|
+
import { createTextInfoEnqueuer, TextInfoEnqueuer } from '../utils/enqueue-text-info';
|
|
4
4
|
import { rebuildFieldsJsonStructure } from '../utils/fields-json-rebuild';
|
|
5
5
|
|
|
6
6
|
export const createFieldsJsonTransformStream = (enableJsonParser: boolean) => {
|
|
7
7
|
let parser: JSONParser;
|
|
8
|
+
let enqueueTextDelta: TextInfoEnqueuer;
|
|
8
9
|
let id: string;
|
|
9
10
|
let isFirstChunk = true;
|
|
11
|
+
// 当 LLM 返回的不是 JSON 而是普通文本时,切换到直通模式
|
|
12
|
+
let passthroughMode = false;
|
|
10
13
|
|
|
11
14
|
const chunkGuard = (chunk: LanguageModelV2StreamPart): boolean => {
|
|
12
|
-
if (!enableJsonParser) {
|
|
15
|
+
if (!enableJsonParser || passthroughMode) {
|
|
13
16
|
return false;
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -33,12 +36,10 @@ export const createFieldsJsonTransformStream = (enableJsonParser: boolean) => {
|
|
|
33
36
|
chunk: LanguageModelV2StreamPart,
|
|
34
37
|
controller: TransformStreamDefaultController<LanguageModelV2StreamPart>
|
|
35
38
|
) => {
|
|
36
|
-
if (!parser) {
|
|
39
|
+
if (!parser && !passthroughMode) {
|
|
37
40
|
return;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
// console.log("chunk arrived", chunk);
|
|
41
|
-
|
|
42
43
|
try {
|
|
43
44
|
if (chunkGuard(chunk)) {
|
|
44
45
|
if ('id' in chunk && 'delta' in chunk) {
|
|
@@ -66,13 +67,10 @@ export const createFieldsJsonTransformStream = (enableJsonParser: boolean) => {
|
|
|
66
67
|
paths: ['$.*'],
|
|
67
68
|
});
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
enqueueTextDelta = enableJsonParser
|
|
70
71
|
? createTextInfoEnqueuer(controller)
|
|
71
72
|
: (content: string) => {};
|
|
72
73
|
|
|
73
|
-
// manualChunkForfieldsBeginTips(controller);
|
|
74
|
-
// enqueueTextDelta("开始构建字段");
|
|
75
|
-
|
|
76
74
|
parser.onValue = (parsedInfo: any) => {
|
|
77
75
|
const value = parsedInfo.value;
|
|
78
76
|
|
|
@@ -90,17 +88,11 @@ export const createFieldsJsonTransformStream = (enableJsonParser: boolean) => {
|
|
|
90
88
|
);
|
|
91
89
|
};
|
|
92
90
|
|
|
93
|
-
let errorLogged = false;
|
|
94
91
|
parser.onError = (err: any) => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// enqueueTextDelta('error', {
|
|
100
|
-
// type: 'agent-error',
|
|
101
|
-
// error: '操作超时,请刷新重试',
|
|
102
|
-
// });
|
|
103
|
-
}
|
|
92
|
+
console.error('JsonWidgetStream: JSON Parsing Error:', err);
|
|
93
|
+
|
|
94
|
+
// LLM 返回的不是 JSON,切换到直通模式,后续 text-delta 直接透传
|
|
95
|
+
passthroughMode = true;
|
|
104
96
|
};
|
|
105
97
|
|
|
106
98
|
parser.onEnd = () => {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LanguageModelV2Content,
|
|
3
|
+
LanguageModelV2FinishReason,
|
|
4
|
+
LanguageModelV2Usage,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
6
|
+
import { ServerSentEventTransformStream } from 'parse-sse';
|
|
7
|
+
import { TextDecoderStream } from 'stream/web';
|
|
8
|
+
|
|
9
|
+
export interface AccumulatedSSEResponse {
|
|
10
|
+
content: LanguageModelV2Content[];
|
|
11
|
+
finishReason: LanguageModelV2FinishReason;
|
|
12
|
+
usage: LanguageModelV2Usage;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 从字节流中解码 SSE 事件并累积完整响应内容
|
|
17
|
+
*/
|
|
18
|
+
export async function accumulateSSEResponse(
|
|
19
|
+
body: ReadableStream<Uint8Array>
|
|
20
|
+
): Promise<AccumulatedSSEResponse> {
|
|
21
|
+
const sseStream = body
|
|
22
|
+
.pipeThrough(
|
|
23
|
+
new TextDecoderStream() as ReadableWritablePair<
|
|
24
|
+
string | undefined,
|
|
25
|
+
Uint8Array<ArrayBufferLike>
|
|
26
|
+
>
|
|
27
|
+
)
|
|
28
|
+
.pipeThrough(new ServerSentEventTransformStream());
|
|
29
|
+
|
|
30
|
+
let textContent = '';
|
|
31
|
+
let reasoningContent = '';
|
|
32
|
+
let finishReason: LanguageModelV2FinishReason = 'stop';
|
|
33
|
+
let usage: LanguageModelV2Usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
34
|
+
|
|
35
|
+
const reader = sseStream.getReader();
|
|
36
|
+
try {
|
|
37
|
+
while (true) {
|
|
38
|
+
const { done, value: evt } = await reader.read();
|
|
39
|
+
if (done) break;
|
|
40
|
+
if (!evt) continue;
|
|
41
|
+
|
|
42
|
+
let evtData;
|
|
43
|
+
try {
|
|
44
|
+
evtData = JSON.parse(evt.data);
|
|
45
|
+
} catch {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const event = evt.type;
|
|
50
|
+
|
|
51
|
+
if (event === 'conversation.message.delta') {
|
|
52
|
+
if (evtData.reasoning_content) {
|
|
53
|
+
reasoningContent += evtData.reasoning_content;
|
|
54
|
+
} else if (evtData.content) {
|
|
55
|
+
textContent += evtData.content;
|
|
56
|
+
}
|
|
57
|
+
} else if (event === 'conversation.chat.completed' || event === 'done') {
|
|
58
|
+
finishReason = 'stop';
|
|
59
|
+
if (evtData.usage) {
|
|
60
|
+
usage = {
|
|
61
|
+
inputTokens: evtData.usage.input_count || 0,
|
|
62
|
+
outputTokens: evtData.usage.output_count || 0,
|
|
63
|
+
totalTokens: evtData.usage.total_count || 0,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
} else if (event === 'error') {
|
|
67
|
+
finishReason = 'error';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
reader.releaseLock();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const content: LanguageModelV2Content[] = [];
|
|
75
|
+
if (reasoningContent) {
|
|
76
|
+
content.push({ type: 'reasoning', text: reasoningContent });
|
|
77
|
+
}
|
|
78
|
+
if (textContent) {
|
|
79
|
+
content.push({ type: 'text', text: textContent });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { content, finishReason, usage };
|
|
83
|
+
}
|