mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-08-04 18:57:44 +00:00
chore: initial Predix state (RD-Agent fork + EURUSD setup)
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
[bumpversion]
|
||||
current_version = 0.0.0
|
||||
commit = True
|
||||
tag = True
|
||||
|
||||
[bumpversion:file:pyproject.toml]
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
rules: {
|
||||
// Configuration Format: [level, applicability, value]
|
||||
// level: Error level, usually expressed as a number:
|
||||
// 0 - disable rule
|
||||
// 1 - Warning (does not prevent commits)
|
||||
// 2 - Error (will block the commit)
|
||||
// applicability: the conditions under which the rule applies, commonly used values:
|
||||
// “always” - always apply the rule
|
||||
// “never” - never apply the rule
|
||||
// value: the specific value of the rule, e.g. a maximum length of 100.
|
||||
// Refs: https://commitlint.js.org/reference/rules-configuration.html
|
||||
"header-max-length": [2, "always", 100],
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "Release-As"]
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
# 1. Pull down your Azure Container Registry image
|
||||
FROM rdagentappregistry.azurecr.io/rd-agent-mle:20250623
|
||||
|
||||
# 2. (Optional) install any additional tools you need
|
||||
# e.g. git, bash-completion, etc.
|
||||
# RUN apt update && \
|
||||
# apt install -y git bash-completion && \
|
||||
# rm -rf /var/lib/apt/lists/*
|
||||
RUN apt update && \
|
||||
apt install -y git bash-completion
|
||||
@@ -1,39 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
!!!!!This dev container is not for public development!!!!!!
|
||||
!!!!!Please don't use it if you are just a public open-source user.!!!!!!
|
||||
|
||||
# Steps to run the dev container (for internal use only)
|
||||
|
||||
Prerequisites(this is the reason why this dev container is not for public use):
|
||||
|
||||
- Make sure you have the `rdagentappregistry.azurecr.io/rd-agent-mle:20250623` image locally & DevContainer is installed in your IDE
|
||||
- The kaggle dataset is located at `/home/shared/RD-Agent/kaggle`
|
||||
|
||||
1. Open the project and select "Open In DevContainer"
|
||||
2. Set up your Kaggle Key (do not share this; other internal URLs are hardcoded in the config files)
|
||||
|
||||
```bash
|
||||
export KAGGLE_USERNAME=
|
||||
export KAGGLE_KEY=
|
||||
```
|
||||
|
||||
3. Run: python rdagent/app/data_science/loop.py --competition nomad2018-predict-transparent-conductors
|
||||
|
||||
|
||||
# Additional Notes
|
||||
- Please install and use this Dev Container in VS Code.
|
||||
- You **must open VS Code remotely and enter the `RD-Agent` directory before running the DevContainer configuration (`.devcontainer/devcontainer.json`)**. Otherwise, the workspace and path mappings will not work as expected.
|
||||
- To open the DevContainer correctly in VS Code:
|
||||
1. Remotely connect to the machine and open the `RD-Agent` folder in VS Code.
|
||||
2. Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac), type and select **"Dev Containers: Reopen in Container"**.
|
||||
|
||||
|
||||
|
||||
# How to grade your submission in the DevContainer
|
||||
|
||||
1. save your submission file in `./sumission.csv`
|
||||
|
||||
2. Run evaluation
|
||||
DS_COMPETITION=<your competition name>
|
||||
conda run -n mlebench mlebench grade-sample submission.csv $DS_COMPETITION --data-dir /tmp/kaggle/zip_files/
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "rd-agent-mle DevContainer",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"context": ".."
|
||||
},
|
||||
"workspaceFolder": "/workspace/RD-Agent",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace/RD-Agent,type=bind,consistency=cached",
|
||||
"remoteUser": "root",
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash"
|
||||
},
|
||||
"mounts": [
|
||||
"source=/home/shared/RD-Agent/kaggle,target=/tmp/kaggle,type=bind,consistency=cached,readonly"
|
||||
],
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-toolsai.jupyter"
|
||||
],
|
||||
"runArgs": [
|
||||
"--init",
|
||||
"--shm-size=1g",
|
||||
"--env-file", "${localWorkspaceFolder}/.devcontainer/env",
|
||||
"--network=host",
|
||||
"--gpus=all"
|
||||
],
|
||||
"postCreateCommand": "make dev"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
# Global configs:
|
||||
|
||||
MAX_RETRY=12000
|
||||
RETRY_WAIT_SECONDS=5
|
||||
TIMEOUT_FAIL_LIMIT=100
|
||||
|
||||
# litellm
|
||||
# CHAT_MODEL=gpt-4o
|
||||
# CHAT_TEMPERATURE=0.7
|
||||
|
||||
CHAT_STREAM=False
|
||||
CHAT_TEMPERATURE=1
|
||||
CHAT_MODEL=o1-preview
|
||||
SYSTEM_PROMPT_ROLE=user
|
||||
|
||||
BACKEND=rdagent.oai.backend.LiteLLMAPIBackend
|
||||
OPENAI_API_KEY=sk-1234
|
||||
OPENAI_API_BASE=http://ep14.213428.xyz:38881
|
||||
|
||||
|
||||
# amc chat model configs:
|
||||
EMBEDDING_MODEL=text-embedding-ada-002
|
||||
|
||||
# Cache Setting (Optional):
|
||||
DUMP_CHAT_CACHE=True
|
||||
USE_CHAT_CACHE=False
|
||||
DUMP_EMBEDDING_CACHE=True
|
||||
USE_EMBEDDING_CACHE=False
|
||||
LOG_LLM_CHAT_CONTENT=True
|
||||
|
||||
DS_LOCAL_DATA_PATH=/tmp/kaggle
|
||||
|
||||
DS_IF_USING_MLE_DATA=True
|
||||
|
||||
|
||||
PICKLE_CACHE_FOLDER_PATH_STR=./log/pickle_cache
|
||||
CACHE_WITH_PICKLE=False
|
||||
ENABLE_CACHE=False
|
||||
PROMPT_CACHE_PATH=./log/prompt_cache.db
|
||||
|
||||
DS_CODER_COSTEER_ENV_TYPE=conda
|
||||
# DS_PROPOSAL_VERSION=v2 deprecated
|
||||
|
||||
DS_CODER_ON_WHOLE_PIPELINE=True
|
||||
COSTEER_V2_QUERY_FORMER_TRACE_LIMIT=3
|
||||
|
||||
# export PYTHONPATH=. # this is for running researcher branch;
|
||||
@@ -1,61 +0,0 @@
|
||||
"""
|
||||
This file is a template for the .env file.
|
||||
|
||||
Please copy this file to .env and fill in the values.
|
||||
|
||||
For more information about configuration options, please refer to the documentation
|
||||
|
||||
"""
|
||||
|
||||
# ==========================================
|
||||
# Global configs:
|
||||
MAX_RETRY=10
|
||||
RETRY_WAIT_SECONDS=20
|
||||
# ==========================================
|
||||
|
||||
|
||||
# ==========================================
|
||||
# Backend Configuration
|
||||
# ==========================================
|
||||
# BACKEND=rdagent.oai.backend.LiteLLMAPIBackend
|
||||
# ==========================================
|
||||
|
||||
# ==========================================
|
||||
# Backend Configuration (choose one)
|
||||
# ==========================================
|
||||
|
||||
# 1. Set universal API key
|
||||
# CHAT_MODEL="gpt-4o"
|
||||
# EMBEDDING_MODEL="text-embedding-3-small"
|
||||
# OPENAI_API_BASE="https://your-endpoint.com/v1"
|
||||
# OPENAI_API_KEY="sk-your-api-key-here"
|
||||
|
||||
# 2. Set separate API KEY
|
||||
# Chat configuration
|
||||
OPENAI_API_KEY="sk-chat-key"
|
||||
OPENAI_API_BASE="https://xxx-litellm.com/v1"
|
||||
CHAT_MODEL='gpt-4o'
|
||||
|
||||
# Embedding configuration (using other service)
|
||||
# Use siliconflow as example, pay attention to the litellm_proxy prefix
|
||||
LITELLM_PROXY_API_KEY="sk-embedding-service-key"
|
||||
LITELLM_PROXY_API_BASE="https://api.siliconflow.cn/v1"
|
||||
EMBEDDING_MODEL="litellm_proxy/BAAI/bge-large-en-v1.5"
|
||||
# ==========================================
|
||||
|
||||
# ==========================================
|
||||
# Other Configuration
|
||||
# ==========================================
|
||||
# CHAT_AZURE_API_BASE=<for_Azure_user>
|
||||
# CHAT_AZURE_API_VERSION=<for_Azure_user>
|
||||
|
||||
# EMBEDDING_AZURE_API_BASE=<for_Azure_user>
|
||||
# EMBEDDING_AZURE_API_VERSION=<for_Azure_user>
|
||||
|
||||
# Cache Setting (Optional):
|
||||
# USE_CHAT_CACHE=True
|
||||
# USE_EMBEDDING_CACHE=True
|
||||
# FT_DOCKER_ENABLE_CACHE=True
|
||||
# DS_DOCKER_ENABLE_CACHE=True
|
||||
# Senario Configs:
|
||||
# ==========================================
|
||||
@@ -1,2 +0,0 @@
|
||||
github:
|
||||
- MIIC-finance
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: "\U0001F41B Bug Report"
|
||||
about: Submit a bug report to help us improve RD-Agent
|
||||
labels: bug
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bug Description
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
## To Reproduce
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
## Screenshot
|
||||
|
||||
<!-- A screenshot of the error message or anything shouldn't appear-->
|
||||
|
||||
## Environment
|
||||
|
||||
**Note**: Users can run `rdagent collect_info` to get system information and paste it directly here.
|
||||
|
||||
- Name of current operating system:
|
||||
- Processor architecture:
|
||||
- System, version, and hardware information:
|
||||
- Version number of the system:
|
||||
- Python version:
|
||||
- Container ID:
|
||||
- Container Name:
|
||||
- Container Status:
|
||||
- Image ID used by the container:
|
||||
- Image tag used by the container:
|
||||
- Container port mapping:
|
||||
- Container Label:
|
||||
- Startup Commands:
|
||||
- RD-Agent version:
|
||||
- Package version:
|
||||
|
||||
## Additional Notes
|
||||
|
||||
<!-- Add any other information about the problem here. -->
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
name: "\U0001F4D6 Documentation"
|
||||
about: Report an issue related to documentation
|
||||
|
||||
---
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
<!-- Please specify whether it's tutorial part or API reference part, and describe it.-->
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
name: "\U0001F31FFeature Request"
|
||||
about: Request for a new RD-Agent feature
|
||||
labels: enhancement
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Feature Description
|
||||
<!-- A clear and concise description of the feature proposal -->
|
||||
|
||||
## Motivation
|
||||
|
||||
1. Application scenario
|
||||
2. Related works (Papers, Github repos etc.):
|
||||
3. Any other relevant and important information:
|
||||
|
||||
<!-- Please describe why the feature is important. -->
|
||||
|
||||
## Alternatives
|
||||
|
||||
<!-- A short description of any alternative solutions or features you've considered. -->
|
||||
|
||||
## Additional Notes
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: "❓Questions & Help"
|
||||
about: Have some questions? We can offer help.
|
||||
labels: question
|
||||
|
||||
---
|
||||
|
||||
## ❓ Questions and Help
|
||||
|
||||
We sincerely suggest you to carefully read the [documentation](http://rdagent.readthedocs.io/). After that, if you still feel puzzled, please describe the question clearly under this issue.
|
||||
@@ -1,34 +0,0 @@
|
||||
<!--- Thank you for submitting a Pull Request! In order to make our work smoother. -->
|
||||
<!--- please make sure your Pull Request meets the following requirements: -->
|
||||
<!--- 1. Provide a general summary of your changes in the Title above; -->
|
||||
<!--- 2. Add appropriate prefixes to titles, such as `build:`, `chore:`, `ci:`, `docs:`, `feat:`, `fix:`, `perf:`, `refactor:`, `revert:`, `style:`, `test:`(Ref: https://www.conventionalcommits.org/). -->
|
||||
<!--- Category: -->
|
||||
<!--- Patch Updates: `fix:` -->
|
||||
<!--- Example: fix(auth): correct login validation issue -->
|
||||
<!--- minor update (introduces new functionality): `feat` -->
|
||||
<!--- Example: feature(parser): add ability to parse arrays -->
|
||||
<!--- major update(destructive update): Include BREAKING CHANGE in the commit message footer, or add `! ` in the commit footer to indicate that there is a destructive update. -->
|
||||
<!--- Example: feat(auth)! : remove support for old authentication method -->
|
||||
<!--- Other updates: `build:`, `chore:`, `ci:`, `docs:`, `perf:`, `refactor:`, `revert:`, `style:`, `test:`. -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Are there any related issues? If so, please put the link here. -->
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
<!--- Put an `x` in all the boxes that apply: --->
|
||||
- [ ] If you are adding a new feature, test on your own test scripts.
|
||||
|
||||
<!--- **ATTENTION**: If you are adding a new feature, please make sure your codes are **correctly tested**. If our test scripts do not cover your cases, please provide your own test scripts under the `tests` folder and test them. More information about test scripts can be found [here](https://docs.python.org/3/library/unittest.html#basic-example), or you could refer to those we provide under the `tests` folder. -->
|
||||
|
||||
## Screenshots of Test Results (if appropriate):
|
||||
1. Your own tests:
|
||||
|
||||
## Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Fix bugs
|
||||
- [ ] Add new feature
|
||||
- [ ] Update documentation
|
||||
@@ -1,19 +0,0 @@
|
||||
updates:
|
||||
- commit-message:
|
||||
prefix: build(actions)
|
||||
directory: /
|
||||
package-ecosystem: github-actions
|
||||
schedule:
|
||||
interval: weekly
|
||||
- commit-message:
|
||||
prefix: build(requirements)
|
||||
directory: /
|
||||
groups:
|
||||
dev:
|
||||
dependency-type: development
|
||||
prod:
|
||||
dependency-type: production
|
||||
package-ecosystem: pip
|
||||
schedule:
|
||||
interval: weekly
|
||||
version: 2
|
||||
@@ -1,69 +0,0 @@
|
||||
concurrency:
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
if: ${{ !cancelled() && ! failure() }}
|
||||
needs: dependabot
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
cache: pip
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- run: make dev
|
||||
- name: lint test docs and build
|
||||
run: make lint docs-gen test-offline # test docs build
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- '3.10'
|
||||
- '3.11'
|
||||
dependabot:
|
||||
if: ${{ github.actor == 'dependabot[bot]' && startsWith(github.head_ref, 'dependabot/pip/') }}
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: Set up Git
|
||||
run: |
|
||||
git config --global user.name github-actions
|
||||
git config --global user.email github-actions@github.com
|
||||
- name: Set up Python with multiple versions.
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
cache: pip
|
||||
python-version: |
|
||||
3.10
|
||||
3.11
|
||||
- name: Install pipenv using pipx
|
||||
run: pipx install pipenv
|
||||
- name: Generate constraints for all supported Python versions
|
||||
run: |
|
||||
CI= PYTHON_VERSION=3.10 make constraints
|
||||
CI= PYTHON_VERSION=3.11 make constraints
|
||||
- name: Push changes if applicable
|
||||
run: |
|
||||
if [[ -n `git status --porcelain` ]]; then
|
||||
git commit -a -m "build: Update constraints for dependabot."
|
||||
git push
|
||||
fi
|
||||
name: CI
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Lint pull request title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
concurrency:
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
lint-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# This step is necessary because the lint title uses the .commitlintrc.js file in the project root directory.
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: Install commitlint
|
||||
run: npm install --save-dev @commitlint/{config-conventional,cli}
|
||||
|
||||
- name: Validate PR Title with commitlint
|
||||
env:
|
||||
BODY: ${{ github.event.pull_request.title }}
|
||||
run: |
|
||||
echo "$BODY" | npx commitlint --config .commitlintrc.js
|
||||
@@ -1,17 +0,0 @@
|
||||
concurrency:
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
documentation-links:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: readthedocs/actions/preview@v1
|
||||
with:
|
||||
project-slug: RDAgent
|
||||
name: Read the Docs Pull Request Preview
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
permissions:
|
||||
pull-requests: write
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
release_and_publish:
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Release please
|
||||
id: release_please
|
||||
uses: googleapis/release-please-action@v4
|
||||
with:
|
||||
# The current PAT (personal access token) was created on 2024-08-05,
|
||||
# since the maximum validity of PAT is 1 year, you need to change the PAT before 2025-08-05.
|
||||
token: ${{ secrets.PAT }}
|
||||
release-type: simple
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ steps.release_please.outputs.release_created }}
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python
|
||||
if: ${{ steps.release_please.outputs.release_created }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
cache: pip
|
||||
python-version: '3.10'
|
||||
- name: Install dependencies
|
||||
if: ${{ steps.release_please.outputs.release_created }}
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine # better-exceptions(optional for debug)
|
||||
- run: make dev
|
||||
if: ${{ steps.release_please.outputs.release_created }}
|
||||
- run: make build
|
||||
if: ${{ steps.release_please.outputs.release_created }}
|
||||
- name: upload
|
||||
if: ${{ steps.release_please.outputs.release_created }}
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
||||
run: |
|
||||
make upload
|
||||
+11
-189
@@ -1,190 +1,12 @@
|
||||
# Custom
|
||||
*.swp
|
||||
.DS_Store
|
||||
Pipfile
|
||||
public
|
||||
release-notes.md
|
||||
typescript*
|
||||
tmp/
|
||||
.ai/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
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/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
/log*/
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.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
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env*
|
||||
*.env
|
||||
.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/
|
||||
|
||||
# all pkl files
|
||||
*.pkl
|
||||
|
||||
# all h5 files
|
||||
*.h5
|
||||
|
||||
# all vs-code files
|
||||
.vscode/
|
||||
|
||||
# reports
|
||||
reports/
|
||||
|
||||
# git_ignore_folder
|
||||
.env
|
||||
.env.backup*
|
||||
*.backup*
|
||||
git_ignore_folder/
|
||||
|
||||
#cache
|
||||
*cache*/
|
||||
*cache.json
|
||||
|
||||
# DB files
|
||||
*.db
|
||||
|
||||
# Docker
|
||||
factor_template/mlruns/
|
||||
env_tpl
|
||||
mlruns/
|
||||
|
||||
# possible output from coder or runner
|
||||
*.pth
|
||||
*qlib_res.csv
|
||||
|
||||
# shell script
|
||||
*.out
|
||||
/*.sh
|
||||
.aider*
|
||||
rdagent/app/benchmark/factor/example.json
|
||||
|
||||
# UI Server resources
|
||||
videos/
|
||||
static/
|
||||
|
||||
# AI assistant
|
||||
.cursor/
|
||||
.claude/
|
||||
AGENTS.md
|
||||
!rdagent/**/AGENTS.md
|
||||
|
||||
scripts/
|
||||
fin_quant.log
|
||||
selector.log
|
||||
pickle_cache/
|
||||
log/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
prompt_cache.db
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.10"
|
||||
# During the build process, you need to fetch tags, and since the default command to read the docs only pulls shallow code, it will cause an error.
|
||||
# So we added the `git fetch --tags --unshallow || true` command to fetch the full tag record.
|
||||
# Adding this command overrides the default command, so we copied it over to make sure the build was successful.
|
||||
commands:
|
||||
- python -mvirtualenv $READTHEDOCS_VIRTUALENV_PATH
|
||||
- python -m pip install --upgrade --no-cache-dir pip setuptools
|
||||
- python -m pip install --upgrade --no-cache-dir sphinx
|
||||
- python -m pip install --exists-action=w --no-cache-dir -r requirements/docs.txt
|
||||
- python -m pip install --upgrade --upgrade-strategy only-if-needed --no-cache-dir .
|
||||
- git fetch --tags --unshallow || true
|
||||
- mkdir -p $READTHEDOCS_OUTPUT/html/
|
||||
- python -m sphinx -T -b html -d _build/doctrees -D language=en ./docs $READTHEDOCS_OUTPUT/html
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# Build all formats
|
||||
formats: all
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: requirements/docs.txt
|
||||
- method: pip
|
||||
path: .
|
||||
@@ -1,2 +0,0 @@
|
||||
[client]
|
||||
showSidebarNavigation = false
|
||||
@@ -1,15 +1,55 @@
|
||||
hypothesis_generation:
|
||||
system: |-
|
||||
You are an expert in financial analysis. Your task is to generate a well-reasoned hypothesis based on the provided financial factors and report content.
|
||||
Please ensure your response is in JSON format as shown below:
|
||||
You are an expert quantitative researcher specialized in FX (foreign exchange) trading,
|
||||
specifically EURUSD intraday strategies on 15-minute bars.
|
||||
|
||||
EURUSD domain knowledge you must apply:
|
||||
- London session (08:00-12:00 UTC): highest volatility, trending behavior — favor momentum strategies
|
||||
- NY session (13:00-17:00 UTC): second volatility peak, also trending
|
||||
- Asian session (00:00-07:00 UTC): low volatility, mean-reverting behavior
|
||||
- London/NY overlap (13:00-17:00 UTC): strongest directional moves of the day
|
||||
- Weekend gap risk: avoid holding positions after Friday 20:00 UTC
|
||||
- Spread cost: ~1.5 bps per trade — strategies must minimize unnecessary entries
|
||||
- EURUSD is mean-reverting on short windows (<1h), trending on longer (>4h)
|
||||
- Key macro drivers: ECB/Fed rate decisions, NFP (first Friday of month), CPI releases
|
||||
|
||||
Available model types you can propose:
|
||||
- TimeSeries: LSTM, GRU, TCN (Temporal Convolutional Network), Transformer, PatchTST
|
||||
- Tabular: XGBoost, LightGBM, RandomForest (on engineered features)
|
||||
- Hybrid: CNN+LSTM, XGBoost+LSTM ensemble
|
||||
- Statistical: Regime-switching (HMM), Kalman filter
|
||||
|
||||
Available features in the dataset:
|
||||
- OHLCV: open, high, low, close, volume (15min bars)
|
||||
- Returns: ret_1, ret_4, ret_8, ret_16, ret_96
|
||||
- Technical: rsi_14, macd_hist, adx_14, atr_14, bb_pct, stoch_k, cci_14
|
||||
- Volatility: vol_real_4, vol_real_16, vol_ratio, zscore_ret_96
|
||||
- Time/Session: hour, is_london, is_ny, is_overlap, hour_sin, hour_cos
|
||||
- Lags: rsi_14_lag1-8, macd_hist_lag1-8, bb_pct_lag1-8
|
||||
|
||||
Your hypothesis must:
|
||||
1. Specify which session(s) the strategy targets
|
||||
2. Name which model type to use and why it fits EURUSD
|
||||
3. Include a session filter (is_london / is_ny)
|
||||
4. Include a spread filter (only trade when expected |return| > 0.0003)
|
||||
5. Specify target: classification (fwd_sign_4) or regression (fwd_ret_4)
|
||||
|
||||
Please ensure your response is in JSON format:
|
||||
{
|
||||
"hypothesis": "A clear and concise hypothesis based on the provided information.",
|
||||
"reason": "A detailed explanation supporting the generated hypothesis.",
|
||||
"hypothesis": "A clear and concise trading hypothesis for EURUSD 15min.",
|
||||
"reason": "Detailed explanation including session, model choice, and expected edge.",
|
||||
"model_type": "One of: TimeSeries / Tabular / XGBoost",
|
||||
"target_session": "london / ny / asian / all",
|
||||
"expected_arr_range": "e.g. 8-12%"
|
||||
}
|
||||
|
||||
user: |-
|
||||
The following are the financial factors and their descriptions:
|
||||
Previously tried approaches and their results:
|
||||
{{ factor_descriptions }}
|
||||
|
||||
The report content is as follows:
|
||||
{{ report_content }}
|
||||
Additional context:
|
||||
{{ report_content }}
|
||||
|
||||
Generate a NEW hypothesis that is meaningfully different from what has been tried.
|
||||
Focus on approaches that have NOT been tested yet.
|
||||
Target: beat current best ARR of 9.62%.
|
||||
|
||||
@@ -31,6 +31,15 @@ evolving_strategy_model_coder:
|
||||
system: |-
|
||||
User is trying to implement some pytorch models in the following scenario:
|
||||
{{ scenario }}
|
||||
|
||||
EURUSD-specific rules (ALWAYS apply these in generated code):
|
||||
1. Session filter: use is_london and is_ny columns — weight/filter signals to active sessions
|
||||
2. Spread filter: only generate signal when abs(predicted_return) > 0.0003
|
||||
3. ADX regime: if adx_proxy > 1.2 use trend model; if adx_proxy < 0.8 use mean-reversion
|
||||
4. Weekend filter: zero out signals when dayofweek==4 and hour>=20
|
||||
5. Max trade frequency: target <15 trades per day (avoid spread cost death)
|
||||
6. Supported model_type values: "Tabular", "TimeSeries", "XGBoost"
|
||||
|
||||
Your code is expected to align the scenario in any form which means The user needs to get the prediction of the model based on the input data.
|
||||
|
||||
To help you write the correct code, the user might provide multiple information that helps you write the correct code:
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from rdagent.log.logger import RDAgentLog
|
||||
from rdagent.log.utils import LogColors
|
||||
|
||||
rdagent_logger: RDAgentLog = RDAgentLog()
|
||||
@@ -1,103 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Literal, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
"""The info unit of the storage"""
|
||||
|
||||
tag: str # namespace like like a.b.c
|
||||
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] # The level of the logging
|
||||
timestamp: datetime # The time when the message is generated
|
||||
caller: Optional[
|
||||
str
|
||||
] # The caller of the logging like `rdagent.oai.llm_utils:_create_chat_completion_inner_function:55`(file:func:line)
|
||||
pid_trace: Optional[str] # The process id trace; A-B-C represents A create B, B create C
|
||||
content: object # The content
|
||||
|
||||
|
||||
class Storage:
|
||||
"""
|
||||
Basic storage to support saving objects;
|
||||
|
||||
# Usage:
|
||||
|
||||
The storage has mainly two kind of users:
|
||||
- The logging end: you can choose any of the following method to use the object
|
||||
- We can use it directly with the native logging storage
|
||||
- We can use it with other logging tools; For example, serve as a handler for loggers
|
||||
- The view end:
|
||||
- Mainly for the subclass of `logging.base.View`
|
||||
- It should provide two kind of ways to provide content
|
||||
- offline content provision.
|
||||
- online content preovision.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def log(
|
||||
self,
|
||||
obj: object,
|
||||
tag: str = "",
|
||||
timestamp: datetime | None = None,
|
||||
) -> str | Path:
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj : object
|
||||
The object for logging.
|
||||
name : str
|
||||
The name of the object. For example "a.b.c"
|
||||
We may log a lot of objects to a same name
|
||||
|
||||
Returns
|
||||
-------
|
||||
str | Path
|
||||
The storage identifier of the object.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def iter_msg(self) -> Generator[Message, None, None]:
|
||||
"""
|
||||
Iterate the message in the storage.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def truncate(self, time: datetime) -> None:
|
||||
"""
|
||||
Remove all log entries after the specified time.
|
||||
"""
|
||||
...
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
class View:
|
||||
"""
|
||||
Motivation:
|
||||
|
||||
Display the content in the storage
|
||||
"""
|
||||
|
||||
# TODO: pleas fix me
|
||||
@abstractmethod
|
||||
def display(self, s: Storage, watch: bool = False) -> None:
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
s : Storage
|
||||
|
||||
watch : bool
|
||||
should we watch the new content and display them
|
||||
"""
|
||||
...
|
||||
@@ -1,34 +0,0 @@
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pydantic_settings import SettingsConfigDict
|
||||
|
||||
from rdagent.core.conf import ExtendedBaseSettings
|
||||
|
||||
|
||||
class LogSettings(ExtendedBaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="LOG_", protected_namespaces=())
|
||||
|
||||
trace_path: str = str(Path.cwd() / "log" / datetime.now(timezone.utc).strftime("%Y-%m-%d_%H-%M-%S-%f"))
|
||||
|
||||
format_console: str | None = None
|
||||
""""If it is None, leave it as the default"""
|
||||
|
||||
ui_server_port: int | None = None
|
||||
|
||||
storages: dict[str, list[int | str]] = {}
|
||||
|
||||
def set_ui_server_port(self, port: int | None) -> None:
|
||||
self.ui_server_port = port
|
||||
if port is None:
|
||||
self.storages.pop("rdagent.log.ui.storage.WebStorage", None)
|
||||
return
|
||||
|
||||
self.storages["rdagent.log.ui.storage.WebStorage"] = [port, self.trace_path]
|
||||
|
||||
def model_post_init(self, _context: Any, /) -> None:
|
||||
self.set_ui_server_port(self.ui_server_port)
|
||||
|
||||
|
||||
LOG_SETTINGS = LogSettings()
|
||||
@@ -1,153 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from contextvars import ContextVar
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
from loguru import logger
|
||||
from psutil import Process
|
||||
|
||||
from rdagent.core.utils import SingletonBaseClass, import_class
|
||||
|
||||
from .base import Storage
|
||||
from .conf import LOG_SETTINGS
|
||||
from .storage import FileStorage
|
||||
from .utils import get_caller_info
|
||||
|
||||
|
||||
class RDAgentLog(SingletonBaseClass):
|
||||
"""
|
||||
The files are organized based on the tag & PID
|
||||
Here is an example tag
|
||||
|
||||
.. code-block::
|
||||
|
||||
a
|
||||
- b
|
||||
- c
|
||||
- 123
|
||||
- common_logs.log
|
||||
- 1322
|
||||
- common_logs.log
|
||||
- 1233
|
||||
- <timestamp>.pkl
|
||||
- d
|
||||
- 1233-673 ...
|
||||
- 1233-4563 ...
|
||||
- 1233-365 ...
|
||||
|
||||
"""
|
||||
|
||||
# Thread-/coroutine-local tag; In Linux forked subprocess, it will be copied to the subprocess.
|
||||
_tag_ctx: ContextVar[str] = ContextVar("_tag_ctx", default="")
|
||||
_raw_log_key = "_rdagent_raw"
|
||||
|
||||
@classmethod
|
||||
def _configure_console_sinks(cls) -> None:
|
||||
raw_filter = lambda record: bool(record["extra"].get(cls._raw_log_key, False))
|
||||
normal_filter = lambda record: not raw_filter(record)
|
||||
|
||||
if LOG_SETTINGS.format_console is not None:
|
||||
logger.add(sys.stdout, format=LOG_SETTINGS.format_console, filter=normal_filter)
|
||||
else:
|
||||
logger.add(sys.stdout, filter=normal_filter)
|
||||
logger.add(sys.stdout, format="{message}", filter=raw_filter)
|
||||
|
||||
@property
|
||||
def _tag(self) -> str: # Get current tag
|
||||
return self._tag_ctx.get()
|
||||
|
||||
@_tag.setter # Set current tag
|
||||
def _tag(self, value: str) -> None:
|
||||
self._tag_ctx.set(value)
|
||||
|
||||
def __init__(self) -> None:
|
||||
logger.remove()
|
||||
self._configure_console_sinks()
|
||||
|
||||
self.storage = FileStorage(LOG_SETTINGS.trace_path)
|
||||
self.other_storages: list[Storage] = []
|
||||
self.refresh_storages_from_settings()
|
||||
|
||||
self.main_pid = os.getpid()
|
||||
|
||||
def refresh_storages_from_settings(self) -> None:
|
||||
self.other_storages = []
|
||||
for storage, args in LOG_SETTINGS.storages.items():
|
||||
storage_cls = import_class(storage)
|
||||
self.other_storages.append(storage_cls(*args))
|
||||
|
||||
def rebind_console_to_current_streams(self) -> None:
|
||||
"""Rebind loguru sinks to the current stdio objects.
|
||||
|
||||
This is needed in forked/spawned subprocesses after stdout/stderr have been
|
||||
redirected, because loguru keeps references to the original stream objects.
|
||||
"""
|
||||
logger.remove()
|
||||
self._configure_console_sinks()
|
||||
|
||||
@contextmanager
|
||||
def tag(self, tag: str) -> Generator[None, None, None]:
|
||||
if tag.strip() == "":
|
||||
raise ValueError("Tag cannot be empty.")
|
||||
# Generate a new complete tag
|
||||
current_tag = self._tag_ctx.get()
|
||||
new_tag = tag if current_tag == "" else f"{current_tag}.{tag}"
|
||||
# Set and save token for later restore
|
||||
token = self._tag_ctx.set(new_tag)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
# Restore previous tag (thread/coroutine safe)
|
||||
self._tag_ctx.reset(token)
|
||||
|
||||
def set_storages_path(self, path: str | Path) -> None:
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
for storage in [self.storage] + self.other_storages:
|
||||
if hasattr(storage, "path"):
|
||||
storage.path = path
|
||||
|
||||
def truncate_storages(self, time: datetime) -> None:
|
||||
for storage in [self.storage] + self.other_storages:
|
||||
storage.truncate(time=time)
|
||||
|
||||
def get_pids(self) -> str:
|
||||
"""
|
||||
Returns a string of pids from the current process to the main process.
|
||||
Split by '-'.
|
||||
"""
|
||||
pid = os.getpid()
|
||||
process = Process(pid)
|
||||
pid_chain = f"{pid}"
|
||||
while process.pid != self.main_pid:
|
||||
parent_pid = process.ppid()
|
||||
parent_process = Process(parent_pid)
|
||||
pid_chain = f"{parent_pid}-{pid_chain}"
|
||||
process = parent_process
|
||||
return pid_chain
|
||||
|
||||
def log_object(self, obj: object, *, tag: str = "") -> None:
|
||||
tag = f"{self._tag}.{tag}.{self.get_pids()}".strip(".")
|
||||
|
||||
for storage in [self.storage] + self.other_storages:
|
||||
storage.log(obj, tag=tag)
|
||||
|
||||
def _log(self, level: str, msg: str, *, tag: str = "", raw: bool = False) -> None:
|
||||
caller_info = get_caller_info(level=3)
|
||||
tag = f"{self._tag}.{tag}.{self.get_pids()}".strip(".")
|
||||
|
||||
patched_logger = logger.patch(lambda r: r.update(caller_info)).bind(**{self._raw_log_key: raw}).opt(raw=raw)
|
||||
log_func = getattr(patched_logger, level)
|
||||
log_func(msg)
|
||||
|
||||
def info(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
|
||||
self._log("info", msg, tag=tag, raw=raw)
|
||||
|
||||
def warning(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
|
||||
self._log("warning", msg, tag=tag, raw=raw)
|
||||
|
||||
def error(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
|
||||
self._log("error", msg, tag=tag, raw=raw)
|
||||
@@ -1,260 +0,0 @@
|
||||
import pickle
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
import fire
|
||||
import pandas as pd
|
||||
|
||||
from rdagent.core.experiment import FBWorkspace
|
||||
from rdagent.core.proposal import ExperimentFeedback
|
||||
from rdagent.log.storage import FileStorage
|
||||
from rdagent.log.utils import extract_json, extract_loopid_func_name, is_valid_session
|
||||
from rdagent.log.utils.folder import get_first_session_file_after_duration
|
||||
from rdagent.scenarios.data_science.experiment.experiment import DSExperiment
|
||||
from rdagent.scenarios.data_science.test_eval import (
|
||||
MLETestEval,
|
||||
NoTestEvalError,
|
||||
get_test_eval,
|
||||
)
|
||||
|
||||
# from rdagent.scenarios.kaggle.kaggle_crawler import score_rank
|
||||
from rdagent.utils.workflow import LoopBase
|
||||
|
||||
|
||||
def save_grade_info(log_trace_path: Path):
|
||||
test_eval = get_test_eval()
|
||||
|
||||
trace_storage = FileStorage(log_trace_path)
|
||||
for msg in trace_storage.iter_msg(tag="competition"):
|
||||
competition = msg.content
|
||||
|
||||
for msg in trace_storage.iter_msg(tag="running"):
|
||||
if isinstance(msg.content, DSExperiment):
|
||||
# TODO: mle_score.txt is not a general name now.
|
||||
# Please use a more general name like test_score.txt
|
||||
try:
|
||||
mle_score_str = test_eval.eval(competition, msg.content.experiment_workspace)
|
||||
trace_storage.log(
|
||||
mle_score_str, tag=f"{msg.tag}.mle_score.pid", save_type="pkl", timestamp=msg.timestamp
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error in {log_trace_path}: {e}", traceback.format_exc())
|
||||
|
||||
|
||||
def save_all_grade_info(log_folder: str | Path) -> None:
|
||||
for log_trace_path in Path(log_folder).iterdir():
|
||||
if is_valid_session(log_trace_path):
|
||||
try:
|
||||
save_grade_info(log_trace_path)
|
||||
except NoTestEvalError as e:
|
||||
print(f"Error in {log_trace_path}: {e}", traceback.format_exc())
|
||||
|
||||
|
||||
def _get_loop_and_fn_after_hours(log_folder: Path, hours: int):
|
||||
stop_session_fp = get_first_session_file_after_duration(log_folder, f"{hours}h")
|
||||
|
||||
with stop_session_fp.open("rb") as f:
|
||||
session_obj: LoopBase = pickle.load(f)
|
||||
|
||||
loop_trace = session_obj.loop_trace
|
||||
stop_li = max(loop_trace.keys())
|
||||
last_loop = loop_trace[stop_li]
|
||||
last_step = last_loop[-1]
|
||||
stop_fn = session_obj.steps[last_step.step_idx]
|
||||
print(f"Stop Loop: {stop_li=}, {stop_fn=}")
|
||||
files = sorted(
|
||||
(log_folder / "__session__").glob("*/*_*"), key=lambda f: (int(f.parent.name), int(f.name.split("_")[0]))
|
||||
)
|
||||
|
||||
print(f"Max Session: {files[-1:]=}")
|
||||
return stop_li, stop_fn
|
||||
|
||||
|
||||
def summarize_folder(log_folder: Path, hours: int | None = None) -> None:
|
||||
test_eval = get_test_eval()
|
||||
|
||||
is_mle = isinstance(test_eval, MLETestEval)
|
||||
"""
|
||||
Summarize the log folder and save the summary as a pickle file.
|
||||
Args:
|
||||
log_folder (Path): The path to the log folder (contains many log traces).
|
||||
hours (int | None): The number of hours to stat. If None, stat all.
|
||||
"""
|
||||
log_folder = Path(log_folder)
|
||||
stat = defaultdict(dict)
|
||||
for log_trace_path in log_folder.iterdir(): # One log trace
|
||||
if not is_valid_session(log_trace_path):
|
||||
continue
|
||||
loop_num = 0
|
||||
made_submission_num = 0
|
||||
valid_submission_num = 0
|
||||
above_median_num = 0
|
||||
get_medal_num = 0
|
||||
bronze_num = 0
|
||||
silver_num = 0
|
||||
gold_num = 0
|
||||
test_scores = {}
|
||||
test_ranks = {}
|
||||
valid_scores = {}
|
||||
bronze_threshold = 0.0
|
||||
silver_threshold = 0.0
|
||||
gold_threshold = 0.0
|
||||
median_threshold = 0.0
|
||||
success_loop_num = 0
|
||||
|
||||
sota_exp_stat = ""
|
||||
sota_exp_score = None
|
||||
sota_exp_rank = None
|
||||
grade_output = None
|
||||
|
||||
if hours:
|
||||
stop_li, stop_fn = _get_loop_and_fn_after_hours(log_trace_path, hours)
|
||||
msgs = [(msg, extract_loopid_func_name(msg.tag)) for msg in FileStorage(log_trace_path).iter_msg()]
|
||||
msgs = [(msg, int(loop_id) if loop_id else loop_id, fn) for msg, (loop_id, fn) in msgs]
|
||||
msgs.sort(key=lambda m: m[1] if m[1] else -1) # sort by loop id
|
||||
for msg, loop_id, fn in msgs: # messages in log trace
|
||||
if loop_id:
|
||||
loop_num = max(loop_id + 1, loop_num)
|
||||
if hours and loop_id == stop_li and fn == stop_fn:
|
||||
break
|
||||
if msg.tag and "llm" not in msg.tag and "session" not in msg.tag:
|
||||
if "competition" in msg.tag:
|
||||
stat[log_trace_path.name]["competition"] = msg.content
|
||||
|
||||
# get threshold scores
|
||||
workflowexp = FBWorkspace()
|
||||
if is_mle:
|
||||
stdout = workflowexp.execute(
|
||||
env=test_eval.env,
|
||||
entry=f"mlebench grade-sample None {stat[log_trace_path.name]['competition']} --data-dir /mle/data",
|
||||
)
|
||||
grade_output = extract_json(stdout)
|
||||
if grade_output:
|
||||
bronze_threshold = grade_output["bronze_threshold"]
|
||||
silver_threshold = grade_output["silver_threshold"]
|
||||
gold_threshold = grade_output["gold_threshold"]
|
||||
median_threshold = grade_output["median_threshold"]
|
||||
|
||||
if "running" in msg.tag:
|
||||
if isinstance(msg.content, DSExperiment):
|
||||
if msg.content.result is not None:
|
||||
valid_scores[loop_id] = msg.content.result
|
||||
elif "mle_score" in msg.tag:
|
||||
grade_output = extract_json(msg.content)
|
||||
if grade_output:
|
||||
if grade_output["submission_exists"]:
|
||||
made_submission_num += 1
|
||||
if grade_output["score"] is not None:
|
||||
test_scores[loop_id] = grade_output["score"]
|
||||
# if is_mle:
|
||||
# _, test_ranks[loop_id] = score_rank(
|
||||
# stat[log_trace_path.name]["competition"], grade_output["score"]
|
||||
# )
|
||||
if grade_output["valid_submission"]:
|
||||
valid_submission_num += 1
|
||||
if grade_output["above_median"]:
|
||||
above_median_num += 1
|
||||
if grade_output["any_medal"]:
|
||||
get_medal_num += 1
|
||||
if grade_output["bronze_medal"]:
|
||||
bronze_num += 1
|
||||
if grade_output["silver_medal"]:
|
||||
silver_num += 1
|
||||
if grade_output["gold_medal"]:
|
||||
gold_num += 1
|
||||
|
||||
if "feedback" in msg.tag and "evolving" not in msg.tag:
|
||||
if isinstance(msg.content, ExperimentFeedback) and bool(msg.content):
|
||||
success_loop_num += 1
|
||||
|
||||
if grade_output: # sota exp's grade output
|
||||
if grade_output["gold_medal"]:
|
||||
sota_exp_stat = "gold"
|
||||
elif grade_output["silver_medal"]:
|
||||
sota_exp_stat = "silver"
|
||||
elif grade_output["bronze_medal"]:
|
||||
sota_exp_stat = "bronze"
|
||||
elif grade_output["above_median"]:
|
||||
sota_exp_stat = "above_median"
|
||||
elif grade_output["valid_submission"]:
|
||||
sota_exp_stat = "valid_submission"
|
||||
elif grade_output["submission_exists"]:
|
||||
sota_exp_stat = "made_submission"
|
||||
if grade_output["score"] is not None:
|
||||
sota_exp_score = grade_output["score"]
|
||||
# if is_mle:
|
||||
# _, sota_exp_rank = score_rank(
|
||||
# stat[log_trace_path.name]["competition"], grade_output["score"]
|
||||
# )
|
||||
|
||||
stat[log_trace_path.name].update(
|
||||
{
|
||||
"loop_num": loop_num,
|
||||
"made_submission_num": made_submission_num,
|
||||
"valid_submission_num": valid_submission_num,
|
||||
"above_median_num": above_median_num,
|
||||
"get_medal_num": get_medal_num,
|
||||
"bronze_num": bronze_num,
|
||||
"silver_num": silver_num,
|
||||
"gold_num": gold_num,
|
||||
"test_scores": test_scores,
|
||||
# "test_ranks": test_ranks,
|
||||
"valid_scores": valid_scores,
|
||||
"success_loop_num": success_loop_num,
|
||||
"sota_exp_stat": sota_exp_stat,
|
||||
"sota_exp_score": sota_exp_score,
|
||||
# "sota_exp_rank": sota_exp_rank,
|
||||
"bronze_threshold": bronze_threshold,
|
||||
"silver_threshold": silver_threshold,
|
||||
"gold_threshold": gold_threshold,
|
||||
"median_threshold": median_threshold,
|
||||
}
|
||||
)
|
||||
|
||||
# Save the summary
|
||||
save_name = f"summary_{hours}h.pkl" if hours else "summary.pkl"
|
||||
save_p = log_folder / save_name
|
||||
if save_p.exists():
|
||||
save_p.unlink()
|
||||
print(f"Old {save_name} removed.")
|
||||
pd.to_pickle(stat, save_p)
|
||||
|
||||
|
||||
# {
|
||||
# "competition_id": "stanford-covid-vaccine",
|
||||
# "score": null,
|
||||
# "gold_threshold": 0.34728,
|
||||
# "silver_threshold": 0.35175,
|
||||
# "bronze_threshold": 0.3534,
|
||||
# "median_threshold": 0.363095,
|
||||
# "any_medal": false,
|
||||
# "gold_medal": false,
|
||||
# "silver_medal": false,
|
||||
# "bronze_medal": false,
|
||||
# "above_median": false,
|
||||
# "submission_exists": true,
|
||||
# "valid_submission": false,
|
||||
# "is_lower_better": true,
|
||||
# "created_at": "2025-01-21T11:59:33.788201",
|
||||
# "submission_path": "submission.csv"
|
||||
# }
|
||||
|
||||
|
||||
def grade_summary(log_folder: str) -> None:
|
||||
"""
|
||||
Generate test scores for log traces in the log folder and save the summary.
|
||||
"""
|
||||
log_folder = Path(log_folder)
|
||||
save_all_grade_info(log_folder)
|
||||
summarize_folder(log_folder)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire(
|
||||
{
|
||||
"grade": save_all_grade_info,
|
||||
"summary": summarize_folder,
|
||||
"grade_summary": grade_summary,
|
||||
}
|
||||
)
|
||||
@@ -1,253 +0,0 @@
|
||||
# API
|
||||
|
||||
## A. Controls
|
||||
|
||||
### 1. /upload [POST]
|
||||
|
||||
#### Request
|
||||
|
||||
- "scenario": one of six values
|
||||
1. "Finance Data Building"
|
||||
2. "Finance Data Building (Reports)"
|
||||
3. "Finance Model Implementation"
|
||||
4. "General Model Implementation"
|
||||
5. "Medical Model Implementation"
|
||||
6. "Data Science"
|
||||
- "files": **2** scenarios need this
|
||||
1. in "Finance Data Building (Reports)" Scenario, one or more pdf files.
|
||||
2. in "General Model Implementation" Scenario, one pdf file or one pdf link like `https://arxiv.org/pdf/2210.09789`
|
||||
- "competition": **Data Science** Scenario need this, one of 75 competitions.
|
||||
- "loops": Number of loops after which RD-Agent will automatically stop (optional; if not set, it will not stop automatically and must be stopped manually).
|
||||
- "all_duration": Total duration (in hours) for which the RD-Agent should run before stopping automatically. If not set, the agent will continue running until stopped manually or by the "loops" parameter.
|
||||
|
||||
#### Response
|
||||
|
||||
- "id": a unique identifier string, such as `/home/rdagent_log/data_science/competition_A/trace_1` or `/home/rdagent_log/finance/trace_1`, used to mark the series of logs generated by this RD-Agent run.
|
||||
|
||||
### 2. /control [POST]
|
||||
|
||||
#### Request
|
||||
|
||||
- "id": identifier
|
||||
- "action": one of three values
|
||||
1. "pause"
|
||||
2. "resume"
|
||||
3. "stop"
|
||||
|
||||
#### Response
|
||||
|
||||
- "status": "success" / "error: ..."
|
||||
|
||||
### 3. /trace [POST]
|
||||
|
||||
Returns the sequence of Messages generated for the current id on the backend that **have not yet been returned to the frontend**.
|
||||
|
||||
#### Request
|
||||
|
||||
- "id": identifier
|
||||
- "all": True / False. True means all Messages not yet provided to the frontend will be returned; False returns a random 1 to 10 Messages. In most cases, this should be True.
|
||||
- "reset": True / False. Reset means the pointer for "not yet returned to the frontend" will be set back to the first Message generated for this id, i.e., return from the beginning. In most cases, this should be False.
|
||||
|
||||
#### Response
|
||||
|
||||
- a list of [Messages](#b-messages)
|
||||
|
||||
## B. Messages
|
||||
|
||||
### Research
|
||||
|
||||
Only **2** Message in one loop
|
||||
|
||||
1. hypothesis
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "research.hypothesis",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"content": {
|
||||
"hypothesis": "...",
|
||||
"reason": "...",
|
||||
"component": "...", // only exists in Data Science Scenario
|
||||
"concise_reason": "...",
|
||||
"concise_justification": "...",
|
||||
"concise_observation": "...",
|
||||
"concise_knowledge": "...",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. tasks
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "research.tasks",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"content": [ // list of tasks
|
||||
{
|
||||
"name": "...",
|
||||
"description": "...",
|
||||
"model_type": "...", // only exists in "Finance Model Implementation", "General Model Implementation", "Medical Model Implementation", or some tasks of "Data Science"
|
||||
"architecture": "...", // same as above
|
||||
"hyperparameters": "...", // same as above
|
||||
},
|
||||
{
|
||||
|
||||
}
|
||||
//... same as above
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### evolving
|
||||
|
||||
- 1 to 10 pairs of Messages (codes & feedbacks), each identified by an "evo_id" indicating the evolving round.
|
||||
- In the **Data Science** scenario, each evolving round contains only **one task**, but the "codes" for that task may include **multiple code files**.
|
||||
- In other scenarios, each evolving round may contain **multiple tasks**, but each task's "codes" will include only **one code file**.
|
||||
|
||||
1. codes
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "evolving.codes",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"evo_id": "0",
|
||||
"content": [ // list of task_name & codes
|
||||
{
|
||||
"evo_id": "0",
|
||||
"target_task_name": "task_1",
|
||||
"workspace": { // one or more codes
|
||||
"a.py": "...<python codes>",
|
||||
"b.py": "...<python codes>",
|
||||
//...
|
||||
}
|
||||
},
|
||||
{
|
||||
"evo_id": "0",
|
||||
"target_task_name": "task_2",
|
||||
"workspace": {
|
||||
"a.py": "...<python codes>",
|
||||
//...
|
||||
}
|
||||
}
|
||||
//... same as above
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
"tag": "evolving.codes",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"evo_id": "1",
|
||||
"content": [
|
||||
//... same as above
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. feedbacks
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "evolving.feedbacks",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"evo_id": "0",
|
||||
"content": [ // list of feedbacks
|
||||
{
|
||||
"evo_id": "0",
|
||||
"final_decision": "True", // True or False
|
||||
"execution": "...",
|
||||
"code": "...",
|
||||
"return_checking": "..."
|
||||
},
|
||||
//... same as above
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
"tag": "evolving.codes",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"evo_id": "1",
|
||||
"content": [
|
||||
//... same as above
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### feedback
|
||||
|
||||
Each tag below appears only once per loop.
|
||||
|
||||
1. config (only exists in "Finance Data Building"/"Finance Data Building (Reports)"/"Finance Model Implementation")
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "feedback.config",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"content": {
|
||||
"config": "a markdown string",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. return_chart (only exists in "Finance Data Building"/"Finance Data Building (Reports)"/"Finance Model Implementation")
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "feedback.return_chart",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"content": {
|
||||
"chart_html": "chart html codes string",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. metric
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "feedback.metric",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"content": {
|
||||
"result": "{ \"<metric_name>\": <value>, ... }" // A JSON string containing metric names and their corresponding values.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. hypothesis_feedback
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "feedback.hypothesis_feedback",
|
||||
"timestamp": "<isoformat>",
|
||||
"loop_id": "1",
|
||||
"content": {
|
||||
"decision": "True",
|
||||
"reason": "...",
|
||||
"exception": "...",
|
||||
"observations": "...", // may not exists
|
||||
"hypothesis_evaluation": "...", // may not existsc
|
||||
"new_hypothesis": "...", // may not exists
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# TODO
|
||||
|
||||
## Session
|
||||
|
||||
- How to continue.
|
||||
- show & copy trace_id(name)?
|
||||
-
|
||||
|
||||
## Page
|
||||
|
||||
1. remove Medical, add Finance Whole Pipeline
|
||||
2.
|
||||
@@ -1,562 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from contextlib import redirect_stderr, redirect_stdout
|
||||
from datetime import datetime, timezone
|
||||
from multiprocessing import Process, Queue
|
||||
from pathlib import Path
|
||||
from queue import Empty
|
||||
|
||||
import randomname
|
||||
import typer
|
||||
from flask import Flask, jsonify, request, send_file, send_from_directory
|
||||
from flask_cors import CORS
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from rdagent.log.storage import FileStorage
|
||||
from rdagent.log.ui.conf import UI_SETTING
|
||||
from rdagent.log.ui.storage import WebStorage
|
||||
|
||||
app = Flask(__name__, static_folder=str(Path(UI_SETTING.static_path).resolve()))
|
||||
CORS(app)
|
||||
app.config["UI_SERVER_PORT"] = 19899
|
||||
|
||||
_YELLOW = "\033[33m"
|
||||
_RESET = "\033[0m"
|
||||
|
||||
|
||||
class _YellowWarningFormatter(logging.Formatter):
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
if record.levelno == logging.WARNING:
|
||||
record.levelname = f"{_YELLOW}{record.levelname}{_RESET}"
|
||||
return super().format(record)
|
||||
|
||||
|
||||
def _configure_app_logger() -> None:
|
||||
formatter = _YellowWarningFormatter(
|
||||
fmt="[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
for handler in app.logger.handlers:
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
|
||||
_configure_app_logger()
|
||||
|
||||
|
||||
_TARGETS_WITHOUT_USER_INTERACTION = {"general_model", "fin_factor_report"}
|
||||
|
||||
|
||||
class RDAgentTask:
|
||||
def __init__(
|
||||
self,
|
||||
target_name: str,
|
||||
kwargs: dict,
|
||||
stdout_path: str,
|
||||
log_trace_path: str,
|
||||
scenario: str,
|
||||
trace_name: str,
|
||||
ui_server_port: int | None = None,
|
||||
create_process: bool = True,
|
||||
) -> None:
|
||||
self.target_name = target_name
|
||||
self.kwargs = kwargs
|
||||
self.stdout_path = stdout_path
|
||||
self.log_trace_path = log_trace_path
|
||||
self.scenario = scenario
|
||||
self.trace_name = trace_name
|
||||
self.ui_server_port = ui_server_port
|
||||
self.process: Process | None = None
|
||||
|
||||
# Two IPC queues for user interaction.
|
||||
# - `user_request_q`: rdagent subprocess -> server (dicts to render on frontend)
|
||||
# - `user_response_q`: server -> rdagent subprocess (user input dicts)
|
||||
# NOTE: Use multiprocessing.Queue because rdagent is started as a separate process.
|
||||
self.user_request_q: Queue = Queue(maxsize=1024)
|
||||
self.user_response_q: Queue = Queue(maxsize=1024)
|
||||
|
||||
if create_process:
|
||||
self.process = Process(
|
||||
target=self._run,
|
||||
name=f"rdagent:{self.scenario}:{self.trace_name}",
|
||||
)
|
||||
self.messages: list[dict] = []
|
||||
self.pointers: defaultdict[str, int] = defaultdict(int)
|
||||
|
||||
def start(self) -> None:
|
||||
if self.process is not None:
|
||||
self.process.start()
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
return self.process is not None and self.process.is_alive()
|
||||
|
||||
def get_end_code(self) -> int:
|
||||
if self.process is None or self.process.exitcode is None:
|
||||
return 0
|
||||
return self.process.exitcode
|
||||
|
||||
def stop(self) -> None:
|
||||
if self.process is not None and self.process.is_alive():
|
||||
self.process.terminate()
|
||||
self.process.join()
|
||||
|
||||
# Best-effort cleanup for IPC queues.
|
||||
for q in (self.user_request_q, self.user_response_q):
|
||||
try:
|
||||
q.cancel_join_thread()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
q.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _run(self) -> None:
|
||||
from rdagent.log.conf import LOG_SETTINGS
|
||||
|
||||
LOG_SETTINGS.set_ui_server_port(self.ui_server_port)
|
||||
|
||||
from rdagent.log import rdagent_logger
|
||||
|
||||
rdagent_logger.refresh_storages_from_settings()
|
||||
rdagent_logger.set_storages_path(self.log_trace_path)
|
||||
Path(self.stdout_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.stdout_path, "w") as log_file:
|
||||
with redirect_stdout(log_file), redirect_stderr(log_file):
|
||||
rdagent_logger.rebind_console_to_current_streams()
|
||||
try:
|
||||
# Only interactive targets should receive IPC queues.
|
||||
if self.target_name not in _TARGETS_WITHOUT_USER_INTERACTION:
|
||||
self.kwargs.setdefault(
|
||||
"user_interaction_queues",
|
||||
(self.user_request_q, self.user_response_q),
|
||||
)
|
||||
|
||||
if self.target_name == "data_science":
|
||||
from rdagent.app.data_science.loop import main as data_science
|
||||
|
||||
data_science(**self.kwargs)
|
||||
elif self.target_name == "general_model":
|
||||
from rdagent.app.general_model.general_model import (
|
||||
extract_models_and_implement as general_model,
|
||||
)
|
||||
|
||||
general_model(**self.kwargs)
|
||||
elif self.target_name == "fin_factor":
|
||||
from rdagent.app.qlib_rd_loop.factor import main as fin_factor
|
||||
|
||||
fin_factor(**self.kwargs)
|
||||
elif self.target_name == "fin_factor_report":
|
||||
from rdagent.app.qlib_rd_loop.factor_from_report import (
|
||||
main as fin_factor_report,
|
||||
)
|
||||
|
||||
fin_factor_report(**self.kwargs)
|
||||
elif self.target_name == "fin_model":
|
||||
from rdagent.app.qlib_rd_loop.model import main as fin_model
|
||||
|
||||
fin_model(**self.kwargs)
|
||||
elif self.target_name == "fin_quant":
|
||||
from rdagent.app.qlib_rd_loop.quant import main as fin_quant
|
||||
|
||||
fin_quant(**self.kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unknown target: {self.target_name}")
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
rdagent_processes: dict[str, RDAgentTask] = {}
|
||||
log_folder_path = Path(UI_SETTING.trace_folder).absolute()
|
||||
|
||||
|
||||
def _drain_user_requests_into_messages(task: RDAgentTask) -> None:
|
||||
"""Move a single pending user-interaction request into `task.messages`.
|
||||
|
||||
Assumption: each rdagent process only has one active request at a time.
|
||||
"""
|
||||
|
||||
try:
|
||||
req = task.user_request_q.get_nowait()
|
||||
except Empty:
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
# Standardize the message shape for the frontend.
|
||||
# The agent can send either a full message dict, or a raw content dict.
|
||||
if isinstance(req, dict) and {"tag", "timestamp", "content"}.issubset(req.keys()):
|
||||
msg = req
|
||||
else:
|
||||
msg = {
|
||||
"tag": "user_interaction.request",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"content": req,
|
||||
}
|
||||
task.messages.append(msg)
|
||||
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory(app.static_folder, "favicon.ico", mimetype="image/vnd.microsoft.icon")
|
||||
|
||||
|
||||
def _normalize_static_request_path(fn: str) -> str:
|
||||
static_prefix = UI_SETTING.static_path.strip("./")
|
||||
if static_prefix and fn.startswith(f"{static_prefix}/"):
|
||||
return fn[len(static_prefix) + 1 :]
|
||||
return fn
|
||||
|
||||
|
||||
def _get_or_create_task(trace_id: str) -> RDAgentTask:
|
||||
task = rdagent_processes.get(trace_id)
|
||||
if task is None:
|
||||
task = RDAgentTask(
|
||||
target_name="",
|
||||
kwargs={},
|
||||
stdout_path="",
|
||||
log_trace_path=trace_id,
|
||||
scenario="",
|
||||
trace_name="",
|
||||
ui_server_port=None,
|
||||
create_process=False,
|
||||
)
|
||||
rdagent_processes[trace_id] = task
|
||||
return task
|
||||
|
||||
|
||||
def _resolve_stdout_path(trace_id: str) -> Path | None:
|
||||
normalized_trace_id = str(trace_id or "").strip()
|
||||
if not normalized_trace_id:
|
||||
return None
|
||||
|
||||
task = rdagent_processes.get(str(log_folder_path / normalized_trace_id))
|
||||
if task is None or not task.stdout_path:
|
||||
return None
|
||||
|
||||
stdout_path = Path(task.stdout_path).resolve()
|
||||
|
||||
try:
|
||||
if os.path.commonpath([str(stdout_path), str(log_folder_path)]) != str(log_folder_path):
|
||||
return None
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
return stdout_path
|
||||
|
||||
|
||||
def read_trace(log_path: Path, id: str = "") -> None:
|
||||
fs = FileStorage(log_path)
|
||||
ws = WebStorage(port=1, path=log_path)
|
||||
task = _get_or_create_task(id)
|
||||
task.messages = []
|
||||
last_timestamp = None
|
||||
for msg in fs.iter_msg():
|
||||
data = ws._obj_to_json(obj=msg.content, tag=msg.tag, id=id, timestamp=msg.timestamp.isoformat())
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
for d in data:
|
||||
task.messages.append(d["msg"])
|
||||
last_timestamp = msg.timestamp
|
||||
else:
|
||||
task.messages.append(data["msg"])
|
||||
last_timestamp = msg.timestamp
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
if last_timestamp and (now - last_timestamp).total_seconds() > 1800:
|
||||
task.messages.append(
|
||||
{
|
||||
"tag": "END",
|
||||
"timestamp": now.isoformat(),
|
||||
"content": {"error_msg": "Trace session has ended.", "end_code": 0},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# load all traces from the log folder
|
||||
# for p in log_folder_path.glob("*/*/"):
|
||||
# read_trace(p, id=str(p))
|
||||
|
||||
|
||||
@app.route("/trace", methods=["POST"])
|
||||
def update_trace():
|
||||
data = request.get_json()
|
||||
trace_id = data.get("id")
|
||||
return_all = data.get("all")
|
||||
reset = data.get("reset")
|
||||
msg_num = random.randint(1, 10)
|
||||
app.logger.info(data)
|
||||
log_folder_path = Path(UI_SETTING.trace_folder).absolute()
|
||||
if not trace_id:
|
||||
return jsonify({"error": "Trace ID is required"}), 400
|
||||
trace_id = str(log_folder_path / trace_id)
|
||||
|
||||
task = _get_or_create_task(trace_id)
|
||||
|
||||
# Make sure any pending user-interaction requests are visible to the frontend.
|
||||
_drain_user_requests_into_messages(task)
|
||||
|
||||
if task.process is not None and not task.is_alive():
|
||||
if not task.messages or task.messages[-1].get("tag") != "END":
|
||||
task.messages.append(
|
||||
{
|
||||
"tag": "END",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"content": {
|
||||
"error_msg": "RD-Agent process has completed.",
|
||||
"end_code": task.get_end_code(),
|
||||
},
|
||||
}
|
||||
)
|
||||
app.logger.warning(f"Process for {trace_id} has ended.")
|
||||
|
||||
user_ip = request.remote_addr
|
||||
|
||||
if reset:
|
||||
task.pointers[user_ip] = 0
|
||||
|
||||
start_pointer = task.pointers[user_ip]
|
||||
end_pointer = start_pointer + msg_num
|
||||
if end_pointer > len(task.messages) or return_all:
|
||||
end_pointer = len(task.messages)
|
||||
|
||||
returned_msgs = task.messages[start_pointer:end_pointer]
|
||||
task.pointers[user_ip] = end_pointer
|
||||
if returned_msgs:
|
||||
app.logger.info([msg["tag"] for msg in returned_msgs])
|
||||
return jsonify(returned_msgs), 200
|
||||
|
||||
|
||||
@app.route("/stdout", methods=["GET"])
|
||||
def download_stdout_file():
|
||||
trace_id = request.args.get("id", "")
|
||||
stdout_path = _resolve_stdout_path(trace_id)
|
||||
|
||||
if stdout_path is None:
|
||||
return jsonify({"error": "Trace ID is required or invalid"}), 400
|
||||
if not stdout_path.exists() or not stdout_path.is_file():
|
||||
return jsonify({"error": "Stdout file not found"}), 404
|
||||
|
||||
return send_file(
|
||||
stdout_path,
|
||||
as_attachment=True,
|
||||
download_name=stdout_path.name,
|
||||
mimetype="text/plain",
|
||||
)
|
||||
|
||||
|
||||
@app.route("/upload", methods=["POST"])
|
||||
def upload_file():
|
||||
# 获取请求体中的字段
|
||||
global rdagent_processes
|
||||
scenario = request.form.get("scenario")
|
||||
files = request.files.getlist("files")
|
||||
competition = request.form.get("competition")
|
||||
loop_n = request.form.get("loops")
|
||||
all_duration = request.form.get("all_duration")
|
||||
|
||||
# scenario = "Data Science Loop"
|
||||
if scenario == "Data Science":
|
||||
competition = competition[10:] # Eg. MLE-Bench:aerial-cactus-competition
|
||||
trace_name = f"{competition}-{randomname.get_name()}"
|
||||
else:
|
||||
trace_name = randomname.get_name()
|
||||
trace_files_path = log_folder_path / "uploads" / scenario / trace_name
|
||||
|
||||
log_trace_path = (log_folder_path / scenario / trace_name).absolute()
|
||||
stdout_path = log_folder_path / scenario / f"{trace_name}.log"
|
||||
if not stdout_path.exists():
|
||||
stdout_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# save files
|
||||
for file in files:
|
||||
if file:
|
||||
p = (log_folder_path / "uploads" / scenario / trace_name).resolve()
|
||||
sanitized_filename = secure_filename(file.filename) # Sanitize filename
|
||||
target_path = (p / sanitized_filename).resolve() # Normalize target path
|
||||
# Ensure target_path is within the allowed base directory
|
||||
if os.path.commonpath([str(target_path), str(p)]) == str(p) and target_path.is_file() == False:
|
||||
if not p.exists():
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
file.save(target_path)
|
||||
else:
|
||||
return jsonify({"error": "Invalid file path"}), 400
|
||||
|
||||
target_name = None
|
||||
kwargs = {}
|
||||
loop_n_val = int(loop_n) if loop_n else None
|
||||
all_duration_val = f"{all_duration}h" if all_duration else None
|
||||
|
||||
if scenario == "Finance Data Building":
|
||||
target_name = "fin_factor"
|
||||
kwargs = {
|
||||
"loop_n": loop_n_val,
|
||||
"all_duration": all_duration_val,
|
||||
"base_features_path": str(trace_files_path),
|
||||
}
|
||||
if scenario == "Finance Model Implementation":
|
||||
target_name = "fin_model"
|
||||
kwargs = {
|
||||
"loop_n": loop_n_val,
|
||||
"all_duration": all_duration_val,
|
||||
"base_features_path": str(trace_files_path),
|
||||
}
|
||||
if scenario == "Finance Whole Pipeline":
|
||||
target_name = "fin_quant"
|
||||
kwargs = {
|
||||
"loop_n": loop_n_val,
|
||||
"all_duration": all_duration_val,
|
||||
"base_features_path": str(trace_files_path),
|
||||
}
|
||||
if scenario == "Finance Data Building (Reports)":
|
||||
target_name = "fin_factor_report"
|
||||
kwargs = {"report_folder": str(trace_files_path), "all_duration": all_duration_val}
|
||||
if scenario == "General Model Implementation":
|
||||
if len(files) == 0: # files is one link
|
||||
rfp = request.form.get("files")[0]
|
||||
else: # one file is uploaded
|
||||
rfp = str(trace_files_path / files[0].filename)
|
||||
target_name = "general_model"
|
||||
kwargs = {"report_file_path": rfp}
|
||||
if scenario == "Data Science":
|
||||
target_name = "data_science"
|
||||
kwargs = {"competition": competition, "loop_n": loop_n_val, "timeout": all_duration_val}
|
||||
|
||||
if target_name is None:
|
||||
return jsonify({"error": "Unknown scenario"}), 400
|
||||
|
||||
app.logger.info(f"Started process for {log_trace_path} with target: {target_name}, kwargs: {kwargs}")
|
||||
task = RDAgentTask(
|
||||
target_name=target_name,
|
||||
kwargs=kwargs,
|
||||
stdout_path=str(stdout_path),
|
||||
log_trace_path=str(log_trace_path),
|
||||
scenario=scenario,
|
||||
trace_name=trace_name,
|
||||
ui_server_port=app.config["UI_SERVER_PORT"],
|
||||
)
|
||||
task.start()
|
||||
app.logger.warning(f"Task {log_trace_path} started.")
|
||||
rdagent_processes[str(log_trace_path)] = task
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"id": f"{scenario}/{trace_name}",
|
||||
}
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/receive", methods=["POST"])
|
||||
def receive_msgs():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "No JSON data received"}), 400
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Internal Server Error"}), 500
|
||||
|
||||
if isinstance(data, list):
|
||||
for d in data:
|
||||
task = _get_or_create_task(d["id"])
|
||||
task.messages.append(d["msg"])
|
||||
else:
|
||||
task = _get_or_create_task(data["id"])
|
||||
task.messages.append(data["msg"])
|
||||
|
||||
return jsonify({"status": "success"}), 200
|
||||
|
||||
|
||||
@app.route("/user_interaction/submit", methods=["POST"])
|
||||
def submit_user_interaction_response():
|
||||
"""Frontend submits a user response; server forwards it to the rdagent subprocess via IPC queue."""
|
||||
data = request.get_json(silent=True) or {}
|
||||
trace_id = data.get("id")
|
||||
payload = data.get("payload")
|
||||
|
||||
if not trace_id:
|
||||
return jsonify({"error": "Trace ID is required"}), 400
|
||||
if payload is None:
|
||||
return jsonify({"error": "Missing 'payload'"}), 400
|
||||
|
||||
trace_id = str(log_folder_path / trace_id)
|
||||
task = _get_or_create_task(trace_id)
|
||||
|
||||
try:
|
||||
task.user_response_q.put(payload, block=False)
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"Failed to enqueue user response: {e}"}), 500
|
||||
|
||||
return jsonify({"status": "success"}), 200
|
||||
|
||||
|
||||
@app.route("/control", methods=["POST"])
|
||||
def control_process():
|
||||
global rdagent_processes
|
||||
data = request.get_json()
|
||||
app.logger.info(data)
|
||||
if not data or "id" not in data or "action" not in data:
|
||||
return jsonify({"error": "Missing 'id' or 'action' in request"}), 400
|
||||
|
||||
id = str(log_folder_path / data["id"])
|
||||
action = data["action"]
|
||||
|
||||
if action != "stop":
|
||||
return jsonify({"error": "Only 'stop' action is supported"}), 400
|
||||
|
||||
if id not in rdagent_processes or rdagent_processes[id] is None:
|
||||
return jsonify({"error": "No running process for given id"}), 400
|
||||
|
||||
task = rdagent_processes[id]
|
||||
|
||||
if task.process is None:
|
||||
return jsonify({"error": "No running process for given id"}), 400
|
||||
|
||||
try:
|
||||
if task.is_alive():
|
||||
task.stop()
|
||||
|
||||
if not task.messages or task.messages[-1].get("tag") != "END":
|
||||
task.messages.append(
|
||||
{
|
||||
"tag": "END",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"content": {"error_msg": "RD-Agent process was stopped by user.", "end_code": -1},
|
||||
}
|
||||
)
|
||||
app.logger.warning(f"Process for {id} has been stopped.")
|
||||
return jsonify({"status": "stopped"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"Failed to {action} process, {e}"}), 500
|
||||
|
||||
|
||||
@app.route("/test", methods=["GET"])
|
||||
def test():
|
||||
# return 'Hello, World!'
|
||||
msgs = {k: [i["tag"] for i in task.messages] for k, task in rdagent_processes.items()}
|
||||
pointers = {k: dict(task.pointers) for k, task in rdagent_processes.items()}
|
||||
return jsonify({"msgs": msgs, "pointers": pointers}), 200
|
||||
|
||||
|
||||
@app.route("/", methods=["GET"])
|
||||
def index():
|
||||
# return 'Hello, World!'
|
||||
# return {k: [i["tag"] for i in v] for k, v in msgs_for_frontend.items()}
|
||||
return send_from_directory(app.static_folder, "index.html")
|
||||
|
||||
|
||||
@app.route("/<path:fn>", methods=["GET"])
|
||||
def server_static_files(fn):
|
||||
return send_from_directory(app.static_folder, _normalize_static_request_path(fn))
|
||||
|
||||
|
||||
def main(port: int = 19899):
|
||||
app.config["UI_SERVER_PORT"] = port
|
||||
app.run(debug=False, host="0.0.0.0", port=port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
||||
@@ -1,174 +0,0 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
import random
|
||||
import signal
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import randomname
|
||||
import typer
|
||||
from flask import Flask, jsonify, request, send_from_directory
|
||||
from flask_cors import CORS
|
||||
|
||||
from rdagent.log.ui.conf import UI_SETTING
|
||||
|
||||
app = Flask(__name__, static_folder=UI_SETTING.static_path)
|
||||
CORS(app)
|
||||
|
||||
rdagent_processes = defaultdict()
|
||||
server_port = 19899
|
||||
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory(app.static_folder, "favicon.ico", mimetype="image/vnd.microsoft.icon")
|
||||
|
||||
|
||||
msgs_for_frontend = defaultdict(list)
|
||||
pointers = defaultdict(int)
|
||||
|
||||
|
||||
@app.route("/trace", methods=["POST"])
|
||||
def update_trace():
|
||||
global pointers, msgs_for_frontend
|
||||
data = request.get_json()
|
||||
# app.logger.info(data)
|
||||
trace_id = data.get("id")
|
||||
return_all = data.get("all")
|
||||
reset = data.get("reset")
|
||||
msg_num = random.randint(1, 10)
|
||||
|
||||
if reset:
|
||||
pointers[trace_id] = 0
|
||||
|
||||
end_pointer = pointers[trace_id] + msg_num
|
||||
if end_pointer > len(msgs_for_frontend[trace_id]) or return_all:
|
||||
end_pointer = len(msgs_for_frontend[trace_id])
|
||||
|
||||
returned_msgs = msgs_for_frontend[trace_id][pointers[trace_id] : end_pointer]
|
||||
|
||||
pointers[trace_id] = end_pointer
|
||||
# if len(returned_msgs):
|
||||
# app.logger.info(data)
|
||||
# app.logger.info([i["tag"] for i in returned_msgs])
|
||||
# try:
|
||||
# import json
|
||||
# resp = json.dumps(returned_msgs, ensure_ascii=False)
|
||||
# except Exception as e:
|
||||
# app.logger.error(f"Error in jsonify: {e}")
|
||||
# for msg in returned_msgs:
|
||||
# try:
|
||||
# rr = json.dumps(msg, ensure_ascii=False)
|
||||
# except Exception as e:
|
||||
# app.logger.error(f"Error in jsonify individual message: {e}")
|
||||
# app.logger.error(msg)
|
||||
|
||||
return jsonify(returned_msgs), 200
|
||||
|
||||
|
||||
@app.route("/upload", methods=["POST"])
|
||||
def upload_file():
|
||||
# 获取请求体中的字段
|
||||
global rdagent_processes, server_port, msgs_for_frontend
|
||||
scenario = request.form.get("scenario")
|
||||
files = request.files.getlist("files")
|
||||
competition = request.form.get("competition")
|
||||
loop_n = request.form.get("loops")
|
||||
all_duration = request.form.get("all_duration")
|
||||
|
||||
log_folder_path = Path("/home/bowen/workspace/new_traces").absolute()
|
||||
|
||||
if scenario == "Data Science":
|
||||
trace_path = log_folder_path / "o1-preview" / f"{competition[10:]}.1"
|
||||
else:
|
||||
trace_path = log_folder_path / scenario
|
||||
id = f"{scenario}/{randomname.get_name()}"
|
||||
|
||||
def read_trace(log_path: Path, t: float = 0.2, id: str = "") -> None:
|
||||
from rdagent.log.storage import FileStorage
|
||||
from rdagent.log.ui.storage import WebStorage
|
||||
|
||||
fs = FileStorage(log_path)
|
||||
ws = WebStorage(port=1, path=log_path)
|
||||
msgs_for_frontend[id] = []
|
||||
for msg in fs.iter_msg():
|
||||
data = ws._obj_to_json(obj=msg.content, tag=msg.tag, id=id, timestamp=msg.timestamp.isoformat())
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
for d in data:
|
||||
time.sleep(t)
|
||||
msgs_for_frontend[id].append(d["msg"])
|
||||
else:
|
||||
time.sleep(t)
|
||||
msgs_for_frontend[id].append(data["msg"])
|
||||
msgs_for_frontend[id].append({"tag": "END", "timestamp": datetime.now(timezone.utc).isoformat(), "content": {}})
|
||||
|
||||
# 启动后台线程,不阻塞 return
|
||||
threading.Thread(target=read_trace, args=(trace_path, 0.5, id), daemon=True).start()
|
||||
|
||||
return jsonify({"id": id}), 200
|
||||
|
||||
|
||||
@app.route("/receive", methods=["POST"])
|
||||
def receive_msgs():
|
||||
try:
|
||||
data = request.get_json()
|
||||
app.logger.info(data["msg"]["tag"])
|
||||
if not data:
|
||||
return jsonify({"error": "No JSON data received"}), 400
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Internal Server Error"}), 500
|
||||
|
||||
if isinstance(data, list):
|
||||
for d in data:
|
||||
msgs_for_frontend[d["id"]].append(d["msg"])
|
||||
else:
|
||||
msgs_for_frontend[data["id"]].append(data["msg"])
|
||||
|
||||
return jsonify({"status": "success"}), 200
|
||||
|
||||
|
||||
@app.route("/control", methods=["POST"])
|
||||
def control_process():
|
||||
global rdagent_processes
|
||||
data = request.get_json()
|
||||
app.logger.info(data)
|
||||
if not data or "id" not in data or "action" not in data:
|
||||
return jsonify({"error": "Missing 'id' or 'action' in request"}), 400
|
||||
|
||||
id = data["id"]
|
||||
action = data["action"]
|
||||
|
||||
return jsonify({"status": "success", "message": f"Received action '{action}' for process with id '{id}'"})
|
||||
|
||||
|
||||
@app.route("/test", methods=["GET"])
|
||||
def test():
|
||||
# return 'Hello, World!'
|
||||
return {k: [i["tag"] for i in v] for k, v in msgs_for_frontend.items()}
|
||||
|
||||
|
||||
@app.route("/", methods=["GET"])
|
||||
def index():
|
||||
# return 'Hello, World!'
|
||||
# return {k: [i["tag"] for i in v] for k, v in msgs_for_frontend.items()}
|
||||
return send_from_directory(app.static_folder, "index.html")
|
||||
|
||||
|
||||
@app.route("/<path:fn>", methods=["GET"])
|
||||
def server_static_files(fn):
|
||||
return send_from_directory(app.static_folder, fn)
|
||||
|
||||
|
||||
def main(port: int = 19899):
|
||||
global server_port
|
||||
server_port = port
|
||||
app.run(debug=True, host="0.0.0.0", port=port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
||||
@@ -1,116 +0,0 @@
|
||||
import json
|
||||
import pickle
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Generator, Literal
|
||||
|
||||
from .base import Message, Storage
|
||||
from .utils import gen_datetime
|
||||
|
||||
LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||
|
||||
|
||||
def _remove_empty_dir(path: Path) -> None:
|
||||
"""
|
||||
Recursively remove empty directories.
|
||||
This function will remove the directory if it is empty after removing its subdirectories.
|
||||
"""
|
||||
if path.is_dir():
|
||||
sub_dirs = [sub for sub in path.iterdir() if sub.is_dir()]
|
||||
for sub in sub_dirs:
|
||||
_remove_empty_dir(sub)
|
||||
|
||||
if not any(path.iterdir()):
|
||||
path.rmdir()
|
||||
|
||||
|
||||
class FileStorage(Storage):
|
||||
"""
|
||||
The info are logginged to the file systems
|
||||
|
||||
TODO: describe the storage format
|
||||
"""
|
||||
|
||||
def __init__(self, path: str | Path) -> None:
|
||||
self.path = Path(path)
|
||||
|
||||
def log(
|
||||
self,
|
||||
obj: object,
|
||||
tag: str = "",
|
||||
timestamp: datetime | None = None,
|
||||
save_type: Literal["json", "text", "pkl"] = "pkl",
|
||||
**kwargs: Any,
|
||||
) -> str | Path:
|
||||
# TODO: We can remove the timestamp after we implement PipeLog
|
||||
timestamp = gen_datetime(timestamp)
|
||||
|
||||
cur_p = self.path / tag.replace(".", "/")
|
||||
cur_p.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
path = cur_p / f"{timestamp.strftime('%Y-%m-%d_%H-%M-%S-%f')}.log"
|
||||
|
||||
if save_type == "json":
|
||||
path = path.with_suffix(".json")
|
||||
with path.open("w") as f:
|
||||
try:
|
||||
json.dump(obj, f)
|
||||
except TypeError:
|
||||
json.dump(json.loads(str(obj)), f)
|
||||
return path
|
||||
elif save_type == "pkl":
|
||||
path = path.with_suffix(".pkl")
|
||||
with path.open("wb") as f:
|
||||
pickle.dump(obj, f)
|
||||
return path
|
||||
elif save_type == "text":
|
||||
obj = str(obj)
|
||||
with path.open("w") as f:
|
||||
f.write(obj)
|
||||
return path
|
||||
|
||||
log_pattern = re.compile(
|
||||
r"(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \| "
|
||||
r"(?P<level>DEBUG|INFO|WARNING|ERROR|CRITICAL) *\| "
|
||||
r"(?P<caller>.+:.+:\d+) - "
|
||||
)
|
||||
|
||||
def iter_msg(self, tag: str | None = None, pattern: str | None = None) -> Generator[Message, None, None]:
|
||||
msg_l = []
|
||||
|
||||
if pattern:
|
||||
pkl_files = pattern
|
||||
elif tag:
|
||||
pkl_files = f"**/{tag.replace('.','/')}/**/*.pkl"
|
||||
else:
|
||||
pkl_files = "**/*.pkl"
|
||||
for file in self.path.glob(pkl_files):
|
||||
if file.name == "debug_llm.pkl":
|
||||
continue
|
||||
pkl_log_tag = ".".join(file.relative_to(self.path).as_posix().replace("/", ".").split(".")[:-3])
|
||||
pid = file.parent.name
|
||||
|
||||
with file.open("rb") as f:
|
||||
content = pickle.load(f)
|
||||
|
||||
timestamp = datetime.strptime(file.stem, "%Y-%m-%d_%H-%M-%S-%f").replace(tzinfo=timezone.utc)
|
||||
|
||||
m = Message(tag=pkl_log_tag, level="INFO", timestamp=timestamp, caller="", pid_trace=pid, content=content)
|
||||
|
||||
msg_l.append(m)
|
||||
|
||||
msg_l.sort(key=lambda x: x.timestamp)
|
||||
for m in msg_l:
|
||||
yield m
|
||||
|
||||
def truncate(self, time: datetime) -> None:
|
||||
for file in self.path.glob("**/*.pkl"):
|
||||
timestamp = datetime.strptime(file.stem, "%Y-%m-%d_%H-%M-%S-%f").replace(tzinfo=timezone.utc)
|
||||
if timestamp > time.replace(tzinfo=timezone.utc):
|
||||
file.unlink()
|
||||
|
||||
_remove_empty_dir(self.path)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"FileStorage({self.path})"
|
||||
@@ -1,86 +0,0 @@
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from rdagent.core.utils import SingletonBaseClass
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
|
||||
|
||||
class RDAgentTimer:
|
||||
def __init__(self) -> None:
|
||||
self.started: bool = False
|
||||
self.target_time: datetime | None = None
|
||||
self.all_duration: timedelta | None = None
|
||||
self._remain_time_duration: timedelta | None = None
|
||||
|
||||
def reset(self, all_duration: str | timedelta) -> None:
|
||||
if isinstance(all_duration, str):
|
||||
pattern = re.compile(r"^\s*(\d*\.?\d+)\s*([smhd]?)\s*$")
|
||||
|
||||
match = pattern.match(all_duration)
|
||||
if not match:
|
||||
return None
|
||||
value = float(match.group(1))
|
||||
unit = match.group(2)
|
||||
if unit == "s":
|
||||
self.all_duration = timedelta(seconds=value)
|
||||
elif unit == "m":
|
||||
self.all_duration = timedelta(minutes=value)
|
||||
elif unit == "h":
|
||||
self.all_duration = timedelta(hours=value)
|
||||
elif unit == "d":
|
||||
self.all_duration = timedelta(days=value)
|
||||
else:
|
||||
self.all_duration = timedelta(seconds=value)
|
||||
elif isinstance(all_duration, timedelta):
|
||||
self.all_duration = all_duration
|
||||
self.target_time = datetime.now() + self.all_duration
|
||||
logger.info(f"Timer set to {self.all_duration} seconds and counting down.")
|
||||
self.started = True
|
||||
return None
|
||||
|
||||
def restart_by_remain_time(self) -> None:
|
||||
if self._remain_time_duration is not None:
|
||||
self.target_time = datetime.now() + self._remain_time_duration
|
||||
self.started = True
|
||||
logger.info(f"Timer restarted with remaining time: {self._remain_time_duration}")
|
||||
else:
|
||||
logger.warning("No remaining time to restart the timer.")
|
||||
return None
|
||||
|
||||
def add_duration(self, duration: timedelta) -> None:
|
||||
if self.started and self.target_time is not None:
|
||||
logger.info(f"Adding {duration} to the timer. Currently {self.remain_time()} remains.")
|
||||
self.target_time = self.target_time + duration
|
||||
self.update_remain_time()
|
||||
|
||||
def is_timeout(self) -> bool:
|
||||
if self.started and self.target_time is not None:
|
||||
self.update_remain_time()
|
||||
if datetime.now() > self.target_time:
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_remain_time(self) -> None:
|
||||
if self.started and self.target_time is not None:
|
||||
self._remain_time_duration = self.target_time - datetime.now()
|
||||
return None
|
||||
|
||||
def remain_time(self) -> timedelta | None:
|
||||
if self.started:
|
||||
self.update_remain_time()
|
||||
return self._remain_time_duration
|
||||
return None
|
||||
|
||||
|
||||
class RDAgentTimerWrapper(SingletonBaseClass):
|
||||
def __init__(self) -> None:
|
||||
self.timer: RDAgentTimer = RDAgentTimer()
|
||||
self.api_fail_count: int = 0
|
||||
self.latest_api_fail_time: datetime | None = None
|
||||
|
||||
def replace_timer(self, timer: RDAgentTimer) -> None:
|
||||
self.timer = timer
|
||||
logger.info("Timer replaced successfully.")
|
||||
|
||||
|
||||
RD_Agent_TIMER_wrapper: RDAgentTimerWrapper = RDAgentTimerWrapper()
|
||||
@@ -1,7 +0,0 @@
|
||||
"""
|
||||
UI is a kind of view for user.
|
||||
|
||||
We are not sure how generality of the UI, we can't make decision among following options:
|
||||
- in general folder like rdagent/log/ui
|
||||
- It is for specific scenario rdagent/scenarios/
|
||||
"""
|
||||
@@ -1,53 +0,0 @@
|
||||
# %%
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import streamlit as st
|
||||
|
||||
from rdagent.log.ui.conf import UI_SETTING
|
||||
from rdagent.utils.repo.diff import generate_diff_from_dict
|
||||
|
||||
aide_path = UI_SETTING.aide_path
|
||||
if not Path(aide_path).exists():
|
||||
st.error(f"Path {aide_path} does not exist, set it by `UI_AIDE_PATH`")
|
||||
st.stop()
|
||||
|
||||
jps = [str(i) for i in Path(aide_path).rglob("**/filtered_journal.json")]
|
||||
jps = sorted(jps)
|
||||
# st.write(jps)
|
||||
left, right = st.columns([1, 4])
|
||||
with left:
|
||||
default = 0
|
||||
ppp = f"{aide_path}/{st.query_params.get('jnp')}/logs/filtered_journal.json"
|
||||
if ppp in jps:
|
||||
default = jps.index(ppp)
|
||||
|
||||
jnp = st.radio("Select Journal", options=jps, index=default, format_func=lambda x: str(x).split("/")[-3])
|
||||
jnp = Path(jnp)
|
||||
|
||||
|
||||
with jnp.open("r") as f:
|
||||
d = json.load(f)
|
||||
# with jnp_.open("r") as f:
|
||||
# d1 = json.load(f)
|
||||
|
||||
nm = {nd["id"]: nd for nd in d["nodes"]}
|
||||
# %%
|
||||
with right:
|
||||
st.header("AIDE trace", divider="rainbow")
|
||||
st.subheader(jnp)
|
||||
for c, p in d["node2parent"].items():
|
||||
f = nm[p]
|
||||
t = nm[c]
|
||||
df_lines = generate_diff_from_dict({"aide.py": f["code"]}, {"aide.py": t["code"]})
|
||||
|
||||
with st.expander(f"Node {p} -> {c}"):
|
||||
st.markdown(f"## Parent ({f['metric']['value']}) Analysis")
|
||||
st.code(f["analysis"], wrap_lines=True)
|
||||
st.markdown(f"## Child ({t['metric']['value']}) Plan")
|
||||
st.code(t["plan"], wrap_lines=True)
|
||||
st.markdown("## Diff")
|
||||
st.code("".join(df_lines), language="diff", wrap_lines=True)
|
||||
# print("".join(df_lines))
|
||||
|
||||
# %%
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
||||
from pydantic_settings import SettingsConfigDict
|
||||
|
||||
from rdagent.core.conf import ExtendedBaseSettings
|
||||
|
||||
|
||||
class UIBasePropSetting(ExtendedBaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="UI_", protected_namespaces=())
|
||||
|
||||
default_log_folders: list[str] = ["./log"]
|
||||
|
||||
baseline_result_path: str = "./baseline.csv"
|
||||
|
||||
aide_path: str = "./aide"
|
||||
|
||||
amlt_path: str = "/data/share_folder_local/amlt"
|
||||
|
||||
static_path: str = "./git_ignore_folder/static"
|
||||
|
||||
trace_folder: str = "./git_ignore_folder/traces"
|
||||
|
||||
enable_cache: bool = True
|
||||
|
||||
|
||||
UI_SETTING = UIBasePropSetting()
|
||||
@@ -1,208 +0,0 @@
|
||||
"""
|
||||
Please refer to rdagent/log/ui/utils.py:get_summary_df for more detailed documents about metrics
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
import streamlit as st
|
||||
from streamlit import session_state as state
|
||||
|
||||
from rdagent.log.ui.utils import (
|
||||
ALL,
|
||||
HIGH,
|
||||
LITE,
|
||||
MEDIUM,
|
||||
curve_figure,
|
||||
get_statistics_df,
|
||||
get_summary_df,
|
||||
lite_curve_figure,
|
||||
percent_df,
|
||||
)
|
||||
from rdagent.scenarios.kaggle.kaggle_crawler import get_metric_direction
|
||||
|
||||
|
||||
def curves_win(summary: dict):
|
||||
# draw curves
|
||||
cbwin1, cbwin2 = st.columns(2)
|
||||
if cbwin1.toggle("Show Curves", key="show_curves"):
|
||||
for k, v in summary.items():
|
||||
with st.container(border=True):
|
||||
st.markdown(f"**:blue[{k}] - :violet[{v['competition']}]**")
|
||||
try:
|
||||
tscores = {k: v for k, v in v["test_scores"].items()}
|
||||
tscores = pd.Series(tscores)
|
||||
vscores = {}
|
||||
for k, vs in v["valid_scores"].items():
|
||||
if not vs.index.is_unique:
|
||||
st.warning(
|
||||
f"Loop {k}'s valid scores index are not unique, only the last one will be kept to show."
|
||||
)
|
||||
st.write(vs)
|
||||
vscores[k] = vs[~vs.index.duplicated(keep="last")].iloc[:, 0]
|
||||
if len(vscores) > 0:
|
||||
metric_name = list(vscores.values())[0].name
|
||||
else:
|
||||
metric_name = "None"
|
||||
vscores = pd.DataFrame(vscores)
|
||||
if "ensemble" in vscores.index:
|
||||
ensemble_row = vscores.loc[["ensemble"]]
|
||||
vscores = pd.concat([ensemble_row, vscores.drop("ensemble")])
|
||||
vscores = vscores.T
|
||||
vscores["test"] = tscores
|
||||
vscores.index = [f"L{i}" for i in vscores.index]
|
||||
vscores.columns.name = metric_name
|
||||
|
||||
st.plotly_chart(curve_figure(vscores))
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
st.markdown("- Error: " + str(e))
|
||||
st.code(traceback.format_exc())
|
||||
st.markdown("- Valid Scores: ")
|
||||
# st.write({k: type(v) for k, v in v["valid_scores"].items()})
|
||||
st.json(v["valid_scores"])
|
||||
if cbwin2.toggle("Show Curves (Lite)", key="show_curves_lite"):
|
||||
st.pyplot(lite_curve_figure(summary))
|
||||
|
||||
|
||||
def all_summarize_win():
|
||||
def shorten_folder_name(folder: str) -> str:
|
||||
if "amlt" in folder:
|
||||
return folder[folder.rfind("amlt") + 5 :].split("/")[0]
|
||||
if "ep" in folder:
|
||||
return folder[folder.rfind("ep") :]
|
||||
return folder
|
||||
|
||||
selected_folders = st.multiselect(
|
||||
"Show these folders",
|
||||
state.log_folders,
|
||||
state.log_folders,
|
||||
format_func=shorten_folder_name,
|
||||
)
|
||||
for lf in selected_folders:
|
||||
if not (Path(lf) / "summary.pkl").exists():
|
||||
st.warning(
|
||||
f"summary.pkl not found in **{lf}**\n\nRun:`dotenv run -- python rdagent/log/mle_summary.py grade_summary --log_folder={lf} --hours=<>`"
|
||||
)
|
||||
summary = {}
|
||||
dfs = []
|
||||
for lf in selected_folders:
|
||||
s, df = get_summary_df(lf)
|
||||
df.index = [f"{shorten_folder_name(lf)} - {idx}" for idx in df.index]
|
||||
|
||||
dfs.append(df)
|
||||
summary.update({f"{shorten_folder_name(lf)} - {k}": v for k, v in s.items()})
|
||||
base_df = pd.concat(dfs)
|
||||
|
||||
valid_rate = float(base_df.get("Valid Improve", pd.Series()).mean())
|
||||
test_rate = float(base_df.get("Test Improve", pd.Series()).mean())
|
||||
submit_merge_rate = float(base_df.get("Submit Merge", pd.Series()).mean())
|
||||
merge_sota_avg = float(base_df.get("Merge Sota", pd.Series()).mean())
|
||||
base_df = percent_df(base_df)
|
||||
base_df.insert(0, "Select", True)
|
||||
bt1, bt2 = st.columns(2)
|
||||
select_lite_level = bt2.selectbox(
|
||||
"Select MLE-Bench Competitions Level",
|
||||
options=["ALL", "HIGH", "MEDIUM", "LITE"],
|
||||
index=0,
|
||||
key="select_lite_level",
|
||||
)
|
||||
if select_lite_level != "ALL":
|
||||
if select_lite_level == "HIGH":
|
||||
lite_set = set(HIGH)
|
||||
elif select_lite_level == "MEDIUM":
|
||||
lite_set = set(MEDIUM)
|
||||
elif select_lite_level == "LITE":
|
||||
lite_set = set(LITE)
|
||||
else:
|
||||
lite_set = set()
|
||||
base_df["Select"] = base_df["Competition"].isin(lite_set)
|
||||
else:
|
||||
base_df["Select"] = True # select all if ALL is chosen
|
||||
|
||||
if bt1.toggle("Select Best", key="select_best"):
|
||||
|
||||
def apply_func(cdf: pd.DataFrame):
|
||||
cp = base_df.loc[cdf.index[0], "Competition"]
|
||||
md = get_metric_direction(cp)
|
||||
# If SOTA Exp Score (valid, to_submit) column is empty, return the first index
|
||||
if cdf["SOTA Exp Score (valid, to_submit)"].dropna().empty:
|
||||
return cdf.index[0]
|
||||
if md:
|
||||
best_idx = cdf["SOTA Exp Score (valid, to_submit)"].idxmax()
|
||||
else:
|
||||
best_idx = cdf["SOTA Exp Score (valid, to_submit)"].idxmin()
|
||||
return best_idx
|
||||
|
||||
best_idxs = base_df.groupby("Competition").apply(apply_func, include_groups=False)
|
||||
base_df["Select"] = base_df.index.isin(best_idxs.values)
|
||||
|
||||
base_df = st.data_editor(
|
||||
base_df,
|
||||
column_config={
|
||||
"Select": st.column_config.CheckboxColumn("Select", help="Stat this trace.", disabled=False),
|
||||
},
|
||||
disabled=(col for col in base_df.columns if col not in ["Select"]),
|
||||
)
|
||||
st.markdown("Ours vs Base: `math.exp(abs(math.log(sota_exp_score / baseline_score)))`")
|
||||
|
||||
# 统计选择的比赛
|
||||
base_df = base_df[base_df["Select"]]
|
||||
st.markdown(f"**统计的比赛数目: :red[{base_df.shape[0]}]**")
|
||||
stat_win_left, stat_win_right = st.columns(2)
|
||||
with stat_win_left:
|
||||
stat_df = get_statistics_df(base_df)
|
||||
st.dataframe(stat_df.round(2))
|
||||
markdown_table = f"""
|
||||
| xxx | {stat_df.iloc[0,1]:.1f} | {stat_df.iloc[1,1]:.1f} | {stat_df.iloc[2,1]:.1f} | {stat_df.iloc[3,1]:.1f} | {stat_df.iloc[4,1]:.1f} | {stat_df.iloc[5,1]:.1f} | {stat_df.iloc[6,1]:.1f} |
|
||||
| Valid Improve {valid_rate * 100:.2f}% | Test Improve {test_rate * 100:.2f}% | Submit Merge {submit_merge_rate * 100:.2f}% | Merge Sota {merge_sota_avg * 100:.2f}% |
|
||||
"""
|
||||
st.text(markdown_table)
|
||||
with stat_win_right:
|
||||
Loop_counts = base_df["Total Loops"]
|
||||
|
||||
# Create histogram
|
||||
fig = px.histogram(
|
||||
Loop_counts, nbins=15, title="Distribution of Total Loops", color_discrete_sequence=["#3498db"]
|
||||
)
|
||||
fig.update_layout(title_font_size=16, title_font_color="#2c3e50")
|
||||
|
||||
# Calculate statistics
|
||||
mean_value = Loop_counts.mean()
|
||||
median_value = Loop_counts.median()
|
||||
|
||||
# Add mean and median lines
|
||||
fig.add_vline(x=mean_value, line_color="#e74c3c", line_width=3)
|
||||
fig.add_vline(x=median_value, line_color="#f39c12", line_width=3)
|
||||
|
||||
fig.add_annotation(
|
||||
x=0.02,
|
||||
y=0.95,
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
text=f"<span style='color:#e74c3c; font-weight:bold'>Mean: {mean_value:.1f}</span><br><span style='color:#f39c12; font-weight:bold'>Median: {median_value:.1f}</span>",
|
||||
showarrow=False,
|
||||
bgcolor="rgba(255,255,255,0.9)",
|
||||
bordercolor="rgba(128,128,128,0.5)",
|
||||
borderwidth=1,
|
||||
font=dict(size=12, color="#333333"),
|
||||
)
|
||||
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# write curve
|
||||
st.subheader("Curves", divider="rainbow")
|
||||
curves_win(summary)
|
||||
|
||||
|
||||
with st.container(border=True):
|
||||
try:
|
||||
all_summarize_win()
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
st.error(f"Error occurred when show summary:\n{e}")
|
||||
st.code(traceback.format_exc())
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,172 +0,0 @@
|
||||
import json
|
||||
import pickle
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import streamlit as st
|
||||
from streamlit import session_state as state
|
||||
|
||||
from rdagent.app.data_science.conf import DS_RD_SETTING
|
||||
|
||||
st.set_page_config(layout="wide", page_title="RD-Agent_user_interact", page_icon="🎓", initial_sidebar_state="expanded")
|
||||
|
||||
# 初始化session state
|
||||
if "sessions" not in state:
|
||||
state.sessions = {}
|
||||
if "selected_session_name" not in state:
|
||||
state.selected_session_name = None
|
||||
|
||||
|
||||
def render_main_content():
|
||||
"""渲染主要内容区域"""
|
||||
if state.selected_session_name is not None and state.selected_session_name in state.sessions:
|
||||
selected_session_data = state.sessions[state.selected_session_name]
|
||||
if selected_session_data is not None:
|
||||
st.title(
|
||||
f"Session: {state.selected_session_name[:4]} with competition {selected_session_data['competition']}"
|
||||
)
|
||||
st.title("Contextual Information:")
|
||||
st.subheader("Competition scenario:", divider=True)
|
||||
scenario = st.code(selected_session_data["scenario_description"], language="yaml")
|
||||
st.subheader("Former attempts summary:", divider=True)
|
||||
scenario = st.code(selected_session_data["ds_trace_desc"], language="yaml")
|
||||
if selected_session_data["current_code"] != "":
|
||||
st.subheader("Current SOTA code", divider=True)
|
||||
scenario = st.code(
|
||||
body=selected_session_data["current_code"],
|
||||
language="python",
|
||||
)
|
||||
|
||||
st.subheader("Hypothesis candidates:", divider=True)
|
||||
hypothesis_candidates = selected_session_data["hypothesis_candidates"]
|
||||
tabs = st.tabs(
|
||||
[
|
||||
f"{'✅' if i == selected_session_data['target_hypothesis_index'] or selected_session_data['target_hypothesis_index'] == -1 else ''}Hypothesis {i+1}"
|
||||
for i in range(len(hypothesis_candidates))
|
||||
]
|
||||
)
|
||||
for index, hypothesis in enumerate(hypothesis_candidates):
|
||||
with tabs[index]:
|
||||
st.code(str(hypothesis), language="yaml")
|
||||
st.text("✅ means picked as target hypothesis")
|
||||
|
||||
st.title("Decisions to make:")
|
||||
|
||||
with st.form(key="user_form"):
|
||||
st.caption("Please modify the fields below and submit to provide your feedback.")
|
||||
target_hypothesis = st.text_area(
|
||||
"Target hypothesis: (you can copy from candidates)",
|
||||
value=(original_hypothesis := selected_session_data["target_hypothesis"].hypothesis),
|
||||
height="content",
|
||||
)
|
||||
target_task = st.text_area(
|
||||
"Target task description:",
|
||||
value=(original_task_desc := selected_session_data["task"].description),
|
||||
height="content",
|
||||
)
|
||||
original_user_instruction = selected_session_data.get("user_instruction")
|
||||
user_instruction_list = []
|
||||
if selected_session_data.get("former_user_instructions") is not None:
|
||||
st.caption(
|
||||
"Former user instructions, you can modify or delete the content to remove certain instruction."
|
||||
)
|
||||
for user_instruction in selected_session_data.get("former_user_instructions"):
|
||||
user_instruction_list.append(
|
||||
st.text_area("Former user instruction", value=user_instruction, height="content")
|
||||
)
|
||||
user_instruction_list.append(st.text_area("Add new user instruction", value="", height="content"))
|
||||
submit = st.form_submit_button("Submit")
|
||||
approve = st.form_submit_button("Approve without changes")
|
||||
|
||||
if submit or approve:
|
||||
if approve:
|
||||
submit_dict = {
|
||||
"action": "confirm",
|
||||
}
|
||||
else:
|
||||
user_instruction_str_list = [ui for ui in user_instruction_list if ui.strip() != ""]
|
||||
user_instruction_str_list = (
|
||||
None if len(user_instruction_str_list) == 0 else user_instruction_str_list
|
||||
)
|
||||
action = (
|
||||
"confirm"
|
||||
if target_hypothesis == original_hypothesis
|
||||
and target_task == original_task_desc
|
||||
and user_instruction_str_list == original_user_instruction
|
||||
else "rewrite"
|
||||
)
|
||||
submit_dict = {
|
||||
"target_hypothesis": target_hypothesis,
|
||||
"task_description": target_task,
|
||||
"user_instruction": user_instruction_str_list,
|
||||
"action": action,
|
||||
}
|
||||
json.dump(
|
||||
submit_dict,
|
||||
open(
|
||||
DS_RD_SETTING.user_interaction_mid_folder / f"{state.selected_session_name}_RET.json", "w"
|
||||
),
|
||||
)
|
||||
Path(DS_RD_SETTING.user_interaction_mid_folder / f"{state.selected_session_name}.pkl").unlink(
|
||||
missing_ok=True
|
||||
)
|
||||
st.success("Your feedback has been submitted. Thank you!")
|
||||
time.sleep(5)
|
||||
state.selected_session_name = None
|
||||
|
||||
if st.button("Extend expiration by 60s"):
|
||||
session_data = pickle.load(
|
||||
open(DS_RD_SETTING.user_interaction_mid_folder / f"{state.selected_session_name}.pkl", "rb")
|
||||
)
|
||||
session_data["expired_datetime"] = session_data["expired_datetime"] + timedelta(seconds=60)
|
||||
pickle.dump(
|
||||
session_data,
|
||||
open(DS_RD_SETTING.user_interaction_mid_folder / f"{state.selected_session_name}.pkl", "wb"),
|
||||
)
|
||||
else:
|
||||
st.warning("Please select a session from the sidebar.")
|
||||
|
||||
|
||||
# 每秒更新一次sessions
|
||||
@st.fragment(run_every=1)
|
||||
def update_sessions():
|
||||
log_folder = Path(DS_RD_SETTING.user_interaction_mid_folder)
|
||||
state.sessions = {}
|
||||
for session_file in log_folder.glob("*.pkl"):
|
||||
try:
|
||||
session_data = pickle.load(open(session_file, "rb"))
|
||||
if session_data["expired_datetime"] > datetime.now():
|
||||
state.sessions[session_file.stem] = session_data
|
||||
else:
|
||||
session_file.unlink(missing_ok=True)
|
||||
ret_file = log_folder / f"{session_file.stem}_RET.json"
|
||||
ret_file.unlink(missing_ok=True)
|
||||
except Exception as e:
|
||||
continue
|
||||
render_main_content()
|
||||
|
||||
|
||||
@st.fragment(run_every=1)
|
||||
def render_sidebar():
|
||||
st.title("R&D-Agent User Interaction Portal")
|
||||
if state.sessions:
|
||||
st.header("Active Sessions")
|
||||
st.caption("Click a session to view:")
|
||||
session_names = [name for name in state.sessions]
|
||||
for session_name in session_names:
|
||||
with st.container(border=True):
|
||||
remaining = state.sessions[session_name]["expired_datetime"] - datetime.now()
|
||||
total_sec = int(remaining.total_seconds())
|
||||
label = f"{total_sec}s to expire" if total_sec > 0 else "Expired"
|
||||
if st.button(f"session id:{session_name[:4]}", key=f"session_btn_{session_name}"):
|
||||
state.selected_session_name = session_name
|
||||
state.data = state.sessions[session_name]
|
||||
st.markdown(f"⏳ {label}")
|
||||
else:
|
||||
st.warning("No active sessions available. Please wait.")
|
||||
|
||||
|
||||
update_sessions()
|
||||
with st.sidebar:
|
||||
render_sidebar()
|
||||
@@ -1,51 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
import streamlit as st
|
||||
from streamlit import session_state as state
|
||||
|
||||
from rdagent.app.data_science.loop import DataScienceRDLoop
|
||||
from rdagent.log.ui.conf import UI_SETTING
|
||||
|
||||
|
||||
def convert_log_folder_str(lf: str) -> str:
|
||||
if "/" not in lf:
|
||||
return f"{UI_SETTING.amlt_path}/{lf.strip()}/combined_logs"
|
||||
return lf.strip()
|
||||
|
||||
|
||||
def extract_amlt_name(x: str) -> str:
|
||||
if "amlt" not in x:
|
||||
return x
|
||||
return x[x.rfind("amlt") + 5 :].split("/")[0]
|
||||
|
||||
|
||||
# 设置主日志路径
|
||||
if "log_folder" not in state:
|
||||
state.log_folder = Path("./log")
|
||||
if "log_folders" not in state:
|
||||
state.log_folders = [convert_log_folder_str(i) for i in UI_SETTING.default_log_folders]
|
||||
|
||||
summary_page = st.Page("ds_summary.py", title="Summary", icon="📊")
|
||||
trace_page = st.Page("ds_trace.py", title="Trace", icon="📈")
|
||||
aide_page = st.Page("aide.py", title="Aide", icon="🧑🏫")
|
||||
st.set_page_config(layout="wide", page_title="RD-Agent", page_icon="🎓", initial_sidebar_state="expanded")
|
||||
st.navigation([summary_page, trace_page, aide_page]).run()
|
||||
|
||||
|
||||
# UI - Sidebar
|
||||
with st.sidebar:
|
||||
st.subheader("Pages", divider="rainbow")
|
||||
st.page_link(summary_page, icon="📊")
|
||||
st.page_link(trace_page, icon="📈")
|
||||
st.page_link(aide_page, icon="🧑🏫")
|
||||
|
||||
st.subheader("Settings", divider="rainbow")
|
||||
with st.form("log_folder_form", border=False):
|
||||
log_folder_str = st.text_area(
|
||||
"**Log Folders**(split by ';')", value=";".join(extract_amlt_name(i) for i in state.log_folders)
|
||||
)
|
||||
if st.form_submit_button("Confirm"):
|
||||
state.log_folders = [
|
||||
convert_log_folder_str(folder) for folder in log_folder_str.split(";") if folder.strip()
|
||||
]
|
||||
st.rerun()
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 123 KiB |
@@ -1,306 +0,0 @@
|
||||
import argparse
|
||||
import json
|
||||
import pickle
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import streamlit as st
|
||||
from streamlit import session_state
|
||||
|
||||
from rdagent.log.ui.conf import UI_SETTING
|
||||
from rdagent.log.utils import extract_evoid, extract_loopid_func_name
|
||||
|
||||
st.set_page_config(layout="wide", page_title="debug_llm", page_icon="🎓", initial_sidebar_state="expanded")
|
||||
|
||||
# 获取 log_path 参数
|
||||
parser = argparse.ArgumentParser(description="RD-Agent Streamlit App")
|
||||
parser.add_argument("--log_dir", type=str, help="Path to the log directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def get_folders_sorted(log_path):
|
||||
"""缓存并返回排序后的文件夹列表,并加入进度打印"""
|
||||
with st.spinner("正在加载文件夹列表..."):
|
||||
folders = sorted(
|
||||
(folder for folder in log_path.iterdir() if folder.is_dir() and list(folder.iterdir())),
|
||||
key=lambda folder: folder.stat().st_mtime,
|
||||
reverse=True,
|
||||
)
|
||||
st.write(f"找到 {len(folders)} 个文件夹")
|
||||
return [folder.name for folder in folders]
|
||||
|
||||
|
||||
if UI_SETTING.enable_cache:
|
||||
get_folders_sorted = st.cache_data(get_folders_sorted)
|
||||
|
||||
|
||||
# 设置主日志路径
|
||||
main_log_path = Path(args.log_dir) if args.log_dir else Path("./log")
|
||||
if not main_log_path.exists():
|
||||
st.error(f"Log dir {main_log_path} does not exist!")
|
||||
st.stop()
|
||||
|
||||
if "data" not in session_state:
|
||||
session_state.data = []
|
||||
if "log_path" not in session_state:
|
||||
session_state.log_path = None
|
||||
|
||||
tlist = []
|
||||
|
||||
|
||||
def load_data():
|
||||
"""加载数据到 session_state 并显示进度"""
|
||||
log_file = main_log_path / session_state.log_path / "debug_llm.pkl"
|
||||
try:
|
||||
with st.spinner(f"正在加载数据文件 {log_file}..."):
|
||||
start_time = time.time()
|
||||
with open(log_file, "rb") as f:
|
||||
session_state.data = pickle.load(f)
|
||||
st.success(f"数据加载完成!耗时 {time.time() - start_time:.2f} 秒")
|
||||
st.session_state["current_loop"] = 1
|
||||
except Exception as e:
|
||||
session_state.data = [{"error": str(e)}]
|
||||
st.error(f"加载数据失败: {e}")
|
||||
|
||||
|
||||
# UI - Sidebar
|
||||
with st.sidebar:
|
||||
st.markdown(":blue[**Log Path**]")
|
||||
manually = st.toggle("Manual Input")
|
||||
if manually:
|
||||
st.text_input("log path", key="log_path", label_visibility="collapsed")
|
||||
else:
|
||||
folders = get_folders_sorted(main_log_path)
|
||||
st.selectbox(f"**Select from {main_log_path.absolute()}**", folders, key="log_path")
|
||||
|
||||
if st.button("Refresh Data"):
|
||||
load_data()
|
||||
st.rerun()
|
||||
|
||||
|
||||
# Helper functions
|
||||
def show_text(text, lang=None):
|
||||
"""显示文本代码块"""
|
||||
if lang:
|
||||
st.code(text, language=lang, wrap_lines=True)
|
||||
elif "\n" in text:
|
||||
st.code(text, language="python", wrap_lines=True)
|
||||
else:
|
||||
st.code(text, language="html", wrap_lines=True)
|
||||
|
||||
|
||||
def highlight_prompts_uri(uri):
|
||||
"""高亮 URI 的格式"""
|
||||
parts = uri.split(":")
|
||||
return f"**{parts[0]}:**:green[**{parts[1]}**]"
|
||||
|
||||
|
||||
# Display Data
|
||||
progress_text = st.empty()
|
||||
progress_bar = st.progress(0)
|
||||
|
||||
# 每页展示一个 Loop
|
||||
LOOPS_PER_PAGE = 1
|
||||
|
||||
# 获取所有的 Loop ID
|
||||
loop_groups = {}
|
||||
for i, d in enumerate(session_state.data):
|
||||
tag = d["tag"]
|
||||
loop_id, _ = extract_loopid_func_name(tag)
|
||||
if loop_id:
|
||||
if loop_id not in loop_groups:
|
||||
loop_groups[loop_id] = []
|
||||
loop_groups[loop_id].append(d)
|
||||
|
||||
# 按 Loop ID 排序
|
||||
sorted_loop_ids = sorted(loop_groups.keys(), key=int) # 假设 Loop ID 是数字
|
||||
total_loops = len(sorted_loop_ids)
|
||||
total_pages = total_loops # 每页展示一个 Loop
|
||||
|
||||
|
||||
# simple display
|
||||
# FIXME: Delete this simple UI if trace have tag(evo_id & loop_id)
|
||||
# with st.sidebar:
|
||||
# start = int(st.text_input("start", 0))
|
||||
# end = int(st.text_input("end", 100))
|
||||
# for m in session_state.data[start:end]:
|
||||
# if "tpl" in m["tag"]:
|
||||
# obj = m["obj"]
|
||||
# uri = obj["uri"]
|
||||
# tpl = obj["template"]
|
||||
# cxt = obj["context"]
|
||||
# rd = obj["rendered"]
|
||||
# with st.expander(highlight_prompts_uri(uri), expanded=False, icon="⚙️"):
|
||||
# t1, t2, t3 = st.tabs([":green[**Rendered**]", ":blue[**Template**]", ":orange[**Context**]"])
|
||||
# with t1:
|
||||
# show_text(rd)
|
||||
# with t2:
|
||||
# show_text(tpl, lang="django")
|
||||
# with t3:
|
||||
# st.json(cxt)
|
||||
# if "llm" in m["tag"]:
|
||||
# obj = m["obj"]
|
||||
# system = obj.get("system", None)
|
||||
# user = obj["user"]
|
||||
# resp = obj["resp"]
|
||||
# with st.expander(f"**LLM**", expanded=False, icon="🤖"):
|
||||
# t1, t2, t3 = st.tabs([":green[**Response**]", ":blue[**User**]", ":orange[**System**]"])
|
||||
# with t1:
|
||||
# try:
|
||||
# rdict = json.loads(resp)
|
||||
# if "code" in rdict:
|
||||
# code = rdict["code"]
|
||||
# st.markdown(":red[**Code in response dict:**]")
|
||||
# st.code(code, language="python", wrap_lines=True, line_numbers=True)
|
||||
# rdict.pop("code")
|
||||
# elif "spec" in rdict:
|
||||
# spec = rdict["spec"]
|
||||
# st.markdown(":red[**Spec in response dict:**]")
|
||||
# st.markdown(spec)
|
||||
# rdict.pop("spec")
|
||||
# else:
|
||||
# # show model codes
|
||||
# showed_keys = []
|
||||
# for k, v in rdict.items():
|
||||
# if k.startswith("model_") and k.endswith(".py"):
|
||||
# st.markdown(f":red[**{k}**]")
|
||||
# st.code(v, language="python", wrap_lines=True, line_numbers=True)
|
||||
# showed_keys.append(k)
|
||||
# for k in showed_keys:
|
||||
# rdict.pop(k)
|
||||
# st.write(":red[**Other parts (except for the code or spec) in response dict:**]")
|
||||
# st.json(rdict)
|
||||
# except:
|
||||
# st.json(resp)
|
||||
# with t2:
|
||||
# show_text(user)
|
||||
# with t3:
|
||||
# show_text(system or "No system prompt available")
|
||||
|
||||
|
||||
if total_pages:
|
||||
# 初始化 current_loop
|
||||
if "current_loop" not in st.session_state:
|
||||
st.session_state["current_loop"] = 1
|
||||
|
||||
# Loop 导航按钮
|
||||
col1, col2, col3, col4, col5 = st.sidebar.columns([1.2, 1, 2, 1, 1.2])
|
||||
|
||||
with col1:
|
||||
if st.button("|<"): # 首页
|
||||
st.session_state["current_loop"] = 1
|
||||
with col2:
|
||||
if st.button("<") and st.session_state["current_loop"] > 1: # 上一页
|
||||
st.session_state["current_loop"] -= 1
|
||||
with col3:
|
||||
# 下拉列表显示所有 Loop
|
||||
st.session_state["current_loop"] = st.selectbox(
|
||||
"选择 Loop",
|
||||
options=list(range(1, total_loops + 1)),
|
||||
index=st.session_state["current_loop"] - 1, # 默认选中当前 Loop
|
||||
label_visibility="collapsed", # 隐藏标签
|
||||
)
|
||||
with col4:
|
||||
if st.button("\>") and st.session_state["current_loop"] < total_loops: # 下一页
|
||||
st.session_state["current_loop"] += 1
|
||||
with col5:
|
||||
if st.button("\>|"): # 最后一页
|
||||
st.session_state["current_loop"] = total_loops
|
||||
|
||||
# 获取当前 Loop
|
||||
current_loop = st.session_state["current_loop"]
|
||||
|
||||
# 渲染当前 Loop 数据
|
||||
loop_id = sorted_loop_ids[current_loop - 1]
|
||||
progress_text = st.empty()
|
||||
progress_text.text(f"正在处理 Loop {loop_id}...")
|
||||
progress_bar.progress(current_loop / total_loops, text=f"Loop :green[**{current_loop}**] / {total_loops}")
|
||||
|
||||
# 渲染 Loop Header
|
||||
loop_anchor = f"Loop_{loop_id}"
|
||||
if loop_anchor not in tlist:
|
||||
tlist.append(loop_anchor)
|
||||
st.header(loop_anchor, anchor=loop_anchor, divider="blue")
|
||||
|
||||
# 渲染当前 Loop 的所有数据
|
||||
loop_data = loop_groups[loop_id]
|
||||
for d in loop_data:
|
||||
tag = d["tag"]
|
||||
obj = d["obj"]
|
||||
_, func_name = extract_loopid_func_name(tag)
|
||||
evo_id = extract_evoid(tag)
|
||||
|
||||
func_anchor = f"loop_{loop_id}.{func_name}"
|
||||
if func_anchor not in tlist:
|
||||
tlist.append(func_anchor)
|
||||
st.header(f"in *{func_name}*", anchor=func_anchor, divider="green")
|
||||
|
||||
evo_anchor = f"loop_{loop_id}.evo_step_{evo_id}"
|
||||
if evo_id and evo_anchor not in tlist:
|
||||
tlist.append(evo_anchor)
|
||||
st.subheader(f"evo_step_{evo_id}", anchor=evo_anchor, divider="orange")
|
||||
|
||||
# 根据 tag 渲染内容
|
||||
if "debug_exp_gen" in tag:
|
||||
with st.expander(
|
||||
f"Exp in :violet[**{obj.experiment_workspace.workspace_path}**]", expanded=False, icon="🧩"
|
||||
):
|
||||
st.write(obj)
|
||||
elif "debug_tpl" in tag:
|
||||
uri = obj["uri"]
|
||||
tpl = obj["template"]
|
||||
cxt = obj["context"]
|
||||
rd = obj["rendered"]
|
||||
with st.expander(highlight_prompts_uri(uri), expanded=False, icon="⚙️"):
|
||||
t1, t2, t3 = st.tabs([":green[**Rendered**]", ":blue[**Template**]", ":orange[**Context**]"])
|
||||
with t1:
|
||||
show_text(rd)
|
||||
with t2:
|
||||
show_text(tpl, lang="django")
|
||||
with t3:
|
||||
st.json(cxt)
|
||||
elif "debug_llm" in tag:
|
||||
system = obj.get("system", None)
|
||||
user = obj["user"]
|
||||
resp = obj["resp"]
|
||||
with st.expander(f"**LLM**", expanded=False, icon="🤖"):
|
||||
t1, t2, t3 = st.tabs([":green[**Response**]", ":blue[**User**]", ":orange[**System**]"])
|
||||
with t1:
|
||||
try:
|
||||
rdict = json.loads(resp)
|
||||
if "code" in rdict:
|
||||
code = rdict["code"]
|
||||
st.markdown(":red[**Code in response dict:**]")
|
||||
st.code(code, language="python", wrap_lines=True, line_numbers=True)
|
||||
rdict.pop("code")
|
||||
elif "spec" in rdict:
|
||||
spec = rdict["spec"]
|
||||
st.markdown(":red[**Spec in response dict:**]")
|
||||
st.markdown(spec)
|
||||
rdict.pop("spec")
|
||||
else:
|
||||
# show model codes
|
||||
showed_keys = []
|
||||
for k, v in rdict.items():
|
||||
if k.startswith("model_") and k.endswith(".py"):
|
||||
st.markdown(f":red[**{k}**]")
|
||||
st.code(v, language="python", wrap_lines=True, line_numbers=True)
|
||||
showed_keys.append(k)
|
||||
for k in showed_keys:
|
||||
rdict.pop(k)
|
||||
st.write(":red[**Other parts (except for the code or spec) in response dict:**]")
|
||||
st.json(rdict)
|
||||
except:
|
||||
st.json(resp)
|
||||
with t2:
|
||||
show_text(user)
|
||||
with t3:
|
||||
show_text(system or "No system prompt available")
|
||||
|
||||
progress_text.text("当前 Loop 数据处理完成!")
|
||||
|
||||
# Sidebar TOC
|
||||
with st.sidebar:
|
||||
toc = "\n".join([f"- [{t}](#{t})" if t.startswith("L") else f" - [{t.split('.')[1]}](#{t})" for t in tlist])
|
||||
st.markdown(toc, unsafe_allow_html=True)
|
||||
@@ -1,445 +0,0 @@
|
||||
import importlib
|
||||
import math
|
||||
|
||||
import pandas as pd
|
||||
import plotly.graph_objs as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
|
||||
class BaseGraph:
|
||||
_name = None
|
||||
|
||||
def __init__(
|
||||
self, df: pd.DataFrame = None, layout: dict = None, graph_kwargs: dict = None, name_dict: dict = None, **kwargs
|
||||
):
|
||||
"""
|
||||
|
||||
:param df:
|
||||
:param layout:
|
||||
:param graph_kwargs:
|
||||
:param name_dict:
|
||||
:param kwargs:
|
||||
layout: dict
|
||||
go.Layout parameters
|
||||
graph_kwargs: dict
|
||||
Graph parameters, eg: go.Bar(**graph_kwargs)
|
||||
"""
|
||||
self._df = df
|
||||
|
||||
self._layout = dict() if layout is None else layout
|
||||
self._graph_kwargs = dict() if graph_kwargs is None else graph_kwargs
|
||||
self._name_dict = name_dict
|
||||
|
||||
self.data = None
|
||||
|
||||
self._init_parameters(**kwargs)
|
||||
self._init_data()
|
||||
|
||||
def _init_data(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
if self._df.empty:
|
||||
raise ValueError("df is empty.")
|
||||
|
||||
self.data = self._get_data()
|
||||
|
||||
def _init_parameters(self, **kwargs):
|
||||
"""
|
||||
|
||||
:param kwargs
|
||||
"""
|
||||
|
||||
# Instantiate graphics parameters
|
||||
self._graph_type = self._name.lower().capitalize()
|
||||
|
||||
# Displayed column name
|
||||
if self._name_dict is None:
|
||||
self._name_dict = {_item: _item for _item in self._df.columns}
|
||||
|
||||
@staticmethod
|
||||
def get_instance_with_graph_parameters(graph_type: str = None, **kwargs):
|
||||
"""
|
||||
|
||||
:param graph_type:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
_graph_module = importlib.import_module("plotly.graph_objs")
|
||||
_graph_class = getattr(_graph_module, graph_type)
|
||||
except AttributeError:
|
||||
_graph_module = importlib.import_module("qlib.contrib.report.graph")
|
||||
_graph_class = getattr(_graph_module, graph_type)
|
||||
return _graph_class(**kwargs)
|
||||
|
||||
def _get_layout(self) -> go.Layout:
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
return go.Layout(**self._layout)
|
||||
|
||||
def _get_data(self) -> list:
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
|
||||
_data = [
|
||||
self.get_instance_with_graph_parameters(
|
||||
graph_type=self._graph_type, x=self._df.index, y=self._df[_col], name=_name, **self._graph_kwargs
|
||||
)
|
||||
for _col, _name in self._name_dict.items()
|
||||
]
|
||||
return _data
|
||||
|
||||
@property
|
||||
def figure(self) -> go.Figure:
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
_figure = go.Figure(data=self.data, layout=self._get_layout())
|
||||
# NOTE: Use the default theme from plotly version 3.x, template=None
|
||||
_figure["layout"].update(template=None)
|
||||
return _figure
|
||||
|
||||
|
||||
class SubplotsGraph:
|
||||
"""Create subplots same as df.plot(subplots=True)
|
||||
|
||||
Simple package for `plotly.tools.subplots`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
df: pd.DataFrame = None,
|
||||
kind_map: dict = None,
|
||||
layout: dict = None,
|
||||
sub_graph_layout: dict = None,
|
||||
sub_graph_data: list = None,
|
||||
subplots_kwargs: dict = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
|
||||
:param df: pd.DataFrame
|
||||
|
||||
:param kind_map: dict, subplots graph kind and kwargs
|
||||
eg: dict(kind='Scatter', kwargs=dict())
|
||||
|
||||
:param layout: `go.Layout` parameters
|
||||
|
||||
:param sub_graph_layout: Layout of each graphic, similar to 'layout'
|
||||
|
||||
:param sub_graph_data: Instantiation parameters for each sub-graphic
|
||||
eg: [(column_name, instance_parameters), ]
|
||||
|
||||
column_name: str or go.Figure
|
||||
|
||||
Instance_parameters:
|
||||
|
||||
- row: int, the row where the graph is located
|
||||
|
||||
- col: int, the col where the graph is located
|
||||
|
||||
- name: str, show name, default column_name in 'df'
|
||||
|
||||
- kind: str, graph kind, default `kind` param, eg: bar, scatter, ...
|
||||
|
||||
- graph_kwargs: dict, graph kwargs, default {}, used in `go.Bar(**graph_kwargs)`
|
||||
|
||||
:param subplots_kwargs: `plotly.tools.make_subplots` original parameters
|
||||
|
||||
- shared_xaxes: bool, default False
|
||||
|
||||
- shared_yaxes: bool, default False
|
||||
|
||||
- vertical_spacing: float, default 0.3 / rows
|
||||
|
||||
- subplot_titles: list, default []
|
||||
If `sub_graph_data` is None, will generate 'subplot_titles' according to `df.columns`,
|
||||
this field will be discarded
|
||||
|
||||
|
||||
- specs: list, see `make_subplots` docs
|
||||
|
||||
- rows: int, Number of rows in the subplot grid, default 1
|
||||
If `sub_graph_data` is None, will generate 'rows' according to `df`, this field will be discarded
|
||||
|
||||
- cols: int, Number of cols in the subplot grid, default 1
|
||||
If `sub_graph_data` is None, will generate 'cols' according to `df`, this field will be discarded
|
||||
|
||||
|
||||
:param kwargs:
|
||||
|
||||
"""
|
||||
|
||||
self._df = df
|
||||
self._layout = layout
|
||||
self._sub_graph_layout = sub_graph_layout
|
||||
|
||||
self._kind_map = kind_map
|
||||
if self._kind_map is None:
|
||||
self._kind_map = dict(kind="Scatter", kwargs=dict())
|
||||
|
||||
self._subplots_kwargs = subplots_kwargs
|
||||
if self._subplots_kwargs is None:
|
||||
self._init_subplots_kwargs()
|
||||
|
||||
self.__cols = self._subplots_kwargs.get("cols", 2) # pylint: disable=W0238
|
||||
self.__rows = self._subplots_kwargs.get( # pylint: disable=W0238
|
||||
"rows", math.ceil(len(self._df.columns) / self.__cols)
|
||||
)
|
||||
|
||||
self._sub_graph_data = sub_graph_data
|
||||
if self._sub_graph_data is None:
|
||||
self._init_sub_graph_data()
|
||||
|
||||
self._init_figure()
|
||||
|
||||
def _init_sub_graph_data(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
self._sub_graph_data = []
|
||||
self._subplot_titles = []
|
||||
|
||||
for i, column_name in enumerate(self._df.columns):
|
||||
row = math.ceil((i + 1) / self.__cols)
|
||||
_temp = (i + 1) % self.__cols
|
||||
col = _temp if _temp else self.__cols
|
||||
res_name = column_name.replace("_", " ")
|
||||
_temp_row_data = (
|
||||
column_name,
|
||||
dict(
|
||||
row=row,
|
||||
col=col,
|
||||
name=res_name,
|
||||
kind=self._kind_map["kind"],
|
||||
graph_kwargs=self._kind_map["kwargs"],
|
||||
),
|
||||
)
|
||||
self._sub_graph_data.append(_temp_row_data)
|
||||
self._subplot_titles.append(res_name)
|
||||
|
||||
def _init_subplots_kwargs(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Default cols, rows
|
||||
_cols = 2
|
||||
_rows = math.ceil(len(self._df.columns) / 2)
|
||||
self._subplots_kwargs = dict()
|
||||
self._subplots_kwargs["rows"] = _rows
|
||||
self._subplots_kwargs["cols"] = _cols
|
||||
self._subplots_kwargs["shared_xaxes"] = False
|
||||
self._subplots_kwargs["shared_yaxes"] = False
|
||||
self._subplots_kwargs["vertical_spacing"] = 0.3 / _rows
|
||||
self._subplots_kwargs["print_grid"] = False
|
||||
self._subplots_kwargs["subplot_titles"] = self._df.columns.tolist()
|
||||
|
||||
def _init_figure(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
self._figure = make_subplots(**self._subplots_kwargs)
|
||||
|
||||
for column_name, column_map in self._sub_graph_data:
|
||||
if isinstance(column_name, go.Figure):
|
||||
_graph_obj = column_name
|
||||
elif isinstance(column_name, str):
|
||||
temp_name = column_map.get("name", column_name.replace("_", " "))
|
||||
kind = column_map.get("kind", self._kind_map.get("kind", "Scatter"))
|
||||
_graph_kwargs = column_map.get("graph_kwargs", self._kind_map.get("kwargs", {}))
|
||||
_graph_obj = BaseGraph.get_instance_with_graph_parameters(
|
||||
kind,
|
||||
**dict(
|
||||
x=self._df.index,
|
||||
y=self._df[column_name],
|
||||
name=temp_name,
|
||||
**_graph_kwargs,
|
||||
),
|
||||
)
|
||||
else:
|
||||
raise TypeError()
|
||||
|
||||
row = column_map["row"]
|
||||
col = column_map["col"]
|
||||
|
||||
self._figure.add_trace(_graph_obj, row=row, col=col)
|
||||
|
||||
if self._sub_graph_layout is not None:
|
||||
for k, v in self._sub_graph_layout.items():
|
||||
self._figure["layout"][k].update(v)
|
||||
|
||||
# NOTE: Use the default theme from plotly version 3.x: template=None
|
||||
self._figure["layout"].update(template=None)
|
||||
self._figure["layout"].update(self._layout)
|
||||
|
||||
@property
|
||||
def figure(self):
|
||||
return self._figure
|
||||
|
||||
|
||||
def _calculate_maximum(df: pd.DataFrame, is_ex: bool = False):
|
||||
"""
|
||||
|
||||
:param df:
|
||||
:param is_ex:
|
||||
:return:
|
||||
"""
|
||||
if is_ex:
|
||||
end_date = df["cum_ex_return_wo_cost_mdd"].idxmin()
|
||||
start_date = df.loc[df.index <= end_date]["cum_ex_return_wo_cost"].idxmax()
|
||||
else:
|
||||
end_date = df["return_wo_mdd"].idxmin()
|
||||
start_date = df.loc[df.index <= end_date]["cum_return_wo_cost"].idxmax()
|
||||
return start_date, end_date
|
||||
|
||||
|
||||
def _calculate_mdd(series):
|
||||
"""
|
||||
Calculate mdd
|
||||
|
||||
:param series:
|
||||
:return:
|
||||
"""
|
||||
return series - series.cummax()
|
||||
|
||||
|
||||
def _calculate_report_data(raw_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
|
||||
:param df:
|
||||
:return:
|
||||
"""
|
||||
df = raw_df.copy(deep=True)
|
||||
index_names = df.index.names
|
||||
df.index = df.index.strftime("%Y-%m-%d")
|
||||
|
||||
report_df = pd.DataFrame()
|
||||
|
||||
report_df["cum_bench"] = df["bench"].cumsum()
|
||||
report_df["cum_return_wo_cost"] = df["return"].cumsum()
|
||||
report_df["cum_return_w_cost"] = (df["return"] - df["cost"]).cumsum()
|
||||
# report_df['cum_return'] - report_df['cum_return'].cummax()
|
||||
report_df["return_wo_mdd"] = _calculate_mdd(report_df["cum_return_wo_cost"])
|
||||
report_df["return_w_cost_mdd"] = _calculate_mdd((df["return"] - df["cost"]).cumsum())
|
||||
|
||||
report_df["cum_ex_return_wo_cost"] = (df["return"] - df["bench"]).cumsum()
|
||||
report_df["cum_ex_return_w_cost"] = (df["return"] - df["bench"] - df["cost"]).cumsum()
|
||||
report_df["cum_ex_return_wo_cost_mdd"] = _calculate_mdd((df["return"] - df["bench"]).cumsum())
|
||||
report_df["cum_ex_return_w_cost_mdd"] = _calculate_mdd((df["return"] - df["cost"] - df["bench"]).cumsum())
|
||||
# return_wo_mdd , return_w_cost_mdd, cum_ex_return_wo_cost_mdd, cum_ex_return_w
|
||||
|
||||
report_df["turnover"] = df["turnover"]
|
||||
report_df.sort_index(ascending=True, inplace=True)
|
||||
|
||||
report_df.index.names = index_names
|
||||
return report_df
|
||||
|
||||
|
||||
def report_figure(df: pd.DataFrame) -> list | tuple:
|
||||
"""
|
||||
|
||||
:param df:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Get data
|
||||
report_df = _calculate_report_data(df)
|
||||
|
||||
# Maximum Drawdown
|
||||
max_start_date, max_end_date = _calculate_maximum(report_df)
|
||||
ex_max_start_date, ex_max_end_date = _calculate_maximum(report_df, True)
|
||||
|
||||
index_name = report_df.index.name
|
||||
_temp_df = report_df.reset_index()
|
||||
_temp_df.loc[-1] = 0
|
||||
_temp_df = _temp_df.shift(1)
|
||||
_temp_df.loc[0, index_name] = "T0"
|
||||
_temp_df.set_index(index_name, inplace=True)
|
||||
_temp_df.iloc[0] = 0
|
||||
report_df = _temp_df
|
||||
|
||||
# Create figure
|
||||
_default_kind_map = dict(kind="Scatter", kwargs={"mode": "lines+markers"})
|
||||
_temp_fill_args = {"fill": "tozeroy", "mode": "lines+markers"}
|
||||
_column_row_col_dict = [
|
||||
("cum_bench", dict(row=1, col=1)),
|
||||
("cum_return_wo_cost", dict(row=1, col=1)),
|
||||
("cum_return_w_cost", dict(row=1, col=1)),
|
||||
("return_wo_mdd", dict(row=2, col=1, graph_kwargs=_temp_fill_args)),
|
||||
("return_w_cost_mdd", dict(row=3, col=1, graph_kwargs=_temp_fill_args)),
|
||||
("cum_ex_return_wo_cost", dict(row=4, col=1)),
|
||||
("cum_ex_return_w_cost", dict(row=4, col=1)),
|
||||
("turnover", dict(row=5, col=1)),
|
||||
("cum_ex_return_w_cost_mdd", dict(row=6, col=1, graph_kwargs=_temp_fill_args)),
|
||||
("cum_ex_return_wo_cost_mdd", dict(row=7, col=1, graph_kwargs=_temp_fill_args)),
|
||||
]
|
||||
|
||||
_subplot_layout = dict()
|
||||
for i in range(1, 8):
|
||||
# yaxis
|
||||
_subplot_layout.update({"yaxis{}".format(i): dict(zeroline=True, showline=True, showticklabels=True)})
|
||||
_show_line = i == 7
|
||||
_subplot_layout.update({"xaxis{}".format(i): dict(showline=_show_line, type="category", tickangle=45)})
|
||||
|
||||
_layout_style = dict(
|
||||
height=1200,
|
||||
title=" ",
|
||||
shapes=[
|
||||
{
|
||||
"type": "rect",
|
||||
"xref": "x",
|
||||
"yref": "paper",
|
||||
"x0": max_start_date,
|
||||
"y0": 0.55,
|
||||
"x1": max_end_date,
|
||||
"y1": 1,
|
||||
"fillcolor": "#d3d3d3",
|
||||
"opacity": 0.3,
|
||||
"line": {
|
||||
"width": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"xref": "x",
|
||||
"yref": "paper",
|
||||
"x0": ex_max_start_date,
|
||||
"y0": 0,
|
||||
"x1": ex_max_end_date,
|
||||
"y1": 0.55,
|
||||
"fillcolor": "#d3d3d3",
|
||||
"opacity": 0.3,
|
||||
"line": {
|
||||
"width": 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
_subplot_kwargs = dict(
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.01,
|
||||
rows=7,
|
||||
cols=1,
|
||||
row_width=[1, 1, 1, 3, 1, 1, 3],
|
||||
print_grid=False,
|
||||
)
|
||||
figure = SubplotsGraph(
|
||||
df=report_df,
|
||||
layout=_layout_style,
|
||||
sub_graph_data=_column_row_col_dict,
|
||||
subplots_kwargs=_subplot_kwargs,
|
||||
kind_map=_default_kind_map,
|
||||
sub_graph_layout=_subplot_layout,
|
||||
).figure
|
||||
return figure
|
||||
@@ -1,126 +0,0 @@
|
||||
from typing import Literal
|
||||
|
||||
import streamlit as st
|
||||
from streamlit.components.v1 import html
|
||||
|
||||
FIXED_CONTAINER_CSS = """
|
||||
:root {{
|
||||
--background-color: #ffffff; /* Default background color */
|
||||
}}
|
||||
div[data-testid="stVerticalBlockBorderWrapper"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) {{
|
||||
position: {mode};
|
||||
width: inherit;
|
||||
background-color: inherit;
|
||||
{position}: {margin};
|
||||
z-index: 999;
|
||||
}}
|
||||
div[data-testid="stVerticalBlockBorderWrapper"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) div[data-testid="stVerticalBlock"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) > div[data-testid="stVerticalBlockBorderWrapper"] {{
|
||||
background-color: transparent;
|
||||
width: 100%;
|
||||
}}
|
||||
div[data-testid="stVerticalBlockBorderWrapper"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) div[data-testid="stVerticalBlock"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) > div[data-testid="stVerticalBlockBorderWrapper"] div[data-testid="stVerticalBlockBorderWrapper"] {{
|
||||
background-color: var(--background-color);
|
||||
}}
|
||||
div[data-testid="stVerticalBlockBorderWrapper"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) div[data-testid="stVerticalBlock"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) > div[data-testid="element-container"] {{
|
||||
display: none;
|
||||
}}
|
||||
div[data-testid="stVerticalBlockBorderWrapper"]:has(div.not-fixed-container):not(:has(div[class^='fixed-container-'])) {{
|
||||
display: none;
|
||||
}}
|
||||
""".strip()
|
||||
|
||||
FIXED_CONTAINER_JS = """
|
||||
const root = parent.document.querySelector('.stApp');
|
||||
let lastBackgroundColor = null;
|
||||
function updateContainerBackground(currentBackground) {
|
||||
parent.document.documentElement.style.setProperty('--background-color', currentBackground);
|
||||
;
|
||||
}
|
||||
function checkForBackgroundColorChange() {
|
||||
const style = window.getComputedStyle(root);
|
||||
const currentBackgroundColor = style.backgroundColor;
|
||||
if (currentBackgroundColor !== lastBackgroundColor) {
|
||||
lastBackgroundColor = currentBackgroundColor; // Update the last known value
|
||||
updateContainerBackground(lastBackgroundColor);
|
||||
}
|
||||
}
|
||||
const observerCallback = (mutationsList, observer) => {
|
||||
for(let mutation of mutationsList) {
|
||||
if (mutation.type === 'attributes' && (mutation.attributeName === 'class' || mutation.attributeName === 'style')) {
|
||||
checkForBackgroundColorChange();
|
||||
}
|
||||
}
|
||||
};
|
||||
const main = () => {
|
||||
checkForBackgroundColorChange();
|
||||
const observer = new MutationObserver(observerCallback);
|
||||
observer.observe(root, { attributes: true, childList: false, subtree: false });
|
||||
}
|
||||
// main();
|
||||
document.addEventListener("DOMContentLoaded", main);
|
||||
""".strip()
|
||||
|
||||
|
||||
MARGINS = {
|
||||
"top": "2.875rem",
|
||||
"bottom": "0",
|
||||
}
|
||||
|
||||
|
||||
counter = 0
|
||||
|
||||
|
||||
def st_fixed_container(
|
||||
*,
|
||||
height: int | None = None,
|
||||
border: bool | None = None,
|
||||
mode: Literal["fixed", "sticky"] = "fixed",
|
||||
position: Literal["top", "bottom"] = "top",
|
||||
margin: str | None = None,
|
||||
transparent: bool = False,
|
||||
):
|
||||
if margin is None:
|
||||
margin = MARGINS[position]
|
||||
global counter
|
||||
|
||||
fixed_container = st.container()
|
||||
non_fixed_container = st.container()
|
||||
css = FIXED_CONTAINER_CSS.format(
|
||||
mode=mode,
|
||||
position=position,
|
||||
margin=margin,
|
||||
id=counter,
|
||||
)
|
||||
with fixed_container:
|
||||
html(f"<script>{FIXED_CONTAINER_JS}</script>", scrolling=False, height=0)
|
||||
st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
|
||||
st.markdown(
|
||||
f"<div class='fixed-container-{counter}'></div>",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
with non_fixed_container:
|
||||
st.markdown(
|
||||
f"<div class='not-fixed-container'></div>",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
counter += 1
|
||||
|
||||
parent_container = fixed_container if transparent else fixed_container.container()
|
||||
return parent_container.container(height=height, border=border)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for i in range(30):
|
||||
st.write(f"Line {i}")
|
||||
|
||||
# with st_fixed_container(mode="sticky", position="top", border=True):
|
||||
# with st_fixed_container(mode="sticky", position="bottom", border=True):
|
||||
# with st_fixed_container(mode="fixed", position="top", border=True):
|
||||
with st_fixed_container(mode="fixed", position="bottom", border=True):
|
||||
st.write("This is a fixed container.")
|
||||
st.write("This is a fixed container.")
|
||||
st.write("This is a fixed container.")
|
||||
|
||||
st.container(border=True).write("This is a regular container.")
|
||||
for i in range(30):
|
||||
st.write(f"Line {i}")
|
||||
@@ -1,345 +0,0 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Generator
|
||||
|
||||
import requests
|
||||
|
||||
from rdagent.log.base import Message, Storage
|
||||
from rdagent.log.utils import extract_evoid, extract_loopid_func_name, gen_datetime
|
||||
|
||||
from .conf import UI_SETTING
|
||||
|
||||
|
||||
class WebStorage(Storage):
|
||||
"""
|
||||
The storage for web app.
|
||||
It is used to provide the data for the web app.
|
||||
"""
|
||||
|
||||
def __init__(self, port: int, path: str) -> None:
|
||||
"""
|
||||
Initializes the storage object with the specified port and identifier.
|
||||
Args:
|
||||
port (int): The port number to use for the storage service.
|
||||
path (str): The unique identifier for local storage, the log path.
|
||||
"""
|
||||
self.url = f"http://localhost:{port}"
|
||||
self.path = path
|
||||
self.msgs = []
|
||||
|
||||
def __str__(self):
|
||||
return f"WebStorage({self.url})"
|
||||
|
||||
def log(self, obj: object, tag: str, timestamp: datetime | None = None, **kwargs: Any) -> str | Path:
|
||||
timestamp = gen_datetime(timestamp)
|
||||
if "pdf_image" in tag or "load_pdf_screenshot" in tag:
|
||||
Path(f"{UI_SETTING.static_path}/pdf_images").mkdir(parents=True, exist_ok=True)
|
||||
obj.save(f"{UI_SETTING.static_path}/pdf_images/{timestamp.isoformat()}.jpg")
|
||||
|
||||
try:
|
||||
data = self._obj_to_json(obj=obj, tag=tag, id=str(self.path), timestamp=timestamp.isoformat())
|
||||
if not data:
|
||||
return "Normal log, skipped"
|
||||
if isinstance(data, list):
|
||||
for d in data:
|
||||
self.msgs.append(d)
|
||||
else:
|
||||
self.msgs.append(data)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
resp = requests.post(f"{self.url}/receive", json=data, headers=headers, timeout=1)
|
||||
return f"{resp.status_code} {resp.text}"
|
||||
except (requests.ConnectionError, requests.Timeout) as e:
|
||||
print(f"Failed to connect to the web storage server at {self.url}: {e}")
|
||||
|
||||
def truncate(self, time: datetime) -> None:
|
||||
self.msgs = [m for m in self.msgs if datetime.fromisoformat(m["msg"]["timestamp"]) <= time]
|
||||
|
||||
def iter_msg(self, **kwargs: Any) -> Generator[Message, None, None]:
|
||||
for msg in self.msgs:
|
||||
yield Message(
|
||||
tag=msg["msg"]["tag"],
|
||||
level="INFO",
|
||||
timestamp=datetime.fromisoformat(msg["msg"]["timestamp"]),
|
||||
content=msg,
|
||||
)
|
||||
|
||||
def _obj_to_json(
|
||||
self,
|
||||
obj: object,
|
||||
tag: str,
|
||||
id: str,
|
||||
timestamp: str,
|
||||
) -> list[dict] | dict:
|
||||
li, fn = extract_loopid_func_name(tag)
|
||||
ei = extract_evoid(tag)
|
||||
data = {}
|
||||
if "hypothesis generation" in tag:
|
||||
from rdagent.core.proposal import Hypothesis
|
||||
|
||||
h: Hypothesis = obj
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "research.hypothesis",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": {
|
||||
"hypothesis": h.hypothesis,
|
||||
"reason": h.reason,
|
||||
"concise_reason": h.concise_reason,
|
||||
"concise_justification": h.concise_justification,
|
||||
"concise_observation": h.concise_observation,
|
||||
"concise_knowledge": h.concise_knowledge,
|
||||
},
|
||||
},
|
||||
}
|
||||
elif "pdf_image" in tag or "load_pdf_screenshot" in tag:
|
||||
# obj.save(f"{app.static_folder}/{timestamp}.jpg")
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "research.pdf_image",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": {"image": f"pdf_images/{timestamp}.jpg"},
|
||||
},
|
||||
}
|
||||
elif "experiment generation" in tag or "load_experiment" in tag:
|
||||
from rdagent.components.coder.factor_coder.factor import FactorTask
|
||||
from rdagent.components.coder.model_coder.model import ModelTask
|
||||
|
||||
if "load_experiment" in tag:
|
||||
tasks: list[FactorTask | ModelTask] = obj.sub_tasks
|
||||
else:
|
||||
tasks: list[FactorTask | ModelTask] = obj
|
||||
if isinstance(tasks[0], FactorTask):
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "research.tasks",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": [
|
||||
{
|
||||
"name": t.factor_name,
|
||||
"description": t.factor_description,
|
||||
"formulation": t.factor_formulation,
|
||||
"variables": t.variables,
|
||||
}
|
||||
for t in tasks
|
||||
],
|
||||
},
|
||||
}
|
||||
elif isinstance(tasks[0], ModelTask):
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "research.tasks",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": [
|
||||
{
|
||||
"name": t.name,
|
||||
"description": t.description,
|
||||
"model_type": t.model_type,
|
||||
"formulation": t.formulation,
|
||||
"variables": t.variables,
|
||||
}
|
||||
for t in tasks
|
||||
],
|
||||
},
|
||||
}
|
||||
elif "direct_exp_gen" in tag:
|
||||
from rdagent.scenarios.data_science.experiment.experiment import (
|
||||
DSExperiment,
|
||||
)
|
||||
|
||||
if isinstance(obj, DSExperiment):
|
||||
from rdagent.scenarios.data_science.proposal.exp_gen.base import (
|
||||
DSHypothesis,
|
||||
)
|
||||
|
||||
h: DSHypothesis = obj.hypothesis
|
||||
tasks = [t[0] for t in obj.pending_tasks_list]
|
||||
t = tasks[0]
|
||||
t.name = type(t).__name__ # TODO: PipelinTask have "COMPONENT" in name, fix this when creating task.
|
||||
data = [
|
||||
{
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "research.hypothesis",
|
||||
"old_tag": tag,
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": {
|
||||
"name_map": {
|
||||
"hypothesis": "RD-Agent proposes the hypothesis⬇️",
|
||||
"concise_justification": "because the reason⬇️",
|
||||
"concise_observation": "based on the observation⬇️",
|
||||
"concise_knowledge": "Knowledge⬇️ gained after practice",
|
||||
"no_hypothesis": f"No hypothesis available. Trying to construct the first runnable {h.component} component.",
|
||||
},
|
||||
"hypothesis": h.hypothesis,
|
||||
"reason": h.reason,
|
||||
"component": h.component,
|
||||
"concise_reason": h.concise_reason,
|
||||
"concise_justification": h.concise_justification,
|
||||
"concise_observation": h.concise_observation,
|
||||
"concise_knowledge": h.concise_knowledge,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "research.tasks",
|
||||
"old_tag": tag,
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": [
|
||||
(
|
||||
{
|
||||
"name": t.name,
|
||||
"description": t.description,
|
||||
}
|
||||
if not hasattr(t, "architecture")
|
||||
else {
|
||||
"name": t.name,
|
||||
"description": t.description,
|
||||
"model_type": t.model_type,
|
||||
"architecture": t.architecture,
|
||||
"hyperparameters": t.hyperparameters,
|
||||
}
|
||||
)
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
elif f"evo_loop_{ei}.evolving code" in tag and "running" not in tag:
|
||||
from rdagent.core.experiment import FBWorkspace
|
||||
|
||||
ws: list[FBWorkspace] = [i for i in obj]
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "evolving.codes",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"evo_id": ei,
|
||||
"content": [
|
||||
{
|
||||
"evo_id": ei,
|
||||
"target_task_name": (
|
||||
w.target_task.name if w.target_task else "PipelineTask"
|
||||
), # TODO: save this when proposal
|
||||
"workspace": w.file_dict,
|
||||
}
|
||||
for w in ws
|
||||
],
|
||||
},
|
||||
}
|
||||
elif f"evo_loop_{ei}.evolving feedback" in tag and "running" not in tag:
|
||||
from rdagent.components.coder.CoSTEER.evaluators import (
|
||||
CoSTEERSingleFeedback,
|
||||
)
|
||||
|
||||
fl: list[CoSTEERSingleFeedback] = [i for i in obj]
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "evolving.feedbacks",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"evo_id": ei,
|
||||
"content": [
|
||||
{
|
||||
"evo_id": ei,
|
||||
"final_decision": f.final_decision,
|
||||
# "final_feedback": f.final_feedback,
|
||||
"execution": f.execution,
|
||||
"code": f.code,
|
||||
"return_checking": f.return_checking,
|
||||
}
|
||||
for f in fl
|
||||
],
|
||||
},
|
||||
}
|
||||
elif "scenario" in tag:
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "feedback.config",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": {"config": obj.experiment_setting},
|
||||
},
|
||||
}
|
||||
|
||||
elif "Quantitative Backtesting Chart" in tag:
|
||||
import plotly
|
||||
|
||||
from rdagent.log.ui.qlib_report_figure import report_figure
|
||||
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "feedback.return_chart",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": {"chart_html": plotly.io.to_html(report_figure(obj))},
|
||||
},
|
||||
}
|
||||
elif "running" in tag:
|
||||
from rdagent.core.experiment import Experiment
|
||||
|
||||
if isinstance(obj, Experiment):
|
||||
try:
|
||||
result = obj.result
|
||||
except AttributeError: # compatibility with old versions
|
||||
result = obj.__dict__["result"]
|
||||
if result is not None:
|
||||
result_str = result.to_json()
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "feedback.metric",
|
||||
"old_tag": tag,
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": {
|
||||
"result": result_str,
|
||||
},
|
||||
},
|
||||
}
|
||||
elif "feedback" in tag:
|
||||
from rdagent.core.proposal import ExperimentFeedback, HypothesisFeedback
|
||||
|
||||
if isinstance(obj, ExperimentFeedback):
|
||||
ef: ExperimentFeedback = obj
|
||||
content = (
|
||||
{
|
||||
"observations": str(ef.observations),
|
||||
"hypothesis_evaluation": ef.hypothesis_evaluation,
|
||||
"new_hypothesis": ef.new_hypothesis,
|
||||
"decision": ef.decision,
|
||||
"reason": ef.reason,
|
||||
"exception": ef.exception,
|
||||
}
|
||||
if isinstance(ef, HypothesisFeedback)
|
||||
else {
|
||||
"decision": ef.decision,
|
||||
"reason": ef.reason,
|
||||
"exception": ef.exception,
|
||||
}
|
||||
)
|
||||
data = {
|
||||
"id": id,
|
||||
"msg": {
|
||||
"tag": "feedback.hypothesis_feedback",
|
||||
"timestamp": timestamp,
|
||||
"loop_id": li,
|
||||
"content": content,
|
||||
},
|
||||
}
|
||||
|
||||
return data
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,629 +0,0 @@
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timezone
|
||||
from typing import Callable, Type
|
||||
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
import streamlit as st
|
||||
from streamlit.delta_generator import DeltaGenerator
|
||||
|
||||
from rdagent.components.coder.factor_coder.evaluators import FactorSingleFeedback
|
||||
from rdagent.components.coder.factor_coder.factor import FactorFBWorkspace, FactorTask
|
||||
from rdagent.components.coder.model_coder.evaluators import ModelSingleFeedback
|
||||
from rdagent.components.coder.model_coder.model import ModelFBWorkspace, ModelTask
|
||||
from rdagent.core.proposal import Hypothesis, HypothesisFeedback, Trace
|
||||
from rdagent.log.base import Message, Storage, View
|
||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
||||
from rdagent.scenarios.qlib.experiment.model_experiment import (
|
||||
QlibModelExperiment,
|
||||
QlibModelScenario,
|
||||
)
|
||||
|
||||
st.set_page_config(layout="wide")
|
||||
|
||||
TIME_DELAY = 0.001
|
||||
|
||||
|
||||
class WebView(View):
|
||||
def __init__(self, ui: "StWindow"):
|
||||
self.ui = ui
|
||||
# Save logs to your desired data structure
|
||||
# ...
|
||||
|
||||
def display(self, s: Storage, watch: bool = False):
|
||||
for msg in s.iter_msg(): # iterate overtime
|
||||
# NOTE: iter_msg will correctly separate the information.
|
||||
# TODO: msg may support streaming mode.
|
||||
self.ui.consume_msg(msg)
|
||||
|
||||
|
||||
class StWindow:
|
||||
def __init__(self, container: "DeltaGenerator"):
|
||||
self.container = container
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
msg_str = f"{msg.timestamp.astimezone(timezone.utc).isoformat()} | {msg.level} | {msg.caller} - {msg.content}"
|
||||
self.container.code(msg_str, language="log")
|
||||
|
||||
|
||||
class LLMWindow(StWindow):
|
||||
def __init__(self, container: "DeltaGenerator", session_name: str = "common"):
|
||||
self.session_name = session_name
|
||||
self.container = container.expander(f"{self.session_name} message")
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
self.container.chat_message("user").markdown(f"{msg.content}")
|
||||
|
||||
|
||||
class ProgressTabsWindow(StWindow):
|
||||
"""
|
||||
For windows with stream messages, will refresh when a new tab is created.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
container: "DeltaGenerator",
|
||||
inner_class: Type[StWindow] = StWindow,
|
||||
mapper: Callable[[Message], str] = lambda x: x.pid_trace,
|
||||
):
|
||||
self.inner_class = inner_class
|
||||
self.mapper = mapper
|
||||
|
||||
self.container = container.empty()
|
||||
self.tab_windows: dict[str, StWindow] = defaultdict(None)
|
||||
self.tab_caches: dict[str, list[Message]] = defaultdict(list)
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
name = self.mapper(msg)
|
||||
|
||||
if name not in self.tab_windows:
|
||||
# new tab need to be created, current streamlit container need to be updated.
|
||||
names = list(self.tab_windows.keys()) + [name]
|
||||
|
||||
if len(names) == 1:
|
||||
tabs = [self.container.container()]
|
||||
else:
|
||||
tabs = self.container.tabs(names)
|
||||
|
||||
for id, name in enumerate(names):
|
||||
self.tab_windows[name] = self.inner_class(tabs[id])
|
||||
|
||||
# consume the cache
|
||||
for name in self.tab_caches:
|
||||
for msg in self.tab_caches[name]:
|
||||
self.tab_windows[name].consume_msg(msg)
|
||||
|
||||
self.tab_caches[name].append(msg)
|
||||
self.tab_windows[name].consume_msg(msg)
|
||||
|
||||
|
||||
class ObjectsTabsWindow(StWindow):
|
||||
def __init__(
|
||||
self,
|
||||
container: "DeltaGenerator",
|
||||
inner_class: Type[StWindow] = StWindow,
|
||||
mapper: Callable[[object], str] = lambda x: str(x),
|
||||
tab_names: list[str] | None = None,
|
||||
):
|
||||
self.inner_class = inner_class
|
||||
self.mapper = mapper
|
||||
self.container = container
|
||||
self.tab_names = tab_names
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
if isinstance(msg.content, list):
|
||||
if self.tab_names:
|
||||
assert len(self.tab_names) == len(
|
||||
msg.content
|
||||
), "List of objects should have the same length as provided tab names."
|
||||
objs_dict = {self.tab_names[id]: obj for id, obj in enumerate(msg.content)}
|
||||
else:
|
||||
objs_dict = {self.mapper(obj): obj for obj in msg.content}
|
||||
elif not isinstance(msg.content, dict):
|
||||
raise ValueError("Message content should be a list or a dict of objects.")
|
||||
|
||||
# two many tabs may cause display problem
|
||||
tab_names = list(objs_dict.keys())
|
||||
tabs = []
|
||||
for i in range(0, len(tab_names), 10):
|
||||
tabs.extend(self.container.tabs(tab_names[i : i + 10]))
|
||||
|
||||
for id, obj in enumerate(objs_dict.values()):
|
||||
splited_msg = Message(
|
||||
tag=msg.tag,
|
||||
level=msg.level,
|
||||
timestamp=msg.timestamp,
|
||||
caller=msg.caller,
|
||||
pid_trace=msg.pid_trace,
|
||||
content=obj,
|
||||
)
|
||||
self.inner_class(tabs[id]).consume_msg(splited_msg)
|
||||
|
||||
|
||||
class RoundTabsWindow(StWindow):
|
||||
def __init__(
|
||||
self,
|
||||
container: "DeltaGenerator",
|
||||
new_tab_func: Callable[[Message], bool],
|
||||
inner_class: Type[StWindow] = StWindow,
|
||||
title: str = "Round tabs",
|
||||
):
|
||||
container.markdown(f"### **{title}**")
|
||||
self.inner_class = inner_class
|
||||
self.new_tab_func = new_tab_func
|
||||
self.round = 0
|
||||
|
||||
self.current_win = StWindow(container)
|
||||
self.tabs_c = container.empty()
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
if self.new_tab_func(msg):
|
||||
self.round += 1
|
||||
self.current_win = self.inner_class(self.tabs_c.tabs([str(i) for i in range(1, self.round + 1)])[-1])
|
||||
|
||||
self.current_win.consume_msg(msg)
|
||||
|
||||
|
||||
class HypothesisWindow(StWindow):
|
||||
def consume_msg(self, msg: Message | Hypothesis):
|
||||
h: Hypothesis = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
self.container.markdown("#### **Hypothesis💡**")
|
||||
self.container.markdown(f"""
|
||||
- **Hypothesis**: {h.hypothesis}
|
||||
- **Reason**: {h.reason}""")
|
||||
|
||||
|
||||
class HypothesisFeedbackWindow(StWindow):
|
||||
def consume_msg(self, msg: Message | HypothesisFeedback):
|
||||
h: HypothesisFeedback = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
self.container.markdown("#### **Hypothesis Feedback🔍**")
|
||||
self.container.markdown(f"""
|
||||
- **Observations**: {h.observations}
|
||||
- **Hypothesis Evaluation**: {h.hypothesis_evaluation}
|
||||
- **New Hypothesis**: {h.new_hypothesis}
|
||||
- **Decision**: {h.decision}
|
||||
- **Reason**: {h.reason}""")
|
||||
|
||||
|
||||
class FactorTaskWindow(StWindow):
|
||||
def consume_msg(self, msg: Message | FactorTask):
|
||||
ft: FactorTask = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
self.container.markdown(f"**Factor Name**: {ft.factor_name}")
|
||||
self.container.markdown(f"**Description**: {ft.factor_description}")
|
||||
self.container.latex(f"Formulation: {ft.factor_formulation}")
|
||||
|
||||
variables_df = pd.DataFrame(ft.variables, index=["Description"]).T
|
||||
variables_df.index.name = "Variable"
|
||||
self.container.table(variables_df)
|
||||
self.container.text(f"Factor resources: {ft.factor_resources}")
|
||||
|
||||
|
||||
class ModelTaskWindow(StWindow):
|
||||
def consume_msg(self, msg: Message | ModelTask):
|
||||
mt: ModelTask = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
self.container.markdown(f"**Model Name**: {mt.name}")
|
||||
self.container.markdown(f"**Model Type**: {mt.model_type}")
|
||||
self.container.markdown(f"**Description**: {mt.description}")
|
||||
self.container.latex(f"Formulation: {mt.formulation}")
|
||||
|
||||
variables_df = pd.DataFrame(mt.variables, index=["Value"]).T
|
||||
variables_df.index.name = "Variable"
|
||||
self.container.table(variables_df)
|
||||
|
||||
|
||||
class FactorFeedbackWindow(StWindow):
|
||||
def consume_msg(self, msg: Message | FactorSingleFeedback):
|
||||
fb: FactorSingleFeedback = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
self.container.markdown(f"""### :blue[Factor Execution Feedback]
|
||||
{fb.execution_feedback}
|
||||
### :blue[Factor Code Feedback]
|
||||
{fb.code_feedback}
|
||||
### :blue[Factor Value Feedback]
|
||||
{fb.value_feedback}
|
||||
### :blue[Factor Final Feedback]
|
||||
{fb.final_feedback}
|
||||
### :blue[Factor Final Decision]
|
||||
This implementation is {'SUCCESS' if fb.final_decision else 'FAIL'}.
|
||||
""")
|
||||
|
||||
|
||||
class ModelFeedbackWindow(StWindow):
|
||||
def consume_msg(self, msg: Message | ModelSingleFeedback):
|
||||
mb: ModelSingleFeedback = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
self.container.markdown(f"""### :blue[Model Execution Feedback]
|
||||
{mb.execution_feedback}
|
||||
### :blue[Model Shape Feedback]
|
||||
{mb.shape_feedback}
|
||||
### :blue[Model Value Feedback]
|
||||
{mb.value_feedback}
|
||||
### :blue[Model Code Feedback]
|
||||
{mb.code_feedback}
|
||||
### :blue[Model Final Feedback]
|
||||
{mb.final_feedback}
|
||||
### :blue[Model Final Decision]
|
||||
This implementation is {'SUCCESS' if mb.final_decision else 'FAIL'}.
|
||||
""")
|
||||
|
||||
|
||||
class WorkspaceWindow(StWindow):
|
||||
def __init__(self, container: "DeltaGenerator", show_task_info: bool = False):
|
||||
self.container = container
|
||||
self.show_task_info = show_task_info
|
||||
|
||||
def consume_msg(self, msg: Message | FactorFBWorkspace | ModelFBWorkspace):
|
||||
ws: FactorFBWorkspace | ModelFBWorkspace = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
# no workspace
|
||||
if ws is None:
|
||||
return
|
||||
|
||||
# task info
|
||||
if self.show_task_info:
|
||||
task_msg = deepcopy(msg)
|
||||
task_msg.content = ws.target_task
|
||||
if isinstance(ws, FactorFBWorkspace):
|
||||
self.container.subheader("Factor Info")
|
||||
FactorTaskWindow(self.container.container()).consume_msg(task_msg)
|
||||
else:
|
||||
self.container.subheader("Model Info")
|
||||
ModelTaskWindow(self.container.container()).consume_msg(task_msg)
|
||||
|
||||
# task codes
|
||||
for k, v in ws.file_dict.items():
|
||||
self.container.markdown(f"`{k}`")
|
||||
self.container.code(v, language="python")
|
||||
|
||||
|
||||
class QlibFactorExpWindow(StWindow):
|
||||
def __init__(self, container: DeltaGenerator, show_task_info: bool = False):
|
||||
self.container = container
|
||||
self.show_task_info = show_task_info
|
||||
|
||||
def consume_msg(self, msg: Message | QlibFactorExperiment):
|
||||
exp: QlibFactorExperiment = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
# factor tasks
|
||||
if self.show_task_info:
|
||||
ftm_msg = deepcopy(msg)
|
||||
ftm_msg.content = [ws for ws in exp.sub_workspace_list if ws]
|
||||
self.container.markdown("**Factor Tasks**")
|
||||
ObjectsTabsWindow(
|
||||
self.container.container(),
|
||||
inner_class=WorkspaceWindow,
|
||||
mapper=lambda x: x.target_task.factor_name,
|
||||
).consume_msg(ftm_msg)
|
||||
|
||||
# result
|
||||
self.container.markdown("**Results**")
|
||||
results = pd.DataFrame({f"base_exp_{id}": e.result for id, e in enumerate(exp.based_experiments)})
|
||||
results["now"] = exp.result
|
||||
|
||||
self.container.expander("results table").table(results)
|
||||
|
||||
try:
|
||||
bar_chart = px.bar(results, orientation="h", barmode="group")
|
||||
self.container.expander("results chart").plotly_chart(bar_chart)
|
||||
except:
|
||||
self.container.text("Results are incomplete.")
|
||||
|
||||
|
||||
class QlibModelExpWindow(StWindow):
|
||||
def __init__(self, container: DeltaGenerator, show_task_info: bool = False):
|
||||
self.container = container
|
||||
self.show_task_info = show_task_info
|
||||
|
||||
def consume_msg(self, msg: Message | QlibModelExperiment):
|
||||
exp: QlibModelExperiment = msg.content if isinstance(msg, Message) else msg
|
||||
|
||||
# model tasks
|
||||
if self.show_task_info:
|
||||
_msg = deepcopy(msg)
|
||||
_msg.content = [ws for ws in exp.sub_workspace_list if ws]
|
||||
self.container.markdown("**Model Tasks**")
|
||||
ObjectsTabsWindow(
|
||||
self.container.container(),
|
||||
inner_class=WorkspaceWindow,
|
||||
mapper=lambda x: x.target_task.name,
|
||||
).consume_msg(_msg)
|
||||
|
||||
# result
|
||||
self.container.subheader("Results", divider=True)
|
||||
results = pd.DataFrame({f"base_exp_{id}": e.result for id, e in enumerate(exp.based_experiments)})
|
||||
results["now"] = exp.result
|
||||
|
||||
self.container.expander("results table").table(results)
|
||||
|
||||
|
||||
class SimpleTraceWindow(StWindow):
|
||||
def __init__(
|
||||
self, container: "DeltaGenerator" = st.container(), show_llm: bool = False, show_common_logs: bool = False
|
||||
):
|
||||
super().__init__(container)
|
||||
self.show_llm = show_llm
|
||||
self.show_common_logs = show_common_logs
|
||||
self.pid_trace = ""
|
||||
self.current_tag = ""
|
||||
|
||||
self.current_win = StWindow(self.container)
|
||||
self.evolving_tasks: list[str] = []
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
# divide tag levels
|
||||
if len(msg.tag) > len(self.current_tag):
|
||||
# write a header about current task, if it is llm message, not write.
|
||||
if not msg.tag.endswith("llm_messages"):
|
||||
self.container.header(msg.tag.replace(".", " ➡ "), divider=True)
|
||||
|
||||
self.current_tag = msg.tag
|
||||
|
||||
# set log writer (window) according to msg
|
||||
if msg.tag.endswith("llm_messages"):
|
||||
# llm messages logs
|
||||
if not self.show_llm:
|
||||
return
|
||||
if not isinstance(self.current_win, LLMWindow):
|
||||
self.current_win = LLMWindow(self.container)
|
||||
elif isinstance(msg.content, Hypothesis):
|
||||
# hypothesis
|
||||
self.current_win = HypothesisWindow(self.container)
|
||||
elif isinstance(msg.content, HypothesisFeedback):
|
||||
# hypothesis feedback
|
||||
self.current_win = HypothesisFeedbackWindow(self.container)
|
||||
elif isinstance(msg.content, QlibFactorExperiment):
|
||||
self.current_win = QlibFactorExpWindow(self.container)
|
||||
elif isinstance(msg.content, QlibModelExperiment):
|
||||
self.current_win = QlibModelExpWindow(self.container)
|
||||
elif isinstance(msg.content, list):
|
||||
msg.content = [m for m in msg.content if m]
|
||||
if len(msg.content) == 0:
|
||||
return
|
||||
if isinstance(msg.content[0], FactorTask):
|
||||
self.current_win = ObjectsTabsWindow(
|
||||
self.container.expander("Factor Tasks"), FactorTaskWindow, lambda x: x.factor_name
|
||||
)
|
||||
elif isinstance(msg.content[0], ModelTask):
|
||||
self.current_win = ObjectsTabsWindow(
|
||||
self.container.expander("Model Tasks"), ModelTaskWindow, lambda x: x.name
|
||||
)
|
||||
|
||||
elif isinstance(msg.content[0], FactorFBWorkspace):
|
||||
self.current_win = ObjectsTabsWindow(
|
||||
self.container.expander("Factor Workspaces"),
|
||||
inner_class=WorkspaceWindow,
|
||||
mapper=lambda x: x.target_task.factor_name,
|
||||
)
|
||||
self.evolving_tasks = [m.target_task.factor_name for m in msg.content]
|
||||
elif isinstance(msg.content[0], ModelFBWorkspace):
|
||||
self.current_win = ObjectsTabsWindow(
|
||||
self.container.expander("Model Workspaces"),
|
||||
inner_class=WorkspaceWindow,
|
||||
mapper=lambda x: x.target_task.name,
|
||||
)
|
||||
self.evolving_tasks = [m.target_task.name for m in msg.content]
|
||||
|
||||
elif isinstance(msg.content[0], FactorSingleFeedback):
|
||||
self.current_win = ObjectsTabsWindow(
|
||||
self.container.expander("Factor Feedbacks"),
|
||||
inner_class=FactorFeedbackWindow,
|
||||
tab_names=self.evolving_tasks,
|
||||
)
|
||||
elif isinstance(msg.content[0], ModelSingleFeedback):
|
||||
self.current_win = ObjectsTabsWindow(
|
||||
self.container.expander("Model Feedbacks"),
|
||||
inner_class=ModelFeedbackWindow,
|
||||
tab_names=self.evolving_tasks,
|
||||
)
|
||||
else:
|
||||
# common logs
|
||||
if not self.show_common_logs:
|
||||
return
|
||||
self.current_win = StWindow(self.container)
|
||||
|
||||
self.current_win.consume_msg(msg)
|
||||
|
||||
|
||||
def mock_msg(obj) -> Message:
|
||||
return Message(tag="mock", level="INFO", timestamp=datetime.now(), pid_trace="000", caller="mock", content=obj)
|
||||
|
||||
|
||||
class TraceObjWindow(StWindow):
|
||||
def __init__(self, container: "DeltaGenerator" = st.container()):
|
||||
self.container = container
|
||||
|
||||
def consume_msg(self, msg: Message | Trace):
|
||||
if isinstance(msg, Message):
|
||||
trace: Trace = msg.content
|
||||
else:
|
||||
trace = msg
|
||||
|
||||
for id, (h, e, hf) in enumerate(trace.hist):
|
||||
self.container.header(f"Trace History {id}", divider=True)
|
||||
HypothesisWindow(self.container).consume_msg(mock_msg(h))
|
||||
if isinstance(e, QlibFactorExperiment):
|
||||
QlibFactorExpWindow(self.container).consume_msg(mock_msg(e))
|
||||
else:
|
||||
QlibModelExpWindow(self.container).consume_msg(mock_msg(e))
|
||||
HypothesisFeedbackWindow(self.container).consume_msg(mock_msg(hf))
|
||||
|
||||
|
||||
class ResearchWindow(StWindow):
|
||||
def consume_msg(self, msg: Message):
|
||||
if msg.tag.endswith("hypothesis generation"):
|
||||
HypothesisWindow(self.container.container()).consume_msg(msg)
|
||||
elif msg.tag.endswith("experiment generation"):
|
||||
if isinstance(msg.content, list):
|
||||
if isinstance(msg.content[0], FactorTask):
|
||||
self.container.markdown("**Factor Tasks**")
|
||||
ObjectsTabsWindow(
|
||||
self.container.container(), FactorTaskWindow, lambda x: x.factor_name
|
||||
).consume_msg(msg)
|
||||
elif isinstance(msg.content[0], ModelTask):
|
||||
self.container.markdown("**Model Tasks**")
|
||||
ObjectsTabsWindow(self.container.container(), ModelTaskWindow, lambda x: x.name).consume_msg(msg)
|
||||
elif msg.tag.endswith("load_pdf_screenshot"):
|
||||
self.container.image(msg.content)
|
||||
elif msg.tag.endswith("load_factor_tasks"):
|
||||
self.container.json(msg.content)
|
||||
|
||||
|
||||
class EvolvingWindow(StWindow):
|
||||
def __init__(self, container: "DeltaGenerator"):
|
||||
self.container = container
|
||||
self.evolving_tasks: list[str] = []
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
if msg.tag.endswith("evolving code"):
|
||||
if isinstance(msg.content, list):
|
||||
msg.content = [m for m in msg.content if m]
|
||||
if len(msg.content) == 0:
|
||||
return
|
||||
if isinstance(msg.content[0], FactorFBWorkspace):
|
||||
self.container.markdown("**Factor Codes**")
|
||||
ObjectsTabsWindow(
|
||||
self.container.container(),
|
||||
inner_class=WorkspaceWindow,
|
||||
mapper=lambda x: x.target_task.factor_name,
|
||||
).consume_msg(msg)
|
||||
self.evolving_tasks = [m.target_task.factor_name for m in msg.content]
|
||||
elif isinstance(msg.content[0], ModelFBWorkspace):
|
||||
self.container.markdown("**Model Codes**")
|
||||
ObjectsTabsWindow(
|
||||
self.container.container(), inner_class=WorkspaceWindow, mapper=lambda x: x.target_task.name
|
||||
).consume_msg(msg)
|
||||
self.evolving_tasks = [m.target_task.name for m in msg.content]
|
||||
elif msg.tag.endswith("evolving feedback"):
|
||||
if isinstance(msg.content, list):
|
||||
msg.content = [m for m in msg.content if m]
|
||||
if len(msg.content) == 0:
|
||||
return
|
||||
if isinstance(msg.content[0], FactorSingleFeedback):
|
||||
self.container.markdown("**Factor Feedbacks🔍**")
|
||||
ObjectsTabsWindow(
|
||||
self.container.container(), inner_class=FactorFeedbackWindow, tab_names=self.evolving_tasks
|
||||
).consume_msg(msg)
|
||||
elif isinstance(msg.content[0], ModelSingleFeedback):
|
||||
self.container.markdown("**Model Feedbacks🔍**")
|
||||
ObjectsTabsWindow(
|
||||
self.container.container(), inner_class=ModelFeedbackWindow, tab_names=self.evolving_tasks
|
||||
).consume_msg(msg)
|
||||
|
||||
|
||||
class DevelopmentWindow(StWindow):
|
||||
def __init__(self, container: "DeltaGenerator"):
|
||||
self.E_win = RoundTabsWindow(
|
||||
container.container(),
|
||||
new_tab_func=lambda x: x.tag.endswith("evolving code"),
|
||||
inner_class=EvolvingWindow,
|
||||
title="Evolving Loops🔧",
|
||||
)
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
if "evolving" in msg.tag:
|
||||
self.E_win.consume_msg(msg)
|
||||
|
||||
|
||||
class FeedbackWindow(StWindow):
|
||||
def __init__(self, container: "DeltaGenerator"):
|
||||
self.container = container
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
if msg.tag.endswith("returns"):
|
||||
fig = px.line(msg.content)
|
||||
self.container.markdown("**Returns📈**")
|
||||
self.container.plotly_chart(fig)
|
||||
elif isinstance(msg.content, HypothesisFeedback):
|
||||
HypothesisFeedbackWindow(self.container.container(border=True)).consume_msg(msg)
|
||||
elif isinstance(msg.content, QlibModelExperiment):
|
||||
QlibModelExpWindow(self.container.container(border=True)).consume_msg(msg)
|
||||
elif isinstance(msg.content, QlibFactorExperiment):
|
||||
QlibFactorExpWindow(self.container.container(border=True)).consume_msg(msg)
|
||||
|
||||
|
||||
class SingleRDLoopWindow(StWindow):
|
||||
def __init__(self, container: "DeltaGenerator"):
|
||||
self.container = container
|
||||
col1, col2 = self.container.columns([2, 3])
|
||||
self.R_win = ResearchWindow(col1.container(border=True))
|
||||
self.F_win = FeedbackWindow(col1.container(border=True))
|
||||
self.D_win = DevelopmentWindow(col2.container(border=True))
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
tags = msg.tag.split(".")
|
||||
if "r" in tags:
|
||||
self.R_win.consume_msg(msg)
|
||||
elif "d" in tags:
|
||||
self.D_win.consume_msg(msg)
|
||||
elif "ef" in tags:
|
||||
self.F_win.consume_msg(msg)
|
||||
|
||||
|
||||
class TraceWindow(StWindow):
|
||||
def __init__(
|
||||
self, container: "DeltaGenerator" = st.container(), show_llm: bool = False, show_common_logs: bool = False
|
||||
):
|
||||
self.show_llm = show_llm
|
||||
self.show_common_logs = show_common_logs
|
||||
image_c, scen_c = container.columns([2, 3], vertical_alignment="center")
|
||||
image_c.image("scen.png")
|
||||
scen_c.container(border=True).markdown(QlibModelScenario().rich_style_description)
|
||||
top_container = container.container()
|
||||
col1, col2 = top_container.columns([2, 3])
|
||||
chart_c = col2.container(border=True, height=500)
|
||||
chart_c.markdown("**Metrics📈**")
|
||||
self.chart_c = chart_c.empty()
|
||||
hypothesis_status_c = col1.container(border=True, height=500)
|
||||
hypothesis_status_c.markdown("**Hypotheses🏅**")
|
||||
self.summary_c = hypothesis_status_c.empty()
|
||||
|
||||
self.RDL_win = RoundTabsWindow(
|
||||
container.container(),
|
||||
new_tab_func=lambda x: x.tag.endswith("hypothesis generation"),
|
||||
inner_class=SingleRDLoopWindow,
|
||||
title="R&D Loops♾️",
|
||||
)
|
||||
|
||||
self.hypothesis_decisions = defaultdict(bool)
|
||||
self.hypotheses: list[Hypothesis] = []
|
||||
|
||||
self.results = []
|
||||
|
||||
def consume_msg(self, msg: Message):
|
||||
if not self.show_llm and "llm_messages" in msg.tag:
|
||||
return
|
||||
if not self.show_common_logs and isinstance(msg.content, str):
|
||||
return
|
||||
if isinstance(msg.content, dict):
|
||||
return
|
||||
if msg.tag.endswith("hypothesis generation"):
|
||||
self.hypotheses.append(msg.content)
|
||||
elif msg.tag.endswith("ef.feedback"):
|
||||
self.hypothesis_decisions[self.hypotheses[-1]] = msg.content.decision
|
||||
self.summary_c.markdown(
|
||||
"\n".join(
|
||||
(
|
||||
f"{id+1}. :green[{self.hypotheses[id].hypothesis}]\n\t>*{self.hypotheses[id].concise_reason}*"
|
||||
if d
|
||||
else f"{id+1}. {self.hypotheses[id].hypothesis}\n\t>*{self.hypotheses[id].concise_reason}*"
|
||||
)
|
||||
for id, (h, d) in enumerate(self.hypothesis_decisions.items())
|
||||
)
|
||||
)
|
||||
elif msg.tag.endswith("ef.model runner result") or msg.tag.endswith("ef.factor runner result"):
|
||||
self.results.append(msg.content.result)
|
||||
if len(self.results) == 1:
|
||||
self.chart_c.table(self.results[0])
|
||||
else:
|
||||
df = pd.DataFrame(self.results, index=range(1, len(self.results) + 1))
|
||||
fig = px.line(df, x=df.index, y=df.columns, markers=True)
|
||||
self.chart_c.plotly_chart(fig)
|
||||
|
||||
self.RDL_win.consume_msg(msg)
|
||||
# time.sleep(TIME_DELAY)
|
||||
@@ -1,129 +0,0 @@
|
||||
import inspect
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, TypedDict, cast
|
||||
|
||||
|
||||
class LogColors:
|
||||
"""
|
||||
ANSI color codes for use in console output.
|
||||
"""
|
||||
|
||||
RED = "\033[91m"
|
||||
GREEN = "\033[92m"
|
||||
YELLOW = "\033[93m"
|
||||
BLUE = "\033[94m"
|
||||
MAGENTA = "\033[95m"
|
||||
CYAN = "\033[96m"
|
||||
WHITE = "\033[97m"
|
||||
GRAY = "\033[90m"
|
||||
BLACK = "\033[30m"
|
||||
|
||||
BOLD = "\033[1m"
|
||||
ITALIC = "\033[3m"
|
||||
|
||||
END = "\033[0m"
|
||||
|
||||
@classmethod
|
||||
def get_all_colors(cls: type["LogColors"]) -> list:
|
||||
names = dir(cls)
|
||||
names = [name for name in names if not name.startswith("__") and not callable(getattr(cls, name))]
|
||||
return [getattr(cls, name) for name in names]
|
||||
|
||||
def render(self, text: str, color: str = "", style: str = "") -> str:
|
||||
"""
|
||||
render text by input color and style.
|
||||
It's not recommend that input text is already rendered.
|
||||
"""
|
||||
# This method is called too frequently, which is not good.
|
||||
colors = self.get_all_colors()
|
||||
# Perhaps color and font should be distinguished here.
|
||||
if color and color in colors:
|
||||
error_message = f"color should be in: {colors} but now is: {color}"
|
||||
raise ValueError(error_message)
|
||||
if style and style in colors:
|
||||
error_message = f"style should be in: {colors} but now is: {style}"
|
||||
raise ValueError(error_message)
|
||||
|
||||
text = f"{color}{text}{self.END}"
|
||||
|
||||
return f"{style}{text}{self.END}"
|
||||
|
||||
@staticmethod
|
||||
def remove_ansi_codes(s: str) -> str:
|
||||
"""
|
||||
It is for removing ansi ctrl characters in the string(e.g. colored text)
|
||||
"""
|
||||
ansi_escape = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")
|
||||
return ansi_escape.sub("", s)
|
||||
|
||||
|
||||
class CallerInfo(TypedDict):
|
||||
function: str
|
||||
line: int
|
||||
name: Optional[str]
|
||||
|
||||
|
||||
def get_caller_info(level: int = 2) -> CallerInfo:
|
||||
# Get the current stack information
|
||||
stack = inspect.stack()
|
||||
# The second element is usually the caller's information
|
||||
caller_info = stack[level]
|
||||
frame = caller_info[0]
|
||||
info: CallerInfo = {
|
||||
"line": caller_info.lineno,
|
||||
"name": frame.f_globals["__name__"], # Get the module name from the frame's globals
|
||||
"function": frame.f_code.co_name, # Get the caller's function name
|
||||
}
|
||||
return info
|
||||
|
||||
|
||||
def is_valid_session(log_path: Path) -> bool:
|
||||
return log_path.is_dir() and log_path.joinpath("__session__").exists()
|
||||
|
||||
|
||||
def extract_loopid_func_name(tag: str) -> tuple[str, str] | tuple[None, None]:
|
||||
"""extract loop id and function name from the tag in Message"""
|
||||
match = re.search(r"Loop_(\d+)\.([^.]+)", tag)
|
||||
return cast(tuple[str, str], match.groups()) if match else (None, None)
|
||||
|
||||
|
||||
def extract_evoid(tag: str) -> str | None:
|
||||
"""extract evo id from the tag in Message"""
|
||||
match = re.search(r"evo_loop_(\d+)\.", tag)
|
||||
return cast(str, match.group(1)) if match else None
|
||||
|
||||
|
||||
def extract_json(log_content: str) -> dict | None:
|
||||
match = re.search(r"\{.*\}", log_content, re.DOTALL)
|
||||
if match:
|
||||
return cast(dict, json.loads(match.group(0)))
|
||||
return None
|
||||
|
||||
|
||||
def gen_datetime(dt: datetime | None = None) -> datetime:
|
||||
"""
|
||||
Generate a datetime object in UTC timezone.
|
||||
- If `dt` is None, it will return the current time in UTC.
|
||||
- If `dt` is provided, it will convert it to UTC timezone.
|
||||
"""
|
||||
if dt is None:
|
||||
return datetime.now(timezone.utc)
|
||||
return dt.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def dict_get_with_warning(d: dict, key: str, default: Any = None) -> Any:
|
||||
"""
|
||||
Motivation:
|
||||
- When handling the repsonse from the LLM, we may use dict get to get the value.
|
||||
- the function prevent falling into default value **silently**.
|
||||
- Instead, it will log a warning message.
|
||||
"""
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
|
||||
if key not in d:
|
||||
logger.warning(f"Key {key} not found in {d}")
|
||||
return default
|
||||
return d[key]
|
||||
@@ -1,77 +0,0 @@
|
||||
"""
|
||||
This module provides some useful functions for working with logger folders.
|
||||
"""
|
||||
|
||||
import pickle
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from rdagent.utils.workflow import LoopBase
|
||||
|
||||
|
||||
def get_first_session_file_after_duration(log_folder: str | Path, duration: str | pd.Timedelta) -> Path:
|
||||
log_folder = Path(log_folder)
|
||||
duration_dt = pd.Timedelta(duration)
|
||||
# iterate the dump steps in increasing order
|
||||
files = sorted(
|
||||
(log_folder / "__session__").glob("*/*_*"), key=lambda f: (int(f.parent.name), int(f.name.split("_")[0]))
|
||||
)
|
||||
fp = None
|
||||
for fp in files:
|
||||
with fp.open("rb") as f:
|
||||
session_obj: LoopBase = pickle.load(f)
|
||||
timer = session_obj.timer
|
||||
all_duration = timer.all_duration
|
||||
remain_time_duration = timer.remain_time()
|
||||
if all_duration is None or remain_time_duration is None:
|
||||
msg = "Timer is not configured"
|
||||
raise ValueError(msg)
|
||||
time_spent = all_duration - remain_time_duration
|
||||
if time_spent >= duration_dt:
|
||||
break
|
||||
if fp is None:
|
||||
msg = f"No session file found after duration {duration}"
|
||||
raise ValueError(msg)
|
||||
return fp
|
||||
|
||||
|
||||
def first_li_si_after_one_time(log_path: Path, hours: int = 12) -> tuple[int, int, str]:
|
||||
"""
|
||||
Based on the hours, find the stop loop id and step id (the first step after <hours> hours).
|
||||
Args:
|
||||
log_path (Path): The path to the log folder (contains many log traces).
|
||||
hours (int): The number of hours to stat.
|
||||
Returns:
|
||||
tuple[int, int, str]: The loop id, step id and function name.
|
||||
"""
|
||||
session_path = log_path / "__session__"
|
||||
max_li = max(int(p.name) for p in session_path.iterdir() if p.is_dir() and p.name.isdigit())
|
||||
max_step = max(int(p.name.split("_")[0]) for p in (session_path / str(max_li)).iterdir() if p.is_file())
|
||||
rdloop_obj_p = next((session_path / str(max_li)).glob(f"{max_step}_*"))
|
||||
|
||||
rdloop_obj = DataScienceRDLoop.load(rdloop_obj_p)
|
||||
loop_trace = rdloop_obj.loop_trace
|
||||
si2fn = rdloop_obj.steps
|
||||
|
||||
duration = timedelta(seconds=0)
|
||||
for li, lts in loop_trace.items():
|
||||
for lt in lts:
|
||||
si = lt.step_idx
|
||||
duration += lt.end - lt.start
|
||||
if duration > timedelta(hours=hours):
|
||||
return li, si, si2fn[si]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from rdagent.app.data_science.loop import DataScienceRDLoop
|
||||
|
||||
f = get_first_session_file_after_duration("<path to log aptos2019-blindness-detection>", pd.Timedelta("12h"))
|
||||
|
||||
with f.open("rb") as f:
|
||||
session_obj: LoopBase = pickle.load(f)
|
||||
loop_trace = session_obj.loop_trace
|
||||
last_loop = loop_trace[max(loop_trace.keys())]
|
||||
last_step = last_loop[-1]
|
||||
session_obj.steps[last_step.step_idx]
|
||||
Executable
+301
@@ -0,0 +1,301 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# setup_predix_eurusd.sh
|
||||
# Richtet Predix für EURUSD 15min Trading ein
|
||||
# Ausführen: bash setup_predix_eurusd.sh
|
||||
# =============================================================================
|
||||
|
||||
set -e # Abbruch bei Fehler
|
||||
|
||||
PREDIX_DIR="$HOME/Predix"
|
||||
CSV_SOURCE="$HOME/Downloads/eurusd_data.csv"
|
||||
DATA_DIR="$PREDIX_DIR/git_ignore_folder/eurusd_data"
|
||||
QLIB_DIR="$HOME/.qlib/qlib_data/eurusd_data"
|
||||
|
||||
echo "========================================"
|
||||
echo " Predix EURUSD Setup"
|
||||
echo "========================================"
|
||||
|
||||
# ─── 1. Prüfen ob alles da ist ───────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "[1/7] Prüfe Voraussetzungen..."
|
||||
|
||||
if [ ! -d "$PREDIX_DIR" ]; then
|
||||
echo "FEHLER: $PREDIX_DIR nicht gefunden!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$CSV_SOURCE" ]; then
|
||||
echo "FEHLER: $CSV_SOURCE nicht gefunden!"
|
||||
echo "Bitte eurusd_data.csv in ~/Downloads/ legen"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Predix gefunden: $PREDIX_DIR"
|
||||
echo "✓ CSV gefunden: $CSV_SOURCE"
|
||||
|
||||
# ─── 2. Ordner anlegen ───────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "[2/7] Erstelle Ordnerstruktur..."
|
||||
|
||||
mkdir -p "$DATA_DIR"
|
||||
mkdir -p "$QLIB_DIR/calendars"
|
||||
mkdir -p "$QLIB_DIR/instruments"
|
||||
mkdir -p "$QLIB_DIR/features/eurusd"
|
||||
mkdir -p "$PREDIX_DIR/git_ignore_folder/log"
|
||||
|
||||
cp "$CSV_SOURCE" "$DATA_DIR/eurusd_data.csv"
|
||||
echo "✓ CSV kopiert nach $DATA_DIR"
|
||||
|
||||
# ─── 3. CSV → Qlib Format konvertieren ───────────────────────────────────────
|
||||
echo ""
|
||||
echo "[3/7] Konvertiere CSV zu Qlib-Format..."
|
||||
|
||||
python3 << 'PYEOF'
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
QLIB_DIR = Path(os.path.expanduser("~/.qlib/qlib_data/eurusd_data"))
|
||||
CSV_PATH = Path(os.path.expanduser("~/Downloads/eurusd_data.csv"))
|
||||
|
||||
# Laden + sortieren
|
||||
df = pd.read_csv(CSV_PATH, parse_dates=["datetime"])
|
||||
df = df.sort_values("datetime").reset_index(drop=True)
|
||||
df.columns = [c.lower() for c in df.columns]
|
||||
|
||||
print(f" Rows: {len(df):,} | Range: {df['datetime'].min().date()} -> {df['datetime'].max().date()}")
|
||||
|
||||
# ── Kalender (alle 15min Timestamps) ────────────────────────────────────────
|
||||
cal = df["datetime"].dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
cal_path = QLIB_DIR / "calendars" / "15min.txt"
|
||||
cal.to_csv(cal_path, index=False, header=False)
|
||||
print(f" ✓ Kalender: {len(cal)} Einträge -> {cal_path}")
|
||||
|
||||
# ── Instruments (nur EURUSD) ─────────────────────────────────────────────────
|
||||
inst_path = QLIB_DIR / "instruments" / "all.txt"
|
||||
start = df["datetime"].min().strftime("%Y-%m-%d")
|
||||
end = df["datetime"].max().strftime("%Y-%m-%d")
|
||||
with open(inst_path, "w") as f:
|
||||
f.write(f"EURUSD\t{start}\t{end}\n")
|
||||
print(f" ✓ Instruments -> {inst_path}")
|
||||
|
||||
# ── Features (Qlib binary format via CSV) ───────────────────────────────────
|
||||
feat_dir = QLIB_DIR / "features" / "eurusd"
|
||||
feat_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Qlib erwartet: $open, $high, $low, $close, $volume
|
||||
for col in ["open", "high", "low", "close", "volume"]:
|
||||
out = feat_dir / f"{col}.day.bin"
|
||||
# Qlib binary: float32 array
|
||||
arr = df[col].astype("float32").values
|
||||
arr.tofile(str(out).replace(".day.bin", f"_15min.bin"))
|
||||
|
||||
# Auch als einfache CSV für direkten Zugriff
|
||||
df.to_csv(QLIB_DIR / "eurusd_15min.csv", index=False)
|
||||
print(f" ✓ Features + CSV -> {feat_dir}")
|
||||
|
||||
# ── Returns + technische Features vorberechnen ───────────────────────────────
|
||||
def ema(s, p): return s.ewm(span=p, adjust=False).mean()
|
||||
def rsi(c, p=14):
|
||||
d = c.diff()
|
||||
g = d.clip(lower=0).ewm(span=p, adjust=False).mean()
|
||||
l = (-d.clip(upper=0)).ewm(span=p, adjust=False).mean()
|
||||
return 100 - 100/(1 + g/(l+1e-9))
|
||||
|
||||
feat = pd.DataFrame()
|
||||
feat["datetime"] = df["datetime"]
|
||||
feat["close"] = df["close"]
|
||||
for n in [1,4,8,16,96]:
|
||||
feat[f"ret_{n}"] = df["close"].pct_change(n)
|
||||
feat["rsi_14"] = rsi(df["close"], 14)
|
||||
feat["macd_hist"] = ema(df["close"],12) - ema(df["close"],26)
|
||||
feat["hour"] = df["datetime"].dt.hour
|
||||
feat["is_london"] = feat["hour"].isin([8,9,10,11]).astype(int)
|
||||
feat["is_ny"] = feat["hour"].isin([13,14,15,16]).astype(int)
|
||||
feat["adx_proxy"] = df["close"].rolling(14).std() / df["close"].rolling(96).std()
|
||||
|
||||
feat.dropna(inplace=True)
|
||||
feat.to_csv(QLIB_DIR / "eurusd_features.csv", index=False)
|
||||
print(f" ✓ Features CSV: {len(feat):,} Zeilen, {len(feat.columns)} Spalten")
|
||||
print(" Fertig!")
|
||||
PYEOF
|
||||
|
||||
echo "✓ Qlib-Daten konvertiert"
|
||||
|
||||
# ─── 4. .env updaten ─────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "[4/7] Update .env..."
|
||||
|
||||
ENV_FILE="$PREDIX_DIR/.env"
|
||||
|
||||
# Backup
|
||||
cp "$ENV_FILE" "$ENV_FILE.backup_$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
# QLIB_DATA_DIR auf EURUSD umbiegen
|
||||
sed -i "s|QLIB_DATA_DIR=.*|QLIB_DATA_DIR=$QLIB_DIR|" "$ENV_FILE"
|
||||
|
||||
# LOG_PATH korrigieren (war /home/nico/RD-Agent-Local/log)
|
||||
sed -i "s|LOG_PATH=.*|LOG_PATH=$PREDIX_DIR/git_ignore_folder/log|" "$ENV_FILE"
|
||||
|
||||
# EURUSD-spezifische Vars hinzufügen (falls noch nicht da)
|
||||
grep -q "EURUSD_DATA_PATH" "$ENV_FILE" || cat >> "$ENV_FILE" << 'ENVEOF'
|
||||
|
||||
# ---------- EURUSD ----------
|
||||
EURUSD_DATA_PATH=/home/nico/.qlib/qlib_data/eurusd_data/eurusd_15min.csv
|
||||
QLIB_FREQ=15min
|
||||
QLIB_MARKET=eurusd
|
||||
BACKTEST_START_TIME=2024-08-09
|
||||
BACKTEST_END_TIME=2026-03-20
|
||||
COST_RATE=0.00015
|
||||
ENVEOF
|
||||
|
||||
echo "✓ .env aktualisiert (Backup erstellt)"
|
||||
|
||||
# ─── 5. Prompts für EURUSD anpassen ──────────────────────────────────────────
|
||||
echo ""
|
||||
echo "[5/7] Passe Qlib-Prompts für EURUSD an..."
|
||||
|
||||
PROMPT_FILE="$PREDIX_DIR/rdagent/app/qlib_rd_loop/prompts.yaml"
|
||||
cp "$PROMPT_FILE" "${PROMPT_FILE}.backup_$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
cat > "$PROMPT_FILE" << 'YAMLEOF'
|
||||
hypothesis_generation:
|
||||
system: |-
|
||||
You are an expert quantitative researcher specialized in FX (foreign exchange) trading,
|
||||
specifically EURUSD intraday strategies on 15-minute bars.
|
||||
|
||||
EURUSD domain knowledge you must apply:
|
||||
- London session (08:00-12:00 UTC): highest volatility, trending behavior — favor momentum strategies
|
||||
- NY session (13:00-17:00 UTC): second volatility peak, also trending
|
||||
- Asian session (00:00-07:00 UTC): low volatility, mean-reverting behavior
|
||||
- London/NY overlap (13:00-17:00 UTC): strongest directional moves of the day
|
||||
- Weekend gap risk: avoid holding positions after Friday 20:00 UTC
|
||||
- Spread cost: ~1.5 bps per trade — strategies must minimize unnecessary entries
|
||||
- EURUSD is mean-reverting on short windows (<1h), trending on longer (>4h)
|
||||
- Key macro drivers: ECB/Fed rate decisions, NFP (first Friday of month), CPI releases
|
||||
|
||||
Available model types you can propose:
|
||||
- TimeSeries: LSTM, GRU, TCN (Temporal Convolutional Network), Transformer, PatchTST
|
||||
- Tabular: XGBoost, LightGBM, RandomForest (on engineered features)
|
||||
- Hybrid: CNN+LSTM, XGBoost+LSTM ensemble
|
||||
- Statistical: Regime-switching (HMM), Kalman filter
|
||||
|
||||
Available features in the dataset:
|
||||
- OHLCV: open, high, low, close, volume (15min bars)
|
||||
- Returns: ret_1, ret_4, ret_8, ret_16, ret_96
|
||||
- Technical: rsi_14, macd_hist, adx_14, atr_14, bb_pct, stoch_k, cci_14
|
||||
- Volatility: vol_real_4, vol_real_16, vol_ratio, zscore_ret_96
|
||||
- Time/Session: hour, is_london, is_ny, is_overlap, hour_sin, hour_cos
|
||||
- Lags: rsi_14_lag1-8, macd_hist_lag1-8, bb_pct_lag1-8
|
||||
|
||||
Your hypothesis must:
|
||||
1. Specify which session(s) the strategy targets
|
||||
2. Name which model type to use and why it fits EURUSD
|
||||
3. Include a session filter (is_london / is_ny)
|
||||
4. Include a spread filter (only trade when expected |return| > 0.0003)
|
||||
5. Specify target: classification (fwd_sign_4) or regression (fwd_ret_4)
|
||||
|
||||
Please ensure your response is in JSON format:
|
||||
{
|
||||
"hypothesis": "A clear and concise trading hypothesis for EURUSD 15min.",
|
||||
"reason": "Detailed explanation including session, model choice, and expected edge.",
|
||||
"model_type": "One of: TimeSeries / Tabular / XGBoost",
|
||||
"target_session": "london / ny / asian / all",
|
||||
"expected_arr_range": "e.g. 8-12%"
|
||||
}
|
||||
|
||||
user: |-
|
||||
Previously tried approaches and their results:
|
||||
{{ factor_descriptions }}
|
||||
|
||||
Additional context:
|
||||
{{ report_content }}
|
||||
|
||||
Generate a NEW hypothesis that is meaningfully different from what has been tried.
|
||||
Focus on approaches that have NOT been tested yet.
|
||||
Target: beat current best ARR of 9.62%.
|
||||
YAMLEOF
|
||||
|
||||
echo "✓ prompts.yaml aktualisiert (Backup erstellt)"
|
||||
|
||||
# ─── 6. Model Coder Prompt erweitern ─────────────────────────────────────────
|
||||
echo ""
|
||||
echo "[6/7] Erweitere Model Coder Prompts..."
|
||||
|
||||
MODEL_PROMPT="$PREDIX_DIR/rdagent/components/coder/model_coder/prompts.yaml"
|
||||
cp "$MODEL_PROMPT" "${MODEL_PROMPT}.backup_$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
# EURUSD session filter als Kommentar in den evolving_strategy Block injizieren
|
||||
python3 << 'PYEOF'
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
path = Path("/home/nico/Predix/rdagent/components/coder/model_coder/prompts.yaml")
|
||||
content = path.read_text()
|
||||
|
||||
eurusd_note = """
|
||||
EURUSD-specific rules (ALWAYS apply these in generated code):
|
||||
1. Session filter: use is_london and is_ny columns — weight/filter signals to active sessions
|
||||
2. Spread filter: only generate signal when abs(predicted_return) > 0.0003
|
||||
3. ADX regime: if adx_proxy > 1.2 use trend model; if adx_proxy < 0.8 use mean-reversion
|
||||
4. Weekend filter: zero out signals when dayofweek==4 and hour>=20
|
||||
5. Max trade frequency: target <15 trades per day (avoid spread cost death)
|
||||
6. Supported model_type values: "Tabular", "TimeSeries", "XGBoost"
|
||||
"""
|
||||
|
||||
# Inject after the scenario line in evolving_strategy_model_coder
|
||||
content = content.replace(
|
||||
" Your code is expected to align the scenario in any form",
|
||||
eurusd_note + "\n Your code is expected to align the scenario in any form"
|
||||
)
|
||||
path.write_text(content)
|
||||
print(" ✓ Model coder prompt erweitert")
|
||||
PYEOF
|
||||
|
||||
echo "✓ Model coder Prompt angepasst"
|
||||
|
||||
# ─── 7. Git Commits ──────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "[7/7] Git Commits..."
|
||||
|
||||
cd "$PREDIX_DIR"
|
||||
|
||||
git add rdagent/app/qlib_rd_loop/prompts.yaml
|
||||
git commit -m "feat: EURUSD 15min prompts - session filter, FX domain knowledge
|
||||
|
||||
- Add London/NY/Asian session awareness to hypothesis generation
|
||||
- Add model type suggestions: LSTM, GRU, TCN, Transformer, XGBoost, LightGBM
|
||||
- Add spread filter (1.5 bps) and ADX regime detection
|
||||
- Target: beat current best ARR of 9.62%"
|
||||
|
||||
git add rdagent/components/coder/model_coder/prompts.yaml
|
||||
git commit -m "feat: inject EURUSD trading rules into model coder
|
||||
|
||||
- Session filter (is_london, is_ny)
|
||||
- Spread filter (|return| > 0.0003)
|
||||
- Weekend position close
|
||||
- Max trade frequency guidance"
|
||||
|
||||
git add .env 2>/dev/null || true
|
||||
echo " (Note: .env nicht committed - enthält API Keys)"
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Setup abgeschlossen!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Nächste Schritte:"
|
||||
echo ""
|
||||
echo " 1. Starten:"
|
||||
echo " cd ~/Predix && rdagent fin_quant"
|
||||
echo ""
|
||||
echo " 2. Dashboard (in zweitem Terminal):"
|
||||
echo " cd ~/Predix && rdagent server_ui --port 19899"
|
||||
echo " → http://localhost:19899"
|
||||
echo ""
|
||||
echo " 3. Logs:"
|
||||
echo " tail -f $PREDIX_DIR/git_ignore_folder/log/*.log"
|
||||
echo ""
|
||||
echo "Backup-Dateien (.backup_*) können nach erfolgreichem Test gelöscht werden."
|
||||
Generated
+10
-651
@@ -818,356 +818,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
||||
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
||||
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
@@ -4882,52 +4532,6 @@
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
||||
"@rollup/rollup-android-arm64": "4.59.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.59.0",
|
||||
"@rollup/rollup-darwin-x64": "4.59.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.59.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
||||
"@rollup/rollup-openbsd-x64": "4.59.0",
|
||||
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
@@ -5918,7 +5522,7 @@
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -6545,8 +6149,7 @@
|
||||
"@element-plus/icons-vue": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
|
||||
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg=="
|
||||
},
|
||||
"@emnapi/core": {
|
||||
"version": "1.9.0",
|
||||
@@ -6929,206 +6532,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-android-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-darwin-x64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
||||
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
||||
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
@@ -7194,8 +6597,7 @@
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
||||
"integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"dev": true
|
||||
},
|
||||
"@volar/language-core": {
|
||||
"version": "2.4.6",
|
||||
@@ -7363,8 +6765,7 @@
|
||||
"vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7384,8 +6785,7 @@
|
||||
"vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9139,8 +8539,7 @@
|
||||
"lodash-unified": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz",
|
||||
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ=="
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.30.12",
|
||||
@@ -9593,8 +8992,7 @@
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-prefix-selector/-/postcss-prefix-selector-1.16.1.tgz",
|
||||
"integrity": "sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"dev": true
|
||||
},
|
||||
"posthtml": {
|
||||
"version": "0.9.2",
|
||||
@@ -9808,43 +9206,6 @@
|
||||
"@rolldown/pluginutils": "1.0.0-rc.9"
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
||||
"@rollup/rollup-android-arm64": "4.59.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.59.0",
|
||||
"@rollup/rollup-darwin-x64": "4.59.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.59.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
||||
"@rollup/rollup-openbsd-x64": "4.59.0",
|
||||
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
@@ -10421,8 +9782,7 @@
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "4.0.3",
|
||||
@@ -10596,7 +9956,7 @@
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||
"devOptional": true
|
||||
"dev": true
|
||||
},
|
||||
"uc.micro": {
|
||||
"version": "2.1.0",
|
||||
@@ -10894,8 +10254,7 @@
|
||||
"vue-demi": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A=="
|
||||
},
|
||||
"vue-echarts": {
|
||||
"version": "7.0.3",
|
||||
|
||||
Reference in New Issue
Block a user