Skip to content

Commit 70d934f

Browse files
authored
Merge pull request #29 from MiroMindAI/patch_pengxiang
feat(agent): add initial support for agent graph and agent skills
2 parents 64b43bf + 5ad2327 commit 70d934f

File tree

6 files changed

+217
-17
lines changed

6 files changed

+217
-17
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ __marimo__/
209209

210210
logs/
211211
tmp/
212+
thirdparty/
212213

213214
data/*
214215
!data/README.md

config/agent_quickstart_graph.yaml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# MiroFlow Gradio Demo Configuration
2+
# A simplified configuration for the Gradio web interface (using MiroThinker)
3+
4+
# No benchmark defaults - this is a standalone config
5+
defaults:
6+
- benchmark: example_dataset
7+
- override hydra/job_logging: none
8+
- _self_
9+
10+
entrypoint: main_agent
11+
12+
main_agent:
13+
name: main_agent
14+
type: IterativeAgentWithTool
15+
max_turns: 30
16+
17+
llm:
18+
_base_: config/llm/base_openai.yaml
19+
provider_class: GPT5OpenAIClient
20+
model_name: gpt-5
21+
max_tokens: 128000
22+
reasoning_effort: medium
23+
24+
prompt: config/prompts/standard_prompt_main_agent.yaml
25+
26+
tools: null
27+
28+
input_processor:
29+
- ${input-message-generator}
30+
31+
output_processor:
32+
- ${output-summary}
33+
- ${output-boxed-extractor}
34+
35+
sub_agents:
36+
agent-worker: ${agent-subagent-1}
37+
38+
agent-subagent-1:
39+
type: IterativeAgentWithTool
40+
name: agent-subagent-1
41+
max_consecutive_rollbacks: 3
42+
max_turns: 200
43+
llm:
44+
_base_: config/llm/base_mirothinker.yaml
45+
prompt: config/prompts/prompt_sub_agent.yaml
46+
tools: null
47+
input_processor:
48+
- ${input-message-generator}
49+
output_processor:
50+
- ${output-summary}
51+
sub_agents:
52+
agent-worker: ${agent-subagent-3}
53+
54+
agent-subagent-2:
55+
type: IterativeAgentWithTool
56+
name: agent-subagent-2
57+
max_consecutive_rollbacks: 3
58+
max_turns: 200
59+
llm:
60+
_base_: config/llm/base_mirothinker.yaml
61+
prompt: config/prompts/prompt_sub_agent.yaml
62+
tools: null
63+
input_processor:
64+
- ${input-message-generator}
65+
output_processor:
66+
- ${output-summary}
67+
sub_agents:
68+
agent-worker: ${agent-subagent-3}
69+
70+
agent-subagent-3:
71+
type: IterativeAgentWithTool
72+
name: agent-subagent-3
73+
max_consecutive_rollbacks: 3
74+
max_turns: 200
75+
llm:
76+
_base_: config/llm/base_mirothinker.yaml
77+
prompt: config/prompts/prompt_sub_agent.yaml
78+
tools:
79+
- config/tool/tool-python.yaml
80+
- config/tool/tool-search-and-scrape-webpage.yaml
81+
- config/tool/tool-jina-scrape-llm-summary.yaml
82+
tool_blacklist:
83+
- server: "tool-search-and-scrape-webpage"
84+
tool: "sogou_search"
85+
- server: "tool-python"
86+
tool: "download_file_from_sandbox_to_local"
87+
input_processor:
88+
- ${input-message-generator}
89+
output_processor:
90+
- ${output-summary}
91+
92+
93+
94+
# Input processor: generates initial message from task description
95+
input-message-generator:
96+
type: InputMessageGenerator
97+
98+
# Output processor: summarizes conversation
99+
output-summary:
100+
type: SummaryGenerator
101+
102+
# Output processor: extracts final answer in a boxed format
103+
output-boxed-extractor:
104+
type: RegexBoxedExtractor
105+
106+
# Output directory for logs
107+
output_dir: logs
108+
data_dir: "${oc.env:DATA_DIR,data}"
109+
110+
benchmark:
111+
exceed_max_turn_summary: false

config/agent_quickstart_skill.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# MiroFlow Quickstart with Skill Configuration
2+
# A simple single-agent setup with the reading tool and simple_file_understanding skill.
3+
#
4+
# Usage:
5+
# bash scripts/test_single_task.sh \
6+
# --config config/agent_quickstart_skill.yaml \
7+
# --task-question "What is the first country listed in the XLSX file that have names starting with Co?" \
8+
# --file-path data/FSI-2023-DOWNLOAD.xlsx
9+
10+
defaults:
11+
- benchmark: example_dataset
12+
- override hydra/job_logging: none
13+
- _self_
14+
15+
entrypoint: main_agent
16+
17+
main_agent:
18+
name: main_agent
19+
type: IterativeAgentWithTool
20+
max_turns: 30
21+
22+
llm:
23+
_base_: config/llm/base_openai.yaml
24+
provider_class: GPT5OpenAIClient
25+
model_name: gpt-5
26+
max_tokens: 128000
27+
reasoning_effort: medium
28+
29+
prompt: config/prompts/standard_prompt_main_agent.yaml
30+
31+
tools:
32+
- config/tool/tool-python.yaml
33+
34+
skills:
35+
- src/skill/skills/simple_file_understanding
36+
37+
input_processor:
38+
- ${input-message-generator}
39+
40+
output_processor:
41+
- ${output-summary}
42+
- ${output-boxed-extractor}
43+
44+
# Input processor: generates initial message from task description
45+
input-message-generator:
46+
type: InputMessageGenerator
47+
48+
# Output processor: summarizes conversation
49+
output-summary:
50+
type: SummaryGenerator
51+
52+
# Output processor: extracts final answer in a boxed format
53+
output-boxed-extractor:
54+
type: RegexBoxedExtractor
55+
56+
# Output directory for logs
57+
output_dir: logs
58+
data_dir: "${oc.env:DATA_DIR,data}"
59+
60+
benchmark:
61+
exceed_max_turn_summary: false

scripts/run_single_task.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,7 @@ def main():
166166

167167
# Load configuration
168168
print(f"Loading configuration from: {args.config_path}")
169-
cfg = load_config(args.config_path)
170-
171-
# Override output directory
172-
cfg.output_dir = args.output_dir
169+
cfg = load_config(args.config_path, f"output_dir={args.output_dir}")
173170

174171
# Determine which task to run
175172
task = None

src/skill/manager.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from __future__ import annotations
22

3+
import logging
34
import re
4-
import sys
55
from dataclasses import dataclass
66
from pathlib import Path
77
from typing import Any, Dict, List, Optional, Tuple
88

9+
logger = logging.getLogger(__name__)
10+
911

1012
@dataclass
1113
class SkillMeta:
@@ -26,7 +28,7 @@ class SkillError(Exception):
2628
def _parse_frontmatter(md_text: str) -> Tuple[Dict[str, Any], str]:
2729
m = _FRONTMATTER_RE.match(md_text)
2830
if not m:
29-
raise SkillError("SKILL.md 缺少 frontmatter(必须以 --- 开头并闭合 ---")
31+
raise SkillError("SKILL.md is missing frontmatter (must start and end with ---)")
3032

3133
fm_raw, body = m.group(1), m.group(2)
3234
meta: Dict[str, Any] = {}
@@ -90,8 +92,8 @@ def __init__(
9092
allowed_skill_ids: Optional[List[str]] = None,
9193
):
9294
"""
93-
allow_python_skills: 是否允许加载执行 python skill(建议默认 True 但配合白名单)
94-
allowed_skill_ids: 若提供,则只有这些 skill_id 能被执行(强烈建议生产环境启用)
95+
allow_python_skills: Whether to allow loading and executing python skills (recommended to keep True but use with whitelist)
96+
allowed_skill_ids: If provided, only these skill_ids can be executed (strongly recommended for production)
9597
"""
9698
self.skill_dirs = skill_dirs
9799
self.allow_python_skills = allow_python_skills
@@ -102,7 +104,7 @@ def __init__(
102104
def get_all_skills_definitions(self) -> List[SkillMeta]:
103105
skills_server_params = []
104106
index = self.discover()
105-
print("index:", index)
107+
logger.info("Discovered skills index: %s", index)
106108
schema = {
107109
"type": "object",
108110
"properties": {"subtask": {"title": "Subtask", "type": "string"}},
@@ -128,7 +130,7 @@ def get_all_skills_definitions(self) -> List[SkillMeta]:
128130

129131
def discover(self) -> Dict[str, SkillMeta]:
130132
"""
131-
扫描目录,解析每个 SKILL.md 的 frontmatter(只加载元数据,不加载正文/资源)
133+
Scan directories and parse the frontmatter of each SKILL.md (loads metadata only, not body/resources)
132134
"""
133135
index: Dict[str, SkillMeta] = {}
134136

@@ -147,7 +149,7 @@ def discover(self) -> Dict[str, SkillMeta]:
147149
name = str(fm.get("name", "")).strip()
148150
desc = str(fm.get("description", "")).strip()
149151
if not name or not desc:
150-
raise SkillError("frontmatter 必须包含 name description")
152+
raise SkillError("frontmatter must contain name and description")
151153

152154
meta = SkillMeta(
153155
skill_id=skill_dir.name,
@@ -158,11 +160,7 @@ def discover(self) -> Dict[str, SkillMeta]:
158160
)
159161
index[meta.skill_id] = meta
160162
except Exception as e:
161-
# 生产环境建议记录日志,不要直接炸
162-
print(
163-
f"[warn] Failed to load skill meta from {skill_md}: {e}",
164-
file=sys.stderr,
165-
)
163+
logger.warning("Failed to load skill meta from %s: %s", skill_md, e)
166164

167165
self._index = index
168166
return index
@@ -184,7 +182,7 @@ def load(self, skill_id: str) -> str:
184182
and meta.skill_id not in self.allowed_skill_ids
185183
):
186184
raise SkillError(
187-
f"Skill '{meta.skill_id}' 不在 allowed_skill_ids 白名单内,拒绝加载执行。"
185+
f"Skill '{meta.skill_id}' is not in the allowed_skill_ids whitelist, loading denied."
188186
)
189187

190188
text = meta.skill_md.read_text(encoding="utf-8")
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
name: simple_file_understanding
3+
description: Understand and analyze CSV files. Use when the task involves reading, parsing, or answering questions about data in a CSV file.
4+
---
5+
6+
# simple_file_understanding
7+
8+
## Instructions
9+
10+
When a task involves a CSV file, follow this workflow:
11+
12+
### Step 1: Read the File
13+
Use the `read_file` tool from the `tool-reading` MCP server to load the file content. Provide the full local file path as the `uri` argument.
14+
15+
### Step 2: Understand the Structure
16+
After reading the file, identify:
17+
- **Column headers**: The first row typically contains column names.
18+
- **Data types**: Determine whether each column contains numbers, text, dates, or mixed types.
19+
- **Row count**: Note the approximate number of data rows.
20+
- **Delimiter**: CSV files use commas by default, but the content returned will already be converted to markdown table format.
21+
22+
### Step 3: Answer the Question
23+
When answering questions about the CSV data:
24+
- **Filtering**: To find rows matching a condition (e.g., "names starting with Co"), scan the relevant column and apply the filter.
25+
- **Sorting**: If the question asks for "first", "last", "highest", or "lowest", identify the ordering criterion. Unless otherwise specified, "first" means the first matching row in the file's original order (top to bottom).
26+
- **Aggregation**: For questions involving counts, sums, averages, or other aggregations, compute them from the relevant column values.
27+
- **Exact matching**: Pay close attention to exact string matching vs. prefix/substring matching. "Starting with Co" means the value begins with "Co", not just contains "Co".
28+
29+
### Important Notes
30+
- Always read the file before attempting to answer. Do not guess the content.
31+
- If the file is large and the markdown output is truncated, focus on the portions relevant to the question.
32+
- Provide the final answer clearly and concisely, wrapped in \boxed{}.

0 commit comments

Comments
 (0)