agentlang 0.9.9 → 0.9.11
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/out/extension/main.cjs +38 -38
- package/out/extension/main.cjs.map +2 -2
- package/out/language/generated/ast.d.ts +1 -1
- package/out/language/generated/ast.js +1 -1
- package/out/language/generated/grammar.d.ts +1 -1
- package/out/language/generated/grammar.js +1 -1
- package/out/language/generated/module.d.ts +1 -1
- package/out/language/generated/module.js +1 -1
- package/out/language/main.cjs +850 -2388
- package/out/language/main.cjs.map +4 -4
- package/out/runtime/agents/common.d.ts +3 -1
- package/out/runtime/agents/common.d.ts.map +1 -1
- package/out/runtime/agents/common.js +35 -31
- package/out/runtime/agents/common.js.map +1 -1
- package/out/runtime/docs.d.ts +1 -0
- package/out/runtime/docs.d.ts.map +1 -1
- package/out/runtime/docs.js +16 -1
- package/out/runtime/docs.js.map +1 -1
- package/out/runtime/interpreter.d.ts +1 -0
- package/out/runtime/interpreter.d.ts.map +1 -1
- package/out/runtime/interpreter.js +60 -9
- package/out/runtime/interpreter.js.map +1 -1
- package/out/runtime/jsmodules.d.ts +2 -1
- package/out/runtime/jsmodules.d.ts.map +1 -1
- package/out/runtime/jsmodules.js +2 -1
- package/out/runtime/jsmodules.js.map +1 -1
- package/out/runtime/loader.d.ts.map +1 -1
- package/out/runtime/loader.js +3 -2
- package/out/runtime/loader.js.map +1 -1
- package/out/runtime/module.d.ts +1 -0
- package/out/runtime/module.d.ts.map +1 -1
- package/out/runtime/module.js +3 -0
- package/out/runtime/module.js.map +1 -1
- package/out/runtime/modules/ai.d.ts +12 -0
- package/out/runtime/modules/ai.d.ts.map +1 -1
- package/out/runtime/modules/ai.js +225 -28
- package/out/runtime/modules/ai.js.map +1 -1
- package/out/runtime/modules/core.d.ts.map +1 -1
- package/out/runtime/modules/core.js +7 -1
- package/out/runtime/modules/core.js.map +1 -1
- package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/impl.js +37 -6
- package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
- package/out/runtime/services/documentFetcher.d.ts +70 -0
- package/out/runtime/services/documentFetcher.d.ts.map +1 -0
- package/out/runtime/services/documentFetcher.js +582 -0
- package/out/runtime/services/documentFetcher.js.map +1 -0
- package/package.json +2 -1
- package/src/language/generated/ast.ts +1 -1
- package/src/language/generated/grammar.ts +1 -1
- package/src/language/generated/module.ts +1 -1
- package/src/runtime/agents/common.ts +37 -31
- package/src/runtime/docs.ts +17 -1
- package/src/runtime/interpreter.ts +64 -7
- package/src/runtime/jsmodules.ts +3 -1
- package/src/runtime/loader.ts +3 -2
- package/src/runtime/module.ts +4 -0
- package/src/runtime/modules/ai.ts +270 -33
- package/src/runtime/modules/core.ts +7 -1
- package/src/runtime/resolvers/sqldb/impl.ts +36 -6
- package/src/runtime/services/documentFetcher.ts +691 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IfPattern, LiteralPattern } from '../../language/syntax.js';
|
|
2
2
|
import { trimQuotes } from '../util.js';
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const PlannerDataModelInstructions = `Agentlang is a very-high-level declarative language that makes it easy to define business applications as 'models'.
|
|
5
5
|
The model of a business application consists of entity definitions and workflows defined in "modules".
|
|
6
6
|
A module is be encoded in a syntax inspired by JavaScript and JSON. Example of a simple module follows:
|
|
7
7
|
|
|
@@ -29,6 +29,36 @@ record EmailMessage {
|
|
|
29
29
|
body String
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
Entities in a module can be connected together in relationships. There are two types of relationships - 'contains' and 'between'.
|
|
33
|
+
'Contains' relationship is for hierarchical data, as in a Library entity containing Books. 'Between' relationship is for graph-like data,
|
|
34
|
+
like two Profiles in a social media app is connected as friends. A 'between' relationship can be one of the following three types - 'one_one' (one-to-one),
|
|
35
|
+
'one_many' (one-to-many) and 'many_many' (many-to-many), which is the default.
|
|
36
|
+
|
|
37
|
+
The following example shows how additional profile data for an employee could be defined as a new entity and attached to the Employee entity as a between-relationship:
|
|
38
|
+
|
|
39
|
+
entity Profile {
|
|
40
|
+
id UUID @id @default(uuid()),
|
|
41
|
+
address String @optional,
|
|
42
|
+
photo URL @optional,
|
|
43
|
+
dateOfBirth DateTime @optional
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
relationship EmployeeProfile between (Erp/Employee, Erp/Profile) @one_one
|
|
47
|
+
|
|
48
|
+
The '@one_one' annotation means exactly one Employee and Profile can be related to each other via 'EmployeeProfile'.
|
|
49
|
+
|
|
50
|
+
As an example of 'contains' relaionships, consider modelling task-assignments for an Employee as folllows:
|
|
51
|
+
|
|
52
|
+
entity TaskAssignment {
|
|
53
|
+
id UUID @id @default(uuid()),
|
|
54
|
+
description String,
|
|
55
|
+
assignmentDate DateTime @default(now())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
relationship EmployeeTaskAssignment contains (Erp/Employee, Erp/TaskAssignment)
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
export const PlannerWorkflowInstructions = `
|
|
32
62
|
Another major construct in Agentlang is the 'workflow'. Workflows contains JSON "patterns" that perform CRUD operations on entities.
|
|
33
63
|
For example, here's is a workflow that creates a new instance of the Employee entity:
|
|
34
64
|
|
|
@@ -179,25 +209,7 @@ A pattern may execute asynchronously and its eventual result can be handled by p
|
|
|
179
209
|
If you are instructed that a particular event will be called asynchronously, always provide the patterns that follows in its '@then' clause. You must add the
|
|
180
210
|
'@then' clause only if an event's documentation or instruction explicitly requires to do so.
|
|
181
211
|
|
|
182
|
-
|
|
183
|
-
'Contains' relationship is for hierarchical data, as in a Library entity containing Books. 'Between' relationship is for graph-like data,
|
|
184
|
-
like two Profiles in a social media app is connected as friends. A 'between' relationship can be one of the following three types - 'one_one' (one-to-one),
|
|
185
|
-
'one_many' (one-to-many) and 'many_many' (many-to-many), which is the default.
|
|
186
|
-
|
|
187
|
-
The following example shows how additional profile data for an employee could be defined as a new entity and attached to the Employee entity as a between-relationship:
|
|
188
|
-
|
|
189
|
-
entity Profile {
|
|
190
|
-
id UUID @id @default(uuid()),
|
|
191
|
-
address String @optional,
|
|
192
|
-
photo URL @optional,
|
|
193
|
-
dateOfBirth DateTime @optional
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
relationship EmployeeProfile between (Erp/Employee, Erp/Profile) @one_one
|
|
197
|
-
|
|
198
|
-
The '@one_one' annotation means exactly one Employee and Profile can be related to each other via 'EmployeeProfile'.
|
|
199
|
-
|
|
200
|
-
Here's the 'CreateEmployee' workflow updated to create the Employee with the his/her Profile attached:
|
|
212
|
+
Earlier we discussed teh concept of 'between' relationship. Here's the 'CreateEmployee' workflow updated to create the Employee with the his/her Profile attached:
|
|
201
213
|
|
|
202
214
|
workflow CreateEmployee {
|
|
203
215
|
{Erp/Employee {firstName CreateEmployee.firstName,
|
|
@@ -214,17 +226,7 @@ The following pattern can be user to query an Employee along with his Profile:
|
|
|
214
226
|
{Erp/Employee {employeeId? "56392e13-0d9a-42f7-b556-0d7cd9468a24"},
|
|
215
227
|
Erp/EmployeeProfile {Erp/Profile? {}}}
|
|
216
228
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
entity TaskAssignment {
|
|
220
|
-
id UUID @id @default(uuid()),
|
|
221
|
-
description String,
|
|
222
|
-
assignmentDate DateTime @default(now())
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
relationship EmployeeTaskAssignment contains (Erp/Employee, Erp/TaskAssignment)
|
|
226
|
-
|
|
227
|
-
The following workflow shows how to assign a new task to an Employee:
|
|
229
|
+
We also discussed the concept of 'contains' relationship and showed an example of modelling employee-task-assignment using it. The following workflow shows how to assign a new task to an Employee:
|
|
228
230
|
|
|
229
231
|
workflow AssignNewTask {
|
|
230
232
|
{Erp/Employee {employeeId? AssignNewTask.employeeId},
|
|
@@ -264,6 +266,10 @@ As an example, if the user request is "send an email to employee 101 with this m
|
|
|
264
266
|
{email {to emp.email, body "please call me as soon as possible"}}]
|
|
265
267
|
|
|
266
268
|
You MUST separate each pattern in the array with a semi-colon (;) and never use a comma (,) for this purpose.
|
|
269
|
+
`;
|
|
270
|
+
|
|
271
|
+
export const PlannerInstructions = `${PlannerDataModelInstructions}
|
|
272
|
+
${PlannerWorkflowInstructions}
|
|
267
273
|
|
|
268
274
|
Now consider the following module definition and generate appropriate patterns in response to the user instructions. You must return only valid patterns or workflows,
|
|
269
275
|
no other descriptive text or comments are needed.
|
package/src/runtime/docs.ts
CHANGED
|
@@ -44,7 +44,7 @@ async function getPDFParse() {
|
|
|
44
44
|
return PDFParse;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
async function parsePdfBuffer(buffer: Uint8Array): Promise<string> {
|
|
47
|
+
export async function parsePdfBuffer(buffer: Uint8Array): Promise<string> {
|
|
48
48
|
try {
|
|
49
49
|
const PDFParseClass = await getPDFParse();
|
|
50
50
|
const parser = new PDFParseClass({
|
|
@@ -166,3 +166,19 @@ async function fetchTextFile(path: string): Promise<string> {
|
|
|
166
166
|
registerDocFetcher('http', httpFetcher);
|
|
167
167
|
registerDocFetcher('https', httpFetcher);
|
|
168
168
|
registerDocFetcher('file', fetchFile);
|
|
169
|
+
|
|
170
|
+
async function documentServiceFetcher(url: string): Promise<string | undefined> {
|
|
171
|
+
try {
|
|
172
|
+
const { documentFetcher } = await import('./services/documentFetcher.js');
|
|
173
|
+
const result = await documentFetcher.fetchDocument({
|
|
174
|
+
title: 'temp', // Title will be updated by handleDocEvent
|
|
175
|
+
url: url,
|
|
176
|
+
});
|
|
177
|
+
return result?.content;
|
|
178
|
+
} catch (error: any) {
|
|
179
|
+
logger.error(`Failed to fetch from document service ${url}: ${error.message}`);
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
registerDocFetcher('document-service', documentServiceFetcher);
|
|
@@ -87,6 +87,7 @@ import {
|
|
|
87
87
|
AgentInstance,
|
|
88
88
|
findAgentByName,
|
|
89
89
|
normalizeGeneratedCode,
|
|
90
|
+
saveFlowStepResult,
|
|
90
91
|
} from './modules/ai.js';
|
|
91
92
|
import { logger } from './logger.js';
|
|
92
93
|
import {
|
|
@@ -103,7 +104,7 @@ import {
|
|
|
103
104
|
flushMonitoringData,
|
|
104
105
|
triggerTimer,
|
|
105
106
|
} from './modules/core.js';
|
|
106
|
-
import { invokeModuleFn } from './jsmodules.js';
|
|
107
|
+
import { getModuleDef, invokeModuleFn } from './jsmodules.js';
|
|
107
108
|
import { invokeOpenApiEvent, isOpenApiEventInstance } from './openapi.js';
|
|
108
109
|
import { fetchDoc } from './docs.js';
|
|
109
110
|
import { FlowSpec, FlowStep, getAgentFlow } from './agents/flows.js';
|
|
@@ -111,6 +112,7 @@ import { isMonitoringEnabled } from './state.js';
|
|
|
111
112
|
import { Monitor, MonitorEntry } from './monitor.js';
|
|
112
113
|
import { detailedDiff } from 'deep-object-diff';
|
|
113
114
|
import { callMcpTool, mcpClientNameFromToolEvent } from './mcpclient.js';
|
|
115
|
+
import { isNodeEnv } from '../utils/runtime.js';
|
|
114
116
|
|
|
115
117
|
export type Result = any;
|
|
116
118
|
|
|
@@ -441,6 +443,11 @@ export class Environment extends Instance {
|
|
|
441
443
|
}
|
|
442
444
|
}
|
|
443
445
|
|
|
446
|
+
softSuspend(): string {
|
|
447
|
+
this.suspensionId = this.preGeneratedSuspensionId;
|
|
448
|
+
return this.suspensionId;
|
|
449
|
+
}
|
|
450
|
+
|
|
444
451
|
releaseSuspension(): Environment {
|
|
445
452
|
this.suspensionId = undefined;
|
|
446
453
|
this.preGeneratedSuspensionId = crypto.randomUUID();
|
|
@@ -1752,7 +1759,39 @@ export function isMcpEventInstance(inst: Instance): boolean {
|
|
|
1752
1759
|
}
|
|
1753
1760
|
|
|
1754
1761
|
async function handleDocEvent(inst: Instance, env: Environment): Promise<void> {
|
|
1755
|
-
const
|
|
1762
|
+
const url = inst.lookup('url');
|
|
1763
|
+
if (typeof url === 'string' && url.startsWith('s3://')) {
|
|
1764
|
+
if (!isNodeEnv) {
|
|
1765
|
+
throw new Error('Document fetching is only available in Node.js environment');
|
|
1766
|
+
}
|
|
1767
|
+
const title = inst.lookup('title');
|
|
1768
|
+
const retrievalConfig = inst.lookup('retrievalConfig');
|
|
1769
|
+
const embeddingConfig = inst.lookup('embeddingConfig');
|
|
1770
|
+
const { documentFetcher } = await import('./services/documentFetcher.js');
|
|
1771
|
+
await documentFetcher.fetchDocument({
|
|
1772
|
+
title,
|
|
1773
|
+
url,
|
|
1774
|
+
retrievalConfig,
|
|
1775
|
+
embeddingConfig,
|
|
1776
|
+
});
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
if (typeof url === 'string' && url.startsWith('document-service://')) {
|
|
1781
|
+
const title = inst.lookup('title');
|
|
1782
|
+
const retrievalConfig = inst.lookup('retrievalConfig');
|
|
1783
|
+
const embeddingConfig = inst.lookup('embeddingConfig');
|
|
1784
|
+
const { documentFetcher } = await import('./services/documentFetcher.js');
|
|
1785
|
+
await documentFetcher.fetchDocument({
|
|
1786
|
+
title,
|
|
1787
|
+
url,
|
|
1788
|
+
retrievalConfig,
|
|
1789
|
+
embeddingConfig,
|
|
1790
|
+
});
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
const s = await fetchDoc(url);
|
|
1756
1795
|
if (s) {
|
|
1757
1796
|
const title = inst.lookup('title');
|
|
1758
1797
|
const doc = makeInstance(
|
|
@@ -1998,7 +2037,7 @@ async function agentInvoke(agent: AgentInstance, msg: string, env: Environment):
|
|
|
1998
2037
|
isWf = true;
|
|
1999
2038
|
}
|
|
2000
2039
|
if (isWf) {
|
|
2001
|
-
const wf = await parseWorkflow(rs);
|
|
2040
|
+
const wf = await parseWorkflow(normalizeGeneratedCode(rs));
|
|
2002
2041
|
if (stmtsExec) {
|
|
2003
2042
|
await stmtsExec(wf.statements, env);
|
|
2004
2043
|
} else {
|
|
@@ -2010,7 +2049,9 @@ async function agentInvoke(agent: AgentInstance, msg: string, env: Environment):
|
|
|
2010
2049
|
const r = await stmtsExec([stmt], env);
|
|
2011
2050
|
env.setLastResult(r);
|
|
2012
2051
|
} else {
|
|
2013
|
-
env.setLastResult(
|
|
2052
|
+
env.setLastResult(
|
|
2053
|
+
await parseAndEvaluateStatement(normalizeGeneratedCode(rs), undefined, env)
|
|
2054
|
+
);
|
|
2014
2055
|
}
|
|
2015
2056
|
}
|
|
2016
2057
|
agent.maybeAddScratchData(env);
|
|
@@ -2139,7 +2180,7 @@ export async function restartFlow(
|
|
|
2139
2180
|
const rootAgent: AgentInstance = await findAgentByName(agentName, env);
|
|
2140
2181
|
const flow = getAgentFlow(agentName, rootAgent.moduleName);
|
|
2141
2182
|
if (flow) {
|
|
2142
|
-
const newCtx = `${ctx}\
|
|
2183
|
+
const newCtx = `${ctx}\nRestart the flow at ${step} using the following user-input as additional guidance:\n${userData}\n`;
|
|
2143
2184
|
env.setScratchPad(JSON.parse(spad));
|
|
2144
2185
|
await iterateOnFlow(flow, rootAgent, newCtx, env);
|
|
2145
2186
|
}
|
|
@@ -2167,7 +2208,8 @@ async function iterateOnFlow(
|
|
|
2167
2208
|
let needAgentProcessing = preprocResult.needAgentProcessing;
|
|
2168
2209
|
let context = initContext;
|
|
2169
2210
|
let stepc = 0;
|
|
2170
|
-
const
|
|
2211
|
+
const chatId = env.getActiveEventInstance()?.lookup('chatId');
|
|
2212
|
+
const iterId = chatId || crypto.randomUUID();
|
|
2171
2213
|
console.debug(`Starting iteration ${iterId} on flow: ${flow}`);
|
|
2172
2214
|
const executedSteps = new Set<string>();
|
|
2173
2215
|
const monitoringEnabled = isMonitoringEnabled();
|
|
@@ -2215,6 +2257,12 @@ async function iterateOnFlow(
|
|
|
2215
2257
|
`\n----> Completed execution of step ${step}, iteration id ${iterId} with result:\n${rs}`
|
|
2216
2258
|
);
|
|
2217
2259
|
context = `${context}\n${step} --> ${rs}\n`;
|
|
2260
|
+
if (chatId) {
|
|
2261
|
+
const suspEnv = new Environment(env.name, env);
|
|
2262
|
+
suspEnv.softSuspend();
|
|
2263
|
+
await saveFlowSuspension(rootAgent, context, step, suspEnv);
|
|
2264
|
+
await saveFlowStepResult(chatId, step, rs, suspEnv.getSuspensionId(), env);
|
|
2265
|
+
}
|
|
2218
2266
|
if (isfxc) {
|
|
2219
2267
|
preprocResult = await preprocessStep(rs, rootModuleName, env);
|
|
2220
2268
|
} else {
|
|
@@ -2462,10 +2510,19 @@ async function followReference(env: Environment, s: string): Promise<Result> {
|
|
|
2462
2510
|
for (let i = 0; i < refs.length; ++i) {
|
|
2463
2511
|
const r: string = refs[i];
|
|
2464
2512
|
const v: Result | undefined = await getRef(r, src, env);
|
|
2465
|
-
if (v === undefined)
|
|
2513
|
+
if (v === undefined || v === null) {
|
|
2514
|
+
result = EmptyResult;
|
|
2515
|
+
break;
|
|
2516
|
+
}
|
|
2466
2517
|
result = v;
|
|
2467
2518
|
src = result;
|
|
2468
2519
|
}
|
|
2520
|
+
if (result === EmptyResult) {
|
|
2521
|
+
result = getModuleDef(s);
|
|
2522
|
+
if (result === undefined) {
|
|
2523
|
+
result = EmptyResult;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2469
2526
|
return result;
|
|
2470
2527
|
}
|
|
2471
2528
|
|
package/src/runtime/jsmodules.ts
CHANGED
|
@@ -303,7 +303,7 @@ export async function invokeModuleFn(
|
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
export function
|
|
306
|
+
export function getModuleDef(fqFnName: string): any {
|
|
307
307
|
const refs: string[] = splitRefs(fqFnName);
|
|
308
308
|
const m = importedModules.get(refs[0]);
|
|
309
309
|
if (m !== undefined) {
|
|
@@ -311,4 +311,6 @@ export function getModuleFn(fqFnName: string): Function | undefined {
|
|
|
311
311
|
} else return undefined;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
export const getModuleFn = getModuleDef;
|
|
315
|
+
|
|
314
316
|
setModuleFnFetcher(getModuleFn);
|
package/src/runtime/loader.ts
CHANGED
|
@@ -819,7 +819,8 @@ async function addAgentDefinition(
|
|
|
819
819
|
}
|
|
820
820
|
const ov = v;
|
|
821
821
|
if (apdef.value.id || apdef.value.ref || apdef.value.array) {
|
|
822
|
-
|
|
822
|
+
if (!(apdef.name === 'instruction' || apdef.name === 'role' || apdef.name === 'llm'))
|
|
823
|
+
v = `"${v}"`;
|
|
823
824
|
} else if (apdef.value.str) {
|
|
824
825
|
v = `"${escapeSpecialChars(v)}"`;
|
|
825
826
|
}
|
|
@@ -837,7 +838,7 @@ async function addAgentDefinition(
|
|
|
837
838
|
// Create a copy of attrsStrs for the database operation
|
|
838
839
|
const dbAttrsStrs = [...attrsStrs];
|
|
839
840
|
// Only add llm to database attributes if we have one
|
|
840
|
-
if (llmName) {
|
|
841
|
+
if (llmName && createDefaultLLM) {
|
|
841
842
|
dbAttrsStrs.push(`llm "${llmName}"`);
|
|
842
843
|
}
|
|
843
844
|
|
package/src/runtime/module.ts
CHANGED
|
@@ -3731,6 +3731,10 @@ export function newInstanceAttributes(): InstanceAttributes {
|
|
|
3731
3731
|
return new Map<string, any>();
|
|
3732
3732
|
}
|
|
3733
3733
|
|
|
3734
|
+
export function objectToInstanceAttributes(obj: any): InstanceAttributes {
|
|
3735
|
+
return new Map(Object.entries(obj));
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3734
3738
|
const EmptyInstanceAttributes: InstanceAttributes = newInstanceAttributes();
|
|
3735
3739
|
|
|
3736
3740
|
export class Instance {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DefaultModuleName,
|
|
2
3
|
escapeSpecialChars,
|
|
3
4
|
isFqName,
|
|
4
5
|
isString,
|
|
@@ -29,9 +30,10 @@ import {
|
|
|
29
30
|
isModule,
|
|
30
31
|
makeInstance,
|
|
31
32
|
newInstanceAttributes,
|
|
33
|
+
objectToInstanceAttributes,
|
|
32
34
|
Record,
|
|
33
|
-
Retry,
|
|
34
35
|
resolveDocumentAliases,
|
|
36
|
+
Retry,
|
|
35
37
|
} from '../module.js';
|
|
36
38
|
import { provider } from '../agents/registry.js';
|
|
37
39
|
import {
|
|
@@ -66,6 +68,8 @@ import { FlowStep } from '../agents/flows.js';
|
|
|
66
68
|
import Handlebars from 'handlebars';
|
|
67
69
|
import { Statement } from '../../language/generated/ast.js';
|
|
68
70
|
import { isMonitoringEnabled, TtlCache } from '../state.js';
|
|
71
|
+
import { isNodeEnv } from '../../utils/runtime.js';
|
|
72
|
+
import { getFileSystem } from '../../utils/fs-utils.js';
|
|
69
73
|
|
|
70
74
|
export const CoreAIModuleName = makeCoreModuleName('ai');
|
|
71
75
|
export const AgentEntityName = 'Agent';
|
|
@@ -74,27 +78,6 @@ export const AgentLearnerType = 'learner';
|
|
|
74
78
|
|
|
75
79
|
const AgentEvalType = 'eval';
|
|
76
80
|
|
|
77
|
-
function buildEmbeddingConfig(): object {
|
|
78
|
-
const config: any = {
|
|
79
|
-
provider: process.env.AGENTLANG_EMBEDDING_PROVIDER || 'openai',
|
|
80
|
-
model: process.env.AGENTLANG_EMBEDDING_MODEL || 'text-embedding-3-small',
|
|
81
|
-
chunkSize: 1000,
|
|
82
|
-
chunkOverlap: 200,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
if (process.env.AGENTLANG_EMBEDDING_CHUNKSIZE) {
|
|
86
|
-
config.chunkSize = parseInt(process.env.AGENTLANG_EMBEDDING_CHUNKSIZE, 1000);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (process.env.AGENTLANG_EMBEDDING_CHUNKOVERLAP) {
|
|
90
|
-
config.chunkOverlap = parseInt(process.env.AGENTLANG_EMBEDDING_CHUNKOVERLAP, 200);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return config;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const embeddingConfig = JSON.stringify(buildEmbeddingConfig());
|
|
97
|
-
|
|
98
81
|
export default `module ${CoreAIModuleName}
|
|
99
82
|
|
|
100
83
|
import "./modules/ai.js" @as ai
|
|
@@ -139,12 +122,24 @@ workflow saveAgentChatSession {
|
|
|
139
122
|
entity Document {
|
|
140
123
|
title String @id,
|
|
141
124
|
content String,
|
|
142
|
-
|
|
125
|
+
embeddingConfig Map @optional,
|
|
126
|
+
@meta {"fullTextSearch": "*"}
|
|
143
127
|
}
|
|
144
128
|
|
|
145
129
|
event doc {
|
|
146
130
|
title String,
|
|
147
|
-
url String
|
|
131
|
+
url String,
|
|
132
|
+
config Map @optional,
|
|
133
|
+
retrievalConfig Map @optional,
|
|
134
|
+
embeddingConfig Map @optional
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
workflow processDoc {
|
|
138
|
+
// Fetch document from URL and create Document entity
|
|
139
|
+
// Supports: local paths, http/https URLs, s3:// URLs
|
|
140
|
+
// S3 config can be provided via retrievalConfig or env vars
|
|
141
|
+
// Embedding config can customize chunking behavior
|
|
142
|
+
await ai.fetchAndCreateDocument(doc.title, doc.url, doc.retrievalConfig, doc.embeddingConfig)
|
|
148
143
|
}
|
|
149
144
|
|
|
150
145
|
entity Directive {
|
|
@@ -193,6 +188,27 @@ entity AgentLearningResult {
|
|
|
193
188
|
workflow agentLearning {
|
|
194
189
|
await ai.processAgentLearning(agentLearning.agentModuleName, agentLearning.agentName, agentLearning.instruction)
|
|
195
190
|
}
|
|
191
|
+
|
|
192
|
+
entity AgentFlowStep {
|
|
193
|
+
id UUID @id @default(uuid()),
|
|
194
|
+
chatId String @indexed,
|
|
195
|
+
step String @indexed,
|
|
196
|
+
result String,
|
|
197
|
+
suspensionId String
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@public event restartFlow {
|
|
201
|
+
chatId String,
|
|
202
|
+
step String,
|
|
203
|
+
userInput String
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
workflow restartFlow {
|
|
207
|
+
await ai.loadFlowStep(restartFlow.chatId, restartFlow.step) @as fs;
|
|
208
|
+
if (fs) {
|
|
209
|
+
{${DefaultModuleName}/restartSuspension {id fs.suspensionId, data restartFlow.userInput}}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
196
212
|
`;
|
|
197
213
|
|
|
198
214
|
enum AgentCacheType {
|
|
@@ -622,7 +638,7 @@ export class AgentInstance {
|
|
|
622
638
|
Only return a pure JSON object with no extra text, annotations etc.`;
|
|
623
639
|
}
|
|
624
640
|
const spad = env.getScratchPad();
|
|
625
|
-
if (spad !== undefined) {
|
|
641
|
+
if (spad !== undefined && Object.keys(spad).length > 0) {
|
|
626
642
|
if (finalInstruction.indexOf('{{') > 0) {
|
|
627
643
|
return AgentInstance.maybeRewriteTemplatePatterns(spad, finalInstruction, env);
|
|
628
644
|
} else {
|
|
@@ -1282,17 +1298,38 @@ function processScenarioResponse(resp: string): string {
|
|
|
1282
1298
|
return resp;
|
|
1283
1299
|
}
|
|
1284
1300
|
|
|
1301
|
+
type ExtractedCode = {
|
|
1302
|
+
language: string | null;
|
|
1303
|
+
code: string;
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
export function extractFencedCodeBlocks(markdown: string): ExtractedCode[] {
|
|
1307
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
1308
|
+
const blocks: ExtractedCode[] = [];
|
|
1309
|
+
let match;
|
|
1310
|
+
|
|
1311
|
+
while ((match = codeBlockRegex.exec(markdown)) !== null) {
|
|
1312
|
+
blocks.push({
|
|
1313
|
+
language: match[1] || null,
|
|
1314
|
+
code: match[2],
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
return blocks;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1285
1321
|
export function normalizeGeneratedCode(code: string | undefined): string {
|
|
1286
1322
|
if (code !== undefined) {
|
|
1287
|
-
|
|
1288
|
-
if (
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1323
|
+
const blocks = extractFencedCodeBlocks(code);
|
|
1324
|
+
if (blocks.length > 0) {
|
|
1325
|
+
return blocks
|
|
1326
|
+
.map((v: ExtractedCode) => {
|
|
1327
|
+
return v.code;
|
|
1328
|
+
})
|
|
1329
|
+
.join('\n\n');
|
|
1330
|
+
} else {
|
|
1331
|
+
return code;
|
|
1294
1332
|
}
|
|
1295
|
-
return s;
|
|
1296
1333
|
} else {
|
|
1297
1334
|
return '';
|
|
1298
1335
|
}
|
|
@@ -1392,3 +1429,203 @@ export async function processAgentLearning(
|
|
|
1392
1429
|
await parseAndInternAgentLearning(moduleName, agentName, learning, env);
|
|
1393
1430
|
return { agentLearning: { result: learning } };
|
|
1394
1431
|
}
|
|
1432
|
+
|
|
1433
|
+
const LocalAgentgFlow = true;
|
|
1434
|
+
const LocalFlowStepsRootDirName = 'flows';
|
|
1435
|
+
const AgentFlowStep = 'AgentFlowStep';
|
|
1436
|
+
|
|
1437
|
+
export async function saveFlowStepResultLocally(
|
|
1438
|
+
chatId: string,
|
|
1439
|
+
step: string,
|
|
1440
|
+
result: string,
|
|
1441
|
+
suspensionId: string
|
|
1442
|
+
): Promise<Instance | undefined> {
|
|
1443
|
+
const fs = await getFileSystem();
|
|
1444
|
+
const attrs = newInstanceAttributes()
|
|
1445
|
+
.set('chatId', chatId)
|
|
1446
|
+
.set('step', step)
|
|
1447
|
+
.set('result', result)
|
|
1448
|
+
.set('suspensionId', suspensionId);
|
|
1449
|
+
const inst = makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
|
|
1450
|
+
const rootDirName = LocalFlowStepsRootDirName;
|
|
1451
|
+
if (!(await fs.exists(rootDirName))) {
|
|
1452
|
+
await fs.mkdir(rootDirName);
|
|
1453
|
+
}
|
|
1454
|
+
const dirName = `${rootDirName}/${chatId}`;
|
|
1455
|
+
if (!(await fs.exists(dirName))) {
|
|
1456
|
+
await fs.mkdir(dirName);
|
|
1457
|
+
}
|
|
1458
|
+
const fileName = `${dirName}/${step}`;
|
|
1459
|
+
if (await fs.exists(fileName)) {
|
|
1460
|
+
await fs.unlink(fileName);
|
|
1461
|
+
}
|
|
1462
|
+
await fs.writeFile(fileName, JSON.stringify(inst.attributesAsObject(true)));
|
|
1463
|
+
return inst;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
export async function saveFlowStepResult(
|
|
1467
|
+
chatId: string,
|
|
1468
|
+
step: string,
|
|
1469
|
+
result: string,
|
|
1470
|
+
suspensionId: string,
|
|
1471
|
+
env: Environment
|
|
1472
|
+
): Promise<Instance | undefined> {
|
|
1473
|
+
if (LocalAgentgFlow) {
|
|
1474
|
+
return await saveFlowStepResultLocally(chatId, step, result, suspensionId);
|
|
1475
|
+
} else {
|
|
1476
|
+
const t = `${CoreAIModuleName}/${AgentFlowStep}`;
|
|
1477
|
+
try {
|
|
1478
|
+
await parseAndEvaluateStatement(
|
|
1479
|
+
`purge {${t} {chatId? "${chatId}", step? "${step}"}}`,
|
|
1480
|
+
undefined,
|
|
1481
|
+
env
|
|
1482
|
+
);
|
|
1483
|
+
const inst: Instance = await parseAndEvaluateStatement(
|
|
1484
|
+
`{${t} {chatId "${chatId}", step "${step}", result "${escapeSpecialChars(result)}", suspensionId "${suspensionId}"}}`,
|
|
1485
|
+
undefined,
|
|
1486
|
+
env
|
|
1487
|
+
);
|
|
1488
|
+
if (isInstanceOfType(inst, t)) return inst;
|
|
1489
|
+
else return undefined;
|
|
1490
|
+
} catch (reason: any) {
|
|
1491
|
+
logger.error(`failed to save flow result for step ${step} - ${reason}`);
|
|
1492
|
+
return undefined;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
export async function loadLocalFlowStepResults(chatId: string): Promise<Instance[]> {
|
|
1498
|
+
const fs = await getFileSystem();
|
|
1499
|
+
const dirName = `${LocalFlowStepsRootDirName}/${chatId}`;
|
|
1500
|
+
if (await fs.exists(dirName)) {
|
|
1501
|
+
const fileNames = await fs.readdir(dirName);
|
|
1502
|
+
const result: Instance[] = new Array<Instance>();
|
|
1503
|
+
for (let i = 0; i < fileNames.length; ++i) {
|
|
1504
|
+
const fileName = fileNames[i];
|
|
1505
|
+
const attrs = objectToInstanceAttributes(
|
|
1506
|
+
JSON.parse(await fs.readFile(`${dirName}/${fileName}`))
|
|
1507
|
+
);
|
|
1508
|
+
result.push(makeInstance(CoreAIModuleName, AgentFlowStep, attrs));
|
|
1509
|
+
}
|
|
1510
|
+
return result;
|
|
1511
|
+
} else {
|
|
1512
|
+
return [];
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
export async function loadFlowStepResults(chatId: string): Promise<Instance[]> {
|
|
1517
|
+
if (LocalAgentgFlow) {
|
|
1518
|
+
return await loadLocalFlowStepResults(chatId);
|
|
1519
|
+
} else {
|
|
1520
|
+
try {
|
|
1521
|
+
return await parseAndEvaluateStatement(`{${CoreAIModuleName}/${AgentFlowStep} {
|
|
1522
|
+
chatId? "${chatId}"}}`);
|
|
1523
|
+
} catch (reason: any) {
|
|
1524
|
+
logger.error(`failed to query flow-steps for ${chatId} - ${reason}`);
|
|
1525
|
+
return [];
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
export async function loadLocalFlowStep(
|
|
1531
|
+
chatId: string,
|
|
1532
|
+
step: string
|
|
1533
|
+
): Promise<Instance | undefined> {
|
|
1534
|
+
const fs = await getFileSystem();
|
|
1535
|
+
const dirName = `${LocalFlowStepsRootDirName}/${chatId}`;
|
|
1536
|
+
if (await fs.exists(dirName)) {
|
|
1537
|
+
const fileNames = await fs.readdir(dirName);
|
|
1538
|
+
for (let i = 0; i < fileNames.length; ++i) {
|
|
1539
|
+
const fileName = fileNames[i];
|
|
1540
|
+
if (fileName === step) {
|
|
1541
|
+
const attrs = objectToInstanceAttributes(
|
|
1542
|
+
JSON.parse(await fs.readFile(`${dirName}/${fileName}`))
|
|
1543
|
+
);
|
|
1544
|
+
return makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return undefined;
|
|
1548
|
+
} else {
|
|
1549
|
+
return undefined;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
export async function loadFlowStep(chatId: string, step: string): Promise<Instance | undefined> {
|
|
1554
|
+
if (LocalAgentgFlow) {
|
|
1555
|
+
return await loadLocalFlowStep(chatId, step);
|
|
1556
|
+
} else {
|
|
1557
|
+
try {
|
|
1558
|
+
const insts: Instance[] =
|
|
1559
|
+
await parseAndEvaluateStatement(`{${CoreAIModuleName}/${AgentFlowStep} {
|
|
1560
|
+
chatId? "${chatId}", step? "${step}"}}`);
|
|
1561
|
+
if (insts.length > 0) {
|
|
1562
|
+
return insts[0];
|
|
1563
|
+
} else {
|
|
1564
|
+
return undefined;
|
|
1565
|
+
}
|
|
1566
|
+
} catch (reason: any) {
|
|
1567
|
+
logger.error(`failed to lookup step ${step} for flow ${chatId} - ${reason}`);
|
|
1568
|
+
return undefined;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
export async function fetchAndCreateDocument(
|
|
1574
|
+
title: string,
|
|
1575
|
+
url: string,
|
|
1576
|
+
retrievalConfig: Map<string, any> | undefined,
|
|
1577
|
+
embeddingConfig: Map<string, any> | undefined,
|
|
1578
|
+
_env: Environment
|
|
1579
|
+
): Promise<any> {
|
|
1580
|
+
if (!isNodeEnv) {
|
|
1581
|
+
throw new Error('Document fetching is only available in Node.js environment');
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
const { documentFetcher } = await import('../services/documentFetcher.js');
|
|
1585
|
+
|
|
1586
|
+
const config: any = {
|
|
1587
|
+
title,
|
|
1588
|
+
url,
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
if (retrievalConfig) {
|
|
1592
|
+
const provider = retrievalConfig.get('provider');
|
|
1593
|
+
const providerConfig = retrievalConfig.get('config');
|
|
1594
|
+
|
|
1595
|
+
config.retrievalConfig = {
|
|
1596
|
+
provider: provider || 's3',
|
|
1597
|
+
config: providerConfig
|
|
1598
|
+
? {
|
|
1599
|
+
region: providerConfig.get('region'),
|
|
1600
|
+
endpoint: providerConfig.get('endpoint'),
|
|
1601
|
+
accessKeyId: providerConfig.get('accessKeyId'),
|
|
1602
|
+
secretAccessKey: providerConfig.get('secretAccessKey'),
|
|
1603
|
+
forcePathStyle: providerConfig.get('forcePathStyle'),
|
|
1604
|
+
}
|
|
1605
|
+
: {},
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
if (embeddingConfig) {
|
|
1610
|
+
config.embeddingConfig = {
|
|
1611
|
+
provider: embeddingConfig.get('provider'),
|
|
1612
|
+
model: embeddingConfig.get('model'),
|
|
1613
|
+
chunkSize: embeddingConfig.get('chunkSize'),
|
|
1614
|
+
chunkOverlap: embeddingConfig.get('chunkOverlap'),
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
const document = await documentFetcher.fetchDocument(config);
|
|
1619
|
+
|
|
1620
|
+
if (document) {
|
|
1621
|
+
return {
|
|
1622
|
+
document: {
|
|
1623
|
+
title: document.title,
|
|
1624
|
+
url: document.url,
|
|
1625
|
+
format: document.format,
|
|
1626
|
+
},
|
|
1627
|
+
};
|
|
1628
|
+
} else {
|
|
1629
|
+
throw new Error(`Failed to fetch document: ${title} from ${url}`);
|
|
1630
|
+
}
|
|
1631
|
+
}
|