chemgraph-loop / src /chemgraph /graphs /graspa_agent.py
rockyaaos's picture
ChemGraph Loop: guarded real-agent API (EMT/TBLite single-point energy)
c509967 verified
Raw
History Blame Contribute Delete
6.02 kB
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import ToolNode
from chemgraph.tools.graspa_tools import run_graspa
from chemgraph.schemas.agent_response import ResponseFormatter
from chemgraph.prompt.single_agent_prompt import (
single_agent_prompt,
formatter_prompt,
)
from chemgraph.utils.logging_config import setup_logger
from chemgraph.state.state import State
logger = setup_logger(__name__)
def route_tools(state: State):
"""Route to the 'tools' node if the last message has tool calls; otherwise, route to 'done'.
Parameters
----------
state : State
The current state containing messages and remaining steps
Returns
-------
str
Either 'tools' or 'done' based on the state conditions
"""
if isinstance(state, list):
ai_message = state[-1]
elif messages := state.get("messages", []):
ai_message = messages[-1]
else:
raise ValueError(f"No messages found in input state to tool_edge: {state}")
if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
return "tools"
return "done"
def ChemGraphAgent(state: State, llm: ChatOpenAI, system_prompt: str, tools=None):
"""LLM node that processes messages and decides next actions.
Parameters
----------
state : State
The current state containing messages and remaining steps
llm : ChatOpenAI
The language model to use for processing
system_prompt : str
The system prompt to guide the LLM's behavior
tools : list, optional
List of tools available to the agent, by default None
Returns
-------
dict
Updated state containing the LLM's response
"""
# Load default tools if no tool is specified.
if tools is None:
tools = [run_graspa]
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"{state['messages']}"},
]
llm_with_tools = llm.bind_tools(tools=tools)
return {"messages": [llm_with_tools.invoke(messages)]}
def ResponseAgent(state: State, llm: ChatOpenAI, formatter_prompt: str):
"""An LLM agent responsible for formatting final messag
Parameters
----------
state : State
The current state containing messages and remaining steps
llm : ChatOpenAI
The language model to use for formatting
formatter_prompt : str
The prompt to guide the LLM's formatting behavior
Returns
-------
dict
Updated state containing the formatted response
"""
messages = [
{"role": "system", "content": formatter_prompt},
{"role": "user", "content": f"{state['messages']}"},
]
llm_structured_output = llm.with_structured_output(ResponseFormatter)
response = llm_structured_output.invoke(messages).model_dump_json()
return {"messages": [response]}
def construct_graspa_graph(
llm: ChatOpenAI,
system_prompt: str = single_agent_prompt,
structured_output: bool = False,
formatter_prompt: str = formatter_prompt,
tools: list = None,
):
"""Construct a geometry optimization graph.
Parameters
----------
llm : ChatOpenAI
The language model to use for the graph
system_prompt : str, optional
The system prompt to guide the LLM's behavior, by default single_agent_prompt
structured_output : bool, optional
Whether to use structured output, by default False
formatter_prompt : str, optional
The prompt to guide the LLM's formatting behavior, by default formatter_prompt
tool: list, optional
The list of tools for the agent, by default None
Returns
-------
StateGraph
The constructed geometry optimization graph
"""
try:
logger.info("Constructing gRASPA graph")
checkpointer = MemorySaver()
if tools is None:
tools = [run_graspa]
tool_node = ToolNode(tools=tools)
graph_builder = StateGraph(State)
if not structured_output:
graph_builder.add_node(
"ChemGraphAgent",
lambda state: ChemGraphAgent(
state, llm, system_prompt=system_prompt, tools=tools
),
)
graph_builder.add_node("tools", tool_node)
graph_builder.add_conditional_edges(
"ChemGraphAgent",
route_tools,
{"tools": "tools", "done": END},
)
graph_builder.add_edge("tools", "ChemGraphAgent")
graph_builder.add_edge(START, "ChemGraphAgent")
graph = graph_builder.compile(checkpointer=checkpointer)
logger.info("gRASPA graph construction completed")
return graph
else:
graph_builder.add_node(
"ChemGraphAgent",
lambda state: ChemGraphAgent(
state, llm, system_prompt=system_prompt, tools=tools
),
)
graph_builder.add_node("tools", tool_node)
graph_builder.add_node(
"ResponseAgent",
lambda state: ResponseAgent(
state, llm, formatter_prompt=formatter_prompt
),
)
graph_builder.add_conditional_edges(
"ChemGraphAgent",
route_tools,
{"tools": "tools", "done": "ResponseAgent"},
)
graph_builder.add_edge("tools", "ChemGraphAgent")
graph_builder.add_edge(START, "ChemGraphAgent")
graph_builder.add_edge("ResponseAgent", END)
graph = graph_builder.compile(checkpointer=checkpointer)
logger.info("gRASPA graph construction completed")
return graph
except Exception as e:
logger.error(f"Error constructing graph: {str(e)}")
raise