Use this file to discover all available pages before exploring further.
ChemAgent implements a dynamic plan-execute-replan pattern using LangGraph, allowing it to adapt its strategy based on validation results and handle complex multi-step chemistry queries.
from typing import Annotated, List, Tuplefrom typing_extensions import TypedDictimport operatorclass PlanExecute(TypedDict): input: str # Original user query plan: List[str] # Current execution plan past_steps: Annotated[List[Tuple], operator.add] # History of completed steps response: str # Final response (when ready)
The past_steps field uses operator.add to automatically accumulate results across iterations.See plan_execute_agent/rdkit_agent.py:75 for state definition.
The planner uses a specialized prompt to ensure chemistry-specific planning:
planner_prompt = ChatPromptTemplate.from_messages([ ("system", """For the given objective, come up with a simple step by step plan. This plan should involve individual tasks, that if executed correctly will yield the correct answer. Do not add any superfluous steps. The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps."""), ("placeholder", "{messages}"),])
See plan_execute_agent/rdkit_agent.py:95 for the complete prompt.
The planner returns a Pydantic model for type safety:
from pydantic import BaseModel, Fieldclass Plan(BaseModel): """Plan to follow in future""" steps: List[str] = Field( description="different steps to follow, should be in sorted order" )
For the query: "What is the SMILES for <IUPAC> aspirin </IUPAC>?"The planner generates:
{ "steps": [ "Call 'structure_chem_prompt' to tag chemical information", "Call 'answer_chemistry_query' to get SMILES representation", "Call 'validate_smiles_rdkit' to validate the SMILES output", "Return the validated SMILES to the user" ]}
Task: “Call ‘structure_chem_prompt’ to tag chemical information”Agent reasoning:
Thought: I need to use the structure_chem_prompt tool to tag the IUPAC name.Action: structure_chem_promptAction Input: "What is the SMILES for aspirin?"Observation: {"new_prompt": "What is the SMILES for <IUPAC> aspirin </IUPAC>?"}
Result added to past_steps:
("Call 'structure_chem_prompt' to tag chemical information", "Successfully structured prompt: What is the SMILES for <IUPAC> aspirin </IUPAC>?")
The replanner uses union types to represent decisions:
from typing import Unionclass Response(BaseModel): """Response to user.""" response: strclass Act(BaseModel): """Action to perform.""" action: Union[Response, Plan] = Field( description="Action to perform. If you want to respond to user, use Response. " "If you need to further use tools to get the answer, use Plan." )
See plan_execute_agent/rdkit_agent.py:115 for decision model definitions.
The replanner receives full context of the conversation:
replanner_prompt = ChatPromptTemplate.from_template( """For the given objective, come up with a simple step by step plan. This plan should involve individual tasks, that if executed correctly will yield the correct answer. Your objective was this: {input} Your original plan was this: {plan} You have currently done the follow steps: {past_steps} Update your plan accordingly. If no more steps are needed OR VRAM is LOW and you can return to the user, then respond with that. Otherwise, fill out the plan. Only add steps to the plan that still NEED to be done. Do not return previously done steps as part of the plan.""")
See plan_execute_agent/rdkit_agent.py:130 for the complete prompt.
The replanner analyzes the error and creates a corrective plan:Example:
{ "plan": [ "Review the validation error indicating unclosed ring", "Call 'answer_chemistry_query' again with clearer instructions", "Call 'validate_smiles_rdkit' to verify the corrected SMILES" ]}
This allows the agent to self-correct based on validation feedback.
The system includes safeguards against infinite replanning:
# Configurationconfig = {"recursion_limit": 50}# Attempt trackingreplanning_attempts = 1def should_end(state: PlanExecute): global replanning_attempts if "response" in state and state["response"]: return END else: replanning_attempts += 1 return "agent"
See plan_execute_agent/rdkit_agent.py:188 for the termination logic.
{"plan": ["Structure the query", "Answer with LlaSMol", "Validate result", "Return answer"]}
Executor: Calls structure_chem_prompt("What is the molecular formula of aspirin?")Result: {"new_prompt": "What is the molecular formula of <IUPAC> aspirin </IUPAC>?"}Replanner Decision: Continue with remaining steps
{"plan": ["Answer with LlaSMol", "Validate result", "Return answer"]}
Executor: Calls answer_chemistry_query("What is the molecular formula of <IUPAC> aspirin </IUPAC>?")Result: "<MOLFORMULA> C9H8O4 </MOLFORMULA>"Replanner Decision: Continue to validation