From c6416622e43986b3f202c9f6adf560f4454b6d04 Mon Sep 17 00:00:00 2001 From: William Jeynes Date: Wed, 28 Jan 2026 21:26:34 +0000 Subject: [PATCH] start adding dummy nodes --- agent/agent.ts | 30 ++++++++++++++++++++ agent/conditionals/tool_end.ts | 31 ++++++++++++++++++++ agent/index.ts | 33 ---------------------- agent/langgraph.json | 2 +- agent/nodes/dummyNormalisationModel.ts | 11 ++++++++ agent/nodes/dummyRagasMetrics.ts | 11 ++++++++ agent/nodes/dummyTriggerEventModel.ts | 11 ++++++++ agent/nodes/dummyVerificationModel.ts | 11 ++++++++ agent/nodes/model.ts | 39 ++++++++++++++------------ agent/nodes/normalizationSetup.ts | 9 ++++++ agent/nodes/tool.ts | 29 +++++++++++++++---- agent/nodes/verificationSetup.ts | 9 ++++++ agent/run.ts | 6 ++-- agent/state.ts | 17 +++++++++++ 14 files changed, 188 insertions(+), 61 deletions(-) create mode 100644 agent/agent.ts create mode 100644 agent/conditionals/tool_end.ts delete mode 100644 agent/index.ts create mode 100644 agent/nodes/dummyNormalisationModel.ts create mode 100644 agent/nodes/dummyRagasMetrics.ts create mode 100644 agent/nodes/dummyTriggerEventModel.ts create mode 100644 agent/nodes/dummyVerificationModel.ts create mode 100644 agent/nodes/normalizationSetup.ts create mode 100644 agent/nodes/verificationSetup.ts create mode 100644 agent/state.ts diff --git a/agent/agent.ts b/agent/agent.ts new file mode 100644 index 0000000..222cd33 --- /dev/null +++ b/agent/agent.ts @@ -0,0 +1,30 @@ +import { END, START, StateGraph } from "@langchain/langgraph"; +import { MessagesState } from "./state"; +import { toolNode } from "./nodes/tool"; +import { createToolConditional } from "./conditionals/tool_end"; +import { normalizationSetup } from "./nodes/normalizationSetup"; +import { dummyNormalisationModel } from "./nodes/dummyNormalisationModel"; +import { dummyTriggerEventModel } from "./nodes/dummyTriggerEventModel"; + +const triggerEventToolConditional = createToolConditional(toolNode.name, END) +const agent = new StateGraph(MessagesState) + + //NODES + .addNode("toolNode", toolNode) + .addNode(normalizationSetup.name, normalizationSetup) + .addNode(dummyNormalisationModel.name, dummyNormalisationModel) + .addNode(dummyTriggerEventModel.name, dummyTriggerEventModel) + + .addEdge(START, normalizationSetup.name) + .addEdge(normalizationSetup.name, dummyNormalisationModel.name) + .addEdge(dummyNormalisationModel.name, dummyTriggerEventModel.name) + + // @ts-expect-error + .addConditionalEdges(dummyTriggerEventModel.name, triggerEventToolConditional, [toolNode.name, END]) + + .addEdge(toolNode.name, dummyTriggerEventModel.name) + + .addEdge(dummyTriggerEventModel.name, END) + .compile(); + + export {agent} \ No newline at end of file diff --git a/agent/conditionals/tool_end.ts b/agent/conditionals/tool_end.ts new file mode 100644 index 0000000..357180a --- /dev/null +++ b/agent/conditionals/tool_end.ts @@ -0,0 +1,31 @@ +import { ConditionalEdgeRouter, END } from "@langchain/langgraph"; +import { MessagesState } from "../state"; +import { AIMessage } from "@langchain/core/messages"; + +export function createToolConditional(a: String, b: String): ConditionalEdgeRouter { + // @ts-expect-error + var genericToolConditional: ConditionalEdgeRouter = (state) => { + const lastMessage = state.messages.at(-1); + + //STARTTEMP + if (lastMessage?.content?.toString().indexOf("qwe") != -1) { + return a + } + //ENDTEMP + + // Check if it's an AIMessage before accessing tool_calls + if (!lastMessage || !AIMessage.isInstance(lastMessage)) { + return b; + } + + // If the LLM makes a tool call, then perform an action + if (lastMessage.tool_calls?.length) { + return a; + } + + // Otherwise, we stop (reply to the user) + return b; + }; + return genericToolConditional +} + diff --git a/agent/index.ts b/agent/index.ts deleted file mode 100644 index 96a38aa..0000000 --- a/agent/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { addMessages, entrypoint } from "@langchain/langgraph"; -import { type BaseMessage } from "@langchain/core/messages"; -import { HumanMessage } from "@langchain/core/messages"; -import { modelNode } from "./nodes/model"; -import { toolNode } from "./nodes/tool"; -import 'dotenv/config'; - -const agent = entrypoint({ name: "agent" }, async (messages: BaseMessage[]) => { - let modelResponse = await modelNode(messages); - - while (true) { - if (!modelResponse.tool_calls?.length) { - break; - } - - // Execute tools - const toolResults = await Promise.all( - modelResponse.tool_calls.map((toolCall) => toolNode(toolCall)) - ); - messages = addMessages(messages, [modelResponse, ...toolResults]); - modelResponse = await modelNode(messages); - } - - return messages; -}); - -export {agent} - -const result = await agent.invoke([new HumanMessage("Add 3 and 4.")]); - -for (const message of result) { - console.log(`[${message.getType()}]: ${message.text}`); -} \ No newline at end of file diff --git a/agent/langgraph.json b/agent/langgraph.json index 3b5e924..070e701 100644 --- a/agent/langgraph.json +++ b/agent/langgraph.json @@ -1,7 +1,7 @@ { "dependencies": ["."], "graphs": { - "agent": "./index.ts:agent" + "agent": "./agent.ts:agent" }, "env": ".env" } diff --git a/agent/nodes/dummyNormalisationModel.ts b/agent/nodes/dummyNormalisationModel.ts new file mode 100644 index 0000000..2c22305 --- /dev/null +++ b/agent/nodes/dummyNormalisationModel.ts @@ -0,0 +1,11 @@ +import { GraphNode } from "@langchain/langgraph"; +import { MessagesState } from "../state"; +import { AIMessage, HumanMessage } from "@langchain/core/messages"; + +export const dummyNormalisationModel: GraphNode = async (state) => { + //TODO: call AI model with collected data + + return { + messages: [ new AIMessage(state.messages.at(-1)?.content + " Processed")] + }; +}; \ No newline at end of file diff --git a/agent/nodes/dummyRagasMetrics.ts b/agent/nodes/dummyRagasMetrics.ts new file mode 100644 index 0000000..33ee364 --- /dev/null +++ b/agent/nodes/dummyRagasMetrics.ts @@ -0,0 +1,11 @@ +import { GraphNode } from "@langchain/langgraph"; +import { MessagesState } from "../state"; +import { AIMessage, HumanMessage } from "@langchain/core/messages"; + +export const dummyRagasMetrics: GraphNode = async (state) => { + //TODO: get ragas metrics + + return { + messages: [ new AIMessage("RAGASSED : " + state.messages.at(-1)?.content)] + }; +}; \ No newline at end of file diff --git a/agent/nodes/dummyTriggerEventModel.ts b/agent/nodes/dummyTriggerEventModel.ts new file mode 100644 index 0000000..8618497 --- /dev/null +++ b/agent/nodes/dummyTriggerEventModel.ts @@ -0,0 +1,11 @@ +import { GraphNode } from "@langchain/langgraph"; +import { MessagesState } from "../state"; +import { AIMessage, HumanMessage } from "@langchain/core/messages"; + +export const dummyTriggerEventModel: GraphNode = async (state) => { + //TODO: call AI model with collected data + + return { + messages: [ new AIMessage("Trigger events of: " + state.messages.at(-1)?.content)] + }; +}; \ No newline at end of file diff --git a/agent/nodes/dummyVerificationModel.ts b/agent/nodes/dummyVerificationModel.ts new file mode 100644 index 0000000..fcd0250 --- /dev/null +++ b/agent/nodes/dummyVerificationModel.ts @@ -0,0 +1,11 @@ +import { GraphNode } from "@langchain/langgraph"; +import { MessagesState } from "../state"; +import { AIMessage, HumanMessage } from "@langchain/core/messages"; + +export const dummyVerificationModel: GraphNode = async (state) => { + //TODO: call AI model with collected data + + return { + messages: [ new AIMessage("Verified : " + state.messages.at(-1)?.content)] + }; +}; \ No newline at end of file diff --git a/agent/nodes/model.ts b/agent/nodes/model.ts index 58d1772..e7cc2a2 100644 --- a/agent/nodes/model.ts +++ b/agent/nodes/model.ts @@ -1,21 +1,24 @@ -import { task, entrypoint } from "@langchain/langgraph"; -import { BaseMessage, SystemMessage } from "@langchain/core/messages"; -import { ChatOpenAI } from "@langchain/openai" -import { arithmeticTools } from "../tools/arithmetic"; +// import { SystemMessage } from "@langchain/core/messages"; +// import { GraphNode } from "@langchain/langgraph"; +// import { MessagesState } from "../state"; +// import { arithmeticTools } from "../tools/arithmetic"; +// import { ChatOpenAI } from "@langchain/openai" -const model = new ChatOpenAI({ - model: "gpt-5-mini" -}); +// const model = new ChatOpenAI({ +// model: "gpt-5-mini" +// }); -const modelWithTools = model.bindTools(arithmeticTools); +// const modelWithTools = model.bindTools(arithmeticTools); -export const modelNode = task({ name: "callLlm" }, async (messages: BaseMessage[]) => { - - - return modelWithTools.invoke([ - new SystemMessage( - "You are a helpful assistant tasked with performing arithmetic on a set of inputs." - ), - ...messages, - ]); -}); \ No newline at end of file +// export const llmCall: GraphNode = async (state) => { +// const response = await modelWithTools.invoke([ +// new SystemMessage( +// "You are a helpful assistant tasked with performing arithmetic on a set of inputs. Any calculation, no matter how trivial, should be done with tools. Output the final answer with %%% on each side" +// ), +// ...state.messages, +// ]); +// return { +// messages: [response], +// llmCalls: 1, +// }; +// }; \ No newline at end of file diff --git a/agent/nodes/normalizationSetup.ts b/agent/nodes/normalizationSetup.ts new file mode 100644 index 0000000..d68e350 --- /dev/null +++ b/agent/nodes/normalizationSetup.ts @@ -0,0 +1,9 @@ +import { GraphNode } from "@langchain/langgraph"; +import { MessagesState } from "../state"; +import { HumanMessage } from "@langchain/core/messages"; + +export const normalizationSetup: GraphNode = async (state) => { + //TODO: Implement claim normalisation, using few shot prompting and CLAN Dataset + + return { messages: [ new HumanMessage(state.disinformationTitle)] }; +}; \ No newline at end of file diff --git a/agent/nodes/tool.ts b/agent/nodes/tool.ts index 8c08941..55f3c6e 100644 --- a/agent/nodes/tool.ts +++ b/agent/nodes/tool.ts @@ -1,8 +1,25 @@ -import type { ToolCall } from "@langchain/core/messages/tool"; -import { task } from "@langchain/langgraph"; +import { AIMessage, ToolMessage } from "@langchain/core/messages"; +import { GraphNode } from "@langchain/langgraph"; +import { MessagesState } from "../state"; import { arithmeticToolsByName } from "../tools/arithmetic"; -export const toolNode = task({ name: "callTool" }, async (toolCall: ToolCall) => { - const tool = arithmeticToolsByName[toolCall.name]; - return tool.invoke(toolCall); -}); \ No newline at end of file +export const toolNode: GraphNode = async (state) => { + const lastMessage = state.messages.at(-1); + + //STARTTEMP + return {messages: [new AIMessage("yeman")]} + //ENDTEMP + + if (lastMessage == null || !AIMessage.isInstance(lastMessage)) { + return { messages: [] }; + } + + const result: ToolMessage[] = []; + for (const toolCall of lastMessage.tool_calls ?? []) { + const tool = arithmeticToolsByName[toolCall.name]; + const observation = await tool.invoke(toolCall); + result.push(observation); + } + + return { messages: result }; +}; \ No newline at end of file diff --git a/agent/nodes/verificationSetup.ts b/agent/nodes/verificationSetup.ts new file mode 100644 index 0000000..65d7c09 --- /dev/null +++ b/agent/nodes/verificationSetup.ts @@ -0,0 +1,9 @@ +import { GraphNode } from "@langchain/langgraph"; +import { MessagesState } from "../state"; +import { HumanMessage } from "@langchain/core/messages"; + +export const verificationSetup: GraphNode = async (state) => { + //TODO: this might not be needed, looks nice on the graph tho + + return { messages: [ new HumanMessage(state.messages.at(-1)?.content ?? "undefined")] }; +}; \ No newline at end of file diff --git a/agent/run.ts b/agent/run.ts index 1ac727a..e30bf1d 100644 --- a/agent/run.ts +++ b/agent/run.ts @@ -8,9 +8,9 @@ const streamResponse = client.runs.stream( thread["thread_id"], "agent", { - input: [ - { role: "user", content: "3+5" } - ], + input: { + "messages": ["3+5" ] + }, streamMode: "messages-tuple", } ); diff --git a/agent/state.ts b/agent/state.ts new file mode 100644 index 0000000..9639a2e --- /dev/null +++ b/agent/state.ts @@ -0,0 +1,17 @@ +import { + StateGraph, + StateSchema, + MessagesValue, + ReducedValue, + GraphNode, + ConditionalEdgeRouter, + START, + END, +} from "@langchain/langgraph"; +import { z } from "zod/v4"; + +export const MessagesState = new StateSchema({ + messages: MessagesValue, + // normalizationContext: z.map(z.string(), z.string()), + disinformationTitle: z.string() +}); \ No newline at end of file