@aigne/core 0.0.1
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/lib/cjs/assistant/generate-output.js +101 -0
- package/lib/cjs/assistant/select-agent.js +76 -0
- package/lib/cjs/assistant/type.js +11 -0
- package/lib/cjs/common/aid.js +42 -0
- package/lib/cjs/common/index.js +238 -0
- package/lib/cjs/common/resource-manager.js +199 -0
- package/lib/cjs/constants.js +9 -0
- package/lib/cjs/executor/agent.js +10 -0
- package/lib/cjs/executor/aigc.js +28 -0
- package/lib/cjs/executor/api.js +64 -0
- package/lib/cjs/executor/base.js +676 -0
- package/lib/cjs/executor/blocklet.js +25 -0
- package/lib/cjs/executor/call-agent.js +105 -0
- package/lib/cjs/executor/decision.js +478 -0
- package/lib/cjs/executor/image-blender.js +32 -0
- package/lib/cjs/executor/index.js +81 -0
- package/lib/cjs/executor/llm.js +379 -0
- package/lib/cjs/executor/logic.js +167 -0
- package/lib/cjs/index.js +17 -0
- package/lib/cjs/libs/blocklet/vc.js +92 -0
- package/lib/cjs/libs/openapi/request/index.js +24 -0
- package/lib/cjs/libs/openapi/request/util.js +146 -0
- package/lib/cjs/libs/openapi/types/index.js +17 -0
- package/lib/cjs/libs/openapi/util/call.js +15 -0
- package/lib/cjs/libs/openapi/util/check-schema.js +67 -0
- package/lib/cjs/libs/openapi/util/convert-schema.js +44 -0
- package/lib/cjs/libs/openapi/util/flatten-open-api.js +21 -0
- package/lib/cjs/libs/openapi/util/get-open-api-i18n-text.js +7 -0
- package/lib/cjs/logger.js +4 -0
- package/lib/cjs/runtime/resource-blocklet.js +5 -0
- package/lib/cjs/runtime/runtime.js +143 -0
- package/lib/cjs/types/assistant/index.js +31 -0
- package/lib/cjs/types/assistant/mustache/ReadableMustache.js +69 -0
- package/lib/cjs/types/assistant/mustache/directive.js +35 -0
- package/lib/cjs/types/assistant/mustache/mustache.js +688 -0
- package/lib/cjs/types/common/index.js +2 -0
- package/lib/cjs/types/index.js +20 -0
- package/lib/cjs/types/resource/index.js +47 -0
- package/lib/cjs/types/resource/project.js +35 -0
- package/lib/cjs/types/runtime/agent.js +2 -0
- package/lib/cjs/types/runtime/error.js +18 -0
- package/lib/cjs/types/runtime/index.js +37 -0
- package/lib/cjs/types/runtime/runtime-resource-blocklet-state.js +4 -0
- package/lib/cjs/types/runtime/schema.js +259 -0
- package/lib/cjs/utils/cron-job.js +48 -0
- package/lib/cjs/utils/extract-metadata-transform.js +58 -0
- package/lib/cjs/utils/extract-metadata-transform.test.js +61 -0
- package/lib/cjs/utils/fs.js +49 -0
- package/lib/cjs/utils/get-blocklet-agent.js +351 -0
- package/lib/cjs/utils/geti.js +37 -0
- package/lib/cjs/utils/is-non-nullable.js +20 -0
- package/lib/cjs/utils/render-message.js +23 -0
- package/lib/cjs/utils/resolve-secret-inputs.js +49 -0
- package/lib/cjs/utils/retry.js +19 -0
- package/lib/cjs/utils/task-id.js +7 -0
- package/lib/cjs/utils/tool-calls-transform.js +18 -0
- package/lib/esm/assistant/generate-output.js +91 -0
- package/lib/esm/assistant/select-agent.js +71 -0
- package/lib/esm/assistant/type.js +7 -0
- package/lib/esm/common/aid.js +38 -0
- package/lib/esm/common/index.js +232 -0
- package/lib/esm/common/resource-manager.js +192 -0
- package/lib/esm/constants.js +6 -0
- package/lib/esm/executor/agent.js +6 -0
- package/lib/esm/executor/aigc.js +24 -0
- package/lib/esm/executor/api.js +34 -0
- package/lib/esm/executor/base.js +668 -0
- package/lib/esm/executor/blocklet.js +21 -0
- package/lib/esm/executor/call-agent.js +98 -0
- package/lib/esm/executor/decision.js +471 -0
- package/lib/esm/executor/image-blender.js +25 -0
- package/lib/esm/executor/index.js +74 -0
- package/lib/esm/executor/llm.js +372 -0
- package/lib/esm/executor/logic.js +160 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/libs/blocklet/vc.js +85 -0
- package/lib/esm/libs/openapi/request/index.js +20 -0
- package/lib/esm/libs/openapi/request/util.js +136 -0
- package/lib/esm/libs/openapi/types/index.js +1 -0
- package/lib/esm/libs/openapi/util/call.js +8 -0
- package/lib/esm/libs/openapi/util/check-schema.js +62 -0
- package/lib/esm/libs/openapi/util/convert-schema.js +42 -0
- package/lib/esm/libs/openapi/util/flatten-open-api.js +19 -0
- package/lib/esm/libs/openapi/util/get-open-api-i18n-text.js +5 -0
- package/lib/esm/logger.js +2 -0
- package/lib/esm/runtime/resource-blocklet.js +2 -0
- package/lib/esm/runtime/runtime.js +136 -0
- package/lib/esm/types/assistant/index.js +9 -0
- package/lib/esm/types/assistant/mustache/ReadableMustache.js +63 -0
- package/lib/esm/types/assistant/mustache/directive.js +29 -0
- package/lib/esm/types/assistant/mustache/mustache.js +686 -0
- package/lib/esm/types/common/index.js +1 -0
- package/lib/esm/types/index.js +4 -0
- package/lib/esm/types/resource/index.js +26 -0
- package/lib/esm/types/resource/project.js +29 -0
- package/lib/esm/types/runtime/agent.js +1 -0
- package/lib/esm/types/runtime/error.js +14 -0
- package/lib/esm/types/runtime/index.js +20 -0
- package/lib/esm/types/runtime/runtime-resource-blocklet-state.js +1 -0
- package/lib/esm/types/runtime/schema.js +249 -0
- package/lib/esm/utils/cron-job.js +44 -0
- package/lib/esm/utils/extract-metadata-transform.js +54 -0
- package/lib/esm/utils/extract-metadata-transform.test.js +59 -0
- package/lib/esm/utils/fs.js +41 -0
- package/lib/esm/utils/get-blocklet-agent.js +344 -0
- package/lib/esm/utils/geti.js +30 -0
- package/lib/esm/utils/is-non-nullable.js +13 -0
- package/lib/esm/utils/render-message.js +20 -0
- package/lib/esm/utils/resolve-secret-inputs.js +46 -0
- package/lib/esm/utils/retry.js +16 -0
- package/lib/esm/utils/task-id.js +3 -0
- package/lib/esm/utils/tool-calls-transform.js +15 -0
- package/lib/types/assistant/generate-output.d.ts +29 -0
- package/lib/types/assistant/select-agent.d.ts +14 -0
- package/lib/types/assistant/type.d.ts +61 -0
- package/lib/types/common/aid.d.ts +18 -0
- package/lib/types/common/index.d.ts +7 -0
- package/lib/types/common/resource-manager.d.ts +88 -0
- package/lib/types/constants.d.ts +6 -0
- package/lib/types/executor/agent.d.ts +5 -0
- package/lib/types/executor/aigc.d.ts +9 -0
- package/lib/types/executor/api.d.ts +9 -0
- package/lib/types/executor/base.d.ts +209 -0
- package/lib/types/executor/blocklet.d.ts +9 -0
- package/lib/types/executor/call-agent.d.ts +12 -0
- package/lib/types/executor/decision.d.ts +20 -0
- package/lib/types/executor/image-blender.d.ts +9 -0
- package/lib/types/executor/index.d.ts +8 -0
- package/lib/types/executor/llm.d.ts +38 -0
- package/lib/types/executor/logic.d.ts +9 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/libs/blocklet/vc.d.ts +17 -0
- package/lib/types/libs/openapi/request/index.d.ts +17 -0
- package/lib/types/libs/openapi/request/util.d.ts +40 -0
- package/lib/types/libs/openapi/types/index.d.ts +20 -0
- package/lib/types/libs/openapi/util/call.d.ts +2 -0
- package/lib/types/libs/openapi/util/check-schema.d.ts +3 -0
- package/lib/types/libs/openapi/util/convert-schema.d.ts +8 -0
- package/lib/types/libs/openapi/util/flatten-open-api.d.ts +3 -0
- package/lib/types/libs/openapi/util/get-open-api-i18n-text.d.ts +2 -0
- package/lib/types/logger.d.ts +2 -0
- package/lib/types/runtime/resource-blocklet.d.ts +2 -0
- package/lib/types/runtime/runtime.d.ts +20 -0
- package/lib/types/types/assistant/index.d.ts +405 -0
- package/lib/types/types/assistant/mustache/ReadableMustache.d.ts +2 -0
- package/lib/types/types/assistant/mustache/directive.d.ts +6 -0
- package/lib/types/types/assistant/mustache/mustache.d.ts +2 -0
- package/lib/types/types/common/index.d.ts +45 -0
- package/lib/types/types/index.d.ts +4 -0
- package/lib/types/types/resource/index.d.ts +17 -0
- package/lib/types/types/resource/project.d.ts +41 -0
- package/lib/types/types/runtime/agent.d.ts +33 -0
- package/lib/types/types/runtime/error.d.ts +10 -0
- package/lib/types/types/runtime/index.d.ts +116 -0
- package/lib/types/types/runtime/runtime-resource-blocklet-state.d.ts +5 -0
- package/lib/types/types/runtime/schema.d.ts +110 -0
- package/lib/types/utils/cron-job.d.ts +22 -0
- package/lib/types/utils/extract-metadata-transform.d.ts +16 -0
- package/lib/types/utils/extract-metadata-transform.test.d.ts +1 -0
- package/lib/types/utils/fs.d.ts +9 -0
- package/lib/types/utils/get-blocklet-agent.d.ts +219 -0
- package/lib/types/utils/geti.d.ts +1 -0
- package/lib/types/utils/is-non-nullable.d.ts +2 -0
- package/lib/types/utils/render-message.d.ts +6 -0
- package/lib/types/utils/resolve-secret-inputs.d.ts +11 -0
- package/lib/types/utils/retry.d.ts +1 -0
- package/lib/types/utils/task-id.d.ts +1 -0
- package/lib/types/utils/tool-calls-transform.d.ts +2 -0
- package/package.json +67 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { TransformStream } from 'stream/web';
|
|
2
|
+
import { isChatCompletionChunk, isChatCompletionUsage } from '@blocklet/ai-kit/api/types/index';
|
|
3
|
+
import { logger } from '@blocklet/sdk/lib/config';
|
|
4
|
+
import { extractMetadataFromStream, metadataOutputFormatPrompt, metadataStreamOutputFormatPrompt, } from '../assistant/generate-output';
|
|
5
|
+
import { defaultTextModel, supportJsonSchemaModels } from '../common';
|
|
6
|
+
import { parseIdentity, stringifyIdentity } from '../common/aid';
|
|
7
|
+
import { AssistantResponseType, RuntimeOutputVariable, jsonSchemaToOpenAIJsonSchema, outputVariablesToJsonSchema, } from '../types';
|
|
8
|
+
import { parseDirectives } from '../types/assistant/mustache/directive';
|
|
9
|
+
import retry from '../utils/retry';
|
|
10
|
+
import { nextId } from '../utils/task-id';
|
|
11
|
+
import { AgentExecutorBase } from './base';
|
|
12
|
+
export class LLMAgentExecutor extends AgentExecutorBase {
|
|
13
|
+
retryTimes = 0;
|
|
14
|
+
get modelInfo() {
|
|
15
|
+
const { agent } = this;
|
|
16
|
+
const model = agent.model || agent.project.model || defaultTextModel;
|
|
17
|
+
const defaultModelInfo = model === agent.project.model ? agent.project : undefined;
|
|
18
|
+
return {
|
|
19
|
+
model,
|
|
20
|
+
temperature: agent.temperature ?? defaultModelInfo?.temperature,
|
|
21
|
+
topP: agent.topP ?? defaultModelInfo?.topP,
|
|
22
|
+
presencePenalty: agent.presencePenalty ?? defaultModelInfo?.presencePenalty,
|
|
23
|
+
frequencyPenalty: agent.frequencyPenalty ?? defaultModelInfo?.frequencyPenalty,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
_executor;
|
|
27
|
+
get executor() {
|
|
28
|
+
this._executor ??= (async () => {
|
|
29
|
+
const { agent } = this;
|
|
30
|
+
const identity = parseIdentity(agent.identity.aid, { rejectWhenError: true });
|
|
31
|
+
return agent.executor?.agent?.id
|
|
32
|
+
? {
|
|
33
|
+
executor: await this.context.getAgent({
|
|
34
|
+
aid: stringifyIdentity({
|
|
35
|
+
blockletDid: agent.executor.agent.blockletDid || identity.blockletDid,
|
|
36
|
+
projectId: agent.executor.agent.projectId || identity.projectId,
|
|
37
|
+
projectRef: identity.projectRef,
|
|
38
|
+
agentId: agent.executor.agent.id,
|
|
39
|
+
}),
|
|
40
|
+
working: agent.identity.working,
|
|
41
|
+
rejectOnEmpty: true,
|
|
42
|
+
}),
|
|
43
|
+
inputValues: agent.executor.inputValues,
|
|
44
|
+
}
|
|
45
|
+
: agent.project.executor?.agent?.id
|
|
46
|
+
? {
|
|
47
|
+
executor: await this.context.getAgent({
|
|
48
|
+
aid: stringifyIdentity({
|
|
49
|
+
blockletDid: agent.project.executor.agent.blockletDid || identity.blockletDid,
|
|
50
|
+
projectId: agent.project.executor.agent.projectId || identity.projectId,
|
|
51
|
+
projectRef: identity.projectRef,
|
|
52
|
+
agentId: agent.project.executor.agent.id,
|
|
53
|
+
}),
|
|
54
|
+
working: agent.identity.working,
|
|
55
|
+
rejectOnEmpty: true,
|
|
56
|
+
}),
|
|
57
|
+
inputValues: agent.project.executor.inputValues,
|
|
58
|
+
}
|
|
59
|
+
: undefined;
|
|
60
|
+
})();
|
|
61
|
+
return this._executor;
|
|
62
|
+
}
|
|
63
|
+
_outputsInfo;
|
|
64
|
+
get outputsInfo() {
|
|
65
|
+
this._outputsInfo ??= (async () => {
|
|
66
|
+
const { agent } = this;
|
|
67
|
+
const outputVariables = (agent.outputVariables ?? []).filter((i) => !!i.name && !i.hidden && i.from?.type !== 'callAgent') ?? [];
|
|
68
|
+
const schema = outputVariablesToJsonSchema(agent, {
|
|
69
|
+
variables: await this.context.getMemoryVariables({
|
|
70
|
+
...parseIdentity(agent.identity.aid, { rejectWhenError: true }),
|
|
71
|
+
working: agent.identity.working,
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
const hasStreamingTextOutput = outputVariables.some((i) => i.name === RuntimeOutputVariable.text);
|
|
75
|
+
const hasJsonOutputs = !!schema && Object.values(schema.properties).length > 0;
|
|
76
|
+
return { outputs: outputVariables, schema, hasStreamingTextOutput, hasJsonOutputs };
|
|
77
|
+
})();
|
|
78
|
+
return this._outputsInfo;
|
|
79
|
+
}
|
|
80
|
+
getMessages({ inputs }) {
|
|
81
|
+
const { agent } = this;
|
|
82
|
+
const createContentStructure = async (content, variables, agent, prompt) => {
|
|
83
|
+
const parameters = agent.parameters ?? [];
|
|
84
|
+
// 没有特殊变量
|
|
85
|
+
if (!variables.length) {
|
|
86
|
+
const renderedContent = await this.renderMessage(content, { ...inputs, ...this.globalContext });
|
|
87
|
+
return renderedContent;
|
|
88
|
+
}
|
|
89
|
+
// 没有图片变量
|
|
90
|
+
const haveImageParameter = parameters.filter((i) => i.type === 'image').some((i) => variables.includes(i.key));
|
|
91
|
+
if (!haveImageParameter) {
|
|
92
|
+
const renderedContent = await this.renderMessage(content, { ...inputs, ...this.globalContext });
|
|
93
|
+
return renderedContent;
|
|
94
|
+
}
|
|
95
|
+
// 创建图片变量标记
|
|
96
|
+
const createImageMarker = (variable) => `__IMAGE_${variable}__`;
|
|
97
|
+
const imageVariablesMap = {};
|
|
98
|
+
const imageVariables = [];
|
|
99
|
+
// 当前参数有图片变量
|
|
100
|
+
parameters
|
|
101
|
+
.filter((i) => i.type === 'image' && i.key && variables.includes(i.key))
|
|
102
|
+
.forEach((param) => {
|
|
103
|
+
if (param.type === 'image' && param.key && inputs[param.key]) {
|
|
104
|
+
const marker = createImageMarker(param.key);
|
|
105
|
+
imageVariablesMap[param.key] = marker;
|
|
106
|
+
imageVariables.push({ marker, value: inputs[param.key] });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const renderedContent = await this.renderMessage(content, {
|
|
110
|
+
...inputs,
|
|
111
|
+
...this.globalContext,
|
|
112
|
+
...imageVariablesMap,
|
|
113
|
+
});
|
|
114
|
+
const contentParts = [];
|
|
115
|
+
if (imageVariables.length === 0) {
|
|
116
|
+
return renderedContent;
|
|
117
|
+
}
|
|
118
|
+
let remainingContent = renderedContent;
|
|
119
|
+
for (const { marker, value } of imageVariables) {
|
|
120
|
+
const parts = remainingContent.split(marker);
|
|
121
|
+
if (parts[0]) {
|
|
122
|
+
contentParts.push({ type: 'text', text: parts[0] });
|
|
123
|
+
}
|
|
124
|
+
// 只有是 user 时,才处理
|
|
125
|
+
if (prompt.data.role === 'user') {
|
|
126
|
+
const list = Array.isArray(value) ? value : [value];
|
|
127
|
+
list.forEach((item) => contentParts.push({ type: 'image_url', imageUrl: { url: item } }));
|
|
128
|
+
}
|
|
129
|
+
remainingContent = parts[1] || '';
|
|
130
|
+
}
|
|
131
|
+
if (remainingContent) {
|
|
132
|
+
contentParts.push({ type: 'text', text: remainingContent });
|
|
133
|
+
}
|
|
134
|
+
logger.info('have image messages', contentParts);
|
|
135
|
+
return contentParts;
|
|
136
|
+
};
|
|
137
|
+
return (async () => (await Promise.all((agent.prompts ?? [])
|
|
138
|
+
.filter((i) => i.visibility !== 'hidden')
|
|
139
|
+
.map(async (prompt) => {
|
|
140
|
+
if (prompt.type === 'message') {
|
|
141
|
+
const content = prompt.data.content
|
|
142
|
+
?.split('\n')
|
|
143
|
+
.filter((i) => !i.startsWith('//'))
|
|
144
|
+
.join('\n') || '';
|
|
145
|
+
const variables = parseDirectives(content)
|
|
146
|
+
.filter((i) => i.type === 'variable')
|
|
147
|
+
.map((i) => i.name);
|
|
148
|
+
const contentStructure = await createContentStructure(content, variables, agent, prompt);
|
|
149
|
+
return {
|
|
150
|
+
role: prompt.data.role,
|
|
151
|
+
content: contentStructure,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
console.warn('Unsupported prompt type', prompt);
|
|
155
|
+
return undefined;
|
|
156
|
+
})))
|
|
157
|
+
.flat()
|
|
158
|
+
.filter((i) => !!i?.content))();
|
|
159
|
+
}
|
|
160
|
+
async process({ inputs }) {
|
|
161
|
+
const { hasJsonOutputs } = await this.outputsInfo;
|
|
162
|
+
const messages = await this.getMessages({ inputs });
|
|
163
|
+
const { modelInfo } = this;
|
|
164
|
+
const e = await this.executor;
|
|
165
|
+
// NOTE: use json_schema output for models that support it
|
|
166
|
+
if (supportJsonSchemaModels.includes(modelInfo.model) &&
|
|
167
|
+
hasJsonOutputs &&
|
|
168
|
+
// check the llm executor is support json schema output
|
|
169
|
+
(!e || e.executor.parameters?.some((i) => i.type === 'llmInputResponseFormat'))) {
|
|
170
|
+
return this.processWithJsonSchemaFormat({ messages, inputs });
|
|
171
|
+
}
|
|
172
|
+
return this.processWithOutJsonSchemaFormatSupport({ inputs, messages });
|
|
173
|
+
}
|
|
174
|
+
async processWithOutJsonSchemaFormatSupport({ inputs, messages, }) {
|
|
175
|
+
const { agent, options: { taskId, parentTaskId }, } = this;
|
|
176
|
+
const { hasStreamingTextOutput, hasJsonOutputs, schema } = await this.outputsInfo;
|
|
177
|
+
const outputSchema = JSON.stringify(schema);
|
|
178
|
+
const messagesWithSystemPrompt = [...messages];
|
|
179
|
+
const lastSystemIndex = messagesWithSystemPrompt.findLastIndex((i) => i.role === 'system');
|
|
180
|
+
if (hasJsonOutputs) {
|
|
181
|
+
if (hasStreamingTextOutput) {
|
|
182
|
+
messagesWithSystemPrompt.splice(lastSystemIndex + 1, 0, {
|
|
183
|
+
role: 'system',
|
|
184
|
+
content: metadataStreamOutputFormatPrompt(outputSchema),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
messagesWithSystemPrompt.splice(lastSystemIndex + 1, 0, {
|
|
189
|
+
role: 'system',
|
|
190
|
+
content: metadataOutputFormatPrompt(outputSchema),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!messagesWithSystemPrompt.length)
|
|
195
|
+
return undefined;
|
|
196
|
+
this.context.callback?.({
|
|
197
|
+
type: AssistantResponseType.INPUT,
|
|
198
|
+
assistantId: agent.id,
|
|
199
|
+
parentTaskId,
|
|
200
|
+
taskId,
|
|
201
|
+
assistantName: agent.name,
|
|
202
|
+
inputParameters: this.hideSecretInputs(inputs, agent),
|
|
203
|
+
promptMessages: messagesWithSystemPrompt,
|
|
204
|
+
});
|
|
205
|
+
const run = async () => {
|
|
206
|
+
this.retryTimes += 1;
|
|
207
|
+
let result = '';
|
|
208
|
+
const metadataStrings = [];
|
|
209
|
+
const chatCompletionChunk = await this.callAIOrExecutor({ messages: messagesWithSystemPrompt, inputs });
|
|
210
|
+
const stream = extractMetadataFromStream(chatCompletionChunk.pipeThrough(new TransformStream({
|
|
211
|
+
transform: (chunk, controller) => {
|
|
212
|
+
if (isChatCompletionUsage(chunk)) {
|
|
213
|
+
this.context.callback?.({
|
|
214
|
+
type: AssistantResponseType.USAGE,
|
|
215
|
+
taskId,
|
|
216
|
+
assistantId: agent.id,
|
|
217
|
+
usage: chunk.usage,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
controller.enqueue(chunk);
|
|
221
|
+
},
|
|
222
|
+
})), hasJsonOutputs);
|
|
223
|
+
for await (const chunk of stream) {
|
|
224
|
+
if (chunk.type === 'text') {
|
|
225
|
+
const { text } = chunk;
|
|
226
|
+
result += text;
|
|
227
|
+
if (hasStreamingTextOutput && this.retryTimes === 1) {
|
|
228
|
+
this.context.callback?.({
|
|
229
|
+
type: AssistantResponseType.CHUNK,
|
|
230
|
+
taskId,
|
|
231
|
+
assistantId: agent.id,
|
|
232
|
+
delta: { content: text },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (chunk.type === 'match') {
|
|
237
|
+
metadataStrings.push(chunk.text);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const json = {};
|
|
241
|
+
for (const i of metadataStrings) {
|
|
242
|
+
try {
|
|
243
|
+
const obj = JSON.parse(i);
|
|
244
|
+
Object.assign(json, obj);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// ignore
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// try to parse all text content as a json
|
|
251
|
+
try {
|
|
252
|
+
Object.assign(json, JSON.parse(result));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// ignore
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
return await super.validateOutputs({
|
|
259
|
+
inputs,
|
|
260
|
+
outputs: { ...json, $text: result },
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
logger.error('validate LLM outputs error', error);
|
|
265
|
+
throw new Error('Unexpected response format from AI');
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
return await retry(run, this.context.maxRetries);
|
|
269
|
+
}
|
|
270
|
+
async processWithJsonSchemaFormat({ messages, inputs, }) {
|
|
271
|
+
const { agent, options: { parentTaskId, taskId }, } = this;
|
|
272
|
+
this.context.callback?.({
|
|
273
|
+
type: AssistantResponseType.INPUT,
|
|
274
|
+
assistantId: agent.id,
|
|
275
|
+
parentTaskId,
|
|
276
|
+
taskId,
|
|
277
|
+
assistantName: agent.name,
|
|
278
|
+
inputParameters: this.hideSecretInputs(inputs, agent),
|
|
279
|
+
promptMessages: messages,
|
|
280
|
+
});
|
|
281
|
+
const { hasStreamingTextOutput, schema } = await this.outputsInfo;
|
|
282
|
+
const [json, { text }] = await Promise.all([
|
|
283
|
+
this.callAIGetJsonOutput({ messages, schema }),
|
|
284
|
+
hasStreamingTextOutput ? this.callAIGetTextStreamOutput({ messages, inputs }) : { text: undefined },
|
|
285
|
+
]);
|
|
286
|
+
try {
|
|
287
|
+
return await super.validateOutputs({
|
|
288
|
+
inputs,
|
|
289
|
+
outputs: { ...json, $text: text },
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
logger.error('validate LLM outputs error', error);
|
|
294
|
+
throw new Error('Unexpected response format from AI');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async callAIGetJsonOutput({ messages, schema }) {
|
|
298
|
+
const { modelInfo } = this;
|
|
299
|
+
const result = await this.context.callAI({
|
|
300
|
+
assistant: this.agent,
|
|
301
|
+
input: {
|
|
302
|
+
messages,
|
|
303
|
+
...modelInfo,
|
|
304
|
+
responseFormat: {
|
|
305
|
+
type: 'json_schema',
|
|
306
|
+
jsonSchema: {
|
|
307
|
+
name: 'output',
|
|
308
|
+
schema: jsonSchemaToOpenAIJsonSchema(schema),
|
|
309
|
+
strict: true,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
let json = '';
|
|
315
|
+
for await (const i of result) {
|
|
316
|
+
if (isChatCompletionChunk(i)) {
|
|
317
|
+
json += i.delta.content || '';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(json);
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
logger.error(json, error);
|
|
325
|
+
throw new Error('parse ai json schema output error');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async callAIGetTextStreamOutput({ messages, inputs, }) {
|
|
329
|
+
const { agent, options: { taskId }, } = this;
|
|
330
|
+
const stream = await this.callAIOrExecutor({
|
|
331
|
+
messages,
|
|
332
|
+
inputs,
|
|
333
|
+
});
|
|
334
|
+
let text = '';
|
|
335
|
+
for await (const chunk of stream) {
|
|
336
|
+
if (isChatCompletionChunk(chunk)) {
|
|
337
|
+
text += chunk.delta.content || '';
|
|
338
|
+
this.context.callback?.({
|
|
339
|
+
type: AssistantResponseType.CHUNK,
|
|
340
|
+
taskId,
|
|
341
|
+
assistantId: agent.id,
|
|
342
|
+
delta: { content: chunk.delta.content },
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return { text };
|
|
347
|
+
}
|
|
348
|
+
async callAIOrExecutor({ messages, inputs, }) {
|
|
349
|
+
const { agent, modelInfo, options: { taskId }, } = this;
|
|
350
|
+
const e = await this.executor;
|
|
351
|
+
return e
|
|
352
|
+
? (await this.context
|
|
353
|
+
.executor(e.executor, {
|
|
354
|
+
inputs: {
|
|
355
|
+
...inputs,
|
|
356
|
+
...e.inputValues,
|
|
357
|
+
[e.executor.parameters?.find((i) => i.type === 'llmInputMessages' && !i.hidden)?.key]: messages,
|
|
358
|
+
},
|
|
359
|
+
taskId: nextId(),
|
|
360
|
+
parentTaskId: taskId,
|
|
361
|
+
})
|
|
362
|
+
.execute())[RuntimeOutputVariable.llmResponseStream]
|
|
363
|
+
: await this.context.callAI({
|
|
364
|
+
assistant: agent,
|
|
365
|
+
input: {
|
|
366
|
+
stream: true,
|
|
367
|
+
messages,
|
|
368
|
+
...modelInfo,
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { Sandbox } from '@blocklet/quickjs';
|
|
3
|
+
import { call, getComponentMountPoint } from '@blocklet/sdk/lib/component';
|
|
4
|
+
import config from '@blocklet/sdk/lib/config';
|
|
5
|
+
import equal from 'fast-deep-equal';
|
|
6
|
+
import Joi from 'joi';
|
|
7
|
+
import pick from 'lodash/pick';
|
|
8
|
+
import { parseIdentity, stringifyIdentity } from '../common/aid';
|
|
9
|
+
import logger from '../logger';
|
|
10
|
+
import { AssistantResponseType } from '../types';
|
|
11
|
+
import { renderMustacheStream } from '../types/assistant/mustache/ReadableMustache';
|
|
12
|
+
import { geti } from '../utils/geti';
|
|
13
|
+
import { nextId } from '../utils/task-id';
|
|
14
|
+
import { AgentExecutorBase } from './base';
|
|
15
|
+
async function parseJSONInVM(str) {
|
|
16
|
+
return Sandbox.callFunction({
|
|
17
|
+
code: `\
|
|
18
|
+
function parse(json) {
|
|
19
|
+
return eval(\`const j = \${json}; j\`)
|
|
20
|
+
}
|
|
21
|
+
`,
|
|
22
|
+
filename: 'parserJSONInVm.js',
|
|
23
|
+
functionName: 'parse',
|
|
24
|
+
args: [str.trim()],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export class LogicAgentExecutor extends AgentExecutorBase {
|
|
28
|
+
async process({ inputs }) {
|
|
29
|
+
const { agent, options: { taskId }, } = this;
|
|
30
|
+
if (!agent.code)
|
|
31
|
+
throw new Error(`Assistant ${agent.id}'s code is empty`);
|
|
32
|
+
const args = Object.fromEntries(await Promise.all((agent.parameters ?? [])
|
|
33
|
+
.filter((i) => !!i.key && !i.hidden)
|
|
34
|
+
.map(async (i) => [i.key, inputs?.[i.key] ?? i.defaultValue])));
|
|
35
|
+
const $json = (variables, ...rest) => {
|
|
36
|
+
const taggedFn = async (t, ...rest) => {
|
|
37
|
+
const template = t.map((s, i) => `${s}${i === t.length - 1 ? '' : rest[i]}`).join('');
|
|
38
|
+
const renderCtx = {
|
|
39
|
+
...args,
|
|
40
|
+
...variables,
|
|
41
|
+
...this.globalContext,
|
|
42
|
+
get: () => async (template, render) => {
|
|
43
|
+
const s = await render(template);
|
|
44
|
+
return geti(renderCtx, s);
|
|
45
|
+
},
|
|
46
|
+
runAgent: () => async (template, render) => {
|
|
47
|
+
const t = (await parseJSONInVM(template))?.template;
|
|
48
|
+
const s = await render(template);
|
|
49
|
+
const j = await parseJSONInVM(s);
|
|
50
|
+
const { agentId, inputs } = await Joi.object({
|
|
51
|
+
agentId: Joi.string().required(),
|
|
52
|
+
inputs: Joi.object().pattern(Joi.string(), Joi.any()).required(),
|
|
53
|
+
}).validateAsync(j, { stripUnknown: true });
|
|
54
|
+
const a = await this.context.getAgent({
|
|
55
|
+
aid: stringifyIdentity({ ...parseIdentity(agent.identity.aid, { rejectWhenError: true }), agentId }),
|
|
56
|
+
working: agent.identity.working,
|
|
57
|
+
rejectOnEmpty: true,
|
|
58
|
+
});
|
|
59
|
+
const result = await this.context.execute(a, { taskId: nextId(), parentTaskId: taskId, inputs });
|
|
60
|
+
return this.renderMessage(t, { ...renderCtx, $result: result }, { escapeJsonSymbols: true });
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const result = renderMustacheStream(template, (enqueue) => ({
|
|
64
|
+
...renderCtx,
|
|
65
|
+
runAgent: () => enqueue(renderCtx.runAgent()),
|
|
66
|
+
}));
|
|
67
|
+
let object;
|
|
68
|
+
for await (const chunk of result) {
|
|
69
|
+
const newObj = await parseJSONInVM(chunk);
|
|
70
|
+
// skip if the object is equal
|
|
71
|
+
// TODO: throttle the output
|
|
72
|
+
if (equal(object, newObj)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
object = newObj;
|
|
76
|
+
try {
|
|
77
|
+
const obj = await this.validateOutputs({ outputs: object, partial: true });
|
|
78
|
+
this.context.callback?.({
|
|
79
|
+
type: AssistantResponseType.CHUNK,
|
|
80
|
+
taskId,
|
|
81
|
+
assistantId: agent.id,
|
|
82
|
+
delta: { object: obj },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
logger.error('validate LLM outputs error', error, object);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return object;
|
|
90
|
+
};
|
|
91
|
+
// 支持 $json({foo:"xxx"})`` 和 $json`` 两种调用方式
|
|
92
|
+
if (Array.isArray(variables)) {
|
|
93
|
+
return taggedFn(variables, ...rest);
|
|
94
|
+
}
|
|
95
|
+
return taggedFn;
|
|
96
|
+
};
|
|
97
|
+
const log = (...args) => this.context.callback({
|
|
98
|
+
type: AssistantResponseType.LOG,
|
|
99
|
+
log: JSON.stringify(args),
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
taskId,
|
|
102
|
+
assistantId: agent.id,
|
|
103
|
+
});
|
|
104
|
+
const global = {
|
|
105
|
+
console: {
|
|
106
|
+
// NOTE: do not return logger.xxx result, it will cause memory leak
|
|
107
|
+
log: (...args) => log(...args),
|
|
108
|
+
info: (...args) => log(...args),
|
|
109
|
+
debug: (...args) => log(...args),
|
|
110
|
+
warn: (...args) => log(...args),
|
|
111
|
+
error: (...args) => log(...args),
|
|
112
|
+
},
|
|
113
|
+
getComponentMountPoint,
|
|
114
|
+
call: (...args) => call(...args).then((res) => ({ data: res.data })),
|
|
115
|
+
crypto: { randomInt: crypto.randomInt },
|
|
116
|
+
config: { env: pick(config.env, 'appId', 'appName', 'appDescription', 'appUrl') },
|
|
117
|
+
};
|
|
118
|
+
const allArgs = {
|
|
119
|
+
$json,
|
|
120
|
+
runAgent: async ({ agentId, inputs, streaming, }) => {
|
|
121
|
+
const a = await this.context.getAgent({
|
|
122
|
+
aid: stringifyIdentity({ ...parseIdentity(agent.identity.aid, { rejectWhenError: true }), agentId }),
|
|
123
|
+
working: agent.identity.working,
|
|
124
|
+
rejectOnEmpty: true,
|
|
125
|
+
});
|
|
126
|
+
const { callback } = this.context;
|
|
127
|
+
const currentTaskId = nextId();
|
|
128
|
+
return this.context
|
|
129
|
+
.copy(streaming
|
|
130
|
+
? {
|
|
131
|
+
callback: function hello(args) {
|
|
132
|
+
callback(args);
|
|
133
|
+
if (args.type === AssistantResponseType.CHUNK &&
|
|
134
|
+
args.delta.content &&
|
|
135
|
+
args.taskId === currentTaskId) {
|
|
136
|
+
callback({ ...args, taskId });
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
: {})
|
|
141
|
+
.execute(a, { taskId: currentTaskId, parentTaskId: taskId, inputs });
|
|
142
|
+
},
|
|
143
|
+
...this.globalContext,
|
|
144
|
+
...args,
|
|
145
|
+
};
|
|
146
|
+
const argKeys = Object.keys(allArgs);
|
|
147
|
+
const resultPromise = await Sandbox.callFunction({
|
|
148
|
+
code: `\
|
|
149
|
+
async function main({${argKeys.join(', ')}) {
|
|
150
|
+
${agent.code}
|
|
151
|
+
}
|
|
152
|
+
`,
|
|
153
|
+
filename: `${agent.name || agent.id}.js`,
|
|
154
|
+
global,
|
|
155
|
+
functionName: 'main',
|
|
156
|
+
args: [allArgs],
|
|
157
|
+
});
|
|
158
|
+
return resultPromise;
|
|
159
|
+
}
|
|
160
|
+
}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './runtime/runtime';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import AuthService from '@blocklet/sdk/lib/service/auth';
|
|
2
|
+
import { AIGNE_ISSUE_VC_PREFIX } from '../../constants';
|
|
3
|
+
export const authService = new AuthService();
|
|
4
|
+
export async function issueVC({ context: { entry }, userDid, name, title, description, reissue = false, displayUrl, notify = true, }) {
|
|
5
|
+
// NOTE: ensure name starts with AIGNE_ISSUE_VC_PREFIX to avoid conflict with internal passports such as admin/owner
|
|
6
|
+
if (!name.startsWith(AIGNE_ISSUE_VC_PREFIX))
|
|
7
|
+
name = `${AIGNE_ISSUE_VC_PREFIX}${name}`;
|
|
8
|
+
await createPassportIfNotExist({ name, title, description });
|
|
9
|
+
const userResult = await authService.getUser(userDid, { includeTags: true });
|
|
10
|
+
if (!userResult.user)
|
|
11
|
+
throw new Error(`User not found ${userDid}`);
|
|
12
|
+
if (!reissue) {
|
|
13
|
+
const vc = userResult.user.passports.find((p) => p.role === name);
|
|
14
|
+
if (vc)
|
|
15
|
+
return { user: userResult.user, vc };
|
|
16
|
+
}
|
|
17
|
+
const issueResult = await authService.issuePassportToUser({
|
|
18
|
+
userDid,
|
|
19
|
+
role: name,
|
|
20
|
+
display: displayUrl ? { type: 'url', content: displayUrl } : undefined,
|
|
21
|
+
notify,
|
|
22
|
+
notification: JSON.stringify({
|
|
23
|
+
title: `You just received a VC ${title}`,
|
|
24
|
+
body: description,
|
|
25
|
+
attachments: [
|
|
26
|
+
{
|
|
27
|
+
type: 'image',
|
|
28
|
+
data: {
|
|
29
|
+
url: displayUrl,
|
|
30
|
+
alt: title,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
appInfo: {
|
|
35
|
+
title: entry.project.name,
|
|
36
|
+
// logo: getProjectIconUrl(entry.project.id, {
|
|
37
|
+
// blockletDid: entry.blockletDid,
|
|
38
|
+
// working: entry.working,
|
|
39
|
+
// updatedAt: entry.project.updatedAt,
|
|
40
|
+
// }),
|
|
41
|
+
url: entry.appUrl,
|
|
42
|
+
description: entry.project.description,
|
|
43
|
+
},
|
|
44
|
+
poweredBy: {
|
|
45
|
+
name: 'AIGNE',
|
|
46
|
+
url: 'https://www.aigne.io',
|
|
47
|
+
},
|
|
48
|
+
severity: 'success',
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
if (issueResult.code !== 'ok')
|
|
52
|
+
throw new Error(`Issue VC failed got ${issueResult.code}`);
|
|
53
|
+
const vc = issueResult.user.passports.findLast((p) => p.role === name);
|
|
54
|
+
if (!vc)
|
|
55
|
+
throw new Error('Issue VC got empty result');
|
|
56
|
+
return { user: issueResult.user, vc };
|
|
57
|
+
}
|
|
58
|
+
const CREATE_PASSPORT_TASKS = {};
|
|
59
|
+
async function createPassportIfNotExist({ name, title, description, }) {
|
|
60
|
+
CREATE_PASSPORT_TASKS[name] ??= (async () => {
|
|
61
|
+
try {
|
|
62
|
+
let result = await authService.getRole(name);
|
|
63
|
+
if (!result.role) {
|
|
64
|
+
// Create a role for custom display
|
|
65
|
+
result = await authService.createRole({
|
|
66
|
+
title,
|
|
67
|
+
name,
|
|
68
|
+
description,
|
|
69
|
+
permissions: [],
|
|
70
|
+
extra: JSON.stringify({
|
|
71
|
+
display: 'custom',
|
|
72
|
+
types: [name],
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
// NOTE: reset task if failed
|
|
80
|
+
delete CREATE_PASSPORT_TASKS[name];
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
return CREATE_PASSPORT_TASKS[name];
|
|
85
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { call } from '@blocklet/sdk/lib/component';
|
|
2
|
+
import { getRequestConfig } from './util';
|
|
3
|
+
export const callBlockletApi = (pathItem, data, options) => {
|
|
4
|
+
const requestConfig = getRequestConfig(pathItem, data, {
|
|
5
|
+
params: options?.params || {},
|
|
6
|
+
data: options?.data || {},
|
|
7
|
+
});
|
|
8
|
+
const { headers, method, url, params, ...config } = requestConfig;
|
|
9
|
+
const did = options?.user?.did || '';
|
|
10
|
+
if (!pathItem.name)
|
|
11
|
+
throw new Error('Blocklet name is required to call blocklet api');
|
|
12
|
+
return call({
|
|
13
|
+
...config,
|
|
14
|
+
method: method,
|
|
15
|
+
name: pathItem.name,
|
|
16
|
+
path: url,
|
|
17
|
+
headers,
|
|
18
|
+
params: { ...params, userId: did },
|
|
19
|
+
});
|
|
20
|
+
};
|