@fluidframework/ai-collab 2.10.0-306579
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/.eslintrc.cjs +26 -0
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +280 -0
- package/alpha.d.ts +11 -0
- package/api-extractor/api-extractor-lint-alpha.cjs.json +5 -0
- package/api-extractor/api-extractor-lint-alpha.esm.json +5 -0
- package/api-extractor/api-extractor-lint-bundle.json +5 -0
- package/api-extractor/api-extractor-lint-index.cjs.json +5 -0
- package/api-extractor/api-extractor-lint-index.esm.json +5 -0
- package/api-extractor/api-extractor-lint-public.cjs.json +5 -0
- package/api-extractor/api-extractor-lint-public.esm.json +5 -0
- package/api-extractor-lint.json +4 -0
- package/api-extractor.json +4 -0
- package/api-report/ai-collab.alpha.api.md +164 -0
- package/api-report/ai-collab.beta.api.md +7 -0
- package/api-report/ai-collab.public.api.md +7 -0
- package/biome.jsonc +4 -0
- package/dist/aiCollab.d.ts +65 -0
- package/dist/aiCollab.d.ts.map +1 -0
- package/dist/aiCollab.js +81 -0
- package/dist/aiCollab.js.map +1 -0
- package/dist/aiCollabApi.d.ts +173 -0
- package/dist/aiCollabApi.d.ts.map +1 -0
- package/dist/aiCollabApi.js +7 -0
- package/dist/aiCollabApi.js.map +1 -0
- package/dist/alpha.d.ts +41 -0
- package/dist/explicit-strategy/agentEditReducer.d.ts +12 -0
- package/dist/explicit-strategy/agentEditReducer.d.ts.map +1 -0
- package/dist/explicit-strategy/agentEditReducer.js +394 -0
- package/dist/explicit-strategy/agentEditReducer.js.map +1 -0
- package/dist/explicit-strategy/agentEditTypes.d.ts +158 -0
- package/dist/explicit-strategy/agentEditTypes.d.ts.map +1 -0
- package/dist/explicit-strategy/agentEditTypes.js +50 -0
- package/dist/explicit-strategy/agentEditTypes.js.map +1 -0
- package/dist/explicit-strategy/idGenerator.d.ts +22 -0
- package/dist/explicit-strategy/idGenerator.d.ts.map +1 -0
- package/dist/explicit-strategy/idGenerator.js +74 -0
- package/dist/explicit-strategy/idGenerator.js.map +1 -0
- package/dist/explicit-strategy/index.d.ts +51 -0
- package/dist/explicit-strategy/index.d.ts.map +1 -0
- package/dist/explicit-strategy/index.js +223 -0
- package/dist/explicit-strategy/index.js.map +1 -0
- package/dist/explicit-strategy/jsonTypes.d.ts +23 -0
- package/dist/explicit-strategy/jsonTypes.d.ts.map +1 -0
- package/dist/explicit-strategy/jsonTypes.js +7 -0
- package/dist/explicit-strategy/jsonTypes.js.map +1 -0
- package/dist/explicit-strategy/promptGeneration.d.ts +51 -0
- package/dist/explicit-strategy/promptGeneration.d.ts.map +1 -0
- package/dist/explicit-strategy/promptGeneration.js +218 -0
- package/dist/explicit-strategy/promptGeneration.js.map +1 -0
- package/dist/explicit-strategy/typeGeneration.d.ts +15 -0
- package/dist/explicit-strategy/typeGeneration.d.ts.map +1 -0
- package/dist/explicit-strategy/typeGeneration.js +264 -0
- package/dist/explicit-strategy/typeGeneration.js.map +1 -0
- package/dist/explicit-strategy/utils.d.ts +37 -0
- package/dist/explicit-strategy/utils.d.ts.map +1 -0
- package/dist/explicit-strategy/utils.js +47 -0
- package/dist/explicit-strategy/utils.js.map +1 -0
- package/dist/implicit-strategy/index.d.ts +8 -0
- package/dist/implicit-strategy/index.d.ts.map +1 -0
- package/dist/implicit-strategy/index.js +18 -0
- package/dist/implicit-strategy/index.js.map +1 -0
- package/dist/implicit-strategy/sharedTreeBranchManager.d.ts +63 -0
- package/dist/implicit-strategy/sharedTreeBranchManager.d.ts.map +1 -0
- package/dist/implicit-strategy/sharedTreeBranchManager.js +212 -0
- package/dist/implicit-strategy/sharedTreeBranchManager.js.map +1 -0
- package/dist/implicit-strategy/sharedTreeDiff.d.ts +102 -0
- package/dist/implicit-strategy/sharedTreeDiff.d.ts.map +1 -0
- package/dist/implicit-strategy/sharedTreeDiff.js +522 -0
- package/dist/implicit-strategy/sharedTreeDiff.js.map +1 -0
- package/dist/implicit-strategy/utils.d.ts +21 -0
- package/dist/implicit-strategy/utils.d.ts.map +1 -0
- package/dist/implicit-strategy/utils.js +49 -0
- package/dist/implicit-strategy/utils.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +3 -0
- package/dist/public.d.ts +19 -0
- package/eslintrc.cjs +11 -0
- package/internal.d.ts +11 -0
- package/lib/aiCollab.d.ts +65 -0
- package/lib/aiCollab.d.ts.map +1 -0
- package/lib/aiCollab.js +77 -0
- package/lib/aiCollab.js.map +1 -0
- package/lib/aiCollabApi.d.ts +173 -0
- package/lib/aiCollabApi.d.ts.map +1 -0
- package/lib/aiCollabApi.js +6 -0
- package/lib/aiCollabApi.js.map +1 -0
- package/lib/alpha.d.ts +41 -0
- package/lib/explicit-strategy/agentEditReducer.d.ts +12 -0
- package/lib/explicit-strategy/agentEditReducer.d.ts.map +1 -0
- package/lib/explicit-strategy/agentEditReducer.js +390 -0
- package/lib/explicit-strategy/agentEditReducer.js.map +1 -0
- package/lib/explicit-strategy/agentEditTypes.d.ts +158 -0
- package/lib/explicit-strategy/agentEditTypes.d.ts.map +1 -0
- package/lib/explicit-strategy/agentEditTypes.js +47 -0
- package/lib/explicit-strategy/agentEditTypes.js.map +1 -0
- package/lib/explicit-strategy/idGenerator.d.ts +22 -0
- package/lib/explicit-strategy/idGenerator.d.ts.map +1 -0
- package/lib/explicit-strategy/idGenerator.js +70 -0
- package/lib/explicit-strategy/idGenerator.js.map +1 -0
- package/lib/explicit-strategy/index.d.ts +51 -0
- package/lib/explicit-strategy/index.d.ts.map +1 -0
- package/lib/explicit-strategy/index.js +219 -0
- package/lib/explicit-strategy/index.js.map +1 -0
- package/lib/explicit-strategy/jsonTypes.d.ts +23 -0
- package/lib/explicit-strategy/jsonTypes.d.ts.map +1 -0
- package/lib/explicit-strategy/jsonTypes.js +6 -0
- package/lib/explicit-strategy/jsonTypes.js.map +1 -0
- package/lib/explicit-strategy/promptGeneration.d.ts +51 -0
- package/lib/explicit-strategy/promptGeneration.d.ts.map +1 -0
- package/lib/explicit-strategy/promptGeneration.js +208 -0
- package/lib/explicit-strategy/promptGeneration.js.map +1 -0
- package/lib/explicit-strategy/typeGeneration.d.ts +15 -0
- package/lib/explicit-strategy/typeGeneration.d.ts.map +1 -0
- package/lib/explicit-strategy/typeGeneration.js +260 -0
- package/lib/explicit-strategy/typeGeneration.js.map +1 -0
- package/lib/explicit-strategy/utils.d.ts +37 -0
- package/lib/explicit-strategy/utils.d.ts.map +1 -0
- package/lib/explicit-strategy/utils.js +41 -0
- package/lib/explicit-strategy/utils.js.map +1 -0
- package/lib/implicit-strategy/index.d.ts +8 -0
- package/lib/implicit-strategy/index.d.ts.map +1 -0
- package/lib/implicit-strategy/index.js +8 -0
- package/lib/implicit-strategy/index.js.map +1 -0
- package/lib/implicit-strategy/sharedTreeBranchManager.d.ts +63 -0
- package/lib/implicit-strategy/sharedTreeBranchManager.d.ts.map +1 -0
- package/lib/implicit-strategy/sharedTreeBranchManager.js +213 -0
- package/lib/implicit-strategy/sharedTreeBranchManager.js.map +1 -0
- package/lib/implicit-strategy/sharedTreeDiff.d.ts +102 -0
- package/lib/implicit-strategy/sharedTreeDiff.d.ts.map +1 -0
- package/lib/implicit-strategy/sharedTreeDiff.js +515 -0
- package/lib/implicit-strategy/sharedTreeDiff.js.map +1 -0
- package/lib/implicit-strategy/utils.d.ts +21 -0
- package/lib/implicit-strategy/utils.d.ts.map +1 -0
- package/lib/implicit-strategy/utils.js +43 -0
- package/lib/implicit-strategy/utils.js.map +1 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +15 -0
- package/lib/index.js.map +1 -0
- package/lib/public.d.ts +19 -0
- package/lib/tsdoc-metadata.json +11 -0
- package/mocharc.cjs +14 -0
- package/package.json +165 -0
- package/prettier.config.cjs +8 -0
- package/src/aiCollab.ts +86 -0
- package/src/aiCollabApi.ts +184 -0
- package/src/explicit-strategy/agentEditReducer.ts +498 -0
- package/src/explicit-strategy/agentEditTypes.ts +177 -0
- package/src/explicit-strategy/idGenerator.ts +90 -0
- package/src/explicit-strategy/index.ts +364 -0
- package/src/explicit-strategy/jsonTypes.ts +27 -0
- package/src/explicit-strategy/promptGeneration.ts +294 -0
- package/src/explicit-strategy/typeGeneration.ts +374 -0
- package/src/explicit-strategy/utils.ts +60 -0
- package/src/implicit-strategy/README.md +4 -0
- package/src/implicit-strategy/index.ts +21 -0
- package/src/implicit-strategy/sharedTreeBranchManager.ts +294 -0
- package/src/implicit-strategy/sharedTreeDiff.ts +735 -0
- package/src/implicit-strategy/utils.ts +54 -0
- package/src/index.ts +39 -0
- package/tsconfig.cjs.json +7 -0
- package/tsconfig.json +12 -0
- package/tsdoc.json +4 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assert, oob } from "@fluidframework/core-utils/internal";
|
|
7
|
+
import { Tree, NodeKind } from "@fluidframework/tree/internal";
|
|
8
|
+
import type {
|
|
9
|
+
TreeNode,
|
|
10
|
+
ImplicitFieldSchema,
|
|
11
|
+
TreeArrayNode,
|
|
12
|
+
TreeFieldFromImplicitField,
|
|
13
|
+
} from "@fluidframework/tree/internal";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Given a tree, generates a set of LLM-friendly, unique IDs for each node in the tree.
|
|
17
|
+
* @remarks The ability to uniquely and stably in the tree is important for the LLM and this library to create and distinguish between different types certain {@link TreeEdit}s.
|
|
18
|
+
*/
|
|
19
|
+
export class IdGenerator {
|
|
20
|
+
private readonly idCountMap = new Map<string, number>();
|
|
21
|
+
private readonly prefixMap = new Map<string, string>();
|
|
22
|
+
private readonly nodeToIdMap = new Map<TreeNode, string>();
|
|
23
|
+
private readonly idToNodeMap = new Map<string, TreeNode>();
|
|
24
|
+
|
|
25
|
+
public constructor() {}
|
|
26
|
+
|
|
27
|
+
public getOrCreateId(node: TreeNode): string {
|
|
28
|
+
const existingID = this.nodeToIdMap.get(node);
|
|
29
|
+
if (existingID !== undefined) {
|
|
30
|
+
return existingID;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const schema = Tree.schema(node).identifier;
|
|
34
|
+
const id = this.generateID(schema);
|
|
35
|
+
this.nodeToIdMap.set(node, id);
|
|
36
|
+
this.idToNodeMap.set(id, node);
|
|
37
|
+
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public getNode(id: string): TreeNode | undefined {
|
|
42
|
+
return this.idToNodeMap.get(id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public getId(node: TreeNode): string | undefined {
|
|
46
|
+
return this.nodeToIdMap.get(node);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public assignIds(node: TreeFieldFromImplicitField<ImplicitFieldSchema>): string | undefined {
|
|
50
|
+
if (typeof node === "object" && node !== null) {
|
|
51
|
+
const schema = Tree.schema(node as unknown as TreeNode);
|
|
52
|
+
if (schema.kind === NodeKind.Array) {
|
|
53
|
+
for (const element of node as unknown as TreeArrayNode) {
|
|
54
|
+
this.assignIds(element);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
// TODO: SharedTree Team needs to either publish TreeNode as a class to use .instanceof() or a typeguard.
|
|
58
|
+
// Uncomment this assertion back once we have a typeguard ready.
|
|
59
|
+
// assert(isTreeNode(node), "Non-TreeNode value in tree.");
|
|
60
|
+
const objId = this.getOrCreateId(node as TreeNode);
|
|
61
|
+
for (const key of Object.keys(node)) {
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
63
|
+
this.assignIds((node as unknown as any)[key]);
|
|
64
|
+
}
|
|
65
|
+
return objId;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private generateID(schema: string): string {
|
|
72
|
+
const segments = schema.split(".");
|
|
73
|
+
|
|
74
|
+
// If there's no period, the schema itself is the last segment
|
|
75
|
+
const lastSegment = segments[segments.length - 1] ?? oob();
|
|
76
|
+
const prefix = segments.length > 1 ? segments.slice(0, -1).join(".") : "";
|
|
77
|
+
|
|
78
|
+
// Check if the last segment already exists with a different prefix
|
|
79
|
+
assert(
|
|
80
|
+
!this.prefixMap.has(lastSegment) || this.prefixMap.get(lastSegment) === prefix,
|
|
81
|
+
"Different scopes not supported yet.",
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
this.prefixMap.set(lastSegment, prefix);
|
|
85
|
+
const count = this.idCountMap.get(lastSegment) ?? 1;
|
|
86
|
+
this.idCountMap.set(lastSegment, count + 1);
|
|
87
|
+
|
|
88
|
+
return `${lastSegment}${count}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
getSimpleSchema,
|
|
8
|
+
Tree,
|
|
9
|
+
type SimpleTreeSchema,
|
|
10
|
+
type TreeNode,
|
|
11
|
+
} from "@fluidframework/tree/internal";
|
|
12
|
+
// eslint-disable-next-line import/no-internal-modules
|
|
13
|
+
import { zodResponseFormat } from "openai/helpers/zod";
|
|
14
|
+
import type {
|
|
15
|
+
ChatCompletionCreateParams,
|
|
16
|
+
// eslint-disable-next-line import/no-internal-modules
|
|
17
|
+
} from "openai/resources/index.mjs";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
|
|
20
|
+
import type { OpenAiClientOptions, TokenLimits, TokenUsage } from "../aiCollabApi.js";
|
|
21
|
+
|
|
22
|
+
import { applyAgentEdit } from "./agentEditReducer.js";
|
|
23
|
+
import type { EditWrapper, TreeEdit } from "./agentEditTypes.js";
|
|
24
|
+
import { IdGenerator } from "./idGenerator.js";
|
|
25
|
+
import {
|
|
26
|
+
getEditingSystemPrompt,
|
|
27
|
+
getPlanningSystemPrompt,
|
|
28
|
+
getReviewSystemPrompt,
|
|
29
|
+
toDecoratedJson,
|
|
30
|
+
type EditLog,
|
|
31
|
+
} from "./promptGeneration.js";
|
|
32
|
+
import { generateGenericEditTypes } from "./typeGeneration.js";
|
|
33
|
+
import { fail } from "./utils.js";
|
|
34
|
+
|
|
35
|
+
const DEBUG_LOG: string[] = [];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* {@link generateTreeEdits} options.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export interface GenerateTreeEditsOptions {
|
|
43
|
+
openAI: OpenAiClientOptions;
|
|
44
|
+
treeNode: TreeNode;
|
|
45
|
+
prompt: {
|
|
46
|
+
systemRoleContext: string;
|
|
47
|
+
userAsk: string;
|
|
48
|
+
};
|
|
49
|
+
limiters?: {
|
|
50
|
+
abortController?: AbortController;
|
|
51
|
+
maxSequentialErrors?: number;
|
|
52
|
+
maxModelCalls?: number;
|
|
53
|
+
tokenLimits?: TokenLimits;
|
|
54
|
+
};
|
|
55
|
+
finalReviewStep?: boolean;
|
|
56
|
+
validator?: (newContent: TreeNode) => void;
|
|
57
|
+
dumpDebugLog?: boolean;
|
|
58
|
+
planningStep?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface GenerateTreeEditsSuccessResponse {
|
|
62
|
+
status: "success";
|
|
63
|
+
tokensUsed: TokenUsage;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface GenerateTreeEditsErrorResponse {
|
|
67
|
+
status: "failure" | "partial-failure";
|
|
68
|
+
errorMessage: "tokenLimitExceeded" | "tooManyErrors" | "tooManyModelCalls" | "aborted";
|
|
69
|
+
tokensUsed: TokenUsage;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Prompts the provided LLM client to generate valid tree edits.
|
|
74
|
+
* Applies those edits to the provided tree branch before returning.
|
|
75
|
+
*
|
|
76
|
+
* @remarks
|
|
77
|
+
* - Optional root nodes are not supported
|
|
78
|
+
* - Primitive root nodes are not supported
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
export async function generateTreeEdits(
|
|
83
|
+
options: GenerateTreeEditsOptions,
|
|
84
|
+
): Promise<GenerateTreeEditsSuccessResponse | GenerateTreeEditsErrorResponse> {
|
|
85
|
+
const idGenerator = new IdGenerator();
|
|
86
|
+
const editLog: EditLog = [];
|
|
87
|
+
let editCount = 0;
|
|
88
|
+
let sequentialErrorCount = 0;
|
|
89
|
+
|
|
90
|
+
const simpleSchema = getSimpleSchema(Tree.schema(options.treeNode));
|
|
91
|
+
|
|
92
|
+
const tokensUsed = { inputTokens: 0, outputTokens: 0 };
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
for await (const edit of generateEdits(
|
|
96
|
+
options,
|
|
97
|
+
simpleSchema,
|
|
98
|
+
idGenerator,
|
|
99
|
+
editLog,
|
|
100
|
+
options.limiters?.tokenLimits,
|
|
101
|
+
tokensUsed,
|
|
102
|
+
)) {
|
|
103
|
+
try {
|
|
104
|
+
const result = applyAgentEdit(
|
|
105
|
+
edit,
|
|
106
|
+
idGenerator,
|
|
107
|
+
simpleSchema.definitions,
|
|
108
|
+
options.validator,
|
|
109
|
+
);
|
|
110
|
+
const explanation = result.explanation;
|
|
111
|
+
editLog.push({ edit: { ...result, explanation } });
|
|
112
|
+
sequentialErrorCount = 0;
|
|
113
|
+
} catch (error: unknown) {
|
|
114
|
+
if (error instanceof Error) {
|
|
115
|
+
sequentialErrorCount += 1;
|
|
116
|
+
editLog.push({ edit, error: error.message });
|
|
117
|
+
DEBUG_LOG?.push(`Error: ${error.message}`);
|
|
118
|
+
} else {
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const responseStatus =
|
|
124
|
+
editCount > 0 && sequentialErrorCount < editCount ? "partial-failure" : "failure";
|
|
125
|
+
|
|
126
|
+
if (options.limiters?.abortController?.signal.aborted === true) {
|
|
127
|
+
return {
|
|
128
|
+
status: responseStatus,
|
|
129
|
+
errorMessage: "aborted",
|
|
130
|
+
tokensUsed,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
sequentialErrorCount >
|
|
136
|
+
(options.limiters?.maxSequentialErrors ?? Number.POSITIVE_INFINITY)
|
|
137
|
+
) {
|
|
138
|
+
return {
|
|
139
|
+
status: responseStatus,
|
|
140
|
+
errorMessage: "tooManyErrors",
|
|
141
|
+
tokensUsed,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (++editCount >= (options.limiters?.maxModelCalls ?? Number.POSITIVE_INFINITY)) {
|
|
146
|
+
return {
|
|
147
|
+
status: responseStatus,
|
|
148
|
+
errorMessage: "tooManyModelCalls",
|
|
149
|
+
tokensUsed,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (error: unknown) {
|
|
154
|
+
if (error instanceof Error) {
|
|
155
|
+
DEBUG_LOG?.push(`Error: ${error.message}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (options.dumpDebugLog ?? false) {
|
|
159
|
+
console.log(DEBUG_LOG.join("\n\n"));
|
|
160
|
+
DEBUG_LOG.length = 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (error instanceof TokenLimitExceededError) {
|
|
164
|
+
return {
|
|
165
|
+
status:
|
|
166
|
+
editCount > 0 && sequentialErrorCount < editCount ? "partial-failure" : "failure",
|
|
167
|
+
errorMessage: "tokenLimitExceeded",
|
|
168
|
+
tokensUsed,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (options.dumpDebugLog ?? false) {
|
|
175
|
+
console.log(DEBUG_LOG.join("\n\n"));
|
|
176
|
+
DEBUG_LOG.length = 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
status: "success",
|
|
181
|
+
tokensUsed,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface ReviewResult {
|
|
186
|
+
goalAccomplished: "yes" | "no";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Generates a single {@link TreeEdit} from an LLM.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* The design of this async generator function is such that which each iteration of this functions values,
|
|
194
|
+
* an LLM will be prompted to generate the next value (a {@link TreeEdit}) based on the users ask.
|
|
195
|
+
* Once the LLM believes it has completed the user's ask, it will no longer return an edit and as a result
|
|
196
|
+
* this generator will no longer yield a next value.
|
|
197
|
+
*/
|
|
198
|
+
async function* generateEdits(
|
|
199
|
+
options: GenerateTreeEditsOptions,
|
|
200
|
+
simpleSchema: SimpleTreeSchema,
|
|
201
|
+
idGenerator: IdGenerator,
|
|
202
|
+
editLog: EditLog,
|
|
203
|
+
tokenLimits: TokenLimits | undefined,
|
|
204
|
+
tokensUsed: TokenUsage,
|
|
205
|
+
): AsyncGenerator<TreeEdit> {
|
|
206
|
+
const [types, rootTypeName] = generateGenericEditTypes(simpleSchema, true);
|
|
207
|
+
|
|
208
|
+
let plan: string | undefined;
|
|
209
|
+
if (options.planningStep !== undefined) {
|
|
210
|
+
const planningPromt = getPlanningSystemPrompt(
|
|
211
|
+
options.treeNode,
|
|
212
|
+
options.prompt.userAsk,
|
|
213
|
+
options.prompt.systemRoleContext,
|
|
214
|
+
);
|
|
215
|
+
DEBUG_LOG?.push(planningPromt);
|
|
216
|
+
plan = await getStringFromLlm(planningPromt, options.openAI, tokensUsed);
|
|
217
|
+
DEBUG_LOG?.push(`AI Generated the following plan: ${planningPromt}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const originalDecoratedJson =
|
|
221
|
+
(options.finalReviewStep ?? false)
|
|
222
|
+
? toDecoratedJson(idGenerator, options.treeNode)
|
|
223
|
+
: undefined;
|
|
224
|
+
// reviewed is implicitly true if finalReviewStep is false
|
|
225
|
+
let hasReviewed = (options.finalReviewStep ?? false) ? false : true;
|
|
226
|
+
async function getNextEdit(): Promise<TreeEdit | undefined> {
|
|
227
|
+
const systemPrompt = getEditingSystemPrompt(
|
|
228
|
+
options.prompt.userAsk,
|
|
229
|
+
idGenerator,
|
|
230
|
+
options.treeNode,
|
|
231
|
+
editLog,
|
|
232
|
+
options.prompt.systemRoleContext,
|
|
233
|
+
plan,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
DEBUG_LOG?.push(systemPrompt);
|
|
237
|
+
|
|
238
|
+
const schema = types[rootTypeName] ?? fail("Root type not found.");
|
|
239
|
+
const wrapper = await getStructuredOutputFromLlm<EditWrapper>(
|
|
240
|
+
systemPrompt,
|
|
241
|
+
options.openAI,
|
|
242
|
+
schema,
|
|
243
|
+
"A JSON object that represents an edit to a JSON tree.",
|
|
244
|
+
tokensUsed,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// eslint-disable-next-line unicorn/no-null
|
|
248
|
+
DEBUG_LOG?.push(JSON.stringify(wrapper, null, 2));
|
|
249
|
+
if (wrapper === undefined) {
|
|
250
|
+
DEBUG_LOG?.push("Failed to get response");
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (wrapper.edit === null) {
|
|
255
|
+
DEBUG_LOG?.push("No more edits.");
|
|
256
|
+
if ((options.finalReviewStep ?? false) && !hasReviewed) {
|
|
257
|
+
const reviewResult = await reviewGoal();
|
|
258
|
+
if (reviewResult === undefined) {
|
|
259
|
+
DEBUG_LOG?.push("Failed to get review response");
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
// eslint-disable-next-line require-atomic-updates
|
|
263
|
+
hasReviewed = true;
|
|
264
|
+
if (reviewResult.goalAccomplished === "yes") {
|
|
265
|
+
return undefined;
|
|
266
|
+
} else {
|
|
267
|
+
// eslint-disable-next-line require-atomic-updates
|
|
268
|
+
editLog.length = 0;
|
|
269
|
+
return getNextEdit();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
return wrapper.edit;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function reviewGoal(): Promise<ReviewResult | undefined> {
|
|
278
|
+
const systemPrompt = getReviewSystemPrompt(
|
|
279
|
+
options.prompt.userAsk,
|
|
280
|
+
idGenerator,
|
|
281
|
+
options.treeNode,
|
|
282
|
+
originalDecoratedJson ?? fail("Original decorated tree not provided."),
|
|
283
|
+
options.prompt.systemRoleContext,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
DEBUG_LOG?.push(systemPrompt);
|
|
287
|
+
|
|
288
|
+
const schema = z.object({
|
|
289
|
+
goalAccomplished: z
|
|
290
|
+
.enum(["yes", "no"])
|
|
291
|
+
.describe('Whether the user\'s goal was met in the "after" tree.'),
|
|
292
|
+
});
|
|
293
|
+
return getStructuredOutputFromLlm<ReviewResult>(systemPrompt, options.openAI, schema);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let edit = await getNextEdit();
|
|
297
|
+
while (edit !== undefined) {
|
|
298
|
+
yield edit;
|
|
299
|
+
if (tokensUsed.inputTokens > (tokenLimits?.inputTokens ?? Number.POSITIVE_INFINITY)) {
|
|
300
|
+
throw new TokenLimitExceededError("Input token limit exceeded.");
|
|
301
|
+
}
|
|
302
|
+
if (tokensUsed.outputTokens > (tokenLimits?.outputTokens ?? Number.POSITIVE_INFINITY)) {
|
|
303
|
+
throw new TokenLimitExceededError("Output token limit exceeded.");
|
|
304
|
+
}
|
|
305
|
+
edit = await getNextEdit();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Calls the LLM to generate a structured output response based on the provided prompt.
|
|
311
|
+
*/
|
|
312
|
+
async function getStructuredOutputFromLlm<T>(
|
|
313
|
+
prompt: string,
|
|
314
|
+
openAi: OpenAiClientOptions,
|
|
315
|
+
structuredOutputSchema: Zod.ZodTypeAny,
|
|
316
|
+
description?: string,
|
|
317
|
+
tokensUsed?: TokenUsage,
|
|
318
|
+
): Promise<T | undefined> {
|
|
319
|
+
const response_format = zodResponseFormat(structuredOutputSchema, "SharedTreeAI", {
|
|
320
|
+
description,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const body: ChatCompletionCreateParams = {
|
|
324
|
+
messages: [{ role: "system", content: prompt }],
|
|
325
|
+
model: openAi.modelName ?? "gpt-4o",
|
|
326
|
+
response_format,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const result = await openAi.client.beta.chat.completions.parse(body);
|
|
330
|
+
|
|
331
|
+
if (result.usage !== undefined && tokensUsed !== undefined) {
|
|
332
|
+
tokensUsed.inputTokens += result.usage?.prompt_tokens;
|
|
333
|
+
tokensUsed.outputTokens += result.usage?.completion_tokens;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// TODO: fix types so this isn't null and doesn't need a cast
|
|
337
|
+
// The type should be derived from the zod schema
|
|
338
|
+
return result.choices[0]?.message.parsed as T | undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Calls the LLM to generate a response based on the provided prompt.
|
|
343
|
+
*/
|
|
344
|
+
async function getStringFromLlm(
|
|
345
|
+
prompt: string,
|
|
346
|
+
openAi: OpenAiClientOptions,
|
|
347
|
+
tokensUsed?: TokenUsage,
|
|
348
|
+
): Promise<string | undefined> {
|
|
349
|
+
const body: ChatCompletionCreateParams = {
|
|
350
|
+
messages: [{ role: "system", content: prompt }],
|
|
351
|
+
model: openAi.modelName ?? "gpt-4o",
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const result = await openAi.client.chat.completions.create(body);
|
|
355
|
+
|
|
356
|
+
if (result.usage !== undefined && tokensUsed !== undefined) {
|
|
357
|
+
tokensUsed.inputTokens += result.usage?.prompt_tokens;
|
|
358
|
+
tokensUsed.outputTokens += result.usage?.completion_tokens;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return result.choices[0]?.message.content ?? undefined;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
class TokenLimitExceededError extends Error {}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Primitive JSON Types
|
|
8
|
+
*/
|
|
9
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
10
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A JSON Object, a collection of key to {@link JsonValue} pairs
|
|
14
|
+
*/
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
|
|
16
|
+
export interface JsonObject {
|
|
17
|
+
[key: string]: JsonValue;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* An Array of {@link JsonValue}
|
|
21
|
+
*/
|
|
22
|
+
export type JsonArray = JsonValue[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A union type of all possible JSON values, including primitives, objects, and arrays
|
|
26
|
+
*/
|
|
27
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|