chore: import upstream snapshot with attribution
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
OPENAI_API_KEY=placeholder
|
||||
ANTHROPIC_API_KEY=placeholder
|
||||
TAVILY_API_KEY=placeholder
|
||||
@@ -0,0 +1 @@
|
||||
.langgraph-data
|
||||
@@ -0,0 +1,13 @@
|
||||
.PHONY: run_w_override
|
||||
|
||||
run:
|
||||
uv run langgraph up --watch --no-pull
|
||||
|
||||
run_faux:
|
||||
cd graphs && uv run langgraph up --no-pull
|
||||
|
||||
run_graphs_reqs_a:
|
||||
cd graphs_reqs_a && uv run langgraph up --no-pull
|
||||
|
||||
run_graphs_reqs_b:
|
||||
cd graphs_reqs_b && uv run langgraph up --no-pull
|
||||
@@ -0,0 +1,88 @@
|
||||
from collections.abc import Sequence
|
||||
from typing import Annotated, Literal, TypedDict
|
||||
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
tools = []
|
||||
|
||||
model_oai = ChatOpenAI(temperature=0)
|
||||
|
||||
model_oai = model_oai.bind_tools(tools)
|
||||
|
||||
|
||||
class AgentState(TypedDict):
|
||||
messages: Annotated[Sequence[BaseMessage], add_messages]
|
||||
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
# If there are no tool calls, then we finish
|
||||
if not last_message.tool_calls:
|
||||
return "end"
|
||||
# Otherwise if there is, we continue
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state, config):
|
||||
model = model_oai
|
||||
messages = state["messages"]
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
|
||||
|
||||
# Define the function to execute tools
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
|
||||
class ContextSchema(TypedDict):
|
||||
model: Literal["anthropic", "openai"]
|
||||
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(AgentState, context_schema=ContextSchema)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
# First, we define the start node. We use `agent`.
|
||||
# This means these are the edges taken after the `agent` node is called.
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "action",
|
||||
# Otherwise we finish.
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("action", "agent")
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
graph = workflow.compile()
|
||||
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "graph-prerelease-reqs-additional-deps"
|
||||
version = "0.1.0"
|
||||
description = "Test for prerelease stuff"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"langgraph==1.1.5"
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "graph-prerelease-reqs-zuper-deps"
|
||||
version = "0.1.0"
|
||||
description = "Test for prerelease stuff"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"langchain-openai==1.1.14"
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"python_version": "3.12",
|
||||
"dependencies": [
|
||||
".",
|
||||
"./deps/additional_deps",
|
||||
"./deps/zuper_deps"
|
||||
],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": "../.env"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "graph-prerelease-reqs"
|
||||
version = "0.1.0"
|
||||
description = "Test for prerelease stuff"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"langchain-openai==1.1.14",
|
||||
"langchain-anthropic==1.4.6",
|
||||
"langgraph==1.1.5"
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
prerelease = "allow"
|
||||
@@ -0,0 +1,89 @@
|
||||
from collections.abc import Sequence
|
||||
from typing import Annotated, Literal, TypedDict
|
||||
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
|
||||
model_oai = ChatOpenAI(temperature=0)
|
||||
|
||||
model_oai = model_oai.bind_tools(tools)
|
||||
|
||||
|
||||
class AgentState(TypedDict):
|
||||
messages: Annotated[Sequence[BaseMessage], add_messages]
|
||||
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
# If there are no tool calls, then we finish
|
||||
if not last_message.tool_calls:
|
||||
return "end"
|
||||
# Otherwise if there is, we continue
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state, config):
|
||||
model = model_oai
|
||||
messages = state["messages"]
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
|
||||
|
||||
# Define the function to execute tools
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
|
||||
class ContextSchema(TypedDict):
|
||||
model: Literal["anthropic", "openai"]
|
||||
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(AgentState, context_schema=ContextSchema)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
# First, we define the start node. We use `agent`.
|
||||
# This means these are the edges taken after the `agent` node is called.
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "action",
|
||||
# Otherwise we finish.
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("action", "agent")
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
graph = workflow.compile()
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"python_version": "3.12",
|
||||
"dependencies": [
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": "../.env"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[project]
|
||||
name = "graph-prerelease-reqs"
|
||||
version = "0.1.0"
|
||||
description = "Test for prerelease stuff"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"langchain-openai==1.1.14",
|
||||
"langgraph==1.1.2",
|
||||
"langchain_community>=0.3.0",
|
||||
]
|
||||
@@ -0,0 +1,96 @@
|
||||
from collections.abc import Sequence
|
||||
from typing import Annotated, Literal, TypedDict
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
|
||||
model_anth = ChatAnthropic(temperature=0, model_name="claude-3-sonnet-20240229")
|
||||
model_oai = ChatOpenAI(temperature=0)
|
||||
|
||||
model_anth = model_anth.bind_tools(tools)
|
||||
model_oai = model_oai.bind_tools(tools)
|
||||
|
||||
|
||||
class AgentContext(TypedDict):
|
||||
model: Literal["anthropic", "openai"]
|
||||
|
||||
|
||||
class AgentState(TypedDict):
|
||||
messages: Annotated[Sequence[BaseMessage], add_messages]
|
||||
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
# If there are no tool calls, then we finish
|
||||
if not last_message.tool_calls:
|
||||
return "end"
|
||||
# Otherwise if there is, we continue
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state, runtime: Runtime[AgentContext]):
|
||||
if runtime.context.get("model", "anthropic") == "anthropic":
|
||||
model = model_anth
|
||||
else:
|
||||
model = model_oai
|
||||
messages = state["messages"]
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
|
||||
|
||||
# Define the function to execute tools
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(AgentState, context_schema=AgentContext)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
# First, we define the start node. We use `agent`.
|
||||
# This means these are the edges taken after the `agent` node is called.
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "action",
|
||||
# Otherwise we finish.
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("action", "agent")
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
graph = workflow.compile()
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://langgra.ph/schema.json",
|
||||
"python_version": "3.12",
|
||||
"dependencies": [
|
||||
"langchain_community",
|
||||
"langchain_anthropic",
|
||||
"langchain_openai",
|
||||
"wikipedia",
|
||||
"scikit-learn",
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph",
|
||||
"storm": "./storm.py:graph"
|
||||
},
|
||||
"env": "../.env"
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Annotated
|
||||
|
||||
from langchain_community.retrievers import WikipediaRetriever
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langchain_community.vectorstores import SKLearnVectorStore
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.messages import (
|
||||
AIMessage,
|
||||
AnyMessage,
|
||||
HumanMessage,
|
||||
ToolMessage,
|
||||
)
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||
from langchain_core.runnables import RunnableConfig, RunnableLambda
|
||||
from langchain_core.runnables import chain as as_runnable
|
||||
from langchain_core.tools import tool
|
||||
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
|
||||
from langgraph.graph import END, StateGraph
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
fast_llm = ChatOpenAI(model="gpt-4o-mini")
|
||||
# Uncomment for a Fireworks model
|
||||
# fast_llm = ChatFireworks(model="accounts/fireworks/models/firefunction-v1", max_tokens=32_000)
|
||||
long_context_llm = ChatOpenAI(model="gpt-4o")
|
||||
|
||||
|
||||
direct_gen_outline_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
(
|
||||
"system",
|
||||
"You are a Wikipedia writer. Write an outline for a Wikipedia page about a user-provided topic. Be comprehensive and specific.",
|
||||
),
|
||||
("user", "{topic}"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class Subsection(BaseModel):
|
||||
subsection_title: str = Field(..., title="Title of the subsection")
|
||||
description: str = Field(..., title="Content of the subsection")
|
||||
|
||||
@property
|
||||
def as_str(self) -> str:
|
||||
return f"### {self.subsection_title}\n\n{self.description}".strip()
|
||||
|
||||
|
||||
class Section(BaseModel):
|
||||
section_title: str = Field(..., title="Title of the section")
|
||||
description: str = Field(..., title="Content of the section")
|
||||
subsections: list[Subsection] | None = Field(
|
||||
default=None,
|
||||
title="Titles and descriptions for each subsection of the Wikipedia page.",
|
||||
)
|
||||
|
||||
@property
|
||||
def as_str(self) -> str:
|
||||
subsections = "\n\n".join(
|
||||
f"### {subsection.subsection_title}\n\n{subsection.description}"
|
||||
for subsection in self.subsections or []
|
||||
)
|
||||
return f"## {self.section_title}\n\n{self.description}\n\n{subsections}".strip()
|
||||
|
||||
|
||||
class Outline(BaseModel):
|
||||
page_title: str = Field(..., title="Title of the Wikipedia page")
|
||||
sections: list[Section] = Field(
|
||||
default_factory=list,
|
||||
title="Titles and descriptions for each section of the Wikipedia page.",
|
||||
)
|
||||
|
||||
@property
|
||||
def as_str(self) -> str:
|
||||
sections = "\n\n".join(section.as_str for section in self.sections)
|
||||
return f"# {self.page_title}\n\n{sections}".strip()
|
||||
|
||||
|
||||
generate_outline_direct = direct_gen_outline_prompt | fast_llm.with_structured_output(
|
||||
Outline
|
||||
)
|
||||
|
||||
gen_related_topics_prompt = ChatPromptTemplate.from_template(
|
||||
"""I'm writing a Wikipedia page for a topic mentioned below. Please identify and recommend some Wikipedia pages on closely related subjects. I'm looking for examples that provide insights into interesting aspects commonly associated with this topic, or examples that help me understand the typical content and structure included in Wikipedia pages for similar topics.
|
||||
|
||||
Please list the as many subjects and urls as you can.
|
||||
|
||||
Topic of interest: {topic}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class RelatedSubjects(BaseModel):
|
||||
topics: list[str] = Field(
|
||||
description="Comprehensive list of related subjects as background research.",
|
||||
)
|
||||
|
||||
|
||||
expand_chain = gen_related_topics_prompt | fast_llm.with_structured_output(
|
||||
RelatedSubjects
|
||||
)
|
||||
|
||||
|
||||
class Editor(BaseModel):
|
||||
affiliation: str = Field(
|
||||
description="Primary affiliation of the editor.",
|
||||
)
|
||||
name: str = Field(
|
||||
description="Name of the editor.",
|
||||
)
|
||||
role: str = Field(
|
||||
description="Role of the editor in the context of the topic.",
|
||||
)
|
||||
description: str = Field(
|
||||
description="Description of the editor's focus, concerns, and motives.",
|
||||
)
|
||||
|
||||
@property
|
||||
def persona(self) -> str:
|
||||
return f"Name: {self.name}\nRole: {self.role}\nAffiliation: {self.affiliation}\nDescription: {self.description}\n"
|
||||
|
||||
|
||||
class Perspectives(BaseModel):
|
||||
editors: list[Editor] = Field(
|
||||
description="Comprehensive list of editors with their roles and affiliations.",
|
||||
# Add a pydantic validation/restriction to be at most M editors
|
||||
)
|
||||
|
||||
|
||||
gen_perspectives_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
(
|
||||
"system",
|
||||
"""You need to select a diverse (and distinct) group of Wikipedia editors who will work together to create a comprehensive article on the topic. Each of them represents a different perspective, role, or affiliation related to this topic.\
|
||||
You can use other Wikipedia pages of related topics for inspiration. For each editor, add a description of what they will focus on.
|
||||
|
||||
Wiki page outlines of related topics for inspiration:
|
||||
{examples}""",
|
||||
),
|
||||
("user", "Topic of interest: {topic}"),
|
||||
]
|
||||
)
|
||||
|
||||
gen_perspectives_chain = gen_perspectives_prompt | ChatOpenAI(
|
||||
model="gpt-4o-mini"
|
||||
).with_structured_output(Perspectives)
|
||||
|
||||
|
||||
wikipedia_retriever = WikipediaRetriever(load_all_available_meta=True, top_k_results=1)
|
||||
|
||||
|
||||
def format_doc(doc, max_length=1000):
|
||||
related = "- ".join(doc.metadata["categories"])
|
||||
return f"### {doc.metadata['title']}\n\nSummary: {doc.page_content}\n\nRelated\n{related}"[
|
||||
:max_length
|
||||
]
|
||||
|
||||
|
||||
def format_docs(docs):
|
||||
return "\n\n".join(format_doc(doc) for doc in docs)
|
||||
|
||||
|
||||
@as_runnable
|
||||
async def survey_subjects(topic: str):
|
||||
related_subjects = await expand_chain.ainvoke({"topic": topic})
|
||||
retrieved_docs = await wikipedia_retriever.abatch(
|
||||
related_subjects.topics, return_exceptions=True
|
||||
)
|
||||
all_docs = []
|
||||
for docs in retrieved_docs:
|
||||
if isinstance(docs, BaseException):
|
||||
continue
|
||||
all_docs.extend(docs)
|
||||
formatted = format_docs(all_docs)
|
||||
return await gen_perspectives_chain.ainvoke({"examples": formatted, "topic": topic})
|
||||
|
||||
|
||||
def add_messages(left, right):
|
||||
if not isinstance(left, list):
|
||||
left = [left]
|
||||
if not isinstance(right, list):
|
||||
right = [right]
|
||||
return left + right
|
||||
|
||||
|
||||
def update_references(references, new_references):
|
||||
if not references:
|
||||
references = {}
|
||||
references.update(new_references)
|
||||
return references
|
||||
|
||||
|
||||
def update_editor(editor, new_editor):
|
||||
# Can only set at the outset
|
||||
if not editor:
|
||||
return new_editor
|
||||
return editor
|
||||
|
||||
|
||||
class InterviewState(TypedDict):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
references: Annotated[dict | None, update_references]
|
||||
editor: Annotated[Editor | None, update_editor]
|
||||
|
||||
|
||||
gen_qn_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
(
|
||||
"system",
|
||||
"""You are an experienced Wikipedia writer and want to edit a specific page. \
|
||||
Besides your identity as a Wikipedia writer, you have a specific focus when researching the topic. \
|
||||
Now, you are chatting with an expert to get information. Ask good questions to get more useful information.
|
||||
|
||||
When you have no more questions to ask, say "Thank you so much for your help!" to end the conversation.\
|
||||
Please only ask one question at a time and don't ask what you have asked before.\
|
||||
Your questions should be related to the topic you want to write.
|
||||
Be comprehensive and curious, gaining as much unique insight from the expert as possible.\
|
||||
|
||||
Stay true to your specific perspective:
|
||||
|
||||
{persona}""",
|
||||
),
|
||||
MessagesPlaceholder(variable_name="messages", optional=True),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def tag_with_name(ai_message: AIMessage, name: str):
|
||||
ai_message.name = name.replace(" ", "_").replace(".", "_")
|
||||
return ai_message
|
||||
|
||||
|
||||
def swap_roles(state: InterviewState, name: str):
|
||||
converted = []
|
||||
for message in state["messages"]:
|
||||
if isinstance(message, AIMessage) and message.name != name:
|
||||
message = HumanMessage(**message.model_dump(exclude={"type"}))
|
||||
converted.append(message)
|
||||
return {"messages": converted}
|
||||
|
||||
|
||||
@as_runnable
|
||||
async def generate_question(state: InterviewState):
|
||||
editor = state["editor"]
|
||||
gn_chain = (
|
||||
RunnableLambda(swap_roles).bind(name=editor.name)
|
||||
| gen_qn_prompt.partial(persona=editor.persona)
|
||||
| fast_llm
|
||||
| RunnableLambda(tag_with_name).bind(name=editor.name)
|
||||
)
|
||||
result = await gn_chain.ainvoke(state)
|
||||
return {"messages": [result]}
|
||||
|
||||
|
||||
class Queries(BaseModel):
|
||||
queries: list[str] = Field(
|
||||
description="Comprehensive list of search engine queries to answer the user's questions.",
|
||||
)
|
||||
|
||||
|
||||
gen_queries_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
(
|
||||
"system",
|
||||
"You are a helpful research assistant. Query the search engine to answer the user's questions.",
|
||||
),
|
||||
MessagesPlaceholder(variable_name="messages", optional=True),
|
||||
]
|
||||
)
|
||||
gen_queries_chain = gen_queries_prompt | ChatOpenAI(
|
||||
model="gpt-4o-mini"
|
||||
).with_structured_output(Queries, include_raw=True)
|
||||
|
||||
|
||||
class AnswerWithCitations(BaseModel):
|
||||
answer: str = Field(
|
||||
description="Comprehensive answer to the user's question with citations.",
|
||||
)
|
||||
cited_urls: list[str] = Field(
|
||||
description="List of urls cited in the answer.",
|
||||
)
|
||||
|
||||
@property
|
||||
def as_str(self) -> str:
|
||||
return f"{self.answer}\n\nCitations:\n\n" + "\n".join(
|
||||
f"[{i + 1}]: {url}" for i, url in enumerate(self.cited_urls)
|
||||
)
|
||||
|
||||
|
||||
gen_answer_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
(
|
||||
"system",
|
||||
"""You are an expert who can use information effectively. You are chatting with a Wikipedia writer who wants\
|
||||
to write a Wikipedia page on the topic you know. You have gathered the related information and will now use the information to form a response.
|
||||
|
||||
Make your response as informative as possible and make sure every sentence is supported by the gathered information.
|
||||
Each response must be backed up by a citation from a reliable source, formatted as a footnote, reproducing the URLS after your response.""",
|
||||
),
|
||||
MessagesPlaceholder(variable_name="messages", optional=True),
|
||||
]
|
||||
)
|
||||
|
||||
gen_answer_chain = gen_answer_prompt | fast_llm.with_structured_output(
|
||||
AnswerWithCitations, include_raw=True
|
||||
).with_config(run_name="GenerateAnswer")
|
||||
|
||||
|
||||
# Tavily is typically a better search engine, but your free queries are limited
|
||||
tavily_search = TavilySearchResults(max_results=4)
|
||||
|
||||
|
||||
@tool
|
||||
async def search_engine(query: str):
|
||||
"""Search engine to the internet."""
|
||||
results = tavily_search.invoke(query)
|
||||
return [{"content": r["content"], "url": r["url"]} for r in results]
|
||||
|
||||
|
||||
async def gen_answer(
|
||||
state: InterviewState,
|
||||
config: RunnableConfig | None = None,
|
||||
name: str = "Subject_Matter_Expert",
|
||||
max_str_len: int = 15000,
|
||||
):
|
||||
swapped_state = swap_roles(state, name) # Convert all other AI messages
|
||||
queries = await gen_queries_chain.ainvoke(swapped_state)
|
||||
query_results = await search_engine.abatch(
|
||||
queries["parsed"].queries, config, return_exceptions=True
|
||||
)
|
||||
successful_results = [
|
||||
res for res in query_results if not isinstance(res, Exception)
|
||||
]
|
||||
all_query_results = {
|
||||
res["url"]: res["content"] for results in successful_results for res in results
|
||||
}
|
||||
# We could be more precise about handling max token length if we wanted to here
|
||||
dumped = json.dumps(all_query_results)[:max_str_len]
|
||||
ai_message: AIMessage = queries["raw"]
|
||||
tool_call = queries["raw"].tool_calls[0]
|
||||
tool_id = tool_call["id"]
|
||||
tool_message = ToolMessage(tool_call_id=tool_id, content=dumped)
|
||||
swapped_state["messages"].extend([ai_message, tool_message])
|
||||
# Only update the shared state with the final answer to avoid
|
||||
# polluting the dialogue history with intermediate messages
|
||||
generated = await gen_answer_chain.ainvoke(swapped_state)
|
||||
cited_urls = set(generated["parsed"].cited_urls)
|
||||
# Save the retrieved information to a the shared state for future reference
|
||||
cited_references = {k: v for k, v in all_query_results.items() if k in cited_urls}
|
||||
formatted_message = AIMessage(name=name, content=generated["parsed"].as_str)
|
||||
return {"messages": [formatted_message], "references": cited_references}
|
||||
|
||||
|
||||
max_num_turns = 5
|
||||
|
||||
|
||||
def route_messages(state: InterviewState, name: str = "Subject_Matter_Expert"):
|
||||
messages = state["messages"]
|
||||
num_responses = len(
|
||||
[m for m in messages if isinstance(m, AIMessage) and m.name == name]
|
||||
)
|
||||
if num_responses >= max_num_turns:
|
||||
return END
|
||||
last_question = messages[-2]
|
||||
if last_question.content.endswith("Thank you so much for your help!"):
|
||||
return END
|
||||
return "ask_question"
|
||||
|
||||
|
||||
builder = StateGraph(InterviewState)
|
||||
|
||||
builder.add_node("ask_question", generate_question)
|
||||
builder.add_node("answer_question", gen_answer)
|
||||
builder.add_conditional_edges("answer_question", route_messages)
|
||||
builder.add_edge("ask_question", "answer_question")
|
||||
|
||||
builder.set_entry_point("ask_question")
|
||||
interview_graph = builder.compile().with_config(run_name="Conduct Interviews")
|
||||
|
||||
refine_outline_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
(
|
||||
"system",
|
||||
"""You are a Wikipedia writer. You have gathered information from experts and search engines. Now, you are refining the outline of the Wikipedia page. \
|
||||
You need to make sure that the outline is comprehensive and specific. \
|
||||
Topic you are writing about: {topic}
|
||||
|
||||
Old outline:
|
||||
|
||||
{old_outline}""",
|
||||
),
|
||||
(
|
||||
"user",
|
||||
"Refine the outline based on your conversations with subject-matter experts:\n\nConversations:\n\n{conversations}\n\nWrite the refined Wikipedia outline:",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
# Using turbo preview since the context can get quite long
|
||||
refine_outline_chain = refine_outline_prompt | long_context_llm.with_structured_output(
|
||||
Outline
|
||||
)
|
||||
|
||||
|
||||
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
|
||||
# reference_docs = [
|
||||
# Document(page_content=v, metadata={"source": k})
|
||||
# for k, v in final_state["references"].items()
|
||||
# ]
|
||||
# # This really doesn't need to be a vectorstore for this size of data.
|
||||
# # It could just be a numpy matrix. Or you could store documents
|
||||
# # across requests if you want.
|
||||
# vectorstore = SKLearnVectorStore.from_documents(
|
||||
# reference_docs,
|
||||
# embedding=embeddings,
|
||||
# )
|
||||
# retriever = vectorstore.as_retriever(k=10)
|
||||
|
||||
vectorstore = SKLearnVectorStore(embedding=embeddings)
|
||||
retriever = vectorstore.as_retriever(k=10)
|
||||
|
||||
|
||||
class SubSection(BaseModel):
|
||||
subsection_title: str = Field(..., title="Title of the subsection")
|
||||
content: str = Field(
|
||||
...,
|
||||
title="Full content of the subsection. Include [#] citations to the cited sources where relevant.",
|
||||
)
|
||||
|
||||
@property
|
||||
def as_str(self) -> str:
|
||||
return f"### {self.subsection_title}\n\n{self.content}".strip()
|
||||
|
||||
|
||||
class WikiSection(BaseModel):
|
||||
section_title: str = Field(..., title="Title of the section")
|
||||
content: str = Field(..., title="Full content of the section")
|
||||
subsections: list[Subsection] | None = Field(
|
||||
default=None,
|
||||
title="Titles and descriptions for each subsection of the Wikipedia page.",
|
||||
)
|
||||
citations: list[str] = Field(default_factory=list)
|
||||
|
||||
@property
|
||||
def as_str(self) -> str:
|
||||
subsections = "\n\n".join(
|
||||
subsection.as_str for subsection in self.subsections or []
|
||||
)
|
||||
citations = "\n".join([f" [{i}] {cit}" for i, cit in enumerate(self.citations)])
|
||||
return (
|
||||
f"## {self.section_title}\n\n{self.content}\n\n{subsections}".strip()
|
||||
+ f"\n\n{citations}".strip()
|
||||
)
|
||||
|
||||
|
||||
section_writer_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
(
|
||||
"system",
|
||||
"You are an expert Wikipedia writer. Complete your assigned WikiSection from the following outline:\n\n"
|
||||
"{outline}\n\nCite your sources, using the following references:\n\n<Documents>\n{docs}\n<Documents>",
|
||||
),
|
||||
("user", "Write the full WikiSection for the {section} section."),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def retrieve(inputs: dict):
|
||||
docs = await retriever.ainvoke(inputs["topic"] + ": " + inputs["section"])
|
||||
formatted = "\n".join(
|
||||
[
|
||||
f'<Document href="{doc.metadata["source"]}"/>\n{doc.page_content}\n</Document>'
|
||||
for doc in docs
|
||||
]
|
||||
)
|
||||
return {"docs": formatted, **inputs}
|
||||
|
||||
|
||||
section_writer = (
|
||||
retrieve
|
||||
| section_writer_prompt
|
||||
| long_context_llm.with_structured_output(WikiSection)
|
||||
)
|
||||
|
||||
|
||||
writer_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
(
|
||||
"system",
|
||||
"You are an expert Wikipedia author. Write the complete wiki article on {topic} using the following section drafts:\n\n"
|
||||
"{draft}\n\nStrictly follow Wikipedia format guidelines.",
|
||||
),
|
||||
(
|
||||
"user",
|
||||
'Write the complete Wiki article using markdown format. Organize citations using footnotes like "[1]",'
|
||||
" avoiding duplicates in the footer. Include URLs in the footer.",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
writer = writer_prompt | long_context_llm | StrOutputParser()
|
||||
|
||||
|
||||
class ResearchState(TypedDict):
|
||||
topic: str
|
||||
outline: Outline
|
||||
editors: list[Editor]
|
||||
interview_results: list[InterviewState]
|
||||
# The final sections output
|
||||
sections: list[WikiSection]
|
||||
article: str
|
||||
|
||||
|
||||
async def initialize_research(state: ResearchState):
|
||||
topic = state["topic"]
|
||||
coros = (
|
||||
generate_outline_direct.ainvoke({"topic": topic}),
|
||||
survey_subjects.ainvoke(topic),
|
||||
)
|
||||
results = await asyncio.gather(*coros)
|
||||
return {
|
||||
**state,
|
||||
"outline": results[0],
|
||||
"editors": results[1].editors,
|
||||
}
|
||||
|
||||
|
||||
async def conduct_interviews(state: ResearchState):
|
||||
topic = state["topic"]
|
||||
initial_states = [
|
||||
{
|
||||
"editor": editor,
|
||||
"messages": [
|
||||
AIMessage(
|
||||
content=f"So you said you were writing an article on {topic}?",
|
||||
name="Subject_Matter_Expert",
|
||||
)
|
||||
],
|
||||
}
|
||||
for editor in state["editors"]
|
||||
]
|
||||
# We call in to the sub-graph here to parallelize the interviews
|
||||
interview_results = await interview_graph.abatch(initial_states)
|
||||
|
||||
return {
|
||||
**state,
|
||||
"interview_results": interview_results,
|
||||
}
|
||||
|
||||
|
||||
def format_conversation(interview_state):
|
||||
messages = interview_state["messages"]
|
||||
convo = "\n".join(f"{m.name}: {m.content}" for m in messages)
|
||||
return f"Conversation with {interview_state['editor'].name}\n\n" + convo
|
||||
|
||||
|
||||
async def refine_outline(state: ResearchState):
|
||||
convos = "\n\n".join(
|
||||
[
|
||||
format_conversation(interview_state)
|
||||
for interview_state in state["interview_results"]
|
||||
]
|
||||
)
|
||||
|
||||
updated_outline = await refine_outline_chain.ainvoke(
|
||||
{
|
||||
"topic": state["topic"],
|
||||
"old_outline": state["outline"].as_str,
|
||||
"conversations": convos,
|
||||
}
|
||||
)
|
||||
return {**state, "outline": updated_outline}
|
||||
|
||||
|
||||
async def index_references(state: ResearchState):
|
||||
all_docs = []
|
||||
for interview_state in state["interview_results"]:
|
||||
reference_docs = [
|
||||
Document(page_content=v, metadata={"source": k})
|
||||
for k, v in interview_state["references"].items()
|
||||
]
|
||||
all_docs.extend(reference_docs)
|
||||
await vectorstore.aadd_documents(all_docs)
|
||||
return state
|
||||
|
||||
|
||||
async def write_sections(state: ResearchState):
|
||||
outline = state["outline"]
|
||||
sections = await section_writer.abatch(
|
||||
[
|
||||
{
|
||||
"outline": outline.as_str,
|
||||
"section": section.section_title,
|
||||
"topic": state["topic"],
|
||||
}
|
||||
for section in outline.sections
|
||||
]
|
||||
)
|
||||
return {
|
||||
**state,
|
||||
"sections": sections,
|
||||
}
|
||||
|
||||
|
||||
async def write_article(state: ResearchState):
|
||||
topic = state["topic"]
|
||||
sections = state["sections"]
|
||||
draft = "\n\n".join([section.as_str for section in sections])
|
||||
article = await writer.ainvoke({"topic": topic, "draft": draft})
|
||||
return {
|
||||
**state,
|
||||
"article": article,
|
||||
}
|
||||
|
||||
|
||||
builder_of_storm = StateGraph(ResearchState)
|
||||
|
||||
nodes = [
|
||||
("init_research", initialize_research),
|
||||
("conduct_interviews", conduct_interviews),
|
||||
("refine_outline", refine_outline),
|
||||
("index_references", index_references),
|
||||
("write_sections", write_sections),
|
||||
("write_article", write_article),
|
||||
]
|
||||
for i in range(len(nodes)):
|
||||
name, node = nodes[i]
|
||||
builder_of_storm.add_node(name, node)
|
||||
if i > 0:
|
||||
builder_of_storm.add_edge(nodes[i - 1][0], name)
|
||||
|
||||
builder_of_storm.set_entry_point(nodes[0][0])
|
||||
builder_of_storm.set_finish_point(nodes[-1][0])
|
||||
graph = builder_of_storm.compile()
|
||||
@@ -0,0 +1,99 @@
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Literal, TypedDict
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
|
||||
model_anth = ChatAnthropic(temperature=0, model_name="claude-3-sonnet-20240229")
|
||||
model_oai = ChatOpenAI(temperature=0)
|
||||
|
||||
model_anth = model_anth.bind_tools(tools)
|
||||
model_oai = model_oai.bind_tools(tools)
|
||||
|
||||
prompt = open(Path(__file__).parent.parent / "prompt.txt").read()
|
||||
subprompt = open(Path(__file__).parent / "subprompt.txt").read()
|
||||
|
||||
|
||||
class AgentContext(TypedDict):
|
||||
model: Literal["anthropic", "openai"]
|
||||
|
||||
|
||||
class AgentState(TypedDict):
|
||||
messages: Annotated[Sequence[BaseMessage], add_messages]
|
||||
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
# If there are no tool calls, then we finish
|
||||
if not last_message.tool_calls:
|
||||
return "end"
|
||||
# Otherwise if there is, we continue
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state, runtime: Runtime[AgentContext]):
|
||||
if runtime.context.get("model", "anthropic") == "anthropic":
|
||||
model = model_anth
|
||||
else:
|
||||
model = model_oai
|
||||
messages = state["messages"]
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
|
||||
|
||||
# Define the function to execute tools
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(AgentState, context_schema=AgentContext)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
# First, we define the start node. We use `agent`.
|
||||
# This means these are the edges taken after the `agent` node is called.
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "action",
|
||||
# Otherwise we finish.
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("action", "agent")
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
graph = workflow.compile()
|
||||
@@ -0,0 +1 @@
|
||||
from graphs_reqs_a.graphs_submod.agent import graph # noqa
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://langgra.ph/schema.json",
|
||||
"dependencies": [
|
||||
"."
|
||||
],
|
||||
"env": "../.env",
|
||||
"graphs": {
|
||||
"graph": "./hello.py:graph"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
requests
|
||||
langchain_anthropic
|
||||
langchain_openai
|
||||
langchain_community
|
||||
@@ -0,0 +1,100 @@
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Literal, TypedDict
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
|
||||
model_anth = ChatAnthropic(temperature=0, model_name="claude-3-sonnet-20240229")
|
||||
model_oai = ChatOpenAI(temperature=0)
|
||||
|
||||
model_anth = model_anth.bind_tools(tools)
|
||||
model_oai = model_oai.bind_tools(tools)
|
||||
|
||||
prompt = open(Path(__file__).parent.parent / "prompt.txt").read()
|
||||
subprompt = open(Path(__file__).parent / "subprompt.txt").read()
|
||||
|
||||
|
||||
class AgentContext(TypedDict):
|
||||
model: Literal["anthropic", "openai"]
|
||||
|
||||
|
||||
class AgentState(TypedDict):
|
||||
messages: Annotated[Sequence[BaseMessage], add_messages]
|
||||
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
# If there are no tool calls, then we finish
|
||||
if not last_message.tool_calls:
|
||||
return "end"
|
||||
# Otherwise if there is, we continue
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state, runtime: Runtime[AgentContext]):
|
||||
if runtime.context.get("model", "anthropic") == "anthropic":
|
||||
model = model_anth
|
||||
else:
|
||||
model = model_oai
|
||||
messages = state["messages"]
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
|
||||
|
||||
# Define the function to execute tools
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(AgentState, context_schema=AgentContext)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
# First, we define the start node. We use `agent`.
|
||||
# This means these are the edges taken after the `agent` node is called.
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "action",
|
||||
# Otherwise we finish.
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("action", "agent")
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
graph = workflow.compile()
|
||||
@@ -0,0 +1,4 @@
|
||||
from graphs_submod.agent import graph # noqa
|
||||
from utils.greeter import greet
|
||||
|
||||
greet()
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://langgra.ph/schema.json",
|
||||
"dependencies": [
|
||||
"."
|
||||
],
|
||||
"env": "../.env",
|
||||
"graphs": {
|
||||
"graph": "./hello.py:graph"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
requests
|
||||
langchain_anthropic
|
||||
langchain_openai
|
||||
langchain_community
|
||||
@@ -0,0 +1,2 @@
|
||||
def greet():
|
||||
print("Hello, world!")
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"pip_config_file": "./pipconf.txt",
|
||||
"dependencies": [
|
||||
"langchain_community",
|
||||
"langchain_anthropic",
|
||||
"langchain_openai",
|
||||
"wikipedia",
|
||||
"scikit-learn",
|
||||
"./graphs"
|
||||
],
|
||||
"keep_pkg_tools": false,
|
||||
"graphs": {
|
||||
"agent": "./graphs/agent.py:graph",
|
||||
"storm": "./graphs/storm.py:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from contextvars import ContextVar
|
||||
from typing import Any
|
||||
|
||||
from starlette.applications import Starlette
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.routing import Route
|
||||
|
||||
my_context_var: ContextVar[str] = ContextVar("my_context_var", default="")
|
||||
LIFESPAN_VAL = ""
|
||||
other_context_var = ContextVar("other_context_var", default="")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def my_lifespan(app):
|
||||
global LIFESPAN_VAL
|
||||
LIFESPAN_VAL = "foobar-lifespan"
|
||||
yield
|
||||
assert LIFESPAN_VAL == "foobar-lifespan"
|
||||
LIFESPAN_VAL = ""
|
||||
|
||||
|
||||
class MyContextMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request: Any, call_next: Any) -> Any:
|
||||
token = my_context_var.set("Foobar")
|
||||
try:
|
||||
response = await call_next(request)
|
||||
return response
|
||||
finally:
|
||||
my_context_var.reset(token)
|
||||
|
||||
|
||||
async def custom_my_route(request):
|
||||
"""A great route."""
|
||||
assert my_context_var.get() == "Foobar"
|
||||
assert LIFESPAN_VAL == "foobar-lifespan"
|
||||
return JSONResponse({"foo": "bar"})
|
||||
|
||||
|
||||
async def runs_afakeroute(request):
|
||||
"""Another great route."""
|
||||
assert my_context_var.get() == "Foobar"
|
||||
assert LIFESPAN_VAL == "foobar-lifespan"
|
||||
return JSONResponse({"foo": "afakeroute"})
|
||||
|
||||
|
||||
async def other_middleware(request: Any, call_next: Any) -> Any:
|
||||
other_context_var.set("foobar")
|
||||
response = await call_next(request)
|
||||
other_context_var.reset()
|
||||
return response
|
||||
|
||||
|
||||
app = Starlette(
|
||||
middleware=[(MyContextMiddleware, {}, {})],
|
||||
routes=[
|
||||
Route("/custom/my-route", custom_my_route),
|
||||
Route("/runs/afakeroute", runs_afakeroute),
|
||||
],
|
||||
lifespan=my_lifespan,
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
[global]
|
||||
timeout = 60
|
||||
Generated
+285
@@ -0,0 +1,285 @@
|
||||
# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.4.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
|
||||
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.7.4"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
|
||||
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["main"]
|
||||
markers = "platform_system == \"Windows\""
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.1"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
markers = "python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
|
||||
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"},
|
||||
{file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"},
|
||||
{file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
h11 = ">=0.16"
|
||||
|
||||
[package.extras]
|
||||
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
trio = ["trio (>=0.22.0,<1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
|
||||
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = "*"
|
||||
certifi = "*"
|
||||
httpcore = "==1.*"
|
||||
idna = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli", "brotlicffi"]
|
||||
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-sse"
|
||||
version = "0.4.0"
|
||||
description = "Consume Server-Sent Event (SSE) messages with HTTPX."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"},
|
||||
{file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.7"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-cli"
|
||||
version = "0.1.52"
|
||||
description = "CLI for interacting with LangGraph API"
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
groups = ["main"]
|
||||
files = []
|
||||
develop = true
|
||||
|
||||
[package.dependencies]
|
||||
click = "^8.1.7"
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
url = ".."
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.29"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
groups = ["main"]
|
||||
files = []
|
||||
develop = true
|
||||
|
||||
[package.dependencies]
|
||||
httpx = ">=0.25.2"
|
||||
httpx-sse = ">=0.4.0"
|
||||
orjson = ">=3.10.1"
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
url = "../../sdk-py"
|
||||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.10.5"
|
||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "orjson-3.10.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:545d493c1f560d5ccfc134803ceb8955a14c3fcb47bbb4b2fee0232646d0b932"},
|
||||
{file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4324929c2dd917598212bfd554757feca3e5e0fa60da08be11b4aa8b90013c1"},
|
||||
{file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c13ca5e2ddded0ce6a927ea5a9f27cae77eee4c75547b4297252cb20c4d30e6"},
|
||||
{file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6c8e30adfa52c025f042a87f450a6b9ea29649d828e0fec4858ed5e6caecf63"},
|
||||
{file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:338fd4f071b242f26e9ca802f443edc588fa4ab60bfa81f38beaedf42eda226c"},
|
||||
{file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6970ed7a3126cfed873c5d21ece1cd5d6f83ca6c9afb71bbae21a0b034588d96"},
|
||||
{file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:235dadefb793ad12f7fa11e98a480db1f7c6469ff9e3da5e73c7809c700d746b"},
|
||||
{file = "orjson-3.10.5-cp310-none-win32.whl", hash = "sha256:be79e2393679eda6a590638abda16d167754393f5d0850dcbca2d0c3735cebe2"},
|
||||
{file = "orjson-3.10.5-cp310-none-win_amd64.whl", hash = "sha256:c4a65310ccb5c9910c47b078ba78e2787cb3878cdded1702ac3d0da71ddc5228"},
|
||||
{file = "orjson-3.10.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cdf7365063e80899ae3a697def1277c17a7df7ccfc979990a403dfe77bb54d40"},
|
||||
{file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b68742c469745d0e6ca5724506858f75e2f1e5b59a4315861f9e2b1df77775a"},
|
||||
{file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d10cc1b594951522e35a3463da19e899abe6ca95f3c84c69e9e901e0bd93d38"},
|
||||
{file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcbe82b35d1ac43b0d84072408330fd3295c2896973112d495e7234f7e3da2e1"},
|
||||
{file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c0eb7e0c75e1e486c7563fe231b40fdd658a035ae125c6ba651ca3b07936f5"},
|
||||
{file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:53ed1c879b10de56f35daf06dbc4a0d9a5db98f6ee853c2dbd3ee9d13e6f302f"},
|
||||
{file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:099e81a5975237fda3100f918839af95f42f981447ba8f47adb7b6a3cdb078fa"},
|
||||
{file = "orjson-3.10.5-cp311-none-win32.whl", hash = "sha256:1146bf85ea37ac421594107195db8bc77104f74bc83e8ee21a2e58596bfb2f04"},
|
||||
{file = "orjson-3.10.5-cp311-none-win_amd64.whl", hash = "sha256:36a10f43c5f3a55c2f680efe07aa93ef4a342d2960dd2b1b7ea2dd764fe4a37c"},
|
||||
{file = "orjson-3.10.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:68f85ecae7af14a585a563ac741b0547a3f291de81cd1e20903e79f25170458f"},
|
||||
{file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28afa96f496474ce60d3340fe8d9a263aa93ea01201cd2bad844c45cd21f5268"},
|
||||
{file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cd684927af3e11b6e754df80b9ffafd9fb6adcaa9d3e8fdd5891be5a5cad51e"},
|
||||
{file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d21b9983da032505f7050795e98b5d9eee0df903258951566ecc358f6696969"},
|
||||
{file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ad1de7fef79736dde8c3554e75361ec351158a906d747bd901a52a5c9c8d24b"},
|
||||
{file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d97531cdfe9bdd76d492e69800afd97e5930cb0da6a825646667b2c6c6c0211"},
|
||||
{file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d69858c32f09c3e1ce44b617b3ebba1aba030e777000ebdf72b0d8e365d0b2b3"},
|
||||
{file = "orjson-3.10.5-cp312-none-win32.whl", hash = "sha256:64c9cc089f127e5875901ac05e5c25aa13cfa5dbbbd9602bda51e5c611d6e3e2"},
|
||||
{file = "orjson-3.10.5-cp312-none-win_amd64.whl", hash = "sha256:b2efbd67feff8c1f7728937c0d7f6ca8c25ec81373dc8db4ef394c1d93d13dc5"},
|
||||
{file = "orjson-3.10.5-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:03b565c3b93f5d6e001db48b747d31ea3819b89abf041ee10ac6988886d18e01"},
|
||||
{file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584c902ec19ab7928fd5add1783c909094cc53f31ac7acfada817b0847975f26"},
|
||||
{file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a35455cc0b0b3a1eaf67224035f5388591ec72b9b6136d66b49a553ce9eb1e6"},
|
||||
{file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1670fe88b116c2745a3a30b0f099b699a02bb3482c2591514baf5433819e4f4d"},
|
||||
{file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185c394ef45b18b9a7d8e8f333606e2e8194a50c6e3c664215aae8cf42c5385e"},
|
||||
{file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ca0b3a94ac8d3886c9581b9f9de3ce858263865fdaa383fbc31c310b9eac07c9"},
|
||||
{file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dfc91d4720d48e2a709e9c368d5125b4b5899dced34b5400c3837dadc7d6271b"},
|
||||
{file = "orjson-3.10.5-cp38-none-win32.whl", hash = "sha256:c05f16701ab2a4ca146d0bca950af254cb7c02f3c01fca8efbbad82d23b3d9d4"},
|
||||
{file = "orjson-3.10.5-cp38-none-win_amd64.whl", hash = "sha256:8a11d459338f96a9aa7f232ba95679fc0c7cedbd1b990d736467894210205c09"},
|
||||
{file = "orjson-3.10.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:85c89131d7b3218db1b24c4abecea92fd6c7f9fab87441cfc342d3acc725d807"},
|
||||
{file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66215277a230c456f9038d5e2d84778141643207f85336ef8d2a9da26bd7ca"},
|
||||
{file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51bbcdea96cdefa4a9b4461e690c75ad4e33796530d182bdd5c38980202c134a"},
|
||||
{file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbead71dbe65f959b7bd8cf91e0e11d5338033eba34c114f69078d59827ee139"},
|
||||
{file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df58d206e78c40da118a8c14fc189207fffdcb1f21b3b4c9c0c18e839b5a214"},
|
||||
{file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4057c3b511bb8aef605616bd3f1f002a697c7e4da6adf095ca5b84c0fd43595"},
|
||||
{file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b39e006b00c57125ab974362e740c14a0c6a66ff695bff44615dcf4a70ce2b86"},
|
||||
{file = "orjson-3.10.5-cp39-none-win32.whl", hash = "sha256:eded5138cc565a9d618e111c6d5c2547bbdd951114eb822f7f6309e04db0fb47"},
|
||||
{file = "orjson-3.10.5-cp39-none-win_amd64.whl", hash = "sha256:cc28e90a7cae7fcba2493953cff61da5a52950e78dc2dacfe931a317ee3d8de7"},
|
||||
{file = "orjson-3.10.5.tar.gz", hash = "sha256:7a5baef8a4284405d96c90c7c62b755e9ef1ada84c2406c24a9ebec86b89f46d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
content-hash = "ec5109729f30d2033a10a10e8f8d3ed94c7d96d5d31025b4815b0123664bb063"
|
||||
@@ -0,0 +1,21 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph-examples"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"langgraph-cli",
|
||||
"langgraph-sdk",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
langgraph-cli = { path = "../cli", editable = true }
|
||||
langgraph-sdk = { path = "../sdk_py", editable = true }
|
||||
|
||||
[tool.hatch.build]
|
||||
packages = []
|
||||
Reference in New Issue
Block a user