langchain-ai / langgraphjs

Build resilient language agents as graphs.
https://langchain-ai.github.io/langgraphjs/
MIT License
639 stars 99 forks source link

langgraph conditionalEdge not seem to be working for new Graph() only workes for stategraph and messageGraph #242

Closed jithinagiboson closed 4 months ago

jithinagiboson commented 4 months ago

langraph version 0.0.26

I have langgraph which is a replica of langchain sql agent but when i run the nodes with individual edges it worked but when i added conditionalEdge its not taken as a edge it says the first node is a dead end this below is the diagram of my a idea its actualy a by directional edge

image // ROUTER function const router=async (state)=>{ log("-------ROUTER---------") let {result}=state let action=result["Action"] log(action) // return action//next action // return "list-tables-sql" return "list-tables-sql" } // query-sql,info-sql,list-tables-sql,query-checker // GRAPH

graph.addEdge("list-tables-sql", "info-sql"); graph.addEdge("info-sql", "query-checker"); graph.addEdge("query-checker", END);

graph.addEdge(START, "query"); graph.addEdge("query", "list-tables-sql"); // this line only work if i coment this it says query is a dead end // graph.addEdge("query", "info-sql"); // graph.addEdge(START,"list-tables-sql"); graph.addConditionalEdges( 'query', // Assess agent decision router, { "list-tables-sql":"list-tables-sql" } ); const runnable = graph.compile();

jithinagiboson commented 4 months ago

my package.json { "name": "ai-chart-generator", "version": "1.0.0", "description": "", "main": "index.js", "type": "module", "scripts": { "start": "nodemon index.js", "agent": "nodemon ./modules/database-ai/agent.js", "test": "nodemon ./modules/database-ai/test.js", "graph": "nodemon ./modules/lang-graph/index.js" }, "author": "", "license": "ISC", "dependencies": { "@langchain/community": "^0.2.17", "@langchain/core": "^0.2.12", "@langchain/langgraph": "^0.0.26", "@langchain/openai": "^0.2.0", "dotenv": "^16.4.5", "langchain": "^0.2.8", "langsmith": "^0.1.34", "mysql": "^2.18.1", "node-querybuilder": "^2.1.1", "typeorm": "^0.3.20", "xss": "^1.0.15" } }

jithinagiboson commented 4 months ago

const graph = new Graph();

... .... ... const runnable = graph.compile();

// new HumanMessage("elephents") // For Message graph, input should always be a message or list of messages. let state = { input: "list 1st 20 customers and genres " }; log(state); const res = await runnable.invoke(state);

i use a custom js object as state

vbarda commented 4 months ago

@jithinagiboson could you please explain the step-by-step flow that you're trying to implement? it would also be helpful to see the full graph implementation in one code snippet that has graph state, nodes, edges and the compiled graph.

also, I believe you can just use a prebuilt ReAct agent to achieve the same behavior as langchain's SQL agent:

import { createReactAgent } from "@langchain/langgraph/prebuilt"
import { ChatAnthropic } from "@langchain/anthropic"

const tools = []  // your SQL tools defined here
const llm = new ChatAnthropic({ model: "claude-3-haiku-20240307" })  // or whichever model you're using
const agent = createReactAgent({ llm, tools })
const res = await agent.invoke(
  { messages: [["user", "list 1st 20 customers and genres"]] }
)
jithinagiboson commented 4 months ago

my requirements -use an opensource ai model for text to sql query current issues faced

using an sql agent doesnt give me much control over the outcome.And sql agent in js already have bug its only prompt is default to sqlite so i had to hard code it to mysql. what i did was using the same prompt used by sql agent inside langraph i used the llm responce to identify the too name to use and tried to route to that node

the below


graph.addNode("query",` async (state) => {
  log("------------NODE QUERY------------");
  log("state", state);

  const parser = new JsonOutputParser();

  const chain = prompt.pipe(modelJSON).pipe(parser);

  let agentSteps = [];
  let result = await chain.invoke({
    dialect: DIALECT,
    input: state.input,
    agentSteps: agentSteps,
    agent_scratchpad: "",
  });
  agentSteps = [{ Thought: result["Thought"] }];

  return { input: state.input, result: result, intermediateSteps: agentSteps };
});

graph.addNode("list-tables-sql", async (state) => {
  log("------------NODE list-tables-sql------------");
  log("state", state);

  const prompt = ChatPromptTemplate.fromTemplate(lama3_70b_prompt);

  const parser = new JsonOutputParser();

  const chain = prompt.pipe(modelJSON).pipe(parser);

  //CALL TOOLS
  let tableNameArray = db.allTables.map((a) => a.tableName); //get all table names

//   log("tableNameArray",tableNameArray)
//   process.exit()
  // let default_result={result:{
  //     Question: state.input,
  //     Thought: 'I should look at the tables in the database to see what I can query.',
  //     Action: 'list-tables-sql',
  //     'Action Input': '',
  //     Observation: getArray2Text(textArray) //'Album,Artist,Customer,Employee,Genre,Invoice,InvoiceLine,MediaType,Playlist,PlaylistTrack,Track'
  //   }}

  let agentSteps = {
    Action: state.result["Action"],
    "Action Input": state.result["Action Input"],
    Observation: getArray2Text(tableNameArray),
    Thought: "",
  };

  state.intermediateSteps.push(agentSteps);

  let concatAgentSteps =arrayJSON2Text(state.intermediateSteps);

  let result = await chain.invoke({
    dialect: DIALECT,
    input: state.input,
    agent_scratchpad: concatAgentSteps,
  }); //llm call

  state.intermediateSteps[state.intermediateSteps.length - 1].Thought =
    result["Thought"]; //thought from llm
dialect:DIALECT,input:state.input,agent_steps:state.agent_steps&&state.agent_steps+'\n'+JSON2Text(agent_steps),agent_scratchpad:JSON2Text(agent_steps) }))

  log("list-tables-sql RESULT", result);

  return {
    input: state.input,
    result: result,
    intermediateSteps: state.intermediateSteps,
  };
});
graph.addEdge(START, "query");
graph.addEdge("query", "list-tables-sql"); // this line only work if i coment this it says query is a dead end
// graph.addEdge("query", "info-sql");
// graph.addEdge(START,"list-tables-sql");
graph.addConditionalEdges(
'query',
// Assess agent decision
router,
{
"list-tables-sql":"list-tables-sql"
}
);

// ROUTER function
const router=async (state)=>{
log("-------ROUTER---------")
let {result}=state
let action=result["Action"]
log(action)
// return action//next action
// return "list-tables-sql"
return "list-tables-sql"// just hard coded to test query to list table flow
}

this is the basic flow one node result has and action input which shloud be used for conditional routing to the next node but unfortunatly its not taken as an edge at all

jithinagiboson commented 4 months ago

i think the issue may be with import { Graph} from "@langchain/langgraph"; Graph doesnt seem to work with conditional edge the below code work with MessageGraph

import { START, END, MessageGraph, Graph,StateGraph, } from "@langchain/langgraph";
import { log } from "../helper/util.js";
import { HumanMessage } from "@langchain/core/messages";

const model=(state)=>{
log('--- AGENT-----')
return state
}

const action=(state)=>{
    log('--- ACTION-----')
    return state
    }
const graph = new MessageGraph();

const workflow = new MessageGraph() //new Graph()

  .addNode("agent", model)
  .addNode("action",action);

  const shouldContinue=(state)=>{
    log("shouldContinue")
    return "action"
  }
  workflow.addConditionalEdges("agent", shouldContinue);
  workflow.addEdge(START, "agent");
  workflow.addEdge("action", END);

  let app=workflow.compile()
 app.invoke({messages:[new HumanMessage("what about ny")]})

OUTPUT [nodemon] starting node modules/lang-graph/route-test.js --- AGENT----- shouldContinue --- ACTION----- [nodemon]

jithinagiboson commented 4 months ago

i have solved my issue by using StateGrpah() instead of Graph()

vbarda commented 4 months ago

ah, yes, it should be a StateGraph, that's correct! glad it worked