From 9353228fce0fae52cc203335c3c13520b018a7a0 Mon Sep 17 00:00:00 2001 From: Harrison Chase Date: Wed, 5 Mar 2025 18:23:12 -0800 Subject: [PATCH] cr --- .gitignore | 10 ++++++++ README.md | 36 +++++++++++++++++++++++++++++ examples/coding.py | 0 examples/llm_as_a_judge.py | 34 +++++++++++++++++++++++++++ pyproject.toml | 19 +++++++++++++++ src/langgraph_reflexion/__init__.py | 30 ++++++++++++++++++++++++ 6 files changed, 129 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 examples/coding.py create mode 100644 examples/llm_as_a_judge.py create mode 100644 pyproject.toml create mode 100644 src/langgraph_reflexion/__init__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..505a3b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/README.md b/README.md new file mode 100644 index 0000000..4db4bbf --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# LangGraph-Reflexion + +This prebuilt graph is an agent that uses a reflection-style architecture to check and improve an initial agent's output. + +This reflection agent uses two subagents: +- A "main" agent, which is the agent attempting to solve the users task +- A "critique" agent, which checks the main agents work and offers any critiques + +The reflection agent has the following architecture: + +1. First, the main agent is called +2. Once the main agent is finished, the critique agent is called +3. Based on the result of the critique agent: + - If the critique agent finds something to critique, then the main agent is called again + - If there is nothing to critique, then the overall reflection agent finishes +4. Repeat until the overall reflection agent finishes + + +We make some assumptions about the graphs: +- The main agent should take as input a list of messages +- The reflection agent should return a **user** message if there is any critiques, otherwise it should return **no** messages. + +## Examples + +Below are a few examples of how to use this reflection agent. + +### LLM-as-a-Judge + +In this example, the reflection agent uses another LLM to judge it's output + + + +### Coding + +Code is easy to "check" in deterministic ways. In this example, we use a linter to check Python code. + diff --git a/examples/coding.py b/examples/coding.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/llm_as_a_judge.py b/examples/llm_as_a_judge.py new file mode 100644 index 0000000..257afcd --- /dev/null +++ b/examples/llm_as_a_judge.py @@ -0,0 +1,34 @@ +from langgraph_reflexion import create_reflection_graph +from langchain.chat_models import init_chat_model +from langgraph.graph import StateGraph, MessagesState, START, END +from typing import TypedDict + +def call_model(state): + return {"messages": init_chat_model(model="claude-3-7-sonnet-latest").invoke(state['messages'])} + +graph = StateGraph(MessagesState).add_node(call_model).add_edge(START, "call_model").add_edge("call_model", END).compile() + + +class Finish(TypedDict): + finish: bool + + + +critique_prompt = """Your job is to look at the conversation below, and look at the AI assistant's response. + +Critique it. Make sure it is complete. Make sure it is well thoughtout. Make sure it does what the user wants. + +If it is good, then call the `Finish` tool. If you don't call the `Finish` tool, your response will be sent back to the assistant, so make sure to include concrete feedback on how it can improve.""" + +def nl_critique(state, config): + response = init_chat_model(model="o3-mini", model_provider="openai").bind_tools([Finish]).invoke( + [{"role": "system", "content": critique_prompt}] + state['messages'] + ) + if len(response.tool_calls) == 1: + return + else: + return {"messages": [{"role": "user", "content": response.content}]} + +critique_graph_general = StateGraph(MessagesState).add_node(nl_critique).add_edge(START, "nl_critique").add_edge("nl_critique", END).compile() + +overall_graph = create_reflection_graph(graph, critique_graph_general) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..757926d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "langgraph-reflexion" +version = "0.0.1" +description = "LangGraph agent that runs a reflection step" +readme = "README.md" +requires-python = ">=3.11" +dependencies = ["langgraph"] +authors = [{name = "Harrison Chase"}] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.uv.sources] +langgraph-reflexion = { workspace = true } + +[dependency-groups] +dev = [ + "langgraph-reflexion", +] diff --git a/src/langgraph_reflexion/__init__.py b/src/langgraph_reflexion/__init__.py new file mode 100644 index 0000000..cdec695 --- /dev/null +++ b/src/langgraph_reflexion/__init__.py @@ -0,0 +1,30 @@ +from typing import Optional, Type, Any, Literal +from langgraph.graph import END, START, StateGraph +from langgraph.graph.state import CompiledStateGraph +from langchain_core.messages import HumanMessage + + +def end_or_reflect(state) -> Literal[END, "graph"]: + if len(state["messages"]) == 0: + return END + last_message = state["messages"][-1] + if isinstance(last_message, HumanMessage): + return "graph" + else: + return END + + +def create_reflection_graph( + graph: CompiledStateGraph, + reflection: CompiledStateGraph, + state_schema: Optional[Type[Any]] = None, + config_schema: Optional[Type[Any]] = None, +) -> StateGraph: + _state_schema = state_schema or graph.builder.schema + rgraph = StateGraph(state_schema, config_schema=config_schema) + rgraph.add_node("graph", graph) + rgraph.add_node("reflection", reflection) + rgraph.add_edge(START, "graph") + rgraph.add_edge("graph", "reflection") + rgraph.add_conditional_edges("reflection", end_or_reflect) + return rgraph