mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-20 06:35:46 +02:00
385 KiB
385 KiB
In [ ]:
! pip install langchain_community tiktoken langchain-openai langchainhub chromadb langchain langgraph faiss-cpuIn [ ]:
from bs4 import BeautifulSoup as Soup
from langchain_community.document_loaders.recursive_url_loader import RecursiveUrlLoader
# LCEL docs
url = "https://python.langchain.com/docs/expression_language/"
loader = RecursiveUrlLoader(
url=url, max_depth=20, extractor=lambda x: Soup(x, "html.parser").text
)
docs = loader.load()
# LCEL w/ PydanticOutputParser (outside the primary LCEL docs)
url = "https://python.langchain.com/docs/modules/model_io/output_parsers/quick_start"
loader = RecursiveUrlLoader(
url=url, max_depth=1, extractor=lambda x: Soup(x, "html.parser").text
)
docs_pydantic = loader.load()
# LCEL w/ Self Query (outside the primary LCEL docs)
url = "https://python.langchain.com/docs/modules/data_connection/retrievers/self_query/"
loader = RecursiveUrlLoader(
url=url, max_depth=1, extractor=lambda x: Soup(x, "html.parser").text
)
docs_sq = loader.load()
# Add
docs.extend([*docs_pydantic, *docs_sq])
# Sort the list based on the URLs in 'metadata' -> 'source'
d_sorted = sorted(docs, key=lambda x: x.metadata["source"])
d_reversed = list(reversed(d_sorted))
# Concatenate the 'page_content' of each sorted dictionary
concatenated_content = "\n\n\n --- \n\n\n".join(
[doc.page_content for doc in d_reversed]
)In [ ]:
from typing import Dict, TypedDict
from langchain_core.messages import BaseMessage
class GraphState(TypedDict):
"""
Represents the state of our graph.
Attributes:
keys: A dictionary where each key is a string.
"""
keys: Dict[str, any]In [ ]:
from operator import itemgetter
from bs4 import BeautifulSoup as Soup
from langchain_community.document_loaders.recursive_url_loader import RecursiveUrlLoader
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain.output_parsers import PydanticOutputParser
from langchain.output_parsers.openai_tools import PydanticToolsParser
from langchain_core.utils.function_calling import convert_to_openai_tool
def generate(state):
"""
Generate a code solution based on LCEL docs and the input question
with optional feedback from code execution tests
Args:
state (dict): The current graph state
Returns:
state (dict): New key added to state, documents, that contains retrieved documents
"""
## State
state_dict = state["keys"]
question = state_dict["question"]
docs = state_dict["docs"]
## Data model
class code(BaseModel):
"""Code output"""
prefix: str = Field(description="Description of the problem and approach")
imports: str = Field(description="Code block import statements")
code: str = Field(description="Code block not including import statements")
## LLM
model = ChatOpenAI(temperature=0, model="gpt-4-0125-preview", streaming=True)
# Tool
code_tool_oai = convert_to_openai_tool(code)
# LLM with tool and enforce invocation
llm_with_tool = model.bind(
tools=[convert_to_openai_tool(code_tool_oai)],
tool_choice={"type": "function", "function": {"name": "code"}},
)
# Parser
parser_tool = PydanticToolsParser(tools=[code])
## Prompt
template = """You are a coding assistant with expertise in LCEL, LangChain expression language. \n
Here is a full set of LCEL documentation:
\n ------- \n
{context}
\n ------- \n
Answer the user question based on the above provided documentation. \n
Ensure any code you provide can be executed with all required imports and variables defined. \n
Structure your answer with a description of the code solution. \n
Then list the imports. And finally list the functioning code block. \n
Here is the user question: \n --- --- --- \n {question}"""
## Generation
if "error" in state_dict:
print("---RE-GENERATE SOLUTION w/ ERROR FEEDBACK---")
error = state_dict["error"]
code_solution = state_dict["generation"]
# Udpate prompt
addendum = """ \n --- --- --- \n You previously tried to solve this problem. \n Here is your solution:
\n --- --- --- \n {generation} \n --- --- --- \n Here is the resulting error from code
execution: \n --- --- --- \n {error} \n --- --- --- \n Please re-try to answer this.
Structure your answer with a description of the code solution. \n Then list the imports.
And finally list the functioning code block. Structure your answer with a description of
the code solution. \n Then list the imports. And finally list the functioning code block.
\n Here is the user question: \n --- --- --- \n {question}"""
template = template + addendum
# Prompt
prompt = PromptTemplate(
template=template,
input_variables=["context", "question", "generation", "error"],
)
# Chain
chain = (
{
"context": lambda x: concatenated_content,
"question": itemgetter("question"),
"generation": itemgetter("generation"),
"error": itemgetter("error"),
}
| prompt
| llm_with_tool
| parser_tool
)
code_solution = chain.invoke({"question":question,
"generation":str(code_solution[0]),
"error":error})
else:
print("---GENERATE SOLUTION---")
# Prompt
prompt = PromptTemplate(
template=template,
input_variables=["context", "question"],
)
# Chain
chain = (
{
"context": lambda x: docs,
"question": itemgetter("question"),
}
| prompt
| llm_with_tool
| parser_tool
)
code_solution = chain.invoke({"question":question})
return {"keys": {"generation": code_solution, "question": question, "docs": docs}}
def check_code_imports(state):
"""
Check imports
Args:
state (dict): The current graph state
Returns:
state (dict): New key added to state, error
"""
## State
print("---CHECKING CODE IMPORTS---")
state_dict = state["keys"]
question = state_dict["question"]
docs = state_dict["docs"]
code_solution = state_dict["generation"]
imports = code_solution[0].imports
try:
# Attempt to execute the imports
exec(imports)
except Exception as e:
print("---CODE IMPORT CHECK: FAILED---")
# Catch any error during execution (e.g., ImportError, SyntaxError)
error = f"Execution error: {e}"
if "error" in state_dict:
error_prev_runs = state_dict["error"]
error = error_prev_runs + "\n --- Most recent run error --- \n" + error
else:
print("---CODE IMPORT CHECK: SUCCESS---")
# No errors occurred
error = "None"
return {"keys": {"generation": code_solution, "question": question, "error": error, "docs": docs}}
def check_code_execution(state):
"""
Check code block execution
Args:
state (dict): The current graph state
Returns:
state (dict): New key added to state, error
"""
## State
print("---CHECKING CODE EXECUTION---")
state_dict = state["keys"]
question = state_dict["question"]
docs = state_dict["docs"]
code_solution = state_dict["generation"]
prefix = code_solution[0].prefix
imports = code_solution[0].imports
code = code_solution[0].code
code_block = imports +"\n"+ code
try:
# Attempt to execute the code block
exec(code_block)
except Exception as e:
print("---CODE BLOCK CHECK: FAILED---")
# Catch any error during execution (e.g., ImportError, SyntaxError)
error = f"Execution error: {e}"
if "error" in state_dict:
error_prev_runs = state_dict["error"]
error = error_prev_runs + "\n --- Most recent run error --- \n" + error
else:
print("---CODE BLOCK CHECK: SUCCESS---")
# No errors occurred
error = "None"
return {"keys": {"generation": code_solution,
"question": question,
"error": error,
"docs": docs,
"prefix":prefix,
"imports":imports,
"code":code}}
### Edges
def decide_to_check_code_exec(state):
"""
Determines whether to test code execution, or re-try answer generation.
Args:
state (dict): The current graph state
Returns:
str: Next node to call
"""
print("---DECIDE TO TEST CODE EXECUTION---")
state_dict = state["keys"]
question = state_dict["question"]
code_solution = state_dict["generation"]
error = state_dict["error"]
if error == "None":
# All documents have been filtered check_relevance
# We will re-generate a new query
print("---DECISION: TEST CODE EXECUTION---")
return "check_code_execution"
else:
# We have relevant documents, so generate answer
print("---DECISION: RE-TRY SOLUTION---")
return "generate"
def decide_to_finish(state):
"""
Determines whether to finish.
Args:
state (dict): The current graph state
Returns:
str: Next node to call
"""
print("---DECIDE TO TEST CODE EXECUTION---")
state_dict = state["keys"]
question = state_dict["question"]
code_solution = state_dict["generation"]
error = state_dict["error"]
if error == "None":
# All documents have been filtered check_relevance
# We will re-generate a new query
print("---DECISION: TEST CODE EXECUTION---")
return "end"
else:
# We have relevant documents, so generate answer
print("---DECISION: RE-TRY SOLUTION---")
return "generate"In [ ]:
from langgraph.graph import END, StateGraph
workflow = StateGraph(GraphState)
# Define the nodes
workflow.add_node("generate", generate) # generation solution
workflow.add_node("check_code_imports", check_code_imports) # check imports
workflow.add_node("check_code_execution", check_code_execution) # check execution
# Build graph
workflow.set_entry_point("generate")
workflow.add_edge("generate", "check_code_imports")
workflow.add_conditional_edges(
"check_code_imports",
decide_to_check_code_exec,
{
"check_code_execution": "check_code_execution",
"generate": "generate",
},
)
workflow.add_conditional_edges(
"check_code_execution",
decide_to_finish,
{
"end": END,
"generate": "generate",
},
)
# Compile
app = workflow.compile()In [ ]:
from langsmith.evaluation import EvaluationResult, run_evaluator
from langsmith.schemas import Example, Run
from typing import Union
@run_evaluator
def check_import(run: Run, example: Union[Example, None] = None):
model_outputs = run.outputs["keys"]
imports = model_outputs['imports']
try:
exec(imports)
score = 1
print("Score: 1!")
except:
score = 0
print("Score: 0!")
return EvaluationResult(key="check_import", score=score)
@run_evaluator
def check_execution(run: Run, example: Union[Example, None] = None):
model_outputs = run.outputs["keys"]
imports = model_outputs['imports']
code = model_outputs['code']
code_to_execute = imports +"\n"+ code
try:
exec(code_to_execute)
score = 1
print("Score: 1!")
except:
score = 0
print("Score: 0!")
return EvaluationResult(key="check_execution", score=score)In [ ]:
from langsmith import Client
from langchain.smith import RunEvalConfig, run_on_dataset
evaluation_config = RunEvalConfig(
custom_evaluators = [check_import,check_execution],
)
client = Client()
def model(input):
return app.invoke({"keys":{**input, "docs": concatenated_content}})
client.run_on_dataset(
dataset_name="lcel-teacher-eval",
llm_or_chain_factory=model,
evaluation=evaluation_config,
)In [ ]:
In [ ]:
In [ ]:

