ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
# IMO Super Agent with Guard Agent
|
||||
|
||||
This folder contains the Super Agent and Guard Agent dialogue system migrated from the GAIA project, specifically designed for solving IMO (International Mathematical Olympiad) problems.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Setup Environment**:
|
||||
```bash
|
||||
cd AWorld/examples/imo
|
||||
./setup_env.sh
|
||||
```
|
||||
|
||||
2. **Configure Environment Variables**:
|
||||
```bash
|
||||
cp .env_template .env
|
||||
# Edit .env file with your API keys
|
||||
```
|
||||
|
||||
3. **Run the Program**:
|
||||
```bash
|
||||
conda activate aworld_imo_env
|
||||
python run.py --q imo4
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
imo/
|
||||
├── run.py # Main execution file
|
||||
├── guard_tool_caller.py # Guard tool caller
|
||||
├── prompt.py # System prompts
|
||||
├── utils.py # Utility functions
|
||||
├── metadata.jsonl # IMO problem dataset
|
||||
├── requirements.txt # Python dependencies
|
||||
├── setup_env.sh # Environment setup script
|
||||
├── README.md # Documentation
|
||||
└── .env # Environment variables configuration file
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
|
||||
### Method 1: Automatic Setup (Recommended)
|
||||
|
||||
```bash
|
||||
# Navigate to the imo directory
|
||||
cd AWorld/examples/imo
|
||||
|
||||
# Run the automatic setup script
|
||||
./setup_env.sh
|
||||
```
|
||||
|
||||
This script will automatically:
|
||||
1. Create a new conda environment named `aworld_imo_env`
|
||||
2. Install all necessary dependencies
|
||||
3. Install the AWorld framework
|
||||
4. Provide usage instructions
|
||||
|
||||
### Method 2: Manual Setup
|
||||
|
||||
If you prefer manual setup, follow these steps:
|
||||
|
||||
```bash
|
||||
# 1. Create a new conda environment
|
||||
conda create -n aworld_imo_env python=3.11 -y
|
||||
|
||||
# 2. Activate the environment
|
||||
conda activate aworld_imo_env
|
||||
|
||||
# 3. Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 4. Install AWorld framework
|
||||
cd ../../../
|
||||
pip install -e .
|
||||
cd AWorld/examples/imo
|
||||
```
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
Before running the program, you need to set up your environment variables:
|
||||
|
||||
1. **Copy the template file**:
|
||||
```bash
|
||||
cp .env_template .env
|
||||
```
|
||||
|
||||
2. **Edit the `.env` file** with your actual API keys and configurations:
|
||||
```bash
|
||||
# LLM Configuration
|
||||
LLM_MODEL_NAME="your_model_name" # e.g., "google/gemini-2.5-pro-preview"
|
||||
LLM_API_KEY="your_api_key" # Your API key from OpenAI, OpenRouter, etc.
|
||||
LLM_BASE_URL="your_base_url" # e.g., "https://openrouter.ai/api/v1"
|
||||
LLM_TEMPERATURE=0.1
|
||||
|
||||
# Path Configurations (use relative paths)
|
||||
IMO_DATASET_PATH="." # Current directory
|
||||
AWORLD_WORKSPACE="Record" # Record directory
|
||||
|
||||
# IMO Server (same as LLM configuration for most cases)
|
||||
IMO_LLM_API_KEY="your_imo_api_key" # Same as LLM_API_KEY
|
||||
IMO_LLM_BASE_URL="your_imo_base_url" # Same as LLM_BASE_URL
|
||||
IMO_LLM_MODEL_NAME="your_imo_model_name" # Same as LLM_MODEL_NAME
|
||||
```
|
||||
|
||||
**Important Notes**:
|
||||
- The `.env_template` file contains a template with empty values. You need to fill in your actual API keys and configurations in the `.env` file.
|
||||
- For most users, the IMO Server configuration can be the same as the LLM configuration.
|
||||
- You can obtain API keys from services like OpenAI, OpenRouter, or other LLM providers.
|
||||
- The path configurations use relative paths (`.`) which means the current directory.
|
||||
|
||||
## Using the Environment
|
||||
|
||||
After setup, use the IMO project:
|
||||
|
||||
```bash
|
||||
# 1. Activate the environment
|
||||
conda activate aworld_imo_env
|
||||
|
||||
# 2. Navigate to the project directory
|
||||
cd AWorld/examples/imo
|
||||
|
||||
# 3. Run the program
|
||||
python run.py --q imo4
|
||||
```
|
||||
|
||||
## Dataset Description
|
||||
|
||||
The IMO dataset is contained in the `metadata.jsonl` file, including the following IMO problems:
|
||||
- imo1: Plane geometry problem
|
||||
- imo2: Circle and triangle problem
|
||||
- imo3: Function problem
|
||||
- imo4: Sequence problem
|
||||
- imo5: Game theory problem
|
||||
- imo6: Grid covering problem
|
||||
|
||||
**Dataset Format**: Each line in `metadata.jsonl` is a JSON object with:
|
||||
- `task_id`: Unique identifier for the problem (e.g., "imo1", "imo2")
|
||||
- `Question`: The mathematical problem statement
|
||||
|
||||
**Adding New Problems**: You can add new problems by appending JSON lines to `metadata.jsonl`:
|
||||
```json
|
||||
{"task_id": "your_problem_id", "Question": "Your mathematical problem statement"}
|
||||
```
|
||||
|
||||
## Running the Main Program
|
||||
|
||||
```bash
|
||||
# Run a specific problem (recommended to start with test for testing)
|
||||
python run.py --q test
|
||||
|
||||
|
||||
# Run a range of problems
|
||||
python run.py --start 0 --end 5
|
||||
|
||||
# Run all problems
|
||||
python run.py --start 0 --end 6
|
||||
```
|
||||
|
||||
## Main Features
|
||||
|
||||
1. **Super Agent**: Responsible for solving IMO mathematical problems
|
||||
2. **Guard Agent**: Acts as an IMO grader to verify the correctness of solutions
|
||||
3. **Dialogue Mechanism**: Two agents engage in multi-round conversations to refine solutions
|
||||
4. **Solution Recording**: Records the complete conversation history and final solution
|
||||
|
||||
## Parameter Description
|
||||
|
||||
- `--q`: Specify problem ID (highest priority), e.g., `imo4`
|
||||
- `--specific_task`: Run only a specific task_id, e.g., `imo4` (overrides --start and --end)
|
||||
- `--start/--end`: Specify problem range (0-5 for all 6 IMO problems)
|
||||
- `--skip`: Skip previously processed problems
|
||||
|
||||
## Output Files
|
||||
|
||||
- Log files: `~/.aworld/solution_*.log`
|
||||
- Result files: `~/.aworld/results.json` (contains conversation history and solutions)
|
||||
|
||||
## Environment Information
|
||||
|
||||
- **Environment Name**: `aworld_imo_env`
|
||||
- **Python Version**: 3.11
|
||||
- **Main Dependencies**:
|
||||
- AWorld framework core components
|
||||
- OpenAI client
|
||||
- Environment variable management tools
|
||||
- Other necessary utility packages
|
||||
|
||||
## Advantages
|
||||
|
||||
1. **Environment Isolation**: Avoids dependency conflicts with existing `aworld_gaia_July` environment
|
||||
2. **Lightweight**: Only installs packages necessary for the IMO project
|
||||
3. **Reproducible**: Ensures environment consistency through `requirements.txt`
|
||||
4. **Easy Management**: Independent environment for easy maintenance and cleanup
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. **conda command not found**: Ensure Miniconda or Anaconda is installed
|
||||
2. **Dependency installation failed**: Try manual installation: `pip install -r requirements.txt`
|
||||
3. **AWorld framework installation failed**: Execute manually: `cd ../../../ && pip install -e .`
|
||||
|
||||
## Cleanup
|
||||
|
||||
To remove this environment:
|
||||
|
||||
```bash
|
||||
conda deactivate
|
||||
conda env remove -n aworld_imo_env
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. Ensure the AWorld framework is properly installed
|
||||
2. Check that environment variables are correctly configured
|
||||
3. IMO dataset is pre-configured in the imo folder
|
||||
4. Recommended to start testing with test problem
|
||||
5. The system focuses on solution quality and reasoning process rather than exact answer matching
|
||||
|
||||
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
The IMO-related prompt code in this repository is adapted from the work of Lin Yang and Yichen Huang. We are grateful for their original implementation.
|
||||
|
||||
- Original Repository: [https://github.com/lyang36/IMO25](https://github.com/lyang36/IMO25)
|
||||
@@ -0,0 +1,2 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
@@ -0,0 +1,165 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2025 Lin Yang, Yichen Huang
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
|
||||
import os
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from aworld.agents.llm_agent import Agent
|
||||
from aworld.config.conf import AgentConfig, TaskConfig
|
||||
from aworld.core.task import Task
|
||||
from aworld.runner import Runners
|
||||
|
||||
|
||||
class GuardToolCaller:
|
||||
"""simple reasoning diagnostic tool caller"""
|
||||
|
||||
def __init__(self):
|
||||
# loading env
|
||||
env_path = ".env"
|
||||
load_dotenv(env_path, override=True, verbose=True)
|
||||
|
||||
# initialize guard llm
|
||||
self.guard_llm = self._init_guard_llm()
|
||||
|
||||
def _init_guard_llm(self):
|
||||
"""initialize reasoning diagnostic tool"""
|
||||
try:
|
||||
agent_config = AgentConfig(
|
||||
llm_provider="openai",
|
||||
llm_model_name=os.getenv("IMO_LLM_MODEL_NAME", "deepseek/deepseek-r1-0528:free"),
|
||||
llm_api_key=os.getenv("IMO_LLM_API_KEY"),
|
||||
llm_base_url=os.getenv("IMO_LLM_BASE_URL"),
|
||||
llm_temperature=0.1,
|
||||
)
|
||||
|
||||
guard_llm = Agent(
|
||||
conf=agent_config,
|
||||
name="guard_llm",
|
||||
system_prompt=self._get_guard_system_prompt(),
|
||||
)
|
||||
|
||||
logging.info("reasoning diagnostic tool LLM initialized successfully")
|
||||
return guard_llm
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"reasoning diagnostic tool LLM initialized failed: {e}")
|
||||
return None
|
||||
|
||||
def _get_guard_system_prompt(self) -> str:
|
||||
"""get reasoning diagnostic tool system prompt"""
|
||||
return """You are an expert mathematician and a meticulous grader for an International Mathematical Olympiad (IMO) level exam. Your primary task is to rigorously verify the provided mathematical solution. A solution is to be judged correct only if every step is rigorously justified. A solution that arrives at a correct final answer through flawed reasoning, educated guesses, or with gaps in its arguments must be flagged as incorrect or incomplete.
|
||||
|
||||
### Instructions ###
|
||||
**1. Core Instructions**
|
||||
* Your sole task is to find and report all issues in the provided solution. You must act as a **verifier**, NOT a solver. **Do NOT attempt to correct the errors or fill the gaps you find.**
|
||||
* You must perform a **step-by-step** check of the entire solution. This analysis will be presented in a **Detailed Verification Log**, where you justify your assessment of each step: for correct steps, a brief justification suffices; for steps with errors or gaps, you must provide a detailed explanation.
|
||||
|
||||
**2. How to Handle Issues in the Solution**
|
||||
When you identify an issue in a step, you MUST first classify it into one of the following two categories and then follow the specified procedure.
|
||||
|
||||
* **a. Critical Error:**
|
||||
This is any error that breaks the logical chain of the proof. This includes both **logical fallacies** (e.g., claiming that ‘A > B, C > D’ implies ‘A-C>B-D’) and factual errors (e.g., a calculation error like ‘2+3=6’).
|
||||
* **Procedure:**
|
||||
* Explain the specific error and state that it **invalidates the current line of reasoning**.
|
||||
* Do NOT check any further steps that rely on this error.
|
||||
* You MUST, however, scan the rest of the solution to identify and verify any fully independent parts. For example, if a proof is split into multiple cases, an error in one case does not prevent you from checking the other cases.
|
||||
* **b. Justification Gap:**
|
||||
This is for steps where the conclusion may be correct, but the provided argument is incomplete, hand-wavy, or lacks sufficient rigor.
|
||||
* **Procedure:**
|
||||
* Explain the gap in the justification.
|
||||
* State that you will **assume the step’s conclusion is true** for the sake of argument.
|
||||
* Then, proceed to verify all subsequent steps to check if the remainder of the argument is sound.
|
||||
|
||||
**3. Output Format**
|
||||
Your response MUST be structured into two main sections: a **Summary** followed by the **Detailed Verification Log**.
|
||||
|
||||
* **a. Summary**
|
||||
This section MUST be at the very beginning of your response. It must contain two components:
|
||||
* **Final Verdict**: A single, clear sentence declaring the overall validity of the solution. For example: "The solution is correct," "The solution contains a Critical Error and is therefore invalid," or "The solution’s approach is viable but contains several Justification Gaps."
|
||||
* **List of Findings**: A bulleted list that summarizes **every** issue you discovered. For each finding, you must provide:
|
||||
* **Location:** A direct quote of the key phrase or equation where the issue occurs.
|
||||
* **Issue:** A brief description of the problem and its classification (**Critical Error** or **Justification Gap**).
|
||||
|
||||
* **b. Detailed Verification Log**
|
||||
Following the summary, provide the full, step-by-step verification log as defined in the Core Instructions. When you refer to a specific part of the solution, **quote the relevant text** to make your reference clear before providing your detailed analysis of that part.
|
||||
|
||||
**Example of the Required Summary Format**
|
||||
*This is a generic example to illustrate the required format. Your findings must be based on the actual solution provided below.*
|
||||
|
||||
**Final Verdict:** The solution is **invalid** because it contains a Critical Error.
|
||||
|
||||
**List of Findings:**
|
||||
* **Location:** "By interchanging the limit and the integral, we get ..."
|
||||
* **Issue:** Justification Gap - The solution interchanges a limit and an integral without providing justification, such as proving uniform convergence.
|
||||
* **Location: **"From $A > B$ and $C > D$, it follows that $A−C>B−D$"
|
||||
* **Issue:** Critical Error - This step is a logical fallacy. Subtracting inequalities in this manner is not a valid mathematical operation.
|
||||
================================================================================================================================================================
|
||||
### Problem ###
|
||||
|
||||
[Paste the TeX for the problem statement here]
|
||||
|
||||
================================================================================================================================================================
|
||||
### Solution ###
|
||||
|
||||
[Paste the TeX for the solution to be verified here]
|
||||
|
||||
================================================================================================================================================================
|
||||
### Verification Task Reminder ###
|
||||
Your task is to act as an IMO grader. Now, generate the **summary** and the **step-by-step verification log** for the solution above. In your log, justify each correct step and explain in detail any errors or justification gaps you find, as specified in the instructions above."""
|
||||
|
||||
async def call_guard_tool(self, super_output: str, original_task: str) -> str:
|
||||
"""call reasoning diagnostic tool"""
|
||||
try:
|
||||
if not self.guard_llm:
|
||||
return "reasoning diagnostic tool is not availble"
|
||||
|
||||
# construct the input
|
||||
input_text = f"""Here is the provided solution:
|
||||
{super_output}
|
||||
|
||||
Here is the original task:
|
||||
{original_task}
|
||||
|
||||
Please do your job as expert mathematician and a meticulous grader for an International Mathematical Olympiad (IMO) level exam."""
|
||||
|
||||
# create task
|
||||
task = Task(
|
||||
input=input_text,
|
||||
agent=self.guard_llm,
|
||||
conf=TaskConfig()
|
||||
)
|
||||
|
||||
# run task
|
||||
result = Runners.sync_run_task(task=task)
|
||||
|
||||
# extract the answer
|
||||
if result and task.id in result:
|
||||
return result[task.id].answer
|
||||
else:
|
||||
return "The reasoning diagnostic tool did not return valid results."
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to invoke the reasoning diagnostic tool: {e}")
|
||||
return f"Failed to invoke the reasoning diagnostic tool.: {str(e)}"
|
||||
@@ -0,0 +1,7 @@
|
||||
{"task_id": "imo1", "Question": "A line in the plane is called sunny if it is not parallel to any of the x–axis, the y–axis, or the line x + y = 0. Let n ≥ 3 be a given integer. Determine all nonnegative integers k such that there exist n distinct lines in the plane satisfying both of the following: 1. for all positive integers a and b with a + b ≤ n + 1, the point (a, b) lies on at least one of the lines; and 2. exactly k of the n lines are sunny."}
|
||||
{"task_id": "imo2", "Question": "Let Ω and Γ be circles with centres M and N, respectively, such that the radius of Ω is less than the radius of Γ. Suppose Ω and Γ intersect at two distinct points A and B. Line MN intersects Ω at C and Γ at D, so that C, M, N, D lie on MN in that order. Let P be the circumcenter of triangle ACD. Line AP meets Ω again at E != A and meets Γ again at F != A. Let H be the orthocenter of triangle PMN. Prove that the line through H parallel to AP is tangent to the circumcircle of triangle BEF."}
|
||||
{"task_id": "imo3", "Question": "A function f: N → N is said to be bonza if f(a) divides b^a − f(b)^f(a) for all positive integers a and b. Determine the smallest real constant c such that f(n) ≤ cn for all bonza functions f and all positive integers n."}
|
||||
{"task_id": "imo4", "Question": "An infinite sequence a_1, a_2, . . . consists of positive integers has each of which has at least three proper divisors. Suppose that for each n ≥ 1, a_n+1 is the sum of the three largest proper divisors of an. Determine all possible values of a_1."}
|
||||
{"task_id": "imo5", "Question": "Alice and Bazza are playing the inekoalaty game, a two‑player game whose rules depend on a positive real number λ which is known to both players. On the nth turn of the game (starting with n = 1) the following happens:• If n is odd, Alice chooses a nonnegative real number xn such that x_1 + x_2 + ... + x_n ≤ λn. • If n is even, Bazza chooses a nonnegative real number xn such that (x_1)^2 + (x_2)^2 + ...+ (x_n)^2 ≤ n. If a player cannot choose a suitable x_n, the game ends and the other player wins. If the game goes on forever, neither player wins. All chosen numbers are known to both players. Determine all values of λ for which Alice has a winning strategy and all those for which Bazza has a winning strategy."}
|
||||
{"task_id": "imo6", "Question": "Consider a 2025 × 2025 grid of unit squares. Matilda wishes to place on the grid some rectangular tiles, possibly of different sizes, such that each side of every tile lies on a grid line and every unit square is covered by at most one tile. Determine the minimum number of tiles Matilda needs to place so that each row and each column of the grid has exactly one unit square that is not covered by any tile."}
|
||||
{"task_id": "test", "Question": "1+1=?"}
|
||||
@@ -0,0 +1,62 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2025 Lin Yang, Yichen Huang
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
|
||||
system_prompt = """
|
||||
### Core Instructions ###
|
||||
|
||||
* ** Rigor is Paramount:** Your primary goal is to produce a complete and rigorously justified solution. Every step in your solution must be logically sound and clearly explained. A correct final answer derived from flawed or incomplete reasoning is considered a failure.
|
||||
|
||||
* ** Honesty About Completeness:** If you cannot find a complete solution, you must **not** guess or create a solution that appears correct but contains hidden flaws or justification gaps. Instead, you should present only significant partial results that you can rigorously prove. A partial result is considered significant if it represents a substantial advancement toward a full solution. Examples include:
|
||||
* Proving a key lemma.
|
||||
* Fully resolving one or more cases within a logically sound case - based proof.
|
||||
* Establishing a critical property of the mathematical objects in the problem.
|
||||
* For an optimization problem, proving an upper or lower bound without proving that this bound is achievable.
|
||||
|
||||
* ** Use TeX for All Mathematics:** All mathematical variables, expressions, and relations must be enclosed in TeX delimiters (e.g., ‘Let $n$ be an integer.‘).
|
||||
|
||||
|
||||
### Output Format ###
|
||||
|
||||
Your response MUST be structured into the following sections, in this exact order.
|
||||
|
||||
**1. Summary **
|
||||
|
||||
Provide a concise overview of your findings. This section must contain two parts:
|
||||
|
||||
* ** a. Verdict:** State clearly whether you have found a complete solution or a partial solution.
|
||||
* ** For a complete solution :** State the final answer, e.g., "I have successfully solved the problem. The final answer is ..."
|
||||
* ** For a partial solution :** State the main rigorous conclusion (s) you were able to prove, e.g., "I have not found a complete solution, but I have rigorously proven that ..."
|
||||
* ** b. Method Sketch:** Present a high-level, conceptual outline of your solution. This sketch should allow an expert to understand the logical flow of your argument without reading the full detail. It should include:
|
||||
* A narrative of your overall strategy.
|
||||
* The full and precise mathematical statements of any key lemmas or major intermediate results.
|
||||
* If applicable, describe any key constructions or case splits that form the backbone of your argument.
|
||||
|
||||
**2. Detailed Solution**
|
||||
|
||||
Present the full , step-by-step mathematical proof. Each step must be logically justified and clearly explained. The level of detail should be sufficient for an expert to verify the correctness of your reasoning without needing to fill in any gaps. This section must contain ONLY the complete, rigorous proof, free of any internal commentary, alternative approaches, or failed attempts.
|
||||
|
||||
|
||||
### Self-Correction Instruction ###
|
||||
|
||||
Before finalizing your output, carefully review your "Method Sketch" and "Detailed Solution" to ensure they are clean, rigorous, and strictly adhere to all instructions provided above. Verify that every statement contributes directly to the final, coherent mathematical argument.
|
||||
"""
|
||||
@@ -0,0 +1,24 @@
|
||||
# IMO Project Requirements
|
||||
# Core AWorld Framework Dependencies
|
||||
pydantic>=2.9.2
|
||||
pyyaml~=6.0.2
|
||||
openai~=1.66.3
|
||||
mcp[cli]~=1.10.1
|
||||
python-dotenv>=1.0.1
|
||||
executing~=2.2.0
|
||||
tiktoken~=0.9.0
|
||||
fastapi
|
||||
aiohttp~=3.9.5
|
||||
|
||||
# Additional dependencies for IMO project
|
||||
requests
|
||||
loguru
|
||||
pathvalidate
|
||||
packaging
|
||||
tabulate
|
||||
|
||||
# For potential future extensions (optional)
|
||||
# numpy
|
||||
# pandas
|
||||
# matplotlib
|
||||
# sympy # for mathematical computations
|
||||
@@ -0,0 +1,306 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
|
||||
from aworld.agents.llm_agent import Agent
|
||||
from aworld.config.conf import AgentConfig, TaskConfig
|
||||
from aworld.core.task import Task
|
||||
from aworld.runner import Runners
|
||||
from aworld.core.task import Task
|
||||
from prompt import system_prompt
|
||||
from utils import (
|
||||
add_file_path,
|
||||
load_dataset_meta,
|
||||
)
|
||||
from guard_tool_caller import GuardToolCaller
|
||||
|
||||
# Create log directory if it doesn't exist
|
||||
if not os.path.exists(os.getenv("AWORLD_WORKSPACE", "~")):
|
||||
os.makedirs(os.getenv("AWORLD_WORKSPACE", "~"))
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--q",
|
||||
type=str,
|
||||
help="Question Index, e.g., imo6. Highest priority: override other arguments if provided.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip",
|
||||
action="store_true",
|
||||
help="Skip the question if it has been processed before.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def setup_logging():
|
||||
logging_logger = logging.getLogger()
|
||||
logging_logger.setLevel(logging.INFO)
|
||||
|
||||
log_file_name = f"/solution_{args.q}.log" if args.q else f"/solution.log"
|
||||
file_handler = logging.FileHandler(
|
||||
os.getenv("AWORLD_WORKSPACE", "~") + log_file_name,
|
||||
mode="a",
|
||||
encoding="utf-8",
|
||||
)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
file_handler.setFormatter(formatter)
|
||||
|
||||
logging_logger.addHandler(file_handler)
|
||||
|
||||
|
||||
class GuardRunner:
|
||||
"""The guard tool runner"""
|
||||
|
||||
def __init__(self, super_agent: Agent, guard_tool_caller: GuardToolCaller, original_task: str):
|
||||
self.super_agent = super_agent
|
||||
self.guard_tool_caller = guard_tool_caller
|
||||
self.original_task = original_task
|
||||
self.conversation_history = []
|
||||
self.max_iterations = 10 # max conversation rounds
|
||||
|
||||
async def run_conversation(self, question: str) -> str:
|
||||
"""run the conversation"""
|
||||
current_input = question
|
||||
iteration = 0
|
||||
|
||||
while iteration < self.max_iterations:
|
||||
logging.info(f"=== The {iteration + 1} round of the conversation ===")
|
||||
|
||||
# 1. Super-agent handles the current input
|
||||
logging.info(f"Super-agent input: {current_input[:100]}...")
|
||||
super_output = await self._call_super_agent(current_input)
|
||||
logging.info(f"Super-agent output: {super_output[:200]}...")
|
||||
|
||||
# 2. Check if the output contains the final answer
|
||||
if self._has_final_answer(super_output) and iteration > 1:
|
||||
logging.info("find the final answer, the conversation should stop")
|
||||
return super_output
|
||||
|
||||
if iteration == self.max_iterations - 1:
|
||||
logging.info("reach the max conversation rounds, the conversation should stop and return the final answer")
|
||||
return super_output
|
||||
|
||||
# 3. Call the guard tool
|
||||
logging.info("guard tool is being called...")
|
||||
guard_output = await self._call_guard_tool(super_output)
|
||||
logging.info(f"guard tool output: {guard_output[:200]}...")
|
||||
|
||||
# 4. Record the current round of the conversation history (before preparing the next input)
|
||||
self.conversation_history.append({
|
||||
"iteration": iteration + 1,
|
||||
"super_input": current_input,
|
||||
"super_output": super_output,
|
||||
"guard_output": guard_output
|
||||
})
|
||||
|
||||
# 5. Prepare the next input
|
||||
next_input = self._prepare_next_input(super_output, guard_output)
|
||||
current_input = next_input
|
||||
|
||||
iteration += 1
|
||||
|
||||
# Check if the conversation should continue
|
||||
if self._should_stop_conversation(super_output, guard_output):
|
||||
logging.info("the conversation should stop")
|
||||
break
|
||||
|
||||
logging.info(f"the converstation finished, there are totally {iteration} rounds")
|
||||
return super_output
|
||||
|
||||
async def _call_super_agent(self, input_text: str) -> str:
|
||||
"""call the super-agent"""
|
||||
try:
|
||||
# create the task
|
||||
task = Task(
|
||||
input=input_text,
|
||||
agent=self.super_agent,
|
||||
conf=TaskConfig()
|
||||
)
|
||||
|
||||
# run the task
|
||||
result = Runners.sync_run_task(task=task)
|
||||
|
||||
# extract the answer
|
||||
if result and task.id in result:
|
||||
return result[task.id].answer
|
||||
else:
|
||||
return "Super-agent fail to return the result"
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"fail to call the super-agent: {e}")
|
||||
return f"fail to call the super-agent: {str(e)}"
|
||||
|
||||
async def _call_guard_tool(self, super_output: str) -> str:
|
||||
"""call the guard tool"""
|
||||
try:
|
||||
# call the guard tool
|
||||
guard_result = await self.guard_tool_caller.call_guard_tool(super_output, self.original_task)
|
||||
return guard_result
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"fail to call the guard tool: {e}")
|
||||
return f"fail to call the guard tool: {str(e)}"
|
||||
|
||||
def _has_final_answer(self, output: str) -> bool:
|
||||
"""check if the output contains the final answer"""
|
||||
answer_patterns = [
|
||||
r"<answer>.*?</answer>",
|
||||
r"Final answer[::]\s*",
|
||||
r"final answer[::]\s*",
|
||||
r"The final answer is[::]\s*",
|
||||
r"the final answer is[::]\s*",
|
||||
r"the final answer is\s*",
|
||||
r"I have successfully solved the problem."
|
||||
]
|
||||
|
||||
for pattern in answer_patterns:
|
||||
if re.search(pattern, output, re.IGNORECASE):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _prepare_next_input(self, super_output: str, guard_output: str) -> str:
|
||||
"""Prepare for the next round with complete conversation history"""
|
||||
# build the complete conversation history
|
||||
conversation_text = ""
|
||||
|
||||
# add the original task
|
||||
conversation_text += f"Original Task:\n{self.original_task}\n\n"
|
||||
|
||||
# add all the history conversation rounds (not including the current round)
|
||||
for i, history in enumerate(self.conversation_history):
|
||||
round_num = history["iteration"]
|
||||
conversation_text += f"=== Round {round_num} ===\n"
|
||||
conversation_text += f"Previous solution:\n{history['super_output']}\n\n"
|
||||
conversation_text += f"IMO grader review:\n{history['guard_output']}\n\n"
|
||||
|
||||
# build the final input
|
||||
combined_input = f"""
|
||||
{conversation_text}
|
||||
Please seriously consider all the above reviews from the IMO grader across all rounds, and provide a refined and improved solution that addresses all the issues identified.
|
||||
"""
|
||||
return combined_input.strip()
|
||||
|
||||
def _should_stop_conversation(self, super_output: str, guard_output: str) -> bool:
|
||||
"""check if the conversation should stop"""
|
||||
# check if the guard tool thinks the conversation should stop
|
||||
stop_indicators = [
|
||||
"The answer is completed",
|
||||
"No need to further refine",
|
||||
"The question has been correctly solved",
|
||||
"No loopholes or oversights found"
|
||||
]
|
||||
|
||||
for indicator in stop_indicators:
|
||||
if indicator in guard_output:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
env_path = ".env"
|
||||
load_dotenv(env_path, override=True, verbose=True)
|
||||
setup_logging()
|
||||
|
||||
imo_dataset_path = os.getenv("IMO_DATASET_PATH", "./imo_dataset")
|
||||
full_dataset = load_dataset_meta(imo_dataset_path)
|
||||
logging.info(f"Total questions: {len(full_dataset)}")
|
||||
|
||||
# create the super-agent (without MCP tools)
|
||||
agent_config = AgentConfig(
|
||||
llm_provider="openai",
|
||||
llm_model_name=os.getenv("LLM_MODEL_NAME", "gpt-4o"),
|
||||
llm_api_key=os.getenv("LLM_API_KEY"),
|
||||
llm_base_url=os.getenv("LLM_BASE_URL"),
|
||||
llm_temperature=0.1,
|
||||
)
|
||||
super_agent = Agent(
|
||||
conf=agent_config,
|
||||
name="gaia_super_agent",
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
|
||||
# create the guard tool caller
|
||||
guard_tool_caller = GuardToolCaller()
|
||||
|
||||
# load results from the checkpoint file
|
||||
if os.path.exists(os.getenv("AWORLD_WORKSPACE", "~") + "/results.json"):
|
||||
with open(os.getenv("AWORLD_WORKSPACE", "~") + "/results.json", "r", encoding="utf-8") as results_f:
|
||||
results: List[Dict[str, Any]] = json.load(results_f)
|
||||
else:
|
||||
results: List[Dict[str, Any]] = []
|
||||
|
||||
try:
|
||||
# appoint the task+id
|
||||
if args.q is not None:
|
||||
dataset_slice = [dataset_record for dataset_record in full_dataset if dataset_record["task_id"] == args.q]
|
||||
if not dataset_slice:
|
||||
logging.error(f"Task ID '{args.q}' not found in dataset")
|
||||
sys.exit()
|
||||
else:
|
||||
logging.error("Please specify a task_id using --q parameter")
|
||||
sys.exit()
|
||||
|
||||
# main loop to execute questions
|
||||
for i, dataset_i in enumerate(dataset_slice):
|
||||
# run
|
||||
try:
|
||||
logging.info(f"Start to process: {dataset_i['task_id']}")
|
||||
logging.info(f"Question: {dataset_i['Question']}")
|
||||
|
||||
question = add_file_path(dataset_i, file_path=imo_dataset_path)["Question"]
|
||||
|
||||
# use the guard tool runner
|
||||
guard_runner = GuardRunner(
|
||||
super_agent=super_agent,
|
||||
guard_tool_caller=guard_tool_caller,
|
||||
original_task=question
|
||||
)
|
||||
|
||||
# run the conversation
|
||||
import asyncio
|
||||
result = asyncio.run(guard_runner.run_conversation(question))
|
||||
|
||||
# Create the new result record
|
||||
new_result = {
|
||||
"task_id": dataset_i["task_id"],
|
||||
"question": question,
|
||||
"response": result,
|
||||
"conversation_history": guard_runner.conversation_history,
|
||||
}
|
||||
|
||||
# Check if this task_id already exists in results
|
||||
existing_index = next(
|
||||
(i for i, result in enumerate(results) if result.get("task_id") == dataset_i["task_id"]),
|
||||
None,
|
||||
)
|
||||
|
||||
if existing_index is not None:
|
||||
# Update existing record
|
||||
results[existing_index] = new_result
|
||||
logging.info(f"Updated existing record for task_id: {dataset_i['task_id']}")
|
||||
else:
|
||||
# Append new record
|
||||
results.append(new_result)
|
||||
logging.info(f"Added new record for task_id: {dataset_i['task_id']}")
|
||||
|
||||
except Exception:
|
||||
logging.error(f"Error processing {i}: {traceback.format_exc()}")
|
||||
continue
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
# Save results to file
|
||||
with open(os.getenv("AWORLD_WORKSPACE", "~") + "/results.json", "w", encoding="utf-8") as f:
|
||||
json.dump(results, f, indent=4, ensure_ascii=False)
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
|
||||
# IMO Project Environment Setup Script
|
||||
# This script creates a new conda environment and installs all necessary dependencies
|
||||
|
||||
echo "🚀 Setting up IMO Project Environment..."
|
||||
|
||||
# Check if conda is available
|
||||
if ! command -v conda &> /dev/null; then
|
||||
echo "❌ Error: conda is not installed or not in PATH"
|
||||
echo "Please install Miniconda or Anaconda first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Environment name
|
||||
ENV_NAME="imo_env"
|
||||
|
||||
echo "📦 Creating conda environment: $ENV_NAME"
|
||||
|
||||
# Create new conda environment with Python 3.11
|
||||
conda create -n $ENV_NAME python=3.11 -y
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to create conda environment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Conda environment created successfully"
|
||||
|
||||
# Activate the environment
|
||||
echo "🔄 Activating environment..."
|
||||
source $(conda info --base)/etc/profile.d/conda.sh
|
||||
conda activate $ENV_NAME
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to activate conda environment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Environment activated"
|
||||
|
||||
# Install requirements
|
||||
echo "📥 Installing dependencies from requirements.txt..."
|
||||
pip install -r requirements.txt
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to install dependencies"
|
||||
echo "You can try installing them manually:"
|
||||
echo "conda activate $ENV_NAME"
|
||||
echo "pip install -r requirements.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Dependencies installed successfully"
|
||||
|
||||
# Install AWorld framework in development mode
|
||||
echo "🔧 Installing AWorld framework..."
|
||||
cd ../../../
|
||||
pip install -e .
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "⚠️ Warning: Failed to install AWorld framework in development mode"
|
||||
echo "You may need to install it manually:"
|
||||
echo "cd ../../../ && pip install -e ."
|
||||
fi
|
||||
|
||||
echo "✅ AWorld framework installed"
|
||||
|
||||
# Go back to imo directory
|
||||
cd AWorld/examples/imo
|
||||
|
||||
echo ""
|
||||
echo "🎉 Environment setup completed successfully!"
|
||||
echo ""
|
||||
echo "To use this environment:"
|
||||
echo "1. Activate the environment: conda activate $ENV_NAME"
|
||||
echo "2. Navigate to the imo directory: cd AWorld/examples/imo"
|
||||
echo "3. Run your script: python run.py --q imo6"
|
||||
echo ""
|
||||
echo "To deactivate the environment: conda deactivate"
|
||||
@@ -0,0 +1,272 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from tabulate import tabulate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def normalize_str(input_str, remove_punct=True) -> str:
|
||||
no_spaces = re.sub(r"\s", "", input_str)
|
||||
if remove_punct:
|
||||
translator = str.maketrans("", "", string.punctuation)
|
||||
return no_spaces.lower().translate(translator)
|
||||
else:
|
||||
return no_spaces.lower()
|
||||
|
||||
|
||||
def split_string(s: str, char_list: Optional[List[str]] = None) -> list[str]:
|
||||
if char_list is None:
|
||||
char_list = [",", ";"]
|
||||
pattern = f"[{''.join(char_list)}]"
|
||||
return re.split(pattern, s)
|
||||
|
||||
|
||||
def normalize_number_str(number_str: str) -> float:
|
||||
for char in ["$", "%", ","]:
|
||||
number_str = number_str.replace(char, "")
|
||||
try:
|
||||
return float(number_str)
|
||||
except ValueError:
|
||||
logger.error(f"String {number_str} cannot be normalized to number str.")
|
||||
return float("inf")
|
||||
|
||||
|
||||
def question_scorer(model_answer: str, ground_truth: str) -> bool:
|
||||
def is_float(element: Any) -> bool:
|
||||
try:
|
||||
float(element)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
try:
|
||||
if is_float(ground_truth):
|
||||
logger.info(f"Evaluating {model_answer} as a number.")
|
||||
normalized_answer = normalize_number_str(model_answer)
|
||||
return normalized_answer == float(ground_truth)
|
||||
|
||||
elif any(char in ground_truth for char in [",", ";"]):
|
||||
logger.info(f"Evaluating {model_answer} as a comma separated list.")
|
||||
gt_elems = split_string(ground_truth)
|
||||
ma_elems = split_string(model_answer)
|
||||
|
||||
if len(gt_elems) != len(ma_elems):
|
||||
logger.warning("Answer lists have different lengths, returning False.")
|
||||
return False
|
||||
|
||||
comparisons = []
|
||||
for ma_elem, gt_elem in zip(ma_elems, gt_elems):
|
||||
if is_float(gt_elem):
|
||||
normalized_ma_elem = normalize_number_str(ma_elem)
|
||||
comparisons.append(normalized_ma_elem == float(gt_elem))
|
||||
else:
|
||||
ma_elem = normalize_str(ma_elem, remove_punct=False)
|
||||
gt_elem = normalize_str(gt_elem, remove_punct=False)
|
||||
comparisons.append(ma_elem == gt_elem)
|
||||
return all(comparisons)
|
||||
else:
|
||||
logger.info(f"Evaluating {model_answer} as a string.")
|
||||
ma_elem = normalize_str(model_answer)
|
||||
gt_elem = normalize_str(ground_truth)
|
||||
return ma_elem == gt_elem
|
||||
except Exception as e:
|
||||
logger.error(f"Error during evaluation: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def load_dataset_meta(path: str):
|
||||
# For IMO dataset, metadata.jsonl is directly placed in the imo folder
|
||||
data_dir = Path(path)
|
||||
|
||||
dataset = []
|
||||
metadata_file = data_dir / "metadata.jsonl"
|
||||
|
||||
if not metadata_file.exists():
|
||||
logger.error(f"Metadata file not found: {metadata_file}")
|
||||
return []
|
||||
|
||||
with open(metadata_file, "r", encoding="utf-8") as metaf:
|
||||
lines = metaf.readlines()
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
try:
|
||||
# Clean trailing commas at the end of lines
|
||||
line = line.strip().rstrip(',')
|
||||
if not line:
|
||||
continue
|
||||
|
||||
data = json.loads(line)
|
||||
if data["task_id"] == "0-0-0-0-0":
|
||||
continue
|
||||
# IMO dataset may not have file_name field
|
||||
if "file_name" in data and data["file_name"]:
|
||||
data["file_name"] = data_dir / data["file_name"]
|
||||
dataset.append(data)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"JSON decode error at line {line_num}: {e}")
|
||||
logger.warning(f"Problematic line: {line[:100]}...")
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing line {line_num}: {e}")
|
||||
continue
|
||||
return dataset
|
||||
|
||||
|
||||
def load_dataset_meta_dict(path: str, split: str = "validation"):
|
||||
data_dir = Path(path) / split
|
||||
|
||||
dataset = {}
|
||||
with open(data_dir / "metadata.jsonl", "r", encoding="utf-8") as metaf:
|
||||
lines = metaf.readlines()
|
||||
for line in lines:
|
||||
data = json.loads(line)
|
||||
if data["task_id"] == "0-0-0-0-0":
|
||||
continue
|
||||
if data["file_name"]:
|
||||
data["file_name"] = data_dir / data["file_name"]
|
||||
dataset[data["task_id"]] = data
|
||||
return dataset
|
||||
|
||||
|
||||
def add_file_path(task: Dict[str, Any], file_path: str = "./imo_dataset"):
|
||||
if "file_name" in task and task["file_name"]:
|
||||
# For IMO dataset, file paths may need adjustment
|
||||
base_path = Path(file_path)
|
||||
file_path = base_path / task["file_name"]
|
||||
|
||||
if file_path.suffix in [".pdf", ".docx", ".doc", ".txt"]:
|
||||
task["Question"] += f" Here are the necessary document files: {file_path}"
|
||||
|
||||
elif file_path.suffix in [".jpg", ".jpeg", ".png"]:
|
||||
task["Question"] += f" Here are the necessary image files: {file_path}"
|
||||
|
||||
elif file_path.suffix in [".xlsx", "xls", ".csv"]:
|
||||
task["Question"] += (
|
||||
f" Here are the necessary table files: {file_path}, for processing excel file,"
|
||||
" you can use the excel tool or write python code to process the file"
|
||||
" step-by-step and get the information."
|
||||
)
|
||||
elif file_path.suffix in [".py"]:
|
||||
task["Question"] += f" Here are the necessary python files: {file_path}"
|
||||
|
||||
else:
|
||||
task["Question"] += f" Here are the necessary files: {file_path}"
|
||||
|
||||
return task
|
||||
|
||||
|
||||
def report_results(entries):
|
||||
# Initialize counters
|
||||
total_entries = len(entries)
|
||||
total_correct = 0
|
||||
|
||||
# Initialize level statistics
|
||||
level_stats = {}
|
||||
|
||||
# Process each entry
|
||||
for entry in entries:
|
||||
level = entry.get("level")
|
||||
is_correct = entry.get("is_correct", False)
|
||||
|
||||
# Initialize level stats if not already present
|
||||
if level not in level_stats:
|
||||
level_stats[level] = {"total": 0, "correct": 0, "accuracy": 0}
|
||||
|
||||
# Update counters
|
||||
level_stats[level]["total"] += 1
|
||||
if is_correct:
|
||||
total_correct += 1
|
||||
level_stats[level]["correct"] += 1
|
||||
|
||||
# Calculate accuracy for each level
|
||||
for level, stats in level_stats.items():
|
||||
if stats["total"] > 0:
|
||||
stats["accuracy"] = (stats["correct"] / stats["total"]) * 100
|
||||
|
||||
# Print overall statistics with colorful logging
|
||||
logger.info("Overall Statistics:")
|
||||
overall_accuracy = (total_correct / total_entries) * 100
|
||||
|
||||
# Create overall statistics table
|
||||
overall_table = [
|
||||
["Total Entries", total_entries],
|
||||
["Total Correct", total_correct],
|
||||
["Overall Accuracy", f"{overall_accuracy:.2f}%"],
|
||||
]
|
||||
logger.info(tabulate(overall_table, tablefmt="grid"))
|
||||
logger.info("")
|
||||
|
||||
# Create level statistics table
|
||||
logger.info("Statistics by Level:")
|
||||
level_table = []
|
||||
headers = ["Level", "Total Entries", "Correct Answers", "Accuracy"]
|
||||
|
||||
for level in sorted(level_stats.keys()):
|
||||
stats = level_stats[level]
|
||||
level_table.append([level, stats["total"], stats["correct"], f"{stats['accuracy']:.2f}%"])
|
||||
|
||||
logger.info(tabulate(level_table, headers=headers, tablefmt="grid"))
|
||||
|
||||
|
||||
def setup_logger(logger_name, output_folder_path, file_name="main.log"):
|
||||
"""
|
||||
Set up a logger with the given name that writes to the specified file.
|
||||
Returns a configured logger instance.
|
||||
"""
|
||||
if not os.path.exists(output_folder_path):
|
||||
os.makedirs(output_folder_path)
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
|
||||
log_file = os.path.join(output_folder_path, file_name)
|
||||
|
||||
# Check if the logger already has handlers to avoid duplicates
|
||||
logger = logging.getLogger(logger_name)
|
||||
|
||||
# Remove existing handlers if any
|
||||
if logger.hasHandlers():
|
||||
for handler in logger.handlers[:]:
|
||||
logger.removeHandler(handler)
|
||||
|
||||
# Add file handler
|
||||
handler = logging.FileHandler(log_file, mode="a", encoding="utf-8")
|
||||
handler.setLevel(logging.INFO)
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.addHandler(handler)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
def color_log(logger: logging.Logger, value: str, color: Optional[str], level: str | None = None):
|
||||
# Default to 'info' level if none specified
|
||||
if level is None:
|
||||
level = "info"
|
||||
|
||||
# Format the message with color
|
||||
if color is None:
|
||||
message = f"{value}"
|
||||
else:
|
||||
message = f"{color}{value}"
|
||||
|
||||
# Log according to the specified level
|
||||
level_lower = level.lower()
|
||||
if level_lower == "debug":
|
||||
logger.debug(message)
|
||||
elif level_lower == "info":
|
||||
logger.info(message)
|
||||
elif level_lower == "warning" or level_lower == "warn":
|
||||
logger.warning(message)
|
||||
elif level_lower == "error":
|
||||
logger.error(message)
|
||||
elif level_lower == "critical":
|
||||
logger.critical(message)
|
||||
else:
|
||||
# Default to info for unknown levels
|
||||
logger.info(message)
|
||||
Reference in New Issue
Block a user