@fluidframework/tree-agent-langchain 2.90.0 → 2.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @fluidframework/tree-agent-langchain
2
2
 
3
+ ## 2.91.0
4
+
5
+ Dependency updates only.
6
+
3
7
  ## 2.90.0
4
8
 
5
9
  Dependency updates only.
package/dist/alpha.d.ts CHANGED
@@ -16,6 +16,7 @@
16
16
 
17
17
  export {
18
18
  // #region @alpha APIs
19
- createLangchainChatModel
19
+ createLangchainChatModel,
20
+ createLegacyLangchainChatModel
20
21
  // #endregion
21
22
  } from "./index.js";
@@ -5,9 +5,21 @@
5
5
  import type { SharedTreeChatModel } from "@fluidframework/tree-agent/alpha";
6
6
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
7
7
  /**
8
- * Creates a `SharedTreeChatModel` that uses the LangChain library to connect to the underlying LLM.
8
+ * Creates a stateless {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
9
+ * @remarks Use with {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent}.
9
10
  * @param langchainModel - The LangChain chat model to use.
10
11
  * @alpha
11
12
  */
12
13
  export declare function createLangchainChatModel(langchainModel: BaseChatModel): SharedTreeChatModel;
14
+ /**
15
+ * Creates a legacy stateful {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
16
+ * @remarks This implementation maintains internal message history and manages the edit loop via the
17
+ * {@link @fluidframework/tree-agent#SharedTreeChatQuery.edit | edit} callback pattern.
18
+ * Use with {@link @fluidframework/tree-agent#SharedTreeSemanticAgent}.
19
+ * @param langchainModel - The LangChain chat model to use.
20
+ * @deprecated Use {@link createLangchainChatModel} with
21
+ * {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent} instead.
22
+ * @alpha
23
+ */
24
+ export declare function createLegacyLangchainChatModel(langchainModel: BaseChatModel): SharedTreeChatModel;
13
25
  //# sourceMappingURL=chatModel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chatModel.d.ts","sourceRoot":"","sources":["../src/chatModel.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAEX,mBAAmB,EAEnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAKjF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,aAAa,GAAG,mBAAmB,CAE3F"}
1
+ {"version":3,"file":"chatModel.d.ts","sourceRoot":"","sources":["../src/chatModel.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAEX,mBAAmB,EAInB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAOjF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,aAAa,GAAG,mBAAmB,CAE3F;AAuHD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC7C,cAAc,EAAE,aAAa,GAC3B,mBAAmB,CAErB"}
package/dist/chatModel.js CHANGED
@@ -4,13 +4,14 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.createLangchainChatModel = void 0;
8
- /* eslint-disable import-x/no-internal-modules */
7
+ exports.createLegacyLangchainChatModel = exports.createLangchainChatModel = void 0;
9
8
  const internal_1 = require("@fluidframework/telemetry-utils/internal");
10
- const messages_1 = require("@langchain/core/messages");
11
- const tools_1 = require("@langchain/core/tools");
9
+ const messages_1 = require("@langchain/core/messages"); // eslint-disable-line import-x/no-internal-modules
10
+ const tools_1 = require("@langchain/core/tools"); // eslint-disable-line import-x/no-internal-modules
11
+ // #region New stateless implementation
12
12
  /**
13
- * Creates a `SharedTreeChatModel` that uses the LangChain library to connect to the underlying LLM.
13
+ * Creates a stateless {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
14
+ * @remarks Use with {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent}.
14
15
  * @param langchainModel - The LangChain chat model to use.
15
16
  * @alpha
16
17
  */
@@ -18,7 +19,120 @@ function createLangchainChatModel(langchainModel) {
18
19
  return new LangchainChatModel(langchainModel);
19
20
  }
20
21
  exports.createLangchainChatModel = createLangchainChatModel;
22
+ /**
23
+ * Stateless LangChain adapter for {@link @fluidframework/tree-agent#SharedTreeChatModel}.
24
+ * @remarks This class does not maintain internal message history. All context is provided
25
+ * via the `history` parameter to {@link LangchainChatModel.invoke}.
26
+ */
21
27
  class LangchainChatModel {
28
+ constructor(model) {
29
+ this.model = model;
30
+ this.editToolName = "GenerateTreeEditingCode";
31
+ }
32
+ get name() {
33
+ const name = this.model.metadata?.modelName;
34
+ return typeof name === "string" ? name : undefined;
35
+ }
36
+ async invoke(history) {
37
+ // Convert TreeAgentChatMessage[] to LangChain BaseMessage[]
38
+ const messages = convertToLangchainMessages(history);
39
+ // Create a placeholder tool definition so the LLM knows the tool exists.
40
+ // The actual execution is handled by the agent's edit loop — this tool
41
+ // is never directly invoked, it only tells the LLM the tool signature.
42
+ const editingTool = (0, tools_1.tool)(async (js) => js, {
43
+ name: this.editToolName,
44
+ description: "Invokes a JavaScript code snippet to edit a tree of application data.",
45
+ });
46
+ const runnable = this.model.bindTools?.([editingTool], {
47
+ tool_choice: "auto",
48
+ });
49
+ if (runnable === undefined) {
50
+ throw new internal_1.UsageError("LLM client must support function calling or tool use.");
51
+ }
52
+ const responseMessage = await runnable.invoke(messages);
53
+ // Parse the response into TreeAgentChatResponse.
54
+ // Return the first tool call as a tool_call message, preserving the raw args.
55
+ // Arg parsing (extracting code) is the agent's responsibility.
56
+ const firstToolCall = responseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0
57
+ ? responseMessage.tool_calls[0]
58
+ : undefined;
59
+ if (firstToolCall !== undefined) {
60
+ return {
61
+ role: "tool_call",
62
+ toolCallId: firstToolCall.id,
63
+ toolName: firstToolCall.name,
64
+ toolArgs: firstToolCall.args,
65
+ };
66
+ }
67
+ const content = typeof responseMessage.text === "string"
68
+ ? responseMessage.text
69
+ : typeof responseMessage.content === "string"
70
+ ? responseMessage.content
71
+ : JSON.stringify(responseMessage.content);
72
+ return { role: "assistant", content };
73
+ }
74
+ }
75
+ /**
76
+ * Converts an array of {@link TreeAgentChatMessage} to LangChain {@link BaseMessage} format.
77
+ */
78
+ function convertToLangchainMessages(history) {
79
+ const messages = [];
80
+ for (const msg of history) {
81
+ switch (msg.role) {
82
+ case "system": {
83
+ messages.push(new messages_1.SystemMessage(msg.content));
84
+ break;
85
+ }
86
+ case "user": {
87
+ messages.push(new messages_1.HumanMessage(msg.content));
88
+ break;
89
+ }
90
+ case "assistant": {
91
+ messages.push(new messages_1.AIMessage(msg.content));
92
+ break;
93
+ }
94
+ case "tool_call": {
95
+ messages.push(new messages_1.AIMessage({
96
+ content: "",
97
+ tool_calls: [
98
+ {
99
+ id: msg.toolCallId,
100
+ name: msg.toolName,
101
+ args: msg.toolArgs,
102
+ },
103
+ ],
104
+ }));
105
+ break;
106
+ }
107
+ case "tool_result": {
108
+ messages.push(new messages_1.ToolMessage({
109
+ content: msg.content,
110
+ tool_call_id: msg.toolCallId ?? "",
111
+ }));
112
+ break;
113
+ }
114
+ // No default
115
+ }
116
+ }
117
+ return messages;
118
+ }
119
+ // #endregion
120
+ // #region Legacy stateful implementation
121
+ /**
122
+ * Creates a legacy stateful {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
123
+ * @remarks This implementation maintains internal message history and manages the edit loop via the
124
+ * {@link @fluidframework/tree-agent#SharedTreeChatQuery.edit | edit} callback pattern.
125
+ * Use with {@link @fluidframework/tree-agent#SharedTreeSemanticAgent}.
126
+ * @param langchainModel - The LangChain chat model to use.
127
+ * @deprecated Use {@link createLangchainChatModel} with
128
+ * {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent} instead.
129
+ * @alpha
130
+ */
131
+ function createLegacyLangchainChatModel(langchainModel) {
132
+ return new LegacyLangchainChatModel(langchainModel);
133
+ }
134
+ exports.createLegacyLangchainChatModel = createLegacyLangchainChatModel;
135
+ class LegacyLangchainChatModel {
22
136
  constructor(model) {
23
137
  this.model = model;
24
138
  this.messages = [];
@@ -31,6 +145,7 @@ class LangchainChatModel {
31
145
  appendContext(text) {
32
146
  this.messages.push(new messages_1.SystemMessage(text));
33
147
  }
148
+ // eslint-disable-next-line import-x/no-deprecated
34
149
  async query(query) {
35
150
  this.messages.push(new messages_1.HumanMessage(query.text));
36
151
  return this.queryEdit(async (js) => query.edit(js));
@@ -40,7 +155,9 @@ class LangchainChatModel {
40
155
  name: this.editToolName,
41
156
  description: "Invokes a JavaScript code snippet to edit a tree of application data.",
42
157
  });
43
- const runnable = this.model.bindTools?.([editingTool], { tool_choice: "auto" });
158
+ const runnable = this.model.bindTools?.([editingTool], {
159
+ tool_choice: "auto",
160
+ });
44
161
  if (runnable === undefined) {
45
162
  throw new internal_1.UsageError("LLM client must support function calling or tool use.");
46
163
  }
@@ -77,4 +194,5 @@ function isEditResult(value) {
77
194
  return (typeof value.type === "string" &&
78
195
  typeof value.message === "string");
79
196
  }
197
+ // #endregion
80
198
  //# sourceMappingURL=chatModel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chatModel.js","sourceRoot":"","sources":["../src/chatModel.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/* eslint-disable import-x/no-internal-modules */\n\nimport { UsageError } from \"@fluidframework/telemetry-utils/internal\";\nimport type {\n\tEditResult,\n\tSharedTreeChatModel,\n\tSharedTreeChatQuery,\n} from \"@fluidframework/tree-agent/alpha\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { tool } from \"@langchain/core/tools\";\n\n/**\n * Creates a `SharedTreeChatModel` that uses the LangChain library to connect to the underlying LLM.\n * @param langchainModel - The LangChain chat model to use.\n * @alpha\n */\nexport function createLangchainChatModel(langchainModel: BaseChatModel): SharedTreeChatModel {\n\treturn new LangchainChatModel(langchainModel);\n}\n\nclass LangchainChatModel implements SharedTreeChatModel {\n\tprivate readonly messages: BaseMessage[] = [];\n\n\tpublic constructor(private readonly model: BaseChatModel) {}\n\n\tpublic readonly editToolName = \"GenerateTreeEditingCode\";\n\n\tpublic get name(): string | undefined {\n\t\tconst name = this.model.metadata?.modelName;\n\t\treturn typeof name === \"string\" ? name : undefined;\n\t}\n\n\tpublic appendContext(text: string): void {\n\t\tthis.messages.push(new SystemMessage(text));\n\t}\n\n\tpublic async query(query: SharedTreeChatQuery): Promise<string> {\n\t\tthis.messages.push(new HumanMessage(query.text));\n\t\treturn this.queryEdit(async (js: string) => query.edit(js));\n\t}\n\n\tprivate async queryEdit(edit: SharedTreeChatQuery[\"edit\"]): Promise<string> {\n\t\tconst editingTool = tool(edit, {\n\t\t\tname: this.editToolName,\n\t\t\tdescription: \"Invokes a JavaScript code snippet to edit a tree of application data.\",\n\t\t});\n\t\tconst runnable = this.model.bindTools?.([editingTool], { tool_choice: \"auto\" });\n\t\tif (runnable === undefined) {\n\t\t\tthrow new UsageError(\"LLM client must support function calling or tool use.\");\n\t\t}\n\n\t\tconst responseMessage = await runnable.invoke(this.messages);\n\t\tthis.messages.push(responseMessage);\n\n\t\tif (responseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0) {\n\t\t\tfor (const toolCall of responseMessage.tool_calls) {\n\t\t\t\tswitch (toolCall.name) {\n\t\t\t\t\tcase editingTool.name: {\n\t\t\t\t\t\tconst toolResult = await editingTool.invoke(toolCall);\n\t\t\t\t\t\tthis.messages.push(toolResult);\n\t\t\t\t\t\tconst editResult: unknown = JSON.parse(toolResult.text);\n\t\t\t\t\t\tif (isEditResult(editResult) && editResult.type === \"tooManyEditsError\") {\n\t\t\t\t\t\t\treturn editResult.message;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn this.queryEdit(edit);\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tthis.messages.push(new HumanMessage(`Unrecognized tool call: ${toolCall.name}`));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn responseMessage.text;\n\t}\n}\n\n/**\n * Type guard for {@link EditResult}.\n */\nfunction isEditResult(value: unknown): value is EditResult {\n\tif (value === null || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn (\n\t\ttypeof (value as EditResult).type === \"string\" &&\n\t\ttypeof (value as EditResult).message === \"string\"\n\t);\n}\n"]}
1
+ {"version":3,"file":"chatModel.js","sourceRoot":"","sources":["../src/chatModel.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,uEAAsE;AAUtE,uDAA+F,CAAC,mDAAmD;AACnJ,iDAA6C,CAAC,mDAAmD;AAEjG,uCAAuC;AAEvC;;;;;GAKG;AACH,SAAgB,wBAAwB,CAAC,cAA6B;IACrE,OAAO,IAAI,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAC/C,CAAC;AAFD,4DAEC;AAED;;;;GAIG;AACH,MAAM,kBAAkB;IAGvB,YAAoC,KAAoB;QAApB,UAAK,GAAL,KAAK,CAAe;QAFxC,iBAAY,GAAG,yBAAyB,CAAC;IAEE,CAAC;IAE5D,IAAW,IAAI;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC;QAC5C,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,CAAC;IAEM,KAAK,CAAC,MAAM,CAClB,OAAwC;QAExC,4DAA4D;QAC5D,MAAM,QAAQ,GAAkB,0BAA0B,CAAC,OAAO,CAAC,CAAC;QAEpE,yEAAyE;QACzE,uEAAuE;QACvE,uEAAuE;QACvE,MAAM,WAAW,GAAG,IAAA,YAAI,EAAC,KAAK,EAAE,EAAU,EAAE,EAAE,CAAC,EAAE,EAAE;YAClD,IAAI,EAAE,IAAI,CAAC,YAAY;YACvB,WAAW,EAAE,uEAAuE;SACpF,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE;YACtD,WAAW,EAAE,MAAM;SACnB,CAAC,CAAC;QACH,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,qBAAU,CAAC,uDAAuD,CAAC,CAAC;QAC/E,CAAC;QAED,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAExD,iDAAiD;QACjD,8EAA8E;QAC9E,+DAA+D;QAC/D,MAAM,aAAa,GAClB,eAAe,CAAC,UAAU,KAAK,SAAS,IAAI,eAAe,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;YAChF,CAAC,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC;YAC/B,CAAC,CAAC,SAAS,CAAC;QACd,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO;gBACN,IAAI,EAAE,WAAW;gBACjB,UAAU,EAAE,aAAa,CAAC,EAAE;gBAC5B,QAAQ,EAAE,aAAa,CAAC,IAAI;gBAC5B,QAAQ,EAAE,aAAa,CAAC,IAAI;aAC5B,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GACZ,OAAO,eAAe,CAAC,IAAI,KAAK,QAAQ;YACvC,CAAC,CAAC,eAAe,CAAC,IAAI;YACtB,CAAC,CAAC,OAAO,eAAe,CAAC,OAAO,KAAK,QAAQ;gBAC5C,CAAC,CAAC,eAAe,CAAC,OAAO;gBACzB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;IACvC,CAAC;CACD;AAED;;GAEG;AACH,SAAS,0BAA0B,CAAC,OAAwC;IAC3E,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC3B,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YAClB,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACf,QAAQ,CAAC,IAAI,CAAC,IAAI,wBAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC9C,MAAM;YACP,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACb,QAAQ,CAAC,IAAI,CAAC,IAAI,uBAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC7C,MAAM;YACP,CAAC;YACD,KAAK,WAAW,CAAC,CAAC,CAAC;gBAClB,QAAQ,CAAC,IAAI,CAAC,IAAI,oBAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC1C,MAAM;YACP,CAAC;YACD,KAAK,WAAW,CAAC,CAAC,CAAC;gBAClB,QAAQ,CAAC,IAAI,CACZ,IAAI,oBAAS,CAAC;oBACb,OAAO,EAAE,EAAE;oBACX,UAAU,EAAE;wBACX;4BACC,EAAE,EAAE,GAAG,CAAC,UAAU;4BAClB,IAAI,EAAE,GAAG,CAAC,QAAQ;4BAClB,IAAI,EAAE,GAAG,CAAC,QAAQ;yBAClB;qBACD;iBACD,CAAC,CACF,CAAC;gBACF,MAAM;YACP,CAAC;YACD,KAAK,aAAa,CAAC,CAAC,CAAC;gBACpB,QAAQ,CAAC,IAAI,CACZ,IAAI,sBAAW,CAAC;oBACf,OAAO,EAAE,GAAG,CAAC,OAAO;oBACpB,YAAY,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE;iBAClC,CAAC,CACF,CAAC;gBACF,MAAM;YACP,CAAC;YACD,aAAa;QACd,CAAC;IACF,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,aAAa;AAEb,yCAAyC;AAEzC;;;;;;;;;GASG;AACH,SAAgB,8BAA8B,CAC7C,cAA6B;IAE7B,OAAO,IAAI,wBAAwB,CAAC,cAAc,CAAC,CAAC;AACrD,CAAC;AAJD,wEAIC;AAED,MAAM,wBAAwB;IAG7B,YAAoC,KAAoB;QAApB,UAAK,GAAL,KAAK,CAAe;QAFvC,aAAQ,GAAkB,EAAE,CAAC;QAI9B,iBAAY,GAAG,yBAAyB,CAAC;IAFE,CAAC;IAI5D,IAAW,IAAI;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC;QAC5C,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,CAAC;IAEM,aAAa,CAAC,IAAY;QAChC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,wBAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,kDAAkD;IAC3C,KAAK,CAAC,KAAK,CAAC,KAA0B;QAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,uBAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,EAAU,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC;IAEO,KAAK,CAAC,SAAS,CACtB,IAAiC;QAEjC,MAAM,WAAW,GAAG,IAAA,YAAI,EAAC,IAAI,EAAE;YAC9B,IAAI,EAAE,IAAI,CAAC,YAAY;YACvB,WAAW,EAAE,uEAAuE;SACpF,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE;YACtD,WAAW,EAAE,MAAM;SACnB,CAAC,CAAC;QACH,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,qBAAU,CAAC,uDAAuD,CAAC,CAAC;QAC/E,CAAC;QAED,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAEpC,IAAI,eAAe,CAAC,UAAU,KAAK,SAAS,IAAI,eAAe,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvF,KAAK,MAAM,QAAQ,IAAI,eAAe,CAAC,UAAU,EAAE,CAAC;gBACnD,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;oBACvB,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;wBACvB,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;wBACtD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;wBAC/B,MAAM,UAAU,GAAY,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;wBACxD,IAAI,YAAY,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;4BACzE,OAAO,UAAU,CAAC,OAAO,CAAC;wBAC3B,CAAC;wBACD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;oBAC7B,CAAC;oBACD,OAAO,CAAC,CAAC,CAAC;wBACT,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,uBAAY,CAAC,2BAA2B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;oBAClF,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,OAAO,eAAe,CAAC,IAAI,CAAC;IAC7B,CAAC;CACD;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,KAAc;IACnC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACjD,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,CACN,OAAQ,KAAoB,CAAC,IAAI,KAAK,QAAQ;QAC9C,OAAQ,KAAoB,CAAC,OAAO,KAAK,QAAQ,CACjD,CAAC;AACH,CAAC;AAED,aAAa","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { UsageError } from \"@fluidframework/telemetry-utils/internal\";\nimport type {\n\tEditResult,\n\tSharedTreeChatModel,\n\tTreeAgentChatMessage,\n\tTreeAgentChatResponse,\n\tSharedTreeChatQuery, // eslint-disable-line import-x/no-deprecated\n} from \"@fluidframework/tree-agent/alpha\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\"; // eslint-disable-line import-x/no-internal-modules\nimport type { BaseMessage } from \"@langchain/core/messages\"; // eslint-disable-line import-x/no-internal-modules\nimport { AIMessage, HumanMessage, SystemMessage, ToolMessage } from \"@langchain/core/messages\"; // eslint-disable-line import-x/no-internal-modules\nimport { tool } from \"@langchain/core/tools\"; // eslint-disable-line import-x/no-internal-modules\n\n// #region New stateless implementation\n\n/**\n * Creates a stateless {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.\n * @remarks Use with {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent}.\n * @param langchainModel - The LangChain chat model to use.\n * @alpha\n */\nexport function createLangchainChatModel(langchainModel: BaseChatModel): SharedTreeChatModel {\n\treturn new LangchainChatModel(langchainModel);\n}\n\n/**\n * Stateless LangChain adapter for {@link @fluidframework/tree-agent#SharedTreeChatModel}.\n * @remarks This class does not maintain internal message history. All context is provided\n * via the `history` parameter to {@link LangchainChatModel.invoke}.\n */\nclass LangchainChatModel implements SharedTreeChatModel {\n\tpublic readonly editToolName = \"GenerateTreeEditingCode\";\n\n\tpublic constructor(private readonly model: BaseChatModel) {}\n\n\tpublic get name(): string | undefined {\n\t\tconst name = this.model.metadata?.modelName;\n\t\treturn typeof name === \"string\" ? name : undefined;\n\t}\n\n\tpublic async invoke(\n\t\thistory: readonly TreeAgentChatMessage[],\n\t): Promise<TreeAgentChatResponse> {\n\t\t// Convert TreeAgentChatMessage[] to LangChain BaseMessage[]\n\t\tconst messages: BaseMessage[] = convertToLangchainMessages(history);\n\n\t\t// Create a placeholder tool definition so the LLM knows the tool exists.\n\t\t// The actual execution is handled by the agent's edit loop — this tool\n\t\t// is never directly invoked, it only tells the LLM the tool signature.\n\t\tconst editingTool = tool(async (js: string) => js, {\n\t\t\tname: this.editToolName,\n\t\t\tdescription: \"Invokes a JavaScript code snippet to edit a tree of application data.\",\n\t\t});\n\n\t\tconst runnable = this.model.bindTools?.([editingTool], {\n\t\t\ttool_choice: \"auto\",\n\t\t});\n\t\tif (runnable === undefined) {\n\t\t\tthrow new UsageError(\"LLM client must support function calling or tool use.\");\n\t\t}\n\n\t\tconst responseMessage = await runnable.invoke(messages);\n\n\t\t// Parse the response into TreeAgentChatResponse.\n\t\t// Return the first tool call as a tool_call message, preserving the raw args.\n\t\t// Arg parsing (extracting code) is the agent's responsibility.\n\t\tconst firstToolCall =\n\t\t\tresponseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0\n\t\t\t\t? responseMessage.tool_calls[0]\n\t\t\t\t: undefined;\n\t\tif (firstToolCall !== undefined) {\n\t\t\treturn {\n\t\t\t\trole: \"tool_call\",\n\t\t\t\ttoolCallId: firstToolCall.id,\n\t\t\t\ttoolName: firstToolCall.name,\n\t\t\t\ttoolArgs: firstToolCall.args,\n\t\t\t};\n\t\t}\n\n\t\tconst content =\n\t\t\ttypeof responseMessage.text === \"string\"\n\t\t\t\t? responseMessage.text\n\t\t\t\t: typeof responseMessage.content === \"string\"\n\t\t\t\t\t? responseMessage.content\n\t\t\t\t\t: JSON.stringify(responseMessage.content);\n\t\treturn { role: \"assistant\", content };\n\t}\n}\n\n/**\n * Converts an array of {@link TreeAgentChatMessage} to LangChain {@link BaseMessage} format.\n */\nfunction convertToLangchainMessages(history: readonly TreeAgentChatMessage[]): BaseMessage[] {\n\tconst messages: BaseMessage[] = [];\n\tfor (const msg of history) {\n\t\tswitch (msg.role) {\n\t\t\tcase \"system\": {\n\t\t\t\tmessages.push(new SystemMessage(msg.content));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"user\": {\n\t\t\t\tmessages.push(new HumanMessage(msg.content));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"assistant\": {\n\t\t\t\tmessages.push(new AIMessage(msg.content));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"tool_call\": {\n\t\t\t\tmessages.push(\n\t\t\t\t\tnew AIMessage({\n\t\t\t\t\t\tcontent: \"\",\n\t\t\t\t\t\ttool_calls: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tid: msg.toolCallId,\n\t\t\t\t\t\t\t\tname: msg.toolName,\n\t\t\t\t\t\t\t\targs: msg.toolArgs,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"tool_result\": {\n\t\t\t\tmessages.push(\n\t\t\t\t\tnew ToolMessage({\n\t\t\t\t\t\tcontent: msg.content,\n\t\t\t\t\t\ttool_call_id: msg.toolCallId ?? \"\",\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// No default\n\t\t}\n\t}\n\treturn messages;\n}\n\n// #endregion\n\n// #region Legacy stateful implementation\n\n/**\n * Creates a legacy stateful {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.\n * @remarks This implementation maintains internal message history and manages the edit loop via the\n * {@link @fluidframework/tree-agent#SharedTreeChatQuery.edit | edit} callback pattern.\n * Use with {@link @fluidframework/tree-agent#SharedTreeSemanticAgent}.\n * @param langchainModel - The LangChain chat model to use.\n * @deprecated Use {@link createLangchainChatModel} with\n * {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent} instead.\n * @alpha\n */\nexport function createLegacyLangchainChatModel(\n\tlangchainModel: BaseChatModel,\n): SharedTreeChatModel {\n\treturn new LegacyLangchainChatModel(langchainModel);\n}\n\nclass LegacyLangchainChatModel implements SharedTreeChatModel {\n\tprivate readonly messages: BaseMessage[] = [];\n\n\tpublic constructor(private readonly model: BaseChatModel) {}\n\n\tpublic readonly editToolName = \"GenerateTreeEditingCode\";\n\n\tpublic get name(): string | undefined {\n\t\tconst name = this.model.metadata?.modelName;\n\t\treturn typeof name === \"string\" ? name : undefined;\n\t}\n\n\tpublic appendContext(text: string): void {\n\t\tthis.messages.push(new SystemMessage(text));\n\t}\n\n\t// eslint-disable-next-line import-x/no-deprecated\n\tpublic async query(query: SharedTreeChatQuery): Promise<string> {\n\t\tthis.messages.push(new HumanMessage(query.text));\n\t\treturn this.queryEdit(async (js: string) => query.edit(js));\n\t}\n\n\tprivate async queryEdit(\n\t\tedit: SharedTreeChatQuery[\"edit\"], // eslint-disable-line import-x/no-deprecated\n\t): Promise<string> {\n\t\tconst editingTool = tool(edit, {\n\t\t\tname: this.editToolName,\n\t\t\tdescription: \"Invokes a JavaScript code snippet to edit a tree of application data.\",\n\t\t});\n\t\tconst runnable = this.model.bindTools?.([editingTool], {\n\t\t\ttool_choice: \"auto\",\n\t\t});\n\t\tif (runnable === undefined) {\n\t\t\tthrow new UsageError(\"LLM client must support function calling or tool use.\");\n\t\t}\n\n\t\tconst responseMessage = await runnable.invoke(this.messages);\n\t\tthis.messages.push(responseMessage);\n\n\t\tif (responseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0) {\n\t\t\tfor (const toolCall of responseMessage.tool_calls) {\n\t\t\t\tswitch (toolCall.name) {\n\t\t\t\t\tcase editingTool.name: {\n\t\t\t\t\t\tconst toolResult = await editingTool.invoke(toolCall);\n\t\t\t\t\t\tthis.messages.push(toolResult);\n\t\t\t\t\t\tconst editResult: unknown = JSON.parse(toolResult.text);\n\t\t\t\t\t\tif (isEditResult(editResult) && editResult.type === \"tooManyEditsError\") {\n\t\t\t\t\t\t\treturn editResult.message;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn this.queryEdit(edit);\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tthis.messages.push(new HumanMessage(`Unrecognized tool call: ${toolCall.name}`));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn responseMessage.text;\n\t}\n}\n\n/**\n * Type guard for {@link EditResult}.\n */\nfunction isEditResult(value: unknown): value is EditResult {\n\tif (value === null || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn (\n\t\ttypeof (value as EditResult).type === \"string\" &&\n\t\ttypeof (value as EditResult).message === \"string\"\n\t);\n}\n\n// #endregion\n"]}
package/dist/index.d.ts CHANGED
@@ -7,5 +7,5 @@
7
7
  *
8
8
  * @packageDocumentation
9
9
  */
10
- export { createLangchainChatModel } from "./chatModel.js";
10
+ export { createLangchainChatModel, createLegacyLangchainChatModel } from "./chatModel.js";
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EAAE,wBAAwB,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.createLangchainChatModel = void 0;
7
+ exports.createLegacyLangchainChatModel = exports.createLangchainChatModel = void 0;
8
8
  /**
9
9
  * LangChain integration helpers for the {@link @fluidframework/tree-agent#SharedTreeSemanticAgent | SharedTreeSemanticAgent}.
10
10
  *
@@ -12,4 +12,5 @@ exports.createLangchainChatModel = void 0;
12
12
  */
13
13
  var chatModel_js_1 = require("./chatModel.js");
14
14
  Object.defineProperty(exports, "createLangchainChatModel", { enumerable: true, get: function () { return chatModel_js_1.createLangchainChatModel; } });
15
+ Object.defineProperty(exports, "createLegacyLangchainChatModel", { enumerable: true, get: function () { return chatModel_js_1.createLegacyLangchainChatModel; } });
15
16
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH;;;;GAIG;AAEH,+CAA0D;AAAjD,wHAAA,wBAAwB,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * LangChain integration helpers for the {@link @fluidframework/tree-agent#SharedTreeSemanticAgent | SharedTreeSemanticAgent}.\n *\n * @packageDocumentation\n */\n\nexport { createLangchainChatModel } from \"./chatModel.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH;;;;GAIG;AAEH,+CAA0F;AAAjF,wHAAA,wBAAwB,OAAA;AAAE,8HAAA,8BAA8B,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * LangChain integration helpers for the {@link @fluidframework/tree-agent#SharedTreeSemanticAgent | SharedTreeSemanticAgent}.\n *\n * @packageDocumentation\n */\n\nexport { createLangchainChatModel, createLegacyLangchainChatModel } from \"./chatModel.js\";\n"]}
package/lib/alpha.d.ts CHANGED
@@ -16,6 +16,7 @@
16
16
 
17
17
  export {
18
18
  // #region @alpha APIs
19
- createLangchainChatModel
19
+ createLangchainChatModel,
20
+ createLegacyLangchainChatModel
20
21
  // #endregion
21
22
  } from "./index.js";
@@ -5,9 +5,21 @@
5
5
  import type { SharedTreeChatModel } from "@fluidframework/tree-agent/alpha";
6
6
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
7
7
  /**
8
- * Creates a `SharedTreeChatModel` that uses the LangChain library to connect to the underlying LLM.
8
+ * Creates a stateless {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
9
+ * @remarks Use with {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent}.
9
10
  * @param langchainModel - The LangChain chat model to use.
10
11
  * @alpha
11
12
  */
12
13
  export declare function createLangchainChatModel(langchainModel: BaseChatModel): SharedTreeChatModel;
14
+ /**
15
+ * Creates a legacy stateful {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
16
+ * @remarks This implementation maintains internal message history and manages the edit loop via the
17
+ * {@link @fluidframework/tree-agent#SharedTreeChatQuery.edit | edit} callback pattern.
18
+ * Use with {@link @fluidframework/tree-agent#SharedTreeSemanticAgent}.
19
+ * @param langchainModel - The LangChain chat model to use.
20
+ * @deprecated Use {@link createLangchainChatModel} with
21
+ * {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent} instead.
22
+ * @alpha
23
+ */
24
+ export declare function createLegacyLangchainChatModel(langchainModel: BaseChatModel): SharedTreeChatModel;
13
25
  //# sourceMappingURL=chatModel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chatModel.d.ts","sourceRoot":"","sources":["../src/chatModel.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAEX,mBAAmB,EAEnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAKjF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,aAAa,GAAG,mBAAmB,CAE3F"}
1
+ {"version":3,"file":"chatModel.d.ts","sourceRoot":"","sources":["../src/chatModel.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAEX,mBAAmB,EAInB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAOjF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,aAAa,GAAG,mBAAmB,CAE3F;AAuHD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC7C,cAAc,EAAE,aAAa,GAC3B,mBAAmB,CAErB"}
package/lib/chatModel.js CHANGED
@@ -2,19 +2,132 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- /* eslint-disable import-x/no-internal-modules */
6
5
  import { UsageError } from "@fluidframework/telemetry-utils/internal";
7
- import { HumanMessage, SystemMessage } from "@langchain/core/messages";
8
- import { tool } from "@langchain/core/tools";
6
+ import { AIMessage, HumanMessage, SystemMessage, ToolMessage } from "@langchain/core/messages"; // eslint-disable-line import-x/no-internal-modules
7
+ import { tool } from "@langchain/core/tools"; // eslint-disable-line import-x/no-internal-modules
8
+ // #region New stateless implementation
9
9
  /**
10
- * Creates a `SharedTreeChatModel` that uses the LangChain library to connect to the underlying LLM.
10
+ * Creates a stateless {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
11
+ * @remarks Use with {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent}.
11
12
  * @param langchainModel - The LangChain chat model to use.
12
13
  * @alpha
13
14
  */
14
15
  export function createLangchainChatModel(langchainModel) {
15
16
  return new LangchainChatModel(langchainModel);
16
17
  }
18
+ /**
19
+ * Stateless LangChain adapter for {@link @fluidframework/tree-agent#SharedTreeChatModel}.
20
+ * @remarks This class does not maintain internal message history. All context is provided
21
+ * via the `history` parameter to {@link LangchainChatModel.invoke}.
22
+ */
17
23
  class LangchainChatModel {
24
+ constructor(model) {
25
+ this.model = model;
26
+ this.editToolName = "GenerateTreeEditingCode";
27
+ }
28
+ get name() {
29
+ const name = this.model.metadata?.modelName;
30
+ return typeof name === "string" ? name : undefined;
31
+ }
32
+ async invoke(history) {
33
+ // Convert TreeAgentChatMessage[] to LangChain BaseMessage[]
34
+ const messages = convertToLangchainMessages(history);
35
+ // Create a placeholder tool definition so the LLM knows the tool exists.
36
+ // The actual execution is handled by the agent's edit loop — this tool
37
+ // is never directly invoked, it only tells the LLM the tool signature.
38
+ const editingTool = tool(async (js) => js, {
39
+ name: this.editToolName,
40
+ description: "Invokes a JavaScript code snippet to edit a tree of application data.",
41
+ });
42
+ const runnable = this.model.bindTools?.([editingTool], {
43
+ tool_choice: "auto",
44
+ });
45
+ if (runnable === undefined) {
46
+ throw new UsageError("LLM client must support function calling or tool use.");
47
+ }
48
+ const responseMessage = await runnable.invoke(messages);
49
+ // Parse the response into TreeAgentChatResponse.
50
+ // Return the first tool call as a tool_call message, preserving the raw args.
51
+ // Arg parsing (extracting code) is the agent's responsibility.
52
+ const firstToolCall = responseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0
53
+ ? responseMessage.tool_calls[0]
54
+ : undefined;
55
+ if (firstToolCall !== undefined) {
56
+ return {
57
+ role: "tool_call",
58
+ toolCallId: firstToolCall.id,
59
+ toolName: firstToolCall.name,
60
+ toolArgs: firstToolCall.args,
61
+ };
62
+ }
63
+ const content = typeof responseMessage.text === "string"
64
+ ? responseMessage.text
65
+ : typeof responseMessage.content === "string"
66
+ ? responseMessage.content
67
+ : JSON.stringify(responseMessage.content);
68
+ return { role: "assistant", content };
69
+ }
70
+ }
71
+ /**
72
+ * Converts an array of {@link TreeAgentChatMessage} to LangChain {@link BaseMessage} format.
73
+ */
74
+ function convertToLangchainMessages(history) {
75
+ const messages = [];
76
+ for (const msg of history) {
77
+ switch (msg.role) {
78
+ case "system": {
79
+ messages.push(new SystemMessage(msg.content));
80
+ break;
81
+ }
82
+ case "user": {
83
+ messages.push(new HumanMessage(msg.content));
84
+ break;
85
+ }
86
+ case "assistant": {
87
+ messages.push(new AIMessage(msg.content));
88
+ break;
89
+ }
90
+ case "tool_call": {
91
+ messages.push(new AIMessage({
92
+ content: "",
93
+ tool_calls: [
94
+ {
95
+ id: msg.toolCallId,
96
+ name: msg.toolName,
97
+ args: msg.toolArgs,
98
+ },
99
+ ],
100
+ }));
101
+ break;
102
+ }
103
+ case "tool_result": {
104
+ messages.push(new ToolMessage({
105
+ content: msg.content,
106
+ tool_call_id: msg.toolCallId ?? "",
107
+ }));
108
+ break;
109
+ }
110
+ // No default
111
+ }
112
+ }
113
+ return messages;
114
+ }
115
+ // #endregion
116
+ // #region Legacy stateful implementation
117
+ /**
118
+ * Creates a legacy stateful {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
119
+ * @remarks This implementation maintains internal message history and manages the edit loop via the
120
+ * {@link @fluidframework/tree-agent#SharedTreeChatQuery.edit | edit} callback pattern.
121
+ * Use with {@link @fluidframework/tree-agent#SharedTreeSemanticAgent}.
122
+ * @param langchainModel - The LangChain chat model to use.
123
+ * @deprecated Use {@link createLangchainChatModel} with
124
+ * {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent} instead.
125
+ * @alpha
126
+ */
127
+ export function createLegacyLangchainChatModel(langchainModel) {
128
+ return new LegacyLangchainChatModel(langchainModel);
129
+ }
130
+ class LegacyLangchainChatModel {
18
131
  constructor(model) {
19
132
  this.model = model;
20
133
  this.messages = [];
@@ -27,6 +140,7 @@ class LangchainChatModel {
27
140
  appendContext(text) {
28
141
  this.messages.push(new SystemMessage(text));
29
142
  }
143
+ // eslint-disable-next-line import-x/no-deprecated
30
144
  async query(query) {
31
145
  this.messages.push(new HumanMessage(query.text));
32
146
  return this.queryEdit(async (js) => query.edit(js));
@@ -36,7 +150,9 @@ class LangchainChatModel {
36
150
  name: this.editToolName,
37
151
  description: "Invokes a JavaScript code snippet to edit a tree of application data.",
38
152
  });
39
- const runnable = this.model.bindTools?.([editingTool], { tool_choice: "auto" });
153
+ const runnable = this.model.bindTools?.([editingTool], {
154
+ tool_choice: "auto",
155
+ });
40
156
  if (runnable === undefined) {
41
157
  throw new UsageError("LLM client must support function calling or tool use.");
42
158
  }
@@ -73,4 +189,5 @@ function isEditResult(value) {
73
189
  return (typeof value.type === "string" &&
74
190
  typeof value.message === "string");
75
191
  }
192
+ // #endregion
76
193
  //# sourceMappingURL=chatModel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chatModel.js","sourceRoot":"","sources":["../src/chatModel.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/* eslint-disable import-x/no-internal-modules */\n\nimport { UsageError } from \"@fluidframework/telemetry-utils/internal\";\nimport type {\n\tEditResult,\n\tSharedTreeChatModel,\n\tSharedTreeChatQuery,\n} from \"@fluidframework/tree-agent/alpha\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { tool } from \"@langchain/core/tools\";\n\n/**\n * Creates a `SharedTreeChatModel` that uses the LangChain library to connect to the underlying LLM.\n * @param langchainModel - The LangChain chat model to use.\n * @alpha\n */\nexport function createLangchainChatModel(langchainModel: BaseChatModel): SharedTreeChatModel {\n\treturn new LangchainChatModel(langchainModel);\n}\n\nclass LangchainChatModel implements SharedTreeChatModel {\n\tprivate readonly messages: BaseMessage[] = [];\n\n\tpublic constructor(private readonly model: BaseChatModel) {}\n\n\tpublic readonly editToolName = \"GenerateTreeEditingCode\";\n\n\tpublic get name(): string | undefined {\n\t\tconst name = this.model.metadata?.modelName;\n\t\treturn typeof name === \"string\" ? name : undefined;\n\t}\n\n\tpublic appendContext(text: string): void {\n\t\tthis.messages.push(new SystemMessage(text));\n\t}\n\n\tpublic async query(query: SharedTreeChatQuery): Promise<string> {\n\t\tthis.messages.push(new HumanMessage(query.text));\n\t\treturn this.queryEdit(async (js: string) => query.edit(js));\n\t}\n\n\tprivate async queryEdit(edit: SharedTreeChatQuery[\"edit\"]): Promise<string> {\n\t\tconst editingTool = tool(edit, {\n\t\t\tname: this.editToolName,\n\t\t\tdescription: \"Invokes a JavaScript code snippet to edit a tree of application data.\",\n\t\t});\n\t\tconst runnable = this.model.bindTools?.([editingTool], { tool_choice: \"auto\" });\n\t\tif (runnable === undefined) {\n\t\t\tthrow new UsageError(\"LLM client must support function calling or tool use.\");\n\t\t}\n\n\t\tconst responseMessage = await runnable.invoke(this.messages);\n\t\tthis.messages.push(responseMessage);\n\n\t\tif (responseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0) {\n\t\t\tfor (const toolCall of responseMessage.tool_calls) {\n\t\t\t\tswitch (toolCall.name) {\n\t\t\t\t\tcase editingTool.name: {\n\t\t\t\t\t\tconst toolResult = await editingTool.invoke(toolCall);\n\t\t\t\t\t\tthis.messages.push(toolResult);\n\t\t\t\t\t\tconst editResult: unknown = JSON.parse(toolResult.text);\n\t\t\t\t\t\tif (isEditResult(editResult) && editResult.type === \"tooManyEditsError\") {\n\t\t\t\t\t\t\treturn editResult.message;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn this.queryEdit(edit);\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tthis.messages.push(new HumanMessage(`Unrecognized tool call: ${toolCall.name}`));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn responseMessage.text;\n\t}\n}\n\n/**\n * Type guard for {@link EditResult}.\n */\nfunction isEditResult(value: unknown): value is EditResult {\n\tif (value === null || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn (\n\t\ttypeof (value as EditResult).type === \"string\" &&\n\t\ttypeof (value as EditResult).message === \"string\"\n\t);\n}\n"]}
1
+ {"version":3,"file":"chatModel.js","sourceRoot":"","sources":["../src/chatModel.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { UsageError } from \"@fluidframework/telemetry-utils/internal\";\nimport type {\n\tEditResult,\n\tSharedTreeChatModel,\n\tTreeAgentChatMessage,\n\tTreeAgentChatResponse,\n\tSharedTreeChatQuery, // eslint-disable-line import-x/no-deprecated\n} from \"@fluidframework/tree-agent/alpha\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\"; // eslint-disable-line import-x/no-internal-modules\nimport type { BaseMessage } from \"@langchain/core/messages\"; // eslint-disable-line import-x/no-internal-modules\nimport { AIMessage, HumanMessage, SystemMessage, ToolMessage } from \"@langchain/core/messages\"; // eslint-disable-line import-x/no-internal-modules\nimport { tool } from \"@langchain/core/tools\"; // eslint-disable-line import-x/no-internal-modules\n\n// #region New stateless implementation\n\n/**\n * Creates a stateless {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.\n * @remarks Use with {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent}.\n * @param langchainModel - The LangChain chat model to use.\n * @alpha\n */\nexport function createLangchainChatModel(langchainModel: BaseChatModel): SharedTreeChatModel {\n\treturn new LangchainChatModel(langchainModel);\n}\n\n/**\n * Stateless LangChain adapter for {@link @fluidframework/tree-agent#SharedTreeChatModel}.\n * @remarks This class does not maintain internal message history. All context is provided\n * via the `history` parameter to {@link LangchainChatModel.invoke}.\n */\nclass LangchainChatModel implements SharedTreeChatModel {\n\tpublic readonly editToolName = \"GenerateTreeEditingCode\";\n\n\tpublic constructor(private readonly model: BaseChatModel) {}\n\n\tpublic get name(): string | undefined {\n\t\tconst name = this.model.metadata?.modelName;\n\t\treturn typeof name === \"string\" ? name : undefined;\n\t}\n\n\tpublic async invoke(\n\t\thistory: readonly TreeAgentChatMessage[],\n\t): Promise<TreeAgentChatResponse> {\n\t\t// Convert TreeAgentChatMessage[] to LangChain BaseMessage[]\n\t\tconst messages: BaseMessage[] = convertToLangchainMessages(history);\n\n\t\t// Create a placeholder tool definition so the LLM knows the tool exists.\n\t\t// The actual execution is handled by the agent's edit loop — this tool\n\t\t// is never directly invoked, it only tells the LLM the tool signature.\n\t\tconst editingTool = tool(async (js: string) => js, {\n\t\t\tname: this.editToolName,\n\t\t\tdescription: \"Invokes a JavaScript code snippet to edit a tree of application data.\",\n\t\t});\n\n\t\tconst runnable = this.model.bindTools?.([editingTool], {\n\t\t\ttool_choice: \"auto\",\n\t\t});\n\t\tif (runnable === undefined) {\n\t\t\tthrow new UsageError(\"LLM client must support function calling or tool use.\");\n\t\t}\n\n\t\tconst responseMessage = await runnable.invoke(messages);\n\n\t\t// Parse the response into TreeAgentChatResponse.\n\t\t// Return the first tool call as a tool_call message, preserving the raw args.\n\t\t// Arg parsing (extracting code) is the agent's responsibility.\n\t\tconst firstToolCall =\n\t\t\tresponseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0\n\t\t\t\t? responseMessage.tool_calls[0]\n\t\t\t\t: undefined;\n\t\tif (firstToolCall !== undefined) {\n\t\t\treturn {\n\t\t\t\trole: \"tool_call\",\n\t\t\t\ttoolCallId: firstToolCall.id,\n\t\t\t\ttoolName: firstToolCall.name,\n\t\t\t\ttoolArgs: firstToolCall.args,\n\t\t\t};\n\t\t}\n\n\t\tconst content =\n\t\t\ttypeof responseMessage.text === \"string\"\n\t\t\t\t? responseMessage.text\n\t\t\t\t: typeof responseMessage.content === \"string\"\n\t\t\t\t\t? responseMessage.content\n\t\t\t\t\t: JSON.stringify(responseMessage.content);\n\t\treturn { role: \"assistant\", content };\n\t}\n}\n\n/**\n * Converts an array of {@link TreeAgentChatMessage} to LangChain {@link BaseMessage} format.\n */\nfunction convertToLangchainMessages(history: readonly TreeAgentChatMessage[]): BaseMessage[] {\n\tconst messages: BaseMessage[] = [];\n\tfor (const msg of history) {\n\t\tswitch (msg.role) {\n\t\t\tcase \"system\": {\n\t\t\t\tmessages.push(new SystemMessage(msg.content));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"user\": {\n\t\t\t\tmessages.push(new HumanMessage(msg.content));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"assistant\": {\n\t\t\t\tmessages.push(new AIMessage(msg.content));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"tool_call\": {\n\t\t\t\tmessages.push(\n\t\t\t\t\tnew AIMessage({\n\t\t\t\t\t\tcontent: \"\",\n\t\t\t\t\t\ttool_calls: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tid: msg.toolCallId,\n\t\t\t\t\t\t\t\tname: msg.toolName,\n\t\t\t\t\t\t\t\targs: msg.toolArgs,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"tool_result\": {\n\t\t\t\tmessages.push(\n\t\t\t\t\tnew ToolMessage({\n\t\t\t\t\t\tcontent: msg.content,\n\t\t\t\t\t\ttool_call_id: msg.toolCallId ?? \"\",\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// No default\n\t\t}\n\t}\n\treturn messages;\n}\n\n// #endregion\n\n// #region Legacy stateful implementation\n\n/**\n * Creates a legacy stateful {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.\n * @remarks This implementation maintains internal message history and manages the edit loop via the\n * {@link @fluidframework/tree-agent#SharedTreeChatQuery.edit | edit} callback pattern.\n * Use with {@link @fluidframework/tree-agent#SharedTreeSemanticAgent}.\n * @param langchainModel - The LangChain chat model to use.\n * @deprecated Use {@link createLangchainChatModel} with\n * {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent} instead.\n * @alpha\n */\nexport function createLegacyLangchainChatModel(\n\tlangchainModel: BaseChatModel,\n): SharedTreeChatModel {\n\treturn new LegacyLangchainChatModel(langchainModel);\n}\n\nclass LegacyLangchainChatModel implements SharedTreeChatModel {\n\tprivate readonly messages: BaseMessage[] = [];\n\n\tpublic constructor(private readonly model: BaseChatModel) {}\n\n\tpublic readonly editToolName = \"GenerateTreeEditingCode\";\n\n\tpublic get name(): string | undefined {\n\t\tconst name = this.model.metadata?.modelName;\n\t\treturn typeof name === \"string\" ? name : undefined;\n\t}\n\n\tpublic appendContext(text: string): void {\n\t\tthis.messages.push(new SystemMessage(text));\n\t}\n\n\t// eslint-disable-next-line import-x/no-deprecated\n\tpublic async query(query: SharedTreeChatQuery): Promise<string> {\n\t\tthis.messages.push(new HumanMessage(query.text));\n\t\treturn this.queryEdit(async (js: string) => query.edit(js));\n\t}\n\n\tprivate async queryEdit(\n\t\tedit: SharedTreeChatQuery[\"edit\"], // eslint-disable-line import-x/no-deprecated\n\t): Promise<string> {\n\t\tconst editingTool = tool(edit, {\n\t\t\tname: this.editToolName,\n\t\t\tdescription: \"Invokes a JavaScript code snippet to edit a tree of application data.\",\n\t\t});\n\t\tconst runnable = this.model.bindTools?.([editingTool], {\n\t\t\ttool_choice: \"auto\",\n\t\t});\n\t\tif (runnable === undefined) {\n\t\t\tthrow new UsageError(\"LLM client must support function calling or tool use.\");\n\t\t}\n\n\t\tconst responseMessage = await runnable.invoke(this.messages);\n\t\tthis.messages.push(responseMessage);\n\n\t\tif (responseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0) {\n\t\t\tfor (const toolCall of responseMessage.tool_calls) {\n\t\t\t\tswitch (toolCall.name) {\n\t\t\t\t\tcase editingTool.name: {\n\t\t\t\t\t\tconst toolResult = await editingTool.invoke(toolCall);\n\t\t\t\t\t\tthis.messages.push(toolResult);\n\t\t\t\t\t\tconst editResult: unknown = JSON.parse(toolResult.text);\n\t\t\t\t\t\tif (isEditResult(editResult) && editResult.type === \"tooManyEditsError\") {\n\t\t\t\t\t\t\treturn editResult.message;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn this.queryEdit(edit);\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tthis.messages.push(new HumanMessage(`Unrecognized tool call: ${toolCall.name}`));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn responseMessage.text;\n\t}\n}\n\n/**\n * Type guard for {@link EditResult}.\n */\nfunction isEditResult(value: unknown): value is EditResult {\n\tif (value === null || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn (\n\t\ttypeof (value as EditResult).type === \"string\" &&\n\t\ttypeof (value as EditResult).message === \"string\"\n\t);\n}\n\n// #endregion\n"]}
package/lib/index.d.ts CHANGED
@@ -7,5 +7,5 @@
7
7
  *
8
8
  * @packageDocumentation
9
9
  */
10
- export { createLangchainChatModel } from "./chatModel.js";
10
+ export { createLangchainChatModel, createLegacyLangchainChatModel } from "./chatModel.js";
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EAAE,wBAAwB,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC"}
package/lib/index.js CHANGED
@@ -7,5 +7,5 @@
7
7
  *
8
8
  * @packageDocumentation
9
9
  */
10
- export { createLangchainChatModel } from "./chatModel.js";
10
+ export { createLangchainChatModel, createLegacyLangchainChatModel } from "./chatModel.js";
11
11
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * LangChain integration helpers for the {@link @fluidframework/tree-agent#SharedTreeSemanticAgent | SharedTreeSemanticAgent}.\n *\n * @packageDocumentation\n */\n\nexport { createLangchainChatModel } from \"./chatModel.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EAAE,wBAAwB,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * LangChain integration helpers for the {@link @fluidframework/tree-agent#SharedTreeSemanticAgent | SharedTreeSemanticAgent}.\n *\n * @packageDocumentation\n */\n\nexport { createLangchainChatModel, createLegacyLangchainChatModel } from \"./chatModel.js\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/tree-agent-langchain",
3
- "version": "2.90.0",
3
+ "version": "2.91.0",
4
4
  "description": "LangChain integration helpers for @fluidframework/tree-agent",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -69,21 +69,21 @@
69
69
  "temp-directory": "nyc/.nyc_output"
70
70
  },
71
71
  "dependencies": {
72
- "@fluidframework/telemetry-utils": "~2.90.0",
73
- "@fluidframework/tree-agent": "~2.90.0",
72
+ "@fluidframework/telemetry-utils": "~2.91.0",
73
+ "@fluidframework/tree-agent": "~2.91.0",
74
74
  "@langchain/core": "^0.3.80"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@arethetypeswrong/cli": "^0.18.2",
78
- "@biomejs/biome": "~1.9.3",
79
- "@fluid-internal/mocha-test-setup": "~2.90.0",
78
+ "@biomejs/biome": "~2.4.5",
79
+ "@fluid-internal/mocha-test-setup": "~2.91.0",
80
80
  "@fluid-tools/build-cli": "^0.63.0",
81
81
  "@fluidframework/build-common": "^2.0.3",
82
82
  "@fluidframework/build-tools": "^0.63.0",
83
- "@fluidframework/core-utils": "~2.90.0",
84
- "@fluidframework/eslint-config-fluid": "~2.90.0",
85
- "@fluidframework/runtime-utils": "~2.90.0",
86
- "@fluidframework/tree": "~2.90.0",
83
+ "@fluidframework/core-utils": "~2.91.0",
84
+ "@fluidframework/eslint-config-fluid": "~2.91.0",
85
+ "@fluidframework/runtime-utils": "~2.91.0",
86
+ "@fluidframework/tree": "~2.91.0",
87
87
  "@langchain/anthropic": "^0.3.24",
88
88
  "@langchain/google-genai": "^0.2.16",
89
89
  "@langchain/openai": "^0.6.12",
package/src/chatModel.ts CHANGED
@@ -3,21 +3,24 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- /* eslint-disable import-x/no-internal-modules */
7
-
8
6
  import { UsageError } from "@fluidframework/telemetry-utils/internal";
9
7
  import type {
10
8
  EditResult,
11
9
  SharedTreeChatModel,
12
- SharedTreeChatQuery,
10
+ TreeAgentChatMessage,
11
+ TreeAgentChatResponse,
12
+ SharedTreeChatQuery, // eslint-disable-line import-x/no-deprecated
13
13
  } from "@fluidframework/tree-agent/alpha";
14
- import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
15
- import type { BaseMessage } from "@langchain/core/messages";
16
- import { HumanMessage, SystemMessage } from "@langchain/core/messages";
17
- import { tool } from "@langchain/core/tools";
14
+ import type { BaseChatModel } from "@langchain/core/language_models/chat_models"; // eslint-disable-line import-x/no-internal-modules
15
+ import type { BaseMessage } from "@langchain/core/messages"; // eslint-disable-line import-x/no-internal-modules
16
+ import { AIMessage, HumanMessage, SystemMessage, ToolMessage } from "@langchain/core/messages"; // eslint-disable-line import-x/no-internal-modules
17
+ import { tool } from "@langchain/core/tools"; // eslint-disable-line import-x/no-internal-modules
18
+
19
+ // #region New stateless implementation
18
20
 
19
21
  /**
20
- * Creates a `SharedTreeChatModel` that uses the LangChain library to connect to the underlying LLM.
22
+ * Creates a stateless {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
23
+ * @remarks Use with {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent}.
21
24
  * @param langchainModel - The LangChain chat model to use.
22
25
  * @alpha
23
26
  */
@@ -25,7 +28,140 @@ export function createLangchainChatModel(langchainModel: BaseChatModel): SharedT
25
28
  return new LangchainChatModel(langchainModel);
26
29
  }
27
30
 
31
+ /**
32
+ * Stateless LangChain adapter for {@link @fluidframework/tree-agent#SharedTreeChatModel}.
33
+ * @remarks This class does not maintain internal message history. All context is provided
34
+ * via the `history` parameter to {@link LangchainChatModel.invoke}.
35
+ */
28
36
  class LangchainChatModel implements SharedTreeChatModel {
37
+ public readonly editToolName = "GenerateTreeEditingCode";
38
+
39
+ public constructor(private readonly model: BaseChatModel) {}
40
+
41
+ public get name(): string | undefined {
42
+ const name = this.model.metadata?.modelName;
43
+ return typeof name === "string" ? name : undefined;
44
+ }
45
+
46
+ public async invoke(
47
+ history: readonly TreeAgentChatMessage[],
48
+ ): Promise<TreeAgentChatResponse> {
49
+ // Convert TreeAgentChatMessage[] to LangChain BaseMessage[]
50
+ const messages: BaseMessage[] = convertToLangchainMessages(history);
51
+
52
+ // Create a placeholder tool definition so the LLM knows the tool exists.
53
+ // The actual execution is handled by the agent's edit loop — this tool
54
+ // is never directly invoked, it only tells the LLM the tool signature.
55
+ const editingTool = tool(async (js: string) => js, {
56
+ name: this.editToolName,
57
+ description: "Invokes a JavaScript code snippet to edit a tree of application data.",
58
+ });
59
+
60
+ const runnable = this.model.bindTools?.([editingTool], {
61
+ tool_choice: "auto",
62
+ });
63
+ if (runnable === undefined) {
64
+ throw new UsageError("LLM client must support function calling or tool use.");
65
+ }
66
+
67
+ const responseMessage = await runnable.invoke(messages);
68
+
69
+ // Parse the response into TreeAgentChatResponse.
70
+ // Return the first tool call as a tool_call message, preserving the raw args.
71
+ // Arg parsing (extracting code) is the agent's responsibility.
72
+ const firstToolCall =
73
+ responseMessage.tool_calls !== undefined && responseMessage.tool_calls.length > 0
74
+ ? responseMessage.tool_calls[0]
75
+ : undefined;
76
+ if (firstToolCall !== undefined) {
77
+ return {
78
+ role: "tool_call",
79
+ toolCallId: firstToolCall.id,
80
+ toolName: firstToolCall.name,
81
+ toolArgs: firstToolCall.args,
82
+ };
83
+ }
84
+
85
+ const content =
86
+ typeof responseMessage.text === "string"
87
+ ? responseMessage.text
88
+ : typeof responseMessage.content === "string"
89
+ ? responseMessage.content
90
+ : JSON.stringify(responseMessage.content);
91
+ return { role: "assistant", content };
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Converts an array of {@link TreeAgentChatMessage} to LangChain {@link BaseMessage} format.
97
+ */
98
+ function convertToLangchainMessages(history: readonly TreeAgentChatMessage[]): BaseMessage[] {
99
+ const messages: BaseMessage[] = [];
100
+ for (const msg of history) {
101
+ switch (msg.role) {
102
+ case "system": {
103
+ messages.push(new SystemMessage(msg.content));
104
+ break;
105
+ }
106
+ case "user": {
107
+ messages.push(new HumanMessage(msg.content));
108
+ break;
109
+ }
110
+ case "assistant": {
111
+ messages.push(new AIMessage(msg.content));
112
+ break;
113
+ }
114
+ case "tool_call": {
115
+ messages.push(
116
+ new AIMessage({
117
+ content: "",
118
+ tool_calls: [
119
+ {
120
+ id: msg.toolCallId,
121
+ name: msg.toolName,
122
+ args: msg.toolArgs,
123
+ },
124
+ ],
125
+ }),
126
+ );
127
+ break;
128
+ }
129
+ case "tool_result": {
130
+ messages.push(
131
+ new ToolMessage({
132
+ content: msg.content,
133
+ tool_call_id: msg.toolCallId ?? "",
134
+ }),
135
+ );
136
+ break;
137
+ }
138
+ // No default
139
+ }
140
+ }
141
+ return messages;
142
+ }
143
+
144
+ // #endregion
145
+
146
+ // #region Legacy stateful implementation
147
+
148
+ /**
149
+ * Creates a legacy stateful {@link @fluidframework/tree-agent#SharedTreeChatModel} backed by LangChain.
150
+ * @remarks This implementation maintains internal message history and manages the edit loop via the
151
+ * {@link @fluidframework/tree-agent#SharedTreeChatQuery.edit | edit} callback pattern.
152
+ * Use with {@link @fluidframework/tree-agent#SharedTreeSemanticAgent}.
153
+ * @param langchainModel - The LangChain chat model to use.
154
+ * @deprecated Use {@link createLangchainChatModel} with
155
+ * {@link @fluidframework/tree-agent#createTreeAgent | createTreeAgent} instead.
156
+ * @alpha
157
+ */
158
+ export function createLegacyLangchainChatModel(
159
+ langchainModel: BaseChatModel,
160
+ ): SharedTreeChatModel {
161
+ return new LegacyLangchainChatModel(langchainModel);
162
+ }
163
+
164
+ class LegacyLangchainChatModel implements SharedTreeChatModel {
29
165
  private readonly messages: BaseMessage[] = [];
30
166
 
31
167
  public constructor(private readonly model: BaseChatModel) {}
@@ -41,17 +177,22 @@ class LangchainChatModel implements SharedTreeChatModel {
41
177
  this.messages.push(new SystemMessage(text));
42
178
  }
43
179
 
180
+ // eslint-disable-next-line import-x/no-deprecated
44
181
  public async query(query: SharedTreeChatQuery): Promise<string> {
45
182
  this.messages.push(new HumanMessage(query.text));
46
183
  return this.queryEdit(async (js: string) => query.edit(js));
47
184
  }
48
185
 
49
- private async queryEdit(edit: SharedTreeChatQuery["edit"]): Promise<string> {
186
+ private async queryEdit(
187
+ edit: SharedTreeChatQuery["edit"], // eslint-disable-line import-x/no-deprecated
188
+ ): Promise<string> {
50
189
  const editingTool = tool(edit, {
51
190
  name: this.editToolName,
52
191
  description: "Invokes a JavaScript code snippet to edit a tree of application data.",
53
192
  });
54
- const runnable = this.model.bindTools?.([editingTool], { tool_choice: "auto" });
193
+ const runnable = this.model.bindTools?.([editingTool], {
194
+ tool_choice: "auto",
195
+ });
55
196
  if (runnable === undefined) {
56
197
  throw new UsageError("LLM client must support function calling or tool use.");
57
198
  }
@@ -94,3 +235,5 @@ function isEditResult(value: unknown): value is EditResult {
94
235
  typeof (value as EditResult).message === "string"
95
236
  );
96
237
  }
238
+
239
+ // #endregion
package/src/index.ts CHANGED
@@ -9,4 +9,4 @@
9
9
  * @packageDocumentation
10
10
  */
11
11
 
12
- export { createLangchainChatModel } from "./chatModel.js";
12
+ export { createLangchainChatModel, createLegacyLangchainChatModel } from "./chatModel.js";