From a60e8909cc0a29c92dd32f1c250288a885f66566 Mon Sep 17 00:00:00 2001 From: ran Date: Fri, 26 Sep 2025 12:51:23 +0200 Subject: [PATCH] chore: fix predictive state update test for LG TS --- .../predictiveStateUpdatePage.spec.ts | 6 +++--- typescript-sdk/apps/dojo/src/files.json | 4 ++-- .../src/agents/predictive_state_updates/agent.ts | 16 ++-------------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphTypescriptTests/predictiveStateUpdatePage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphTypescriptTests/predictiveStateUpdatePage.spec.ts index 9a7f2dea2..d82de7a04 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/langgraphTypescriptTests/predictiveStateUpdatePage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/langgraphTypescriptTests/predictiveStateUpdatePage.spec.ts @@ -5,7 +5,7 @@ import { } from "../../test-isolation-helper"; import { PredictiveStateUpdatesPage } from "../../pages/langGraphPages/PredictiveStateUpdatesPage"; -test.fixme("Predictive Status Updates Feature", () => { +test.describe("Predictive Status Updates Feature", () => { test("[LangGraph] should interact with agent and approve asked changes", async ({ page, }) => { @@ -47,7 +47,7 @@ test.fixme("Predictive Status Updates Feature", () => { }); }); - test.fixme("[LangGraph] should interact with agent and reject asked changes", async ({ + test("[LangGraph] should interact with agent and reject asked changes", async ({ page, }) => { await retryOnAIFailure(async () => { @@ -88,4 +88,4 @@ test.fixme("Predictive Status Updates Feature", () => { expect(dragonNameAfterRejection).not.toBe("Lola"); }); }); -}); \ No newline at end of file +}); diff --git a/typescript-sdk/apps/dojo/src/files.json b/typescript-sdk/apps/dojo/src/files.json index 0c5db02df..754c74e4d 100644 --- a/typescript-sdk/apps/dojo/src/files.json +++ b/typescript-sdk/apps/dojo/src/files.json @@ -590,7 +590,7 @@ }, { "name": "agent.ts", - "content": "/**\n * A demo of predictive state updates using LangGraph.\n */\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { SystemMessage } from \"@langchain/core/messages\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { Command, Annotation, MessagesAnnotation, StateGraph, END, START } from \"@langchain/langgraph\";\n\nconst WRITE_DOCUMENT_TOOL = {\n type: \"function\",\n function: {\n name: \"write_document_local\",\n description: [\n \"Write a document. Use markdown formatting to format the document.\",\n \"It's good to format the document extensively so it's easy to read.\",\n \"You can use all kinds of markdown.\",\n \"However, do not use italic or strike-through formatting, it's reserved for another purpose.\",\n \"You MUST write the full document, even when changing only a few words.\",\n \"When making edits to the document, try to make them minimal - do not change every word.\",\n \"Keep stories SHORT!\"\n ].join(\" \"),\n parameters: {\n type: \"object\",\n properties: {\n document: {\n type: \"string\",\n description: \"The document to write\"\n },\n },\n }\n }\n};\n\nexport const AgentStateAnnotation = Annotation.Root({\n document: Annotation({\n reducer: (x, y) => y ?? x,\n default: () => undefined\n }),\n tools: Annotation(),\n ...MessagesAnnotation.spec,\n});\nexport type AgentState = typeof AgentStateAnnotation.State;\n\nasync function startFlow(state: AgentState, config?: RunnableConfig): Promise {\n /**\n * This is the entry point for the flow.\n */\n return new Command({\n goto: \"chat_node\"\n });\n}\n\nasync function chatNode(state: AgentState, config?: RunnableConfig): Promise {\n /**\n * Standard chat node.\n */\n\n const systemPrompt = `\n You are a helpful assistant for writing documents.\n To write the document, you MUST use the write_document_local tool.\n You MUST write the full document, even when changing only a few words.\n When you wrote the document, DO NOT repeat it as a message.\n Just briefly summarize the changes you made. 2 sentences max.\n This is the current state of the document: ----\\n ${state.document || ''}\\n-----\n `;\n\n // Define the model\n const model = new ChatOpenAI({ model: \"gpt-4o\" });\n\n // Define config for the model with emit_intermediate_state to stream tool calls to frontend\n if (!config) {\n config = { recursionLimit: 25 };\n }\n\n // Use \"predict_state\" metadata to set up streaming for the write_document_local tool\n if (!config.metadata) config.metadata = {};\n config.metadata.predict_state = [{\n state_key: \"document\",\n tool: \"write_document_local\",\n tool_argument: \"document\"\n }];\n\n // Bind the tools to the model\n const modelWithTools = model.bindTools(\n [\n ...state.tools,\n WRITE_DOCUMENT_TOOL\n ],\n {\n // Disable parallel tool calls to avoid race conditions\n parallel_tool_calls: false,\n }\n );\n\n // Run the model to generate a response\n const response = await modelWithTools.invoke([\n new SystemMessage({ content: systemPrompt }),\n ...state.messages,\n ], config);\n\n // Update messages with the response\n const messages = [...state.messages, response];\n\n // Extract any tool calls from the response\n if (response.tool_calls && response.tool_calls.length > 0) {\n const toolCall = response.tool_calls[0];\n\n if (toolCall.name === \"write_document_local\") {\n // Add the tool response to messages\n const toolResponse = {\n role: \"tool\" as const,\n content: \"Document written.\",\n tool_call_id: toolCall.id\n };\n\n // Add confirmation tool call\n const confirmToolCall = {\n role: \"assistant\" as const,\n content: \"\",\n tool_calls: [{\n id: uuidv4(),\n type: \"function\" as const,\n function: {\n name: \"confirm_changes\",\n arguments: \"{}\"\n }\n }]\n };\n\n const updatedMessages = [...messages, toolResponse, confirmToolCall];\n\n // Return Command to route to end\n return new Command({\n goto: END,\n update: {\n messages: updatedMessages,\n document: toolCall.args.document\n }\n });\n }\n }\n\n // If no tool was called, go to end\n return new Command({\n goto: END,\n update: {\n messages: messages\n }\n });\n}\n\n// Define the graph\nconst workflow = new StateGraph(AgentStateAnnotation);\n\n// Add nodes\nworkflow.addNode(\"start_flow\", startFlow);\nworkflow.addNode(\"chat_node\", chatNode);\n\n// Add edges\nworkflow.setEntryPoint(\"start_flow\");\nworkflow.addEdge(START, \"start_flow\");\nworkflow.addEdge(\"start_flow\", \"chat_node\");\nworkflow.addEdge(\"chat_node\", END);\n\n// Compile the graph\nexport const predictiveStateUpdatesGraph = workflow.compile();", + "content": "/**\n * A demo of predictive state updates using LangGraph.\n */\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { SystemMessage } from \"@langchain/core/messages\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { Command, Annotation, MessagesAnnotation, StateGraph, END, START } from \"@langchain/langgraph\";\n\nconst WRITE_DOCUMENT_TOOL = {\n type: \"function\",\n function: {\n name: \"write_document_local\",\n description: [\n \"Write a document. Use markdown formatting to format the document.\",\n \"It's good to format the document extensively so it's easy to read.\",\n \"You can use all kinds of markdown.\",\n \"However, do not use italic or strike-through formatting, it's reserved for another purpose.\",\n \"You MUST write the full document, even when changing only a few words.\",\n \"When making edits to the document, try to make them minimal - do not change every word.\",\n \"Keep stories SHORT!\"\n ].join(\" \"),\n parameters: {\n type: \"object\",\n properties: {\n document: {\n type: \"string\",\n description: \"The document to write\"\n },\n },\n }\n }\n};\n\nexport const AgentStateAnnotation = Annotation.Root({\n document: Annotation({\n reducer: (x, y) => y ?? x,\n default: () => undefined\n }),\n tools: Annotation(),\n ...MessagesAnnotation.spec,\n});\nexport type AgentState = typeof AgentStateAnnotation.State;\n\nasync function chatNode(state: AgentState, config?: RunnableConfig): Promise {\n /**\n * Standard chat node.\n */\n\n const systemPrompt = `\n You are a helpful assistant for writing documents.\n To write the document, you MUST use the write_document_local tool.\n You MUST write the full document, even when changing only a few words.\n When you wrote the document, DO NOT repeat it as a message.\n Just briefly summarize the changes you made. 2 sentences max.\n This is the current state of the document: ----\\n ${state.document || ''}\\n-----\n `;\n\n // Define the model\n const model = new ChatOpenAI({ model: \"gpt-4o\" });\n\n // Define config for the model with emit_intermediate_state to stream tool calls to frontend\n if (!config) {\n config = { recursionLimit: 25 };\n }\n\n // Use \"predict_state\" metadata to set up streaming for the write_document_local tool\n if (!config.metadata) config.metadata = {};\n config.metadata.predict_state = [{\n state_key: \"document\",\n tool: \"write_document_local\",\n tool_argument: \"document\"\n }];\n\n // Bind the tools to the model\n const modelWithTools = model.bindTools(\n [\n ...state.tools,\n WRITE_DOCUMENT_TOOL\n ],\n {\n // Disable parallel tool calls to avoid race conditions\n parallel_tool_calls: false,\n }\n );\n\n // Run the model to generate a response\n const response = await modelWithTools.invoke([\n new SystemMessage({ content: systemPrompt }),\n ...state.messages,\n ], config);\n\n // Update messages with the response\n const messages = [...state.messages, response];\n\n // Extract any tool calls from the response\n if (response.tool_calls && response.tool_calls.length > 0) {\n const toolCall = response.tool_calls[0];\n\n if (toolCall.name === \"write_document_local\") {\n // Add the tool response to messages\n const toolResponse = {\n role: \"tool\" as const,\n content: \"Document written.\",\n tool_call_id: toolCall.id\n };\n\n // Add confirmation tool call\n const confirmToolCall = {\n role: \"assistant\" as const,\n content: \"\",\n tool_calls: [{\n id: uuidv4(),\n type: \"function\" as const,\n function: {\n name: \"confirm_changes\",\n arguments: \"{}\"\n }\n }]\n };\n\n const updatedMessages = [...messages, toolResponse, confirmToolCall];\n\n // Return Command to route to end\n return new Command({\n goto: END,\n update: {\n messages: updatedMessages,\n document: toolCall.args.document\n }\n });\n }\n }\n\n // If no tool was called, go to end\n return new Command({\n goto: END,\n update: {\n messages: messages\n }\n });\n}\n\n// Define the graph\nconst workflow = new StateGraph(AgentStateAnnotation);\n\n// Add nodes\nworkflow.addNode(\"chat_node\", chatNode);\n\n// Add edges\nworkflow.addEdge(START, \"chat_node\");\nworkflow.addEdge(\"chat_node\", END);\n\n// Compile the graph\nexport const predictiveStateUpdatesGraph = workflow.compile();", "language": "ts", "type": "file" } @@ -1049,7 +1049,7 @@ }, { "name": "agent.ts", - "content": "/**\n * A demo of predictive state updates using LangGraph.\n */\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { SystemMessage } from \"@langchain/core/messages\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { Command, Annotation, MessagesAnnotation, StateGraph, END, START } from \"@langchain/langgraph\";\n\nconst WRITE_DOCUMENT_TOOL = {\n type: \"function\",\n function: {\n name: \"write_document_local\",\n description: [\n \"Write a document. Use markdown formatting to format the document.\",\n \"It's good to format the document extensively so it's easy to read.\",\n \"You can use all kinds of markdown.\",\n \"However, do not use italic or strike-through formatting, it's reserved for another purpose.\",\n \"You MUST write the full document, even when changing only a few words.\",\n \"When making edits to the document, try to make them minimal - do not change every word.\",\n \"Keep stories SHORT!\"\n ].join(\" \"),\n parameters: {\n type: \"object\",\n properties: {\n document: {\n type: \"string\",\n description: \"The document to write\"\n },\n },\n }\n }\n};\n\nexport const AgentStateAnnotation = Annotation.Root({\n document: Annotation({\n reducer: (x, y) => y ?? x,\n default: () => undefined\n }),\n tools: Annotation(),\n ...MessagesAnnotation.spec,\n});\nexport type AgentState = typeof AgentStateAnnotation.State;\n\nasync function startFlow(state: AgentState, config?: RunnableConfig): Promise {\n /**\n * This is the entry point for the flow.\n */\n return new Command({\n goto: \"chat_node\"\n });\n}\n\nasync function chatNode(state: AgentState, config?: RunnableConfig): Promise {\n /**\n * Standard chat node.\n */\n\n const systemPrompt = `\n You are a helpful assistant for writing documents.\n To write the document, you MUST use the write_document_local tool.\n You MUST write the full document, even when changing only a few words.\n When you wrote the document, DO NOT repeat it as a message.\n Just briefly summarize the changes you made. 2 sentences max.\n This is the current state of the document: ----\\n ${state.document || ''}\\n-----\n `;\n\n // Define the model\n const model = new ChatOpenAI({ model: \"gpt-4o\" });\n\n // Define config for the model with emit_intermediate_state to stream tool calls to frontend\n if (!config) {\n config = { recursionLimit: 25 };\n }\n\n // Use \"predict_state\" metadata to set up streaming for the write_document_local tool\n if (!config.metadata) config.metadata = {};\n config.metadata.predict_state = [{\n state_key: \"document\",\n tool: \"write_document_local\",\n tool_argument: \"document\"\n }];\n\n // Bind the tools to the model\n const modelWithTools = model.bindTools(\n [\n ...state.tools,\n WRITE_DOCUMENT_TOOL\n ],\n {\n // Disable parallel tool calls to avoid race conditions\n parallel_tool_calls: false,\n }\n );\n\n // Run the model to generate a response\n const response = await modelWithTools.invoke([\n new SystemMessage({ content: systemPrompt }),\n ...state.messages,\n ], config);\n\n // Update messages with the response\n const messages = [...state.messages, response];\n\n // Extract any tool calls from the response\n if (response.tool_calls && response.tool_calls.length > 0) {\n const toolCall = response.tool_calls[0];\n\n if (toolCall.name === \"write_document_local\") {\n // Add the tool response to messages\n const toolResponse = {\n role: \"tool\" as const,\n content: \"Document written.\",\n tool_call_id: toolCall.id\n };\n\n // Add confirmation tool call\n const confirmToolCall = {\n role: \"assistant\" as const,\n content: \"\",\n tool_calls: [{\n id: uuidv4(),\n type: \"function\" as const,\n function: {\n name: \"confirm_changes\",\n arguments: \"{}\"\n }\n }]\n };\n\n const updatedMessages = [...messages, toolResponse, confirmToolCall];\n\n // Return Command to route to end\n return new Command({\n goto: END,\n update: {\n messages: updatedMessages,\n document: toolCall.args.document\n }\n });\n }\n }\n\n // If no tool was called, go to end\n return new Command({\n goto: END,\n update: {\n messages: messages\n }\n });\n}\n\n// Define the graph\nconst workflow = new StateGraph(AgentStateAnnotation);\n\n// Add nodes\nworkflow.addNode(\"start_flow\", startFlow);\nworkflow.addNode(\"chat_node\", chatNode);\n\n// Add edges\nworkflow.setEntryPoint(\"start_flow\");\nworkflow.addEdge(START, \"start_flow\");\nworkflow.addEdge(\"start_flow\", \"chat_node\");\nworkflow.addEdge(\"chat_node\", END);\n\n// Compile the graph\nexport const predictiveStateUpdatesGraph = workflow.compile();", + "content": "/**\n * A demo of predictive state updates using LangGraph.\n */\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { SystemMessage } from \"@langchain/core/messages\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { Command, Annotation, MessagesAnnotation, StateGraph, END, START } from \"@langchain/langgraph\";\n\nconst WRITE_DOCUMENT_TOOL = {\n type: \"function\",\n function: {\n name: \"write_document_local\",\n description: [\n \"Write a document. Use markdown formatting to format the document.\",\n \"It's good to format the document extensively so it's easy to read.\",\n \"You can use all kinds of markdown.\",\n \"However, do not use italic or strike-through formatting, it's reserved for another purpose.\",\n \"You MUST write the full document, even when changing only a few words.\",\n \"When making edits to the document, try to make them minimal - do not change every word.\",\n \"Keep stories SHORT!\"\n ].join(\" \"),\n parameters: {\n type: \"object\",\n properties: {\n document: {\n type: \"string\",\n description: \"The document to write\"\n },\n },\n }\n }\n};\n\nexport const AgentStateAnnotation = Annotation.Root({\n document: Annotation({\n reducer: (x, y) => y ?? x,\n default: () => undefined\n }),\n tools: Annotation(),\n ...MessagesAnnotation.spec,\n});\nexport type AgentState = typeof AgentStateAnnotation.State;\n\nasync function chatNode(state: AgentState, config?: RunnableConfig): Promise {\n /**\n * Standard chat node.\n */\n\n const systemPrompt = `\n You are a helpful assistant for writing documents.\n To write the document, you MUST use the write_document_local tool.\n You MUST write the full document, even when changing only a few words.\n When you wrote the document, DO NOT repeat it as a message.\n Just briefly summarize the changes you made. 2 sentences max.\n This is the current state of the document: ----\\n ${state.document || ''}\\n-----\n `;\n\n // Define the model\n const model = new ChatOpenAI({ model: \"gpt-4o\" });\n\n // Define config for the model with emit_intermediate_state to stream tool calls to frontend\n if (!config) {\n config = { recursionLimit: 25 };\n }\n\n // Use \"predict_state\" metadata to set up streaming for the write_document_local tool\n if (!config.metadata) config.metadata = {};\n config.metadata.predict_state = [{\n state_key: \"document\",\n tool: \"write_document_local\",\n tool_argument: \"document\"\n }];\n\n // Bind the tools to the model\n const modelWithTools = model.bindTools(\n [\n ...state.tools,\n WRITE_DOCUMENT_TOOL\n ],\n {\n // Disable parallel tool calls to avoid race conditions\n parallel_tool_calls: false,\n }\n );\n\n // Run the model to generate a response\n const response = await modelWithTools.invoke([\n new SystemMessage({ content: systemPrompt }),\n ...state.messages,\n ], config);\n\n // Update messages with the response\n const messages = [...state.messages, response];\n\n // Extract any tool calls from the response\n if (response.tool_calls && response.tool_calls.length > 0) {\n const toolCall = response.tool_calls[0];\n\n if (toolCall.name === \"write_document_local\") {\n // Add the tool response to messages\n const toolResponse = {\n role: \"tool\" as const,\n content: \"Document written.\",\n tool_call_id: toolCall.id\n };\n\n // Add confirmation tool call\n const confirmToolCall = {\n role: \"assistant\" as const,\n content: \"\",\n tool_calls: [{\n id: uuidv4(),\n type: \"function\" as const,\n function: {\n name: \"confirm_changes\",\n arguments: \"{}\"\n }\n }]\n };\n\n const updatedMessages = [...messages, toolResponse, confirmToolCall];\n\n // Return Command to route to end\n return new Command({\n goto: END,\n update: {\n messages: updatedMessages,\n document: toolCall.args.document\n }\n });\n }\n }\n\n // If no tool was called, go to end\n return new Command({\n goto: END,\n update: {\n messages: messages\n }\n });\n}\n\n// Define the graph\nconst workflow = new StateGraph(AgentStateAnnotation);\n\n// Add nodes\nworkflow.addNode(\"chat_node\", chatNode);\n\n// Add edges\nworkflow.addEdge(START, \"chat_node\");\nworkflow.addEdge(\"chat_node\", END);\n\n// Compile the graph\nexport const predictiveStateUpdatesGraph = workflow.compile();", "language": "ts", "type": "file" } diff --git a/typescript-sdk/integrations/langgraph/examples/typescript/src/agents/predictive_state_updates/agent.ts b/typescript-sdk/integrations/langgraph/examples/typescript/src/agents/predictive_state_updates/agent.ts index ca074459f..5ffece963 100644 --- a/typescript-sdk/integrations/langgraph/examples/typescript/src/agents/predictive_state_updates/agent.ts +++ b/typescript-sdk/integrations/langgraph/examples/typescript/src/agents/predictive_state_updates/agent.ts @@ -43,15 +43,6 @@ export const AgentStateAnnotation = Annotation.Root({ }); export type AgentState = typeof AgentStateAnnotation.State; -async function startFlow(state: AgentState, config?: RunnableConfig): Promise { - /** - * This is the entry point for the flow. - */ - return new Command({ - goto: "chat_node" - }); -} - async function chatNode(state: AgentState, config?: RunnableConfig): Promise { /** * Standard chat node. @@ -152,16 +143,13 @@ async function chatNode(state: AgentState, config?: RunnableConfig): Promise(AgentStateAnnotation); +const workflow = new StateGraph(AgentStateAnnotation); // Add nodes -workflow.addNode("start_flow", startFlow); workflow.addNode("chat_node", chatNode); // Add edges -workflow.setEntryPoint("start_flow"); -workflow.addEdge(START, "start_flow"); -workflow.addEdge("start_flow", "chat_node"); +workflow.addEdge(START, "chat_node"); workflow.addEdge("chat_node", END); // Compile the graph