Skip to content

Commit 52f4acf

Browse files
committed
update gitmcp.py
1 parent 4ed124c commit 52f4acf

File tree

7 files changed

+484
-15
lines changed

7 files changed

+484
-15
lines changed

examples/basic/planner-workflow.py

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
"""
2+
Task: Process a number through a sequence of two steps:
3+
- Burify: increment the number by 3
4+
- Tonify: multiply the number by 4
5+
6+
Planner Agent oversees the process, using two worker agents:
7+
- BurifyAgent: handles the Burify step
8+
- TonifyAgent: handles the Tonify step
9+
10+
Planner checks intermediate results and provides feedback to worker agents,
11+
until their step is complete, before proceeding to the next step.
12+
13+
Run like this from repo root (omit `-m` to use default model gpt-4.1-mini):
14+
15+
uv run examples/basic/planner-workflow.py -m gpt-4.1-mini
16+
"""
17+
18+
from typing import List
19+
import langroid as lr
20+
import langroid.language_models as lm
21+
from langroid.pydantic_v1 import Field
22+
from langroid.agent.tools.orchestration import AgentDoneTool, ForwardTool
23+
from fire import Fire
24+
import logging
25+
26+
logger = logging.getLogger(__name__)
27+
MODEL = lm.OpenAIChatModel.GPT4_1_MINI
28+
29+
30+
class BurifyTool(lr.ToolMessage):
31+
request: str = "burify_tool"
32+
purpose: str = "To apply the 'Burify' process to a <number>"
33+
number: int = Field(..., description="The number (int) to Burify")
34+
35+
def handle(self) -> str:
36+
# stateless tool: handler used in BurifyAgent
37+
return f"Burify this number: {self.number}"
38+
39+
40+
class TonifyTool(lr.ToolMessage):
41+
request: str = "tonify_tool"
42+
purpose: str = "To apply the 'Tonify' process to a <number>"
43+
number: int = Field(..., description="The number (int) to Tonify")
44+
45+
def handle(self) -> str:
46+
# stateless tool: handler used in TonifyAgent
47+
return f"Tonify this number: {self.number}"
48+
49+
50+
class BurifyCheckTool(lr.ToolMessage):
51+
request: str = "burify_check_tool"
52+
purpose: str = "To check if the Burify process is complete"
53+
number: int = Field(..., description="The number (int) to check")
54+
original_number: int = Field(
55+
...,
56+
description="The original number (int) given to the BurifyAgent",
57+
)
58+
59+
def handle(self) -> str:
60+
# stateless tool
61+
if self.number == self.original_number + 3:
62+
return AcceptTool(result=self.number)
63+
else:
64+
return BurifyRevisionTool(
65+
feedback="Burify is NOT complete! Please try again.",
66+
recipient="Burify",
67+
)
68+
69+
70+
class TonifyCheckTool(lr.ToolMessage):
71+
request: str = "tonify_check_tool"
72+
purpose: str = "To check if the Tonify process is complete"
73+
number: int = Field(..., description="The number (int) to check")
74+
original_number: int = Field(
75+
...,
76+
description="The original number (int) given to the TonifyAgent",
77+
)
78+
79+
def handle(self):
80+
# stateless tool
81+
if self.number == self.original_number * 4:
82+
return AcceptTool(result=self.number)
83+
else:
84+
return TonifyRevisionTool(
85+
feedback="Tonify is NOT complete! Please try again.",
86+
recipient="Tonify",
87+
)
88+
89+
90+
class BurifyRevisionTool(lr.ToolMessage):
91+
request: str = "burify_revision_tool"
92+
purpose: str = "To give <feedback> to the 'BurifyAgent' on their Burify Attempt"
93+
feedback: str = Field(..., description="Feedback for the BurifyAgent")
94+
95+
def handle(self):
96+
return f"""
97+
Below is feedback on your attempt to Burify:
98+
<Feedback>
99+
{self.feedback}
100+
</Feedback>
101+
Please try again!
102+
"""
103+
104+
105+
class TonifyRevisionTool(lr.ToolMessage):
106+
request: str = "tonify_revision_tool"
107+
purpose: str = "To give <feedback> to the 'TonifyAgent' on their Tonify Attempt"
108+
feedback: str = Field(..., description="Feedback for the TonifyAgent")
109+
110+
def handle(self):
111+
return f"""
112+
Below is feedback on your attempt to Tonify:
113+
<Feedback>
114+
{self.feedback}
115+
</Feedback>
116+
Please try again!
117+
"""
118+
119+
120+
class BurifySubmitTool(lr.ToolMessage):
121+
request: str = "burify_submit_tool"
122+
purpose: str = "To submit the result of an attempt of the Burify process"
123+
result: int = Field(..., description="The result (int) to submit")
124+
125+
def handle(self):
126+
return AgentDoneTool(content=str(self.result))
127+
128+
129+
class TonifySubmitTool(lr.ToolMessage):
130+
request: str = "tonify_submit_tool"
131+
purpose: str = "To submit the result of an attempt of the Tonify process"
132+
result: int = Field(..., description="The result (int) to submit")
133+
134+
def handle(self):
135+
return AgentDoneTool(content=str(self.result))
136+
137+
138+
class AcceptTool(lr.ToolMessage):
139+
request: str = "accept_tool"
140+
purpose: str = "To accept the result of the 'Burify' or 'Tonify' process"
141+
result: int
142+
143+
144+
class PlannerConfig(lr.ChatAgentConfig):
145+
name: str = "Planner"
146+
steps: List[str] = ["Burify", "Tonify"]
147+
handle_llm_no_tool: str = "You FORGOT to use one of your TOOLs!"
148+
system_message: str = f"""
149+
You are a Planner in charge of PROCESSING a given integer through
150+
a SEQUENCE of 2 processing STEPS, which you CANNOT do by yourself, but you must
151+
rely on WORKER AGENTS who will do these for you:
152+
- Burify - will be done by the BurifyAgent
153+
- Tonify - will be done by the TonifyAgent
154+
155+
In order to INITIATE each process, you MUST use the appropriate TOOLs:
156+
- `{BurifyTool.name()}` to Burify the number (the tool will be handled by the BurifyAgent)
157+
- `{TonifyTool.name()}` to Tonify the number (the tool will be handled by the TonifyAgent)
158+
159+
Each of the WORKER AGENTS works like this:
160+
- The Agent will ATTEMPT a processing step, using the number you give it.
161+
- You will VERIFY whether the processing step is COMPLETE or NOT
162+
using the CORRESPONDING CHECK TOOL:
163+
- check if the Burify step is complete using the `{BurifyCheckTool.name()}`
164+
- check if the Tonify step is complete using the `{TonifyCheckTool.name()}`
165+
- If the step is NOT complete, you will ask the Agent to try again,
166+
by using the CORRESPONDING Revision TOOL where you can include your FEEDBACK:
167+
- `{BurifyRevisionTool.name()}` to revise the Burify step
168+
- `{TonifyRevisionTool.name()}` to revise the Tonify step
169+
- If you determine (see below) that the step is COMPLETE, you MUST
170+
use the `{AcceptTool.name()}` to ACCEPT the result of the step.
171+
"""
172+
173+
174+
class PlannerAgent(lr.ChatAgent):
175+
current_step: int
176+
current_num: int
177+
original_num: int
178+
179+
def __init__(self, config: PlannerConfig):
180+
super().__init__(config)
181+
self.config: PlannerConfig = config
182+
self.current_step = 0
183+
self.current_num = 0
184+
185+
def burify_tool(self, msg: BurifyTool) -> str:
186+
"""Handler of BurifyTool: uses/updates Agent state"""
187+
self.original_num = msg.number
188+
logger.warning(f"Planner handled BurifyTool: {self.current_num}")
189+
190+
return ForwardTool(agent="Burify")
191+
192+
def tonify_tool(self, msg: TonifyTool) -> str:
193+
"""Handler of TonifyTool: uses/updates Agent state"""
194+
self.original_num = msg.number
195+
logger.warning(f"Planner handled TonifyTool: {self.current_num}")
196+
197+
return ForwardTool(agent="Tonify")
198+
199+
def accept_tool(self, msg: AcceptTool) -> str:
200+
"""Handler of AcceptTool: uses/updates Agent state"""
201+
curr_step_name = self.config.steps[self.current_step]
202+
n_steps = len(self.config.steps)
203+
self.current_num = msg.result
204+
if self.current_step == n_steps - 1:
205+
# last step -> done
206+
return AgentDoneTool(content=str(self.current_num))
207+
208+
self.current_step += 1
209+
next_step_name = self.config.steps[self.current_step]
210+
return f"""
211+
You have ACCEPTED the result of the {curr_step_name} step.
212+
Your next step is to apply the {next_step_name} process
213+
to the result of the {curr_step_name} step, which is {self.current_num}.
214+
So use a TOOL to initiate the {next_step_name} process!
215+
"""
216+
217+
218+
class BurifyAgentConfig(lr.ChatAgentConfig):
219+
name: str = "Burify"
220+
handle_llm_no_tool: str = f"You FORGOT to use the TOOL `{BurifySubmitTool.name()}`!"
221+
system_message: str = f"""
222+
You will receive an integer from your supervisor, to apply
223+
a process Burify to it, which you are not quite sure how to do,
224+
but you only know that it involves INCREMENTING the number by 1 a few times
225+
(but you don't know how many times).
226+
When you first receive a number to Burify, simply return the number + 1.
227+
If this is NOT sufficient, you will be asked to try again, and
228+
you must CONTINUE to return your last number, INCREMENTED by 1.
229+
To send your result, you MUST use the TOOL `{BurifySubmitTool.name()}`.
230+
"""
231+
232+
233+
class TonifyAgentConfig(lr.ChatAgentConfig):
234+
name: str = "Tonify"
235+
handle_llm_no_tool: str = f"You FORGOT to use the TOOL `{TonifySubmitTool.name()}`!"
236+
system_message: str = """
237+
You will receive an integer from your supervisor, to apply
238+
a process Tonify to it, which you are not quite sure how to do,
239+
but you only know that it involves MULTIPLYING the number by 2 a few times
240+
(and you don't know how many times).
241+
When you first receive a number to Tonify, simply return the number * 2.
242+
If this is NOT sufficient, you will be asked to try again, and
243+
you must CONTINUE to return your last number, MULTIPLIED by 2.
244+
To send your result, you MUST use the TOOL `{TonifySubmitTool.name()}`.
245+
"""
246+
247+
248+
def main(model: str = ""):
249+
planner = PlannerAgent(
250+
PlannerConfig(
251+
llm=lm.OpenAIGPTConfig(
252+
chat_model=model or MODEL,
253+
)
254+
),
255+
)
256+
257+
planner.enable_message(
258+
[
259+
BurifyRevisionTool,
260+
TonifyRevisionTool,
261+
],
262+
use=True, # LLM allowed to generate
263+
handle=False, # agent cannot handle
264+
)
265+
266+
planner.enable_message( # can use and handle
267+
[
268+
AcceptTool,
269+
BurifyCheckTool,
270+
TonifyCheckTool,
271+
BurifyTool,
272+
TonifyTool,
273+
]
274+
)
275+
276+
burifier = lr.ChatAgent(
277+
BurifyAgentConfig(
278+
llm=lm.OpenAIGPTConfig(
279+
chat_model=model or MODEL,
280+
)
281+
)
282+
)
283+
burifier.enable_message(
284+
[
285+
BurifyTool,
286+
BurifyRevisionTool,
287+
],
288+
use=False, # LLM cannot generate
289+
handle=True, # agent can handle
290+
)
291+
burifier.enable_message(BurifySubmitTool)
292+
293+
tonifier = lr.ChatAgent(
294+
TonifyAgentConfig(
295+
llm=lm.OpenAIGPTConfig(
296+
chat_model=model or MODEL,
297+
)
298+
)
299+
)
300+
301+
tonifier.enable_message(
302+
[
303+
TonifyTool,
304+
TonifyRevisionTool,
305+
],
306+
use=False, # LLM cannot generate
307+
handle=True, # agent can handle
308+
)
309+
tonifier.enable_message(TonifySubmitTool)
310+
311+
planner_task = lr.Task(planner, interactive=False)
312+
burifier_task = lr.Task(burifier, interactive=False)
313+
tonifier_task = lr.Task(tonifier, interactive=False)
314+
315+
planner_task.add_sub_task(
316+
[
317+
burifier_task,
318+
tonifier_task,
319+
]
320+
)
321+
322+
# Buify(5) = 5+3 = 8; Tonify(8) = 8*4 = 32
323+
result = planner_task.run("Sequentially all processes to this number: 5")
324+
assert "32" in result.content, f"Expected 32, got {result.content}"
325+
326+
327+
if __name__ == "__main__":
328+
Fire(main)

examples/mcp/any-mcp.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
Generic script to connect to any MCP Server.
3+
4+
Steps:
5+
- from the MCP server page, determine what type of transport is need to connect.
6+
- import the appropriate transport
7+
- set up the `transport` variable in the first line
8+
9+
Run like this (omitting the `--model` argument will use the default GPT-4.1-Mini):
10+
11+
uv run examples/mcp/any-mcp.py --model ollama/qwen2.5-coder:32b
12+
13+
See docs on various types of transports that are available:
14+
https://langroid.github.io/langroid/notes/mcp-tools/
15+
"""
16+
17+
import langroid as lr
18+
import langroid.language_models as lm
19+
from langroid.mytypes import NonToolAction
20+
from langroid.agent.tools.mcp.fastmcp_client import get_tools_async
21+
from fastmcp.client.transports import StdioTransport
22+
from fire import Fire
23+
24+
25+
async def main(model: str = ""):
26+
transport = StdioTransport( # or any other transport
27+
command="...",
28+
args=[],
29+
env=dict(MY_VAR="blah"),
30+
)
31+
all_tools = await get_tools_async(transport)
32+
33+
agent = lr.ChatAgent(
34+
lr.ChatAgentConfig(
35+
# forward to user when LLM doesn't use a tool
36+
handle_llm_no_tool=NonToolAction.FORWARD_USER,
37+
llm=lm.OpenAIGPTConfig(
38+
chat_model=model or "gpt-4.1-mini",
39+
max_output_tokens=1000,
40+
async_stream_quiet=False,
41+
),
42+
)
43+
)
44+
45+
# enable the agent to use all tools
46+
agent.enable_message(all_tools)
47+
# make task with interactive=False =>
48+
# waits for user only when LLM doesn't use a tool
49+
task = lr.Task(agent, interactive=False)
50+
await task.run_async(
51+
"Based on the TOOLs available to you, greet the user and"
52+
"tell them what kinds of help you can provide."
53+
)
54+
55+
56+
if __name__ == "__main__":
57+
Fire(main)

0 commit comments

Comments
 (0)