Commit
·
36383cc
1
Parent(s):
69dad1b
add human in the loop
Browse files- .env.example +4 -0
- main.py +52 -8
- pmcp/agents/executor.py +12 -13
- pmcp/agents/planner.py +4 -4
- pyproject.toml +4 -1
- uv.lock +35 -2
.env.example
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MCP_TRELLO_PATH=C:/Users/Matteo/Desktop/dev/trello-mcp-server/main.py
|
| 2 |
+
MCP_GITHUB_PATH=C:/Users/Matteo/Desktop/dev/github-mcp-server/main.py
|
| 3 |
+
|
| 4 |
+
NEBIUS_API_KEY=
|
main.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import os
|
| 2 |
import pprint
|
|
|
|
| 3 |
import uuid
|
|
|
|
| 4 |
|
| 5 |
from langchain_mcp_adapters.client import MultiServerMCPClient
|
| 6 |
from langchain_openai import ChatOpenAI
|
|
@@ -8,6 +10,8 @@ from langgraph.prebuilt import ToolNode
|
|
| 8 |
from langgraph.graph import MessagesState, END, StateGraph
|
| 9 |
from langchain_core.messages import HumanMessage
|
| 10 |
from langgraph.checkpoint.memory import MemorySaver
|
|
|
|
|
|
|
| 11 |
|
| 12 |
from pmcp.agents.executor import ExecutorAgent
|
| 13 |
from pmcp.agents.trello_agent import TrelloAgent
|
|
@@ -16,27 +20,60 @@ from pmcp.agents.planner import PlannerAgent
|
|
| 16 |
|
| 17 |
from pmcp.models.state import PlanningState
|
| 18 |
|
|
|
|
|
|
|
| 19 |
|
| 20 |
async def call_llm(llm_with_tools: ChatOpenAI, state: MessagesState):
|
| 21 |
response = llm_with_tools.invoke(state["messages"])
|
| 22 |
return {"messages": [response]}
|
| 23 |
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
async def main():
|
| 26 |
mcp_client_trello = MultiServerMCPClient(
|
| 27 |
{
|
| 28 |
"trello": {
|
| 29 |
-
"
|
| 30 |
-
"
|
|
|
|
| 31 |
}
|
| 32 |
}
|
| 33 |
)
|
| 34 |
-
|
| 35 |
mcp_client_github = MultiServerMCPClient(
|
| 36 |
{
|
| 37 |
"github": {
|
| 38 |
-
"
|
| 39 |
-
"
|
|
|
|
| 40 |
}
|
| 41 |
}
|
| 42 |
)
|
|
@@ -45,6 +82,7 @@ async def main():
|
|
| 45 |
|
| 46 |
trello_tools = await mcp_client_trello.get_tools()
|
| 47 |
github_tools = await mcp_client_github.get_tools()
|
|
|
|
| 48 |
tool_node = ToolNode(github_tools + trello_tools)
|
| 49 |
|
| 50 |
llm = ChatOpenAI(
|
|
@@ -72,17 +110,19 @@ async def main():
|
|
| 72 |
graph.add_node(github_agent.agent.agent_name, github_agent.acall_github_agent)
|
| 73 |
graph.add_node(executor_agent.agent.agent_name, executor_agent.acall_executor_agent)
|
| 74 |
graph.add_node("tool", tool_node)
|
|
|
|
| 75 |
graph.set_entry_point(planner_agent.agent.agent_name)
|
| 76 |
|
| 77 |
def should_continue(state: PlanningState):
|
| 78 |
last_message = state.messages[-1]
|
| 79 |
if last_message.tool_calls:
|
| 80 |
-
return "
|
| 81 |
return executor_agent.agent.agent_name
|
| 82 |
|
| 83 |
def execute_agent(state: PlanningState):
|
| 84 |
if state.current_step:
|
| 85 |
return state.current_step.agent
|
|
|
|
| 86 |
return END
|
| 87 |
|
| 88 |
graph.add_conditional_edges(trello_agent.agent.agent_name, should_continue)
|
|
@@ -94,10 +134,14 @@ async def main():
|
|
| 94 |
graph.add_edge(planner_agent.agent.agent_name, executor_agent.agent.agent_name)
|
| 95 |
|
| 96 |
app = graph.compile(checkpointer=memory)
|
| 97 |
-
app.get_graph(xray=True).
|
|
|
|
| 98 |
|
| 99 |
user_input = input("user >")
|
| 100 |
-
config = {
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
while user_input.lower() != "q":
|
| 103 |
async for res in app.astream(
|
|
|
|
| 1 |
import os
|
| 2 |
import pprint
|
| 3 |
+
from typing import Literal
|
| 4 |
import uuid
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
|
| 7 |
from langchain_mcp_adapters.client import MultiServerMCPClient
|
| 8 |
from langchain_openai import ChatOpenAI
|
|
|
|
| 10 |
from langgraph.graph import MessagesState, END, StateGraph
|
| 11 |
from langchain_core.messages import HumanMessage
|
| 12 |
from langgraph.checkpoint.memory import MemorySaver
|
| 13 |
+
from langgraph.types import Command, interrupt
|
| 14 |
+
|
| 15 |
|
| 16 |
from pmcp.agents.executor import ExecutorAgent
|
| 17 |
from pmcp.agents.trello_agent import TrelloAgent
|
|
|
|
| 20 |
|
| 21 |
from pmcp.models.state import PlanningState
|
| 22 |
|
| 23 |
+
load_dotenv()
|
| 24 |
+
|
| 25 |
|
| 26 |
async def call_llm(llm_with_tools: ChatOpenAI, state: MessagesState):
|
| 27 |
response = llm_with_tools.invoke(state["messages"])
|
| 28 |
return {"messages": [response]}
|
| 29 |
|
| 30 |
|
| 31 |
+
def human_review_node(state) -> Command[Literal["PLANNER_AGENT", "tool"]]:
|
| 32 |
+
last_message = state["messages"][-1]
|
| 33 |
+
tool_call = last_message.tool_calls[-1]
|
| 34 |
+
if tool_call.get("name", "").startswith("get_"):
|
| 35 |
+
return Command(goto="tool")
|
| 36 |
+
|
| 37 |
+
human_review = interrupt(
|
| 38 |
+
{
|
| 39 |
+
"question": "Is this correct?",
|
| 40 |
+
# Surface tool calls for review
|
| 41 |
+
"tool_call": tool_call,
|
| 42 |
+
}
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
review_action = human_review["action"]
|
| 46 |
+
review_data = human_review.get("data")
|
| 47 |
+
|
| 48 |
+
if review_action == "continue":
|
| 49 |
+
return Command(goto="tool")
|
| 50 |
+
|
| 51 |
+
else:
|
| 52 |
+
tool_message = {
|
| 53 |
+
"role": "tool",
|
| 54 |
+
"content": review_data,
|
| 55 |
+
"name": tool_call["name"],
|
| 56 |
+
"tool_call_id": tool_call["id"],
|
| 57 |
+
}
|
| 58 |
+
return Command(goto="PLANNER_AGENT", update={"messages": [tool_message]})
|
| 59 |
+
|
| 60 |
+
|
| 61 |
async def main():
|
| 62 |
mcp_client_trello = MultiServerMCPClient(
|
| 63 |
{
|
| 64 |
"trello": {
|
| 65 |
+
"command": "python",
|
| 66 |
+
"args": [os.getenv("MCP_TRELLO_PATH")],
|
| 67 |
+
"transport": "stdio",
|
| 68 |
}
|
| 69 |
}
|
| 70 |
)
|
|
|
|
| 71 |
mcp_client_github = MultiServerMCPClient(
|
| 72 |
{
|
| 73 |
"github": {
|
| 74 |
+
"command": "python",
|
| 75 |
+
"args": [os.getenv("MCP_GITHUB_PATH")],
|
| 76 |
+
"transport": "stdio",
|
| 77 |
}
|
| 78 |
}
|
| 79 |
)
|
|
|
|
| 82 |
|
| 83 |
trello_tools = await mcp_client_trello.get_tools()
|
| 84 |
github_tools = await mcp_client_github.get_tools()
|
| 85 |
+
|
| 86 |
tool_node = ToolNode(github_tools + trello_tools)
|
| 87 |
|
| 88 |
llm = ChatOpenAI(
|
|
|
|
| 110 |
graph.add_node(github_agent.agent.agent_name, github_agent.acall_github_agent)
|
| 111 |
graph.add_node(executor_agent.agent.agent_name, executor_agent.acall_executor_agent)
|
| 112 |
graph.add_node("tool", tool_node)
|
| 113 |
+
graph.add_node("human_review", human_review_node)
|
| 114 |
graph.set_entry_point(planner_agent.agent.agent_name)
|
| 115 |
|
| 116 |
def should_continue(state: PlanningState):
|
| 117 |
last_message = state.messages[-1]
|
| 118 |
if last_message.tool_calls:
|
| 119 |
+
return "human_review"
|
| 120 |
return executor_agent.agent.agent_name
|
| 121 |
|
| 122 |
def execute_agent(state: PlanningState):
|
| 123 |
if state.current_step:
|
| 124 |
return state.current_step.agent
|
| 125 |
+
|
| 126 |
return END
|
| 127 |
|
| 128 |
graph.add_conditional_edges(trello_agent.agent.agent_name, should_continue)
|
|
|
|
| 134 |
graph.add_edge(planner_agent.agent.agent_name, executor_agent.agent.agent_name)
|
| 135 |
|
| 136 |
app = graph.compile(checkpointer=memory)
|
| 137 |
+
app.get_graph(xray=True).draw_mermaid()
|
| 138 |
+
|
| 139 |
|
| 140 |
user_input = input("user >")
|
| 141 |
+
config = {
|
| 142 |
+
"configurable": {"thread_id": f"{str(uuid.uuid4())}"},
|
| 143 |
+
"recursion_limit": 100,
|
| 144 |
+
}
|
| 145 |
|
| 146 |
while user_input.lower() != "q":
|
| 147 |
async for res in app.astream(
|
pmcp/agents/executor.py
CHANGED
|
@@ -31,18 +31,17 @@ class ExecutorAgent:
|
|
| 31 |
}
|
| 32 |
|
| 33 |
async def acall_executor_agent(self, state: PlanningState):
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
if len(state.plan.steps) >
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
| 40 |
return {
|
| 41 |
-
"plan_step":
|
| 42 |
-
"messages":
|
| 43 |
-
|
| 44 |
-
content=f"The {plan_step.agent} agent should perform the following action:\n{plan_step.description}"
|
| 45 |
-
)
|
| 46 |
-
],
|
| 47 |
-
"current_step": plan_step,
|
| 48 |
}
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
async def acall_executor_agent(self, state: PlanningState):
|
| 34 |
+
plan_step_index = state.plan_step
|
| 35 |
+
current_step = None
|
| 36 |
+
messages = []
|
| 37 |
+
if len(state.plan.steps) > plan_step_index:
|
| 38 |
+
current_step = state.plan.steps[plan_step_index]
|
| 39 |
+
messages = [HumanMessage(
|
| 40 |
+
content=f"The {current_step.agent} agent should perform the following action:\n{current_step.description}"
|
| 41 |
+
)]
|
| 42 |
+
|
| 43 |
return {
|
| 44 |
+
"plan_step": plan_step_index + 1,
|
| 45 |
+
"messages": messages,
|
| 46 |
+
"current_step": current_step,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
}
|
pmcp/agents/planner.py
CHANGED
|
@@ -10,8 +10,8 @@ from pmcp.models.state import PlanningState
|
|
| 10 |
|
| 11 |
SYSTEM_PROMPT = """
|
| 12 |
You are a Planner Agent responsible for breaking down high-level project goals into clear, actionable steps. You do not execute tasks yourself — instead, you delegate them to two specialized agents:
|
| 13 |
-
-
|
| 14 |
-
-
|
| 15 |
Your job is to:
|
| 16 |
- Analyze the user’s request or project goal.
|
| 17 |
- Decompose it into a step-by-step plan with granular, unambiguous tasks.
|
|
@@ -42,11 +42,11 @@ class PlannerAgent:
|
|
| 42 |
messages=[SystemMessage(content=self.agent.system_prompt)] + state.messages,
|
| 43 |
clazz=Plan,
|
| 44 |
)
|
| 45 |
-
return {"plan": response}
|
| 46 |
|
| 47 |
async def acall_planner_agent(self, state: PlanningState):
|
| 48 |
response = await self.agent.acall_agent_structured(
|
| 49 |
messages=[SystemMessage(content=self.agent.system_prompt)] + state.messages,
|
| 50 |
clazz=Plan,
|
| 51 |
)
|
| 52 |
-
return {"plan": response}
|
|
|
|
| 10 |
|
| 11 |
SYSTEM_PROMPT = """
|
| 12 |
You are a Planner Agent responsible for breaking down high-level project goals into clear, actionable steps. You do not execute tasks yourself — instead, you delegate them to two specialized agents:
|
| 13 |
+
- TRELLO_AGENT – Handles all operations related to Trello (boards (only read), lists, cards, assignments, due dates, etc.).
|
| 14 |
+
- GITHUB_AGENT – Handles all operations related to GitHub (issues, can see in textual form the repository).
|
| 15 |
Your job is to:
|
| 16 |
- Analyze the user’s request or project goal.
|
| 17 |
- Decompose it into a step-by-step plan with granular, unambiguous tasks.
|
|
|
|
| 42 |
messages=[SystemMessage(content=self.agent.system_prompt)] + state.messages,
|
| 43 |
clazz=Plan,
|
| 44 |
)
|
| 45 |
+
return {"plan": response, "plan_step": 0, "current_step": None}
|
| 46 |
|
| 47 |
async def acall_planner_agent(self, state: PlanningState):
|
| 48 |
response = await self.agent.acall_agent_structured(
|
| 49 |
messages=[SystemMessage(content=self.agent.system_prompt)] + state.messages,
|
| 50 |
clazz=Plan,
|
| 51 |
)
|
| 52 |
+
return {"plan": response, "plan_step": 0, "current_step": None}
|
pyproject.toml
CHANGED
|
@@ -10,8 +10,11 @@ dependencies = [
|
|
| 10 |
"langchain-openai>=0.3.18",
|
| 11 |
"langgraph>=0.4.7",
|
| 12 |
"langgraph-checkpoint-sqlite>=2.0.10",
|
| 13 |
-
"mcp>=1.9.0",
|
| 14 |
"smolagents[litellm,mcp,toolkit]>=1.17.0",
|
|
|
|
|
|
|
|
|
|
| 15 |
]
|
| 16 |
|
| 17 |
[dependency-groups]
|
|
|
|
| 10 |
"langchain-openai>=0.3.18",
|
| 11 |
"langgraph>=0.4.7",
|
| 12 |
"langgraph-checkpoint-sqlite>=2.0.10",
|
| 13 |
+
"mcp[cli]>=1.9.0",
|
| 14 |
"smolagents[litellm,mcp,toolkit]>=1.17.0",
|
| 15 |
+
"httpx>=0.28.1",
|
| 16 |
+
"requests>=2.32.3",
|
| 17 |
+
"grandalf>=0.8",
|
| 18 |
]
|
| 19 |
|
| 20 |
[dependency-groups]
|
uv.lock
CHANGED
|
@@ -488,6 +488,18 @@ wheels = [
|
|
| 488 |
{ url = "https://files.pythonhosted.org/packages/9b/1b/b372308c263379ae3ebc440512432979458330113bdee26cef86c89bf48e/gradio_client-1.10.2-py3-none-any.whl", hash = "sha256:6de67b6224123d264c7887caa0586b2a9e2c369ec32ca38927cf8a841694edcd", size = 323311, upload-time = "2025-05-30T13:59:54.555Z" },
|
| 489 |
]
|
| 490 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
[[package]]
|
| 492 |
name = "groovy"
|
| 493 |
version = "0.1.2"
|
|
@@ -1004,6 +1016,12 @@ wheels = [
|
|
| 1004 |
{ url = "https://files.pythonhosted.org/packages/a5/d5/22e36c95c83c80eb47c83f231095419cf57cf5cca5416f1c960032074c78/mcp-1.9.0-py3-none-any.whl", hash = "sha256:9dfb89c8c56f742da10a5910a1f64b0d2ac2c3ed2bd572ddb1cfab7f35957178", size = 125082, upload-time = "2025-05-15T18:51:04.916Z" },
|
| 1005 |
]
|
| 1006 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1007 |
[[package]]
|
| 1008 |
name = "mcpadapt"
|
| 1009 |
version = "0.1.9"
|
|
@@ -1315,11 +1333,14 @@ version = "0.1.0"
|
|
| 1315 |
source = { virtual = "." }
|
| 1316 |
dependencies = [
|
| 1317 |
{ name = "gradio", extra = ["mcp"] },
|
|
|
|
|
|
|
| 1318 |
{ name = "langchain-mcp-adapters" },
|
| 1319 |
{ name = "langchain-openai" },
|
| 1320 |
{ name = "langgraph" },
|
| 1321 |
{ name = "langgraph-checkpoint-sqlite" },
|
| 1322 |
-
{ name = "mcp" },
|
|
|
|
| 1323 |
{ name = "smolagents", extra = ["litellm", "mcp", "toolkit"] },
|
| 1324 |
]
|
| 1325 |
|
|
@@ -1332,11 +1353,14 @@ dev = [
|
|
| 1332 |
[package.metadata]
|
| 1333 |
requires-dist = [
|
| 1334 |
{ name = "gradio", extras = ["mcp"], specifier = ">=5.32.0" },
|
|
|
|
|
|
|
| 1335 |
{ name = "langchain-mcp-adapters", specifier = ">=0.1.1" },
|
| 1336 |
{ name = "langchain-openai", specifier = ">=0.3.18" },
|
| 1337 |
{ name = "langgraph", specifier = ">=0.4.7" },
|
| 1338 |
{ name = "langgraph-checkpoint-sqlite", specifier = ">=2.0.10" },
|
| 1339 |
-
{ name = "mcp", specifier = ">=1.9.0" },
|
|
|
|
| 1340 |
{ name = "smolagents", extras = ["litellm", "mcp", "toolkit"], specifier = ">=1.17.0" },
|
| 1341 |
]
|
| 1342 |
|
|
@@ -1533,6 +1557,15 @@ wheels = [
|
|
| 1533 |
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
|
| 1534 |
]
|
| 1535 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1536 |
[[package]]
|
| 1537 |
name = "python-dateutil"
|
| 1538 |
version = "2.9.0.post0"
|
|
|
|
| 488 |
{ url = "https://files.pythonhosted.org/packages/9b/1b/b372308c263379ae3ebc440512432979458330113bdee26cef86c89bf48e/gradio_client-1.10.2-py3-none-any.whl", hash = "sha256:6de67b6224123d264c7887caa0586b2a9e2c369ec32ca38927cf8a841694edcd", size = 323311, upload-time = "2025-05-30T13:59:54.555Z" },
|
| 489 |
]
|
| 490 |
|
| 491 |
+
[[package]]
|
| 492 |
+
name = "grandalf"
|
| 493 |
+
version = "0.8"
|
| 494 |
+
source = { registry = "https://pypi.org/simple" }
|
| 495 |
+
dependencies = [
|
| 496 |
+
{ name = "pyparsing" },
|
| 497 |
+
]
|
| 498 |
+
sdist = { url = "https://files.pythonhosted.org/packages/95/0e/4ac934b416857969f9135dec17ac80660634327e003a870835dd1f382659/grandalf-0.8.tar.gz", hash = "sha256:2813f7aab87f0d20f334a3162ccfbcbf085977134a17a5b516940a93a77ea974", size = 38128, upload-time = "2023-01-26T07:37:06.668Z" }
|
| 499 |
+
wheels = [
|
| 500 |
+
{ url = "https://files.pythonhosted.org/packages/61/30/44c7eb0a952478dbb5f2f67df806686d6a7e4b19f6204e091c4f49dc7c69/grandalf-0.8-py3-none-any.whl", hash = "sha256:793ca254442f4a79252ea9ff1ab998e852c1e071b863593e5383afee906b4185", size = 41802, upload-time = "2023-01-10T15:16:19.753Z" },
|
| 501 |
+
]
|
| 502 |
+
|
| 503 |
[[package]]
|
| 504 |
name = "groovy"
|
| 505 |
version = "0.1.2"
|
|
|
|
| 1016 |
{ url = "https://files.pythonhosted.org/packages/a5/d5/22e36c95c83c80eb47c83f231095419cf57cf5cca5416f1c960032074c78/mcp-1.9.0-py3-none-any.whl", hash = "sha256:9dfb89c8c56f742da10a5910a1f64b0d2ac2c3ed2bd572ddb1cfab7f35957178", size = 125082, upload-time = "2025-05-15T18:51:04.916Z" },
|
| 1017 |
]
|
| 1018 |
|
| 1019 |
+
[package.optional-dependencies]
|
| 1020 |
+
cli = [
|
| 1021 |
+
{ name = "python-dotenv" },
|
| 1022 |
+
{ name = "typer" },
|
| 1023 |
+
]
|
| 1024 |
+
|
| 1025 |
[[package]]
|
| 1026 |
name = "mcpadapt"
|
| 1027 |
version = "0.1.9"
|
|
|
|
| 1333 |
source = { virtual = "." }
|
| 1334 |
dependencies = [
|
| 1335 |
{ name = "gradio", extra = ["mcp"] },
|
| 1336 |
+
{ name = "grandalf" },
|
| 1337 |
+
{ name = "httpx" },
|
| 1338 |
{ name = "langchain-mcp-adapters" },
|
| 1339 |
{ name = "langchain-openai" },
|
| 1340 |
{ name = "langgraph" },
|
| 1341 |
{ name = "langgraph-checkpoint-sqlite" },
|
| 1342 |
+
{ name = "mcp", extra = ["cli"] },
|
| 1343 |
+
{ name = "requests" },
|
| 1344 |
{ name = "smolagents", extra = ["litellm", "mcp", "toolkit"] },
|
| 1345 |
]
|
| 1346 |
|
|
|
|
| 1353 |
[package.metadata]
|
| 1354 |
requires-dist = [
|
| 1355 |
{ name = "gradio", extras = ["mcp"], specifier = ">=5.32.0" },
|
| 1356 |
+
{ name = "grandalf", specifier = ">=0.8" },
|
| 1357 |
+
{ name = "httpx", specifier = ">=0.28.1" },
|
| 1358 |
{ name = "langchain-mcp-adapters", specifier = ">=0.1.1" },
|
| 1359 |
{ name = "langchain-openai", specifier = ">=0.3.18" },
|
| 1360 |
{ name = "langgraph", specifier = ">=0.4.7" },
|
| 1361 |
{ name = "langgraph-checkpoint-sqlite", specifier = ">=2.0.10" },
|
| 1362 |
+
{ name = "mcp", extras = ["cli"], specifier = ">=1.9.0" },
|
| 1363 |
+
{ name = "requests", specifier = ">=2.32.3" },
|
| 1364 |
{ name = "smolagents", extras = ["litellm", "mcp", "toolkit"], specifier = ">=1.17.0" },
|
| 1365 |
]
|
| 1366 |
|
|
|
|
| 1557 |
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
|
| 1558 |
]
|
| 1559 |
|
| 1560 |
+
[[package]]
|
| 1561 |
+
name = "pyparsing"
|
| 1562 |
+
version = "3.2.3"
|
| 1563 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1564 |
+
sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" }
|
| 1565 |
+
wheels = [
|
| 1566 |
+
{ url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" },
|
| 1567 |
+
]
|
| 1568 |
+
|
| 1569 |
[[package]]
|
| 1570 |
name = "python-dateutil"
|
| 1571 |
version = "2.9.0.post0"
|