Showing preview only (1,174K chars total). Download the full file or copy to clipboard to get everything.
Repository: MiroMindAI/MiroThinker
Branch: main
Commit: 40a9faef2efd
Files: 169
Total size: 1.1 MB
Directory structure:
gitextract_qqy1lifh/
├── .github/
│ └── workflows/
│ └── run-ruff.yml
├── .gitignore
├── LICENSE
├── README.md
├── apps/
│ ├── collect-trace/
│ │ ├── README.md
│ │ ├── pyproject.toml
│ │ ├── scripts/
│ │ │ ├── collect_trace_claude37.sh
│ │ │ ├── collect_trace_gpt41.sh
│ │ │ ├── collect_trace_gpt5.sh
│ │ │ └── collect_trace_qwen3.sh
│ │ └── utils/
│ │ ├── converters/
│ │ │ ├── __init__.py
│ │ │ ├── convert_non_oai_to_chatml.py
│ │ │ ├── convert_oai_to_chatml.py
│ │ │ ├── convert_to_chatml_auto_batch.py
│ │ │ ├── example_usage.py
│ │ │ └── system_prompts.py
│ │ ├── merge_chatml_msgs_to_one_json.py
│ │ └── process_logs.py
│ ├── gradio-demo/
│ │ ├── README.md
│ │ ├── main.py
│ │ ├── prompt_patch.py
│ │ ├── pyproject.toml
│ │ └── utils.py
│ ├── lobehub-compatibility/
│ │ ├── MiroThinkerToolParser.py
│ │ ├── README.md
│ │ ├── chat_template.jinja
│ │ ├── requirements.txt
│ │ ├── test_tool_parser.py
│ │ └── unit_test.py
│ ├── miroflow-agent/
│ │ ├── README.md
│ │ ├── benchmarks/
│ │ │ ├── __init__.py
│ │ │ ├── check_progress/
│ │ │ │ ├── check_progress_aime2025.py
│ │ │ │ ├── check_progress_browsecomp.py
│ │ │ │ ├── check_progress_browsecomp_zh.py
│ │ │ │ ├── check_progress_deepsearchqa.py
│ │ │ │ ├── check_progress_frames.py
│ │ │ │ ├── check_progress_gaia-validation-text-103.py
│ │ │ │ ├── check_progress_gaia-validation.py
│ │ │ │ ├── check_progress_hle-text-2158.py
│ │ │ │ ├── check_progress_hle-text-500.py
│ │ │ │ ├── check_progress_hle.py
│ │ │ │ ├── check_progress_seal-0.py
│ │ │ │ ├── check_progress_webwalkerqa.py
│ │ │ │ ├── check_progress_xbench_deepsearch.py
│ │ │ │ └── common.py
│ │ │ ├── common_benchmark.py
│ │ │ ├── evaluators/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── calculate_average_score.py
│ │ │ │ ├── eval_utils.py
│ │ │ │ └── extract_futurex_results.py
│ │ │ └── subset_extraction/
│ │ │ ├── gaia-text-103-grader.py
│ │ │ └── gaia-to-text-103-mover.py
│ │ ├── conf/
│ │ │ ├── __init__.py
│ │ │ ├── agent/
│ │ │ │ ├── default.yaml
│ │ │ │ ├── demo.yaml
│ │ │ │ ├── mirothinker_1.7_keep5_max200.yaml
│ │ │ │ ├── mirothinker_1.7_keep5_max300.yaml
│ │ │ │ ├── mirothinker_v1.0.yaml
│ │ │ │ ├── mirothinker_v1.0_keep5.yaml
│ │ │ │ ├── mirothinker_v1.5.yaml
│ │ │ │ ├── mirothinker_v1.5_keep5_max200.yaml
│ │ │ │ ├── mirothinker_v1.5_keep5_max400.yaml
│ │ │ │ ├── multi_agent.yaml
│ │ │ │ ├── multi_agent_os.yaml
│ │ │ │ ├── single_agent.yaml
│ │ │ │ └── single_agent_keep5.yaml
│ │ │ ├── benchmark/
│ │ │ │ ├── aime2025.yaml
│ │ │ │ ├── browsecomp.yaml
│ │ │ │ ├── browsecomp_zh.yaml
│ │ │ │ ├── collect_trace.yaml
│ │ │ │ ├── debug.yaml
│ │ │ │ ├── deepsearchqa.yaml
│ │ │ │ ├── default.yaml
│ │ │ │ ├── frames.yaml
│ │ │ │ ├── futurex.yaml
│ │ │ │ ├── gaia-validation-text-103.yaml
│ │ │ │ ├── gaia-validation.yaml
│ │ │ │ ├── hle-text-2158.yaml
│ │ │ │ ├── hle-text-500.yaml
│ │ │ │ ├── hle.yaml
│ │ │ │ ├── seal-0.yaml
│ │ │ │ ├── webwalkerqa.yaml
│ │ │ │ └── xbench_deepsearch.yaml
│ │ │ ├── config.yaml
│ │ │ └── llm/
│ │ │ ├── claude-3-7.yaml
│ │ │ ├── default.yaml
│ │ │ ├── gpt-5.yaml
│ │ │ └── qwen-3.yaml
│ │ ├── main.py
│ │ ├── pyproject.toml
│ │ ├── scripts/
│ │ │ ├── run_evaluate_multiple_runs_aime2025.sh
│ │ │ ├── run_evaluate_multiple_runs_browsecomp.sh
│ │ │ ├── run_evaluate_multiple_runs_browsecomp_zh.sh
│ │ │ ├── run_evaluate_multiple_runs_debug.sh
│ │ │ ├── run_evaluate_multiple_runs_deepsearchqa.sh
│ │ │ ├── run_evaluate_multiple_runs_frames.sh
│ │ │ ├── run_evaluate_multiple_runs_futurex.sh
│ │ │ ├── run_evaluate_multiple_runs_gaia-validation-text-103.sh
│ │ │ ├── run_evaluate_multiple_runs_gaia-validation.sh
│ │ │ ├── run_evaluate_multiple_runs_hle-text-2158.sh
│ │ │ ├── run_evaluate_multiple_runs_hle-text-500.sh
│ │ │ ├── run_evaluate_multiple_runs_hle.sh
│ │ │ ├── run_evaluate_multiple_runs_seal-0.sh
│ │ │ ├── run_evaluate_multiple_runs_webwalkerqa.sh
│ │ │ └── run_evaluate_multiple_runs_xbench_deepsearch.sh
│ │ └── src/
│ │ ├── __init__.py
│ │ ├── config/
│ │ │ ├── __init__.py
│ │ │ └── settings.py
│ │ ├── core/
│ │ │ ├── __init__.py
│ │ │ ├── answer_generator.py
│ │ │ ├── orchestrator.py
│ │ │ ├── pipeline.py
│ │ │ ├── stream_handler.py
│ │ │ └── tool_executor.py
│ │ ├── io/
│ │ │ ├── __init__.py
│ │ │ ├── input_handler.py
│ │ │ └── output_formatter.py
│ │ ├── llm/
│ │ │ ├── __init__.py
│ │ │ ├── base_client.py
│ │ │ ├── factory.py
│ │ │ ├── providers/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── anthropic_client.py
│ │ │ │ └── openai_client.py
│ │ │ └── util.py
│ │ ├── logging/
│ │ │ ├── __init__.py
│ │ │ ├── summary_time_cost.py
│ │ │ └── task_logger.py
│ │ └── utils/
│ │ ├── __init__.py
│ │ ├── parsing_utils.py
│ │ ├── prompt_utils.py
│ │ └── wrapper_utils.py
│ └── visualize-trace/
│ ├── .python-version
│ ├── README.md
│ ├── app.py
│ ├── pyproject.toml
│ ├── requirements.txt
│ ├── run.py
│ ├── static/
│ │ ├── css/
│ │ │ └── style.css
│ │ └── js/
│ │ └── script.js
│ ├── templates/
│ │ └── index.html
│ └── trace_analyzer.py
├── assets/
│ ├── LOCAL-TOOL-DEPLOYMENT.md
│ ├── QA.md
│ └── qwen3_nonthinking.jinja
├── justfile
└── libs/
└── miroflow-tools/
├── README.md
├── pyproject.toml
└── src/
├── __init__.py
└── miroflow_tools/
├── __init__.py
├── dev_mcp_servers/
│ ├── jina_scrape_llm_summary.py
│ ├── search_and_scrape_webpage.py
│ ├── stateless_python_server.py
│ └── task_planner.py
├── manager.py
└── mcp_servers/
├── __init__.py
├── audio_mcp_server.py
├── audio_mcp_server_os.py
├── browser_session.py
├── python_mcp_server.py
├── reading_mcp_server.py
├── reasoning_mcp_server.py
├── reasoning_mcp_server_os.py
├── searching_google_mcp_server.py
├── searching_sogou_mcp_server.py
├── serper_mcp_server.py
├── utils/
│ ├── __init__.py
│ └── url_unquote.py
├── vision_mcp_server.py
└── vision_mcp_server_os.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/run-ruff.yml
================================================
name: lint
on:
pull_request:
branches: [ "main" ]
jobs:
lint:
if: github.repository_owner == 'MiroMindAI'
name: lint pull request
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Check static error
run: |
uv tool run ruff@0.8.0 check --show-fixes --output-format=github
- name: Reformat code style
run: |
echo '## Reformat summary' >> $GITHUB_STEP_SUMMARY
if diff_output="$(uv tool run ruff@0.8.0 format --diff 2>&1)"; then
echo "$diff_output"
echo '✅ Format check passed.' >> "$GITHUB_STEP_SUMMARY"
else
echo "$diff_output"
echo '❌ Format issues detected.' >> "$GITHUB_STEP_SUMMARY"
{
echo '```diff'
echo "$diff_output"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# -- ADDED --
# Log files
logs/
# Data directory - exclude everything except README
data/
.idea/
.DS_Store
apps/collect-trace/scripts/*/*.sh
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: README.md
================================================
<div align="center">
<img src="assets/mirothinker_logo.png" width="55%" alt="MiroThinker" />
</div>
<br>
<div align="center">
[](https://huggingface.co/collections/miromind-ai/mirothinker-17)
[](https://miromind.ai/#blog)
[](https://huggingface.co/datasets/miromind-ai/MiroVerse-v0.1)
[](https://github.com/MiroMindAI)
[](https://miromind.ai/)
[](https://discord.com/invite/GPqEnkzQZd)
</div>
<div align="center">
### 🚀 [Try MiroThinker!](https://dr.miromind.ai/)
</div>
**MiroThinker**: A deep research agent optimized for research and prediction. It achieves a 88.2 on the challenging BrowseComp benchmark. See [Quick Start](#-quick-start).
## 📋 Table of Contents
- 📰 [News & Updates](#-news--updates)
- 📝 [Introduction](#-introduction)
- ✨ [Key Features](#-key-features)
- 📈 [Performance on Benchmarks](#-performance-on-benchmarks)
- 🚀 [Quick Start](#-quick-start)
- 📊 [Benchmark Evaluation](#-benchmark-evaluation)
- 🔬 [Trace Collection](#-trace-collection)
- ❓ [FAQ & Troubleshooting](#-faq--troubleshooting)
- 📄 [License](#-license)
- 🙏 [Acknowledgments](#-acknowledgments)
## 📰 News & Updates
- **[2026-03-11]** 🎉🎉🎉 Introducing [MiroThinker-1.7](https://huggingface.co/collections/miromind-ai/mirothinker-17), including [MiroThinker-1.7-mini](https://huggingface.co/miromind-ai/MiroThinker-1.7-mini) and [MiroThinker-1.7](https://huggingface.co/miromind-ai/MiroThinker-1.7). MiroThinker-1.7-mini achieves 72.3 on BrowseComp-ZH, setting a new SOTA among open-source models while using only 30B parameters. Our proprietary agent MiroThinker-H1 achieves leading performance on BrowseComp and BrowseComp-ZH among open-source and commercial models.
- **\[2026-01-23\]** 🎉 We have brought two important updates to [MiroThinker online](http://dr.miromind.ai): (a) Core Research Report Generation: Deep Research online reports now support generation, preview, and sharing. (b) Extended Document Upload Types: Now supports the upload of various file formats, such as `.pdf`, `.doc`, `.ppt`, `.xls`, `.jpg`. Welcome to try it out! MiroThinker will continue to be maintained and iteratively upgraded, with the goal of becoming the best Research Agent you'll ever use!
- **\[2026-01-05\]** 🎉🎉 We release [MiroThinker-v1.5](https://huggingface.co/collections/miromind-ai/mirothinker-v15), a series of open-source deep research agents optimized for financial prediction. [MiroThinker-v1.5-30B](https://huggingface.co/miromind-ai/MiroThinker-v1.5-30B) surpasses Kimi-K2-Thinking on BrowseComp-ZH at much lower cost, using only 1/30 of the parameters. [MiroThinker-v1.5-235B](https://huggingface.co/miromind-ai/MiroThinker-v1.5-235B) scores 39.2% on HLE-Text, 69.8% on BrowseComp, 71.5% on BrowseComp-ZH, and 80.8% on GAIA-Val-165, setting a new state-of-the-art among search agents.
<details>
<summary>📜 Click to expand older updates</summary>
- **\[2025-11-13\]** 🎉 [MiroThinker-v1.0](https://huggingface.co/collections/miromind-ai/mirothinker-v10) is now released! Introducing **interactive scaling** as a third dimension of performance improvement, MiroThinker v1.0 supports 256K context window and up to 600 tool calls per task. Available in 8B, 30B, and 72B parameter scales, achieving 37.7%, 47.1%, 55.6%, and 81.9% on HLE-Text, BrowseComp, BrowseComp-ZH, and GAIA-Text-103, respectively. See [Technical Report](https://arxiv.org/abs/2511.11793) for more details.
- **\[2025-09-11\]** MiroThinker-72B-Preview ranked 4th in this week's FutureX benchmark. See [FutureX](https://futurex-ai.github.io/).
- **\[2025-09-08\]** [MiroThinker-v0.2](https://huggingface.co/collections/miromind-ai/mirothinker-v02) is now released, achieving open-source SOTA performance across multiple benchmarks, including HLE (17.8%), HLE-Text-Only (19.1%), BrowseComp-EN (17.2%), BrowseComp-ZH (29.4%), XBench-DeepSearch (56.0%), and Frames (74.8%).
- **\[2025-09-07\]** We supported more benchmarks, including [BrowseComp-ZH](https://arxiv.org/abs/2504.19314), [XBench-DeepSearch](https://xbench.org/agi/aisearch), and [FutureX](https://futurex-ai.github.io/). We plan to add more benchmarks in the future.
- **\[2025-08-22\]** Introducing streamlined deployment options for MiroThinker with optimized resource usage and faster startup times. Experience the interactive demo: [🚀 Try Gradio Demo](apps/gradio-demo)
- **\[2025-08-08\]** [MiroThinker-v0.1](https://huggingface.co/collections/miromind-ai/mirothinker-v01-689301b6d0563321862d44a1) released.
</details>
## 📝 Introduction
### MiroThinker-1.7
Our new MiroThinker family represents a significant leap in building reliable agents for long-chain tasks. Engineered with enhanced post-training pipeline, our MiroThinker-1.7 family achieve SOTA performance in deep research tasks among open-source models.
**Key Features**
- 🚀 MiroThinker-1.7 supports a 256K context window, long-horizon reasoning, and deep multi-step analysis.
- 🔧 Handles up to 300 tool interactions per task, now with more accurate stepwise reasoning and decision-making.
- 📦 Released in 30B and 235B parameter scales, accompanied by a comprehensive suite of tools and workflows to flexibly support diverse research settings and compute budgets.
- Our proprietary agent, MiroThinker-H1 provides promising evidence for long-chain verifiable reasoning — reasoning processes that are step-verifiable and globally verifiable, improving the performance of complex agentic workflows.
<div align="center">
| Model Name | Parameters | Max Context | Max Tool Calls | HF Link |
|:---------------------:|:-----------------------------:|:-----------:|:--------------:|:------------------------------------------------------------------:|
| MiroThinker-1.7-mini | 30B | 256K | 300 | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-1.7-mini) |
| MiroThinker-1.7 | 235B | 256K | 300 | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-1.7) |
</div>
MiroThinker-1.7 demonstrates strong general-research performance across a broad range of benchmarks, achieving 74.0%, 75.3%, 82.7% and 42.9% on BrowseComp, BrowseComp-ZH, GAIA-Val-165 and HLE-Text, respectively. MiroThinker-1.7 achieves SOTA performance on BrowseComp-ZH.

### MiroThinker-v1.5
<details>
<summary>📦 Click to expand MiroThinker-v1.5 details</summary>
MiroThinker v1.5 is the world-leading open-source search agent that advances tool-augmented reasoning through **interactive scaling** — training the agent to handle deeper and more frequent agent-environment interactions as a third dimension of performance improvement, beyond model size and context length.

**Key Features**
- 🚀 MiroThinker v1.5 supports a 256K context window, long-horizon reasoning, and deep multi-step analysis.
- 🔧 Handles up to 400 tool calls per task — a substantial improvement over previous open-source research agents.
- 📦 Released in 30B and 235B parameter scales, accompanied by a comprehensive suite of tools and workflows to flexibly support diverse research settings and compute budgets.
<div align="center">
| Agent Name | Base Agent | Max Context | Max Tool Calls | HF Link |
|:---------------------:|:-----------------------------:|:-----------:|:--------------:|:------------------------------------------------------------------:|
| MiroThinker-v1.5-30B | Qwen3-30B-A3B-Thinking-2507 | 256K | 400 | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-v1.5-30B) |
| MiroThinker-v1.5-235B | Qwen3-235B-A22B-Thinking-2507 | 256K | 400 | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-v1.5-235B) |
</div>
MiroThinker v1.5 demonstrates strong general-research performance across a broad range of benchmarks, achieving 39.2%, 69.8%, 71.5%, and 80.8% on HLE-Text, BrowseComp, BrowseComp-ZH, and GAIA-Val-165, respectively. These results surpass previous open-source agents and set the new world-leading BrowseComp performance.

</details>
### MiroThinker-v1.0
<details>
<summary>📦 Click to expand MiroThinker-v1.0 details</summary>
Unlike previous agents that scale only model size or context length, MiroThinker v1.0 introduces **interactive scaling** at the agent level, systematically training the agent to handle deeper and more frequent agent–environment interactions as a third dimension of performance improvement. Interactive scaling leverages environment feedback and external information acquisition to correct errors and refine trajectories.

### ✨ Key Features
- 🚀 **256K Context Window**: Supports long-horizon reasoning and deep multi-step analysis
- 🔧 **600 Tool Calls**: Handles up to 600 tool calls per task — a substantial improvement over previous open-source research agents
- 📦 **Multiple Scales**: Released in 8B, 30B, and 72B parameter scales, accompanied by a comprehensive suite of tools and workflows to flexibly support diverse research settings and compute budgets
<div align="center">
| Agent Name | Base Agent | Max Context | Max Tool Calls | HF Link |
|:--------------------:|:---------------------------:|:-----------:|:--------------:|:------------------------------------------------------------------:|
| MiroThinker-v1.0-8B | Qwen3-8B | 256K | 600 | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-v1.0-8B) |
| MiroThinker-v1.0-30B | Qwen3-30B-A3B-Thinking-2507 | 256K | 600 | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-v1.0-30B) |
| MiroThinker-v1.0-72B | Qwen2.5-72B-Instruct | 256K | 600 | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-v1.0-72B) |
</div>
MiroThinker v1.0 demonstrates strong general-research performance across a broad range of benchmarks, achieving **37.7%**, **47.1%**, **55.6%**, and **81.9%** on HLE-Text, BrowseComp, BrowseComp-ZH, and GAIA-Text-103, respectively. These results surpass previous open-source agents and narrow the gap with commercial counterparts such as **GPT-5-high**.
<div align="center">
<img src="https://huggingface.co/datasets/miromind-ai/MiroFlow-Benchmarks/resolve/main/assets/MiroThinker_v1.0_Performance_1.png" width="100%" alt="MiroThinker" />
</div>
</details>
### MiroThinker-v0.2
<details>
<summary>📦 Click to expand MiroThinker-v0.2 details</summary>
In this new version, we introduced three key improvements:
- 📚 **Richer training data** from both English and Chinese sources, yielding significant gains in benchmark performance and generalization
- 🎯 **Unified DPO training** with a single preference dataset across all agents
- 📏 **Extended context length** from 40k to 64k for more challenging multi-turn tool-use tasks
Compared to v0.1, MiroThinker v0.2 delivers consistent gains across benchmarks. For example, scores improved from **57.3 → 64.1** on **GAIA-Text-103** and from **17.0 → 29.4** on **BrowseComp-ZH**, reflecting substantial advancements in the model’s general research agent capabilities.
<div align="center">
| Agent Name | Base Agent | Max Context | HF Link |
|:------------------------:|:---------------------:|:-----------:|:----------------------------------------------------------------------:|
| MiroThinker-4B-SFT-v0.2 | Qwen3-4B | 64K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-4B-SFT-v0.2) |
| MiroThinker-4B-DPO-v0.2 | Qwen3-4B | 64K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-4B-DPO-v0.2) |
| MiroThinker-8B-SFT-v0.2 | Qwen3-8B | 64K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-8B-SFT-v0.2) |
| MiroThinker-8B-DPO-v0.2 | Qwen3-8B | 64K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-8B-DPO-v0.2) |
| MiroThinker-14B-SFT-v0.2 | Qwen3-14B | 64K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-14B-SFT-v0.2) |
| MiroThinker-14B-DPO-v0.2 | Qwen3-14B | 64K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-14B-DPO-v0.2) |
| MiroThinker-32B-SFT-v0.2 | Qwen3-32B | 64K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-32B-SFT-v0.2) |
| MiroThinker-32B-DPO-v0.2 | Qwen3-32B | 64K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-32B-DPO-v0.2) |
</div>
</details>
### MiroThinker-v0.1
<details>
<summary>📦 Click to expand MiroThinker-v0.1 details</summary>
<div align="center">
<img src="assets/gaia_text_103.png" width="98%" alt="MiroFlow Performance on GAIA-Validation" />
<p><strong>Performance of Open-Source Agents on GAIA-Validation Benchmark.</strong></p>
</div>
We have released the **MiroThinker v0.1** series, including both SFT and DPO variants at parameter scales of **8B**, **14B**, and **32B**. Notably, MiroThinker v0.1 achieves **state-of-the-art performance** among open-source models on the [GAIA benchmark](https://huggingface.co/datasets/gaia-benchmark/GAIA), a rigorous evaluation suite for advanced agentic capabilities, demonstrating its strength in long-context, decision-intensive, and real-world task scenarios.
<div align="center">
| Agent Name | Base Agent | Max Context | HF Link |
| :-----------------------: |:----------:|:-----------:| :--------------------------------------------------------------------:|
| MiroThinker-8B-SFT-v0.1 | Qwen3-8B | 40K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-8B-SFT-v0.1) |
| MiroThinker-8B-DPO-v0.1 | Qwen3-8B | 40K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-8B-DPO-v0.1) |
| MiroThinker-14B-SFT-v0.1 | Qwen3-14B | 40K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-14B-SFT-v0.1) |
| MiroThinker-14B-DPO-v0.1 | Qwen3-14B | 40K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-14B-DPO-v0.1) |
| MiroThinker-32B-SFT-v0.1 | Qwen3-32B | 40K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-32B-SFT-v0.1) |
| MiroThinker-32B-DPO-v0.1 | Qwen3-32B | 40K | [🤗 link](https://huggingface.co/miromind-ai/MiroThinker-32B-DPO-v0.1) |
</div>
</details>
## ✨ Key Features
### 🤖 **MiroThinker-Optimized Framework**
- 🔓 **Fully Open-Source Agent Framework**: Complete transparency with open framework and open agents
- 🔗 **Tool Integration**: Seamless integration with external tools and APIs
- 📝 **Trace Collection**: Comprehensive logging and analysis of agent interactions with elapsed time and estimated completion time displayed in minutes. Ready for SFT and DPO
- 📊 **Benchmark Evaluation**: Extensive testing across multiple benchmark datasets
### 📊 **Comprehensive Benchmark Suite**
<details open>
<summary>📋 Click to expand benchmark list</summary>
- **GAIA Validation**: A benchmark for General AI Assistants. ([paper](https://arxiv.org/abs/2311.12983))
- **GAIA-Text-103**: A subset of GAIA Validation for text-only tasks. ([paper](https://arxiv.org/abs/2505.22648))
- **HLE**: Humanity's Last Exam. ([paper](https://arxiv.org/abs/2501.14249))
- **HLE-Text-2158**: A subset of HLE for text-only tasks. ([paper](https://arxiv.org/abs/2501.14249))
- **HLE-Text-500**: A subset of HLE for text-only tasks, created by [WebThinker](https://arxiv.org/pdf/2504.21776). ([paper](https://arxiv.org/pdf/2504.21776))
- **BrowseComp-EN**: Web browsing and comprehension tasks. ([paper](https://arxiv.org/abs/2504.12516))
- **BrowseComp-ZH**: A Chinese version of BrowseComp. ([paper](https://arxiv.org/abs/2504.19314))
- **WebWalkerQA**: Web navigation and question answering. ([paper](https://arxiv.org/abs/2501.07572))
- **Frames**: Factuality, Retrieval, And reasoning MEasurement Set. ([paper](https://arxiv.org/abs/2409.12941))
- **XBench-DeepSearch**: A benchmark for deep research agents. ([website](https://xbench.org/agi/aisearch))
- **FutureX**: A live benchmark designed for predicting unknown future. ([website](https://futurex-ai.github.io/))
- **SEAL-0**: A benchmark for evaluating LLMs on conflicting-evidence web questions. ([paper](https://arxiv.org/abs/2506.01062))
- **AIME2025**: American Invitational Mathematics Examination 2025. ([website](https://artificialanalysis.ai/evaluations/aime-2025))
- **DeepSearchQA**: Google's Deep Search Question Answering benchmark. ([paper](https://arxiv.org/abs/2505.20827))
</details>
## 📈 Performance on Benchmarks
### MiroThinker-1.7
> To prevent potential information leakage (e.g., retrieving benchmark answers from HuggingFace), we blocked access to certain websites during evaluation.
<div>
<img src="assets/17_table.png" width="100%" alt="MiroThinker" />
</div>
</details>
### MiroThinker-v1.5
<details>
<summary>📦 Click to expand MiroThinker-v1.5 details</summary>
> To prevent potential information leakage (e.g., searching benchmark answers from HuggingFace), access to HuggingFace has been explicitly disabled in these tools.
> We further perform canary string testing on the tool outputs of all trajectories and disregard any trajectory found to be contaminated, treating it as an incorrect answer.
<div>
<img src="https://huggingface.co/datasets/miromind-ai/MiroFlow-Benchmarks/resolve/main/assets/mirothinker_v1.5_performance.png" width="100%" alt="MiroThinker" />
</div>
</details>
### MiroThinker-v1.0
<details>
<summary>📦 Click to expand MiroThinker-v1.0 details</summary>
<div align="center">
<img src="https://github.com/user-attachments/assets/108a2105-4e1d-499e-a001-4713a03fd8ac" width="100%" alt="MiroThinker" />
</div>
</details>
### MiroThinker-v0.2
<details>
<summary>📦 Click to expand MiroThinker-v0.2 details</summary>
#### Comparison with SOTA Research Agents
<div align="center">
<img src="https://huggingface.co/datasets/miromind-ai/MiroFlow-Benchmarks/resolve/main/assets/MiroThinker_v0.2_Performance_2.png" width="90%" alt="MiroThinker" />
</div>
#### GAIA Benchmark
<div align="center">
<img src="https://huggingface.co/datasets/miromind-ai/MiroFlow-Benchmarks/resolve/main/assets/MiroThinker_v0.2_Performance_1.png" width="80%" alt="MiroThinker" />
</div>
</details>
### MiroThinker-v0.1
<details>
<summary>📦 Click to expand MiroThinker-v0.1 details</summary>
#### GAIA Benchmark
<div align="center">
| **Method** | Text-103<br>Best Pass@1 | Text-103<br>Pass@1 (Avg@8) | Val-165<br>Best Pass@1 | Val-165<br>Pass@1 (Avg@8) |
|------------------------------|:-----------------------:|:--------------------------:|:----------------------:|:-------------------------:|
| **🔹—— 7B/8B Agents ——** | | | | |
| Search-o1-7B | 17.5 | - | - | - |
| R1-Searcher-7B | 20.4 | - | - | - |
| WebDancer-7B | 31.0 | - | - | - |
| WebSailor-7B | 37.9 | - | - | - |
| CK-Pro-8B | 40.3 | - | 32.7 | - |
| **MiroThinker-8B-SFT-v0.1** | 44.7 | 40.1 | 34.6 | 31.8 |
| + Commercial Tools | 46.6 | 42.1 | 37.6 | 33.9 |
| **MiroThinker-8B-DPO-v0.1** | 46.6 | 44.8 | 37.0 | 35.4 |
| + Commercial Tools | **50.5** | **46.7** | **38.2** | **35.9** |
| **🔹—— 14B Agents ——** | | | | |
| **MiroThinker-14B-SFT-v0.1** | 47.6 | 44.4 | 37.0 | 34.4 |
| + Commercial Tools | 49.5 | 47.5 | 41.8 | 39.8 |
| **MiroThinker-14B-DPO-v0.1** | 48.5 | 46.6 | 42.4 | 39.2 |
| + Commercial Tools | **52.4** | **48.5** | **45.5** | **42.0** |
| **🔹—— 32B Agents ——** | | | | |
| Qwen3-32B | 31.1 | 26.7 | 29.7 | 26.4 |
| Search-o1-32B | 28.2 | - | - | - |
| WebThinker-32B-RL | 48.5 | - | - | - |
| WebDancer-QwQ-32B | 51.5 | - | - | - |
| WebSailor-32B | 53.2 | - | - | - |
| WebShaper-QwQ-32B | 53.3 | - | - | - |
| **MiroThinker-32B-SFT-v0.1** | 55.3 | 51.3 | 44.9 | 42.7 |
| + Commercial Tools | 58.3 | 54.2 | 48.5 | 45.8 |
| **MiroThinker-32B-DPO-v0.1** | 57.3 | 54.1 | 48.5 | 45.9 |
| + Commercial Tools | **60.2** | **57.9** | **50.9** | **48.9** |
</div>
1. Following the practices of WebThinker, WebAgents, and CognitiveKernel, we report the Best Pass@1, the highest score across three runs, which often reflects stronger performance, though it may exhibit some variability. To provide a more stable measure, we additionally report Pass@1 (Avg@8), which offers greater consistency at the cost of slightly lower scores.
1. For consistency with prior open-source works, we evaluate GAIA-Text-103 using the WebAgents LLM-as-a-Judge template, and report results on GAIA-Val-165 using the official GAIA scorer script.
1. By default, we use open-source tools wherever possible, except for the code tool [E2B](https://github.com/e2b-dev/E2B) and the Google search tool [Serper](https://serper.dev/). We use [Whisper](https://huggingface.co/openai/whisper-large-v3-turbo), [Qwen2.5-VL-72B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-72B-Instruct), and [Qwen3-235B-A22B-Thinking-2507](https://huggingface.co/Qwen/Qwen3-235B-A22B-Thinking-2507) in our implementation. The framework can be easily extended to other open-source tools of your choice.
1. Replacing these open-source tools with commercial alternatives can yield performance gains. Commercial tools were mainly used for multimodal capabilities and certain complex reasoning subtasks. The majority of tasks, including planning, browsing, refinement, navigation, and more, were handled by our agents.
#### More Benchmarks
<div align="center">
| Method | HLE<br>Pass@1 | Frames<br>Pass@1 | BrowseComp<br>Pass@1 | BrowseComp-ZH<br>Pass@1 | WebWalkerQA<br>Pass@1 |
|------------------------------|:-------------:|:----------------:|:--------------------:|:-----------------------:|:---------------------:|
| OpenAI Deep Research | 26.6 | - | 51.5 | 42.9 | - |
| Gemini Deep Research | 26.9 | - | - | - | - |
| Kimi-Researcher | 26.9 | 78.8 | - | - | - |
| | | | | | |
| WebDancer-7B | - | - | - | - | 36.0 |
| WebSailor-7B | - | - | 6.7 | 14.2 | - |
| **MiroThinker-8B-SFT-v0.1** | - | 58.0 | 5.5 | 9.3 | 41.3 |
| **MiroThinker-8B-DPO-v0.1** | - | 64.4 | 8.7 | 13.6 | 45.7 |
| | | | | | |
| WebThinker-32B-RL | - | - | - | - | 46.5 |
| WebDancer-QwQ-32B | - | - | 3.8 | 18.0 | 47.9 |
| WebSailor-32B | - | - | 10.5 | 25.5 | - |
| WebShaper-32B | - | - | - | - | 51.4 |
| **MiroThinker-32B-SFT-v0.1** | 10.2 | 70.4 | 10.6 | 13.8 | 45.7 |
| **MiroThinker-32B-DPO-v0.1** | 11.8 | 71.7 | 13.0 | 17.0 | 49.3 |
</div>
1. MiroThinker’s performance was tested with this repository and open-source tools; other agents’ results are from their papers and official sites.
1. As [MiroVerse-v0.1](https://huggingface.co/datasets/miromind-ai/MiroVerse-v0.1) mainly contains English data, the agent’s Chinese capability is limited. We plan to add more Chinese data to improve performance in the next version.
</details>
## 🚀 Quick Start
For optimal usage, we recommend using MiroThinker with this tool-enabled agent framework and thinking mode enabled.
### Prerequisites
- 🐍 **Python 3.10+**
- 📦 **uv package manager** ([Installation guide](https://github.com/astral-sh/uv))
- 🔑 **Required API keys** (see configuration section below)
### Installation
```bash
# Clone the repository
git clone https://github.com/MiroMindAI/MiroThinker
cd MiroThinker
# Setup environment
cd apps/miroflow-agent
uv sync
# Configure API keys
cp .env.example .env
# Edit .env with your API keys (SERPER_API_KEY, JINA_API_KEY, E2B_API_KEY, etc.)
```
> **📝 Environment Variables**: See [Tool Configuration](#tool-configuration) section for required API keys.
### Tool Configuration
#### Minimal Configuration for MiroThinker-1.7.
| Server | Description | Tools Provided | Required Environment Variables |
|:-------|:------------|:---------------|:-------------------------------|
| **`tool-python`** | Execution environment and file management (E2B sandbox) | `create_sandbox`, `run_command`, `run_python_code`, `upload_file_from_local_to_sandbox`, `download_file_from_sandbox_to_local`, `download_file_from_internet_to_sandbox` | `E2B_API_KEY` |
| **`search_and_scrape_webpage`** | Google search via Serper API | `google_search` | `SERPER_API_KEY`, `SERPER_BASE_URL` |
| **`jina_scrape_llm_summary`** | Web scraping with LLM-based information extraction | `scrape_and_extract_info` | `JINA_API_KEY`, `JINA_BASE_URL`, `SUMMARY_LLM_BASE_URL`, `SUMMARY_LLM_MODEL_NAME`, `SUMMARY_LLM_API_KEY` |
**Minimal `.env` configuration example:**
```bash
# Required for MiroThinker v1.5 and v1.0 (minimal setup)
SERPER_API_KEY=your_serper_key
SERPER_BASE_URL="https://google.serper.dev"
JINA_API_KEY=your_jina_key
JINA_BASE_URL="https://r.jina.ai"
E2B_API_KEY=your_e2b_key
# Required for jina_scrape_llm_summary
# Note: Summary LLM can be a small model (e.g., Qwen3-14B or GPT-5-Nano)
# The choice has minimal impact on performance, use what's most convenient
SUMMARY_LLM_BASE_URL="https://your_summary_llm_base_url/v1/chat/completions"
SUMMARY_LLM_MODEL_NAME=your_llm_model_name # e.g., "Qwen/Qwen3-14B" or "gpt-5-nano"
SUMMARY_LLM_API_KEY=your_llm_api_key # Optional, depends on LLM provider
# Required for benchmark evaluation (LLM-as-a-Judge)
OPENAI_API_KEY=your_openai_key # Required for running benchmark evaluations
OPENAI_BASE_URL="https://api.openai.com/v1" # Optional, defaults to OpenAI's API
```
> **💡 Why this is minimal**: These 3 MCP servers cover the core capabilities needed for research tasks: web search, content extraction, and code execution. All other servers are optional enhancements.
>
> **🤖 Summary LLM**: The `SUMMARY_LLM` can be a small model like Qwen3-14B or GPT-5-Nano. The choice has minimal impact on overall performance, use whichever is most convenient for your setup.
>
> **📊 For Benchmark Evaluation**: If you plan to run benchmark evaluations, you also need `OPENAI_API_KEY` (and optionally `OPENAI_BASE_URL`) for LLM-as-a-Judge functionality used in evaluation scripts.
>
> **🖼️ For GAIA Multimodal Tasks**: GAIA-Val-165 includes tasks with image/audio/video files. Since MiroThinker is a text-only LLM, GPT-4o is used to pre-process these files into text descriptions. The same `OPENAI_API_KEY` is used for both this preprocessing and LLM-as-a-Judge.
>
> **📖 For more details**: See [MiroFlow Tools README](libs/miroflow-tools/README.md) for complete documentation of all available tools.
<details>
<summary>🔧 Click to expand additional available tools</summary>
The following optional tools are available but were not used in MiroThinker v1.0-1.7 evaluation:
| Server Name | Type | Description |
|:---------------------|:-------------|:--------------------------------------------|
| `tool-vqa` | Commercial | Vision processing using Claude |
| `tool-vqa-os` | Open-Source | Vision processing (open-source alternative) |
| `tool-transcribe` | Commercial | Audio transcription using OpenAI |
| `tool-transcribe-os` | Open-Source | Audio transcription using Whisper |
| `tool-reasoning` | Commercial | Reasoning engine using Claude |
| `tool-reasoning-os` | Open-Source | Reasoning engine (open-source alternative) |
| `tool-reading` | Open-Source | Document reading using MarkItDown |
| `tool-google-search` | Commercial | Web search using Google + scraping |
| `tool-sogou-search` | Commercial | Web search using Sogou (Chinese) |
> **📖 Local Deployment**: For instructions on deploying open-source tools (`tool-vqa-os`, `tool-transcribe-os`, `tool-reasoning-os`) locally, see [Local Tool Deployment Guide](assets/LOCAL-TOOL-DEPLOYMENT.md).
See the [MiroFlow Tools README](libs/miroflow-tools/README.md) for complete documentation of all available tools.
</details>
#### Pre-configured Agent Settings
The `apps/miroflow-agent/conf/agent/` directory contains several pre-configured agent settings. Each configuration uses different tools and requires corresponding environment variables in your `.env` file.
> **💡 Recommended**: For MiroThinker-1.7, use `mirothinker_1.7_keep5_max200` (with context management, recommended for most tasks) or `mirothinker_v1.7_keep5_max300` (only used for BrowseComp and BrowseComp-ZH).
| Configuration | Description | Max Turns | Context Retention | Required Environment Variables | Recommended For |
|:---------------------------------------|:------------|:----------|:------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------|
| **`mirothinker_1.7_keep5_max200`** ⭐ | Single-agent with context management | 200 | Keep 5 most recent | `SERPER_API_KEY`, `SERPER_BASE_URL`, `JINA_API_KEY`, `JINA_BASE_URL`, `E2B_API_KEY`, `SUMMARY_LLM_BASE_URL`, `SUMMARY_LLM_MODEL_NAME`, `SUMMARY_LLM_API_KEY` | **1.7 (recommended for most tasks)** |
| **`mirothinker_1.7_keep5_max300`** ⭐ | Single-agent with context management | 300 | Keep 5 most recent | Same as above | **1.7 (for BrowseComp & BrowseComp-ZH)** |
<details>
<summary>📦 Click to expand legacy configurations (v0.1/v0.2)</summary>
| Configuration | Description | Max Turns | Context Retention | Required Environment Variables | Recommended For |
|:-------------------------|:------------|:----------|:------------------|:-------------------------------|:----------------|
| **`mirothinker_v1.5_keep5_max200`** | Single-agent with context management | 200 | Keep 5 most recent | `SERPER_API_KEY`, `SERPER_BASE_URL`, `JINA_API_KEY`, `JINA_BASE_URL`, `E2B_API_KEY`, `SUMMARY_LLM_BASE_URL`, `SUMMARY_LLM_MODEL_NAME`, `SUMMARY_LLM_API_KEY` | **v1.5 (recommended for most tasks)** |
| **`mirothinker_v1.5_keep5_max400`** | Single-agent with context management | 400 | Keep 5 most recent | Same as above | **v1.5 (for BrowseComp & BrowseComp-ZH)** |
| **`mirothinker_v1.5`** | Single-agent for MiroThinker v1.5 | 600 | Keep all results | Same as above | **v1.5** |
| **`mirothinker_v1.0_keep5`** | Single-agent with context management | 600 | Keep 5 most recent | Same as above | **v1.0** |
| **`mirothinker_v1.0`** | Single-agent for MiroThinker v1.0 | 600 | Keep all results | Same as above | **v1.0** |
| **`multi_agent`** | Multi-agent with commercial tools (v0.1/v0.2) | 50 | Keep all results | `E2B_API_KEY`, `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `SERPER_API_KEY`, `SERPER_BASE_URL`, `JINA_API_KEY`, `JINA_BASE_URL` | v0.1/v0.2 |
| **`multi_agent_os`** | Multi-agent with open-source tools (v0.1/v0.2) | 50 | Keep all results | `E2B_API_KEY`, `VISION_API_KEY`, `VISION_BASE_URL`, `VISION_MODEL_NAME`, `WHISPER_API_KEY`, `WHISPER_BASE_URL`, `WHISPER_MODEL_NAME`, `REASONING_API_KEY`, `REASONING_BASE_URL`, `REASONING_MODEL_NAME`, `SERPER_API_KEY`, `SERPER_BASE_URL`, `JINA_API_KEY`, `JINA_BASE_URL` | v0.1/v0.2 |
</details>
> **💡 Note**: All environment variables are listed in `apps/miroflow-agent/.env.example`. Copy it to `.env` and fill in the values for the tools you plan to use.
#### Creating Custom Tool Configurations
<details>
<summary>🔧 Click to expand custom tool configuration guide</summary>
You can create your own YAML configuration file to freely combine MCP servers. Here's how:
1. **Create a new YAML file** in `apps/miroflow-agent/conf/agent/`:
```yaml
# conf/agent/my_custom_config.yaml
defaults:
- default
- _self_
main_agent:
tools:
- tool-python # Execution environment
- search_and_scrape_webpage # Google search
- jina_scrape_llm_summary # Web scraping with LLM
- tool-vqa # Vision processing (optional)
- tool-transcribe # Audio processing (optional)
- tool-reasoning # Reasoning engine (optional)
- tool-reading # Document reading (optional)
max_turns: 300 # Maximum number of turns
sub_agents:
agent-browsing: # Optional sub-agent
tools:
- tool-google-search
- tool-vqa
- tool-reading
- tool-python
max_turns: 50
keep_tool_result: -1 # Context retention budget: -1 keeps all tool results, or specify K to keep only the K most recent tool responses
```
> **💡 Context Retention Strategy**: The `keep_tool_result` parameter implements a **recency-based context retention** strategy. In the standard ReAct paradigm, all tool outputs are retained in the message history, which can lead to inefficient context utilization. Empirically, we observe that the agent's subsequent actions depend primarily on recent observations rather than distant ones. This strategy retains only the most recent K tool responses (where K is the `keep_tool_result` value) while preserving the complete sequence of thoughts and actions.
>
> **Benefits:**
>
> - ✅ Preserves the reasoning and action trace
> - ✅ Focuses the agent's attention on the most contextually relevant observations
> - ✅ Frees additional context space for extended reasoning and deeper tool-use trajectories
> - ✅ Does not lead to performance degradation while allowing more context space for interactive scaling
>
> **Usage:** Set `keep_tool_result: -1` to keep all tool results, or specify a positive integer K (e.g., `keep_tool_result: 5`) to keep only the K most recent tool responses.
2. **Use your custom configuration** when running evaluations:
```bash
cd apps/miroflow-agent
uv run main.py llm=qwen-3 agent=my_custom_config llm.base_url=https://your_base_url/v1
```
3. **Configure environment variables** in `.env` based on the tools you use.
All available environment variables are listed in `apps/miroflow-agent/.env.example`. Copy it to `.env` and configure the variables according to your chosen configuration:
```bash
cd apps/miroflow-agent
cp .env.example .env
# Edit .env with your actual API keys
```
**For MiroThinker v1.5** (`mirothinker_v1.5_keep5_max200.yaml`, `mirothinker_v1.5_keep5_max400.yaml`, or `mirothinker_v1.5.yaml`) and **v1.0** (`mirothinker_v1.0_keep5.yaml` or `mirothinker_v1.0.yaml`), see the [Minimal Configuration](#minimal-configuration-for-mirothinker-v15-and-v10) section above for the complete configuration example.
**For other configurations**, refer to the [Pre-configured Agent Settings](#pre-configured-agent-settings) table above to see which environment variables are required.
</details>
<details>
<summary>🔑 Click to expand optional API keys</summary>
```bash
# API for LLM-as-a-Judge (for benchmark testing, required for benchmark evaluation)
OPENAI_API_KEY=your_openai_key
OPENAI_BASE_URL="https://api.openai.com/v1" # Optional, defaults to OpenAI's API
# API for Open-Source Audio Transcription Tool (for benchmark testing, optional)
WHISPER_MODEL_NAME="openai/whisper-large-v3-turbo"
WHISPER_API_KEY=your_whisper_key
WHISPER_BASE_URL="https://your_whisper_base_url/v1"
# API for Open-Source VQA Tool (for benchmark testing, optional)
VISION_MODEL_NAME="Qwen/Qwen2.5-VL-72B-Instruct"
VISION_API_KEY=your_vision_key
VISION_BASE_URL="https://your_vision_base_url/v1/chat/completions"
# API for Open-Source Reasoning Tool (for benchmark testing, optional)
REASONING_MODEL_NAME="Qwen/Qwen3-235B-A22B-Thinking-2507"
REASONING_API_KEY=your_reasoning_key
REASONING_BASE_URL="https://your_reasoning_base_url/v1/chat/completions"
# API for Claude Sonnet 3.7 as Commercial Tools (optional)
ANTHROPIC_API_KEY=your_anthropic_key
# API for Sogou Search (optional)
TENCENTCLOUD_SECRET_ID=your_tencent_cloud_secret_id
TENCENTCLOUD_SECRET_KEY=your_tencent_cloud_secret_key
# API for Summary LLM (can use small models like Qwen3-14B or GPT-5-Nano)
SUMMARY_LLM_BASE_URL="https://your_summary_llm_base_url/v1/chat/completions"
SUMMARY_LLM_MODEL_NAME=your_summary_llm_model_name # e.g., "Qwen/Qwen3-14B" or "gpt-5-nano"
SUMMARY_LLM_API_KEY=your_summary_llm_api_key
```
</details>
### Serve the MiroThinker Agent
#### Option 1 (Recommended): Serve with SGLang or vLLM
Use SGLang to serve MiroThinker models at port 61002:
```bash
NUM_GPUS=4
PORT=61002
# Downloading agent from HF
AGENT_PATH=miromind-ai/MiroThinker-1.7-mini
python3 -m sglang.launch_server \
--model-path $AGENT_PATH \
--tp $NUM_GPUS \
--dp 1 \
--host 0.0.0.0 \
--port $PORT \
--trust-remote-code
```
> **📍 Server URL**: This will start a server at `http://0.0.0.0:$PORT`. Use this as your server base URL (e.g., `http://0.0.0.0:61002/v1`).
#### Option 2: Quantized Light-Weight Options
We also provide comprehensive guidance for serving MiroThinker agents using CPU-optimized and GPU-accelerated quantization techniques, along with detailed analysis and guidelines for deployment with llama.cpp, Ollama, SGLang, and other inference frameworks.
> **📖 Complete Guide**: See [Deployment Documentation](apps/gradio-demo/) for detailed deployment instructions.
### Run Your First Task
After setting up the environment and starting your server, run `main.py` to test with a default question: *"What is the title of today's arxiv paper in computer science?"*
```bash
cd apps/miroflow-agent
# Using MiroThinker agents (requires your own server)
uv run python main.py llm=qwen-3 agent=mirothinker_1.7_keep5_max200 llm.base_url=http://localhost:61002/v1
# Or using Claude (requires ANTHROPIC_API_KEY in .env)
uv run python main.py llm=claude-3-7 agent=single_agent_keep5
# Or using GPT-5 (requires OPENAI_API_KEY in .env)
uv run python main.py llm=gpt-5 agent=single_agent_keep5
```
**To customize your question**, edit `main.py` line 32:
```python
task_description = "Your custom question here"
```
The agent will search the web, execute code if needed, and provide an answer with sources.
> **📖 More details**: See [apps/miroflow-agent/README.md](apps/miroflow-agent/README.md) for available configurations and troubleshooting.
## 📊 Benchmark Evaluation
> For researchers who want to reproduce our benchmark results or evaluate on standard benchmarks.
### Download Benchmark Data
```bash
cd MiroThinker # Back to project root
wget https://huggingface.co/datasets/miromind-ai/MiroFlow-Benchmarks/resolve/main/data_20251115_password_protected.zip
unzip data_20251115_password_protected.zip
# Password: pf4*
rm data_20251115_password_protected.zip
```
### Run Benchmark Evaluation
> **Note:** For MiroThinker-1.7, use `mirothinker_1.7_keep5_max200` (with context management), `mirothinker_1.7_keep5_max300` (with context management).
**Available Parameters:**
You can customize the evaluation by setting the following environment variables before running the script:
| Parameter | Default | Description |
|:----------|:--------|:------------|
| `LLM_MODEL` | `"MiroThinker-Agents"` | Agent name identifier |
| `BASE_URL` | `"https://your-api.com/v1"` | Base URL of your server |
| `NUM_RUNS` | Varies by benchmark | Number of evaluation runs (3 for most benchmarks, 8 for GAIA/XBench/FutureX/SEAL-0, 32 for AIME2025) |
| `LLM_PROVIDER` | `"qwen"` | LLM provider (e.g., `qwen`, `openai`, `anthropic`) |
| `AGENT_SET` | `"mirothinker_1.7_keep5_max200"` | Agent configuration (e.g., `mirothinker_1.7_keep5_max200`, `mirothinker_1.7_keep5_max300`.) |
| `MAX_CONTEXT_LENGTH` | `262144` | Maximum context length (256K) |
| `MAX_CONCURRENT` | `10` | Maximum concurrent tasks |
| `PASS_AT_K` | `1` | Pass@K evaluation metric |
| `TEMPERATURE` | `1.0` | Sampling temperature |
| `API_KEY` | `"xxx"` | API key for the server |
**Example Usage:**
```bash
# Navigate to the miroflow-agent directory first
cd apps/miroflow-agent
# Basic usage with v1.5 (recommended)
NUM_RUNS=8 LLM_MODEL="MiroThinker-1.7-mini" BASE_URL="https://your-api.com/v1" bash scripts/run_evaluate_multiple_runs_gaia-validation-text-103.sh
# Or with v1.0
# NUM_RUNS=8 LLM_MODEL="MiroThinker-v1.0-30B" BASE_URL="https://your-api.com/v1" bash scripts/run_evaluate_multiple_runs_gaia-validation-text-103.sh
# Customize number of runs and agent configuration (v1.5 with context management)
LLM_MODEL="MiroThinker-1.7-mini" \
BASE_URL="https://your-api.com/v1" \
NUM_RUNS=8 \
AGENT_SET="mirothinker_1.7_keep5_max200" \
bash scripts/run_evaluate_multiple_runs_gaia-validation-text-103.sh
```
<details open>
<summary>📋 Click to expand all benchmark commands</summary>
> **⚠️ Important for MiroThinker-1.7**: To reproduce our reported results, you must set the correct `AGENT_SET`:
>
> - **BrowseComp & BrowseComp-ZH**: Use `AGENT_SET="mirothinker_1.7_keep5_max300"`
> - **All other benchmarks**: Use `AGENT_SET="mirothinker_1.7_keep5_max200"`
```bash
# Navigate to the miroflow-agent directory first
cd apps/miroflow-agent
# HLE
NUM_RUNS=3 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_hle.sh
# HLE-Text-2158
NUM_RUNS=3 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_hle-text-2158.sh
# HLE-Text-500
NUM_RUNS=3 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_hle-text-500.sh
# GAIA-Text-103
NUM_RUNS=8 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_gaia-validation-text-103.sh
# GAIA-Validation (GAIA-Val-165)
NUM_RUNS=8 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_gaia-validation.sh
# BrowseComp-EN (⚠️ use max300)
NUM_RUNS=3 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max300" bash scripts/run_evaluate_multiple_runs_browsecomp.sh
# BrowseComp-ZH (⚠️ use max300)
NUM_RUNS=3 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max300" bash scripts/run_evaluate_multiple_runs_browsecomp_zh.sh
# WebWalkerQA
NUM_RUNS=3 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_webwalkerqa.sh
# XBench-DeepSearch
NUM_RUNS=8 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_xbench_deepsearch.sh
# FRAMES
NUM_RUNS=3 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_frames.sh
# SEAL-0
NUM_RUNS=8 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_seal-0.sh
# FutureX
NUM_RUNS=8 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_futurex.sh
# AIME2025
NUM_RUNS=32 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_aime2025.sh
# DeepSearchQA
NUM_RUNS=3 LLM_MODEL="xxx" BASE_URL="xxx" AGENT_SET="mirothinker_1.7_keep5_max200" bash scripts/run_evaluate_multiple_runs_deepsearchqa.sh
```
</details>
#### 3. **Monitor evaluation progress**
<details>
<summary>📊 Click to expand progress monitoring commands</summary>
```bash
# Navigate to the miroflow-agent directory first
cd apps/miroflow-agent
# For HLE
python benchmarks/check_progress/check_progress_hle.py /path/to/evaluation/logs
# For HLE-Text-2158
python benchmarks/check_progress/check_progress_hle-text-2158.py /path/to/evaluation/logs
# For HLE-Text-500
python benchmarks/check_progress/check_progress_hle-text-500.py /path/to/evaluation/logs
# For BrowseComp-EN
python benchmarks/check_progress/check_progress_browsecomp.py /path/to/evaluation/logs
# For BrowseComp-ZH
python benchmarks/check_progress/check_progress_browsecomp_zh.py /path/to/evaluation/logs
# For GAIA-Validation
python benchmarks/check_progress/check_progress_gaia-validation.py /path/to/evaluation/logs
# For GAIA-Text-103
python benchmarks/check_progress/check_progress_gaia-validation-text-103.py /path/to/evaluation/logs
# For WebWalkerQA
python benchmarks/check_progress/check_progress_webwalkerqa.py /path/to/evaluation/logs
# For Frames
python benchmarks/check_progress/check_progress_frames.py /path/to/evaluation/logs
# For XBench-DeepSearch
python benchmarks/check_progress/check_progress_xbench_deepsearch.py /path/to/evaluation/logs
# For SEAL-0
python benchmarks/check_progress/check_progress_seal-0.py /path/to/evaluation/logs
# For AIME2025
python benchmarks/check_progress/check_progress_aime2025.py /path/to/evaluation/logs
# For DeepSearchQA
python benchmarks/check_progress/check_progress_deepsearchqa.py /path/to/evaluation/logs
```
</details>
## 🔬 Trace Collection
<details>
<summary>📋 Click to expand trace collection commands</summary>
```bash
cd apps/collect-trace
# Collect Traces for SFT
bash scripts/collect_trace_claude37.sh
bash scripts/collect_trace_gpt5.sh
# Collect Traces for DPO
bash scripts/collect_trace_qwen3.sh
```
</details>
## ❓ FAQ & Troubleshooting
### Common Issues
<details>
<summary>🔧 Click to expand troubleshooting guide</summary>
#### **Q: Which version should I use?**
**A:** We recommend **MiroThinker-1.7** ⭐ with the minimal configuration:
- **v1.7** ⭐: Latest version with 256K context, world-leading performance. Use config (with context management):
- `mirothinker_1.7_keep5_max200` (up to 200 turns, recommended for most tasks)
- `mirothinker_1.7_keep5_max300` (up to 300 turns, only used for BrowseComp and BrowseComp-ZH)
#### **Q: How do I get API keys?**
**A:** You need these keys for minimal setup:
- **SERPER_API_KEY**: Get from [Serper.dev](https://serper.dev/) (Google search API)
- **JINA_API_KEY**: Get from [Jina.ai](https://jina.ai/) (Web scraping)
- **E2B_API_KEY**: Get from [E2B.dev](https://e2b.dev/) (Code execution sandbox)
- **SUMMARY_LLM_API_KEY**: Your LLM API credentials (for content summarization). Can be a small model like Qwen3-14B or GPT-5-Nano—the choice has minimal impact on performance.
- **OPENAI_API_KEY**: Get from [OpenAI](https://platform.openai.com/) (Required for benchmark evaluation, used for LLM-as-a-Judge)
- **OPENAI_BASE_URL**: Optional, defaults to `https://api.openai.com/v1`. Can be changed to use OpenAI-compatible APIs.
#### **Q: Agent server connection errors**
**A:** Common issues:
- **Check base URL format**: Should end with `/v1` (e.g., `https://your-api.com/v1`)
- **Verify API key**: Ensure `API_KEY` is set correctly in environment or script
- **Check server status**: Make sure your server is running and accessible
- **Network issues**: Verify firewall/network settings allow connections
#### **Q: Evaluation script fails to run**
**A:** Troubleshooting steps:
1. **Check working directory**: Make sure you're in `apps/miroflow-agent` directory
1. **Verify environment**: Run `uv sync` to ensure dependencies are installed
1. **Check .env file**: Ensure all required environment variables are set
1. **Review logs**: Check `logs/` directory for detailed error messages
1. **Verify data path**: Ensure benchmark data is downloaded and in correct location
#### **Q: Out of memory errors**
**A:** Solutions:
- **Reduce context length**: Set `MAX_CONTEXT_LENGTH` to a smaller value (e.g., 131072 for 128K)
- **Use context management with fewer turns**:
- For v1.5: Use `mirothinker_1.7_keep5_max200` or `mirothinker_1.7_keep5_max300` (with context management)
- **Reduce concurrent tasks**: Set `MAX_CONCURRENT` to a smaller number (e.g., 5)
- **Use smaller agents**:
- For v1.5: Try 30B instead of 235B
- For v1.0: Try 8B or 30B instead of 72B
#### **Q: Tool execution errors**
**A:** Common fixes:
- **E2B errors**: Verify `E2B_API_KEY` is valid and account has credits
- **Serper errors**: Check `SERPER_API_KEY` and rate limits
- **Jina errors**: Verify `JINA_API_KEY` and `JINA_BASE_URL` are correct
- **LLM summarization errors**: Check `SUMMARY_LLM_*` variables and agent availability
#### **Q: How to monitor long-running evaluations?**
**A:** Use the progress monitoring scripts:
```bash
cd apps/miroflow-agent
python benchmarks/check_progress/check_progress_<benchmark_name>.py /path/to/logs
```
The scripts show completion status, elapsed time, and estimated remaining time.
</details>
### Getting Help
- 📖 **Documentation**: Check [MiroFlow Tools README](libs/miroflow-tools/README.md) for tool details
- 💬 **Discord**: Join our [Discord community](https://discord.com/invite/GPqEnkzQZd)
- 🐛 **Issues**: Report bugs on [GitHub Issues](https://github.com/MiroMindAI/MiroThinker/issues)
- 📧 **Contact**: Visit [our website](https://miromind.ai/) for more information
## 📄 License
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
We extend our sincere gratitude to:
- 🏆 **Benchmark Contributors** for the comprehensive evaluation datasets
- 🌍 **Open Source Community** for the tools and libraries that make this possible
- 👥 **All Contributors** who have helped make MiroThinker better
<div align="center">
<a href="https://github.com/MiroMindAI/MiroThinker/graphs/contributors">
<img src="https://contrib.rocks/image?repo=MiroMindAI/MiroThinker" />
</a>
</div>
Join our community and help us build the future of AI agents!
### References
If you find this project useful in your research, please consider citing:
**MiroThinker** (Model & Method)
```
@article{miromind2026mirothinker,
title={MiroThinker-1.7 & H1: Towards Heavy-Duty Research Agents via Verification},
author={MiroMind Team and Bai, S. and Bing, L. and Lei, L. and Li, R. and Li, X. and Lin, X. and Min, E. and Su, L. and Wang, B. and Wang, L. and Wang, L. and Wang, S. and Wang, X. and Zhang, Y. and Zhang, Z. and others},
journal={arXiv preprint arXiv:2603.15726},
year={2026}
}
```
**MiroFlow** (Framework)
```bibtex
@article{miromind2026miroflow,
title={MiroFlow: Towards High-Performance and Robust Open-Source Agent Framework for General Deep Research Tasks},
author={Su, Shiqian and Xing, Sen and Dong, Xuan and Zhong, Muyan and Wang, Bin and Zhu, Xizhou and Chen, Yuntao and Wang, Wenhai and Deng, Yue and Zhu, Pengxiang and others},
journal={arXiv preprint arXiv:2602.22808},
year={2026}
}
```
[](https://star-history.com/#MiroMindAI/MiroThinker&Date)
================================================
FILE: apps/collect-trace/README.md
================================================
# Collect Trace
> TL;DR: Treat an RLVR-format dataset (Question + verifiable answer) as a benchmark. Run the evaluation pipeline; use LLM-as-a-Judge to verify correctness; then harvest the correct interaction traces as training data (for SFT / DPO).
## 📝 Overview
Collect Trace is a key component in the MiroThinker training pipeline. Instead of hand-curating training samples, it reuses RLVR datasets as test sets, and collects multi-turn interaction traces only from items judged correct.
Workflow:
1. Load each RLVR item’s question and verifiable answer.
1. Run the agent in the evaluation pipeline (with tool use / browsing as needed).
1. Verify the model's answer with an LLM-as-a-Judge against the RLVR reference answer.
1. Only for items judged correct, collect the full multi-turn trace and convert it into SFT / DPO-ready samples.
## 🚀 Quick Start
### Prerequisites
- Python 3.10+
- [uv](https://github.com/astral-sh/uv) package manager
- OpenAI API key (for LLM-based validation)
- RLVR dataset (JSONL; contains question and a verifiable answer)
### Installation
1. **Navigate to the collect-trace directory**:
```bash
cd apps/collect-trace
```
1. **Install dependencies**:
```bash
uv sync
```
1. **Set up environment variables**:
```bash
# Create .env if missing (safe; won't overwrite existing file)
[ -f ../miroflow-agent/.env ] || cp ../miroflow-agent/.env.example ../miroflow-agent/.env
# (Alternative on macOS/Linux) cp -n ../miroflow-agent/.env.example ../miroflow-agent/.env || true
# Edit .env and fill in your keys
# Required: OPENAI_API_KEY (for LLM-as-a-Judge)
# Optional: other keys for specific tools
```
### Basic Usage
Run a benchmark evaluation to collect traces:
```bash
# Using Claude-3.7 for trace collection
bash scripts/collect_trace_claude37.sh
# Using GPT-5 for trace collection
bash scripts/collect_trace_gpt5.sh
# Using Qwen-3 for trace collection
bash scripts/collect_trace_qwen3.sh
```
================================================
FILE: apps/collect-trace/pyproject.toml
================================================
[project]
name = "collect-trace"
version = "0.1.0"
description = "Executes a user-defined agent loop for capturing multi-turn interaction traces"
readme = "README.md"
requires-python = ">=3.12"
authors = [{ name = "MiroMind Team", email = "service@miromind.ai" }]
dependencies = [
"miroflow-tools>=0.1.0",
"dotenv>=0.9.9",
"openai>=1.90.0",
]
[tool.uv.sources]
miroflow-tools = { path = "../../libs/miroflow-tools", editable = true }
================================================
FILE: apps/collect-trace/scripts/collect_trace_claude37.sh
================================================
# Check if ANTHROPIC_API_KEY is set
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "Error: ANTHROPIC_API_KEY is not set."
exit 1
else
echo "ANTHROPIC_API_KEY detected."
fi
# Get the directory where the current script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "Current script directory: $SCRIPT_DIR"
# Enter the apps/miroflow-agent directory
TARGET_DIR="$SCRIPT_DIR/../../miroflow-agent"
echo "Target directory: $TARGET_DIR"
cd $TARGET_DIR
mkdir -p ../../logs
LOG_DIR="../../logs/collect_trace_claude37"
echo "Log directory: $LOG_DIR"
mkdir -p $LOG_DIR
# Collect traces
uv run python benchmarks/common_benchmark.py \
benchmark=collect_trace \
benchmark.data.data_dir="../../data/debug" \
benchmark.data.metadata_file="standardized_data.jsonl" \
llm=claude-3-7 \
llm.provider=anthropic \
llm.model_name=claude-3-7-sonnet-20250219 \
llm.api_key="$ANTHROPIC_API_KEY" \
llm.base_url=https://api.anthropic.com \
llm.async_client=true \
benchmark.execution.max_tasks=null \
benchmark.execution.max_concurrent=10 \
benchmark.execution.pass_at_k=1 \
agent=single_agent \
hydra.run.dir=$LOG_DIR \
2>&1 | tee "$LOG_DIR/output.log"
# Enter the apps/collect-trace directory
TARGET_DIR="$SCRIPT_DIR/../"
echo "Target directory: $TARGET_DIR"
cd $TARGET_DIR
# Process traces
uv run python $TARGET_DIR/utils/process_logs.py $LOG_DIR/benchmark_results.jsonl
================================================
FILE: apps/collect-trace/scripts/collect_trace_gpt41.sh
================================================
# Check if OPENAI_API_KEY is set
if [ -z "$OPENAI_API_KEY" ]; then
echo "Error: OPENAI_API_KEY is not set."
exit 1
else
echo "OPENAI_API_KEY detected."
fi
# Get the directory where the current script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "Current script directory: $SCRIPT_DIR"
# Enter the apps/miroflow-agent directory
TARGET_DIR="$SCRIPT_DIR/../../miroflow-agent"
echo "Target directory: $TARGET_DIR"
cd $TARGET_DIR
mkdir -p ../../logs
LOG_DIR="../../logs/collect_trace_gpt41"
echo "Log directory: $LOG_DIR"
mkdir -p $LOG_DIR
# Collect traces
uv run python benchmarks/common_benchmark.py \
benchmark=collect_trace \
benchmark.data.data_dir="../../data/debug" \
benchmark.data.metadata_file="standardized_data.jsonl" \
llm=gpt-5 \
llm.provider=openai \
llm.model_name=gpt-4.1-mini \
llm.api_key="$OPENAI_API_KEY" \
llm.base_url=https://api.openai.com/v1 \
llm.async_client=true \
benchmark.execution.max_tasks=null \
benchmark.execution.max_concurrent=10 \
benchmark.execution.pass_at_k=1 \
agent=single_agent \
hydra.run.dir=$LOG_DIR \
2>&1 | tee "$LOG_DIR/output.log"
# Enter the apps/collect-trace directory
TARGET_DIR="$SCRIPT_DIR/../"
echo "Target directory: $TARGET_DIR"
cd $TARGET_DIR
# Process traces
uv run python $TARGET_DIR/utils/process_logs.py $LOG_DIR/benchmark_results.jsonl
================================================
FILE: apps/collect-trace/scripts/collect_trace_gpt5.sh
================================================
# Check if OPENAI_API_KEY is set
if [ -z "$OPENAI_API_KEY" ]; then
echo "Error: OPENAI_API_KEY is not set."
exit 1
else
echo "OPENAI_API_KEY detected."
fi
# Get the directory where the current script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "Current script directory: $SCRIPT_DIR"
# Enter the apps/miroflow-agent directory
TARGET_DIR="$SCRIPT_DIR/../../miroflow-agent"
echo "Target directory: $TARGET_DIR"
cd $TARGET_DIR
mkdir -p ../../logs
LOG_DIR="../../logs/collect_trace_gpt5"
echo "Log directory: $LOG_DIR"
mkdir -p $LOG_DIR
# Collect traces
uv run python benchmarks/common_benchmark.py \
benchmark=collect_trace \
benchmark.data.data_dir="../../data/debug" \
benchmark.data.metadata_file="standardized_data.jsonl" \
llm=gpt-5 \
llm.provider=openai \
llm.model_name=gpt-5-2025-08-07 \
llm.api_key="$OPENAI_API_KEY" \
llm.base_url=https://api.openai.com/v1 \
llm.async_client=true \
benchmark.execution.max_tasks=null \
benchmark.execution.max_concurrent=10 \
benchmark.execution.pass_at_k=1 \
agent=single_agent \
hydra.run.dir=$LOG_DIR \
2>&1 | tee "$LOG_DIR/output.log"
# Enter the apps/collect-trace directory
TARGET_DIR="$SCRIPT_DIR/../"
echo "Target directory: $TARGET_DIR"
cd $TARGET_DIR
# Process traces
uv run python $TARGET_DIR/utils/process_logs.py $LOG_DIR/benchmark_results.jsonl
================================================
FILE: apps/collect-trace/scripts/collect_trace_qwen3.sh
================================================
# Get the directory where the current script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "Current script directory: $SCRIPT_DIR"
# Enter the apps/miroflow-agent directory
TARGET_DIR="$SCRIPT_DIR/../../miroflow-agent"
echo "Target directory: $TARGET_DIR"
cd $TARGET_DIR
mkdir -p ../../logs
LOG_DIR="../../logs/collect_trace_qwen3"
echo "Log directory: $LOG_DIR"
mkdir -p $LOG_DIR
# Collect traces
uv run python benchmarks/common_benchmark.py \
benchmark=collect_trace \
benchmark.data.data_dir="../../data/debug" \
benchmark.data.metadata_file="standardized_data.jsonl" \
llm=qwen-3 \
llm.provider=qwen \
llm.model_name=qwen-3-32b \
llm.api_key="" \
llm.base_url=https://your-api.com/v1 \
llm.async_client=true \
llm.temperature=1.0 \
llm.max_context_length=131072 \
benchmark.execution.max_tasks=null \
benchmark.execution.max_concurrent=10 \
benchmark.execution.pass_at_k=1 \
agent=single_agent \
hydra.run.dir=$LOG_DIR \
2>&1 | tee "$LOG_DIR/output.log"
# Enter the apps/collect-trace directory
TARGET_DIR="$SCRIPT_DIR/../"
echo "Target directory: $TARGET_DIR"
cd $TARGET_DIR
# Process traces
uv run python $TARGET_DIR/utils/process_logs.py $LOG_DIR/benchmark_results.jsonl
================================================
FILE: apps/collect-trace/utils/converters/__init__.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
from .convert_non_oai_to_chatml import (
convert_to_json_chatml,
extract_and_save_chat_history,
)
from .convert_oai_to_chatml import (
extract_message_history_from_log,
oai_tool_message_to_chat_message,
process_log_file,
save_chatml_to_files,
)
from .convert_to_chatml_auto_batch import (
batch_process_files,
determine_conversion_method,
get_llm_provider,
process_single_file,
)
__all__ = [
# OAI conversion functions
"oai_tool_message_to_chat_message",
"extract_message_history_from_log",
"save_chatml_to_files",
"process_log_file",
# Non-OAI conversion functions
"convert_to_json_chatml",
"extract_and_save_chat_history",
# Auto batch conversion functions
"get_llm_provider",
"determine_conversion_method",
"process_single_file",
"batch_process_files",
]
================================================
FILE: apps/collect-trace/utils/converters/convert_non_oai_to_chatml.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
import json
import sys
from pathlib import Path
from typing import Any, Dict, List
def convert_to_json_chatml(messages: List[Dict[str, Any]]) -> List[Dict[str, str]]:
"""
Convert message list to OpenAI JSON format ChatML
Filter out messages with role 'tool', convert content None to empty string
"""
chatml_list = []
for message in messages:
role = message.get("role", "")
if role == "tool":
continue # Skip tool messages
if role == "system":
continue # Skip system messages
content = message.get("content", "")
if content is None:
content = ""
# Handle different content formats
if isinstance(content, list):
text_parts = []
for item in content:
if isinstance(item, dict) and item.get("type") == "text":
text_parts.append(item.get("text", ""))
content = " ".join(text_parts)
elif isinstance(content, str):
pass
else:
content = str(content)
chatml_list.append({"role": role, "content": content})
return chatml_list
def extract_and_save_chat_history(
log_data: Dict[str, Any], output_dir: Path, input_filename: str
):
"""
Extract message history from log data and save as ChatML format
Args:
log_data: Log data dictionary
output_dir: Output directory
input_filename: Input filename (without extension)
"""
# Ensure output directory exists
output_dir.mkdir(parents=True, exist_ok=True)
# 1. Extract main_agent_message_history
main_agent_history = log_data.get("main_agent_message_history", {})
if main_agent_history and "message_history" in main_agent_history:
main_messages = main_agent_history["message_history"]
if main_messages:
chatml_list = convert_to_json_chatml(main_messages)
chatml_list.insert(
0,
{
"role": "system",
"content": main_agent_history.get("system_prompt", ""),
},
)
# Save main agent chat records
main_output_file = output_dir / f"{input_filename}_main_agent_chatml.json"
with open(main_output_file, "w", encoding="utf-8") as f:
json.dump(chatml_list, f, ensure_ascii=False, indent=2)
print(f"✓ Saved main agent chat records: {main_output_file}")
# 2. Extract sub_agent_message_history_sessions
sub_agent_sessions = log_data.get("sub_agent_message_history_sessions", {})
if sub_agent_sessions:
for session_name, session_data in sub_agent_sessions.items():
if "message_history" in session_data:
sub_agent_messages = session_data["message_history"]
if sub_agent_messages:
chatml_list = convert_to_json_chatml(sub_agent_messages)
chatml_list.insert(
0,
{
"role": "system",
"content": session_data.get("system_prompt", ""),
},
)
# Save browser agent chat records
sub_agent_output_file = (
output_dir / f"{input_filename}_{session_name}_chatml.json"
)
with open(sub_agent_output_file, "w", encoding="utf-8") as f:
json.dump(chatml_list, f, ensure_ascii=False, indent=2)
print(f"✓ Saved sub agent chat records: {sub_agent_output_file}")
def main():
"""Main function"""
if len(sys.argv) < 2:
print("Usage: python convert_non_oai_to_chatml.py <log_file_path> [output_dir]")
print(
"Example: python convert_non_oai_to_chatml.py logs/debug_logs/task_1.json"
)
print(
"Example: python convert_non_oai_to_chatml.py logs/debug_logs/task_1.json ./extracted_chats"
)
sys.exit(1)
log_file_path = Path(sys.argv[1])
output_dir = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("extracted_chats")
# Check if input file exists
if not log_file_path.exists():
print(f"Error: Log file does not exist: {log_file_path}")
sys.exit(1)
try:
# Read log file
print(f"Reading log file: {log_file_path}")
with open(log_file_path, "r", encoding="utf-8") as f:
log_data = json.load(f)
# Extract input filename (without extension)
input_filename = log_file_path.stem
# Extract and save chat history
print(f"Extracting chat history to: {output_dir}")
extract_and_save_chat_history(log_data, output_dir, input_filename)
print("\n✓ Chat history extraction completed!")
print(f"Output directory: {output_dir.absolute()}")
except json.JSONDecodeError as e:
print(f"Error: Cannot parse JSON file: {e}")
sys.exit(1)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
================================================
FILE: apps/collect-trace/utils/converters/convert_oai_to_chatml.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
import ast
import json
import os
import sys
from copy import deepcopy
from datetime import datetime
from pathlib import Path
from typing import Any, Dict
from system_prompts import (
main_system_prompt_foreword,
sub_agent_system_prompt_foreword,
system_prompt_tool_instrcutions,
)
# Initialize creation_time_str with current time
creation_time_str = datetime.now().strftime("%Y-%m-%d")
def oai_tool_message_to_chat_message(oai_messages, agent_type, tool_definition):
def convert_oai_tool_call_to_mcp_tool_call_str(oai_tool_call):
if isinstance(oai_tool_call, list):
assert len(oai_tool_call) >= 1
if isinstance(oai_tool_call, str):
oai_tool_call = [json.loads(oai_tool_call)]
mcp_tool_call_templates = []
for each_oai_tool_call in oai_tool_call:
assert isinstance(
each_oai_tool_call, dict
), f"oai_tool_call should be a dict, but got {type(each_oai_tool_call)}"
server_name, tool_name = each_oai_tool_call["function"]["name"].rsplit(
"-", maxsplit=1
)
arguments = json.loads(each_oai_tool_call["function"]["arguments"])
mcp_tool_call_template = f"<use_mcp_tool>\n<server_name>{server_name}</server_name>\n<tool_name>{tool_name}</tool_name>\n<arguments>\n{json.dumps(arguments)}\n</arguments>\n</use_mcp_tool>"
mcp_tool_call_templates.append(mcp_tool_call_template)
return "\n\n".join(mcp_tool_call_templates)
def safe_get_text(content):
"""Safely extract text content, handling different content formats"""
if isinstance(content, list) and content:
if isinstance(content[0], dict) and "text" in content[0]:
return content[0]["text"]
elif isinstance(content[0], str):
return content[0]
else:
return str(content[0])
elif isinstance(content, str):
return content
elif content is None:
return ""
else:
return str(content)
def generate_mcp_servers_str(tool_definition):
mcp_servers_str = ""
if tool_definition and len(tool_definition) > 0:
for server in tool_definition:
mcp_servers_str += f"## Server name: {server['name']}\n"
if "tools" in server and len(server["tools"]) > 0:
for tool in server["tools"]:
# Skip tools that failed to load (they only have 'error' key)
if "error" in tool and "name" not in tool:
continue
mcp_servers_str += f"### Tool name: {tool['name']}\n"
mcp_servers_str += f"Description: {tool['description']}\n"
mcp_servers_str += f"Input JSON schema: {tool['schema']}\n"
return mcp_servers_str
oai_messages = deepcopy(oai_messages)
chat_messages = []
idx = 0
pending_user_tool_contents = []
# Merge pending_user_tool_contents into a single user message and add to chat_messages
def flush_pending(pending_user_tool_contents, chat_messages):
if pending_user_tool_contents:
combined_content = "\n\n".join(pending_user_tool_contents)
chat_messages.append(
{
"role": "user",
"content": combined_content,
}
)
return [] # Always return a new empty list
try:
for idx, msg in enumerate(oai_messages):
if msg["role"] in ["developer", "system"]:
assert idx == 0, "System messages should be the first message"
time_str = f" Today is: {creation_time_str}\n"
tool_definition_str = generate_mcp_servers_str(tool_definition)
ori_system_prompt = msg["content"][0]["text"]
system_prompt_after_general_objective = ori_system_prompt[
ori_system_prompt.find("# General Objective") :
]
if agent_type == "main":
system_prompt = (
main_system_prompt_foreword
+ time_str
+ system_prompt_tool_instrcutions
+ tool_definition_str
+ system_prompt_after_general_objective
)
elif agent_type == "sub_agent":
system_prompt = (
sub_agent_system_prompt_foreword
+ time_str
+ system_prompt_tool_instrcutions
+ tool_definition_str
+ system_prompt_after_general_objective
)
else:
raise ValueError(f"Unknown agent type: {agent_type}")
chat_messages.append(
{
"role": "system",
"content": system_prompt,
}
)
elif msg["role"] in ["user", "tool"]:
content = safe_get_text(msg["content"])
pending_user_tool_contents.append(content)
elif msg["role"] == "assistant" and "tool_calls" in msg:
# Flush pending user/tool messages
pending_user_tool_contents = flush_pending(
pending_user_tool_contents, chat_messages
)
content = safe_get_text(msg.get("content", ""))
if content != "":
content += "\n\n" # Concatenate thinking text with tool call
chat_messages.append(
{
"role": "assistant",
"content": content
+ convert_oai_tool_call_to_mcp_tool_call_str(msg["tool_calls"]),
}
)
elif msg["role"] == "assistant" and "tool_calls" not in msg:
# Flush pending user/tool messages
pending_user_tool_contents = flush_pending(
pending_user_tool_contents, chat_messages
)
content = safe_get_text(msg["content"])
chat_messages.append(
{
"role": "assistant",
"content": content,
}
)
else:
raise ValueError(f"Unknown role: {msg['role']}")
assert (
len(pending_user_tool_contents) == 0
), "Error: Trace ends with user/tool round. Pending user/tool contents should be empty."
except Exception as e:
raise ValueError(f"Error processing messages: {e}")
return chat_messages
def extract_message_history_from_log(
log_data: Dict[str, Any],
):
"""
Extract message history from log data and convert to OpenAI ChatML format
Args:
log_data: Log data dictionary
Returns:
Dictionary containing main_agent and sub_agents message history
"""
result = {"main_agent": [], "sub_agents": {}}
# Extract main_agent_message_history
main_agent_history = log_data.get("main_agent_message_history", {})
if main_agent_history and "message_history" in main_agent_history:
main_messages = main_agent_history["message_history"]
if main_messages:
tool_main_agent_definition = extract_step_message(
log_data, "get_main_tool_definitions"
)
result["main_agent"] = oai_tool_message_to_chat_message(
main_messages,
"main",
tool_main_agent_definition,
)
# Extract sub_agent_message_history_sessions
sub_agent_sessions = log_data.get("sub_agent_message_history_sessions", {})
if sub_agent_sessions:
for session_name, session_data in sub_agent_sessions.items():
if "message_history" in session_data:
sub_agent_messages = session_data["message_history"]
if sub_agent_messages:
sub_agent_type = session_name.split("_")[0]
tool_sub_agent_definition = extract_step_message(
log_data, f"get_sub_{sub_agent_type}_tool_definitions"
)
result["sub_agents"][session_name] = (
oai_tool_message_to_chat_message(
sub_agent_messages, "sub_agent", tool_sub_agent_definition
)
)
return result
def save_chatml_to_files(
chatml_data: Dict[str, Any],
output_dir: Path,
input_filename: str,
):
"""
Save ChatML format messages to files
Args:
chatml_data: Dictionary containing message history
output_dir: Output directory
input_filename: Input filename (without extension)
"""
# Ensure output directory exists
output_dir.mkdir(parents=True, exist_ok=True)
# Save main agent messages
if chatml_data["main_agent"]:
main_output_file = output_dir / f"{input_filename}_main_agent_chatml.json"
with open(main_output_file, "w", encoding="utf-8") as f:
json.dump(chatml_data["main_agent"], f, ensure_ascii=False, indent=2)
print(f"✓ Saved main agent ChatML: {main_output_file}")
# Save sub agent messages
for session_name, messages in chatml_data["sub_agents"].items():
# Extract numeric suffix
sub_agent_output_file = (
output_dir / f"{input_filename}_{session_name}_chatml.json"
)
with open(sub_agent_output_file, "w", encoding="utf-8") as f:
json.dump(messages, f, ensure_ascii=False, indent=2)
print(f"✓ Saved sub agent {session_name} ChatML: {sub_agent_output_file}")
def extract_step_message(data, target_step_name):
try:
# Check if step_logs field exists
if "step_logs" not in data:
print("step_logs field not found in log file")
return None
# Iterate through step_logs to find target step_name
for i, step in enumerate(data["step_logs"]):
step_name = step.get("step_name")
if step_name == target_step_name:
message = step.get("message")
return ast.literal_eval(message)
print(f"No record found with step_name '{target_step_name}'")
return None
except Exception as e:
print(f"Error processing file: {e}")
return None
def process_log_file(log_file_path: str, output_dir: str = "extracted_chatml"):
"""
Process a single log file, extract message history and convert to ChatML format
Args:
log_file_path: Log file path
output_dir: Output directory
"""
log_path = Path(log_file_path)
output_path = Path(output_dir)
if not log_path.exists():
print(f"Error: Log file does not exist: {log_file_path}")
return
# Get file creation time
global creation_time_str
try:
stat_info = os.stat(log_path)
creation_time = datetime.fromtimestamp(stat_info.st_ctime)
creation_time_str = creation_time.strftime("%Y-%m-%d")
print(f"File creation time: {creation_time_str}")
except Exception as e:
print(f"Warning: Could not get file creation time: {e}")
try:
# Read log file
print(f"Reading log file: {log_path}")
with open(log_path, "r", encoding="utf-8") as f:
log_data = json.load(f)
# Extract input filename (without extension)
input_filename = log_path.stem
# Extract message history and convert to ChatML format
print("Extracting message history...")
chatml_data = extract_message_history_from_log(log_data)
# Save to files
print(f"Saving ChatML files to: {output_path}")
save_chatml_to_files(chatml_data, output_path, input_filename)
print("\n✓ Processing completed!")
print(f"Output directory: {output_path.absolute()}")
except json.JSONDecodeError as e:
print(f"Error: Cannot parse JSON file: {e}")
except Exception as e:
print(f"Error: {e}")
def main():
"""Main function"""
if len(sys.argv) < 2:
print("Usage: python convert_oai_to_chatml.py <log_file_path> [output_dir]")
print("Example: python convert_oai_to_chatml.py logs/debug_logs/task_1.json")
print(
"Example: python convert_oai_to_chatml.py logs/debug_logs/task_1.json ./extracted_chatml"
)
sys.exit(1)
log_file_path = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) > 2 else "extracted_chatml"
process_log_file(log_file_path, output_dir)
if __name__ == "__main__":
main()
================================================
FILE: apps/collect-trace/utils/converters/convert_to_chatml_auto_batch.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
import json
import subprocess
import sys
from pathlib import Path
from typing import Dict, List
def get_llm_provider(json_file_path: str) -> str:
"""
Extract llm_provider from JSON file
Args:
json_file_path: Path to JSON file
Returns:
llm_provider value or 'unknown' if not found
"""
try:
with open(json_file_path, "r", encoding="utf-8") as f:
data = json.load(f)
# Extract llm_provider from env_info
provider = data.get("env_info", {}).get("llm_provider")
if provider:
return provider
else:
return "unknown"
except Exception as e:
print(f"Error reading JSON file {json_file_path}: {e}")
return "error"
def determine_conversion_method(provider: str) -> str:
"""
Determine conversion method based on provider
Args:
provider: LLM provider name
Returns:
'oai' for OpenAI, 'non-oai' for others
"""
if provider.lower() in ["openai", "claude_newapi", "deepseek_newapi"]:
return "oai"
else:
return "non-oai"
def get_script_paths() -> tuple:
"""
Get paths to conversion scripts
Returns:
Tuple of (oai_script_path, non_oai_script_path)
"""
# Get directory of current script
current_dir = Path(__file__).parent
oai_script = current_dir / "convert_oai_to_chatml.py"
non_oai_script = current_dir / "convert_non_oai_to_chatml.py"
# Check if scripts exist
if not oai_script.exists():
raise FileNotFoundError(f"OAI conversion script not found: {oai_script}")
if not non_oai_script.exists():
raise FileNotFoundError(
f"Non-OAI conversion script not found: {non_oai_script}"
)
return str(oai_script), str(non_oai_script)
def process_single_file(json_file_path: str, output_dir: str) -> bool:
"""
Process a single JSON file
Args:
json_file_path: Path to JSON file
output_dir: Output directory
Returns:
True if successful, False otherwise
"""
try:
# Get llm_provider
provider = get_llm_provider(json_file_path)
if provider == "error":
print(f"❌ Failed to read provider from: {json_file_path}")
return False
# Determine conversion method
conversion_method = determine_conversion_method(provider)
# Get script paths
oai_script, non_oai_script = get_script_paths()
# Choose script based on conversion method
if conversion_method == "oai":
script_path = oai_script
print(f"🔧 Using OAI conversion for provider: {provider}")
else:
script_path = non_oai_script
print(f"🔧 Using Non-OAI conversion for provider: {provider}")
# Run conversion script
result = subprocess.run(
[sys.executable, script_path, json_file_path, output_dir],
capture_output=True,
text=True,
)
if result.returncode == 0:
print(f"✅ Successfully processed: {json_file_path}")
return True
else:
print(f"❌ Failed to process {json_file_path}: {result.stderr}")
return False
except Exception as e:
print(f"❌ Error processing {json_file_path}: {e}")
return False
def find_json_files(input_paths: List[str]) -> List[str]:
"""
Find JSON files from input paths
Args:
input_paths: List of file paths, directories, or patterns
Returns:
List of JSON file paths
"""
json_files = []
for path in input_paths:
path_obj = Path(path)
if path_obj.is_file():
# Single file
if path_obj.suffix.lower() == ".json":
json_files.append(str(path_obj))
elif path_obj.is_dir():
# Directory - find all JSON files
for json_file in path_obj.glob("*.json"):
json_files.append(str(json_file))
else:
# Pattern matching
try:
for json_file in Path(".").glob(path):
if json_file.suffix.lower() == ".json":
json_files.append(str(json_file))
except Exception:
print(f"Warning: Could not process pattern: {path}")
return json_files
def batch_process_files(input_paths: List[str], output_dir: str) -> Dict[str, int]:
"""
Batch process multiple files
Args:
input_paths: List of input paths
output_dir: Output directory
Returns:
Dictionary with processing statistics
"""
# Find JSON files
json_files = find_json_files(input_paths)
if not json_files:
print("❌ No JSON files found in the specified paths")
return {"total": 0, "success": 0, "failed": 0}
print(f"📁 Found {len(json_files)} JSON files to process")
# Create output directory
Path(output_dir).mkdir(parents=True, exist_ok=True)
# Process files
success_count = 0
failed_count = 0
for json_file in json_files:
if process_single_file(json_file, output_dir):
success_count += 1
else:
failed_count += 1
return {"total": len(json_files), "success": success_count, "failed": failed_count}
def show_help():
"""Show help information"""
help_text = """
Auto ChatML Conversion Script
============================
Automatically determines conversion method based on llm_provider field in JSON files
Usage:
python convert_to_chatml_auto_batch.py <input_paths...> [output_dir]
python convert_to_chatml_auto_batch.py <log_dir> [output_dir]
python convert_to_chatml_auto_batch.py <log_file_pattern> [output_dir]
Parameters:
input_paths: JSON files, directories, or patterns
output_dir: Output directory (optional, default: extracted_chatml)
Examples:
python convert_to_chatml_auto_batch.py logs/debug_logs/
python convert_to_chatml_auto_batch.py logs/debug_logs/*.json
python convert_to_chatml_auto_batch.py logs/debug_logs/ ./my_output
python convert_to_chatml_auto_batch.py task_1.json task_2.json
Conversion Logic:
- If llm_provider = 'openai': Use convert_oai_to_chatml.py
- If llm_provider = anything else: Use convert_non_oai_to_chatml.py
Features:
1. Auto-detect conversion method per file
2. Batch process log files
3. Extract main_agent_message_history
4. Extract browser_agent_message_history_sessions
5. Convert to OpenAI ChatML format
6. Save as separate files
7. Generate processing summary
"""
print(help_text)
def main():
"""Main function"""
# Check for help
if len(sys.argv) < 2 or sys.argv[1] in ["-h", "--help"]:
show_help()
return
# Parse arguments
args = sys.argv[1:]
# Check if last argument is output directory
if len(args) > 1 and not args[-1].startswith("-"):
# Check if last argument looks like a directory
last_arg = args[-1]
if (
last_arg.endswith("/")
or not Path(last_arg).suffix
or last_arg == "extracted_chatml"
or last_arg.startswith("./")
):
output_dir = last_arg
input_paths = args[:-1]
else:
output_dir = "extracted_chatml"
input_paths = args
else:
output_dir = "extracted_chatml"
input_paths = args
print("🚀 Starting auto ChatML conversion")
print(f"📂 Input paths: {input_paths}")
print(f"📁 Output directory: {output_dir}")
try:
# Check if conversion scripts exist
get_script_paths()
# Process files
stats = batch_process_files(input_paths, output_dir)
# Show results
print("\n" + "=" * 50)
print("📊 Processing Summary")
print("=" * 50)
print(f"Total files: {stats['total']}")
print(f"Successfully processed: {stats['success']}")
print(f"Failed: {stats['failed']}")
print(f"Output directory: {Path(output_dir).absolute()}")
if stats["failed"] > 0:
print(f"\n⚠️ {stats['failed']} files failed to process")
sys.exit(1)
else:
print("\n✅ All files processed successfully!")
except FileNotFoundError as e:
print(f"❌ {e}")
sys.exit(1)
except Exception as e:
print(f"❌ Unexpected error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
================================================
FILE: apps/collect-trace/utils/converters/example_usage.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
import json
import os
import sys
import tempfile
from pathlib import Path
# Add parent directory to Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from utils.converters import (
extract_and_save_chat_history,
extract_message_history_from_log,
)
def example_1_basic_conversion():
"""Example 1: Basic conversion using Python API"""
print("=== Example 1: Basic Conversion ===")
# Sample log data
log_data = {
"main_agent_message_history": {
"system_prompt": "You are a helpful assistant.",
"message_history": [
{
"role": "developer",
"content": [
{"type": "text", "text": "You are a helpful assistant."}
],
},
{
"role": "user",
"content": [{"type": "text", "text": "Hello, how are you?"}],
},
{
"role": "assistant",
"content": [{"type": "text", "text": "I'm doing well, thank you!"}],
},
],
},
"browser_agent_message_history_sessions": {
"browser_agent_1": {
"system_prompt": "You are a browsing agent.",
"message_history": [
{
"role": "developer",
"content": [
{"type": "text", "text": "You are a browsing agent."}
],
},
{
"role": "user",
"content": [{"type": "text", "text": "Search for something"}],
},
{
"role": "assistant",
"content": [{"type": "text", "text": "I found it."}],
},
],
}
},
"env_info": {"llm_provider": "openai"},
}
# Convert using OAI method
chatml_data = extract_message_history_from_log(log_data)
print(
f"OAI conversion result: {len(chatml_data['main_agent'])} messages in main agent"
)
print(
f"OAI conversion result: {len(chatml_data['browser_agents']['browser_agent_1'])} messages in browser agent"
)
# Convert using Non-OAI method
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
extract_and_save_chat_history(log_data, temp_path, "example")
# Check generated files
main_file = temp_path / "example_main_agent_chatml.json"
browser_file = temp_path / "example_browser_agent_1_chatml.json"
if main_file.exists():
with open(main_file, "r") as f:
main_content = json.load(f)
print(
f"Non-OAI conversion result: {len(main_content)} messages in main agent"
)
if browser_file.exists():
with open(browser_file, "r") as f:
browser_content = json.load(f)
print(
f"Non-OAI conversion result: {len(browser_content)} messages in browser agent"
)
if __name__ == "__main__":
print("ChatML Conversion Utilities - Usage Examples")
print("=" * 50)
example_1_basic_conversion()
print("\n" + "=" * 50)
print("Examples completed successfully!")
print("\nFor more information, see the README.md file.")
================================================
FILE: apps/collect-trace/utils/converters/system_prompts.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
main_system_prompt_foreword = """In this environment you have access to a set of tools you can use to answer the user's question. \n \nYou only have access to the tools provided below. You can only use one tool per message, and will receive the result of that tool in the user's next response. You use tools step-by-step to accomplish a given task, with each tool-use informed by the result of the previous tool-use."""
sub_agent_system_prompt_foreword = """In this environment you have access to a set of tools you can use to answer the user's question. \n \nYou only have access to the tools provided below. You can only use one tool per message, and will receive the result of that tool in the user's next response. You use tools step-by-step to accomplish a given task, with each tool-use informed by the result of the previous tool-use."""
system_prompt_tool_instrcutions = """# Tool-Use Formatting Instructions \n\nTool-use is formatted using XML-style tags. The tool-use is enclosed in <use_mcp_tool></use_mcp_tool> and each parameter is similarly enclosed within its own set of tags.\n\nThe Model Context Protocol (MCP) connects to servers that provide additional tools and resources to extend your capabilities. You can use the server's tools via the `use_mcp_tool`.\n\nDescription: \nRequest to use a tool provided by a MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters.\n\nParameters:\n- server_name: (required) The name of the MCP server providing the tool\n- tool_name: (required) The name of the tool to execute\n- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema, quotes within string must be properly escaped, ensure it's valid JSON\n\nUsage:\n<use_mcp_tool>\n<server_name>server name here</server_name>\n<tool_name>tool name here</tool_name>\n<arguments>\n{\n\"param1\": \"value1\",\n\"param2\": \"value2 \\\"escaped string\\\"\"\n}\n</arguments>\n</use_mcp_tool>\n\nImportant Notes:\n- Tool-use must be placed **at the end** of your response, **top-level**, and not nested within other tags.\n- Always adhere to this format for the tool use to ensure proper parsing and execution.\n\nString and scalar parameters should be specified as is, while lists and objects should use JSON format. Note that spaces for string values are not stripped. The output is not expected to be valid XML and is parsed with regular expressions.\nHere are the functions available in JSONSchema format:\n\n"""
================================================
FILE: apps/collect-trace/utils/merge_chatml_msgs_to_one_json.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
import argparse
import glob
import json
import os
def merge_json_files(input_dir, type="main"):
# List to store all messages
all_conversations = []
# Get all JSON files matching the pattern
json_files = glob.glob(os.path.join(input_dir, f"*{type}*.json"))
# Read each JSON file and merge its content
for json_file in json_files:
try:
with open(json_file, "r", encoding="utf-8") as f:
data = json.load(f)
conversation = {
"messages": data,
}
all_conversations.append(conversation)
print(f"Successfully processed: {json_file}")
except Exception as e:
print(f"Error processing {json_file}: {str(e)}")
output_file = os.path.join(input_dir, f"{type}_merged.json")
# Write the merged data to a new JSON file
with open(output_file, "w", encoding="utf-8") as f:
json.dump(all_conversations, f, ensure_ascii=False, indent=2)
print(
f"\nMerging complete! All {type} JSON files have been merged into {output_file}"
)
print(f"Total number of files processed: {len(json_files)}")
print(f"Total number of messages: {len(all_conversations)}")
def main():
parser = argparse.ArgumentParser(
description="Merge multiple JSON files which contain chat messages into a single file"
)
parser.add_argument(
"--input_dir",
type=str,
required=True,
help="File pattern with wildcards to match JSON files (e.g., '*.json' or 'data/*main*.json')",
)
args = parser.parse_args()
merge_json_files(args.input_dir, type="main_agent")
merge_json_files(args.input_dir, type="agent-browsing")
if __name__ == "__main__":
main()
================================================
FILE: apps/collect-trace/utils/process_logs.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
import argparse
import json
import os
import shutil
def get_successful_log_paths(jsonl_file_path: str) -> list:
"""
Collects the paths of successful log files from a dataset.
This function extracts log file paths of successful records based on
the value of `final_judge_result`. If the dataset has been fully
processed, it reads from a `benchmark_results.jsonl` file. Otherwise,
if processing was interrupted, it falls back to scanning individual
`.json` files in the given directory.
Success is determined by:
- `PASS_AT_K_SUCCESS` for records in JSONL files.
- `CORRECT` for records in individual JSON files.
Args:
jsonl_file_path (str): Path to a JSONL file or a directory of JSON files.
Returns:
list: A list of log file paths for successful records.
"""
log_paths = []
if jsonl_file_path.endswith(".jsonl"):
with open(jsonl_file_path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
try:
data = json.loads(line)
if data.get("final_judge_result") == "PASS_AT_K_SUCCESS":
log_path = data.get("log_file_path")
if log_path:
log_paths.append(log_path)
except json.JSONDecodeError:
continue
else:
filenames = os.listdir(jsonl_file_path)
filenames = [filename for filename in filenames if filename.endswith(".json")]
for filename in filenames:
filepath = os.path.join(jsonl_file_path, filename)
try:
data = json.load(open(filepath, "r"))
except Exception:
continue
try:
final_judge_result = data["final_judge_result"]
except KeyError:
print(data.keys())
continue
if final_judge_result == "CORRECT":
log_paths.append(filepath)
return log_paths
# Usage example
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Extract successful log paths from JSONL file"
)
parser.add_argument(
"file_path", help="Path to the JSONL file containing benchmark results"
)
args = parser.parse_args()
result = get_successful_log_paths(args.file_path)
# Get the parent directory of args.file_path
parent_dir = os.path.abspath(os.path.dirname(args.file_path))
# Create successful logs directory
success_log_dir = parent_dir + "/successful_logs"
success_chatml_log_dir = parent_dir + "/successful_chatml_logs"
os.makedirs(success_log_dir, exist_ok=True)
print(f"Successful logs directory: {success_log_dir}")
for i, path in enumerate(result, 1):
basename = os.path.basename(path)
print(f"Copying file: {path} to {success_log_dir}/{basename}")
shutil.copy(path, f"{success_log_dir}/{basename}")
os.system(
f"uv run utils/converters/convert_to_chatml_auto_batch.py {success_log_dir}/*.json -o {success_chatml_log_dir}"
)
os.system(
f"uv run utils/merge_chatml_msgs_to_one_json.py --input_dir {success_chatml_log_dir}"
)
================================================
FILE: apps/gradio-demo/README.md
================================================
# Local Deep Research Demo with Gradio Web UI
Host your own Deep Research demo using our [MiroThinker v1.5](https://huggingface.co/miromind-ai/MiroThinker-v1.5-30B) models and lightweight Gradio-based web interface.
## 🖥️ Hardware Requirements
- **GPU**: NVIDIA RTX 40xx/50xx series or equivalent
- **VRAM**:
- **16GB minimum** (with Q4 quantization via llama.cpp)
- **48GB+ recommended** (for FP8 quantization or longer context)
- MiroThinker-v1.5-30B is a 30B MoE model with 3B active parameters
## ⚙️ LLM Server Deployment
### Download Model Checkpoints
Download the full checkpoint from Hugging Face:
```python
from huggingface_hub import snapshot_download
snapshot_download(repo_id="miromind-ai/MiroThinker-v1.5-30B", local_dir="model/MiroThinker-v1.5-30B")
```
### Option 1: SGLang Server (Recommended)
FP8 is a highly efficient 8-bit floating point format that significantly reduces memory usage while maintaining model quality. This approach provides excellent performance for inference workloads on modern GPUs.
Please install [SGLang](https://github.com/sgl-project/sglang) first. Then initialize fast inference with FP8 precision:
```bash
MODEL_PATH=model/MiroThinker-v1.5-30B
python3 -m sglang.launch_server \
--model-path $MODEL_PATH \
--mem-fraction-static 0.9 \
--quantization fp8 \
--tp 1 \
--dp 1 \
--host 0.0.0.0 \
--port 61005 \
--trust-remote-code
```
It will start an openai compatible server with BASE_URL=`http://0.0.0.0:61005/v1`.
### Option 2: llama.cpp (Quantized)
For memory-efficient inference, download the pre-quantized GGUF version from the community:
**Note**: Thanks to the community for providing quantized versions: [mradermacher](https://huggingface.co/mradermacher)
```bash
# Download Q4_K_M quantized model (recommended balance)
wget https://huggingface.co/mradermacher/MiroThinker-v1.5-30B-GGUF/resolve/main/MiroThinker-v1.5-30B.Q4_K_M.gguf
```
Follow the [official llama.cpp installation guide](https://github.com/ggml-org/llama.cpp) to set up the environment. After that:
```bash
# Set up model path
MODEL_PATH=model/MiroThinker-v1.5-30B.Q4_K_M.gguf
# Start the server
llama-server -m $MODEL_PATH \
--port 61005 \
-ngl 99 \
-v
```
This will start an OpenAI-compatible server at `http://0.0.0.0:61005/v1`.
### Other Options
You can also leverage other frameworks for model serving like Ollama, vLLM, and Text Generation Inference (TGI) for different deployment scenarios.
## 🚀 Quick Start Guide
### 1. **Environment Setup**
Get your API keys:
- [Serper](https://serper.dev/): 2,500 free search credits for new accounts (required for web search)
- [E2B](https://e2b.dev/): Free tier available (required for Python code execution)
- [Jina](https://jina.ai/): Free tier available (required for web scraping)
Edit the `apps/miroflow-agent/.env` file with your API keys:
```bash
# Required - Web Search
SERPER_API_KEY=your_serper_key
# Required - Python Code Execution (E2B Cloud Sandbox)
E2B_API_KEY=your_e2b_key
# Required - Web Scraping
JINA_API_KEY=your_jina_key
# Required - Summary LLM (for webpage summarization)
# Option 1: Use OpenAI GPT-5-Nano (recommended, cost-effective)
SUMMARY_LLM_BASE_URL=https://api.openai.com/v1
SUMMARY_LLM_MODEL_NAME=gpt-5-nano
SUMMARY_LLM_API_KEY=your_openai_key
# Option 2: Use MiroThinker itself (if you have enough VRAM)
# SUMMARY_LLM_BASE_URL=http://0.0.0.0:61005/v1
# SUMMARY_LLM_MODEL_NAME=MiroThinker
# SUMMARY_LLM_API_KEY=none
```
### 2. **Install Dependencies**
We use [uv](https://github.com/astral-sh/uv) to manage all dependencies.
```bash
cd apps/gradio-demo
uv sync
```
### 3. **Configure API Endpoint**
Set your LLM API endpoint and API key:
```bash
export BASE_URL=http://your-sglang-address:your-sglang-port/v1
export API_KEY=your_api_key # Optional, required if your endpoint needs authentication
```
### 4. **Launch the Application**
```bash
uv run main.py
```
### 5. **Access the Web Interface**
Open your browser and navigate to: `http://localhost:8080`
### 📝 Notes
- Ensure your LLM server is up and running before launching the demo
- The demo will use your local CPU/GPU for inference while leveraging external APIs for search and code execution
- Monitor your API usage through the respective provider dashboards
================================================
FILE: apps/gradio-demo/main.py
================================================
import asyncio
import json
import logging
import os
import threading
import time
import uuid
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import AsyncGenerator, List, Optional
import gradio as gr
from dotenv import load_dotenv
from hydra import compose, initialize_config_dir
from omegaconf import DictConfig
from prompt_patch import apply_prompt_patch
from src.config.settings import expose_sub_agents_as_tools
from src.core.pipeline import create_pipeline_components, execute_task_pipeline
from utils import replace_chinese_punctuation
# Apply custom system prompt patch (adds MiroThinker identity)
apply_prompt_patch()
# Create global cleanup thread pool for operations that won't be affected by asyncio.cancel
cleanup_executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="cleanup")
logger = logging.getLogger(__name__)
# Set DEMO_MODE for simplified tool configuration
os.environ["DEMO_MODE"] = "1"
# Load environment variables from .env file
load_dotenv()
# Global Hydra initialization flag
_hydra_initialized = False
def load_miroflow_config(config_overrides: Optional[dict] = None) -> DictConfig:
"""
Load the full MiroFlow configuration using Hydra, similar to how benchmarks work.
"""
global _hydra_initialized
# Get the path to the miroflow agent config directory
miroflow_config_dir = Path(__file__).parent.parent / "miroflow-agent" / "conf"
miroflow_config_dir = miroflow_config_dir.resolve()
logger.debug(f"Config dir: {miroflow_config_dir}")
if not miroflow_config_dir.exists():
raise FileNotFoundError(
f"MiroFlow config directory not found: {miroflow_config_dir}"
)
# Initialize Hydra if not already done
if not _hydra_initialized:
try:
initialize_config_dir(
config_dir=str(miroflow_config_dir), version_base=None
)
_hydra_initialized = True
except Exception as e:
logger.warning(f"Hydra already initialized or error: {e}")
# Compose configuration with environment variable overrides
overrides = []
# Add environment variable based overrides (refer to scripts/debug.sh)
llm_provider = os.getenv(
"DEFAULT_LLM_PROVIDER", "qwen"
) # debug.sh defaults to qwen
model_name = os.getenv(
"DEFAULT_MODEL_NAME", "MiroThinker"
) # debug.sh default model
agent_set = os.getenv("DEFAULT_AGENT_SET", "demo") # Use demo config
base_url = os.getenv("BASE_URL", "http://localhost:11434")
api_key = os.getenv("API_KEY", "") # API key for LLM endpoint
logger.debug(f"LLM base_url: {base_url}")
# Map provider names to config files
# Available configs: default.yaml, claude-3-7.yaml, gpt-5.yaml, qwen-3.yaml
provider_config_map = {
"anthropic": "claude-3-7",
"openai": "gpt-5",
"qwen": "qwen-3",
}
llm_config = provider_config_map.get(
llm_provider, "qwen-3"
) # fallback to qwen-3 config
overrides.extend(
[
f"llm={llm_config}",
f"llm.provider={llm_provider}",
f"llm.model_name={model_name}",
f"llm.base_url={base_url}",
f"llm.api_key={api_key}",
f"agent={agent_set}",
"agent.main_agent.max_turns=50", # Limit max turns for gradio demo
"benchmark=gaia-validation", # refer to debug.sh
]
)
# Add config overrides from request
if config_overrides:
for key, value in config_overrides.items():
if isinstance(value, dict):
for subkey, subvalue in value.items():
overrides.append(f"{key}.{subkey}={subvalue}")
else:
overrides.append(f"{key}={value}")
try:
cfg = compose(config_name="config", overrides=overrides)
return cfg
except Exception as e:
logger.error(f"Failed to compose Hydra config: {e}")
exit()
# Lazy loading for tool definitions to speed up page load
# Tools will be loaded on first request instead of blocking startup
_preload_cache = {
"cfg": None,
"main_agent_tool_manager": None,
"sub_agent_tool_managers": None,
"output_formatter": None,
"tool_definitions": None,
"sub_agent_tool_definitions": None,
"loaded": False,
}
_preload_lock = threading.Lock()
def _ensure_preloaded():
"""Lazy load pipeline components on first request."""
global _preload_cache
if _preload_cache["loaded"]:
return
with _preload_lock:
if _preload_cache["loaded"]:
return
logger.info("Loading pipeline components (first request)...")
cfg = load_miroflow_config(None)
main_agent_tool_manager, sub_agent_tool_managers, output_formatter = (
create_pipeline_components(cfg)
)
tool_definitions = asyncio.run(
main_agent_tool_manager.get_all_tool_definitions()
)
if cfg.agent.sub_agents:
tool_definitions += expose_sub_agents_as_tools(cfg.agent.sub_agents)
sub_agent_tool_definitions = {
name: asyncio.run(sub_agent_tool_manager.get_all_tool_definitions())
for name, sub_agent_tool_manager in sub_agent_tool_managers.items()
}
_preload_cache["cfg"] = cfg
_preload_cache["main_agent_tool_manager"] = main_agent_tool_manager
_preload_cache["sub_agent_tool_managers"] = sub_agent_tool_managers
_preload_cache["output_formatter"] = output_formatter
_preload_cache["tool_definitions"] = tool_definitions
_preload_cache["sub_agent_tool_definitions"] = sub_agent_tool_definitions
_preload_cache["loaded"] = True
logger.info("Pipeline components loaded successfully.")
class ThreadSafeAsyncQueue:
"""Thread-safe async queue wrapper"""
def __init__(self):
self._queue = asyncio.Queue()
self._loop = None
self._closed = False
def set_loop(self, loop):
self._loop = loop
async def put(self, item):
"""Put data safely from any thread"""
if self._closed:
return
await self._queue.put(item)
def put_nowait_threadsafe(self, item):
"""Put data from other threads - use direct queue put for lower latency"""
if self._closed or not self._loop:
return
# Use put_nowait directly instead of creating a task for lower latency
self._loop.call_soon_threadsafe(lambda: self._queue.put_nowait(item))
async def get(self):
return await self._queue.get()
def close(self):
self._closed = True
def filter_google_search_organic(organic: List[dict]) -> List[dict]:
"""
Filter google search organic results to remove unnecessary information
"""
result = []
for item in organic:
result.append(
{
"title": item.get("title", ""),
"link": item.get("link", ""),
}
)
return result
def is_scrape_error(result: str) -> bool:
"""
Check if the scrape result is an error
"""
try:
json.loads(result)
return False
except json.JSONDecodeError:
return True
def filter_message(message: dict) -> dict:
"""
Filter message to remove unnecessary information
"""
if message["event"] == "tool_call":
tool_name = message["data"].get("tool_name")
tool_input = message["data"].get("tool_input")
if (
tool_name == "google_search"
and isinstance(tool_input, dict)
and "result" in tool_input
):
result_dict = json.loads(tool_input["result"])
if "organic" in result_dict:
new_result = {
"organic": filter_google_search_organic(result_dict["organic"])
}
message["data"]["tool_input"]["result"] = json.dumps(
new_result, ensure_ascii=False
)
if (
tool_name in ["scrape", "scrape_website"]
and isinstance(tool_input, dict)
and "result" in tool_input
):
# if error, it can not be json
if is_scrape_error(tool_input["result"]):
message["data"]["tool_input"] = {"error": tool_input["result"]}
else:
message["data"]["tool_input"] = {}
return message
async def stream_events_optimized(
task_id: str, query: str, _: Optional[dict] = None, disconnect_check=None
) -> AsyncGenerator[dict, None]:
"""Optimized event stream generator that directly outputs structured events, no longer wrapped as SSE strings."""
workflow_id = task_id
last_send_time = time.time()
last_heartbeat_time = time.time()
# Create thread-safe queue
stream_queue = ThreadSafeAsyncQueue()
stream_queue.set_loop(asyncio.get_event_loop())
cancel_event = threading.Event()
def run_pipeline_in_thread():
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
class ThreadQueueWrapper:
def __init__(self, thread_queue, cancel_event):
self.thread_queue = thread_queue
self.cancel_event = cancel_event
async def put(self, item):
if self.cancel_event.is_set():
logger.info("Pipeline cancelled, stopping execution")
return
self.thread_queue.put_nowait_threadsafe(filter_message(item))
wrapper_queue = ThreadQueueWrapper(stream_queue, cancel_event)
# Ensure pipeline components are loaded (lazy loading)
_ensure_preloaded()
async def pipeline_with_cancellation():
pipeline_task = asyncio.create_task(
execute_task_pipeline(
cfg=_preload_cache["cfg"],
task_id=workflow_id,
task_description=query,
task_file_name=None,
main_agent_tool_manager=_preload_cache[
"main_agent_tool_manager"
],
sub_agent_tool_managers=_preload_cache[
"sub_agent_tool_managers"
],
output_formatter=_preload_cache["output_formatter"],
stream_queue=wrapper_queue,
log_dir=os.getenv("LOG_DIR", "logs/api-server"),
tool_definitions=_preload_cache["tool_definitions"],
sub_agent_tool_definitions=_preload_cache[
"sub_agent_tool_definitions"
],
)
)
async def check_cancellation():
while not cancel_event.is_set():
await asyncio.sleep(0.5)
logger.info("Cancel event detected, cancelling pipeline")
pipeline_task.cancel()
cancel_task = asyncio.create_task(check_cancellation())
try:
done, pending = await asyncio.wait(
[pipeline_task, cancel_task],
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
for task in done:
if task == pipeline_task:
try:
await task
except asyncio.CancelledError:
logger.info("Pipeline task was cancelled")
except Exception as e:
logger.error(f"Pipeline execution error: {e}")
pipeline_task.cancel()
cancel_task.cancel()
loop.run_until_complete(pipeline_with_cancellation())
except Exception as e:
if not cancel_event.is_set():
logger.error(f"Pipeline error: {e}", exc_info=True)
stream_queue.put_nowait_threadsafe(
{
"event": "error",
"data": {"error": str(e), "workflow_id": workflow_id},
}
)
finally:
stream_queue.put_nowait_threadsafe(None)
if "loop" in locals():
loop.close()
executor = ThreadPoolExecutor(max_workers=1)
future = executor.submit(run_pipeline_in_thread)
try:
while True:
try:
if disconnect_check and await disconnect_check():
logger.info("Client disconnected, stopping pipeline")
cancel_event.set()
break
message = await asyncio.wait_for(stream_queue.get(), timeout=0.1)
if message is None:
logger.info("Pipeline completed")
break
yield message
last_send_time = time.time()
except asyncio.TimeoutError:
current_time = time.time()
if current_time - last_send_time > 300:
logger.info("Stream timeout")
break
if future.done():
try:
message = stream_queue._queue.get_nowait()
if message is not None:
yield message
continue
except Exception:
break
if current_time - last_heartbeat_time >= 15:
yield {
"event": "heartbeat",
"data": {"timestamp": current_time, "workflow_id": workflow_id},
}
last_heartbeat_time = current_time
except Exception as e:
logger.error(f"Stream error: {e}", exc_info=True)
yield {
"event": "error",
"data": {"workflow_id": workflow_id, "error": f"Stream error: {str(e)}"},
}
finally:
cancel_event.set()
stream_queue.close()
try:
future.result(timeout=1.0)
except Exception:
pass
executor.shutdown(wait=False)
# ========================= Gradio Integration =========================
def _init_render_state():
return {
"agent_order": [],
"agents": {}, # agent_id -> {"agent_name": str, "tool_call_order": [], "tools": {tool_call_id: {...}}}
"current_agent_id": None,
"errors": [],
}
def _format_think_content(text: str) -> str:
"""Convert <think> tags to readable markdown format."""
import re
# Replace <think> tags with blockquote format (no label)
text = re.sub(r"<think>\s*", "\n> ", text)
text = re.sub(r"\s*</think>", "\n", text)
# Convert newlines within thinking to blockquote continuation
lines = text.split("\n")
result = []
in_thinking = False
for line in lines:
if line.strip().startswith(">") and not in_thinking:
in_thinking = True
result.append(line)
elif in_thinking and line.strip() and not line.startswith(">"):
result.append(f"> {line}")
else:
if line.strip() == "" and in_thinking:
in_thinking = False
result.append(line)
return "\n".join(result)
def _append_show_text(tool_entry: dict, delta: str):
existing = tool_entry.get("content", "")
# Skip "Final boxed answer" content (already shown in main response)
if "Final boxed answer" in delta:
return
# Format think tags for display
formatted_delta = _format_think_content(delta)
tool_entry["content"] = existing + formatted_delta
def _is_empty_payload(value) -> bool:
if value is None:
return True
if isinstance(value, str):
stripped = value.strip()
return stripped == "" or stripped in ("{}", "[]")
if isinstance(value, (dict, list, tuple, set)):
return len(value) == 0
return False
def _format_search_results(tool_input: dict, tool_output: dict) -> str:
"""Format google_search results in a beautiful card layout."""
lines = []
# Get search query from input
query = ""
if isinstance(tool_input, dict):
query = tool_input.get("q", "") or tool_input.get("query", "")
# Parse results from output - handle multiple formats
results = []
if isinstance(tool_output, dict):
# Case 1: output has "result" field containing JSON string
result_str = tool_output.get("result", "")
if isinstance(result_str, str) and result_str.strip():
try:
result_data = json.loads(result_str)
if isinstance(result_data, dict):
results = result_data.get("organic", [])
except json.JSONDecodeError:
pass
elif isinstance(result_str, dict):
results = result_str.get("organic", [])
# Case 2: output directly contains "organic" field
if not results and "organic" in tool_output:
results = tool_output.get("organic", [])
if not results and not query:
return ""
# Build the card
lines.append('<div class="search-card">')
# Header with query
if query:
lines.append('<div class="search-header">')
lines.append('<span class="search-icon">🔍</span>')
lines.append(f'<span class="search-query">Search: "{query}"</span>')
lines.append("</div>")
# Results count
if results:
lines.append(f'<div class="search-count">≡ Found {len(results)} results</div>')
# Results list
lines.append('<div class="search-results">')
for item in results[:10]: # Limit to 10 results
title = item.get("title", "Untitled")
link = item.get("link", "#")
lines.append(f"""<a href="{link}" target="_blank" class="search-result-item">
<span class="result-icon">🌐</span>
<span class="result-title">{title}</span>
</a>""")
lines.append("</div>")
lines.append("</div>")
return "\n".join(lines)
def _format_sogou_search_results(tool_input: dict, tool_output: dict) -> str:
"""Format sogou_search results in a beautiful card layout."""
lines = []
# Get search query from input
query = ""
if isinstance(tool_input, dict):
query = tool_input.get("q", "") or tool_input.get("query", "")
# Parse results from output - sogou uses "Pages" instead of "organic"
results = []
if isinstance(tool_output, dict):
result_str = tool_output.get("result", "")
if isinstance(result_str, str) and result_str.strip():
try:
result_data = json.loads(result_str)
if isinstance(result_data, dict):
results = result_data.get("Pages", [])
except json.JSONDecodeError:
pass
elif isinstance(result_str, dict):
results = result_str.get("Pages", [])
if not results and "Pages" in tool_output:
results = tool_output.get("Pages", [])
if not results and not query:
return ""
# Build the card
lines.append('<div class="search-card">')
# Header with query
if query:
lines.append('<div class="search-header">')
lines.append('<span class="search-icon">🔍</span>')
lines.append(f'<span class="search-query">Search: "{query}"</span>')
lines.append("</div>")
# Results count
if results:
lines.append(f'<div class="search-count">≡ Found {len(results)} results</div>')
# Results list
lines.append('<div class="search-results">')
for item in results[:10]: # Limit to 10 results
title = item.get("title", "Untitled")
link = item.get("url", item.get("link", "#"))
lines.append(f"""<a href="{link}" target="_blank" class="search-result-item">
<span class="result-icon">🌐</span>
<span class="result-title">{title}</span>
</a>""")
lines.append("</div>")
lines.append("</div>")
return "\n".join(lines)
def _format_scrape_results(tool_input: dict, tool_output: dict) -> str:
"""Format scrape/webpage results in a card layout."""
lines = []
# Get URL
url = ""
if isinstance(tool_input, dict):
url = tool_input.get("url", tool_input.get("link", ""))
# Check for error
if isinstance(tool_output, dict) and "error" in tool_output:
lines.append('<div class="scrape-card scrape-error">')
lines.append('<div class="scrape-header">')
lines.append('<span class="scrape-icon">🌐</span>')
lines.append(
f'<span class="scrape-url">{url[:60]}{"..." if len(url) > 60 else ""}</span>'
)
lines.append("</div>")
lines.append('<div class="scrape-status error">❌ Failed</div>')
lines.append("</div>")
return "\n".join(lines)
# Success case
lines.append('<div class="scrape-card">')
if url:
lines.append('<div class="scrape-header">')
lines.append('<span class="scrape-icon">🌐</span>')
lines.append(
f'<span class="scrape-url">{url[:60]}{"..." if len(url) > 60 else ""}</span>'
)
lines.append("</div>")
lines.append('<div class="scrape-status success">✓ Done</div>')
lines.append("</div>")
return "\n".join(lines)
def _render_markdown(state: dict) -> str:
lines = []
final_summary_lines = [] # Collect final summary content separately
# Render errors first if any
if state.get("errors"):
for err in state["errors"]:
lines.append(f'<div class="error-block">❌ {err}</div>')
# Render all agents' content
for agent_id in state.get("agent_order", []):
agent = state["agents"].get(agent_id, {})
agent_name = agent.get("agent_name", "")
is_final_summary = agent_name == "Final Summary"
for call_id in agent.get("tool_call_order", []):
call = agent["tools"].get(call_id, {})
tool_name = call.get("tool_name", "unknown_tool")
# Show text / message - display directly
if tool_name in ("show_text", "message"):
content = call.get("content", "")
if content:
if is_final_summary:
final_summary_lines.append(content)
else:
lines.append(content)
continue
tool_input = call.get("input", {})
tool_output = call.get("output", {})
has_input = not _is_empty_payload(tool_input)
has_output = not _is_empty_payload(tool_output)
# Special formatting for google_search
if tool_name == "google_search" and (has_input or has_output):
formatted = _format_search_results(tool_input, tool_output)
if formatted:
lines.append(formatted)
continue
# Special formatting for sogou_search
if tool_name == "sogou_search" and (has_input or has_output):
formatted = _format_sogou_search_results(tool_input, tool_output)
if formatted:
lines.append(formatted)
continue
# Special formatting for scrape/webpage tools
if tool_name in (
"scrape",
"scrape_website",
"scrape_webpage",
"scrape_and_extract_info",
) and (has_input or has_output):
formatted = _format_scrape_results(tool_input, tool_output)
if formatted:
lines.append(formatted)
continue
# Special formatting for code execution tools
if tool_name in ("python", "run_python_code") and (has_input or has_output):
# Use pure Markdown to avoid HTML wrapper blocking Markdown rendering
lines.append("\n---\n")
lines.append("#### 💻 Code Execution\n")
# Show code input - try multiple possible keys
code = ""
if isinstance(tool_input, dict):
code = tool_input.get("code") or tool_input.get("code_block") or ""
elif isinstance(tool_input, str):
code = tool_input
if code:
lines.append(f"\n```python\n{code}\n```\n")
# Show output if available
if has_output:
output = ""
if isinstance(tool_output, dict):
output = (
tool_output.get("result")
or tool_output.get("output")
or tool_output.get("stdout")
or ""
)
elif isinstance(tool_output, str):
output = tool_output
if isinstance(output, str) and output.strip():
lines.append("\n**Output:**\n")
lines.append(
f'\n```text\n{output[:1000]}{"..." if len(output) > 1000 else ""}\n```\n'
)
lines.append("\n✅ Executed\n")
continue
# Other tools - show as compact card
if has_input or has_output:
target_lines = final_summary_lines if is_final_summary else lines
target_lines.append('<div class="tool-card">')
target_lines.append(f'<div class="tool-header">🔧 {tool_name}</div>')
if has_input:
# Show brief input summary
if isinstance(tool_input, dict):
brief = ", ".join(
f"{k}: {str(v)[:30]}..."
if len(str(v)) > 30
else f"{k}: {v}"
for k, v in list(tool_input.items())[:2]
)
target_lines.append(f'<div class="tool-brief">{brief}</div>')
if has_output:
target_lines.append('<div class="tool-status">✓ Done</div>')
target_lines.append("</div>")
# Add final summary with Markdown-based styling (no HTML wrapper to preserve Markdown rendering)
if final_summary_lines:
lines.append("\n\n---\n\n") # Markdown horizontal rule as divider
lines.append("## 📋 Research Summary\n\n")
lines.extend(final_summary_lines)
return "\n".join(lines) if lines else "*Waiting to start research...*"
def _update_state_with_event(state: dict, message: dict):
event = message.get("event")
data = message.get("data", {})
if event == "start_of_agent":
agent_id = data.get("agent_id")
agent_name = data.get("agent_name", "unknown")
if agent_id and agent_id not in state["agents"]:
state["agents"][agent_id] = {
"agent_name": agent_name,
"tool_call_order": [],
"tools": {},
}
state["agent_order"].append(agent_id)
state["current_agent_id"] = agent_id
elif event == "end_of_agent":
# End marker, no special handling needed, keep structure
state["current_agent_id"] = None
elif event == "tool_call":
tool_call_id = data.get("tool_call_id")
tool_name = data.get("tool_name", "unknown_tool")
agent_id = state.get("current_agent_id") or (
state["agent_order"][-1] if state["agent_order"] else None
)
if not agent_id:
return state
agent = state["agents"].setdefault(
agent_id, {"agent_name": "unknown", "tool_call_order": [], "tools": {}}
)
tools = agent["tools"]
if tool_call_id not in tools:
tools[tool_call_id] = {"tool_name": tool_name}
agent["tool_call_order"].append(tool_call_id)
entry = tools[tool_call_id]
if tool_name == "show_text" and "delta_input" in data:
delta = data.get("delta_input", {}).get("text", "")
_append_show_text(entry, delta)
elif tool_name == "show_text" and "tool_input" in data:
ti = data.get("tool_input")
text = ""
if isinstance(ti, dict):
text = ti.get("text", "") or (
(ti.get("result") or {}).get("text")
if isinstance(ti.get("result"), dict)
else ""
)
elif isinstance(ti, str):
text = ti
if text:
_append_show_text(entry, text)
else:
# Distinguish between input and output:
if "tool_input" in data:
# Could be input (first time) or output with result (second time)
ti = data["tool_input"]
# If contains result, assign to output; otherwise assign to input
if isinstance(ti, dict) and "result" in ti:
entry["output"] = ti
else:
# Only update input if we don't already have valid input data, or if the new data is not empty
if "input" not in entry or not _is_empty_payload(ti):
entry["input"] = ti
elif event == "message":
# Same incremental text display as show_text, aggregated by message_id
message_id = data.get("message_id")
agent_id = state.get("current_agent_id") or (
state["agent_order"][-1] if state["agent_order"] else None
)
if not agent_id:
return state
agent = state["agents"].setdefault(
agent_id, {"agent_name": "unknown", "tool_call_order": [], "tools": {}}
)
tools = agent["tools"]
if message_id not in tools:
tools[message_id] = {"tool_name": "message"}
agent["tool_call_order"].append(message_id)
entry = tools[message_id]
delta_content = (data.get("delta") or {}).get("content", "")
if isinstance(delta_content, str) and delta_content:
_append_show_text(entry, delta_content)
elif event == "error":
# Collect errors, display uniformly during rendering
err_text = data.get("error") if isinstance(data, dict) else None
if not err_text:
try:
err_text = json.dumps(data, ensure_ascii=False)
except Exception:
err_text = str(data)
state.setdefault("errors", []).append(err_text)
else:
# Ignore heartbeat or other events
pass
return state
_CANCEL_FLAGS = {}
_CANCEL_LOCK = threading.Lock()
def _set_cancel_flag(task_id: str):
with _CANCEL_LOCK:
_CANCEL_FLAGS[task_id] = True
def _reset_cancel_flag(task_id: str):
with _CANCEL_LOCK:
_CANCEL_FLAGS[task_id] = False
async def _disconnect_check_for_task(task_id: str):
with _CANCEL_LOCK:
return _CANCEL_FLAGS.get(task_id, False)
def _spinner_markup(running: bool) -> str:
if not running:
return ""
return (
'\n\n<div style="display:flex;align-items:center;gap:8px;color:#555;margin-top:8px;">'
'<div style="width:16px;height:16px;border:2px solid #ddd;border-top-color:#3b82f6;border-radius:50%;animation:spin 0.8s linear infinite;"></div>'
"<span>Generating...</span>"
"</div>\n<style>@keyframes spin{to{transform:rotate(360deg)}}</style>\n"
)
async def gradio_run(query: str, ui_state: Optional[dict]):
query = replace_chinese_punctuation(query or "")
task_id = str(uuid.uuid4())
_reset_cancel_flag(task_id)
if not ui_state:
ui_state = {"task_id": task_id}
else:
ui_state = {**ui_state, "task_id": task_id}
state = _init_render_state()
# Initial: disable Run, enable Stop, and show spinner at bottom of text
yield (
_render_markdown(state) + _spinner_markup(True),
gr.update(interactive=False),
gr.update(interactive=True),
ui_state,
)
async for message in stream_events_optimized(
task_id, query, None, lambda: _disconnect_check_for_task(task_id)
):
# Skip heartbeat events - they don't need UI update
event_type = message.get("event", "unknown")
if event_type == "heartbeat":
continue
state = _update_state_with_event(state, message)
md = _render_markdown(state)
yield (
md + _spinner_markup(True),
gr.update(interactive=False),
gr.update(interactive=True),
ui_state,
)
# Small delay to allow Gradio to process the update
await asyncio.sleep(0.01)
# End: enable Run, disable Stop, remove spinner
yield (
_render_markdown(state),
gr.update(interactive=True),
gr.update(interactive=False),
ui_state,
)
def stop_current(ui_state: Optional[dict]):
tid = (ui_state or {}).get("task_id")
if tid:
_set_cancel_flag(tid)
# Immediately switch button availability: enable Run, disable Stop
return (
gr.update(interactive=True),
gr.update(interactive=False),
)
def build_demo():
# Use remote logo from dr.miromind.ai for faster page load
custom_css = """
/* ========== MiroThinker - Modern Clean Design ========== */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
/* Base */
.gradio-container {
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
background: #ffffff !important;
min-height: 100vh;
}
footer { display: none !important; }
/* ===== Top Navigation ===== */
.top-nav {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 32px;
border-bottom: 1px solid #f0f0f0;
background: #ffffff;
}
.nav-left {
display: flex;
align-items: center;
gap: 20px;
}
.nav-brand {
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
font-size: 1.1em;
color: #18181b;
}
.brand-logo {
width: 32px;
height: 32px;
border-radius: 6px;
}
.nav-links {
display: flex;
align-items: center;
gap: 12px;
}
.nav-links a {
color: #71717a;
font-size: 1.1em;
text-decoration: none;
transition: color 0.2s;
}
.nav-links a:hover {
color: #18181b;
}
.nav-right {
display: flex;
align-items: center;
gap: 16px;
}
.nav-right a {
color: #52525b;
text-decoration: none;
font-size: 0.9em;
}
/* ===== Hero Section ===== */
.hero-section {
text-align: center;
padding: 60px 24px 40px;
max-width: 900px;
margin: 0 auto;
}
.hero-title {
font-size: 3em;
font-weight: 700;
background: linear-gradient(135deg, #10b981 0%, #14b8a6 50%, #06b6d4 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0 0 16px 0;
letter-spacing: -0.02em;
}
.hero-subtitle {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
color: #71717a;
font-size: 1em;
}
.hero-line {
width: 40px;
height: 1px;
background: #d4d4d8;
}
/* ===== Input Section ===== */
#input-section {
max-width: 720px !important;
margin: 0 auto 40px !important;
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
}
#question-input {
padding: 20px 24px !important;
background: #ffffff !important;
border: none !important;
}
#question-input textarea {
background: #ffffff !important;
border: none !important;
font-size: 1.05em !important;
line-height: 1.7 !important;
color: #18181b !important;
box-shadow: none !important;
}
#question-input textarea:focus {
outline: none !important;
box-shadow: none !important;
}
#question-input textarea::placeholder {
color: #9ca3af !important;
}
#btn-row {
padding: 16px 24px !important;
border-top: 1px solid #f0f0f0;
gap: 12px !important;
}
#run-btn {
background: linear-gradient(135deg, #10b981 0%, #14b8a6 100%) !important;
color: #ffffff !important;
border: none !important;
border-radius: 10px !important;
padding: 12px 24px !important;
font-size: 0.95em !important;
font-weight: 500 !important;
cursor: pointer !important;
transition: opacity 0.2s, transform 0.2s !important;
}
#run-btn:hover {
opacity: 0.9 !important;
transform: translateY(-1px) !important;
}
#stop-btn {
background: #ffffff !important;
color: #71717a !important;
border: 1px solid #e5e5e5 !important;
border-radius: 10px !important;
padding: 12px 20px !important;
font-size: 0.95em !important;
font-weight: 500 !important;
cursor: pointer !important;
transition: all 0.2s !important;
}
#stop-btn:hover {
color: #ef4444 !important;
border-color: #fecaca !important;
background: #fef2f2 !important;
}
/* ===== Output Section ===== */
#output-section {
max-width: 900px !important;
margin: 0 auto !important;
padding: 0 24px 60px !important;
}
.output-label {
font-size: 0.85em;
font-weight: 500;
color: #71717a;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 12px;
padding: 0 4px;
}
#log-view {
padding: 24px !important;
min-height: 400px;
max-height: 70vh;
overflow-y: auto;
background: #ffffff !important;
border: 1px solid #e5e5e5 !important;
border-radius: 16px !important;
}
#log-view h3 {
font-size: 0.95em;
font-weight: 600;
color: #18181b;
margin: 24px 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid #f4f4f5;
}
#log-view h3:first-child {
margin-top: 0;
}
/* Error block */
.error-block {
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 10px;
padding: 12px 16px;
margin: 12px 0;
color: #dc2626;
font-size: 0.9em;
}
/* Tool card */
.tool-card {
background: #fafafa;
border: 1px solid #e5e5e5;
border-radius: 10px;
padding: 12px 16px;
margin: 12px 0;
}
.tool-header {
font-size: 0.9em;
font-weight: 500;
color: #3f3f46;
margin-bottom: 4px;
}
.tool-brief {
font-size: 0.8em;
color: #71717a;
margin-top: 4px;
}
.tool-status {
font-size: 0.8em;
color: #10b981;
margin-top: 6px;
}
#log-view blockquote {
background: linear-gradient(135deg, #f0fdf4 0%, #ecfeff 100%);
border: none;
border-left: 3px solid #10b981;
padding: 16px 20px;
margin: 16px 0;
border-radius: 0 12px 12px 0;
font-style: normal;
color: #065f46;
font-size: 0.9em;
line-height: 1.7;
}
#log-view pre {
background: #f8f9fa !important;
color: #1e293b !important;
border-radius: 8px !important;
padding: 16px !important;
font-size: 0.85em !important;
line-height: 1.6 !important;
overflow-x: auto;
margin: 12px 0;
border: 1px solid #e2e8f0;
}
#log-view pre code {
background: transparent !important;
color: #1e293b !important;
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace !important;
font-size: inherit !important;
padding: 0 !important;
white-space: pre-wrap;
word-break: break-word;
}
#log-view code {
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace !important;
background: #f1f5f9 !important;
color: #1e293b !important;
padding: 2px 6px !important;
border-radius: 4px !important;
font-size: 0.9em !important;
}
#log-view p {
line-height: 1.7;
color: #3f3f46;
}
#log-view::-webkit-scrollbar {
width: 6px;
}
#log-view::-webkit-scrollbar-track {
background: transparent;
}
#log-view::-webkit-scrollbar-thumb {
background: #e5e5e5;
border-radius: 3px;
}
#log-view::-webkit-scrollbar-thumb:hover {
background: #d4d4d8;
}
/* ===== Footer ===== */
.app-footer {
text-align: center;
padding: 24px;
color: #a1a1aa;
font-size: 0.85em;
border-top: 1px solid #f0f0f0;
}
/* ===== Loading Spinner ===== */
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-indicator {
display: inline-flex;
align-items: center;
gap: 10px;
color: #10b981;
font-size: 0.9em;
padding: 12px 0;
}
.loading-indicator::before {
content: '';
width: 16px;
height: 16px;
border: 2px solid #d1fae5;
border-top-color: #10b981;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
/* ===== Search Results Card ===== */
.search-card {
background: #ffffff;
border: 1px solid #e5e5e5;
border-radius: 12px;
margin: 16px 0;
overflow: hidden;
}
.search-header {
display: flex;
align-items: center;
gap: 10px;
padding: 14px 18px;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
.search-icon {
font-size: 1em;
color: #10b981;
}
.search-query {
font-size: 0.9em;
color: #3f3f46;
font-weight: 500;
}
.search-count {
padding: 10px 18px;
font-size: 0.8em;
color: #71717a;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
.search-results {
padding: 8px 0;
}
.search-result-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 18px;
text-decoration: none;
color: #3f3f46;
font-size: 0.9em;
transition: background 0.15s;
border-left: 3px solid transparent;
}
.search-result-item:hover {
background: #f9fafb;
border-left-color: #10b981;
}
.result-icon {
font-size: 1em;
flex-shrink: 0;
opacity: 0.6;
}
.result-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ===== Scrape Card ===== */
.scrape-card {
background: #ffffff;
border: 1px solid #e5e5e5;
border-radius: 10px;
margin: 12px 0;
padding: 12px 16px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.scrape-card.scrape-error {
border-color: #fecaca;
background: #fef2f2;
}
.scrape-header {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.scrape-icon {
font-size: 1em;
opacity: 0.6;
}
.scrape-url {
font-size: 0.85em;
color: #52525b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.scrape-status {
font-size: 0.8em;
padding: 4px 10px;
border-radius: 6px;
flex-shrink: 0;
}
.scrape-status.success {
background: #ecfdf5;
color: #059669;
}
.scrape-status.error {
background: #fef2f2;
color: #dc2626;
}
/* ===== Final Summary Section ===== */
.final-summary-divider {
height: 1px;
background: linear-gradient(to right, transparent, #e5e5e5, transparent);
margin: 32px 0;
}
.final-summary-section {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border: 1px solid #e2e8f0;
border-radius: 16px;
padding: 24px;
margin-top: 16px;
}
.final-summary-header {
font-size: 1.1em;
font-weight: 600;
color: #1e293b;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #3b82f6;
display: inline-block;
}
.final-summary-content {
color: #334155;
line-height: 1.8;
}
.final-summary-content h1,
.final-summary-content h2,
.final-summary-content h3 {
color: #1e293b;
margin-top: 1.5em;
margin-bottom: 0.5em;
}
.final-summary-content h1 { font-size: 1.4em; }
.final-summary-content h2 { font-size: 1.2em; }
.final-summary-content h3 { font-size: 1.1em; }
.final-summary-content p {
margin: 0.8em 0;
}
.final-summary-content ul,
.final-summary-content ol {
margin: 0.8em 0;
padding-left: 1.5em;
}
.final-summary-content li {
margin: 0.4em 0;
}
.final-summary-content a {
color: #3b82f6;
text-decoration: none;
}
.final-summary-content a:hover {
text-decoration: underline;
}
.final-summary-content code {
background: #e2e8f0;
padding: 2px 6px;
border-radius: 4px;
font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 0.9em;
}
.final-summary-content pre {
background: #1e293b;
color: #e2e8f0;
padding: 16px;
border-radius: 8px;
overflow-x: auto;
}
.final-summary-content pre code {
background: transparent;
padding: 0;
color: inherit;
}
.final-summary-content table {
width: 100%;
border-collapse: collapse;
margin: 1em 0;
}
.final-summary-content th,
.final-summary-content td {
padding: 10px 12px;
border: 1px solid #e2e8f0;
text-align: left;
}
.final-summary-content th {
background: #f1f5f9;
font-weight: 600;
}
.final-summary-content blockquote {
border-left: 4px solid #3b82f6;
margin: 1em 0;
padding: 0.5em 1em;
background: #f8fafc;
color: #475569;
}
/* ===== Code Execution Card ===== */
.code-card {
background: #1e1e2e;
border: 1px solid #313244;
border-radius: 12px;
margin: 12px 0;
padding: 16px;
overflow: hidden;
}
.code-header {
font-size: 0.9em;
font-weight: 600;
color: #cdd6f4;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.code-card pre {
background: #11111b !important;
border-radius: 8px;
padding: 12px 16px;
margin: 8px 0;
overflow-x: auto;
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace !important;
font-size: 0.85em;
line-height: 1.5;
}
.code-card code {
background: transparent !important;
color: #cdd6f4 !important;
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace !important;
}
.code-output-label {
font-size: 0.8em;
color: #a6adc8;
margin-top: 12px;
margin-bottom: 4px;
}
.code-status {
font-size: 0.8em;
color: #a6e3a1;
margin-top: 8px;
text-align: right;
}
/* ===== Responsive ===== */
@media (max-width: 768px) {
.hero-title {
font-size: 2em;
}
.hero-section {
padding: 40px 16px 24px;
}
.input-wrapper, .output-wrapper {
padding: 0 16px;
}
#log-view {
max-height: 50vh;
}
}
"""
# Favicon head content
favicon_head = '<link rel="icon" href="https://dr.miromind.ai/favicon.ico?v=2">'
with gr.Blocks(
css=custom_css,
title="MiroThinker - Deep Research",
theme=gr.themes.Base(),
head=favicon_head,
) as demo:
# Top Navigation
gr.HTML("""
<nav class="top-nav">
<div class="nav-left">
<div class="nav-brand">
<img src="https://dr.miromind.ai/favicon.png" class="brand-logo" alt="MiroThinker" />
MiroThinker
</div>
<div class="nav-links">
<a href="https://huggingface.co/MiroMind" target="_blank">🤗</a>
<a href="https://github.com/MiroMind/MiroThinker" target="_blank">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
</a>
</div>
</div>
<div class="nav-right">
<a href="https://miromind.ai" target="_blank">Visit Website</a>
</div>
</nav>
""")
# Hero Section
gr.HTML("""
<div class="hero-section">
<h1 class="hero-title">Research Deep. Uncover the Future</h1>
<div class="hero-subtitle">
<span class="hero-line"></span>
Don't just chat. Predict, verify, and discover with science-based AI.
<span class="hero-line"></span>
</div>
</div>
""")
# Input Section
with gr.Column(elem_id="input-section"):
inp = gr.Textbox(
lines=4,
placeholder="Enter your research question...",
show_label=False,
elem_id="question-input",
)
with gr.Row(elem_id="btn-row"):
stop_btn = gr.Button(
"⏹ Stop",
elem_id="stop-btn",
variant="stop",
interactive=False,
scale=1,
)
run_btn = gr.Button(
"Start Research ➤", elem_id="run-btn", variant="primary", scale=2
)
# Output Section
with gr.Column(elem_id="output-section"):
gr.HTML('<div class="output-label">Research Progress</div>')
out_md = gr.Markdown("*Waiting to start research...*", elem_id="log-view")
# State
ui_state = gr.State({"task_id": None})
# Event handlers
run_btn.click(
fn=gradio_run,
inputs=[inp, ui_state],
outputs=[out_md, run_btn, stop_btn, ui_state],
)
stop_btn.click(fn=stop_current, inputs=[ui_state], outputs=[run_btn, stop_btn])
# Footer
gr.HTML("""
<div class="app-footer">
Content generated by MiroMind AI. Please verify important information.
</div>
""")
return demo
if __name__ == "__main__":
demo = build_demo()
host = os.getenv("HOST", "0.0.0.0")
port = int(os.getenv("PORT", "8080"))
demo.queue().launch(server_name=host, server_port=port)
================================================
FILE: apps/gradio-demo/prompt_patch.py
================================================
# Copyright (c) 2025 MiroMind
# This source code is licensed under the Apache 2.0 License.
"""
Custom Prompt Override (Monkey Patching)
This module allows customizing prompts without modifying miroflow-agent code.
Patches applied:
1. `generate_mcp_system_prompt` - Prepends custom identity prompt
2. `process_input` - Removes the boxed format requirement suffix
3. `generate_agent_summarize_prompt` - Uses user-friendly summary prompt for demo
4. `format_final_summary_and_log` - Disables boxed format check to prevent retry
Usage:
from prompt_patch import apply_prompt_patch
apply_prompt_patch()
"""
import re
# ============================================================================
# Custom Identity Prompt
# ============================================================================
CUSTOM_IDENTITY_PROMPT = """You are MiroThinker, a specialized deep research AI assistant developed by MiroMind.
IMPORTANT IDENTITY REMINDER:
- You are NOT ChatGPT, Claude, or any other AI assistant
"""
# ============================================================================
# Strings to Remove from Input Processing
# ============================================================================
# This string is appended to task descriptions in input_handler.py
# We remove it for demo mode since we don't need strict boxed format
BOXED_FORMAT_SUFFIX = "\nYou should follow the format instruction in the request strictly and wrap the final answer in \\boxed{}."
# ============================================================================
# Custom Summarize Prompt for Demo Mode
# ============================================================================
def get_demo_summarize_prompt(target_language: str, task_description: str) -> str:
"""
Generate a user-friendly summarize prompt for demo mode.
This prompt is designed for better user experience, producing well-formatted
Markdown responses instead of strict boxed answers.
Args:
target_language: The language to write the response in
task_description: The original user question
Returns:
The summarize prompt string
"""
return f"""Please provide the final research summary based only on the information already gathered.
No further tool calls are allowed.
## Requirements
- **Language**: Write the entire response in **{target_language}**.
- **Focus**: Directly answer the original question above. Do not just summarize gathered information — provide a clear, actionable answer.
- **Response Length**: Match the complexity of your response to the question. For simple or short questions, provide a concise and direct answer without unnecessary elaboration. For complex questions, provide a detailed and structured report.
- Use clear and structured Markdown formatting when appropriate.
- Use appropriate Markdown headings (e.g., #, ##, ###) only when the content warrants structure.
- Present key findings in an organized, concise, and readable way.
- Use tables only when they genuinely improve clarity.
- **Currency Format**: Use `\\$` instead of `$` for currency amounts (e.g., `\\$100`, `\\$1,000`) to avoid conflicts with inline math syntax.
- **Citation Format**:
- **In-Text**: Use the format `[ID]`, where `ID` is a **numeric identifier only** (digits 0–9), e.g. `[1]`, `[2]`.
- **References Section(if has any sources)**: At the very end, add "References" (or equivalent in {target_language}). Format: [ID] TITLE/SECTION_TITLE. <URL>/<FILENAME>.
- Do NOT mention tools, tool calls, or internal reasoning steps.
- Focus solely on delivering a professional, easy-to-read response that answers the user's original question.
## Original Question (for reference)
{task_description}"""
def _detect_language(text: str) -> str:
"""
Simple language detection based on character analysis.
Returns a language description suitable for the summarize prompt.
"""
# Count characters by script
chinese_chars = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
japanese_chars = sum(
1 for c in text if "\u3040" <= c <= "\u30ff" or "\u31f0" <= c <= "\u31ff"
)
korean_chars = sum(1 for c in text if "\uac00" <= c <= "\ud7af")
total_chars = len(text.replace(" ", ""))
if total_chars == 0:
return "English"
# Determine primary language
if chinese_chars / total_chars > 0.1:
return "Chinese (Simplified)"
elif japanese_chars / total_chars > 0.1:
return "Japanese"
elif korean_chars / total_chars > 0.1:
return "Korean"
else:
return "the same language as the user's question"
# ============================================================================
# Monkey Patching
# ============================================================================
_patched = False
def apply_prompt_patch():
"""
Apply monkey patches to customize prompts for demo mode.
Patches applied:
1. `generate_mcp_system_prompt` - Prepends custom identity prompt to system prompt
2. `process_input` - Removes the boxed format requirement from task descriptions
3. `generate_agent_summarize_prompt` - Uses user-friendly summary prompt
4. `format_final_summary_and_log` - Disables boxed format check to prevent retry
This function is idempotent - calling it multiple times has no additional effect.
"""
global _patched
if _patched:
return
_patch_system_prompt()
_patch_input_handler()
_patch_summarize_prompt()
_patch_output_formatter()
_patched = True
def _patch_system_prompt():
"""Patch system prompt generation to include custom identity."""
from src.llm.providers import anthropic_client, openai_client
from src.utils import prompt_utils
# Store original function
original_generate_mcp_system_prompt = prompt_utils.generate_mcp_system_prompt
def patched_generate_mcp_system_prompt(date, mcp_servers):
"""Patched version that prepends custom identity prompt."""
original_prompt = original_generate_mcp_system_prompt(date, mcp_servers)
return CUSTOM_IDENTITY_PROMPT + original_prompt
# Apply patches to all modules that import and use this function
prompt_utils.generate_mcp_system_prompt = patched_generate_mcp_system_prompt
openai_client.generate_mcp_system_prompt = patched_generate_mcp_system_prompt
anthropic_client.generate_mcp_system_prompt = patched_generate_mcp_system_prompt
def _patch_input_handler():
"""Patch input handler to remove boxed format requirement."""
from src.core import orchestrator
from src.io import input_handler
# Store original function
original_process_input = input_handler.process_input
def patched_process_input(task_description: str, task_file_name: str):
"""Patched version that removes boxed format requirement."""
result1, result2 = original_process_input(task_description, task_file_name)
# Remove the boxed format suffix from both results
result1 = result1.replace(BOXED_FORMAT_SUFFIX, "")
result2 = result2.replace(BOXED_FORMAT_SUFFIX, "")
return result1, result2
# Apply patch to input_handler module
input_handler.process_input = patched_process_input
# Also patch in orchestrator where it's imported
orchestrator.process_input = patched_process_input
def _patch_summarize_prompt():
"""Patch summarize prompt generation for better user experience."""
from src.core import answer_generator, orchestrator
from src.utils import prompt_utils
def patched_generate_agent_summarize_prompt(
task_description: str, agent_type: str = ""
) -> str:
"""
Patched version that uses user-friendly prompt for main agent.
For main agent in demo mode, uses a Markdown-friendly prompt instead of
the strict boxed format prompt used for benchmarks.
"""
if agent_type == "main":
# Detect language from task description
target_language = _detect_language(task_description)
return get_demo_summarize_prompt(target_language, task_description)
elif agent_type == "agent-browsing" or agent_type == "browsing-agent":
# Keep original behavior for sub-agents
summarize_prompt = (
"This is a direct instruction to you (the assistant), not the result of a tool call.\n\n"
"We are now ending this session, and your conversation history will be deleted. "
"You must NOT initiate any further tool use. This is your final opportunity to report "
"*all* of the information gathered during the session.\n\n"
"The original task is repeated here for reference:\n\n"
f'"{task_description}"\n\n'
"Summarize the above search and browsing history. Output the FINAL RESPONSE and detailed supporting information of the task given to you.\n\n"
"If you found any useful facts, data, quotes, or answers directly relevant to the original task, include them clearly and completely.\n"
"If you reached a conclusion or answer, include it as part of the response.\n"
"If the task could not be fully answered, do NOT make up any content. Instead, return all partially relevant findings, "
"Search results, quotes, and observations that might help a downstream agent solve the problem.\n"
"If partial, conflicting, or inconclusive information was found, clearly indicate this in your response.\n\n"
"Your final response should be a clear, complete, and structured report.\n"
"Organize the content into logical sections with appropriate headings.\n"
"Do NOT include any tool call instructions, speculative filler, or vague summaries.\n"
"Focus on factual, specific, and well-organized information."
)
return summarize_prompt.strip()
else:
raise ValueError(f"Unknown agent type: {agent_type}")
# Apply patches to all modules that import and use this function
prompt_utils.generate_agent_summarize_prompt = (
patched_generate_agent_summarize_prompt
)
orchestrator.generate_agent_summarize_prompt = (
patched_generate_agent_summarize_prompt
)
answer_generator.generate_agent_summarize_prompt = (
patched_generate_agent_summarize_prompt
)
def _patch_output_formatter():
"""
Patch output formatter to disable boxed format check.
In demo mode, we don't require \boxed{} format, so we patch the
format_final_summary_and_log method to always return a valid result
instead of FORMAT_ERROR_MESSAGE, which would trigger retry logic.
"""
from src.io import output_formatter
# Get the OutputFormatter class
OutputFormatter = output_formatter.OutputFormatter
def patched_format_final_summary_and_log(self, final_answer_text: str, client=None):
"""
Patched version that doesn't return FORMAT_ERROR_MESSAGE.
Instead of checking for \boxed{} content, we use the entire answer
(with thinking tags removed) as the result.
"""
summary_lines = []
summary_lines.append("\n" + "=" * 30 + " Final Answer " + "=" * 30)
summary_lines.append(final_answer_text)
# In demo mode, use the full answer text (minus thinking) as the result
# Remove <think>...</think> tags for the extracted result
boxed_result = re.sub(
r"<think>.*?</think>", "", final_answer_text, flags=re.DOTALL
).strip()
# If there's actual boxed content, extract it (for compatibility)
actual_boxed = self._extract_boxed_content(final_answer_text)
if actual_boxed:
boxed_result = actual_boxed
# Add extracted result section
summary_lines.append("\n" + "-" * 20 + " Extracted Result " + "-" * 20)
summary_lines.append(boxed_result if boxed_result else final_answer_text)
# Token usage statistics and cost estimation
if client and hasattr(client, "format_token_usage_summary"):
token_summary_lines, log_string = client.format_token_usage_summary()
summary_lines.extend(token_summary_lines)
else:
summary_lines.append("\n" + "-" * 20 + " Token Usage & Cost " + "-" * 20)
summary_lines.append("Token usage information not available.")
summary_lines.append("-" * (40 + len(" Token Usage & Cost ")))
log_string = "Token usage information not available."
# Return boxed_result (never FORMAT_ERROR_MESSAGE in demo mode)
# This ensures no retry is triggered
return (
"\n".join(summary_lines),
boxed_result or "Demo mode - no boxed format required",
log_string,
)
# Apply patch
OutputFormatter.format_final_summary_and_log = patched_format_final_summary_and_log
def get_custom_identity_prompt() -> str:
"""Return the custom identity prompt string."""
return CUSTOM_IDENTITY_PROMPT
================================================
FILE: apps/gradio-demo/pyproject.toml
================================================
[project]
name = "gradio-demo"
version = "0.1.0"
description = "Gradio Demo"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pydantic>=2.10.0",
"python-dotenv>=1.0.0",
"hydra-core>=1.3.0",
"miroflow-agent",
"aiohttp>=3.12.15",
"gradio>=5.42.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["./"]
[tool.uv.sources]
miroflow-agent = { path = "../miroflow-agent", editable = true }
[dependency-groups]
dev = [
"pytest>=8.4.1",
"pytest-asyncio>=1.0.0",
"httpx>=0.28.1",
]
================================================
FILE: apps/gradio-demo/utils.py
================================================
import re
def contains_chinese(text):
"""
Detect if a string contains Chinese characters or Chinese punctuation
Args:
text (str): The string to detect
Returns:
bool: True if contains Chinese characters or punctuation, False otherwise
"""
# Chinese character Unicode ranges:
# \u4e00-\u9fff: CJK Unified Ideographs
# \u3400-\u4dbf: CJK Extension A
# \uf900-\ufaff: CJK Compatibility Ideographs
# \u3000-\u303f: CJK Symbols and Punctuation
# \uff00-\uffef: Fullwidth ASCII, Fullwidth punctuation
chinese_pattern = re.compile(
r"[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3000-\u303f\uff00-\uffef]"
)
return bool(chinese_pattern.search(text))
def replace_chinese_punctuation(text):
# Handle single-character replacements with translate
punctuation_map = str.maketrans(
{
",": ",",
"。": ".",
"!": "!",
"?": "?",
";": ";",
":": ":",
"“": '"',
"”": '"',
"‘": "'",
"’": "'",
"(": "(",
")": ")",
"【": "[",
"】": "]",
"《": "<",
"》": ">",
"、": ",",
"—": "-",
}
)
# First, replace multi-character punctuation
text = text.replace("……", "...")
# Then apply single-character replacements
return text.translate(punctuation_map)
================================================
FILE: apps/lobehub-compatibility/MiroThinkerToolParser.py
================================================
"""
Tool parser plugin for vLLM for MiroThinker MCP format to compatible with the tool calling interface of openai.
MCP format:
<use_mcp_tool>
<server_name>server name</server_name>
<tool_name>tool name</tool_name>
<arguments>
{...}
</arguments>
</use_mcp_tool>
"""
import json
from collections.abc import Sequence
import json_repair
import regex as re
from vllm.entrypoints.chat_utils import make_tool_call_id
from vllm.entrypoints.openai.protocol import (
ChatCompletionRequest,
DeltaFunctionCall,
DeltaMessage,
DeltaToolCall,
ExtractedToolCallInformation,
FunctionCall,
ToolCall,
)
from vllm.entrypoints.openai.tool_parsers.abstract_tool_parser import (
ToolParser,
ToolParserManager,
)
from vllm.logger import init_logger
logger = init_logger(__name__)
class MirothinkerToolParser(ToolParser):
def __init__(self, tokenizer):
super().__init__(tokenizer)
# State tracking for streaming
self.current_tool_name_sent: bool = False
self.prev_tool_call_arr: list[dict] = []
self.current_tool_id: int = -1
self.streamed_args_for_tool: list[str] = []
self.buffer: str = "" # Buffer for potential tool call tags
self._resolved_tool_name_cache: dict[tuple[str, str], str] = {}
# Correctness-first streaming state (incremental state machine)
self._stream_mode: str = "text" # "text" | "tool"
self._text_token_prefix: str = "" # possible prefix of <use_mcp_tool>
self._tool_end_token_prefix: str = "" # possible prefix of </use_mcp_tool>
self._tool_block_buffer: str = (
"" # accumulates between <use_mcp_tool> and </use_mcp_tool>
)
self._stream_tool_call_ids: list[str] = []
# Token definitions
self.tool_call_start_token: str = "<use_mcp_tool>"
self.tool_call_end_token: str = "</use_mcp_tool>"
# Regex patterns
self.tool_call_regex = re.compile(
r"<use_mcp_tool>\s*"
r"<server_name>(.*?)</server_name>\s*"
r"<tool_name>(.*?)</tool_name>\s*"
r"<arguments>\s*(.*?)\s*</arguments>\s*"
r"</use_mcp_tool>",
re.DOTALL,
)
# For streaming partial tool calls
# IMPORTANT: Use GREEDY matching (.*) for arguments to capture all content
# in streaming mode. We'll clean up </arguments> tag in the code if present.
# The outer ()? makes the whole <arguments> section optional
# The inner (.*) will match empty string if <arguments> exists but has no content yet
self.partial_tool_regex = re.compile(
r"<use_mcp_tool>\s*"
r"(?:<server_name>(.*?)</server_name>\s*)?"
r"(?:<tool_name>(.*?)</tool_name>\s*)?"
r"(?:<arguments>(\s*.*))?", # Move \s* inside capture group so empty match returns ""
re.DOTALL,
)
# For correctness-first parsing on COMPLETE tool blocks only
self._complete_tool_block_regex = re.compile(
r"<use_mcp_tool>\s*"
r"(?:<server_name>(.*?)</server_name>\s*)?"
r"(?:<tool_name>(.*?)</tool_name>\s*)?"
r"(?:<arguments>\s*(.*?)\s*(?:</arguments>\s*)?)?"
r"</use_mcp_tool>",
re.DOTALL,
)
def _resolve_tool_name(
self, server_name: str, tool_name: str, request: ChatCompletionRequest
) -> str:
"""
Resolve the actual tool name by combining server_name and tool_name
if server_name is not 'default'.
"""
if not server_name or server_name == "default":
return tool_name
if not request or not request.tools:
return tool_name
cache_key = (server_name, tool_name)
cached = self._resolved_tool_name_cache.get(cache_key)
if cached:
return cached
# Filter tools that contain server_name
candidates = []
for tool in request.tools:
if hasattr(tool, "function") and hasattr(tool.function, "name"):
gitextract_qqy1lifh/
├── .github/
│ └── workflows/
│ └── run-ruff.yml
├── .gitignore
├── LICENSE
├── README.md
├── apps/
│ ├── collect-trace/
│ │ ├── README.md
│ │ ├── pyproject.toml
│ │ ├── scripts/
│ │ │ ├── collect_trace_claude37.sh
│ │ │ ├── collect_trace_gpt41.sh
│ │ │ ├── collect_trace_gpt5.sh
│ │ │ └── collect_trace_qwen3.sh
│ │ └── utils/
│ │ ├── converters/
│ │ │ ├── __init__.py
│ │ │ ├── convert_non_oai_to_chatml.py
│ │ │ ├── convert_oai_to_chatml.py
│ │ │ ├── convert_to_chatml_auto_batch.py
│ │ │ ├── example_usage.py
│ │ │ └── system_prompts.py
│ │ ├── merge_chatml_msgs_to_one_json.py
│ │ └── process_logs.py
│ ├── gradio-demo/
│ │ ├── README.md
│ │ ├── main.py
│ │ ├── prompt_patch.py
│ │ ├── pyproject.toml
│ │ └── utils.py
│ ├── lobehub-compatibility/
│ │ ├── MiroThinkerToolParser.py
│ │ ├── README.md
│ │ ├── chat_template.jinja
│ │ ├── requirements.txt
│ │ ├── test_tool_parser.py
│ │ └── unit_test.py
│ ├── miroflow-agent/
│ │ ├── README.md
│ │ ├── benchmarks/
│ │ │ ├── __init__.py
│ │ │ ├── check_progress/
│ │ │ │ ├── check_progress_aime2025.py
│ │ │ │ ├── check_progress_browsecomp.py
│ │ │ │ ├── check_progress_browsecomp_zh.py
│ │ │ │ ├── check_progress_deepsearchqa.py
│ │ │ │ ├── check_progress_frames.py
│ │ │ │ ├── check_progress_gaia-validation-text-103.py
│ │ │ │ ├── check_progress_gaia-validation.py
│ │ │ │ ├── check_progress_hle-text-2158.py
│ │ │ │ ├── check_progress_hle-text-500.py
│ │ │ │ ├── check_progress_hle.py
│ │ │ │ ├── check_progress_seal-0.py
│ │ │ │ ├── check_progress_webwalkerqa.py
│ │ │ │ ├── check_progress_xbench_deepsearch.py
│ │ │ │ └── common.py
│ │ │ ├── common_benchmark.py
│ │ │ ├── evaluators/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── calculate_average_score.py
│ │ │ │ ├── eval_utils.py
│ │ │ │ └── extract_futurex_results.py
│ │ │ └── subset_extraction/
│ │ │ ├── gaia-text-103-grader.py
│ │ │ └── gaia-to-text-103-mover.py
│ │ ├── conf/
│ │ │ ├── __init__.py
│ │ │ ├── agent/
│ │ │ │ ├── default.yaml
│ │ │ │ ├── demo.yaml
│ │ │ │ ├── mirothinker_1.7_keep5_max200.yaml
│ │ │ │ ├── mirothinker_1.7_keep5_max300.yaml
│ │ │ │ ├── mirothinker_v1.0.yaml
│ │ │ │ ├── mirothinker_v1.0_keep5.yaml
│ │ │ │ ├── mirothinker_v1.5.yaml
│ │ │ │ ├── mirothinker_v1.5_keep5_max200.yaml
│ │ │ │ ├── mirothinker_v1.5_keep5_max400.yaml
│ │ │ │ ├── multi_agent.yaml
│ │ │ │ ├── multi_agent_os.yaml
│ │ │ │ ├── single_agent.yaml
│ │ │ │ └── single_agent_keep5.yaml
│ │ │ ├── benchmark/
│ │ │ │ ├── aime2025.yaml
│ │ │ │ ├── browsecomp.yaml
│ │ │ │ ├── browsecomp_zh.yaml
│ │ │ │ ├── collect_trace.yaml
│ │ │ │ ├── debug.yaml
│ │ │ │ ├── deepsearchqa.yaml
│ │ │ │ ├── default.yaml
│ │ │ │ ├── frames.yaml
│ │ │ │ ├── futurex.yaml
│ │ │ │ ├── gaia-validation-text-103.yaml
│ │ │ │ ├── gaia-validation.yaml
│ │ │ │ ├── hle-text-2158.yaml
│ │ │ │ ├── hle-text-500.yaml
│ │ │ │ ├── hle.yaml
│ │ │ │ ├── seal-0.yaml
│ │ │ │ ├── webwalkerqa.yaml
│ │ │ │ └── xbench_deepsearch.yaml
│ │ │ ├── config.yaml
│ │ │ └── llm/
│ │ │ ├── claude-3-7.yaml
│ │ │ ├── default.yaml
│ │ │ ├── gpt-5.yaml
│ │ │ └── qwen-3.yaml
│ │ ├── main.py
│ │ ├── pyproject.toml
│ │ ├── scripts/
│ │ │ ├── run_evaluate_multiple_runs_aime2025.sh
│ │ │ ├── run_evaluate_multiple_runs_browsecomp.sh
│ │ │ ├── run_evaluate_multiple_runs_browsecomp_zh.sh
│ │ │ ├── run_evaluate_multiple_runs_debug.sh
│ │ │ ├── run_evaluate_multiple_runs_deepsearchqa.sh
│ │ │ ├── run_evaluate_multiple_runs_frames.sh
│ │ │ ├── run_evaluate_multiple_runs_futurex.sh
│ │ │ ├── run_evaluate_multiple_runs_gaia-validation-text-103.sh
│ │ │ ├── run_evaluate_multiple_runs_gaia-validation.sh
│ │ │ ├── run_evaluate_multiple_runs_hle-text-2158.sh
│ │ │ ├── run_evaluate_multiple_runs_hle-text-500.sh
│ │ │ ├── run_evaluate_multiple_runs_hle.sh
│ │ │ ├── run_evaluate_multiple_runs_seal-0.sh
│ │ │ ├── run_evaluate_multiple_runs_webwalkerqa.sh
│ │ │ └── run_evaluate_multiple_runs_xbench_deepsearch.sh
│ │ └── src/
│ │ ├── __init__.py
│ │ ├── config/
│ │ │ ├── __init__.py
│ │ │ └── settings.py
│ │ ├── core/
│ │ │ ├── __init__.py
│ │ │ ├── answer_generator.py
│ │ │ ├── orchestrator.py
│ │ │ ├── pipeline.py
│ │ │ ├── stream_handler.py
│ │ │ └── tool_executor.py
│ │ ├── io/
│ │ │ ├── __init__.py
│ │ │ ├── input_handler.py
│ │ │ └── output_formatter.py
│ │ ├── llm/
│ │ │ ├── __init__.py
│ │ │ ├── base_client.py
│ │ │ ├── factory.py
│ │ │ ├── providers/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── anthropic_client.py
│ │ │ │ └── openai_client.py
│ │ │ └── util.py
│ │ ├── logging/
│ │ │ ├── __init__.py
│ │ │ ├── summary_time_cost.py
│ │ │ └── task_logger.py
│ │ └── utils/
│ │ ├── __init__.py
│ │ ├── parsing_utils.py
│ │ ├── prompt_utils.py
│ │ └── wrapper_utils.py
│ └── visualize-trace/
│ ├── .python-version
│ ├── README.md
│ ├── app.py
│ ├── pyproject.toml
│ ├── requirements.txt
│ ├── run.py
│ ├── static/
│ │ ├── css/
│ │ │ └── style.css
│ │ └── js/
│ │ └── script.js
│ ├── templates/
│ │ └── index.html
│ └── trace_analyzer.py
├── assets/
│ ├── LOCAL-TOOL-DEPLOYMENT.md
│ ├── QA.md
│ └── qwen3_nonthinking.jinja
├── justfile
└── libs/
└── miroflow-tools/
├── README.md
├── pyproject.toml
└── src/
├── __init__.py
└── miroflow_tools/
├── __init__.py
├── dev_mcp_servers/
│ ├── jina_scrape_llm_summary.py
│ ├── search_and_scrape_webpage.py
│ ├── stateless_python_server.py
│ └── task_planner.py
├── manager.py
└── mcp_servers/
├── __init__.py
├── audio_mcp_server.py
├── audio_mcp_server_os.py
├── browser_session.py
├── python_mcp_server.py
├── reading_mcp_server.py
├── reasoning_mcp_server.py
├── reasoning_mcp_server_os.py
├── searching_google_mcp_server.py
├── searching_sogou_mcp_server.py
├── serper_mcp_server.py
├── utils/
│ ├── __init__.py
│ └── url_unquote.py
├── vision_mcp_server.py
└── vision_mcp_server_os.py
SYMBOL INDEX (545 symbols across 73 files)
FILE: apps/collect-trace/utils/converters/convert_non_oai_to_chatml.py
function convert_to_json_chatml (line 10) | def convert_to_json_chatml(messages: List[Dict[str, Any]]) -> List[Dict[...
function extract_and_save_chat_history (line 40) | def extract_and_save_chat_history(
function main (line 100) | def main():
FILE: apps/collect-trace/utils/converters/convert_oai_to_chatml.py
function oai_tool_message_to_chat_message (line 23) | def oai_tool_message_to_chat_message(oai_messages, agent_type, tool_defi...
function extract_message_history_from_log (line 180) | def extract_message_history_from_log(
function save_chatml_to_files (line 230) | def save_chatml_to_files(
function extract_step_message (line 266) | def extract_step_message(data, target_step_name):
function process_log_file (line 288) | def process_log_file(log_file_path: str, output_dir: str = "extracted_ch...
function main (line 339) | def main():
FILE: apps/collect-trace/utils/converters/convert_to_chatml_auto_batch.py
function get_llm_provider (line 11) | def get_llm_provider(json_file_path: str) -> str:
function determine_conversion_method (line 36) | def determine_conversion_method(provider: str) -> str:
function get_script_paths (line 52) | def get_script_paths() -> tuple:
function process_single_file (line 77) | def process_single_file(json_file_path: str, output_dir: str) -> bool:
function find_json_files (line 129) | def find_json_files(input_paths: List[str]) -> List[str]:
function batch_process_files (line 164) | def batch_process_files(input_paths: List[str], output_dir: str) -> Dict...
function show_help (line 200) | def show_help():
function main (line 239) | def main():
FILE: apps/collect-trace/utils/converters/example_usage.py
function example_1_basic_conversion (line 19) | def example_1_basic_conversion():
FILE: apps/collect-trace/utils/merge_chatml_msgs_to_one_json.py
function merge_json_files (line 10) | def merge_json_files(input_dir, type="main"):
function main (line 42) | def main():
FILE: apps/collect-trace/utils/process_logs.py
function get_successful_log_paths (line 10) | def get_successful_log_paths(jsonl_file_path: str) -> list:
FILE: apps/gradio-demo/main.py
function load_miroflow_config (line 39) | def load_miroflow_config(config_overrides: Optional[dict] = None) -> Dic...
function _ensure_preloaded (line 135) | def _ensure_preloaded():
class ThreadSafeAsyncQueue (line 171) | class ThreadSafeAsyncQueue:
method __init__ (line 174) | def __init__(self):
method set_loop (line 179) | def set_loop(self, loop):
method put (line 182) | async def put(self, item):
method put_nowait_threadsafe (line 188) | def put_nowait_threadsafe(self, item):
method get (line 195) | async def get(self):
method close (line 198) | def close(self):
function filter_google_search_organic (line 202) | def filter_google_search_organic(organic: List[dict]) -> List[dict]:
function is_scrape_error (line 217) | def is_scrape_error(result: str) -> bool:
function filter_message (line 228) | def filter_message(message: dict) -> dict:
function stream_events_optimized (line 261) | async def stream_events_optimized(
function _init_render_state (line 414) | def _init_render_state():
function _format_think_content (line 423) | def _format_think_content(text: str) -> str:
function _append_show_text (line 447) | def _append_show_text(tool_entry: dict, delta: str):
function _is_empty_payload (line 457) | def _is_empty_payload(value) -> bool:
function _format_search_results (line 468) | def _format_search_results(tool_input: dict, tool_output: dict) -> str:
function _format_sogou_search_results (line 530) | def _format_sogou_search_results(tool_input: dict, tool_output: dict) ->...
function _format_scrape_results (line 590) | def _format_scrape_results(tool_input: dict, tool_output: dict) -> str:
function _render_markdown (line 627) | def _render_markdown(state: dict) -> str:
function _update_state_with_event (line 748) | def _update_state_with_event(state: dict, message: dict):
function _set_cancel_flag (line 848) | def _set_cancel_flag(task_id: str):
function _reset_cancel_flag (line 853) | def _reset_cancel_flag(task_id: str):
function _disconnect_check_for_task (line 858) | async def _disconnect_check_for_task(task_id: str):
function _spinner_markup (line 863) | def _spinner_markup(running: bool) -> str:
function gradio_run (line 874) | async def gradio_run(query: str, ui_state: Optional[dict]):
function stop_current (line 917) | def stop_current(ui_state: Optional[dict]):
function build_demo (line 928) | def build_demo():
FILE: apps/gradio-demo/prompt_patch.py
function get_demo_summarize_prompt (line 46) | def get_demo_summarize_prompt(target_language: str, task_description: st...
function _detect_language (line 82) | def _detect_language(text: str) -> str:
function apply_prompt_patch (line 117) | def apply_prompt_patch():
function _patch_system_prompt (line 142) | def _patch_system_prompt():
function _patch_input_handler (line 161) | def _patch_input_handler():
function _patch_summarize_prompt (line 183) | def _patch_summarize_prompt():
function _patch_output_formatter (line 237) | def _patch_output_formatter():
function get_custom_identity_prompt (line 298) | def get_custom_identity_prompt() -> str:
FILE: apps/gradio-demo/utils.py
function contains_chinese (line 4) | def contains_chinese(text):
function replace_chinese_punctuation (line 26) | def replace_chinese_punctuation(text):
FILE: apps/lobehub-compatibility/MiroThinkerToolParser.py
class MirothinkerToolParser (line 37) | class MirothinkerToolParser(ToolParser):
method __init__ (line 38) | def __init__(self, tokenizer):
method _resolve_tool_name (line 95) | def _resolve_tool_name(
method adjust_request (line 138) | def adjust_request(self, request: ChatCompletionRequest) -> ChatComple...
method _ensure_tool_id_valid (line 145) | def _ensure_tool_id_valid(self, tool_id: int) -> bool:
method extract_tool_calls (line 158) | def extract_tool_calls(
method extract_tool_calls_streaming (line 252) | def extract_tool_calls_streaming(
FILE: apps/lobehub-compatibility/test_tool_parser.py
function test_tool_call_regex (line 51) | def test_tool_call_regex():
function test_partial_tool_regex (line 153) | def test_partial_tool_regex():
function test_complete_tool_block_regex (line 192) | def test_complete_tool_block_regex():
function test_edge_cases (line 232) | def test_edge_cases():
function check_unused_code (line 291) | def check_unused_code():
function main (line 337) | def main():
FILE: apps/lobehub-compatibility/unit_test.py
function strftime_now (line 19) | def strftime_now(format_str: str) -> str:
function template (line 25) | def template():
function today_date (line 37) | def today_date():
class TestBasicMessageFormatting (line 47) | class TestBasicMessageFormatting:
method test_user_message_format (line 50) | def test_user_message_format(self, template):
method test_system_message_format (line 57) | def test_system_message_format(self, template):
method test_assistant_message_format (line 67) | def test_assistant_message_format(self, template):
method test_add_generation_prompt (line 81) | def test_add_generation_prompt(self, template):
method test_multi_turn_conversation (line 88) | def test_multi_turn_conversation(self, template):
class TestThinkingContent (line 112) | class TestThinkingContent:
method test_reasoning_content_field (line 115) | def test_reasoning_content_field(self, template):
method test_think_tags_in_content (line 130) | def test_think_tags_in_content(self, template):
method test_think_preserved_in_history (line 144) | def test_think_preserved_in_history(self, template):
method test_enable_thinking_false (line 160) | def test_enable_thinking_false(self, template):
method test_enable_thinking_true (line 169) | def test_enable_thinking_true(self, template):
class TestToolDefinitions (line 185) | class TestToolDefinitions:
method test_tools_trigger_system_prompt (line 188) | def test_tools_trigger_system_prompt(self, template, today_date):
method test_tool_name_format (line 209) | def test_tool_name_format(self, template):
method test_tool_server_name (line 228) | def test_tool_server_name(self, template):
method test_tool_description_indentation (line 247) | def test_tool_description_indentation(self, template):
method test_tool_args_auto_generated (line 266) | def test_tool_args_auto_generated(self, template):
method test_tool_args_not_duplicated (line 293) | def test_tool_args_not_duplicated(self, template):
method test_tool_json_schema_included (line 318) | def test_tool_json_schema_included(self, template):
method test_tool_without_function_wrapper (line 341) | def test_tool_without_function_wrapper(self, template):
method test_tool_none_description (line 357) | def test_tool_none_description(self, template):
method test_tool_empty_description (line 376) | def test_tool_empty_description(self, template):
method test_system_message_prepended_with_tools (line 394) | def test_system_message_prepended_with_tools(self, template):
class TestToolCalls (line 425) | class TestToolCalls:
method test_tool_call_format (line 428) | def test_tool_call_format(self, template):
method test_tool_call_no_content (line 469) | def test_tool_call_no_content(self, template):
method test_multiple_tool_calls (line 504) | def test_multiple_tool_calls(self, template):
method test_tool_call_arguments_dict (line 551) | def test_tool_call_arguments_dict(self, template):
class TestToolResponses (line 593) | class TestToolResponses:
method test_tool_response_in_user_message (line 596) | def test_tool_response_in_user_message(self, template):
method test_multiple_tool_responses_merged (line 633) | def test_multiple_tool_responses_merged(self, template):
method test_tool_response_no_wrapper_tags (line 676) | def test_tool_response_no_wrapper_tags(self, template):
class TestEdgeCases (line 715) | class TestEdgeCases:
method test_only_system_message (line 718) | def test_only_system_message(self, template):
method test_assistant_empty_content (line 724) | def test_assistant_empty_content(self, template):
method test_unicode_content (line 734) | def test_unicode_content(self, template):
method test_special_characters_in_content (line 744) | def test_special_characters_in_content(self, template):
method test_newlines_preserved (line 752) | def test_newlines_preserved(self, template):
class TestCompleteFlow (line 766) | class TestCompleteFlow:
method test_full_tool_use_flow (line 769) | def test_full_tool_use_flow(self, template, today_date):
method test_reasoning_with_tool_use (line 824) | def test_reasoning_with_tool_use(self, template):
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_aime2025.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_browsecomp.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_browsecomp_zh.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_deepsearchqa.py
function extract_eval_details_from_log (line 21) | def extract_eval_details_from_log(log_file: str) -> dict:
function calculate_deepsearchqa_metrics_from_logs (line 92) | def calculate_deepsearchqa_metrics_from_logs(base_path: str) -> dict:
function calculate_deepsearchqa_metrics (line 173) | def calculate_deepsearchqa_metrics(results_file: str) -> dict:
function show_deepsearchqa_metrics (line 266) | def show_deepsearchqa_metrics(base_path: str):
function parse_args (line 337) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_frames.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_gaia-validation-text-103.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_gaia-validation.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_hle-text-2158.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_hle-text-500.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_hle.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_seal-0.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_webwalkerqa.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/check_progress_xbench_deepsearch.py
function parse_args (line 18) | def parse_args():
FILE: apps/miroflow-agent/benchmarks/check_progress/common.py
function create_progress_bar (line 35) | def create_progress_bar(percentage: float, width: int = PROGRESS_BAR_WID...
function find_earliest_start_time (line 54) | def find_earliest_start_time(completed_files: List[str]) -> Optional[dat...
function find_latest_end_time (line 81) | def find_latest_end_time(completed_files: List[str]) -> Optional[datetime]:
function calculate_mean_and_std (line 109) | def calculate_mean_and_std(values: List[float]) -> Tuple[float, float]:
function estimate_completion_time (line 126) | def estimate_completion_time(
class TaskStats (line 166) | class TaskStats:
method __post_init__ (line 185) | def __post_init__(self):
method judge_accuracy (line 190) | def judge_accuracy(self) -> float:
method completion_rate (line 197) | def completion_rate(self) -> float:
method average_turns (line 202) | def average_turns(self) -> float:
class GAIATaskStats (line 212) | class GAIATaskStats(TaskStats):
method level1_accuracy (line 224) | def level1_accuracy(self) -> float:
method level2_accuracy (line 233) | def level2_accuracy(self) -> float:
method level3_accuracy (line 242) | def level3_accuracy(self) -> float:
class SummaryStats (line 252) | class SummaryStats:
method total_judge_accuracy (line 263) | def total_judge_accuracy(self) -> float:
method average_run_accuracy (line 271) | def average_run_accuracy(
method total_completion_rate (line 294) | def total_completion_rate(self) -> float:
class GAIASummaryStats (line 304) | class GAIASummaryStats(SummaryStats):
method level1_accuracy (line 316) | def level1_accuracy(self) -> float:
method level2_accuracy (line 325) | def level2_accuracy(self) -> float:
method level3_accuracy (line 334) | def level3_accuracy(self) -> float:
class ProgressChecker (line 343) | class ProgressChecker:
method __init__ (line 346) | def __init__(self, target_path: str, task_per_run: int, data_path: str):
method _load_benchmark_data (line 354) | def _load_benchmark_data(self, data_path) -> None:
method find_run_directories (line 365) | def find_run_directories(self) -> List[str]:
method _extract_run_number (line 395) | def _extract_run_number(self, path: str) -> int:
method _extract_task_id (line 403) | def _extract_task_id(self, filename: str, task_id_pattern: str) -> Opt...
method _get_latest_task_files (line 408) | def _get_latest_task_files(self, run_dir: str, task_id_pattern: str) -...
method _is_task_completed (line 464) | def _is_task_completed(self, data: Dict) -> bool:
method _is_judge_correct (line 477) | def _is_judge_correct(self, judge_result) -> bool:
method _calculate_turns (line 496) | def _calculate_turns(self, data: Dict) -> int:
method analyze_run_directory (line 518) | def analyze_run_directory(
method run_analysis (line 590) | def run_analysis(
method _calculate_pass_at_n (line 661) | def _calculate_pass_at_n(
method _display_summary (line 683) | def _display_summary(
method _save_analysis_log (line 797) | def _save_analysis_log(
class GAIAProgressChecker (line 950) | class GAIAProgressChecker(ProgressChecker):
method __init__ (line 955) | def __init__(self, target_path: str, task_per_run: int, data_path: str):
method _load_benchmark_data (line 965) | def _load_benchmark_data(self, data_path) -> None:
method _update_difficulty_stats (line 994) | def _update_difficulty_stats(
method analyze_run_directory (line 1014) | def analyze_run_directory(
method run_analysis (line 1077) | def run_analysis(
method _update_summary_stats (line 1143) | def _update_summary_stats(
method _display_summary (line 1162) | def _display_summary(
FILE: apps/miroflow-agent/benchmarks/common_benchmark.py
function _task_worker (line 34) | def _task_worker(task_dict, cfg_dict, evaluator_kwargs):
class BenchmarkTask (line 87) | class BenchmarkTask:
class BenchmarkResult (line 100) | class BenchmarkResult:
class BenchmarkEvaluator (line 120) | class BenchmarkEvaluator(ABC):
method __init__ (line 123) | def __init__(self, data_dir: str, benchmark_name: str, cfg: DictConfig):
method get_log_dir (line 158) | def get_log_dir(self) -> Path:
method run_single_task (line 162) | async def run_single_task(self, task: BenchmarkTask) -> BenchmarkResult:
method _run_single_task_sync (line 553) | def _run_single_task_sync(self, task: BenchmarkTask) -> BenchmarkResult:
method run_parallel_inference (line 571) | def run_parallel_inference(
method save_results (line 720) | def save_results(self, output_file: str) -> str:
method evaluate_accuracy (line 732) | def evaluate_accuracy(self) -> float:
method _update_log_file_with_evaluation (line 771) | def _update_log_file_with_evaluation(
class GenericEvaluator (line 806) | class GenericEvaluator(BenchmarkEvaluator):
method __init__ (line 809) | def __init__(
method load_tasks (line 843) | def load_tasks(self, limit: Optional[int] = None) -> List[BenchmarkTask]:
method prepare_task_description (line 903) | def prepare_task_description(
class CommonBenchmark (line 929) | class CommonBenchmark:
method __init__ (line 932) | def __init__(self, cfg: DictConfig):
method run_evaluation (line 963) | def run_evaluation(self) -> float:
function run_benchmark (line 1013) | def run_benchmark(cfg: DictConfig) -> None:
FILE: apps/miroflow-agent/benchmarks/evaluators/calculate_average_score.py
function detect_pass_at_k (line 12) | def detect_pass_at_k(results_dir: str) -> tuple:
function calculate_average_scores (line 46) | def calculate_average_scores(results_dir: str) -> dict:
function print_results (line 91) | def print_results(stats: dict):
function main (line 108) | def main():
FILE: apps/miroflow-agent/benchmarks/evaluators/eval_utils.py
function verify_answer_simpleqa (line 111) | async def verify_answer_simpleqa(
class HLEExtractedAnswer (line 165) | class HLEExtractedAnswer(BaseModel):
function verify_answer_hle (line 173) | async def verify_answer_hle(question: str, target: str, predicted_answer...
function verify_answer_gaia (line 224) | async def verify_answer_gaia(question: str, target: str, predicted_answe...
function verify_answer_gaia_validation_text_103 (line 361) | async def verify_answer_gaia_validation_text_103(
function verify_answer_browsecomp (line 544) | async def verify_answer_browsecomp(
function verify_answer_browsecomp_zh (line 584) | async def verify_answer_browsecomp_zh(
function verify_answer_xbench_deepsearch (line 650) | async def verify_answer_xbench_deepsearch(
function verify_answer_deepsearchqa (line 781) | async def verify_answer_deepsearchqa(
function _verify_answer_for_datasets_core (line 912) | async def _verify_answer_for_datasets_core(
function verify_answer_for_datasets (line 1006) | async def verify_answer_for_datasets(
FILE: apps/miroflow-agent/benchmarks/evaluators/extract_futurex_results.py
function majority_vote (line 11) | def majority_vote(
function discover_runs (line 44) | def discover_runs(results_dir: str) -> List[str]:
function parse_args (line 60) | def parse_args() -> argparse.Namespace:
function main (line 77) | def main() -> None:
FILE: apps/miroflow-agent/benchmarks/subset_extraction/gaia-text-103-grader.py
class GradingResult (line 32) | class GradingResult:
class GAIAText103Grader (line 47) | class GAIAText103Grader:
method __init__ (line 50) | def __init__(self, extraction_dir: str):
method find_task_files (line 66) | def find_task_files(self) -> List[Path]:
method extract_task_info (line 78) | def extract_task_info(self, task_file: Path) -> Optional[Dict]:
method grade_single_task (line 119) | async def grade_single_task(self, task_info: Dict) -> GradingResult:
method grade_all_tasks (line 158) | async def grade_all_tasks(self, max_concurrent: int = 5) -> List[Gradi...
method update_original_files (line 204) | def update_original_files(self):
method print_summary (line 233) | def print_summary(self):
function main (line 245) | async def main():
FILE: apps/miroflow-agent/benchmarks/subset_extraction/gaia-to-text-103-mover.py
class GAIAtoText103Copier (line 21) | class GAIAtoText103Copier:
method __init__ (line 24) | def __init__(self, gaia_text_103_data_path: str, output_dir: str):
method _load_gaia_text_103_tasks (line 40) | def _load_gaia_text_103_tasks(self):
method copy_gaia_text_103_tasks (line 59) | def copy_gaia_text_103_tasks(self, gaia_logs_dir: str) -> int:
method print_summary (line 119) | def print_summary(self):
function main (line 129) | def main():
FILE: apps/miroflow-agent/main.py
function amain (line 20) | async def amain(cfg: DictConfig) -> None:
function main (line 49) | def main(cfg: DictConfig) -> None:
FILE: apps/miroflow-agent/src/config/settings.py
function create_mcp_server_parameters (line 69) | def create_mcp_server_parameters(cfg: DictConfig, agent_cfg: DictConfig):
function expose_sub_agents_as_tools (line 383) | def expose_sub_agents_as_tools(sub_agents_cfg: DictConfig):
function get_env_info (line 422) | def get_env_info(cfg: DictConfig) -> dict:
FILE: apps/miroflow-agent/src/core/answer_generator.py
class AnswerGenerator (line 38) | class AnswerGenerator:
method __init__ (line 46) | def __init__(
method handle_llm_call (line 80) | async def handle_llm_call(
method generate_failure_summary (line 170) | async def generate_failure_summary(
method generate_final_answer_with_retries (line 256) | async def generate_final_answer_with_retries(
method handle_no_context_management_fallback (line 355) | def handle_no_context_management_fallback(
method handle_context_management_no_fallback (line 409) | def handle_context_management_no_fallback(
method generate_and_finalize_answer (line 462) | async def generate_and_finalize_answer(
FILE: apps/miroflow-agent/src/core/orchestrator.py
function _list_tools (line 56) | def _list_tools(sub_agent_tool_managers: Dict[str, ToolManager]):
class Orchestrator (line 86) | class Orchestrator:
method __init__ (line 94) | def __init__(
method _save_message_history (line 170) | def _save_message_history(
method _handle_response_format_issues (line 180) | async def _handle_response_format_issues(
method _check_duplicate_query (line 257) | async def _check_duplicate_query(
method _record_query (line 318) | async def _record_query(self, cache_name: str, tool_name: str, argumen...
method run_sub_agent (line 327) | async def run_sub_agent(
method run_main_agent (line 736) | async def run_main_agent(
FILE: apps/miroflow-agent/src/core/pipeline.py
function execute_task_pipeline (line 35) | async def execute_task_pipeline(
function create_pipeline_components (line 180) | def create_pipeline_components(cfg: DictConfig):
FILE: apps/miroflow-agent/src/core/stream_handler.py
class StreamHandler (line 18) | class StreamHandler:
method __init__ (line 26) | def __init__(self, stream_queue: Optional[Any] = None):
method update (line 36) | async def update(self, event_type: str, data: dict):
method start_workflow (line 54) | async def start_workflow(self, user_input: str) -> str:
method end_workflow (line 79) | async def end_workflow(self, workflow_id: str):
method show_error (line 93) | async def show_error(self, error: str):
method start_agent (line 107) | async def start_agent(self, agent_name: str, display_name: str = None)...
method end_agent (line 129) | async def end_agent(self, agent_name: str, agent_id: str):
method start_llm (line 145) | async def start_llm(self, agent_name: str, display_name: str = None):
method end_llm (line 161) | async def end_llm(self, agent_name: str):
method message (line 175) | async def message(self, message_id: str, delta_content: str):
method tool_call (line 193) | async def tool_call(
FILE: apps/miroflow-agent/src/core/tool_executor.py
class ToolExecutor (line 30) | class ToolExecutor:
method __init__ (line 38) | def __init__(
method fix_tool_call_arguments (line 68) | def fix_tool_call_arguments(self, tool_name: str, arguments: dict) -> ...
method get_query_str_from_tool_call (line 102) | def get_query_str_from_tool_call(
method is_duplicate_query (line 136) | def is_duplicate_query(self, cache_name: str, query_str: str) -> Tuple...
method record_query (line 151) | def record_query(self, cache_name: str, query_str: str):
method is_google_search_empty_result (line 162) | def is_google_search_empty_result(self, tool_name: str, tool_result: d...
method get_scrape_result (line 193) | def get_scrape_result(self, result: str) -> str:
method post_process_tool_call_result (line 214) | def post_process_tool_call_result(
method should_rollback_result (line 240) | def should_rollback_result(
method execute_single_tool_call (line 260) | async def execute_single_tool_call(
method format_tool_result_for_llm (line 346) | def format_tool_result_for_llm(self, tool_result: dict) -> dict:
FILE: apps/miroflow-agent/src/io/input_handler.py
function _generate_image_caption (line 58) | def _generate_image_caption(image_path: str) -> str:
function _generate_audio_caption (line 123) | def _generate_audio_caption(audio_path: str) -> str:
function _generate_video_caption (line 155) | def _generate_video_caption(video_path: str) -> str:
function _extract_task_relevant_info_from_image (line 220) | def _extract_task_relevant_info_from_image(
function _extract_task_relevant_info_from_audio (line 292) | def _extract_task_relevant_info_from_audio(
function _extract_task_relevant_info_from_video (line 366) | def _extract_task_relevant_info_from_video(
function process_input (line 438) | def process_input(task_description: str, task_file_name: str) -> Tuple[s...
class _CustomMarkdownify (line 654) | class _CustomMarkdownify(markdownify.MarkdownConverter):
method __init__ (line 664) | def __init__(self, **options: Any):
method convert_hn (line 669) | def convert_hn(self, n: int, el: Any, text: str, convert_as_inline: bo...
method convert_a (line 677) | def convert_a(self, el: Any, text: str, convert_as_inline: bool):
method convert_img (line 719) | def convert_img(self, el: Any, text: str, convert_as_inline: bool) -> ...
method convert_soup (line 738) | def convert_soup(self, soup: Any) -> str:
class DocumentConverterResult (line 742) | class DocumentConverterResult:
method __init__ (line 745) | def __init__(self, title: Union[str, None] = None, text_content: str =...
function convert_html_to_md (line 750) | def convert_html_to_md(html_content):
function HtmlConverter (line 775) | def HtmlConverter(local_path: str):
function DocxConverter (line 791) | def DocxConverter(local_path: str):
function XlsxConverter (line 810) | def XlsxConverter(local_path: str):
function PptxConverter (line 1058) | def PptxConverter(local_path: str) -> DocumentConverterResult:
function ZipConverter (line 1160) | def ZipConverter(local_path: str, **kwargs):
FILE: apps/miroflow-agent/src/io/output_formatter.py
class OutputFormatter (line 15) | class OutputFormatter:
method _extract_boxed_content (line 18) | def _extract_boxed_content(self, text: str) -> str:
method format_tool_result_for_user (line 95) | def format_tool_result_for_user(self, tool_call_execution_result: dict...
method format_final_summary_and_log (line 126) | def format_final_summary_and_log(
FILE: apps/miroflow-agent/src/llm/base_client.py
class TokenUsage (line 32) | class TokenUsage(TypedDict, total=True):
class BaseClient (line 52) | class BaseClient(ABC):
method __post_init__ (line 78) | def __post_init__(self):
method _reset_token_usage (line 110) | def _reset_token_usage(self) -> TokenUsage:
method _remove_tool_result_from_messages (line 124) | def _remove_tool_result_from_messages(
method create_message (line 223) | async def create_message(
method convert_tool_definition_to_tool_call (line 273) | async def convert_tool_definition_to_tool_call(tools_definitions):
method close (line 303) | def close(self):
method _format_response_for_log (line 327) | def _format_response_for_log(self, response) -> Dict:
FILE: apps/miroflow-agent/src/llm/factory.py
function ClientFactory (line 24) | def ClientFactory(
FILE: apps/miroflow-agent/src/llm/providers/anthropic_client.py
class AnthropicClient (line 39) | class AnthropicClient(BaseClient):
method __post_init__ (line 40) | def __post_init__(self):
method _create_client (line 49) | def _create_client(self) -> Union[AsyncAnthropic, Anthropic]:
method _update_token_usage (line 65) | def _update_token_usage(self, usage_data: Any) -> None:
method _create_message (line 101) | async def _create_message(
method process_llm_response (line 187) | def process_llm_response(
method extract_tool_calls_info (line 244) | def extract_tool_calls_info(
method update_message_history (line 252) | def update_message_history(
method generate_agent_system_prompt (line 274) | def generate_agent_system_prompt(self, date: Any, mcp_servers: List[Di...
method _estimate_tokens (line 281) | def _estimate_tokens(self, text: str) -> int:
method ensure_summary_context (line 302) | def ensure_summary_context(
method format_token_usage_summary (line 364) | def format_token_usage_summary(self) -> tuple[List[str], str]:
method get_token_usage (line 395) | def get_token_usage(self):
method _apply_cache_control (line 398) | def _apply_cache_control(self, messages: List[Dict]) -> List[Dict]:
FILE: apps/miroflow-agent/src/llm/providers/openai_client.py
class OpenAIClient (line 32) | class OpenAIClient(BaseClient):
method _create_client (line 33) | def _create_client(self) -> Union[AsyncOpenAI, OpenAI]:
method _update_token_usage (line 49) | def _update_token_usage(self, usage_data: Any) -> None:
method _create_message (line 80) | async def _create_message(
method process_llm_response (line 279) | def process_llm_response(
method extract_tool_calls_info (line 334) | def extract_tool_calls_info(
method update_message_history (line 342) | def update_message_history(
method generate_agent_system_prompt (line 364) | def generate_agent_system_prompt(self, date: Any, mcp_servers: List[Di...
method _estimate_tokens (line 371) | def _estimate_tokens(self, text: str) -> int:
method ensure_summary_context (line 392) | def ensure_summary_context(
method format_token_usage_summary (line 454) | def format_token_usage_summary(self) -> tuple[List[str], str]:
method get_token_usage (line 480) | def get_token_usage(self):
FILE: apps/miroflow-agent/src/llm/util.py
function with_timeout (line 19) | def with_timeout(
FILE: apps/miroflow-agent/src/logging/summary_time_cost.py
function _get_summary_template (line 11) | def _get_summary_template():
function _update_summary_data (line 25) | def _update_summary_data(summary_block, perf_summary, tool_workload):
function _calculate_averages (line 47) | def _calculate_averages(summary_block):
function generate_summary (line 82) | def generate_summary(log_dir: Path):
FILE: apps/miroflow-agent/src/logging/task_logger.py
function get_color_for_level (line 34) | def get_color_for_level(level: str) -> str:
class ColoredFormatter (line 48) | class ColoredFormatter(logging.Formatter):
method format (line 51) | def format(self, record):
function bootstrap_logger (line 72) | def bootstrap_logger() -> logging.Logger:
function get_utc_plus_8_time (line 106) | def get_utc_plus_8_time() -> str:
class LLMCallLog (line 113) | class LLMCallLog:
class ToolCallLog (line 126) | class ToolCallLog:
class StepLog (line 138) | class StepLog:
method __post_init__ (line 147) | def __post_init__(self):
class TaskLog (line 157) | class TaskLog:
method start_sub_agent_session (line 188) | def start_sub_agent_session(
method end_sub_agent_session (line 206) | def end_sub_agent_session(self, sub_agent_name: str) -> Optional[str]:
method log_step (line 217) | def log_step(
method serialize_for_json (line 280) | def serialize_for_json(self, obj):
method to_json (line 293) | def to_json(self) -> str:
method save (line 317) | def save(self):
method from_dict (line 336) | def from_dict(cls, d: dict) -> "TaskLog":
FILE: apps/miroflow-agent/src/utils/parsing_utils.py
function parse_tool_server_mapping (line 24) | def parse_tool_server_mapping(system_prompt: str) -> dict:
function set_tool_server_mapping (line 62) | def set_tool_server_mapping(system_prompt: str) -> None:
function fix_server_name_in_text (line 75) | def fix_server_name_in_text(text: str) -> str:
function filter_none_values (line 124) | def filter_none_values(arguments: Union[Dict, Any]) -> Union[Dict, Any]:
function _fix_backslash_escapes (line 139) | def _fix_backslash_escapes(json_str: str) -> str:
function safe_json_loads (line 193) | def safe_json_loads(arguments_str: str) -> Dict[str, Any]:
function extract_failure_experience_summary (line 228) | def extract_failure_experience_summary(text: str) -> str:
function extract_llm_response_text (line 279) | def extract_llm_response_text(llm_response: Union[str, Dict]) -> str:
function parse_llm_response_for_tool_calls (line 311) | def parse_llm_response_for_tool_calls(
FILE: apps/miroflow-agent/src/utils/prompt_utils.py
function generate_mcp_system_prompt (line 85) | def generate_mcp_system_prompt(date, mcp_servers):
function generate_no_mcp_system_prompt (line 167) | def generate_no_mcp_system_prompt(date):
function generate_agent_specific_system_prompt (line 204) | def generate_agent_specific_system_prompt(agent_type=""):
function generate_agent_summarize_prompt (line 236) | def generate_agent_summarize_prompt(task_description, agent_type=""):
FILE: apps/miroflow-agent/src/utils/wrapper_utils.py
class ErrorBox (line 9) | class ErrorBox:
method __init__ (line 21) | def __init__(self, error_msg: str) -> None:
method __str__ (line 24) | def __str__(self) -> str:
method __repr__ (line 27) | def __repr__(self) -> str:
method is_error_box (line 31) | def is_error_box(something: Any) -> bool:
class ResponseBox (line 36) | class ResponseBox:
method __init__ (line 48) | def __init__(
method __str__ (line 54) | def __str__(self) -> str:
method __repr__ (line 57) | def __repr__(self) -> str:
method is_response_box (line 61) | def is_response_box(something: Any) -> bool:
method has_extra_info (line 65) | def has_extra_info(self) -> bool:
method get_extra_info (line 69) | def get_extra_info(self) -> Optional[Dict[str, Any]]:
method get_response (line 73) | def get_response(self) -> Any:
FILE: apps/visualize-trace/app.py
function index (line 16) | def index():
function list_files (line 22) | def list_files():
function load_trace (line 86) | def load_trace():
function get_basic_info (line 117) | def get_basic_info():
function get_performance_summary (line 129) | def get_performance_summary():
function get_execution_flow (line 141) | def get_execution_flow():
function get_execution_summary (line 153) | def get_execution_summary():
function get_spans_summary (line 165) | def get_spans_summary():
function get_step_logs_summary (line 177) | def get_step_logs_summary():
function get_raw_messages (line 189) | def get_raw_messages():
FILE: apps/visualize-trace/run.py
function check_dependencies (line 10) | def check_dependencies():
function install_dependencies (line 29) | def install_dependencies():
function main (line 51) | def main():
FILE: apps/visualize-trace/static/js/script.js
function initializeApp (line 39) | function initializeApp() {
function showLoading (line 66) | function showLoading() {
function hideLoading (line 70) | function hideLoading() {
function showError (line 74) | function showError(message) {
function showSuccess (line 80) | function showSuccess(message) {
function formatTimestamp (line 86) | function formatTimestamp(timestamp) {
function truncateText (line 96) | function truncateText(text, maxLength = 100) {
function formatFileSize (line 102) | function formatFileSize(bytes) {
function formatMcpToolCallWithPlaceholders (line 111) | function formatMcpToolCallWithPlaceholders(text, placeholders) {
function createNewFormatToolCallHTML (line 169) | function createNewFormatToolCallHTML(tool) {
function renderMarkdown (line 203) | function renderMarkdown(text) {
function isJsonString (line 231) | function isJsonString(str) {
function formatJsonContent (line 245) | function formatJsonContent(content) {
function renderContent (line 256) | function renderContent(content, isBrowserAgent = false) {
function apiCall (line 276) | async function apiCall(url, options = {}) {
function setDefaultDirectory (line 298) | function setDefaultDirectory() {
function browseDirectory (line 305) | async function browseDirectory() {
function refreshFileList (line 315) | async function refreshFileList(directory = null) {
function onFileSelect (line 371) | function onFileSelect() {
function gotoPrevFile (line 379) | function gotoPrevFile() {
function gotoNextFile (line 387) | function gotoNextFile() {
function selectFileByIndex (line 395) | function selectFileByIndex(index) {
function updateNavigationButtons (line 403) | function updateNavigationButtons() {
function handleKeyboardShortcuts (line 424) | function handleKeyboardShortcuts(event) {
function loadTraceFile (line 464) | async function loadTraceFile() {
function updateBasicInfo (line 514) | function updateBasicInfo(data) {
function updateExecutionSummary (line 546) | function updateExecutionSummary(data) {
function updatePerformanceSummary (line 569) | function updatePerformanceSummary(data) {
function updateExecutionFlow (line 593) | function updateExecutionFlow(data) {
function createStepHTML (line 622) | function createStepHTML(step, index) {
function createToolCallHTML (line 692) | function createToolCallHTML(tool) {
function createBrowserStepHTML (line 717) | function createBrowserStepHTML(step, parentIndex) {
function updateSpansStats (line 758) | function updateSpansStats(data) {
function updateStepLogsStats (line 794) | function updateStepLogsStats(data) {
function bindStepEventListeners (line 829) | function bindStepEventListeners() {
function expandAllSteps (line 848) | function expandAllSteps() {
function collapseAllSteps (line 866) | function collapseAllSteps() {
function toggleBrowserPreview (line 886) | function toggleBrowserPreview(browserId, parentIndex, browserStepId) {
function showFullMessage (line 921) | function showFullMessage(stepId) {
function updateNavigationList (line 1005) | function updateNavigationList(data) {
function scrollToStep (line 1059) | function scrollToStep(stepIndex) {
function scrollToBrowserStep (line 1081) | function scrollToBrowserStep(parentIndex, browserStepId) {
function toggleBrowserNav (line 1103) | function toggleBrowserNav(stepIndex, event) {
function updateActiveNavItem (line 1118) | function updateActiveNavItem(activeIndex, browserStepId = null) {
function handleScroll (line 1140) | function handleScroll() {
FILE: apps/visualize-trace/trace_analyzer.py
class TraceAnalyzer (line 9) | class TraceAnalyzer:
method __init__ (line 18) | def __init__(self, json_file_path: str):
method _load_json (line 28) | def _load_json(self) -> Dict[str, Any]:
method _parse_new_format_tool_name (line 36) | def _parse_new_format_tool_name(self, tool_name: str) -> tuple[str, str]:
method get_basic_info (line 85) | def get_basic_info(self) -> Dict[str, Any]:
method get_performance_summary (line 99) | def get_performance_summary(self) -> Dict[str, Any]:
method get_main_agent_history (line 106) | def get_main_agent_history(self) -> Dict[str, Any]:
method get_main_agent_messages (line 110) | def get_main_agent_messages(self) -> List[Dict[str, Any]]:
method get_browser_agent_sessions (line 117) | def get_browser_agent_sessions(self) -> Dict[str, Any]:
method get_browser_agent_session_messages (line 125) | def get_browser_agent_session_messages(
method parse_mcp_tool_call (line 135) | def parse_mcp_tool_call(self, text: str) -> Optional[Dict[str, Any]]:
method extract_text_content (line 158) | def extract_text_content(self, content) -> str:
method analyze_conversation_flow (line 168) | def analyze_conversation_flow(self) -> List[Dict[str, Any]]:
method analyze_browser_session_flow (line 284) | def analyze_browser_session_flow(self, session_id: str) -> List[Dict[s...
method get_execution_summary (line 350) | def get_execution_summary(self) -> Dict[str, Any]:
method get_spans_summary (line 393) | def get_spans_summary(self) -> Dict[str, Any]:
method get_step_logs_summary (line 421) | def get_step_logs_summary(self) -> Dict[str, Any]:
FILE: libs/miroflow-tools/src/miroflow_tools/dev_mcp_servers/jina_scrape_llm_summary.py
function scrape_and_extract_info (line 28) | async def scrape_and_extract_info(
function _is_huggingface_dataset_or_space_url (line 122) | def _is_huggingface_dataset_or_space_url(url):
function scrape_url_with_jina (line 133) | async def scrape_url_with_jina(
function scrape_url_with_python (line 371) | async def scrape_url_with_python(
function get_prompt_with_truncation (line 577) | def get_prompt_with_truncation(
function extract_info_with_llm (line 588) | async def extract_info_with_llm(
FILE: libs/miroflow-tools/src/miroflow_tools/dev_mcp_servers/search_and_scrape_webpage.py
function make_serper_request (line 47) | async def make_serper_request(
function _is_banned_url (line 61) | def _is_banned_url(url: str) -> bool:
function google_search (line 78) | async def google_search(
function make_sogou_request (line 212) | async def make_sogou_request(query: str, cnt: int) -> Dict[str, Any]:
function sogou_search (line 227) | async def sogou_search(
FILE: libs/miroflow-tools/src/miroflow_tools/dev_mcp_servers/stateless_python_server.py
function python (line 20) | async def python(code: str) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/dev_mcp_servers/task_planner.py
function load_todos (line 38) | def load_todos() -> List[Dict[str, Any]]:
function save_todos (line 51) | def save_todos(todos: List[Dict[str, Any]]) -> bool:
function format_todos_as_markdown (line 62) | def format_todos_as_markdown(todos: List[Dict[str, Any]], message: str =...
function add_todo (line 100) | async def add_todo(titles: List[str]) -> str:
function list_todos (line 160) | async def list_todos() -> str:
function complete_todo (line 175) | async def complete_todo(todo_ids: List[str]) -> str:
function delete_todo (line 238) | async def delete_todo(todo_ids: List[str]) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/manager.py
function with_timeout (line 19) | def with_timeout(timeout_s: float = 300.0):
class ToolManagerProtocol (line 39) | class ToolManagerProtocol(Protocol):
method get_all_tool_definitions (line 42) | async def get_all_tool_definitions(self) -> Any: ...
method execute_tool_call (line 43) | async def execute_tool_call(
class ToolManager (line 48) | class ToolManager(ToolManagerProtocol):
method __init__ (line 49) | def __init__(self, server_configs, tool_blacklist=None):
method set_task_log (line 62) | def set_task_log(self, task_log):
method _log (line 72) | def _log(self, level, step_name, message, metadata=None):
method _is_huggingface_dataset_or_space_url (line 77) | def _is_huggingface_dataset_or_space_url(self, url):
method _should_block_hf_scraping (line 87) | def _should_block_hf_scraping(self, tool_name, arguments):
method get_server_params (line 100) | def get_server_params(self, server_name):
method get_all_tool_definitions (line 104) | async def get_all_tool_definitions(self):
method execute_tool_call (line 198) | async def execute_tool_call(self, server_name, tool_name, arguments) -...
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/audio_mcp_server.py
function _get_audio_extension (line 25) | def _get_audio_extension(url: str, content_type: str = None) -> str:
function _get_audio_duration (line 66) | def _get_audio_duration(audio_path: str) -> float:
function _encode_audio_file (line 102) | def _encode_audio_file(audio_path: str) -> tuple[str, str]:
function audio_transcription (line 127) | async def audio_transcription(audio_path_or_url: str) -> str:
function audio_question_answering (line 202) | async def audio_question_answering(audio_path_or_url: str, question: str...
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/audio_mcp_server_os.py
function _get_audio_extension (line 26) | def _get_audio_extension(url: str, content_type: str = None) -> str:
function _get_audio_duration (line 67) | def _get_audio_duration(audio_path: str) -> float:
function _encode_audio_file (line 99) | def _encode_audio_file(audio_path: str) -> tuple[str, str]:
function audio_transcription (line 124) | async def audio_transcription(audio_path_or_url: str) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/browser_session.py
class PlaywrightSession (line 16) | class PlaywrightSession:
method __init__ (line 19) | def __init__(self, server_params):
method connect (line 26) | async def connect(self):
method call_tool (line 39) | async def call_tool(self, tool_name, arguments=None):
method close (line 49) | async def close(self):
function test_persistent_session (line 64) | async def test_persistent_session():
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/python_mcp_server.py
function looks_like_dir (line 56) | def looks_like_dir(path: str) -> bool:
function truncate_result (line 73) | def truncate_result(result: str) -> str:
function create_sandbox (line 90) | async def create_sandbox(timeout: int = DEFAULT_TIMEOUT) -> str:
function run_command (line 129) | async def run_command(command: str, sandbox_id: str) -> str:
function run_python_code (line 173) | async def run_python_code(code_block: str, sandbox_id: str) -> str:
function upload_file_from_local_to_sandbox (line 230) | async def upload_file_from_local_to_sandbox(
function download_file_from_internet_to_sandbox (line 294) | async def download_file_from_internet_to_sandbox(
function download_file_from_sandbox_to_local (line 390) | async def download_file_from_sandbox_to_local(
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/reading_mcp_server.py
function convert_to_markdown (line 19) | async def convert_to_markdown(uri: str) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/reasoning_mcp_server.py
function reasoning (line 20) | async def reasoning(question: str) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/reasoning_mcp_server_os.py
function post_with_retry (line 27) | def post_with_retry(url, json, headers):
function reasoning (line 55) | async def reasoning(question: str) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/searching_google_mcp_server.py
function filter_google_search_result (line 41) | def filter_google_search_result(result_content: str) -> str:
function google_search (line 85) | async def google_search(
function wiki_get_page_content (line 167) | async def wiki_get_page_content(entity: str, first_sentences: int = 10) ...
function search_wiki_revision (line 276) | async def search_wiki_revision(
function search_archived_webpage (line 433) | async def search_archived_webpage(url: str, year: int, month: int, day: ...
function scrape_website (line 649) | async def scrape_website(url: str) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/searching_sogou_mcp_server.py
function sogou_search (line 30) | async def sogou_search(Query: str, Cnt: int = 10) -> str:
function scrape_website (line 91) | async def scrape_website(url: str) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/serper_mcp_server.py
function make_serper_request (line 38) | def make_serper_request(
function _is_huggingface_dataset_or_space_url (line 47) | def _is_huggingface_dataset_or_space_url(url):
function google_search (line 59) | def google_search(
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/utils/url_unquote.py
function safe_unquote (line 45) | def safe_unquote(url: str) -> str:
function decode_http_urls_in_dict (line 117) | def decode_http_urls_in_dict(data):
function strip_markdown_links (line 141) | def strip_markdown_links(markdown: str) -> str:
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/vision_mcp_server.py
function guess_mime_media_type_from_extension (line 22) | def guess_mime_media_type_from_extension(file_path: str) -> tuple[str, s...
function _validate_file_size (line 62) | def _validate_file_size(file_path: str, media_category: str) -> tuple[bo...
function visual_question_answering (line 89) | async def visual_question_answering(media_path_or_url: str, question: st...
FILE: libs/miroflow-tools/src/miroflow_tools/mcp_servers/vision_mcp_server_os.py
function guess_mime_media_type_from_extension (line 19) | def guess_mime_media_type_from_extension(file_path: str) -> str:
function visual_question_answering (line 34) | async def visual_question_answering(image_path_or_url: str, question: st...
Condensed preview — 169 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,206K chars).
[
{
"path": ".github/workflows/run-ruff.yml",
"chars": 968,
"preview": "name: lint\n\non:\n pull_request:\n branches: [ \"main\" ]\n\njobs:\n lint:\n if: github.repository_owner == 'MiroMindAI'\n"
},
{
"path": ".gitignore",
"chars": 4835,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[codz]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packag"
},
{
"path": "LICENSE",
"chars": 10173,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 55758,
"preview": "<div align=\"center\">\n <img src=\"assets/mirothinker_logo.png\" width=\"55%\" alt=\"MiroThinker\" />\n</div>\n\n<br>\n\n<div align="
},
{
"path": "apps/collect-trace/README.md",
"chars": 1994,
"preview": "# Collect Trace\n\n> TL;DR: Treat an RLVR-format dataset (Question + verifiable answer) as a benchmark. Run the evaluation"
},
{
"path": "apps/collect-trace/pyproject.toml",
"chars": 447,
"preview": "[project]\nname = \"collect-trace\"\nversion = \"0.1.0\"\ndescription = \"Executes a user-defined agent loop for capturing multi"
},
{
"path": "apps/collect-trace/scripts/collect_trace_claude37.sh",
"chars": 1449,
"preview": "# Check if ANTHROPIC_API_KEY is set\nif [ -z \"$ANTHROPIC_API_KEY\" ]; then\n echo \"Error: ANTHROPIC_API_KEY is not set.\""
},
{
"path": "apps/collect-trace/scripts/collect_trace_gpt41.sh",
"chars": 1409,
"preview": "# Check if OPENAI_API_KEY is set\nif [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"Error: OPENAI_API_KEY is not set.\"\n exit"
},
{
"path": "apps/collect-trace/scripts/collect_trace_gpt5.sh",
"chars": 1412,
"preview": "# Check if OPENAI_API_KEY is set\nif [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"Error: OPENAI_API_KEY is not set.\"\n exit"
},
{
"path": "apps/collect-trace/scripts/collect_trace_qwen3.sh",
"chars": 1283,
"preview": "# Get the directory where the current script is located\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\necho "
},
{
"path": "apps/collect-trace/utils/converters/__init__.py",
"chars": 943,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nfrom .convert_non_oai_to_cha"
},
{
"path": "apps/collect-trace/utils/converters/convert_non_oai_to_chatml.py",
"chars": 5261,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport json\nimport sys\nfrom "
},
{
"path": "apps/collect-trace/utils/converters/convert_oai_to_chatml.py",
"chars": 13001,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport ast\nimport json\nimpor"
},
{
"path": "apps/collect-trace/utils/converters/convert_to_chatml_auto_batch.py",
"chars": 8572,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport json\nimport subproces"
},
{
"path": "apps/collect-trace/utils/converters/example_usage.py",
"chars": 3615,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport json\nimport os\nimport"
},
{
"path": "apps/collect-trace/utils/converters/system_prompts.py",
"chars": 2679,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nmain_system_prompt_foreword "
},
{
"path": "apps/collect-trace/utils/merge_chatml_msgs_to_one_json.py",
"chars": 1870,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport glob\n"
},
{
"path": "apps/collect-trace/utils/process_logs.py",
"chars": 3403,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport json\n"
},
{
"path": "apps/gradio-demo/README.md",
"chars": 4307,
"preview": "# Local Deep Research Demo with Gradio Web UI\n\nHost your own Deep Research demo using our [MiroThinker v1.5](https://hug"
},
{
"path": "apps/gradio-demo/main.py",
"chars": 53763,
"preview": "import asyncio\nimport json\nimport logging\nimport os\nimport threading\nimport time\nimport uuid\nfrom concurrent.futures imp"
},
{
"path": "apps/gradio-demo/prompt_patch.py",
"chars": 13174,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nCustom Prompt Override ("
},
{
"path": "apps/gradio-demo/pyproject.toml",
"chars": 602,
"preview": "[project]\nname = \"gradio-demo\"\nversion = \"0.1.0\"\ndescription = \"Gradio Demo\"\nreadme = \"README.md\"\nrequires-python = \">=3"
},
{
"path": "apps/gradio-demo/utils.py",
"chars": 1457,
"preview": "import re\n\n\ndef contains_chinese(text):\n \"\"\"\n Detect if a string contains Chinese characters or Chinese punctuatio"
},
{
"path": "apps/lobehub-compatibility/MiroThinkerToolParser.py",
"chars": 16934,
"preview": "\"\"\"\nTool parser plugin for vLLM for MiroThinker MCP format to compatible with the tool calling interface of openai.\nMCP "
},
{
"path": "apps/lobehub-compatibility/README.md",
"chars": 3042,
"preview": "# LobeChat Integration Guide\n\nThis guide describes how to integrate the MiroThinker model with [LobeChat](https://github"
},
{
"path": "apps/lobehub-compatibility/chat_template.jinja",
"chars": 6403,
"preview": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {{- messages[0].con"
},
{
"path": "apps/lobehub-compatibility/requirements.txt",
"chars": 30,
"preview": "vllm>=0.11.0\njson-repair\nregex"
},
{
"path": "apps/lobehub-compatibility/test_tool_parser.py",
"chars": 10739,
"preview": "#!/usr/bin/env python3\n\"\"\"\nTest MiroThinkerToolParser for correctness.\n\"\"\"\n\nimport json\nimport sys\nfrom types import Sim"
},
{
"path": "apps/lobehub-compatibility/unit_test.py",
"chars": 31117,
"preview": "#!/usr/bin/env python3\n\"\"\"\nUnit tests for MiroThinker chat template.\n\nRun with: pytest unit_test.py -v\n\"\"\"\n\nfrom datetim"
},
{
"path": "apps/miroflow-agent/README.md",
"chars": 3901,
"preview": "# MiroFlow Agent\n\n> For comprehensive documentation, installation guide, and tool configuration, see the [main README](."
},
{
"path": "apps/miroflow-agent/benchmarks/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_aime2025.py",
"chars": 1519,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_browsecomp.py",
"chars": 1528,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_browsecomp_zh.py",
"chars": 1530,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_deepsearchqa.py",
"chars": 16780,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport glob\n"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_frames.py",
"chars": 1516,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_gaia-validation-text-103.py",
"chars": 1576,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_gaia-validation.py",
"chars": 1566,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_hle-text-2158.py",
"chars": 1531,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_hle-text-500.py",
"chars": 1528,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_hle.py",
"chars": 1516,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_seal-0.py",
"chars": 1516,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_webwalkerqa.py",
"chars": 1528,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/check_progress_xbench_deepsearch.py",
"chars": 1538,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport os\n\nf"
},
{
"path": "apps/miroflow-agent/benchmarks/check_progress/common.py",
"chars": 52191,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport glob\nimport json\nimpo"
},
{
"path": "apps/miroflow-agent/benchmarks/common_benchmark.py",
"chars": 43705,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport gc\nimp"
},
{
"path": "apps/miroflow-agent/benchmarks/evaluators/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "apps/miroflow-agent/benchmarks/evaluators/calculate_average_score.py",
"chars": 4642,
"preview": "#!/usr/bin/env python3\n# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimpor"
},
{
"path": "apps/miroflow-agent/benchmarks/evaluators/eval_utils.py",
"chars": 45100,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport json\ni"
},
{
"path": "apps/miroflow-agent/benchmarks/evaluators/extract_futurex_results.py",
"chars": 5395,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport json\n"
},
{
"path": "apps/miroflow-agent/benchmarks/subset_extraction/gaia-text-103-grader.py",
"chars": 10098,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nGAIA-Text-103 Task Grade"
},
{
"path": "apps/miroflow-agent/benchmarks/subset_extraction/gaia-to-text-103-mover.py",
"chars": 6845,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nGAIA to Text-103 Task Co"
},
{
"path": "apps/miroflow-agent/conf/__init__.py",
"chars": 54,
"preview": "# This file makes the conf directory a Python package\n"
},
{
"path": "apps/miroflow-agent/conf/agent/default.yaml",
"chars": 669,
"preview": "# conf/agent/default.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/settings.py\n# Ea"
},
{
"path": "apps/miroflow-agent/conf/agent/demo.yaml",
"chars": 690,
"preview": "# conf/agent/demo.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/settings.py\n# Each "
},
{
"path": "apps/miroflow-agent/conf/agent/mirothinker_1.7_keep5_max200.yaml",
"chars": 758,
"preview": "# conf/agent/mirothinker_1.7_keep5_max200.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/co"
},
{
"path": "apps/miroflow-agent/conf/agent/mirothinker_1.7_keep5_max300.yaml",
"chars": 758,
"preview": "# conf/agent/mirothinker_1.7_keep5_max300.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/co"
},
{
"path": "apps/miroflow-agent/conf/agent/mirothinker_v1.0.yaml",
"chars": 703,
"preview": "# conf/agent/mirothinker_v1.0.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/setting"
},
{
"path": "apps/miroflow-agent/conf/agent/mirothinker_v1.0_keep5.yaml",
"chars": 708,
"preview": "# conf/agent/mirothinker_v1.0_keep5.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/s"
},
{
"path": "apps/miroflow-agent/conf/agent/mirothinker_v1.5.yaml",
"chars": 703,
"preview": "# conf/agent/mirothinker_v1.5.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/setting"
},
{
"path": "apps/miroflow-agent/conf/agent/mirothinker_v1.5_keep5_max200.yaml",
"chars": 715,
"preview": "# conf/agent/mirothinker_v1.5_keep5_max200.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/c"
},
{
"path": "apps/miroflow-agent/conf/agent/mirothinker_v1.5_keep5_max400.yaml",
"chars": 715,
"preview": "# conf/agent/mirothinker_v1.5_keep5_max400.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/c"
},
{
"path": "apps/miroflow-agent/conf/agent/multi_agent.yaml",
"chars": 708,
"preview": "# conf/agent/multi_agent.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/settings.py\n"
},
{
"path": "apps/miroflow-agent/conf/agent/multi_agent_os.yaml",
"chars": 723,
"preview": "# conf/agent/multi_agent_os.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/settings."
},
{
"path": "apps/miroflow-agent/conf/agent/single_agent.yaml",
"chars": 699,
"preview": "# conf/agent/single_agent.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/settings.py"
},
{
"path": "apps/miroflow-agent/conf/agent/single_agent_keep5.yaml",
"chars": 704,
"preview": "# conf/agent/single_agent_keep5.yaml\n# The name of tools and sub-agents defined in: apps/miroflow-agent/src/config/setti"
},
{
"path": "apps/miroflow-agent/conf/benchmark/aime2025.yaml",
"chars": 210,
"preview": "# conf/benchmark/aime2025.yaml\ndefaults:\n - default\n - _self_\n\nname: \"aime2025\"\n\ndata:\n data_dir: \"../../data/aime202"
},
{
"path": "apps/miroflow-agent/conf/benchmark/browsecomp.yaml",
"chars": 217,
"preview": "# conf/benchmark/browsecomp.yaml\ndefaults:\n - default\n - _self_\n\nname: \"browsecomp\"\n\ndata:\n data_dir: \"../../data/bro"
},
{
"path": "apps/miroflow-agent/conf/benchmark/browsecomp_zh.yaml",
"chars": 226,
"preview": "# conf/benchmark/browsecomp_zh.yaml\ndefaults:\n - default\n - _self_\n\nname: \"browsecomp_zh\"\n\ndata:\n data_dir: \"../../da"
},
{
"path": "apps/miroflow-agent/conf/benchmark/collect_trace.yaml",
"chars": 218,
"preview": "# conf/benchmark/collect_trace.yaml\ndefaults:\n - default\n - _self_\n\nname: \"collect_trace\"\n\ndata:\n data_dir: \"../../da"
},
{
"path": "apps/miroflow-agent/conf/benchmark/debug.yaml",
"chars": 202,
"preview": "# conf/benchmark/debug.yaml\ndefaults:\n - default\n - _self_\n\nname: \"debug\"\n\ndata:\n data_dir: \"../../data/debug\"\n\nexecu"
},
{
"path": "apps/miroflow-agent/conf/benchmark/deepsearchqa.yaml",
"chars": 223,
"preview": "# conf/benchmark/deepsearchqa.yaml\ndefaults:\n - default\n - _self_\n\nname: \"deepsearchqa\"\n\ndata:\n data_dir: \"../../data"
},
{
"path": "apps/miroflow-agent/conf/benchmark/default.yaml",
"chars": 473,
"preview": "# conf/benchmark/default.yaml - Default benchmark configuration\n# This is a base configuration for benchmarks. Specific "
},
{
"path": "apps/miroflow-agent/conf/benchmark/frames.yaml",
"chars": 205,
"preview": "# conf/benchmark/frames.yaml\ndefaults:\n - default\n - _self_\n\nname: \"frames\"\n\ndata:\n data_dir: \"../../data/frames\"\n\nex"
},
{
"path": "apps/miroflow-agent/conf/benchmark/futurex.yaml",
"chars": 208,
"preview": "# conf/benchmark/futurex.yaml\ndefaults:\n - default\n - _self_\n\nname: \"futurex\"\n\ndata:\n data_dir: \"../../data/futurex\"\n"
},
{
"path": "apps/miroflow-agent/conf/benchmark/gaia-validation-text-103.yaml",
"chars": 264,
"preview": "# conf/benchmark/gaia-validation-text-103.yaml\ndefaults:\n - default\n - _self_\n\nname: \"gaia-validation-text-103\"\n\ndata:"
},
{
"path": "apps/miroflow-agent/conf/benchmark/gaia-validation.yaml",
"chars": 237,
"preview": "# conf/benchmark/gaia-validation.yaml\ndefaults:\n - default\n - _self_\n\nname: \"gaia-validation\"\n\ndata:\n data_dir: \"../."
},
{
"path": "apps/miroflow-agent/conf/benchmark/hle-text-2158.yaml",
"chars": 225,
"preview": "# conf/benchmark/hle-text-2158.yaml\ndefaults:\n - default\n - _self_\n\nname: \"hle-text-2158\"\n\ndata:\n data_dir: \"../../da"
},
{
"path": "apps/miroflow-agent/conf/benchmark/hle-text-500.yaml",
"chars": 222,
"preview": "# conf/benchmark/hle-text-500.yaml\ndefaults:\n - default\n - _self_\n\nname: \"hle-text-500\"\n\ndata:\n data_dir: \"../../data"
},
{
"path": "apps/miroflow-agent/conf/benchmark/hle.yaml",
"chars": 196,
"preview": "# conf/benchmark/hle.yaml\ndefaults:\n - default\n - _self_\n\nname: \"hle\"\n\ndata:\n data_dir: \"../../data/hle\"\n\nexecution:\n"
},
{
"path": "apps/miroflow-agent/conf/benchmark/seal-0.yaml",
"chars": 204,
"preview": "# conf/benchmark/seal-0.yaml\ndefaults:\n - default\n - _self_\n\nname: \"seal-0\"\n\ndata:\n data_dir: \"../../data/seal-0\"\n\nex"
},
{
"path": "apps/miroflow-agent/conf/benchmark/webwalkerqa.yaml",
"chars": 220,
"preview": "# conf/benchmark/webwalkerqa.yaml\ndefaults:\n - default\n - _self_\n\nname: \"webwalkerqa\"\n\ndata:\n data_dir: \"../../data/w"
},
{
"path": "apps/miroflow-agent/conf/benchmark/xbench_deepsearch.yaml",
"chars": 238,
"preview": "# conf/benchmark/xbench_deepsearch.yaml\ndefaults:\n - default\n - _self_\n\nname: \"xbench_deepsearch\"\n\ndata:\n data_dir: \""
},
{
"path": "apps/miroflow-agent/conf/config.yaml",
"chars": 319,
"preview": "# conf/config.yaml\ndefaults:\n - llm: default\n - agent: default\n - benchmark: default\n - _self_ # Allows variables t"
},
{
"path": "apps/miroflow-agent/conf/llm/claude-3-7.yaml",
"chars": 186,
"preview": "# conf/llm/claude-3-7.yaml\ndefaults:\n - default\n - _self_\n\nprovider: \"anthropic\"\nmodel_name: \"claude-3-7-sonnet-202502"
},
{
"path": "apps/miroflow-agent/conf/llm/default.yaml",
"chars": 299,
"preview": "# conf/llm/default.yaml - Default LLM configuration\nprovider: \"anthropic\" # openai, anthropic, qwen\nmodel_name: \"claude-"
},
{
"path": "apps/miroflow-agent/conf/llm/gpt-5.yaml",
"chars": 168,
"preview": "# conf/llm/gpt-5.yaml\ndefaults:\n - default\n - _self_\n\nprovider: \"openai\"\nmodel_name: \"gpt-5-2025-08-07\"\nbase_url: http"
},
{
"path": "apps/miroflow-agent/conf/llm/qwen-3.yaml",
"chars": 229,
"preview": "# conf/llm/qwen-3.yaml\ndefaults:\n - default\n - _self_\n\nprovider: \"qwen\"\nmodel_name: \"qwen-3\"\nbase_url: \"https://your-a"
},
{
"path": "apps/miroflow-agent/main.py",
"chars": 1529,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\n\nimport hydra"
},
{
"path": "apps/miroflow-agent/pyproject.toml",
"chars": 2440,
"preview": "[project]\nname = \"miroflow-agent\"\nversion = \"0.1.0\"\ndescription = \"An agent framework for complex task solving with LLM "
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_aime2025.sh",
"chars": 3258,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_browsecomp.sh",
"chars": 3261,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_browsecomp_zh.sh",
"chars": 3267,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_debug.sh",
"chars": 3250,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_deepsearchqa.sh",
"chars": 3265,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_frames.sh",
"chars": 3253,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_futurex.sh",
"chars": 3732,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_gaia-validation-text-103.sh",
"chars": 3294,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_gaia-validation.sh",
"chars": 3276,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_hle-text-2158.sh",
"chars": 3276,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_hle-text-500.sh",
"chars": 3274,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_hle.sh",
"chars": 3247,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_seal-0.sh",
"chars": 3253,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_webwalkerqa.sh",
"chars": 3263,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/scripts/run_evaluate_multiple_runs_xbench_deepsearch.sh",
"chars": 3275,
"preview": "#!/bin/bash\n\n# Parse environment variables, use defaults if not set\nLLM_MODEL=${LLM_MODEL:-\"MiroThinker-Models\"}\nBASE_UR"
},
{
"path": "apps/miroflow-agent/src/__init__.py",
"chars": 611,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"MiroFlow Agent - A modula"
},
{
"path": "apps/miroflow-agent/src/config/__init__.py",
"chars": 355,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"Configuration module for "
},
{
"path": "apps/miroflow-agent/src/config/settings.py",
"chars": 17392,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nConfiguration settings a"
},
{
"path": "apps/miroflow-agent/src/core/__init__.py",
"chars": 561,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"Core module containing or"
},
{
"path": "apps/miroflow-agent/src/core/answer_generator.py",
"chars": 22815,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nAnswer generator module "
},
{
"path": "apps/miroflow-agent/src/core/orchestrator.py",
"chars": 47022,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nOrchestrator module for "
},
{
"path": "apps/miroflow-agent/src/core/pipeline.py",
"chars": 7550,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nTask execution pipeline "
},
{
"path": "apps/miroflow-agent/src/core/stream_handler.py",
"chars": 6523,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nStream handler module fo"
},
{
"path": "apps/miroflow-agent/src/core/tool_executor.py",
"chars": 12126,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nTool executor module for"
},
{
"path": "apps/miroflow-agent/src/io/__init__.py",
"chars": 316,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"Input/Output module for p"
},
{
"path": "apps/miroflow-agent/src/io/input_handler.py",
"chars": 54137,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nInput handler module for"
},
{
"path": "apps/miroflow-agent/src/io/output_formatter.py",
"chars": 6494,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"Output formatting utiliti"
},
{
"path": "apps/miroflow-agent/src/llm/__init__.py",
"chars": 326,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nfrom .base_client import Bas"
},
{
"path": "apps/miroflow-agent/src/llm/base_client.py",
"chars": 14434,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nBase client module for L"
},
{
"path": "apps/miroflow-agent/src/llm/factory.py",
"chars": 2221,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nLLM Client Factory modul"
},
{
"path": "apps/miroflow-agent/src/llm/providers/__init__.py",
"chars": 236,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nfrom .anthropic_client impor"
},
{
"path": "apps/miroflow-agent/src/llm/providers/anthropic_client.py",
"chars": 17699,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nAnthropic Claude LLM cli"
},
{
"path": "apps/miroflow-agent/src/llm/providers/openai_client.py",
"chars": 20192,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nOpenAI-compatible LLM cl"
},
{
"path": "apps/miroflow-agent/src/llm/util.py",
"chars": 963,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nUtility decorators and h"
},
{
"path": "apps/miroflow-agent/src/logging/__init__.py",
"chars": 411,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"Logging module for task e"
},
{
"path": "apps/miroflow-agent/src/logging/summary_time_cost.py",
"chars": 5023,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport json\nfrom collections"
},
{
"path": "apps/miroflow-agent/src/logging/task_logger.py",
"chars": 11356,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nTask logging and structu"
},
{
"path": "apps/miroflow-agent/src/utils/__init__.py",
"chars": 1046,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"Utility functions for par"
},
{
"path": "apps/miroflow-agent/src/utils/parsing_utils.py",
"chars": 14922,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nParsing utilities for LL"
},
{
"path": "apps/miroflow-agent/src/utils/prompt_utils.py",
"chars": 14293,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nPrompt templates and uti"
},
{
"path": "apps/miroflow-agent/src/utils/wrapper_utils.py",
"chars": 2255,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"Wrapper utilities for han"
},
{
"path": "apps/visualize-trace/.python-version",
"chars": 5,
"preview": "3.11 "
},
{
"path": "apps/visualize-trace/README.md",
"chars": 1163,
"preview": "# Trace Analysis Web Demo\n\nAn interactive web interface for analyzing and visualizing trace JSON files.\n\n## Installation"
},
{
"path": "apps/visualize-trace/app.py",
"chars": 7240,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport os\n\nfrom flask import"
},
{
"path": "apps/visualize-trace/pyproject.toml",
"chars": 237,
"preview": "[project]\nname = \"trace-dashboard\"\nversion = \"1.0.0\"\ndescription = \"A web dashboard for analyzing trace JSON files\"\nrequ"
},
{
"path": "apps/visualize-trace/requirements.txt",
"chars": 29,
"preview": "flask==2.3.3\nwerkzeug==2.3.7 "
},
{
"path": "apps/visualize-trace/run.py",
"chars": 3389,
"preview": "#!/usr/bin/env python3\n# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimpor"
},
{
"path": "apps/visualize-trace/static/css/style.css",
"chars": 16118,
"preview": "/* Global styles */\nbody {\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n background-color: #f8f9"
},
{
"path": "apps/visualize-trace/static/js/script.js",
"chars": 44258,
"preview": "// Global variables\nlet currentFlowData = null;\nlet currentBasicInfo = null;\nlet currentFileList = [];\nlet currentFileIn"
},
{
"path": "apps/visualize-trace/templates/index.html",
"chars": 10049,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width"
},
{
"path": "apps/visualize-trace/trace_analyzer.py",
"chars": 18492,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport json\nimport re\nfrom t"
},
{
"path": "assets/LOCAL-TOOL-DEPLOYMENT.md",
"chars": 4979,
"preview": "# Local Tool Deployment Guide\n\nThis guide explains how to deploy open-source tools locally for use with MiroThinker. The"
},
{
"path": "assets/QA.md",
"chars": 2096,
"preview": "# MiroFlow QA Documentation\n\n## Q1: Can I extract GAIA-Text-103 results from existing GAIA-Validation evaluations?\n\n**An"
},
{
"path": "assets/qwen3_nonthinking.jinja",
"chars": 4012,
"preview": " {%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {{- messages[0].co"
},
{
"path": "justfile",
"chars": 885,
"preview": "default:\n just --list\n\n# lint monorepo\n[group('precommit')]\nlint:\n uv tool run ruff@0.8.0 check --fix .\n\n# sort im"
},
{
"path": "libs/miroflow-tools/README.md",
"chars": 29697,
"preview": "# 🛠️ MiroFlow Tools\n\n> A comprehensive tool management system and MCP (Model Context Protocol) server collection for Mir"
},
{
"path": "libs/miroflow-tools/pyproject.toml",
"chars": 1479,
"preview": "[project]\nname = \"miroflow-tools\"\nversion = \"0.1.0\"\ndescription = \"Tool management and MCP server utilities for MiroFlow"
},
{
"path": "libs/miroflow-tools/src/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/__init__.py",
"chars": 152,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nfrom .manager import ToolMan"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/dev_mcp_servers/jina_scrape_llm_summary.py",
"chars": 31990,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport json\ni"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/dev_mcp_servers/search_and_scrape_webpage.py",
"chars": 10575,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport json\nimport logging\ni"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/dev_mcp_servers/stateless_python_server.py",
"chars": 1703,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport os\n\nfrom e2b_code_int"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/dev_mcp_servers/task_planner.py",
"chars": 9256,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport json\nimport logging\ni"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/manager.py",
"chars": 16822,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport functo"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/audio_mcp_server.py",
"chars": 13220,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport base64"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/audio_mcp_server_os.py",
"chars": 7652,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport base64"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/browser_session.py",
"chars": 3264,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport json\ni"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/python_mcp_server.py",
"chars": 19841,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport os\nimp"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/reading_mcp_server.py",
"chars": 3291,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport argparse\nimport loggi"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/reasoning_mcp_server.py",
"chars": 1626,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport logging\nimport os\n\nfr"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/reasoning_mcp_server_os.py",
"chars": 3003,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport logging\nimport os\nimp"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/searching_google_mcp_server.py",
"chars": 27481,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport calend"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/searching_sogou_mcp_server.py",
"chars": 6751,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport json\ni"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/serper_mcp_server.py",
"chars": 5001,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\n\"\"\"\nadapted from\nhttps://git"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/utils/__init__.py",
"chars": 181,
"preview": "from .url_unquote import decode_http_urls_in_dict, safe_unquote, strip_markdown_links\n\n__all__ = [\n \"safe_unquote\",\n "
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/utils/url_unquote.py",
"chars": 6298,
"preview": "import re\nfrom urllib.parse import unquote\n\nfrom markdown_it import MarkdownIt\n\n# RFC 3986 reserved characters percent-e"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/vision_mcp_server.py",
"chars": 6728,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport asyncio\nimport base64"
},
{
"path": "libs/miroflow-tools/src/miroflow_tools/mcp_servers/vision_mcp_server_os.py",
"chars": 3462,
"preview": "# Copyright (c) 2025 MiroMind\n# This source code is licensed under the Apache 2.0 License.\n\nimport base64\nimport os\n\nimp"
}
]
About this extraction
This page contains the full source code of the MiroMindAI/MiroThinker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 169 files (1.1 MB), approximately 258.0k tokens, and a symbol index with 545 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.