Skip to content

Commit 7978a9c

Browse files
committed
fix: tasks format
1 parent ee06cfa commit 7978a9c

File tree

10 files changed

+97
-21
lines changed

10 files changed

+97
-21
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@
3131

3232
## User Cases
3333

34-
- `Find all .py file and explain them to me`
34+
<div align="center">
35+
<video width="80%" controls>
36+
<source src="./examples/imgs/nano-manus.mp4" type="video/mp4">
37+
</video>
38+
<p>nano-manus demo video</p>
39+
</div>
40+
41+
- `Find all .py file and explain them to me `
3542
- `Give me the latest weather in SF in last 7 days and save it to csv`
3643

3744
> Welcome to give more user cases!
@@ -70,12 +77,14 @@ uv run examples/basic_planner.py
7077
- [x] Execute codes and commands in your computer (`@wonderwhy-er/desktop-commander`)
7178
- [ ] (coming soon) Read `.pdf, .doc`
7279
- [ ] (coming soon) browser use
80+
- [ ] (coming soon) multi-model router (`claude`, `qwen`, `deepseek`...)
7381

7482

7583

7684

7785

7886
## Known Issues
7987

88+
- `nano-manus` is extremely unstable! My guess is `gpt-4o` is not that good at tool use.
8089
- `Unable to exit`: seem like some MCPs will cause the problems of unable to exit the program when all the tasks were done.
8190
- `nano-manus` will operate files and run command **on the current dir of your local computer**, make sure you don't run it on some important folders.

examples/imgs/nano-manus.mp4

15.7 MB
Binary file not shown.

examples/load_weather.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
def read_and_print_weather(file_path):
2+
try:
3+
with open(file_path, 'r') as file:
4+
contents = file.read()
5+
print(contents)
6+
except FileNotFoundError:
7+
print(f"The file {file_path} does not exist.")
8+
except Exception as e:
9+
print(f"An error occurred: {e}")
10+
11+
if __name__ == "__main__":
12+
read_and_print_weather('sf_weather.csv')

examples/sf_weather.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Weather Information
2+
High chance of rain, 90% probability of precipitation, Rainfall expected near half an inch

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "nano_manus"
3-
version = "0.0.2"
3+
version = "0.0.3"
44
description = "Implementing some features of Manus with MCP"
55
readme = "README.md"
66
authors = [{ name = "Gus", email = "[email protected]" }]
@@ -16,5 +16,8 @@ dependencies = [
1616
requires = ["hatchling"]
1717
build-backend = "hatchling.build"
1818

19+
[tool.hatch.build]
20+
exclude = ["examples/*", "docs/*", ".github/*", ".git/*", "tests/*"]
21+
1922
[dependency-groups]
2023
dev = ["pytest-asyncio>=0.25.3", "pytest>=8.3.5"]

src/nano_manus/env.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ def get_async_openai_client() -> AsyncOpenAI:
2626

2727
async def llm_complete(model: str, messages: list[dict], **kwargs) -> ChatCompletion:
2828
client = get_async_openai_client()
29+
if "temperature" not in kwargs:
30+
kwargs["temperature"] = 0.1
2931
response = await client.chat.completions.create(
3032
model=model, messages=messages, **kwargs
3133
)

src/nano_manus/planner/parser.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import re
2-
from ..env import CONSOLE
32

4-
CODE_BLOCK_PATTERN = re.compile(r"```tasks(.*?)```", re.DOTALL)
5-
GOAL_BLOCK_PATTERN = re.compile(r"^(.*?)```tasks", re.DOTALL)
3+
CODE_BLOCK_PATTERN = re.compile(r"<tasks>(.*?)</tasks>", re.DOTALL)
4+
TASK_BLOCK_PATTERN = re.compile(r"<subtask>(.*?)</subtask>", re.DOTALL)
5+
GOAL_BLOCK_PATTERN = re.compile(r"^(.*?)<tasks>", re.DOTALL)
66

77

88
def parse_step(step: str) -> dict:
@@ -18,8 +18,8 @@ def parse_step(step: str) -> dict:
1818
return goal, []
1919
else:
2020
subtasks = code_blocks[0].strip()
21-
subtasks = [l.strip() for l in subtasks.split("\n") if l.startswith("- ")]
22-
expressions = [parse_subtask_expression(st[2:]) for st in subtasks]
21+
subtask_blocks = TASK_BLOCK_PATTERN.findall(subtasks)
22+
expressions = [parse_subtask_expression(st.strip()) for st in subtask_blocks]
2323
expressions = [e for e in expressions if e is not None]
2424
return goal, expressions
2525

@@ -29,7 +29,11 @@ def parse_subtask_expression(subtask: str) -> dict:
2929
subtask = subtask.strip()
3030

3131
# Split by '=' to separate result name and the rest
32-
result_name, expression = [x.strip() for x in subtask.split("=")]
32+
parts = subtask.split("=")
33+
result_name, expression = (
34+
parts[0].strip(),
35+
"=".join(parts[1:]).strip(),
36+
)
3337

3438
# Extract agent_id and param from subtask(agent_id, "param")
3539
# Remove 'subtask(' from start and ')' from end
@@ -44,3 +48,23 @@ def parse_subtask_expression(subtask: str) -> dict:
4448
param = param.strip("\"'")
4549

4650
return {"result_name": result_name, "agent_id": agent_id, "param": param}
51+
52+
53+
if __name__ == "__main__":
54+
print(
55+
parse_step(
56+
"""
57+
### Step 3: Write a Python script to load the CSV file.
58+
59+
Sub-goal: Use the Terminal Agent to create a Python file that loads and prints the content of the `sf_weather.csv` file.
60+
61+
<tasks>
62+
<subtask>
63+
result_31 = subtask(agent_1, "Create a Python file named 'load_sf_weather.py' with the following content:\n\n```\nimport csv\n\nwith open('sf_weather.csv', 'r') as file:\n reader = csv.reader(file)\n for row in reader:\n print(', '.join(row))\n```\n")
64+
</subtask>
65+
</tasks>
66+
67+
Please let me know once this step is completed or if there are any issues.
68+
"""
69+
)
70+
)

src/nano_manus/planner/planner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Planner:
1111
def __init__(
1212
self, max_steps: int = 30, max_tasks: int = 60, workers: list[BaseWorker] = []
1313
):
14+
1415
self.__workers: list[BaseWorker] = workers
1516
self.__max_steps = max_steps
1617
self.__max_tasks = max_tasks

src/nano_manus/planner/prompts.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,25 @@
2121
</input>
2222
<output id=0>
2323
I need to use agent_0 to perform 3+5 and 5+8, they can be executed in parallel.
24-
```tasks
25-
- result_11 = subtask(agent_0, "Perform 3 + 5")
26-
- result_12 = subtask(agent_0, "Perform 5 + 8")
27-
```
24+
<tasks>
25+
<subtask>
26+
result_11 = subtask(agent_0, "Perform 3 + 5")
27+
</subtask>
28+
<subtask>
29+
result_12 = subtask(agent_0, "Perform 5 + 8")
30+
</subtask>
31+
</tasks>
2832
<input id=1>
2933
- result_11 = 8
3034
- result_12 = 13
3135
</input>
3236
<output id=1>
3337
I need to use agent_1 to perform the subtraction between result_11 and result_12
34-
```tasks
35-
- result_21 = subtask(agent_1, "Perform result_11 - result_12")
36-
```
38+
<tasks>
39+
<subtask>
40+
result_21 = subtask(agent_1, "Perform result_11 - result_12")
41+
</subtask>
42+
</tasks>
3743
</output>
3844
<input id=2>
3945
- result_21 = -5
@@ -44,9 +50,16 @@
4450
<explanation>
4551
Above is an example of how to plan the task into a list of steps. The following is the explanation step by step:
4652
- For each new step, you should start with your goal in this step.
47-
- After goal, you should wrap the tasks of this step with code block ```tasks.
48-
- The content of the code block is lines of subtasks:
49-
- `- RESULT_ID = subtask(AGENT_ID, TASK_DESCRIPTION)` is a notation of a subtask, where you use the agent with id AGENT_ID to perform the task TASK_DESCRIPTION, and denote the result of this subtask as RESULT_ID.
53+
- After goal, you should wrap the tasks of this step with tag <tasks>.
54+
- The content of the <tasks> is XML of subtasks:
55+
```
56+
<subtask>
57+
RESULT_ID = subtask(AGENT_ID, TASK_DESCRIPTION)
58+
</subtask>
59+
<subtask>
60+
...
61+
```
62+
- `RESULT_ID = subtask(AGENT_ID, TASK_DESCRIPTION)` is a notation of a subtask, where you use the agent with id AGENT_ID to perform the task TASK_DESCRIPTION, and denote the result of this subtask as RESULT_ID.
5063
- After your plan, the user will give you the result of tasks, with the format:
5164
```
5265
- RESULT_ID1 = AGENT_RETURN_1
@@ -55,7 +68,7 @@
5568
```
5669
- You need to decide which step to go next based on the result.
5770
- Remember to use the same RESULT_ID in your later steps instead the actual value.
58-
- When you have finished the user task or you have no more steps to go, output the final result to response and no more ```tasks is needed.
71+
- When you have finished the user task or you have no more steps to go, output the final result to response and no more <tasks> is needed.
5972
</explanation>
6073
</example>
6174
@@ -68,7 +81,8 @@
6881
## Notes
6982
- If you find the previous results is wrong or not useful, you can re-plan the task with same result_id.
7083
- You should decide if you have enough agents to perform the task, if not, you should ask the user to add more agents.
71-
- Make sure you give enough context to the agents in TASK_DESCRIPTION, you ```tasks block is the only way to communicate with other agents.
84+
- Make sure you give enough context to the agents in TASK_DESCRIPTION, you <tasks> block is the only way to communicate with other agents.
85+
- If you want to agent to use the previous results, make sure you told the agent about the RESULT_ID you need them to use in TASK_DESCRIPTION
7286
- If you have finished the task, you need to answer the user's question and restate some result if needed.
7387
7488
Now, understand the task in user input, and plan the task into a list of steps based on the above instructions:

src/nano_manus/worker/mcp_agent.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
# Tools
99
{tools}
1010
11+
# Additional Context
12+
Below is the additional context you may need to understand the user's instruction.
13+
{additional_context}
14+
1115
# Planning step by step
1216
Before you start to call functions, you should always plan step by step and explain your plan in a concise way.
1317
@@ -44,6 +48,9 @@ async def handle(self, instruction: str, global_ctx: dict = {}) -> str:
4448
CONSOLE.print(
4549
f"🤖 {self.name}: I will use previouse results", additional_context
4650
)
51+
additional_context_string = "\n".join(
52+
[f"- {k}: {v}" for k, v in additional_context.items()]
53+
)
4754
hints = [await m.hint() for m in self.__mcps]
4855
tool_schemas = []
4956
for m in self.__mcps:
@@ -54,7 +61,9 @@ async def handle(self, instruction: str, global_ctx: dict = {}) -> str:
5461
find_tools[tool.name] = m.call_tool(tool.name)
5562

5663
system_prompt = PROMPT.format(
57-
system=self.overwrite_system(), tools="\n".join(hints)
64+
system=self.overwrite_system(),
65+
tools="\n".join(hints),
66+
additional_context=additional_context_string,
5867
)
5968
messages = [
6069
{

0 commit comments

Comments
 (0)