Repository: qte77/agents-eval
Files analyzed: 92
Estimated tokens: 238.1k
Directory structure:
└── qte77-agents-eval/
├── README.md
├── AGENTS.md
├── CHANGELOG.md
├── CLAUDE.md
├── Dockerfile
├── LICENSE.md
├── Makefile
├── mkdocs.yaml
├── pyproject.toml
├── uv.lock
├── .env.example
├── .gitmessage
├── assets/
│ └── images/
├── context/
│ └── PRPs/
│ ├── coordination_quality.md
│ ├── tool_efficiency.md
│ ├── features/
│ │ ├── coordination_quality.md
│ │ └── tool_efficiency.md
│ └── templates/
│ ├── feature_base.md
│ └── prp_base.md
├── docs/
│ ├── llms.txt
│ ├── PRD.md
│ ├── SprintPlan.md
│ ├── UserStory.md
│ └── architecture/
│ ├── c4-multi-agent-system.plantuml
│ ├── customer-journey-activity-dark
│ ├── customer-journey-activity-light.plantuml
│ └── metrics-eval-sweep.plantuml
├── src/
│ ├── run_gui.py
│ ├── app/
│ │ ├── __init__.py
│ │ ├── main.py
│ │ ├── py.typed
│ │ ├── agents/
│ │ │ ├── __init__.py
│ │ │ ├── agent_system.py
│ │ │ └── llm_model_funs.py
│ │ ├── config/
│ │ │ ├── __init__.py
│ │ │ ├── config_app.py
│ │ │ ├── config_chat.json
│ │ │ ├── config_eval.json
│ │ │ └── data_models.py
│ │ ├── evals/
│ │ │ ├── __init__.py
│ │ │ └── metrics.py
│ │ └── utils/
│ │ ├── __init__.py
│ │ ├── error_messages.py
│ │ ├── load_configs.py
│ │ ├── load_settings.py
│ │ ├── log.py
│ │ ├── login.py
│ │ └── utils.py
│ ├── examples/
│ │ ├── config.json
│ │ ├── run_simple_agent_no_tools.py
│ │ ├── run_simple_agent_system.py
│ │ ├── run_simple_agent_tools.py
│ │ └── utils/
│ │ ├── agent_simple_no_tools.py
│ │ ├── agent_simple_system.py
│ │ ├── agent_simple_tools.py
│ │ ├── data_models.py
│ │ ├── tools.py
│ │ └── utils.py
│ └── gui/
│ ├── components/
│ │ ├── footer.py
│ │ ├── header.py
│ │ ├── output.py
│ │ ├── prompts.py
│ │ └── sidebar.py
│ ├── config/
│ │ ├── config.py
│ │ ├── styling.py
│ │ └── text.py
│ └── pages/
│ ├── home.py
│ ├── prompts.py
│ ├── run_app.py
│ └── settings.py
├── tests/
│ ├── test_agent_system.py
│ ├── test_env.py
│ ├── test_metrics_output_similarity.py
│ ├── test_metrics_time_taken.py
│ └── test_provider_config.py
├── .claude/
│ ├── settings.local.json
│ └── commands/
│ ├── execute-prp.md
│ └── generate-prp.md
├── .devcontainer/
│ ├── setup_dev/
│ │ └── devcontainer.json
│ ├── setup_dev_claude/
│ │ └── devcontainer.json
│ └── setup_dev_ollama/
│ └── devcontainer.json
├── .github/
│ ├── dependabot.yaml
│ ├── scripts/
│ │ ├── create_pr.sh
│ │ └── delete_branch_pr_tag.sh
│ └── workflows/
│ ├── bump-my-version.yaml
│ ├── codeql.yaml
│ ├── generate-deploy-mkdocs-ghpages.yaml
│ ├── links-fail-fast.yaml
│ ├── pytest.yaml
│ ├── ruff.yaml
│ ├── summarize-jobs-reusable.yaml
│ └── write-llms-txt.yaml
└── .streamlit/
└── config.toml
================================================
FILE: README.md
================================================
# Agents-eval
This project aims to implement an evaluation pipeline to assess the effectiveness of open-source agentic AI systems across various use cases, focusing on use case agnostic metrics that measure core capabilities such as task decomposition, tool integration, adaptability, and overall performance.


[](https://github.com/qte77/Agents-eval/actions/workflows/codeql.yaml)
[](https://www.codefactor.io/repository/github/qte77/Agents-eval)
[](https://github.com/qte77/Agents-eval/actions/workflows/ruff.yaml)
[](https://github.com/qte77/Agents-eval/actions/workflows/pytest.yaml)
[](https://github.com/qte77/Agents-eval/actions/workflows/links-fail-fast.yaml)
[](https://github.com/qte77/Agents-eval/actions/workflows/generate-deploy-mkdocs-ghpages.yaml)
**DevEx** [](https://vscode.dev/github/qte77/Agents-eval)
[](https://github.com/codespaces/new?repo=qte77/Agents-eval&devcontainer_path=.devcontainer/setup_dev/devcontainer.json)
[](https://github.com/codespaces/new?repo=qte77/Agents-eval&devcontainer_path=.devcontainer/setup_dev_claude/devcontainer.json)
[](https://github.com/codespaces/new?repo=qte77/Agents-eval&devcontainer_path=.devcontainer/setup_dev_ollama/devcontainer.json)
[](https://talktogithub.com/qte77/Agents-eval)
[](https://github.com/qte77/Agents-eval)
[](https://gittodoc.com/qte77/Agents-eval)
## Status
(DRAFT) (WIP) ----> Not fully implemented yet
For version history have a look at the [CHANGELOG](CHANGELOG.md).
## Setup and Usage
- `make setup_prod`
- `make setup_dev` or `make setup_dev_claude` or `make setup_dev_ollama`
- `make run_cli` or `make run_cli ARGS="--help"`
- `make run_gui`
- `make test_all`
### Configuration
- [config_app.py](src/app/config/config_app.py) contains configuration constants for the application.
- [config_chat.json](src/app/config/config_chat.json) contains inference provider configuration and prompts. inference endpoints used should adhere to [OpenAI Model Spec 2024-05-08](https://cdn.openai.com/spec/model-spec-2024-05-08.html) which is used by [pydantic-ai OpenAI-compatible Models](https://ai.pydantic.dev/models/#openai-compatible-models).
- [config_eval.json](src/app/config/config_eval.json) contains evaluation metrics and their weights.
- [data_models.py](src/app/config/data_models.py) contains the pydantic data models for agent system configuration and results.
### Environment
[.env.example](.env.example) contains examples for usage of API keys and variables.
```text
# inference EP
GEMINI_API_KEY="xyz"
# tools
TAVILY_API_KEY=""
# log/mon/trace
WANDB_API_KEY="xyz"
```
### Customer Journey
Show Customer Journey
### Note
1. The contained chat configuration uses free inference endpoints which are subject to change by the providers. See lists such as [free-llm-api-resources](https://github.com/cheahjs/free-llm-api-resources) to find other providers.
2. The contained chat configuration uses models which are also subject to change by the providers and have to be updated from time to time.
3. LLM-as-judge is also subject to the chat configuration.
## Documentation
[Agents-eval](https://qte77.github.io/Agents-eval)
### Project Outline
`# TODO`
### Agents
#### Manager Agent
- **Description**: Oversees research and analysis tasks, coordinating the efforts of the research, analysis, and synthesizer agents to provide comprehensive answers to user queries. Delegates tasks and ensures the accuracy of the information.
- **Responsibilities**:
- Coordinates the research, analysis, and synthesis agents.
- Delegates research tasks to the Research Agent.
- Delegates analysis tasks to the Analysis Agent.
- Delegates synthesis tasks to the Synthesizer Agent.
- Ensures the accuracy of the information.
- **Location**: [src/app/agents/agent_system.py](https://github.com/qte77/Agents-eval/blob/main/src/app/agents/agent_system.py)
#### Researcher Agent
- **Description**: Gathers and analyzes data relevant to a given topic, utilizing search tools to collect data and verifying the accuracy of assumptions, facts, and conclusions.
- **Responsibilities**:
- Gathers and analyzes data relevant to the topic.
- Uses search tools to collect data.
- Checks the accuracy of assumptions, facts, and conclusions.
- **Tools**:
- [DuckDuckGo Search Tool](https://ai.pydantic.dev/common-tools/#duckduckgo-search-tool)
- **Location**: [src/app/agents/agent_system.py](https://github.com/qte77/Agents-eval/blob/main/src/app/agents/agent_system.py)
#### Analyst Agent
- **Description**: Checks the accuracy of assumptions, facts, and conclusions in the provided data, providing relevant feedback and ensuring data integrity.
- **Responsibilities**:
- Checks the accuracy of assumptions, facts, and conclusions.
- Provides relevant feedback if the result is not approved.
- Ensures data integrity.
- **Location**: [src/app/agents/agent_system.py](https://github.com/qte77/Agents-eval/blob/main/src/app/agents/agent_system.py)
#### Synthesizer Agent
- **Description**: Outputs a well-formatted scientific report using the data provided, maintaining the original facts, conclusions, and sources.
- **Responsibilities**:
- Outputs a well-formatted scientific report using the provided data.
- Maintains the original facts, conclusions, and sources.
- **Location**: [src/app/agents/agent_system.py](https://github.com/qte77/Agents-eval/blob/main/src/app/agents/agent_system.py)
### Datasets used
`# TODO`
### Evalutions metrics
`# TODO`
- Time to complete task (time_taken)
- Task success rate (task_success)
- Agent coordination (coordination_quality)
- Tool usage efficiency (tool_efficiency)
- Plan coherence (planning_rational)
- Text response quality (text_similarity)
- Autonomy vs. human intervention (HITL, user feedback)
- Reactivity (adapt to changes of tasks and environments)
- Memory consistency
### Evaluations Metrics Baseline
As configured in [config_eval.json](src/app/config/config_eval.json).
```json
{
"evaluators_and_weights": {
"planning_rational": "1/6",
"task_success": "1/6",
"tool_efficiency": "1/6",
"coordination_quality": "1/6",
"time_taken": "1/6",
"text_similarity": "1/6"
}
}
```
### Eval Metrics Sweep
Eval Metrics Sweep
### Tools available
Other pydantic-ai agents and [pydantic-ai DuckDuckGo Search Tool](https://ai.pydantic.dev/common-tools/#duckduckgo-search-tool).
### Agentic System Architecture
Show Agentic System Architecture
### Project Repo Structure
Show Repo Structure
```sh
|- .claude # claude code config and commands
|- .devcontainer # pre-configured dev env
|- .github # workflows
|- .streamlit # config.toml
|- .vscode # extensions, settings
|- assets/images
|- docs
|- src # source code
|- app
|- agents
|- config
|- evals
|- utils
|- __init__.py
|- main.py
\- py.typed
|- examples
|- gui
\- run_gui.py
|- tests
|- .env.example # example env vars
|- .gitignore
|- .gitmessage
|- AGENTS.md # common file like agentsmd.com
|- CHANGEOG.md # short project history
|- CLAUDE.md # points to AGENTS.md
|- Dockerfile # create app image
|- LICENSE.md
|- Makefile # helper scripts
|- mkdocs.yaml # docu from docstrings
|- pyproject.toml # project settings
|- README.md # project description
\- uv.lock # resolved package versions
```
## Landscape overview
### Agentic System Frameworks
- [PydanticAI](https://github.com/pydantic/pydantic-ai)
- [restack](https://www.restack.io/)
- [smolAgents](https://github.com/huggingface/smolagents)
- [AutoGen](https://github.com/microsoft/autogen)
- [Semantic Kernel](https://github.com/microsoft/semantic-kernel)
- [CrewAI](https://github.com/crewAIInc/crewAI)
- [Langchain](https://github.com/langchain-ai/langchain)
- [Langflow](https://github.com/langflow-ai/langflow)
### Agent-builder
- [Archon](https://github.com/coleam00/Archon)
- [Agentstack](https://github.com/AgentOps-AI/AgentStack)
### Evaluation
- Focusing on agentic systems
- [AgentNeo](https://github.com/raga-ai-hub/agentneo)
- [AutoGenBench](https://github.com/microsoft/autogen/blob/0.2/samples/tools/autogenbench)
- [Langchain AgentEvals](https://github.com/langchain-ai/agentevals)
- [Mosaic AI Agent Evaluation](https://docs.databricks.com/en/generative-ai/agent-evaluation/index.html)
- [RagaAI-Catalyst](https://github.com/raga-ai-hub/RagaAI-Catalyst)
- [AgentBench](https://github.com/THUDM/AgentBench)
- RAG oriented
- [RAGAs](https://github.com/explodinggradients/ragas)
- LLM apps
- [DeepEval](https://github.com/confident-ai/deepeval)
- [Langchain OpenEvals](https://github.com/langchain-ai/openevals)
- [MLFlow LLM Evaluate](https://mlflow.org/docs/latest/llms/llm-evaluate/index.html)
- [DeepEval (DeepSeek)]( github.com/confident-ai/deepeval)
### Observation, Monitoring, Tracing
- [AgentOps - Agency](https://www.agentops.ai/)
- [arize](https://arize.com/)
- [Langtrace](https://www.langtrace.ai/)
- [LangSmith - Langchain](https://www.langchain.com/langsmith)
- [Weave - Weights & Biases](https://wandb.ai/site/weave/)
- [Pydantic- Logfire](https://pydantic.dev/logfire)
### Datasets
- [awesome-reasoning - Collection of datasets](https://github.com/neurallambda/awesome-reasoning)
#### Scientific
- [SWIF2T](https://arxiv.org/abs/2405.20477), Automated Focused Feedback Generation for Scientific Writing Assistance, 2024, 300 peer reviews citing weaknesses in scientific papers and conduct human evaluation
- [PeerRead](https://github.com/allenai/PeerRead), A Dataset of Peer Reviews (PeerRead): Collection, Insights and NLP Applications, 2018, 14K paper drafts and the corresponding accept/reject decisions, over 10K textual peer reviews written by experts for a subset of the papers, structured JSONL, clear labels
- [BigSurvey](https://www.ijcai.org/proceedings/2022/0591.pdf), Generating a Structured Summary of Numerous Academic Papers: Dataset and Method, 2022, 7K survey papers and 430K referenced papers abstracts
- [SciXGen](https://arxiv.org/abs/2110.10774), A Scientific Paper Dataset for Context-Aware Text Generation, 2021, 205k papers
- [scientific_papers](https://huggingface.co/datasets/armanc/scientific_papers), 2018, two sets of long and structured documents, obtained from ArXiv and PubMed OpenAccess, 300k+ papers, total disk 7GB
#### Reasoning, Deduction, Commonsense, Logic
- [LIAR](https://www.cs.ucsb.edu/~william/data/liar_dataset.zip), fake news detection, only 12.8k records, single label
- [X-Fact](https://github.com/utahnlp/x-fact/), Benchmark Dataset for Multilingual Fact Checking, 31.1k records, large, multilingual
- [MultiFC](https://www.copenlu.com/publication/2019_emnlp_augenstein/), A Real-World Multi-Domain Dataset for Evidence-Based Fact Checking of Claims, 34.9k records
- [FEVER](https://fever.ai/dataset/fever.html), Fact Extraction and VERification, 185.4k records
- TODO GSM8K, bAbI, CommonsenseQA, DROP, LogiQA, MNLI
#### Planning, Execution
- [Plancraft](https://arxiv.org/abs/2412.21033), an evaluation dataset for planning with LLM agents, both a text-only and multi-modal interface
- [IDAT](https://arxiv.org/abs/2407.08898), A Multi-Modal Dataset and Toolkit for Building and Evaluating Interactive Task-Solving Agents
- [PDEBench](https://github.com/pdebench/PDEBench), set of benchmarks for scientific machine learning
- [MatSci-NLP](https://arxiv.org/abs/2305.08264), evaluating the performance of natural language processing (NLP) models on materials science text
- TODO BigBench Hard, FSM Game
#### Tool Use, Function Invocation
- [Trelis Function Calling](https://huggingface.co/datasets/Trelis/function_calling_v3)
- [KnowLM Tool](https://huggingface.co/datasets/zjunlp/KnowLM-Tool)
- [StatLLM](https://arxiv.org/abs/2502.17657), statistical analysis tasks, LLM-generated SAS code, and human evaluation scores
- TODO ToolComp
### Benchmarks
- [SciArena: A New Platform for Evaluating Foundation Models in Scientific Literature Tasks](https://allenai.org/blog/sciarena)
- [AgentEvals CORE-Bench Leaderboard](https://huggingface.co/spaces/agent-evals/core_leaderboard)
- [Berkeley Function-Calling Leaderboard](https://gorilla.cs.berkeley.edu/leaderboard.html)
- [Chatbot Arena LLM Leaderboard](https://lmsys.org/projects/)
- [GAIA Leaderboard](https://gaia-benchmark-leaderboard.hf.space/)
- [GalileoAI Agent Leaderboard](https://huggingface.co/spaces/galileo-ai/agent-leaderboard)
- [WebDev Arena Leaderboard](https://web.lmarena.ai/leaderboard)
- [MiniWoB++: a web interaction benchmark for reinforcement learning](https://miniwob.farama.org/)
### Research Agents
- [Ai2 Scholar QA](https://qa.allen.ai/chat)
## Further Reading
- [[2506.18096] Deep Research Agents: A Systematic Examination And Roadmap](https://arxiv.org/abs/2506.18096), [gh / ai-agents-2030 / awesome-deep-research-agent](https://github.com/ai-agents-2030/awesome-deep-research-agent)
- [[2504.19678] From LLM Reasoning to Autonomous AI Agents: A Comprehensive Review](https://arxiv.org/abs/2504.19678)
- [[2503.21460] Large Language Model Agent: A Survey on Methodology, Applications and Challenges](https://arxiv.org/abs/2503.21460)
- [[2503.16416] Survey on Evaluation of LLM-based Agents](https://arxiv.org/abs/2503.16416)
- [[2503.13657] Why Do Multi-Agent LLM Systems Fail?](https://arxiv.org/abs/2503.13657)
- [[2502.14776] SurveyX: Academic Survey Automation via Large Language Models](https://arxiv.org/abs/2502.14776)
- [[2502.05957] AutoAgent: A Fully-Automated and Zero-Code Framework for LLM Agents](https://arxiv.org/abs/2502.05957)
- [[2502.02649] Fully Autonomous AI Agents Should Not be Developed](https://arxiv.org/abs/2502.02649)
- [[2501.16150] AI Agents for Computer Use: A Review of Instruction-based Computer Control, GUI Automation, and Operator Assistants](https://arxiv.org/abs/2501.16150)
- [[2501.06590] ChemAgent](https://arxiv.org/abs/2501.06590)
- [[2501.06322] Multi-Agent Collaboration Mechanisms: A Survey of LLMs](https://arxiv.org/abs/2501.06322)
- [[2501.04227] Agent Laboratory: Using LLM Agents as Research Assitants](https://arxiv.org/abs/2501.04227), [AgentRxiv:Towards Collaborative Autonomous Research](https://agentrxiv.github.io/)
- [[2501.00881] Agentic Systems: A Guide to Transforming Industries with Vertical AI Agents](https://arxiv.org/abs/2501.00881)
- [[2412.04093] Practical Considerations for Agentic LLM Systems](https://arxiv.org/abs/2412.04093)
- [[2411.13768] Evaluation-driven Approach to LLM Agents](https://arxiv.org/abs/2411.13768)
- [[2411.10478] Large Language Models for Constructing and Optimizing Machine Learning Workflows: A Survey](https://arxiv.org/abs/2411.10478)
- [[2411.05285] A taxonomy of agentops for enabling observability of foundation model based agents](https://arxiv.org/abs/2411.05285)
- [[2410.22457] Advancing Agentic Systems: Dynamic Task Decomposition, Tool Integration and Evaluation using Novel Metrics and Dataset](https://arxiv.org/abs/2410.22457)
- [[2408.06361] Large Language Model Agent in Financial Trading: A Survey](https://arxiv.org/abs/2408.06361)
- [[2408.06292] The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery](https://arxiv.org/abs/2408.06292)
- [[2404.13501] A Survey on the Memory Mechanism of Large Language Model based Agents](https://arxiv.org/pdf/2404.13501)
- [[2402.06360] CoSearchAgent: A Lightweight Collaborative Search Agent with Large Language Models](https://arxiv.org/abs/2402.06360)
- [[2402.02716] Understanding the planning of LLM agents: A survey](https://arxiv.org/abs/2402.02716)
- [[2402.01030] Executable Code Actions Elicit Better LLM Agents](https://arxiv.org/abs/2402.01030)
- [[2308.11432] A Survey on Large Language Model based Autonomous Agents](https://arxiv.org/abs/2308.11432)
================================================
FILE: AGENTS.md
================================================
# Agent instructions for `Agents-eval` repository
As proposed by [agentsmd.net](https://agentsmd.net/) and used by [wandb weave AGENTS.md](https://github.com/wandb/weave/blob/master/AGENTS.md).
## Core Rules & AI Behavior
* When you learn something new about the codebase or introduce a new concept, **update this file (`AGENTS.md`)** to reflect the new knowledge. This is YOUR FILE! It should grow and evolve with you.
* If something doesn't make sense architecturally, from a developer experience standpoint, or product-wise, please add it to the **`Requests to Humans`** section below.
* Always follow the established coding patterns, conventions, and architectural decisions documented here and in the `docs/` directory.
* **Never assume missing context.** Ask questions if you are uncertain about requirements or implementation details.
* **Never hallucinate libraries or functions.** Only use known, verified Python packages listed in `pyproject.toml`.
* **Always confirm file paths and module names** exist before referencing them in code or tests.
* **Never delete or overwrite existing code** unless explicitly instructed to or as part of a documented refactoring task.
## Architecture Overview
This is a multi-agent evaluation system for assessing agentic AI systems. The project uses **PydanticAI** as the core framework for agent orchestration and is designed for evaluation purposes, not for production agent deployment.
### Data Flow
1. User input → Manager Agent
2. Manager delegates to Researcher Agent (with DuckDuckGo search)
3. Researcher results → Analyst Agent for validation
4. Validated data → Synthesizer Agent for report generation
5. Results evaluated using configurable metrics
### Key Dependencies
* **PydanticAI**: Agent framework and orchestration
* **uv**: Fast Python dependency management
* **Streamlit**: GUI framework
* **Ruff**: Code formatting and linting
* **MyPy**: Static type checking
## Codebase Structure & Modularity
### Main Components
* `src/app/`: The core application logic. This is where most of your work will be.
* `main.py`: The main entry point for the CLI application.
* `agents/agent_system.py`: Defines the multi-agent system, their interactions, and orchestration. **This is the central logic for agent behavior.**
* `config/data_models.py`: Contains all **Pydantic** models that define the data contracts. This is a critical file for understanding data flow.
* `config/config_chat.json`: Holds provider settings and system prompts for agents.
* `config/config_eval.json`: Defines evaluation metrics and their weights.
* `evals/metrics.py`: Implements the evaluation metrics.
* `src/gui/`: Contains the source code for the Streamlit GUI.
* `docs/`: Contains project documentation, including the Product Requirements Document (`PRD.md`) and the C4 architecture model.
* `tests/`: Contains all tests for the project, written using **pytest**.
### Code Organization Rules
* **Never create a file longer than 500 lines of code.** If a file approaches this limit, refactor by splitting it into smaller, more focused modules or helper files.
* Organize code into clearly separated modules grouped by feature.
* Use clear, consistent, and absolute imports within packages.
## Development Commands & Environment
### Environment Setup
The project requirements are stated in `pyproject.toml`. Your development environment should be set up automatically using the provided `Makefile`, which configures the virtual environment.
* `make setup_dev`: Install all dev dependencies.
* `make setup_dev_claude`: Setup dev environment with Claude Code CLI.
* `make setup_dev_ollama`: Setup dev environment with Ollama local LLM.
### Running the Application
* `make run_cli`: Run the CLI application.
* `make run_cli ARGS="--help"`: Run CLI with specific arguments.
* `make run_gui`: Run the Streamlit GUI.
### Testing and Code Quality
Testing is managed by **pytest** and orchestrated via the `Makefile`.
* `make test_all`: Run all tests with pytest.
* `make coverage_all`: Run tests and generate a coverage report.
* `make ruff`: Format code and fix linting issues with Ruff.
* `make type_check`: Run mypy static type checking on `src/app/`.
## Testing & Reliability
* **Always create Pytest unit tests** for new features (functions, classes, etc.).
* Tests must live in the `tests/` folder, mirroring the `src/app` structure.
* After updating any logic, check whether existing unit tests need to be updated. If so, do it.
* For each new feature, include at least:
* 1 test for the expected use case (happy path).
* 1 test for a known edge case.
* 1 test for an expected failure case (e.g., invalid input).
* **To run a specific test file or function, use `uv run pytest` directly:**
* `uv run pytest tests/test_specific_file.py`
* `uv run pytest tests/test_specific_file.py::test_function`
## Style, Patterns & Documentation
### Coding Style
* **Use Pydantic** models in `src/app/config/data_models.py` for all data validation and data contracts. **Always use or update these models** when modifying data flows.
* Use the predefined error message functions from `src/app/utils/error_messages.py` for consistency.
* When writing complex logic, **add an inline `# Reason:` comment** explaining the *why*, not just the *what*.
* Comment non-obvious code to ensure it is understandable to a mid-level developer.
### Documentation
* Write **docstrings for every function, class, and method** using the Google style format. This is critical as the documentation site is built automatically from docstrings.
```python
def example_function(param1: int) -> str:
"""A brief summary of the function.
Args:
param1 (int): A description of the first parameter.
Returns:
str: A description of the return value.
"""
return "example"
```
* Update this `AGENTS.md` file when introducing new patterns or concepts.
* Document significant architectural decisions in `docs/ADR.md`.
* Document all significant changes, features, and bug fixes in `docs/CHANGELOG.md`.
## Code Review & PR Guidelines
### PR Requirements
* **Title Format**: Commit messages and PR titles must follow the **Conventional Commits** specification, as outlined in the `.gitmessage` template.
* Provide detailed PR summaries including the purpose of the changes and the testing performed.
### Pre-commit Checklist
1. Run the linter and formatter: `make ruff`.
2. Ensure all tests pass: `make test_all`.
3. Ensure static type checks pass: `make type_check`.
4. Update documentation as described below.
## Requests to Humans
This section contains a list of questions, clarifications, or tasks that AI agents wish to have humans complete or elaborate on.
* [ ] The `agent_system.py` module has a `NotImplementedError` for streaming with Pydantic model outputs. Please clarify the intended approach for streaming structured data.
* [ ] The `llm_model_funs.py` module has `NotImplementedError` for the Gemini and HuggingFace providers. Please provide the correct implementation or remove them if they are not supported.
* [ ] The `agent_system.py` module contains a `FIXME` note regarding the use of a try-catch context manager. Please review and implement the intended error handling.
* [ ] Add TypeScript testing guidelines (if a TypeScript frontend is planned for the future).
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Guiding Principles
- Changelogs are for humans, not machines.
- There should be an entry for every single version.
- The same types of changes should be grouped.
- Versions and sections should be linkable.
- The latest version comes first.
- The release date of each version is displayed.
- Mention whether you follow Semantic Versioning.
## Types of changes
- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.
## [Unreleased]
## [2.0.0] - 2025-07-06
### Added
- Claude code functionality, commands and settings
## [1.1.0] - 2025-07-05
### Added
- Makefile command and devcontainer.json for claude code usage
### Changed
- Moved streamlit_gui and examples to /src
- Moved app to /src/app
## [1.0.0] - 2025-03-18
### 2025-03-18
- refactor(agent,streamlit): Convert main and run_manager functions again to async for streamli output
- fix(prompts): Update system prompts for manager,researcher and synthesiser roles to remove complexity
- chore(workflows): Update action versions in GitHub workflows for consistency
- chore(workflows): Update action versions for deploy docs to pgh-pages
- docs(deps): Add documentation dependencies for MkDocs and related plugins to pyproject.toml
### 2025-03-17
- feat(main,agent): refactor entry point to support async execution and enhance login handling
- feat(cli,login,log): refactor entry point to integrate Typer, enhance logging, added login every run
- feat(streamlit): replace load_config with load_app_config, enhance sidebar rendering, and improve output rendering with type support
- feat(streamlit): enhance render_output function with detailed docstring and improve query handling in run_app
- feat(streamlit): enhance render_output function with additional info parameter and improve output handling in run_app
- feat(streamlit,app): add Typer dependency, update main entry point for async execution, add streamlit provider input
- feat(agent): update configuration and improve agent system setup with enhanced error handling and new environment variables
- feat(config,login,catch): add inference settings with usage limits and result retries, enhance login function to initialize environment and handle exceptions, comment out raise in error handling context to prevent unintended crashes
- feat(login,catch): integrate logfire configuration in login function and improve error handling context
### 2025-03-16
- feta(devconatiner): Refactor devcontainer setup: remove old configurations and add new setup targets for development and Ollama
- feat(devcontainer): Changed from vscode to astral-sh devcontainer
- feat(devcontainer): Changed to vscode container, added postcreatecommand make setup_env
- feat(devcontainer): restructure environment setup with new devcontainer configurations
- feat(devcontainer): update environment names for clarity in devcontainer configurations
- refactor(agent): Added AgentConfig class for better agent configuration management, Refactored main function for streamlined agent initialization.
- feat(config,agents): Update model providers and enhance configuration management, examples: Added new model providers: Gemini and OpenRouter, src: Enabled streaming responses in the agent system
- chore: Remove unused prompt files, update configuration, and enhance logging setup
- refactor(exception,logfire): Enhance error handling and update model configurations in agent system
### 2025-03-14
- feat(scalene): Add profiling support and update dependencies
- refactor(Makefile): Improve target descriptions and organization
### 2025-03-13
- refactor(API,except): .env.example, add OpenRouter configuration, enhance error handling in run_simple_agent_system.py, and update ModelConfig to allow optional API key.
- feat(streamlit): add Streamlit app structure with header, footer, sidebar, and main content components
- feat(streamlit): enhance Streamlit app with detailed docstrings, improved header/footer, and refined main content layout
- feat(makefile,streamlit): update Makefile commands for CLI and GUI execution, and modify README for usage instructions, add streamlit config.toml
- feat(streamlit): restructure Streamlit app by removing unused components, adding new header, footer, sidebar, and output components, and updating configuration settings
- chore: replace app entrypoint with main, remove unused tools and tests, and update makefile for linting and type checking
- chore: Enhance makefile with coverage and help commands, update mkdocs.yaml and pyproject.toml for improved project structure and documentation
- test: Update makefile for coverage reporting, modify pyproject.toml to include pytest-cov, and adjust dependency settings
- test: Add coverage support with pytest-cov and update makefile for coverage reporting
- test: makefile for coverage reporting, update dependencies in pyproject.toml for improved testing and coverage support
- chore: Remove redundant help command from makefile
- refactor(agent,async): Refactor agent tests to use async fixtures and update verification methods for async results
- fix(Dockerfile): Remove unnecessary user creation and pip install commands from Dockerfile
- feat(agent): Update dependencies and add new example structures; remove obsolete files
- chore(structure): simplified agents.py
- fix(pyproject): Replace pydantic-ai with pydantic-ai-slim and update dependencies
- feat(examples): add new examples and data models; update configuration structure
- feat(agent): update dependencies, enhance examples, and introduce new data models for research and analysis agents
- feat(examples): enhance prompts structure and refactor research agent integration
- feat(examples): improve documentation and enhance error handling in agent examples
- feat(agent): Added data models and configuration for research and analysis agents, Added System C4 plantuml
- feat(weave,dependencies): update dependencies and integrate Weave for enhanced functionality in the agent system
- feat(agent): initialize agentops with API key and default tags for enhanced agent functionality
- feat(agent): integrate logfire for logging and configure initial logging settings
- feat(agent): adjust usage limits for ollama provider to enhance performance
- feat(agent): refine system prompts and enhance data model structure for improved agent interactions
- feat(agent): update system prompts for improved clarity and accuracy; add example environment configuration
- feat(agent): enhance agent system with synthesiser functionality and update prompts for improved coordination
- feat(agent): add Grok and Gemini API configurations; initialize logging and agent operations
- feat(agent): improve documentation and refactor model configuration handling for agent system
- feat(agent): update environment configuration, enhance logging, and refine agent management functionality
- feat(agent): refactor login handling, update model retrieval, and enhance agent configuration
## [0.0.2] - 2025-01-20
### Added
- PRD.md
- C4 architecture diagrams: system context, code
- tests: basic agent evals, config.json
### Changed
- make recipes
## [0.0.1] - 2025-01-20
### Added
- Makefile: setup, test, ruff
- devcontainer: python only, w/o Jetbrains clutter from default devcontainer
- ollama: server and model download successful
- agent: tools use full run red
- pytest: e2e runm final result red
- Readme: basic project info
- pyproject.toml
================================================
FILE: CLAUDE.md
================================================
@AGENTS.md
================================================
FILE: Dockerfile
================================================
ARG APP_ROOT="/src"
ARG PYTHON_VERSION="3.12"
ARG USER="appuser"
# Stage 1: Builder Image
FROM python:${PYTHON_VERSION}-slim AS builder
LABEL author="qte77"
LABEL builder=true
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY pyproject.toml uv.lock /
RUN set -xe \
&& pip install --no-cache-dir uv \
&& uv sync --frozen
# Stage 2: Runtime Image
FROM python:${PYTHON_VERSION}-slim AS runtime
LABEL author="qte77"
LABEL runtime=true
ARG APP_ROOT
ARG USER
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=${APP_ROOT} \
PATH="${APP_ROOT}:${PATH}"
# WANDB_KEY=${WANDB_KEY} \
# WANDB_DISABLE_CODE=true
USER ${USER}
WORKDIR ${APP_ROOT}
COPY --from=builder /.venv .venv
COPY --chown=${USER}:${USER} ${APP_ROOT} .
CMD [ \
"uv", "run", \
"--locked", "--no-sync", \
"python", "-m", "." \
]
================================================
FILE: LICENSE.md
================================================
# BSD 3-Clause License
Copyright (c) 2025 qte77
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: Makefile
================================================
# This Makefile automates the build, test, and clean processes for the project.
# It provides a convenient way to run common tasks using the 'make' command.
# It is designed to work with the 'uv' tool for managing Python environments and dependencies.
# Run `make help` to see all available recipes.
.SILENT:
.ONESHELL:
.PHONY: all setup_prod setup_dev setup_prod_ollama setup_dev_ollama setup_dev_claude setup_claude_code setup_ollama start_ollama stop_ollama clean_ollama ruff run_cli run_gui run_profile prp_gen_claude prp_exe_claude test_all coverage_all type_check output_unset_app_env_sh help
# .DEFAULT: setup_dev_ollama
.DEFAULT_GOAL := setup_dev_ollama
SRC_PATH := src
APP_PATH := $(SRC_PATH)/app
GUI_PATH_ST := $(SRC_PATH)/run_gui.py
CHAT_CFG_FILE := $(APP_PATH)/config_chat.json
OLLAMA_SETUP_URL := https://ollama.com/install.sh
OLLAMA_MODEL_NAME := $$(jq -r '.providers.ollama.model_name' $(CHAT_CFG_FILE))
PRP_DEF_PATH := /context/PRPs/features
PRP_CLAUDE_GEN_CMD := generate-prp
PRP_CLAUDE_EXE_CMD := execute-prp
# construct the full path to the PRP definition file
define CLAUDE_PRP_RUNNER
echo "Starting Claude Code PRP runner ..."
# 1. Extract arguments and validate that they are not empty.
prp_file=$(firstword $(strip $(1)))
cmd_prp=$(firstword $(strip $(2)))
if [ -z "$${prp_file}" ]; then
echo "Error: ARGS for PRP filename is empty. Please provide a PRP filename."
exit 1
fi
if [ -z "$${cmd_prp}" ]; then
echo "Error: ARGS for command is empty. Please provide a command."
exit 2
fi
cmd_prp="/project:$${cmd_prp} $(PRP_DEF_PATH)/$${prp_file}"
cmd_cost="/cost"
echo "Executing command '$${cmd_prp}' ..."
claude -p "$${cmd_prp}" 2>&1
claude -p "$${cmd_cost}" 2>&1
endef
setup_prod: ## Install uv and deps, Download and start Ollama
echo "Setting up prod environment ..."
pip install uv -q
uv sync --frozen
setup_dev: ## Install uv and deps, Download and start Ollama
echo "Setting up dev environment ..."
pip install uv -q
uv sync --all-groups
setup_prod_ollama:
$(MAKE) -s setup_prod
$(MAKE) -s setup_ollama
$(MAKE) -s start_ollama
setup_dev_ollama:
$(MAKE) -s setup_dev
$(MAKE) -s setup_ollama
$(MAKE) -s start_ollama
setup_dev_claude:
$(MAKE) -s setup_dev
$(MAKE) -s setup_claude_code
setup_claude_code: ## Setup claude code CLI, node.js and npm have to be present
echo "Setting up claude code ..."
npm install -g @anthropic-ai/claude-code
claude config set --global preferredNotifChannel terminal_bell
echo "npm version: $$(npm --version)"
claude --version
# Ollama BINDIR in /usr/local/bin /usr/bin /bin
setup_ollama: ## Download Ollama, script does start local Ollama server
echo "Downloading Ollama binary... Using '$(OLLAMA_SETUP_URL)'."
# script does start server but not consistently
curl -fsSL $(OLLAMA_SETUP_URL) | sh
echo "Pulling model '$(OLLAMA_MODEL_NAME)' ..."
ollama pull $(OLLAMA_MODEL_NAME)
start_ollama: ## Start local Ollama server, default 127.0.0.1:11434
ollama serve
stop_ollama: ## Stop local Ollama server
echo "Stopping Ollama server..."
pkill ollama
clean_ollama: ## Remove local Ollama from system
echo "Searching for Ollama binary..."
for BINDIR in /usr/local/bin /usr/bin /bin; do
if echo $$PATH | grep -q $$BINDIR; then
echo "Ollama binary found in '$$BINDIR'"
BIN="$$BINDIR/ollama"
break
fi
done
echo "Cleaning up..."
rm -f $(BIN)
ruff: ## Lint: Format and check with ruff
uv run ruff format
uv run ruff check --fix
run_cli: ## Run app on CLI only
path=$$(echo "$(APP_PATH)" | tr '/' '.')
uv run python -m $${path}.main $(ARGS)
run_gui: ## Run app with Streamlit GUI
uv run streamlit run $(GUI_PATH_ST)
run_profile: ## Profile app with scalene
uv run scalene --outfile \
"$(APP_PATH)/scalene-profiles/profile-$(date +%Y%m%d-%H%M%S)" \
"$(APP_PATH)/main.py"
prp_gen_claude: ## generates the PRP from the file passed in ARGS
$(call CLAUDE_PRP_RUNNER, $(ARGS), $(PRP_CLAUDE_GEN_CMD))
prp_exe_claude: ## executes the PRP from the file passed in ARGS
$(call CLAUDE_PRP_RUNNER, $(ARGS), $(PRP_CLAUDE_EXE_CMD))
test_all: ## Run all tests
uv run pytest
coverage_all: ## Get test coverage
uv run coverage run -m pytest || true
uv run coverage report -m
type_check: ## Check for static typing errors
uv run mypy $(APP_PATH)
output_unset_app_env_sh: ## Unset app environment variables
uf="./unset_env.sh"
echo "Outputing '$${uf}' ..."
printenv | awk -F= '/_API_KEY=/ {print "unset " $$1}' > $$uf
help: ## Displays this message with available recipes
# TODO add stackoverflow source
echo "Usage: make [recipe]"
echo "Recipes:"
awk '/^[a-zA-Z0-9_-]+:.*?##/ {
helpMessage = match($$0, /## (.*)/)
if (helpMessage) {
recipe = $$1
sub(/:/, "", recipe)
printf " \033[36m%-20s\033[0m %s\n", recipe, substr($$0, RSTART + 3, RLENGTH)
}
}' $(MAKEFILE_LIST)
================================================
FILE: mkdocs.yaml
================================================
---
# https://github.com/james-willett/mkdocs-material-youtube-tutorial
# https://mkdocstrings.github.io/recipes/
# site info set in workflow
site_name: ''
site_description: ''
repo_url: ''
edit_uri: edit/main
theme:
name: material
language: en
features:
- content.code.annotation
- content.code.copy
- content.tabs.link
- navigation.footer
- navigation.sections
- navigation.tabs
- navigation.top
- toc.integrate
- search.suggest
- search.highlight
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
# icon: material/brightness-7
icon: material/toggle-switch-off-outline
name: "Toggle Dark Mode"
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
# icon: material/brightness-4
icon: material/toggle-switch
name: "Toggle Light Mode"
nav:
- Home: index.md
- PRD: PRD.md
- User Story: UserStory.md
- Sprint Plan: SprintPlan.md
- Code: docstrings.md
- Change Log: CHANGELOG.md
- License: LICENSE.md
- llms.txt: llms.txt
plugins:
- search:
lang: en
- autorefs
- mkdocstrings:
handlers:
python:
paths: [src]
options:
show_root_heading: true
show_root_full_path: true
show_object_full_path: false
show_root_members_full_path: false
show_category_heading: true
show_submodules: true
markdown_extensions:
- attr_list
- pymdownx.magiclink
- pymdownx.tabbed
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.superfences
- pymdownx.snippets:
check_paths: true
- pymdownx.tasklist:
custom_checkbox: true
- sane_lists
- smarty
- toc:
permalink: true
validation:
links:
not_found: warn
anchors: warn
# builds only if validation succeeds while
# threating warnings as errors
# also checks for broken links
# strict: true
...
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
version = "2.0.0"
name = "Agents-eval"
description = "Assess the effectiveness of agentic AI systems across various use cases focusing on agnostic metrics that measure core agentic capabilities."
authors = [
{name = "qte77", email = "qte@77.gh"}
]
readme = "README.md"
requires-python = "==3.13.*"
license = "bsd-3-clause"
dependencies = [
"agentops>=0.4.14",
"logfire>=3.16.1",
"loguru>=0.7.3",
"pydantic>=2.10.6",
# "pydantic-ai>=0.0.36",
"pydantic-ai-slim[duckduckgo,openai,tavily]>=0.2.12",
"pydantic-settings>=2.9.1",
"scalene>=1.5.51",
"weave>=0.51.49",
]
# [project.urls]
# Documentation = ""
[dependency-groups]
dev = [
# "commitizen>=4.4.1",
"mypy>=1.16.0",
"ruff>=0.11.12",
]
gui = [
"streamlit>=1.43.1",
]
test = [
"pytest>=8.3.4",
"pytest-cov>=6.0.0",
"pytest-asyncio>=0.25.3",
"pytest-bdd>=8.1.0",
"requests>=2.32.3",
"ruff>=0.9.2",
]
docs = [
"griffe>=1.5.1",
"mkdocs>=1.6.1",
"mkdocs-awesome-pages-plugin>=2.9.3",
"mkdocs-gen-files>=0.5.0",
"mkdocs-literate-nav>=0.6.1",
"mkdocs-material>=9.5.44",
"mkdocs-section-index>=0.3.8",
"mkdocstrings[python]>=0.27.0",
]
[tool.uv]
package = true
exclude-newer = "2025-05-31T00:00:00Z"
[tool.hatch.build.targets.wheel]
only-include = ["/README.md"]
[tool.hatch.build.targets.sdist]
include = ["/README.md", "/Makefile", "/tests"]
[tool.logfire]
ignore_no_config=true
send_to_logfire="if-token-present"
[[tool.mypy.overrides]]
module = "agentops"
ignore_missing_imports = true
[tool.ruff]
target-version = "py313"
src = ["src", "tests"]
[tool.ruff.format]
docstring-code-format = true
[tool.ruff.lint]
# ignore = ["E203"] # Whitespace before ':'
unfixable = ["B"]
select = [
# pycodestyle
"E",
# Pyflakes
"F",
# pyupgrade
"UP",
# isort
"I",
]
[tool.ruff.lint.isort]
known-first-party = ["src", "tests"]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.pytest.ini_options]
addopts = "--strict-markers"
# "function", "class", "module", "package", "session"
asyncio_default_fixture_loop_scope = "function"
pythonpath = ["src"]
testpaths = ["tests/"]
[tool.coverage]
[tool.coverage.run]
include = [
"tests/**/*.py",
]
# omit = []
# branch = true
[tool.coverage.report]
show_missing = true
exclude_lines = [
# 'pragma: no cover',
'raise AssertionError',
'raise NotImplementedError',
]
omit = [
'env/*',
'venv/*',
'.venv/*',
'*/virtualenv/*',
'*/virtualenvs/*',
'*/tests/*',
]
[tool.bumpversion]
current_version = "2.0.0"
parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)"
serialize = ["{major}.{minor}.{patch}"]
commit = true
tag = true
allow_dirty = false
ignore_missing_version = false
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
message = "Bump version: {current_version} → {new_version}"
commit_args = ""
[[tool.bumpversion.files]]
filename = "pyproject.toml"
search = 'version = "{current_version}"'
replace = 'version = "{new_version}"'
[[tool.bumpversion.files]]
filename = "src/app/__init__.py"
search = '__version__ = "{current_version}"'
replace = '__version__ = "{new_version}"'
[[tool.bumpversion.files]]
filename = "README.md"
search = "version-{current_version}-58f4c2"
replace = "version-{new_version}-58f4c2"
[[tool.bumpversion.files]]
filename = "CHANGELOG.md"
search = """
## [Unreleased]
"""
replace = """
## [Unreleased]
## [{new_version}] - {now:%Y-%m-%d}
"""
================================================
FILE: uv.lock
================================================
version = 1
revision = 2
requires-python = "==3.13.*"
resolution-markers = [
"sys_platform == 'linux'",
"sys_platform != 'linux'",
]
[options]
exclude-newer = "2025-05-31T00:00:00Z"
[[package]]
name = "agentops"
version = "0.4.14"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-sdk" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "ordered-set" },
{ name = "packaging" },
{ name = "psutil" },
{ name = "pyyaml" },
{ name = "requests" },
{ name = "termcolor" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/25/93c81d2860a122a92091d5e8cd960beafa354bd37d3a796d45db5d2c071d/agentops-0.4.14.tar.gz", hash = "sha256:041cfc93280f6ea4639808d383442a5b70e148c0c357719315b8330768b9a3f0", size = 298334, upload-time = "2025-05-30T20:46:56.56Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f2/3f/fbbb6b6f81f82943e1d19dd38dc7eda566b630b5f2fd02205d0c1a05f491/agentops-0.4.14-py3-none-any.whl", hash = "sha256:5efa6b2c7a0e5b854b2c0aa8248b49e865dac83e5404332bf2eab4d226a0d3bd", size = 214837, upload-time = "2025-05-30T20:46:55.103Z" },
]
[[package]]
name = "agents-eval"
version = "1.1.0"
source = { editable = "." }
dependencies = [
{ name = "agentops" },
{ name = "logfire" },
{ name = "loguru" },
{ name = "pydantic" },
{ name = "pydantic-ai-slim", extra = ["duckduckgo", "openai", "tavily"] },
{ name = "pydantic-settings" },
{ name = "scalene" },
{ name = "weave" },
]
[package.dev-dependencies]
dev = [
{ name = "mypy" },
{ name = "ruff" },
]
docs = [
{ name = "griffe" },
{ name = "mkdocs" },
{ name = "mkdocs-awesome-pages-plugin" },
{ name = "mkdocs-gen-files" },
{ name = "mkdocs-literate-nav" },
{ name = "mkdocs-material" },
{ name = "mkdocs-section-index" },
{ name = "mkdocstrings", extra = ["python"] },
]
gui = [
{ name = "streamlit" },
]
test = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-bdd" },
{ name = "pytest-cov" },
{ name = "requests" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "agentops", specifier = ">=0.4.14" },
{ name = "logfire", specifier = ">=3.16.1" },
{ name = "loguru", specifier = ">=0.7.3" },
{ name = "pydantic", specifier = ">=2.10.6" },
{ name = "pydantic-ai-slim", extras = ["duckduckgo", "openai", "tavily"], specifier = ">=0.2.12" },
{ name = "pydantic-settings", specifier = ">=2.9.1" },
{ name = "scalene", specifier = ">=1.5.51" },
{ name = "weave", specifier = ">=0.51.49" },
]
[package.metadata.requires-dev]
dev = [
{ name = "mypy", specifier = ">=1.16.0" },
{ name = "ruff", specifier = ">=0.11.12" },
]
docs = [
{ name = "griffe", specifier = ">=1.5.1" },
{ name = "mkdocs", specifier = ">=1.6.1" },
{ name = "mkdocs-awesome-pages-plugin", specifier = ">=2.9.3" },
{ name = "mkdocs-gen-files", specifier = ">=0.5.0" },
{ name = "mkdocs-literate-nav", specifier = ">=0.6.1" },
{ name = "mkdocs-material", specifier = ">=9.5.44" },
{ name = "mkdocs-section-index", specifier = ">=0.3.8" },
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.27.0" },
]
gui = [{ name = "streamlit", specifier = ">=1.43.1" }]
test = [
{ name = "pytest", specifier = ">=8.3.4" },
{ name = "pytest-asyncio", specifier = ">=0.25.3" },
{ name = "pytest-bdd", specifier = ">=8.1.0" },
{ name = "pytest-cov", specifier = ">=6.0.0" },
{ name = "requests", specifier = ">=2.32.3" },
{ name = "ruff", specifier = ">=0.9.2" },
]
[[package]]
name = "aiohappyeyeballs"
version = "2.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" },
]
[[package]]
name = "aiohttp"
version = "3.12.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
{ name = "aiosignal" },
{ name = "attrs" },
{ name = "frozenlist" },
{ name = "multidict" },
{ name = "propcache" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/57/77/92b356837fad83cc5709afc0b6e21dce65a413293fed15e6999bafdf36b0/aiohttp-3.12.4.tar.gz", hash = "sha256:d8229b412121160740f5745583c786f3f494d2416fe5f76aabd815da6ab6b193", size = 7781788, upload-time = "2025-05-29T01:36:57.715Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/c5/acc9a65cd92b263050dcc2986e2aee598fc6f3e0b251c9ce7138bf9f387c/aiohttp-3.12.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:789e9ddd591a3161a4e222942e10036d3fb4477464d9a454be2613966b0bce6b", size = 687716, upload-time = "2025-05-29T01:35:37.749Z" },
{ url = "https://files.pythonhosted.org/packages/3b/8b/c36084efb762c8b388e35b564c5c87d287e4d24a77422f7570e36f8195f4/aiohttp-3.12.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8eb37972e6aebe4cab53b0008c4ca7cd412f3f01872f255763ac4bb0ce253d83", size = 465372, upload-time = "2025-05-29T01:35:39.701Z" },
{ url = "https://files.pythonhosted.org/packages/d0/d5/c390226c7f0a2a0e4a7477fb293d311157092231fdb7ab79eb8ad325b3b0/aiohttp-3.12.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ca6af3e929de2c2d3272680437ee5b1e32fa4ac1fb9dfdcc06f5441542d06110", size = 457673, upload-time = "2025-05-29T01:35:42.458Z" },
{ url = "https://files.pythonhosted.org/packages/bc/1a/fdf6ade28154d249b605a6e85f7eb424363618ebcb35f93a7f837fd1f9c9/aiohttp-3.12.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a9b8b482be5c81ceee91fecead2c82b7bec7cfb8b81c0389d6fa4cd82f3bb53", size = 1696485, upload-time = "2025-05-29T01:35:44.489Z" },
{ url = "https://files.pythonhosted.org/packages/71/02/1670b62c82d6e19c77df235b96a56ec055eb40d63b6feff93146544d0224/aiohttp-3.12.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b3f9d7c7486f28cc0fd6bfe5b9accc4ecfe3d4f0471ec53e08aa610e5642dbf3", size = 1677750, upload-time = "2025-05-29T01:35:47.567Z" },
{ url = "https://files.pythonhosted.org/packages/af/eb/75c9863328a9f1f7200ebadf0fefec3a50a2f31e9ccf489faf9c132b87ad/aiohttp-3.12.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42986c6fc949926bcf0928b5440e6adf20b9a14c04dd9ea5e3ba9c7bbd4433a", size = 1729821, upload-time = "2025-05-29T01:35:49.98Z" },
{ url = "https://files.pythonhosted.org/packages/8a/ac/75ef05d10aae033d9bc87d0eea35d904e505c0a7a5d7c7838d1d8b63e954/aiohttp-3.12.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58dded319d52e63ea3c40dbae3f44c1264fa4bb692845b7ff8ce1ddc9319fce3", size = 1779191, upload-time = "2025-05-29T01:35:52.257Z" },
{ url = "https://files.pythonhosted.org/packages/b3/5e/36e5957a073dddb69ed37e5ffa8581548d5d7b9d00daa4ba98fff6c85219/aiohttp-3.12.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1102668bf8c4b744528ef0b5bdaeeb17930832653d1ed9558ab59a0fae91dcf9", size = 1701521, upload-time = "2025-05-29T01:35:54.413Z" },
{ url = "https://files.pythonhosted.org/packages/4e/98/16c3dc7c2534d5109f02da5c88e34e327d8ceddb9b976b4861d787461a59/aiohttp-3.12.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e46c5ad27747416ef0a914da2ad175d9066d8d011960f7b66c9b4f02ef7acfcc", size = 1615227, upload-time = "2025-05-29T01:35:56.595Z" },
{ url = "https://files.pythonhosted.org/packages/74/cb/87eaf79aa41a6bc99c3dd1219caf190f282b5742647bf3abb7b66b7eb221/aiohttp-3.12.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cbcde696c4d4d07b616e10f942e183f90a86ff65e27a03c338067deb1204b148", size = 1668248, upload-time = "2025-05-29T01:36:00.295Z" },
{ url = "https://files.pythonhosted.org/packages/d6/04/2ff57af92f76b0973652710bf9a539d66eb78b4cddace90fc39a5b04bdd7/aiohttp-3.12.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:002e027d4840cb187e5ba6889043e1e90ed114ef8e798133d51db834696a6de2", size = 1699915, upload-time = "2025-05-29T01:36:02.599Z" },
{ url = "https://files.pythonhosted.org/packages/15/d6/0d9916e03cebd697b3c4fc48998733188e8b834368e727b46650a3a1b005/aiohttp-3.12.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cf12c660159897cebdd3ab377550b3563218286f33a57f56753018b1897796ae", size = 1642508, upload-time = "2025-05-29T01:36:05.236Z" },
{ url = "https://files.pythonhosted.org/packages/83/b4/9cf887a3d2cf58828ac6a076d240171d6196dcf7d1edafcb005103f457fb/aiohttp-3.12.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c9e3db6a3c3e53e48b3324eb40e7c5da2a4c78cdcd3ac4e7d7945876dd421de1", size = 1718642, upload-time = "2025-05-29T01:36:07.362Z" },
{ url = "https://files.pythonhosted.org/packages/e5/b0/266567f3c5232e211f1c9bea121a05d115a3f7761c7029ff4ee4f88e6fba/aiohttp-3.12.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e10365dcf61a7c5ed9287c4e20edc0d7a6cc09faf042d7dc570f16ed3291c680", size = 1752113, upload-time = "2025-05-29T01:36:09.519Z" },
{ url = "https://files.pythonhosted.org/packages/61/f9/58b3ce002d1b0b3630ccd02ecbfc6932d00242eb40182e76a65ddbf6ec26/aiohttp-3.12.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c20421e165410bb632f64c5693b1f69e6911dbde197fa0dcd3a0c65d505f776b", size = 1701004, upload-time = "2025-05-29T01:36:11.692Z" },
{ url = "https://files.pythonhosted.org/packages/ee/7c/c1a5e7704fef91f115bd399e47b9613cf11c8caec041a326e966f190c994/aiohttp-3.12.4-cp313-cp313-win32.whl", hash = "sha256:834a2f08eb800af07066af9f26eda4c2d6f7fe0737a3c0aef448f1ba8132fed9", size = 413468, upload-time = "2025-05-29T01:36:13.876Z" },
{ url = "https://files.pythonhosted.org/packages/65/31/e252246332a12abf17f66c8f8360730a5a3a1dd354ca48ccfb90bbb122db/aiohttp-3.12.4-cp313-cp313-win_amd64.whl", hash = "sha256:4c78018c4e8118efac767d5d91c3565919c7e021762c4644198ec5b8d426a071", size = 439411, upload-time = "2025-05-29T01:36:16.365Z" },
]
[[package]]
name = "aiosignal"
version = "1.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "frozenlist" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424, upload-time = "2024-12-13T17:10:40.86Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" },
]
[[package]]
name = "altair"
version = "5.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinja2" },
{ name = "jsonschema" },
{ name = "narwhals" },
{ name = "packaging" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/16/b1/f2969c7bdb8ad8bbdda031687defdce2c19afba2aa2c8e1d2a17f78376d8/altair-5.5.0.tar.gz", hash = "sha256:d960ebe6178c56de3855a68c47b516be38640b73fb3b5111c2a9ca90546dd73d", size = 705305, upload-time = "2024-11-23T23:39:58.542Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/f3/0b6ced594e51cc95d8c1fc1640d3623770d01e4969d29c0bd09945fafefa/altair-5.5.0-py3-none-any.whl", hash = "sha256:91a310b926508d560fe0148d02a194f38b824122641ef528113d029fcd129f8c", size = 731200, upload-time = "2024-11-23T23:39:56.4Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "anyio"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
]
[[package]]
name = "attrs"
version = "25.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
]
[[package]]
name = "babel"
version = "2.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
]
[[package]]
name = "backoff"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" },
]
[[package]]
name = "backrefs"
version = "5.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994, upload-time = "2025-02-25T18:15:32.003Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337, upload-time = "2025-02-25T16:53:14.607Z" },
{ url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142, upload-time = "2025-02-25T16:53:17.266Z" },
{ url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021, upload-time = "2025-02-25T16:53:26.378Z" },
{ url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915, upload-time = "2025-02-25T16:53:28.167Z" },
{ url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336, upload-time = "2025-02-25T16:53:29.858Z" },
]
[[package]]
name = "blinker"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
]
[[package]]
name = "bracex"
version = "2.5.post1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/6c/57418c4404cd22fe6275b8301ca2b46a8cdaa8157938017a9ae0b3edf363/bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6", size = 26641, upload-time = "2024-09-28T21:41:22.017Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/02/8db98cdc1a58e0abd6716d5e63244658e6e63513c65f469f34b6f1053fd0/bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6", size = 11558, upload-time = "2024-09-28T21:41:21.016Z" },
]
[[package]]
name = "cachetools"
version = "5.5.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" },
]
[[package]]
name = "certifi"
version = "2025.4.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
{ url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
{ url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
{ url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
{ url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
{ url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
{ url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
{ url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
{ url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
{ url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
]
[[package]]
name = "click"
version = "8.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
]
[[package]]
name = "cloudpickle"
version = "3.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113, upload-time = "2025-01-14T17:02:05.085Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992, upload-time = "2025-01-14T17:02:02.417Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "coverage"
version = "7.8.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759, upload-time = "2025-05-23T11:39:57.856Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/93/eb6400a745ad3b265bac36e8077fdffcf0268bdbbb6c02b7220b624c9b31/coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca", size = 211898, upload-time = "2025-05-23T11:38:49.066Z" },
{ url = "https://files.pythonhosted.org/packages/1b/7c/bdbf113f92683024406a1cd226a199e4200a2001fc85d6a6e7e299e60253/coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d", size = 212171, upload-time = "2025-05-23T11:38:51.207Z" },
{ url = "https://files.pythonhosted.org/packages/91/22/594513f9541a6b88eb0dba4d5da7d71596dadef6b17a12dc2c0e859818a9/coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85", size = 245564, upload-time = "2025-05-23T11:38:52.857Z" },
{ url = "https://files.pythonhosted.org/packages/1f/f4/2860fd6abeebd9f2efcfe0fd376226938f22afc80c1943f363cd3c28421f/coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257", size = 242719, upload-time = "2025-05-23T11:38:54.529Z" },
{ url = "https://files.pythonhosted.org/packages/89/60/f5f50f61b6332451520e6cdc2401700c48310c64bc2dd34027a47d6ab4ca/coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108", size = 244634, upload-time = "2025-05-23T11:38:57.326Z" },
{ url = "https://files.pythonhosted.org/packages/3b/70/7f4e919039ab7d944276c446b603eea84da29ebcf20984fb1fdf6e602028/coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0", size = 244824, upload-time = "2025-05-23T11:38:59.421Z" },
{ url = "https://files.pythonhosted.org/packages/26/45/36297a4c0cea4de2b2c442fe32f60c3991056c59cdc3cdd5346fbb995c97/coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050", size = 242872, upload-time = "2025-05-23T11:39:01.049Z" },
{ url = "https://files.pythonhosted.org/packages/a4/71/e041f1b9420f7b786b1367fa2a375703889ef376e0d48de9f5723fb35f11/coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48", size = 244179, upload-time = "2025-05-23T11:39:02.709Z" },
{ url = "https://files.pythonhosted.org/packages/bd/db/3c2bf49bdc9de76acf2491fc03130c4ffc51469ce2f6889d2640eb563d77/coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7", size = 214393, upload-time = "2025-05-23T11:39:05.457Z" },
{ url = "https://files.pythonhosted.org/packages/c6/dc/947e75d47ebbb4b02d8babb1fad4ad381410d5bc9da7cfca80b7565ef401/coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3", size = 215194, upload-time = "2025-05-23T11:39:07.171Z" },
{ url = "https://files.pythonhosted.org/packages/90/31/a980f7df8a37eaf0dc60f932507fda9656b3a03f0abf188474a0ea188d6d/coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7", size = 213580, upload-time = "2025-05-23T11:39:08.862Z" },
{ url = "https://files.pythonhosted.org/packages/8a/6a/25a37dd90f6c95f59355629417ebcb74e1c34e38bb1eddf6ca9b38b0fc53/coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008", size = 212734, upload-time = "2025-05-23T11:39:11.109Z" },
{ url = "https://files.pythonhosted.org/packages/36/8b/3a728b3118988725f40950931abb09cd7f43b3c740f4640a59f1db60e372/coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36", size = 212959, upload-time = "2025-05-23T11:39:12.751Z" },
{ url = "https://files.pythonhosted.org/packages/53/3c/212d94e6add3a3c3f412d664aee452045ca17a066def8b9421673e9482c4/coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46", size = 257024, upload-time = "2025-05-23T11:39:15.569Z" },
{ url = "https://files.pythonhosted.org/packages/a4/40/afc03f0883b1e51bbe804707aae62e29c4e8c8bbc365c75e3e4ddeee9ead/coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be", size = 252867, upload-time = "2025-05-23T11:39:17.64Z" },
{ url = "https://files.pythonhosted.org/packages/18/a2/3699190e927b9439c6ded4998941a3c1d6fa99e14cb28d8536729537e307/coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740", size = 255096, upload-time = "2025-05-23T11:39:19.328Z" },
{ url = "https://files.pythonhosted.org/packages/b4/06/16e3598b9466456b718eb3e789457d1a5b8bfb22e23b6e8bbc307df5daf0/coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625", size = 256276, upload-time = "2025-05-23T11:39:21.077Z" },
{ url = "https://files.pythonhosted.org/packages/a7/d5/4b5a120d5d0223050a53d2783c049c311eea1709fa9de12d1c358e18b707/coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b", size = 254478, upload-time = "2025-05-23T11:39:22.838Z" },
{ url = "https://files.pythonhosted.org/packages/ba/85/f9ecdb910ecdb282b121bfcaa32fa8ee8cbd7699f83330ee13ff9bbf1a85/coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199", size = 255255, upload-time = "2025-05-23T11:39:24.644Z" },
{ url = "https://files.pythonhosted.org/packages/50/63/2d624ac7d7ccd4ebbd3c6a9eba9d7fc4491a1226071360d59dd84928ccb2/coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8", size = 215109, upload-time = "2025-05-23T11:39:26.722Z" },
{ url = "https://files.pythonhosted.org/packages/22/5e/7053b71462e970e869111c1853afd642212568a350eba796deefdfbd0770/coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d", size = 216268, upload-time = "2025-05-23T11:39:28.429Z" },
{ url = "https://files.pythonhosted.org/packages/07/69/afa41aa34147655543dbe96994f8a246daf94b361ccf5edfd5df62ce066a/coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b", size = 214071, upload-time = "2025-05-23T11:39:30.55Z" },
{ url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" },
]
[[package]]
name = "deprecated"
version = "1.2.18"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" },
]
[[package]]
name = "diskcache"
version = "5.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" },
]
[[package]]
name = "distro"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
]
[[package]]
name = "docker-pycreds"
version = "0.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c5/e6/d1f6c00b7221e2d7c4b470132c931325c8b22c51ca62417e300f5ce16009/docker-pycreds-0.4.0.tar.gz", hash = "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4", size = 8754, upload-time = "2018-11-29T03:26:50.996Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f5/e8/f6bd1eee09314e7e6dee49cbe2c5e22314ccdb38db16c9fc72d2fa80d054/docker_pycreds-0.4.0-py2.py3-none-any.whl", hash = "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49", size = 8982, upload-time = "2018-11-29T03:26:49.575Z" },
]
[[package]]
name = "duckduckgo-search"
version = "8.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "lxml" },
{ name = "primp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/c0/e18c2148d33a9d87f6a0cc00acba30b4e547be0f8cb85ccb313a6e8fbac7/duckduckgo_search-8.0.2.tar.gz", hash = "sha256:3109a99967b29cab8862823bbe320d140d5c792415de851b9d6288de2311b3ec", size = 21807, upload-time = "2025-05-15T08:43:25.311Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/6c/e36d22e76f4aa4e1ea7ea9b443bd49b5ffd2f13d430840f47e35284f797a/duckduckgo_search-8.0.2-py3-none-any.whl", hash = "sha256:b5ff8b6b8f169b8e1b15a788a5749aa900ebcefd6e1ab485787582f8d5b4f1ef", size = 18184, upload-time = "2025-05-15T08:43:23.713Z" },
]
[[package]]
name = "emoji"
version = "2.14.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cb/7d/01cddcbb6f5cc0ba72e00ddf9b1fa206c802d557fd0a20b18e130edf1336/emoji-2.14.1.tar.gz", hash = "sha256:f8c50043d79a2c1410ebfae833ae1868d5941a67a6cd4d18377e2eb0bd79346b", size = 597182, upload-time = "2025-01-16T06:31:24.983Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/91/db/a0335710caaa6d0aebdaa65ad4df789c15d89b7babd9a30277838a7d9aac/emoji-2.14.1-py3-none-any.whl", hash = "sha256:35a8a486c1460addb1499e3bf7929d3889b2e2841a57401903699fef595e942b", size = 590617, upload-time = "2025-01-16T06:31:23.526Z" },
]
[[package]]
name = "eval-type-backport"
version = "0.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" },
]
[[package]]
name = "executing"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" },
]
[[package]]
name = "frozenlist"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/f4/d744cba2da59b5c1d88823cf9e8a6c74e4659e2b27604ed973be2a0bf5ab/frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68", size = 42831, upload-time = "2025-04-17T22:38:53.099Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/e5/04c7090c514d96ca00887932417f04343ab94904a56ab7f57861bf63652d/frozenlist-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1d7fb014fe0fbfee3efd6a94fc635aeaa68e5e1720fe9e57357f2e2c6e1a647e", size = 158182, upload-time = "2025-04-17T22:37:16.837Z" },
{ url = "https://files.pythonhosted.org/packages/e9/8f/60d0555c61eec855783a6356268314d204137f5e0c53b59ae2fc28938c99/frozenlist-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01bcaa305a0fdad12745502bfd16a1c75b14558dabae226852f9159364573117", size = 122838, upload-time = "2025-04-17T22:37:18.352Z" },
{ url = "https://files.pythonhosted.org/packages/5a/a7/d0ec890e3665b4b3b7c05dc80e477ed8dc2e2e77719368e78e2cd9fec9c8/frozenlist-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b314faa3051a6d45da196a2c495e922f987dc848e967d8cfeaee8a0328b1cd4", size = 120980, upload-time = "2025-04-17T22:37:19.857Z" },
{ url = "https://files.pythonhosted.org/packages/cc/19/9b355a5e7a8eba903a008579964192c3e427444752f20b2144b10bb336df/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da62fecac21a3ee10463d153549d8db87549a5e77eefb8c91ac84bb42bb1e4e3", size = 305463, upload-time = "2025-04-17T22:37:21.328Z" },
{ url = "https://files.pythonhosted.org/packages/9c/8d/5b4c758c2550131d66935ef2fa700ada2461c08866aef4229ae1554b93ca/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1eb89bf3454e2132e046f9599fbcf0a4483ed43b40f545551a39316d0201cd1", size = 297985, upload-time = "2025-04-17T22:37:23.55Z" },
{ url = "https://files.pythonhosted.org/packages/48/2c/537ec09e032b5865715726b2d1d9813e6589b571d34d01550c7aeaad7e53/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18689b40cb3936acd971f663ccb8e2589c45db5e2c5f07e0ec6207664029a9c", size = 311188, upload-time = "2025-04-17T22:37:25.221Z" },
{ url = "https://files.pythonhosted.org/packages/31/2f/1aa74b33f74d54817055de9a4961eff798f066cdc6f67591905d4fc82a84/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e67ddb0749ed066b1a03fba812e2dcae791dd50e5da03be50b6a14d0c1a9ee45", size = 311874, upload-time = "2025-04-17T22:37:26.791Z" },
{ url = "https://files.pythonhosted.org/packages/bf/f0/cfec18838f13ebf4b37cfebc8649db5ea71a1b25dacd691444a10729776c/frozenlist-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc5e64626e6682638d6e44398c9baf1d6ce6bc236d40b4b57255c9d3f9761f1f", size = 291897, upload-time = "2025-04-17T22:37:28.958Z" },
{ url = "https://files.pythonhosted.org/packages/ea/a5/deb39325cbbea6cd0a46db8ccd76150ae2fcbe60d63243d9df4a0b8c3205/frozenlist-1.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:437cfd39564744ae32ad5929e55b18ebd88817f9180e4cc05e7d53b75f79ce85", size = 305799, upload-time = "2025-04-17T22:37:30.889Z" },
{ url = "https://files.pythonhosted.org/packages/78/22/6ddec55c5243a59f605e4280f10cee8c95a449f81e40117163383829c241/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62dd7df78e74d924952e2feb7357d826af8d2f307557a779d14ddf94d7311be8", size = 302804, upload-time = "2025-04-17T22:37:32.489Z" },
{ url = "https://files.pythonhosted.org/packages/5d/b7/d9ca9bab87f28855063c4d202936800219e39db9e46f9fb004d521152623/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a66781d7e4cddcbbcfd64de3d41a61d6bdde370fc2e38623f30b2bd539e84a9f", size = 316404, upload-time = "2025-04-17T22:37:34.59Z" },
{ url = "https://files.pythonhosted.org/packages/a6/3a/1255305db7874d0b9eddb4fe4a27469e1fb63720f1fc6d325a5118492d18/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:482fe06e9a3fffbcd41950f9d890034b4a54395c60b5e61fae875d37a699813f", size = 295572, upload-time = "2025-04-17T22:37:36.337Z" },
{ url = "https://files.pythonhosted.org/packages/2a/f2/8d38eeee39a0e3a91b75867cc102159ecccf441deb6ddf67be96d3410b84/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e4f9373c500dfc02feea39f7a56e4f543e670212102cc2eeb51d3a99c7ffbde6", size = 307601, upload-time = "2025-04-17T22:37:37.923Z" },
{ url = "https://files.pythonhosted.org/packages/38/04/80ec8e6b92f61ef085422d7b196822820404f940950dde5b2e367bede8bc/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e69bb81de06827147b7bfbaeb284d85219fa92d9f097e32cc73675f279d70188", size = 314232, upload-time = "2025-04-17T22:37:39.669Z" },
{ url = "https://files.pythonhosted.org/packages/3a/58/93b41fb23e75f38f453ae92a2f987274c64637c450285577bd81c599b715/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7613d9977d2ab4a9141dde4a149f4357e4065949674c5649f920fec86ecb393e", size = 308187, upload-time = "2025-04-17T22:37:41.662Z" },
{ url = "https://files.pythonhosted.org/packages/6a/a2/e64df5c5aa36ab3dee5a40d254f3e471bb0603c225f81664267281c46a2d/frozenlist-1.6.0-cp313-cp313-win32.whl", hash = "sha256:4def87ef6d90429f777c9d9de3961679abf938cb6b7b63d4a7eb8a268babfce4", size = 114772, upload-time = "2025-04-17T22:37:43.132Z" },
{ url = "https://files.pythonhosted.org/packages/a0/77/fead27441e749b2d574bb73d693530d59d520d4b9e9679b8e3cb779d37f2/frozenlist-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:37a8a52c3dfff01515e9bbbee0e6063181362f9de3db2ccf9bc96189b557cbfd", size = 119847, upload-time = "2025-04-17T22:37:45.118Z" },
{ url = "https://files.pythonhosted.org/packages/df/bd/cc6d934991c1e5d9cafda83dfdc52f987c7b28343686aef2e58a9cf89f20/frozenlist-1.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46138f5a0773d064ff663d273b309b696293d7a7c00a0994c5c13a5078134b64", size = 174937, upload-time = "2025-04-17T22:37:46.635Z" },
{ url = "https://files.pythonhosted.org/packages/f2/a2/daf945f335abdbfdd5993e9dc348ef4507436936ab3c26d7cfe72f4843bf/frozenlist-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f88bc0a2b9c2a835cb888b32246c27cdab5740059fb3688852bf91e915399b91", size = 136029, upload-time = "2025-04-17T22:37:48.192Z" },
{ url = "https://files.pythonhosted.org/packages/51/65/4c3145f237a31247c3429e1c94c384d053f69b52110a0d04bfc8afc55fb2/frozenlist-1.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:777704c1d7655b802c7850255639672e90e81ad6fa42b99ce5ed3fbf45e338dd", size = 134831, upload-time = "2025-04-17T22:37:50.485Z" },
{ url = "https://files.pythonhosted.org/packages/77/38/03d316507d8dea84dfb99bdd515ea245628af964b2bf57759e3c9205cc5e/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ef8d41764c7de0dcdaf64f733a27352248493a85a80661f3c678acd27e31f2", size = 392981, upload-time = "2025-04-17T22:37:52.558Z" },
{ url = "https://files.pythonhosted.org/packages/37/02/46285ef9828f318ba400a51d5bb616ded38db8466836a9cfa39f3903260b/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:da5cb36623f2b846fb25009d9d9215322318ff1c63403075f812b3b2876c8506", size = 371999, upload-time = "2025-04-17T22:37:54.092Z" },
{ url = "https://files.pythonhosted.org/packages/0d/64/1212fea37a112c3c5c05bfb5f0a81af4836ce349e69be75af93f99644da9/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbb56587a16cf0fb8acd19e90ff9924979ac1431baea8681712716a8337577b0", size = 392200, upload-time = "2025-04-17T22:37:55.951Z" },
{ url = "https://files.pythonhosted.org/packages/81/ce/9a6ea1763e3366e44a5208f76bf37c76c5da570772375e4d0be85180e588/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6154c3ba59cda3f954c6333025369e42c3acd0c6e8b6ce31eb5c5b8116c07e0", size = 390134, upload-time = "2025-04-17T22:37:57.633Z" },
{ url = "https://files.pythonhosted.org/packages/bc/36/939738b0b495b2c6d0c39ba51563e453232813042a8d908b8f9544296c29/frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e8246877afa3f1ae5c979fe85f567d220f86a50dc6c493b9b7d8191181ae01e", size = 365208, upload-time = "2025-04-17T22:37:59.742Z" },
{ url = "https://files.pythonhosted.org/packages/b4/8b/939e62e93c63409949c25220d1ba8e88e3960f8ef6a8d9ede8f94b459d27/frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0f6cce16306d2e117cf9db71ab3a9e8878a28176aeaf0dbe35248d97b28d0c", size = 385548, upload-time = "2025-04-17T22:38:01.416Z" },
{ url = "https://files.pythonhosted.org/packages/62/38/22d2873c90102e06a7c5a3a5b82ca47e393c6079413e8a75c72bff067fa8/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1b8e8cd8032ba266f91136d7105706ad57770f3522eac4a111d77ac126a25a9b", size = 391123, upload-time = "2025-04-17T22:38:03.049Z" },
{ url = "https://files.pythonhosted.org/packages/44/78/63aaaf533ee0701549500f6d819be092c6065cb5c577edb70c09df74d5d0/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e2ada1d8515d3ea5378c018a5f6d14b4994d4036591a52ceaf1a1549dec8e1ad", size = 394199, upload-time = "2025-04-17T22:38:04.776Z" },
{ url = "https://files.pythonhosted.org/packages/54/45/71a6b48981d429e8fbcc08454dc99c4c2639865a646d549812883e9c9dd3/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:cdb2c7f071e4026c19a3e32b93a09e59b12000751fc9b0b7758da899e657d215", size = 373854, upload-time = "2025-04-17T22:38:06.576Z" },
{ url = "https://files.pythonhosted.org/packages/3f/f3/dbf2a5e11736ea81a66e37288bf9f881143a7822b288a992579ba1b4204d/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:03572933a1969a6d6ab509d509e5af82ef80d4a5d4e1e9f2e1cdd22c77a3f4d2", size = 395412, upload-time = "2025-04-17T22:38:08.197Z" },
{ url = "https://files.pythonhosted.org/packages/b3/f1/c63166806b331f05104d8ea385c4acd511598568b1f3e4e8297ca54f2676/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:77effc978947548b676c54bbd6a08992759ea6f410d4987d69feea9cd0919911", size = 394936, upload-time = "2025-04-17T22:38:10.056Z" },
{ url = "https://files.pythonhosted.org/packages/ef/ea/4f3e69e179a430473eaa1a75ff986526571215fefc6b9281cdc1f09a4eb8/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a2bda8be77660ad4089caf2223fdbd6db1858462c4b85b67fbfa22102021e497", size = 391459, upload-time = "2025-04-17T22:38:11.826Z" },
{ url = "https://files.pythonhosted.org/packages/d3/c3/0fc2c97dea550df9afd072a37c1e95421652e3206bbeaa02378b24c2b480/frozenlist-1.6.0-cp313-cp313t-win32.whl", hash = "sha256:a4d96dc5bcdbd834ec6b0f91027817214216b5b30316494d2b1aebffb87c534f", size = 128797, upload-time = "2025-04-17T22:38:14.013Z" },
{ url = "https://files.pythonhosted.org/packages/ae/f5/79c9320c5656b1965634fe4be9c82b12a3305bdbc58ad9cb941131107b20/frozenlist-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e18036cb4caa17ea151fd5f3d70be9d354c99eb8cf817a3ccde8a7873b074348", size = 134709, upload-time = "2025-04-17T22:38:15.551Z" },
{ url = "https://files.pythonhosted.org/packages/71/3e/b04a0adda73bd52b390d730071c0d577073d3d26740ee1bad25c3ad0f37b/frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191", size = 12404, upload-time = "2025-04-17T22:38:51.668Z" },
]
[[package]]
name = "gherkin-official"
version = "29.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/d8/7a28537efd7638448f7512a0cce011d4e3bf1c7f4794ad4e9c87b3f1e98e/gherkin_official-29.0.0.tar.gz", hash = "sha256:dbea32561158f02280d7579d179b019160d072ce083197625e2f80a6776bb9eb", size = 32303, upload-time = "2024-08-12T09:41:09.595Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/fc/b86c22ad3b18d8324a9d6fe5a3b55403291d2bf7572ba6a16efa5aa88059/gherkin_official-29.0.0-py3-none-any.whl", hash = "sha256:26967b0d537a302119066742669e0e8b663e632769330be675457ae993e1d1bc", size = 37085, upload-time = "2024-08-12T09:41:07.954Z" },
]
[[package]]
name = "ghp-import"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "python-dateutil" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" },
]
[[package]]
name = "gitdb"
version = "4.0.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "smmap" },
]
sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
]
[[package]]
name = "gitpython"
version = "3.1.44"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "gitdb" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196, upload-time = "2025-01-02T07:32:43.59Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" },
]
[[package]]
name = "googleapis-common-protos"
version = "1.70.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" },
]
[[package]]
name = "gql"
version = "3.5.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "backoff" },
{ name = "graphql-core" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/34/ed/44ffd30b06b3afc8274ee2f38c3c1b61fe4740bf03d92083e43d2c17ac77/gql-3.5.3.tar.gz", hash = "sha256:393b8c049d58e0d2f5461b9d738a2b5f904186a40395500b4a84dd092d56e42b", size = 180504, upload-time = "2025-05-20T12:34:08.954Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/50/2f4e99b216821ac921dbebf91c644ba95818f5d07857acadee17220221f3/gql-3.5.3-py2.py3-none-any.whl", hash = "sha256:e1fcbde2893fcafdd28114ece87ff47f1cc339a31db271fc4e1d528f5a1d4fbc", size = 74348, upload-time = "2025-05-20T12:34:07.687Z" },
]
[package.optional-dependencies]
aiohttp = [
{ name = "aiohttp" },
]
requests = [
{ name = "requests" },
{ name = "requests-toolbelt" },
]
[[package]]
name = "graphql-core"
version = "3.2.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c4/16/7574029da84834349b60ed71614d66ca3afe46e9bf9c7b9562102acb7d4f/graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab", size = 505353, upload-time = "2025-01-26T16:36:27.374Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/4f/7297663840621022bc73c22d7d9d80dbc78b4db6297f764b545cd5dd462d/graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f", size = 203416, upload-time = "2025-01-26T16:36:24.868Z" },
]
[[package]]
name = "griffe"
version = "1.7.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a9/3e/5aa9a61f7c3c47b0b52a1d930302992229d191bf4bc76447b324b731510a/griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b", size = 395137, upload-time = "2025-04-23T11:29:09.147Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/c6/5c20af38c2a57c15d87f7f38bee77d63c1d2a3689f74fefaf35915dd12b2/griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75", size = 129303, upload-time = "2025-04-23T11:29:07.145Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "importlib-metadata"
version = "8.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767, upload-time = "2025-01-20T22:21:30.429Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971, upload-time = "2025-01-20T22:21:29.177Z" },
]
[[package]]
name = "iniconfig"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "jiter"
version = "0.10.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" },
{ url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" },
{ url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" },
{ url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" },
{ url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" },
{ url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" },
{ url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" },
{ url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" },
{ url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" },
{ url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" },
{ url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" },
{ url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" },
{ url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" },
{ url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" },
{ url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" },
]
[[package]]
name = "jsonschema"
version = "4.24.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "jsonschema-specifications" },
{ name = "referencing" },
{ name = "rpds-py" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "referencing" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" },
]
[[package]]
name = "logfire"
version = "3.16.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "executing" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-sdk" },
{ name = "protobuf" },
{ name = "rich" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e7/1d/ec4d24a12b3e96e19e9874170c63ebdd2bcc118370fb60dd86a88b758f0e/logfire-3.16.1.tar.gz", hash = "sha256:de91504243737cf161d4704a9980fbe3640f1e20c6df5f1948cb1cc559356a28", size = 477077, upload-time = "2025-05-26T12:08:47.597Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/1b/f0a5677c470184a342987ee6cfda539fdc0e8cfaffc3808c24f64f203d43/logfire-3.16.1-py3-none-any.whl", hash = "sha256:0622089e776294f54de31ede0c6cb23d4891f8f7e4bd4dbd89ee5fed8eb8c27f", size = 194633, upload-time = "2025-05-26T12:08:43.952Z" },
]
[[package]]
name = "logfire-api"
version = "3.16.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/86/d5/1fde2adc24a2535faee363cdb5a8a15fe0c0cc542d1f731c37cd4689e258/logfire_api-3.16.1.tar.gz", hash = "sha256:b624927dd2da1f3ce7031434a3db61ecbbfecb94d1e2636b9eb616adde0dfeee", size = 48243, upload-time = "2025-05-26T12:08:49.334Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ee/a4/8200b279a44990ad9d4233f05c2bc4029ba02f25de51fee61f51bc5c5a98/logfire_api-3.16.1-py3-none-any.whl", hash = "sha256:da0d232fffadded58339b91a5a1b5f45c4bd05a62e9241c973de9c5bebe34521", size = 80121, upload-time = "2025-05-26T12:08:46.108Z" },
]
[[package]]
name = "loguru"
version = "0.7.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
]
[[package]]
name = "lxml"
version = "5.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" },
{ url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" },
{ url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" },
{ url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" },
{ url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" },
{ url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" },
{ url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" },
{ url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" },
{ url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" },
{ url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" },
{ url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" },
{ url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" },
{ url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" },
{ url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" },
{ url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" },
{ url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" },
{ url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" },
]
[[package]]
name = "mako"
version = "1.3.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" },
]
[[package]]
name = "markdown"
version = "3.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f", size = 360906, upload-time = "2025-04-11T14:42:50.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/3f/afe76f8e2246ffbc867440cbcf90525264df0e658f8a5ca1f872b3f6192a/markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc", size = 106210, upload-time = "2025-04-11T14:42:49.178Z" },
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "mergedeep"
version = "1.3.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" },
]
[[package]]
name = "mkdocs"
version = "1.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "ghp-import" },
{ name = "jinja2" },
{ name = "markdown" },
{ name = "markupsafe" },
{ name = "mergedeep" },
{ name = "mkdocs-get-deps" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "pyyaml" },
{ name = "pyyaml-env-tag" },
{ name = "watchdog" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" },
]
[[package]]
name = "mkdocs-autorefs"
version = "1.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown" },
{ name = "markupsafe" },
{ name = "mkdocs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/47/0c/c9826f35b99c67fa3a7cddfa094c1a6c43fafde558c309c6e4403e5b37dc/mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749", size = 54961, upload-time = "2025-05-20T13:09:09.886Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/dc/fc063b78f4b769d1956319351704e23ebeba1e9e1d6a41b4b602325fd7e4/mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13", size = 24969, upload-time = "2025-05-20T13:09:08.237Z" },
]
[[package]]
name = "mkdocs-awesome-pages-plugin"
version = "2.10.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mkdocs" },
{ name = "natsort" },
{ name = "wcmatch" },
]
sdist = { url = "https://files.pythonhosted.org/packages/92/e8/6ae9c18d8174a5d74ce4ade7a7f4c350955063968bc41ff1e5833cff4a2b/mkdocs_awesome_pages_plugin-2.10.1.tar.gz", hash = "sha256:cda2cb88c937ada81a4785225f20ef77ce532762f4500120b67a1433c1cdbb2f", size = 16303, upload-time = "2024-12-22T21:13:49.19Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/61/19fc1e9c579dbfd4e8a402748f1d63cab7aabe8f8d91eb0235e45b32d040/mkdocs_awesome_pages_plugin-2.10.1-py3-none-any.whl", hash = "sha256:c6939dbea37383fc3cf8c0a4e892144ec3d2f8a585e16fdc966b34e7c97042a7", size = 15118, upload-time = "2024-12-22T21:13:46.945Z" },
]
[[package]]
name = "mkdocs-gen-files"
version = "0.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mkdocs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/48/85/2d634462fd59136197d3126ca431ffb666f412e3db38fd5ce3a60566303e/mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc", size = 7539, upload-time = "2023-04-27T19:48:04.894Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e7/0f/1e55b3fd490ad2cecb6e7b31892d27cb9fc4218ec1dab780440ba8579e74/mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea", size = 8380, upload-time = "2023-04-27T19:48:07.059Z" },
]
[[package]]
name = "mkdocs-get-deps"
version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mergedeep" },
{ name = "platformdirs" },
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" },
]
[[package]]
name = "mkdocs-literate-nav"
version = "0.6.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mkdocs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f6/5f/99aa379b305cd1c2084d42db3d26f6de0ea9bf2cc1d10ed17f61aff35b9a/mkdocs_literate_nav-0.6.2.tar.gz", hash = "sha256:760e1708aa4be86af81a2b56e82c739d5a8388a0eab1517ecfd8e5aa40810a75", size = 17419, upload-time = "2025-03-18T21:53:09.711Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/84/b5b14d2745e4dd1a90115186284e9ee1b4d0863104011ab46abb7355a1c3/mkdocs_literate_nav-0.6.2-py3-none-any.whl", hash = "sha256:0a6489a26ec7598477b56fa112056a5e3a6c15729f0214bea8a4dbc55bd5f630", size = 13261, upload-time = "2025-03-18T21:53:08.1Z" },
]
[[package]]
name = "mkdocs-material"
version = "9.6.14"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "babel" },
{ name = "backrefs" },
{ name = "colorama" },
{ name = "jinja2" },
{ name = "markdown" },
{ name = "mkdocs" },
{ name = "mkdocs-material-extensions" },
{ name = "paginate" },
{ name = "pygments" },
{ name = "pymdown-extensions" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b3/fa/0101de32af88f87cf5cc23ad5f2e2030d00995f74e616306513431b8ab4b/mkdocs_material-9.6.14.tar.gz", hash = "sha256:39d795e90dce6b531387c255bd07e866e027828b7346d3eba5ac3de265053754", size = 3951707, upload-time = "2025-05-13T13:27:57.173Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/a1/7fdb959ad592e013c01558822fd3c22931a95a0f08cf0a7c36da13a5b2b5/mkdocs_material-9.6.14-py3-none-any.whl", hash = "sha256:3b9cee6d3688551bf7a8e8f41afda97a3c39a12f0325436d76c86706114b721b", size = 8703767, upload-time = "2025-05-13T13:27:54.089Z" },
]
[[package]]
name = "mkdocs-material-extensions"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
]
[[package]]
name = "mkdocs-section-index"
version = "0.3.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mkdocs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/93/40/4aa9d3cfa2ac6528b91048847a35f005b97ec293204c02b179762a85b7f2/mkdocs_section_index-0.3.10.tar.gz", hash = "sha256:a82afbda633c82c5568f0e3b008176b9b365bf4bd8b6f919d6eff09ee146b9f8", size = 14446, upload-time = "2025-04-05T20:56:45.387Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/53/76c109e6f822a6d19befb0450c87330b9a6ce52353de6a9dda7892060a1f/mkdocs_section_index-0.3.10-py3-none-any.whl", hash = "sha256:bc27c0d0dc497c0ebaee1fc72839362aed77be7318b5ec0c30628f65918e4776", size = 8796, upload-time = "2025-04-05T20:56:43.975Z" },
]
[[package]]
name = "mkdocstrings"
version = "0.29.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinja2" },
{ name = "markdown" },
{ name = "markupsafe" },
{ name = "mkdocs" },
{ name = "mkdocs-autorefs" },
{ name = "pymdown-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/41/e8/d22922664a627a0d3d7ff4a6ca95800f5dde54f411982591b4621a76225d/mkdocstrings-0.29.1.tar.gz", hash = "sha256:8722f8f8c5cd75da56671e0a0c1bbed1df9946c0cef74794d6141b34011abd42", size = 1212686, upload-time = "2025-03-31T08:33:11.997Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/14/22533a578bf8b187e05d67e2c1721ce10e3f526610eebaf7a149d557ea7a/mkdocstrings-0.29.1-py3-none-any.whl", hash = "sha256:37a9736134934eea89cbd055a513d40a020d87dfcae9e3052c2a6b8cd4af09b6", size = 1631075, upload-time = "2025-03-31T08:33:09.661Z" },
]
[package.optional-dependencies]
python = [
{ name = "mkdocstrings-python" },
]
[[package]]
name = "mkdocstrings-python"
version = "1.16.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "griffe" },
{ name = "mkdocs-autorefs" },
{ name = "mkdocstrings" },
]
sdist = { url = "https://files.pythonhosted.org/packages/90/a3/0c7559a355fa21127a174a5aa2d3dca2de6e479ddd9c63ca4082d5f9980c/mkdocstrings_python-1.16.11.tar.gz", hash = "sha256:935f95efa887f99178e4a7becaaa1286fb35adafffd669b04fd611d97c00e5ce", size = 205392, upload-time = "2025-05-24T10:41:32.078Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/c4/ffa32f2c7cdb1728026c7a34aab87796b895767893aaa54611a79b4eef45/mkdocstrings_python-1.16.11-py3-none-any.whl", hash = "sha256:25d96cc9c1f9c272ea1bd8222c900b5f852bf46c984003e9c7c56eaa4696190f", size = 124282, upload-time = "2025-05-24T10:41:30.008Z" },
]
[[package]]
name = "multidict"
version = "6.4.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183, upload-time = "2025-05-19T14:16:37.381Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/2a/e166d2ffbf4b10131b2d5b0e458f7cee7d986661caceae0de8753042d4b2/multidict-6.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:82ffabefc8d84c2742ad19c37f02cde5ec2a1ee172d19944d380f920a340e4b9", size = 64123, upload-time = "2025-05-19T14:15:11.044Z" },
{ url = "https://files.pythonhosted.org/packages/8c/96/e200e379ae5b6f95cbae472e0199ea98913f03d8c9a709f42612a432932c/multidict-6.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a2f58a66fe2c22615ad26156354005391e26a2f3721c3621504cd87c1ea87bf", size = 38049, upload-time = "2025-05-19T14:15:12.902Z" },
{ url = "https://files.pythonhosted.org/packages/75/fb/47afd17b83f6a8c7fa863c6d23ac5ba6a0e6145ed8a6bcc8da20b2b2c1d2/multidict-6.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5883d6ee0fd9d8a48e9174df47540b7545909841ac82354c7ae4cbe9952603bd", size = 37078, upload-time = "2025-05-19T14:15:14.282Z" },
{ url = "https://files.pythonhosted.org/packages/fa/70/1af3143000eddfb19fd5ca5e78393985ed988ac493bb859800fe0914041f/multidict-6.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9abcf56a9511653fa1d052bfc55fbe53dbee8f34e68bd6a5a038731b0ca42d15", size = 224097, upload-time = "2025-05-19T14:15:15.566Z" },
{ url = "https://files.pythonhosted.org/packages/b1/39/d570c62b53d4fba844e0378ffbcd02ac25ca423d3235047013ba2f6f60f8/multidict-6.4.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6ed5ae5605d4ad5a049fad2a28bb7193400700ce2f4ae484ab702d1e3749c3f9", size = 230768, upload-time = "2025-05-19T14:15:17.308Z" },
{ url = "https://files.pythonhosted.org/packages/fd/f8/ed88f2c4d06f752b015933055eb291d9bc184936903752c66f68fb3c95a7/multidict-6.4.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbfcb60396f9bcfa63e017a180c3105b8c123a63e9d1428a36544e7d37ca9e20", size = 231331, upload-time = "2025-05-19T14:15:18.73Z" },
{ url = "https://files.pythonhosted.org/packages/9c/6f/8e07cffa32f483ab887b0d56bbd8747ac2c1acd00dc0af6fcf265f4a121e/multidict-6.4.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0f1987787f5f1e2076b59692352ab29a955b09ccc433c1f6b8e8e18666f608b", size = 230169, upload-time = "2025-05-19T14:15:20.179Z" },
{ url = "https://files.pythonhosted.org/packages/e6/2b/5dcf173be15e42f330110875a2668ddfc208afc4229097312212dc9c1236/multidict-6.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0121ccce8c812047d8d43d691a1ad7641f72c4f730474878a5aeae1b8ead8c", size = 222947, upload-time = "2025-05-19T14:15:21.714Z" },
{ url = "https://files.pythonhosted.org/packages/39/75/4ddcbcebe5ebcd6faa770b629260d15840a5fc07ce8ad295a32e14993726/multidict-6.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ec4967114295b8afd120a8eec579920c882831a3e4c3331d591a8e5bfbbc0f", size = 215761, upload-time = "2025-05-19T14:15:23.242Z" },
{ url = "https://files.pythonhosted.org/packages/6a/c9/55e998ae45ff15c5608e384206aa71a11e1b7f48b64d166db400b14a3433/multidict-6.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:995f985e2e268deaf17867801b859a282e0448633f1310e3704b30616d269d69", size = 227605, upload-time = "2025-05-19T14:15:24.763Z" },
{ url = "https://files.pythonhosted.org/packages/04/49/c2404eac74497503c77071bd2e6f88c7e94092b8a07601536b8dbe99be50/multidict-6.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d832c608f94b9f92a0ec8b7e949be7792a642b6e535fcf32f3e28fab69eeb046", size = 226144, upload-time = "2025-05-19T14:15:26.249Z" },
{ url = "https://files.pythonhosted.org/packages/62/c5/0cd0c3c6f18864c40846aa2252cd69d308699cb163e1c0d989ca301684da/multidict-6.4.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d21c1212171cf7da703c5b0b7a0e85be23b720818aef502ad187d627316d5645", size = 221100, upload-time = "2025-05-19T14:15:28.303Z" },
{ url = "https://files.pythonhosted.org/packages/71/7b/f2f3887bea71739a046d601ef10e689528d4f911d84da873b6be9194ffea/multidict-6.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cbebaa076aaecad3d4bb4c008ecc73b09274c952cf6a1b78ccfd689e51f5a5b0", size = 232731, upload-time = "2025-05-19T14:15:30.263Z" },
{ url = "https://files.pythonhosted.org/packages/e5/b3/d9de808349df97fa75ec1372758701b5800ebad3c46ae377ad63058fbcc6/multidict-6.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c93a6fb06cc8e5d3628b2b5fda215a5db01e8f08fc15fadd65662d9b857acbe4", size = 229637, upload-time = "2025-05-19T14:15:33.337Z" },
{ url = "https://files.pythonhosted.org/packages/5e/57/13207c16b615eb4f1745b44806a96026ef8e1b694008a58226c2d8f5f0a5/multidict-6.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8cd8f81f1310182362fb0c7898145ea9c9b08a71081c5963b40ee3e3cac589b1", size = 225594, upload-time = "2025-05-19T14:15:34.832Z" },
{ url = "https://files.pythonhosted.org/packages/3a/e4/d23bec2f70221604f5565000632c305fc8f25ba953e8ce2d8a18842b9841/multidict-6.4.4-cp313-cp313-win32.whl", hash = "sha256:3e9f1cd61a0ab857154205fb0b1f3d3ace88d27ebd1409ab7af5096e409614cd", size = 35359, upload-time = "2025-05-19T14:15:36.246Z" },
{ url = "https://files.pythonhosted.org/packages/a7/7a/cfe1a47632be861b627f46f642c1d031704cc1c0f5c0efbde2ad44aa34bd/multidict-6.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:8ffb40b74400e4455785c2fa37eba434269149ec525fc8329858c862e4b35373", size = 38903, upload-time = "2025-05-19T14:15:37.507Z" },
{ url = "https://files.pythonhosted.org/packages/68/7b/15c259b0ab49938a0a1c8f3188572802704a779ddb294edc1b2a72252e7c/multidict-6.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6a602151dbf177be2450ef38966f4be3467d41a86c6a845070d12e17c858a156", size = 68895, upload-time = "2025-05-19T14:15:38.856Z" },
{ url = "https://files.pythonhosted.org/packages/f1/7d/168b5b822bccd88142e0a3ce985858fea612404edd228698f5af691020c9/multidict-6.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d2b9712211b860d123815a80b859075d86a4d54787e247d7fbee9db6832cf1c", size = 40183, upload-time = "2025-05-19T14:15:40.197Z" },
{ url = "https://files.pythonhosted.org/packages/e0/b7/d4b8d98eb850ef28a4922ba508c31d90715fd9b9da3801a30cea2967130b/multidict-6.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d2fa86af59f8fc1972e121ade052145f6da22758f6996a197d69bb52f8204e7e", size = 39592, upload-time = "2025-05-19T14:15:41.508Z" },
{ url = "https://files.pythonhosted.org/packages/18/28/a554678898a19583548e742080cf55d169733baf57efc48c2f0273a08583/multidict-6.4.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50855d03e9e4d66eab6947ba688ffb714616f985838077bc4b490e769e48da51", size = 226071, upload-time = "2025-05-19T14:15:42.877Z" },
{ url = "https://files.pythonhosted.org/packages/ee/dc/7ba6c789d05c310e294f85329efac1bf5b450338d2542498db1491a264df/multidict-6.4.4-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5bce06b83be23225be1905dcdb6b789064fae92499fbc458f59a8c0e68718601", size = 222597, upload-time = "2025-05-19T14:15:44.412Z" },
{ url = "https://files.pythonhosted.org/packages/24/4f/34eadbbf401b03768dba439be0fb94b0d187facae9142821a3d5599ccb3b/multidict-6.4.4-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66ed0731f8e5dfd8369a883b6e564aca085fb9289aacabd9decd70568b9a30de", size = 228253, upload-time = "2025-05-19T14:15:46.474Z" },
{ url = "https://files.pythonhosted.org/packages/c0/e6/493225a3cdb0d8d80d43a94503fc313536a07dae54a3f030d279e629a2bc/multidict-6.4.4-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:329ae97fc2f56f44d91bc47fe0972b1f52d21c4b7a2ac97040da02577e2daca2", size = 226146, upload-time = "2025-05-19T14:15:48.003Z" },
{ url = "https://files.pythonhosted.org/packages/2f/70/e411a7254dc3bff6f7e6e004303b1b0591358e9f0b7c08639941e0de8bd6/multidict-6.4.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c27e5dcf520923d6474d98b96749e6805f7677e93aaaf62656005b8643f907ab", size = 220585, upload-time = "2025-05-19T14:15:49.546Z" },
{ url = "https://files.pythonhosted.org/packages/08/8f/beb3ae7406a619100d2b1fb0022c3bb55a8225ab53c5663648ba50dfcd56/multidict-6.4.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:058cc59b9e9b143cc56715e59e22941a5d868c322242278d28123a5d09cdf6b0", size = 212080, upload-time = "2025-05-19T14:15:51.151Z" },
{ url = "https://files.pythonhosted.org/packages/9c/ec/355124e9d3d01cf8edb072fd14947220f357e1c5bc79c88dff89297e9342/multidict-6.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:69133376bc9a03f8c47343d33f91f74a99c339e8b58cea90433d8e24bb298031", size = 226558, upload-time = "2025-05-19T14:15:52.665Z" },
{ url = "https://files.pythonhosted.org/packages/fd/22/d2b95cbebbc2ada3be3812ea9287dcc9712d7f1a012fad041770afddb2ad/multidict-6.4.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d6b15c55721b1b115c5ba178c77104123745b1417527ad9641a4c5e2047450f0", size = 212168, upload-time = "2025-05-19T14:15:55.279Z" },
{ url = "https://files.pythonhosted.org/packages/4d/c5/62bfc0b2f9ce88326dbe7179f9824a939c6c7775b23b95de777267b9725c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a887b77f51d3d41e6e1a63cf3bc7ddf24de5939d9ff69441387dfefa58ac2e26", size = 217970, upload-time = "2025-05-19T14:15:56.806Z" },
{ url = "https://files.pythonhosted.org/packages/79/74/977cea1aadc43ff1c75d23bd5bc4768a8fac98c14e5878d6ee8d6bab743c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:632a3bf8f1787f7ef7d3c2f68a7bde5be2f702906f8b5842ad6da9d974d0aab3", size = 226980, upload-time = "2025-05-19T14:15:58.313Z" },
{ url = "https://files.pythonhosted.org/packages/48/fc/cc4a1a2049df2eb84006607dc428ff237af38e0fcecfdb8a29ca47b1566c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a145c550900deb7540973c5cdb183b0d24bed6b80bf7bddf33ed8f569082535e", size = 220641, upload-time = "2025-05-19T14:15:59.866Z" },
{ url = "https://files.pythonhosted.org/packages/3b/6a/a7444d113ab918701988d4abdde373dbdfd2def7bd647207e2bf645c7eac/multidict-6.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc5d83c6619ca5c9672cb78b39ed8542f1975a803dee2cda114ff73cbb076edd", size = 221728, upload-time = "2025-05-19T14:16:01.535Z" },
{ url = "https://files.pythonhosted.org/packages/2b/b0/fdf4c73ad1c55e0f4dbbf2aa59dd37037334091f9a4961646d2b7ac91a86/multidict-6.4.4-cp313-cp313t-win32.whl", hash = "sha256:3312f63261b9df49be9d57aaa6abf53a6ad96d93b24f9cc16cf979956355ce6e", size = 41913, upload-time = "2025-05-19T14:16:03.199Z" },
{ url = "https://files.pythonhosted.org/packages/8e/92/27989ecca97e542c0d01d05a98a5ae12198a243a9ee12563a0313291511f/multidict-6.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:ba852168d814b2c73333073e1c7116d9395bea69575a01b0b3c89d2d5a87c8fb", size = 46112, upload-time = "2025-05-19T14:16:04.909Z" },
{ url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481, upload-time = "2025-05-19T14:16:36.024Z" },
]
[[package]]
name = "mypy"
version = "1.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139, upload-time = "2025-05-29T13:46:12.532Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/97/9c/ca03bdbefbaa03b264b9318a98950a9c683e06472226b55472f96ebbc53d/mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b", size = 11059753, upload-time = "2025-05-29T13:18:18.167Z" },
{ url = "https://files.pythonhosted.org/packages/36/92/79a969b8302cfe316027c88f7dc6fee70129490a370b3f6eb11d777749d0/mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0", size = 10073338, upload-time = "2025-05-29T13:19:48.079Z" },
{ url = "https://files.pythonhosted.org/packages/14/9b/a943f09319167da0552d5cd722104096a9c99270719b1afeea60d11610aa/mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b", size = 11827764, upload-time = "2025-05-29T13:46:04.47Z" },
{ url = "https://files.pythonhosted.org/packages/ec/64/ff75e71c65a0cb6ee737287c7913ea155845a556c64144c65b811afdb9c7/mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d", size = 12701356, upload-time = "2025-05-29T13:35:13.553Z" },
{ url = "https://files.pythonhosted.org/packages/0a/ad/0e93c18987a1182c350f7a5fab70550852f9fabe30ecb63bfbe51b602074/mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52", size = 12900745, upload-time = "2025-05-29T13:17:24.409Z" },
{ url = "https://files.pythonhosted.org/packages/28/5d/036c278d7a013e97e33f08c047fe5583ab4f1fc47c9a49f985f1cdd2a2d7/mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb", size = 9572200, upload-time = "2025-05-29T13:33:44.92Z" },
{ url = "https://files.pythonhosted.org/packages/99/a3/6ed10530dec8e0fdc890d81361260c9ef1f5e5c217ad8c9b21ecb2b8366b/mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031", size = 2265773, upload-time = "2025-05-29T13:35:18.762Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "narwhals"
version = "1.41.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/32/fc/7b9a3689911662be59889b1b0b40e17d5dba6f98080994d86ca1f3154d41/narwhals-1.41.0.tar.gz", hash = "sha256:0ab2e5a1757a19b071e37ca74b53b0b5426789321d68939738337dfddea629b5", size = 488446, upload-time = "2025-05-26T12:46:07.43Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/e0/ade8619846645461c012498f02b93a659e50f07d9d9a6ffefdf5ea2c02a0/narwhals-1.41.0-py3-none-any.whl", hash = "sha256:d958336b40952e4c4b7aeef259a7074851da0800cf902186a58f2faeff97be02", size = 357968, upload-time = "2025-05-26T12:46:05.207Z" },
]
[[package]]
name = "natsort"
version = "8.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" },
]
[[package]]
name = "nest-asyncio"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
]
[[package]]
name = "numpy"
version = "2.2.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" },
{ url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" },
{ url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" },
{ url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" },
{ url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" },
{ url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" },
{ url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" },
{ url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" },
{ url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" },
{ url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" },
{ url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" },
{ url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" },
{ url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" },
{ url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" },
{ url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" },
{ url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" },
{ url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" },
{ url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" },
{ url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" },
]
[[package]]
name = "nvidia-ml-py"
version = "12.575.51"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d2/4d/6f017814ed5ac28e08e1b8a62e3a258957da27582c89b7f8f8b15ac3d2e7/nvidia_ml_py-12.575.51.tar.gz", hash = "sha256:6490e93fea99eb4e966327ae18c6eec6256194c921f23459c8767aee28c54581", size = 46597, upload-time = "2025-05-06T20:46:37.962Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/24/552ebea28f0570b9e65e62b50287a273804c9f997cc1c2dcd4e2d64b9e7d/nvidia_ml_py-12.575.51-py3-none-any.whl", hash = "sha256:eb8641800d98ce40a22f479873f34b482e214a7e80349c63be51c3919845446e", size = 47547, upload-time = "2025-05-06T20:46:36.457Z" },
]
[[package]]
name = "openai"
version = "1.82.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "httpx" },
{ name = "jiter" },
{ name = "pydantic" },
{ name = "sniffio" },
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5e/53/fd5318cd79202744711c120f008d9bd987eacc063b15910a820bc9b9f40e/openai-1.82.1.tar.gz", hash = "sha256:ffc529680018e0417acac85f926f92aa0bbcbc26e82e2621087303c66bc7f95d", size = 461322, upload-time = "2025-05-29T16:15:14.526Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/d9/7ec61c010f0d0b0bc57dab8b8dff398f84230d269e8bfa068ad542ff050c/openai-1.82.1-py3-none-any.whl", hash = "sha256:334eb5006edf59aa464c9e932b9d137468d810b2659e5daea9b3a8c39d052395", size = 720466, upload-time = "2025-05-29T16:15:12.531Z" },
]
[[package]]
name = "opentelemetry-api"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
{ name = "importlib-metadata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9a/8d/1f5a45fbcb9a7d87809d460f09dc3399e3fbd31d7f3e14888345e9d29951/opentelemetry_api-1.33.1.tar.gz", hash = "sha256:1c6055fc0a2d3f23a50c7e17e16ef75ad489345fd3df1f8b8af7c0bbf8a109e8", size = 65002, upload-time = "2025-05-16T18:52:41.146Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/05/44/4c45a34def3506122ae61ad684139f0bbc4e00c39555d4f7e20e0e001c8a/opentelemetry_api-1.33.1-py3-none-any.whl", hash = "sha256:4db83ebcf7ea93e64637ec6ee6fabee45c5cbe4abd9cf3da95c43828ddb50b83", size = 65771, upload-time = "2025-05-16T18:52:17.419Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-common"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-proto" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7a/18/a1ec9dcb6713a48b4bdd10f1c1e4d5d2489d3912b80d2bcc059a9a842836/opentelemetry_exporter_otlp_proto_common-1.33.1.tar.gz", hash = "sha256:c57b3fa2d0595a21c4ed586f74f948d259d9949b58258f11edb398f246bec131", size = 20828, upload-time = "2025-05-16T18:52:43.795Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/09/52/9bcb17e2c29c1194a28e521b9d3f2ced09028934c3c52a8205884c94b2df/opentelemetry_exporter_otlp_proto_common-1.33.1-py3-none-any.whl", hash = "sha256:b81c1de1ad349785e601d02715b2d29d6818aed2c809c20219f3d1f20b038c36", size = 18839, upload-time = "2025-05-16T18:52:22.447Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-http"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
{ name = "googleapis-common-protos" },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-exporter-otlp-proto-common" },
{ name = "opentelemetry-proto" },
{ name = "opentelemetry-sdk" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/48/e4314ac0ed2ad043c07693d08c9c4bf5633857f5b72f2fefc64fd2b114f6/opentelemetry_exporter_otlp_proto_http-1.33.1.tar.gz", hash = "sha256:46622d964a441acb46f463ebdc26929d9dec9efb2e54ef06acdc7305e8593c38", size = 15353, upload-time = "2025-05-16T18:52:45.522Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/63/ba/5a4ad007588016fe37f8d36bf08f325fe684494cc1e88ca8fa064a4c8f57/opentelemetry_exporter_otlp_proto_http-1.33.1-py3-none-any.whl", hash = "sha256:ebd6c523b89a2ecba0549adb92537cc2bf647b4ee61afbbd5a4c6535aa3da7cf", size = 17733, upload-time = "2025-05-16T18:52:25.137Z" },
]
[[package]]
name = "opentelemetry-instrumentation"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "packaging" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c3/fd/5756aea3fdc5651b572d8aef7d94d22a0a36e49c8b12fcb78cb905ba8896/opentelemetry_instrumentation-0.54b1.tar.gz", hash = "sha256:7658bf2ff914b02f246ec14779b66671508125c0e4227361e56b5ebf6cef0aec", size = 28436, upload-time = "2025-05-16T19:03:22.223Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/89/0790abc5d9c4fc74bd3e03cb87afe2c820b1d1a112a723c1163ef32453ee/opentelemetry_instrumentation-0.54b1-py3-none-any.whl", hash = "sha256:a4ae45f4a90c78d7006c51524f57cd5aa1231aef031eae905ee34d5423f5b198", size = 31019, upload-time = "2025-05-16T19:02:15.611Z" },
]
[[package]]
name = "opentelemetry-proto"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f6/dc/791f3d60a1ad8235930de23eea735ae1084be1c6f96fdadf38710662a7e5/opentelemetry_proto-1.33.1.tar.gz", hash = "sha256:9627b0a5c90753bf3920c398908307063e4458b287bb890e5c1d6fa11ad50b68", size = 34363, upload-time = "2025-05-16T18:52:52.141Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c4/29/48609f4c875c2b6c80930073c82dd1cafd36b6782244c01394007b528960/opentelemetry_proto-1.33.1-py3-none-any.whl", hash = "sha256:243d285d9f29663fc7ea91a7171fcc1ccbbfff43b48df0774fd64a37d98eda70", size = 55854, upload-time = "2025-05-16T18:52:36.269Z" },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/67/12/909b98a7d9b110cce4b28d49b2e311797cffdce180371f35eba13a72dd00/opentelemetry_sdk-1.33.1.tar.gz", hash = "sha256:85b9fcf7c3d23506fbc9692fd210b8b025a1920535feec50bd54ce203d57a531", size = 161885, upload-time = "2025-05-16T18:52:52.832Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/8e/ae2d0742041e0bd7fe0d2dcc5e7cce51dcf7d3961a26072d5b43cc8fa2a7/opentelemetry_sdk-1.33.1-py3-none-any.whl", hash = "sha256:19ea73d9a01be29cacaa5d6c8ce0adc0b7f7b4d58cc52f923e4413609f670112", size = 118950, upload-time = "2025-05-16T18:52:37.297Z" },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
{ name = "opentelemetry-api" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/2c/d7990fc1ffc82889d466e7cd680788ace44a26789809924813b164344393/opentelemetry_semantic_conventions-0.54b1.tar.gz", hash = "sha256:d1cecedae15d19bdaafca1e56b29a66aa286f50b5d08f036a145c7f3e9ef9cee", size = 118642, upload-time = "2025-05-16T18:52:53.962Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/80/08b1698c52ff76d96ba440bf15edc2f4bc0a279868778928e947c1004bdd/opentelemetry_semantic_conventions-0.54b1-py3-none-any.whl", hash = "sha256:29dab644a7e435b58d3a3918b58c333c92686236b30f7891d5e51f02933ca60d", size = 194938, upload-time = "2025-05-16T18:52:38.796Z" },
]
[[package]]
name = "ordered-set"
version = "4.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826, upload-time = "2022-01-26T14:38:56.6Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634, upload-time = "2022-01-26T14:38:48.677Z" },
]
[[package]]
name = "packaging"
version = "24.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" },
]
[[package]]
name = "paginate"
version = "0.5.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" },
]
[[package]]
name = "pandas"
version = "2.2.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "python-dateutil" },
{ name = "pytz" },
{ name = "tzdata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643, upload-time = "2024-09-20T13:09:25.522Z" },
{ url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573, upload-time = "2024-09-20T13:09:28.012Z" },
{ url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085, upload-time = "2024-09-20T19:02:10.451Z" },
{ url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809, upload-time = "2024-09-20T13:09:30.814Z" },
{ url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316, upload-time = "2024-09-20T19:02:13.825Z" },
{ url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055, upload-time = "2024-09-20T13:09:33.462Z" },
{ url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175, upload-time = "2024-09-20T13:09:35.871Z" },
{ url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650, upload-time = "2024-09-20T13:09:38.685Z" },
{ url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177, upload-time = "2024-09-20T13:09:41.141Z" },
{ url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526, upload-time = "2024-09-20T19:02:16.905Z" },
{ url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013, upload-time = "2024-09-20T13:09:44.39Z" },
{ url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620, upload-time = "2024-09-20T19:02:20.639Z" },
{ url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" },
]
[[package]]
name = "parse"
version = "1.20.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" },
]
[[package]]
name = "parse-type"
version = "0.6.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "parse" },
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/17/e9/a3b2ae5f8a852542788ac1f1865dcea0c549cc40af243f42cabfa0acf24d/parse_type-0.6.4.tar.gz", hash = "sha256:5e1ec10440b000c3f818006033372939e693a9ec0176f446d9303e4db88489a6", size = 96480, upload-time = "2024-10-03T11:51:00.353Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/b3/f6cc950042bfdbe98672e7c834d930f85920fb7d3359f59096e8d2799617/parse_type-0.6.4-py2.py3-none-any.whl", hash = "sha256:83d41144a82d6b8541127bf212dd76c7f01baff680b498ce8a4d052a7a5bce4c", size = 27442, upload-time = "2024-10-03T11:50:58.519Z" },
]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
]
[[package]]
name = "pillow"
version = "11.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" },
{ url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" },
{ url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" },
{ url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" },
{ url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" },
{ url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" },
{ url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" },
{ url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" },
{ url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" },
{ url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" },
{ url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" },
{ url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" },
{ url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" },
{ url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" },
{ url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" },
{ url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" },
{ url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" },
{ url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" },
{ url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" },
{ url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" },
{ url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" },
{ url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" },
]
[[package]]
name = "platformdirs"
version = "4.3.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "primp"
version = "0.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/56/0b/a87556189da4de1fc6360ca1aa05e8335509633f836cdd06dd17f0743300/primp-0.15.0.tar.gz", hash = "sha256:1af8ea4b15f57571ff7fc5e282a82c5eb69bc695e19b8ddeeda324397965b30a", size = 113022, upload-time = "2025-04-17T11:41:05.315Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f5/5a/146ac964b99ea7657ad67eb66f770be6577dfe9200cb28f9a95baffd6c3f/primp-0.15.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1b281f4ca41a0c6612d4c6e68b96e28acfe786d226a427cd944baa8d7acd644f", size = 3178914, upload-time = "2025-04-17T11:40:59.558Z" },
{ url = "https://files.pythonhosted.org/packages/bc/8a/cc2321e32db3ce64d6e32950d5bcbea01861db97bfb20b5394affc45b387/primp-0.15.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:489cbab55cd793ceb8f90bb7423c6ea64ebb53208ffcf7a044138e3c66d77299", size = 2955079, upload-time = "2025-04-17T11:40:57.398Z" },
{ url = "https://files.pythonhosted.org/packages/c3/7b/cbd5d999a07ff2a21465975d4eb477ae6f69765e8fe8c9087dab250180d8/primp-0.15.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c18b45c23f94016215f62d2334552224236217aaeb716871ce0e4dcfa08eb161", size = 3281018, upload-time = "2025-04-17T11:40:55.308Z" },
{ url = "https://files.pythonhosted.org/packages/1b/6e/a6221c612e61303aec2bcac3f0a02e8b67aee8c0db7bdc174aeb8010f975/primp-0.15.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e985a9cba2e3f96a323722e5440aa9eccaac3178e74b884778e926b5249df080", size = 3255229, upload-time = "2025-04-17T11:40:47.811Z" },
{ url = "https://files.pythonhosted.org/packages/3b/54/bfeef5aca613dc660a69d0760a26c6b8747d8fdb5a7f20cb2cee53c9862f/primp-0.15.0-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:6b84a6ffa083e34668ff0037221d399c24d939b5629cd38223af860de9e17a83", size = 3014522, upload-time = "2025-04-17T11:40:50.191Z" },
{ url = "https://files.pythonhosted.org/packages/ac/96/84078e09f16a1dad208f2fe0f8a81be2cf36e024675b0f9eec0c2f6e2182/primp-0.15.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:592f6079646bdf5abbbfc3b0a28dac8de943f8907a250ce09398cda5eaebd260", size = 3418567, upload-time = "2025-04-17T11:41:01.595Z" },
{ url = "https://files.pythonhosted.org/packages/6c/80/8a7a9587d3eb85be3d0b64319f2f690c90eb7953e3f73a9ddd9e46c8dc42/primp-0.15.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a728e5a05f37db6189eb413d22c78bd143fa59dd6a8a26dacd43332b3971fe8", size = 3606279, upload-time = "2025-04-17T11:41:03.61Z" },
{ url = "https://files.pythonhosted.org/packages/0c/dd/f0183ed0145e58cf9d286c1b2c14f63ccee987a4ff79ac85acc31b5d86bd/primp-0.15.0-cp38-abi3-win_amd64.whl", hash = "sha256:aeb6bd20b06dfc92cfe4436939c18de88a58c640752cf7f30d9e4ae893cdec32", size = 3149967, upload-time = "2025-04-17T11:41:07.067Z" },
]
[[package]]
name = "propcache"
version = "0.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651, upload-time = "2025-03-26T03:06:12.05Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/60/f645cc8b570f99be3cf46714170c2de4b4c9d6b827b912811eff1eb8a412/propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8", size = 77865, upload-time = "2025-03-26T03:04:53.406Z" },
{ url = "https://files.pythonhosted.org/packages/6f/d4/c1adbf3901537582e65cf90fd9c26fde1298fde5a2c593f987112c0d0798/propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f", size = 45452, upload-time = "2025-03-26T03:04:54.624Z" },
{ url = "https://files.pythonhosted.org/packages/d1/b5/fe752b2e63f49f727c6c1c224175d21b7d1727ce1d4873ef1c24c9216830/propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111", size = 44800, upload-time = "2025-03-26T03:04:55.844Z" },
{ url = "https://files.pythonhosted.org/packages/62/37/fc357e345bc1971e21f76597028b059c3d795c5ca7690d7a8d9a03c9708a/propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5", size = 225804, upload-time = "2025-03-26T03:04:57.158Z" },
{ url = "https://files.pythonhosted.org/packages/0d/f1/16e12c33e3dbe7f8b737809bad05719cff1dccb8df4dafbcff5575002c0e/propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb", size = 230650, upload-time = "2025-03-26T03:04:58.61Z" },
{ url = "https://files.pythonhosted.org/packages/3e/a2/018b9f2ed876bf5091e60153f727e8f9073d97573f790ff7cdf6bc1d1fb8/propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7", size = 234235, upload-time = "2025-03-26T03:05:00.599Z" },
{ url = "https://files.pythonhosted.org/packages/45/5f/3faee66fc930dfb5da509e34c6ac7128870631c0e3582987fad161fcb4b1/propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120", size = 228249, upload-time = "2025-03-26T03:05:02.11Z" },
{ url = "https://files.pythonhosted.org/packages/62/1e/a0d5ebda5da7ff34d2f5259a3e171a94be83c41eb1e7cd21a2105a84a02e/propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654", size = 214964, upload-time = "2025-03-26T03:05:03.599Z" },
{ url = "https://files.pythonhosted.org/packages/db/a0/d72da3f61ceab126e9be1f3bc7844b4e98c6e61c985097474668e7e52152/propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e", size = 222501, upload-time = "2025-03-26T03:05:05.107Z" },
{ url = "https://files.pythonhosted.org/packages/18/6d/a008e07ad7b905011253adbbd97e5b5375c33f0b961355ca0a30377504ac/propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b", size = 217917, upload-time = "2025-03-26T03:05:06.59Z" },
{ url = "https://files.pythonhosted.org/packages/98/37/02c9343ffe59e590e0e56dc5c97d0da2b8b19fa747ebacf158310f97a79a/propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53", size = 217089, upload-time = "2025-03-26T03:05:08.1Z" },
{ url = "https://files.pythonhosted.org/packages/53/1b/d3406629a2c8a5666d4674c50f757a77be119b113eedd47b0375afdf1b42/propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5", size = 228102, upload-time = "2025-03-26T03:05:09.982Z" },
{ url = "https://files.pythonhosted.org/packages/cd/a7/3664756cf50ce739e5f3abd48febc0be1a713b1f389a502ca819791a6b69/propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7", size = 230122, upload-time = "2025-03-26T03:05:11.408Z" },
{ url = "https://files.pythonhosted.org/packages/35/36/0bbabaacdcc26dac4f8139625e930f4311864251276033a52fd52ff2a274/propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef", size = 226818, upload-time = "2025-03-26T03:05:12.909Z" },
{ url = "https://files.pythonhosted.org/packages/cc/27/4e0ef21084b53bd35d4dae1634b6d0bad35e9c58ed4f032511acca9d4d26/propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24", size = 40112, upload-time = "2025-03-26T03:05:14.289Z" },
{ url = "https://files.pythonhosted.org/packages/a6/2c/a54614d61895ba6dd7ac8f107e2b2a0347259ab29cbf2ecc7b94fa38c4dc/propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037", size = 44034, upload-time = "2025-03-26T03:05:15.616Z" },
{ url = "https://files.pythonhosted.org/packages/5a/a8/0a4fd2f664fc6acc66438370905124ce62e84e2e860f2557015ee4a61c7e/propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f", size = 82613, upload-time = "2025-03-26T03:05:16.913Z" },
{ url = "https://files.pythonhosted.org/packages/4d/e5/5ef30eb2cd81576256d7b6caaa0ce33cd1d2c2c92c8903cccb1af1a4ff2f/propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c", size = 47763, upload-time = "2025-03-26T03:05:18.607Z" },
{ url = "https://files.pythonhosted.org/packages/87/9a/87091ceb048efeba4d28e903c0b15bcc84b7c0bf27dc0261e62335d9b7b8/propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc", size = 47175, upload-time = "2025-03-26T03:05:19.85Z" },
{ url = "https://files.pythonhosted.org/packages/3e/2f/854e653c96ad1161f96194c6678a41bbb38c7947d17768e8811a77635a08/propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de", size = 292265, upload-time = "2025-03-26T03:05:21.654Z" },
{ url = "https://files.pythonhosted.org/packages/40/8d/090955e13ed06bc3496ba4a9fb26c62e209ac41973cb0d6222de20c6868f/propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6", size = 294412, upload-time = "2025-03-26T03:05:23.147Z" },
{ url = "https://files.pythonhosted.org/packages/39/e6/d51601342e53cc7582449e6a3c14a0479fab2f0750c1f4d22302e34219c6/propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7", size = 294290, upload-time = "2025-03-26T03:05:24.577Z" },
{ url = "https://files.pythonhosted.org/packages/3b/4d/be5f1a90abc1881884aa5878989a1acdafd379a91d9c7e5e12cef37ec0d7/propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458", size = 282926, upload-time = "2025-03-26T03:05:26.459Z" },
{ url = "https://files.pythonhosted.org/packages/57/2b/8f61b998c7ea93a2b7eca79e53f3e903db1787fca9373af9e2cf8dc22f9d/propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11", size = 267808, upload-time = "2025-03-26T03:05:28.188Z" },
{ url = "https://files.pythonhosted.org/packages/11/1c/311326c3dfce59c58a6098388ba984b0e5fb0381ef2279ec458ef99bd547/propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c", size = 290916, upload-time = "2025-03-26T03:05:29.757Z" },
{ url = "https://files.pythonhosted.org/packages/4b/74/91939924b0385e54dc48eb2e4edd1e4903ffd053cf1916ebc5347ac227f7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf", size = 262661, upload-time = "2025-03-26T03:05:31.472Z" },
{ url = "https://files.pythonhosted.org/packages/c2/d7/e6079af45136ad325c5337f5dd9ef97ab5dc349e0ff362fe5c5db95e2454/propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27", size = 264384, upload-time = "2025-03-26T03:05:32.984Z" },
{ url = "https://files.pythonhosted.org/packages/b7/d5/ba91702207ac61ae6f1c2da81c5d0d6bf6ce89e08a2b4d44e411c0bbe867/propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757", size = 291420, upload-time = "2025-03-26T03:05:34.496Z" },
{ url = "https://files.pythonhosted.org/packages/58/70/2117780ed7edcd7ba6b8134cb7802aada90b894a9810ec56b7bb6018bee7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18", size = 290880, upload-time = "2025-03-26T03:05:36.256Z" },
{ url = "https://files.pythonhosted.org/packages/4a/1f/ecd9ce27710021ae623631c0146719280a929d895a095f6d85efb6a0be2e/propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a", size = 287407, upload-time = "2025-03-26T03:05:37.799Z" },
{ url = "https://files.pythonhosted.org/packages/3e/66/2e90547d6b60180fb29e23dc87bd8c116517d4255240ec6d3f7dc23d1926/propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d", size = 42573, upload-time = "2025-03-26T03:05:39.193Z" },
{ url = "https://files.pythonhosted.org/packages/cb/8f/50ad8599399d1861b4d2b6b45271f0ef6af1b09b0a2386a46dbaf19c9535/propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e", size = 46757, upload-time = "2025-03-26T03:05:40.811Z" },
{ url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" },
]
[[package]]
name = "protobuf"
version = "5.29.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226, upload-time = "2025-05-28T23:51:59.82Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963, upload-time = "2025-05-28T23:51:41.204Z" },
{ url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818, upload-time = "2025-05-28T23:51:44.297Z" },
{ url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091, upload-time = "2025-05-28T23:51:45.907Z" },
{ url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824, upload-time = "2025-05-28T23:51:47.545Z" },
{ url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942, upload-time = "2025-05-28T23:51:49.11Z" },
{ url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" },
]
[[package]]
name = "psutil"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", size = 508067, upload-time = "2024-06-18T21:40:10.559Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", size = 250961, upload-time = "2024-06-18T21:41:11.662Z" },
{ url = "https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", size = 287478, upload-time = "2024-06-18T21:41:16.18Z" },
{ url = "https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", size = 290455, upload-time = "2024-06-18T21:41:29.048Z" },
{ url = "https://files.pythonhosted.org/packages/cd/5f/60038e277ff0a9cc8f0c9ea3d0c5eb6ee1d2470ea3f9389d776432888e47/psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132", size = 292046, upload-time = "2024-06-18T21:41:33.53Z" },
{ url = "https://files.pythonhosted.org/packages/8b/20/2ff69ad9c35c3df1858ac4e094f20bd2374d33c8643cf41da8fd7cdcb78b/psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d", size = 253560, upload-time = "2024-06-18T21:41:46.067Z" },
{ url = "https://files.pythonhosted.org/packages/73/44/561092313ae925f3acfaace6f9ddc4f6a9c748704317bad9c8c8f8a36a79/psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3", size = 257399, upload-time = "2024-06-18T21:41:52.1Z" },
{ url = "https://files.pythonhosted.org/packages/7c/06/63872a64c312a24fb9b4af123ee7007a306617da63ff13bcc1432386ead7/psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0", size = 251988, upload-time = "2024-06-18T21:41:57.337Z" },
]
[[package]]
name = "pyarrow"
version = "20.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/ee/a7810cb9f3d6e9238e61d312076a9859bf3668fd21c69744de9532383912/pyarrow-20.0.0.tar.gz", hash = "sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1", size = 1125187, upload-time = "2025-04-27T12:34:23.264Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/aa/daa413b81446d20d4dad2944110dcf4cf4f4179ef7f685dd5a6d7570dc8e/pyarrow-20.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a15532e77b94c61efadde86d10957950392999503b3616b2ffcef7621a002893", size = 30798501, upload-time = "2025-04-27T12:30:48.351Z" },
{ url = "https://files.pythonhosted.org/packages/ff/75/2303d1caa410925de902d32ac215dc80a7ce7dd8dfe95358c165f2adf107/pyarrow-20.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:dd43f58037443af715f34f1322c782ec463a3c8a94a85fdb2d987ceb5658e061", size = 32277895, upload-time = "2025-04-27T12:30:55.238Z" },
{ url = "https://files.pythonhosted.org/packages/92/41/fe18c7c0b38b20811b73d1bdd54b1fccba0dab0e51d2048878042d84afa8/pyarrow-20.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0d288143a8585806e3cc7c39566407aab646fb9ece164609dac1cfff45f6ae", size = 41327322, upload-time = "2025-04-27T12:31:05.587Z" },
{ url = "https://files.pythonhosted.org/packages/da/ab/7dbf3d11db67c72dbf36ae63dcbc9f30b866c153b3a22ef728523943eee6/pyarrow-20.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6953f0114f8d6f3d905d98e987d0924dabce59c3cda380bdfaa25a6201563b4", size = 42411441, upload-time = "2025-04-27T12:31:15.675Z" },
{ url = "https://files.pythonhosted.org/packages/90/c3/0c7da7b6dac863af75b64e2f827e4742161128c350bfe7955b426484e226/pyarrow-20.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:991f85b48a8a5e839b2128590ce07611fae48a904cae6cab1f089c5955b57eb5", size = 40677027, upload-time = "2025-04-27T12:31:24.631Z" },
{ url = "https://files.pythonhosted.org/packages/be/27/43a47fa0ff9053ab5203bb3faeec435d43c0d8bfa40179bfd076cdbd4e1c/pyarrow-20.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:97c8dc984ed09cb07d618d57d8d4b67a5100a30c3818c2fb0b04599f0da2de7b", size = 42281473, upload-time = "2025-04-27T12:31:31.311Z" },
{ url = "https://files.pythonhosted.org/packages/bc/0b/d56c63b078876da81bbb9ba695a596eabee9b085555ed12bf6eb3b7cab0e/pyarrow-20.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9b71daf534f4745818f96c214dbc1e6124d7daf059167330b610fc69b6f3d3e3", size = 42893897, upload-time = "2025-04-27T12:31:39.406Z" },
{ url = "https://files.pythonhosted.org/packages/92/ac/7d4bd020ba9145f354012838692d48300c1b8fe5634bfda886abcada67ed/pyarrow-20.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8b88758f9303fa5a83d6c90e176714b2fd3852e776fc2d7e42a22dd6c2fb368", size = 44543847, upload-time = "2025-04-27T12:31:45.997Z" },
{ url = "https://files.pythonhosted.org/packages/9d/07/290f4abf9ca702c5df7b47739c1b2c83588641ddfa2cc75e34a301d42e55/pyarrow-20.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:30b3051b7975801c1e1d387e17c588d8ab05ced9b1e14eec57915f79869b5031", size = 25653219, upload-time = "2025-04-27T12:31:54.11Z" },
{ url = "https://files.pythonhosted.org/packages/95/df/720bb17704b10bd69dde086e1400b8eefb8f58df3f8ac9cff6c425bf57f1/pyarrow-20.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ca151afa4f9b7bc45bcc791eb9a89e90a9eb2772767d0b1e5389609c7d03db63", size = 30853957, upload-time = "2025-04-27T12:31:59.215Z" },
{ url = "https://files.pythonhosted.org/packages/d9/72/0d5f875efc31baef742ba55a00a25213a19ea64d7176e0fe001c5d8b6e9a/pyarrow-20.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:4680f01ecd86e0dd63e39eb5cd59ef9ff24a9d166db328679e36c108dc993d4c", size = 32247972, upload-time = "2025-04-27T12:32:05.369Z" },
{ url = "https://files.pythonhosted.org/packages/d5/bc/e48b4fa544d2eea72f7844180eb77f83f2030b84c8dad860f199f94307ed/pyarrow-20.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f4c8534e2ff059765647aa69b75d6543f9fef59e2cd4c6d18015192565d2b70", size = 41256434, upload-time = "2025-04-27T12:32:11.814Z" },
{ url = "https://files.pythonhosted.org/packages/c3/01/974043a29874aa2cf4f87fb07fd108828fc7362300265a2a64a94965e35b/pyarrow-20.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1f8a47f4b4ae4c69c4d702cfbdfe4d41e18e5c7ef6f1bb1c50918c1e81c57b", size = 42353648, upload-time = "2025-04-27T12:32:20.766Z" },
{ url = "https://files.pythonhosted.org/packages/68/95/cc0d3634cde9ca69b0e51cbe830d8915ea32dda2157560dda27ff3b3337b/pyarrow-20.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:a1f60dc14658efaa927f8214734f6a01a806d7690be4b3232ba526836d216122", size = 40619853, upload-time = "2025-04-27T12:32:28.1Z" },
{ url = "https://files.pythonhosted.org/packages/29/c2/3ad40e07e96a3e74e7ed7cc8285aadfa84eb848a798c98ec0ad009eb6bcc/pyarrow-20.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:204a846dca751428991346976b914d6d2a82ae5b8316a6ed99789ebf976551e6", size = 42241743, upload-time = "2025-04-27T12:32:35.792Z" },
{ url = "https://files.pythonhosted.org/packages/eb/cb/65fa110b483339add6a9bc7b6373614166b14e20375d4daa73483755f830/pyarrow-20.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f3b117b922af5e4c6b9a9115825726cac7d8b1421c37c2b5e24fbacc8930612c", size = 42839441, upload-time = "2025-04-27T12:32:46.64Z" },
{ url = "https://files.pythonhosted.org/packages/98/7b/f30b1954589243207d7a0fbc9997401044bf9a033eec78f6cb50da3f304a/pyarrow-20.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e724a3fd23ae5b9c010e7be857f4405ed5e679db5c93e66204db1a69f733936a", size = 44503279, upload-time = "2025-04-27T12:32:56.503Z" },
{ url = "https://files.pythonhosted.org/packages/37/40/ad395740cd641869a13bcf60851296c89624662575621968dcfafabaa7f6/pyarrow-20.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:82f1ee5133bd8f49d31be1299dc07f585136679666b502540db854968576faf9", size = 25944982, upload-time = "2025-04-27T12:33:04.72Z" },
]
[[package]]
name = "pydantic"
version = "2.11.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" },
]
[[package]]
name = "pydantic-ai-slim"
version = "0.2.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "eval-type-backport" },
{ name = "griffe" },
{ name = "httpx" },
{ name = "opentelemetry-api" },
{ name = "pydantic" },
{ name = "pydantic-graph" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d5/ba/fac7901a13003fe3006c8c7309c9d42b38a49af2e94c25591c078292af0d/pydantic_ai_slim-0.2.12.tar.gz", hash = "sha256:27cd77c5085b579c48d5ed5bda9305bce191851e89942e06952852e7900ccf4c", size = 138286, upload-time = "2025-05-29T14:48:25.578Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/9d/762bd98e7d73bc6d900a5672f898bccae828be2b877b7b69493eb74392df/pydantic_ai_slim-0.2.12-py3-none-any.whl", hash = "sha256:d5de8fa538767a0124c630a1f40e372f7f5b3e25d94fa2156551e2b5a1a7abae", size = 186705, upload-time = "2025-05-29T14:48:11.168Z" },
]
[package.optional-dependencies]
duckduckgo = [
{ name = "duckduckgo-search" },
]
openai = [
{ name = "openai" },
]
tavily = [
{ name = "tavily-python" },
]
[[package]]
name = "pydantic-core"
version = "2.33.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
]
[[package]]
name = "pydantic-graph"
version = "0.2.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "logfire-api" },
{ name = "pydantic" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ed/b7/17d77972660ec5538808b59d4f396635498a1d0fbad6ab88077c3c10c453/pydantic_graph-0.2.12.tar.gz", hash = "sha256:3966d138fce5eb3cd56c0b3779464199fb191f6f28447af4c8ec618f77b4b9be", size = 21842, upload-time = "2025-05-29T14:48:27.797Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/53/c8d823822cff0049df0be9ce7896729f2c799eb82fa08078c541f29cf6f6/pydantic_graph-0.2.12-py3-none-any.whl", hash = "sha256:de85b094d6e70c0e1913d530889a34fac8820c1c369d42e1025c40ac77b05bdd", size = 27485, upload-time = "2025-05-29T14:48:15.058Z" },
]
[[package]]
name = "pydantic-settings"
version = "2.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" },
]
[[package]]
name = "pydeck"
version = "0.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinja2" },
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/40e14e196864a0f61a92abb14d09b3d3da98f94ccb03b49cf51688140dab/pydeck-0.9.1.tar.gz", hash = "sha256:f74475ae637951d63f2ee58326757f8d4f9cd9f2a457cf42950715003e2cb605", size = 3832240, upload-time = "2024-05-10T15:36:21.153Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload-time = "2024-05-10T15:36:17.36Z" },
]
[[package]]
name = "pygments"
version = "2.19.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
]
[[package]]
name = "pymdown-extensions"
version = "10.15"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown" },
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/08/92/a7296491dbf5585b3a987f3f3fc87af0e632121ff3e490c14b5f2d2b4eb5/pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7", size = 852320, upload-time = "2025-04-27T23:48:29.183Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/d1/c54e608505776ce4e7966d03358ae635cfd51dff1da6ee421c090dbc797b/pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f", size = 265845, upload-time = "2025-04-27T23:48:27.359Z" },
]
[[package]]
name = "pytest"
version = "8.3.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" },
]
[[package]]
name = "pytest-asyncio"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960, upload-time = "2025-05-26T04:54:40.484Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" },
]
[[package]]
name = "pytest-bdd"
version = "8.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "gherkin-official" },
{ name = "mako" },
{ name = "packaging" },
{ name = "parse" },
{ name = "parse-type" },
{ name = "pytest" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2d/2f/14c2e55372a5718a93b56aea48cd6ccc15d2d245364e516cd7b19bbd07ad/pytest_bdd-8.1.0.tar.gz", hash = "sha256:ef0896c5cd58816dc49810e8ff1d632f4a12019fb3e49959b2d349ffc1c9bfb5", size = 56147, upload-time = "2024-12-05T21:45:58.83Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/7d/1461076b0cc9a9e6fa8b51b9dea2677182ba8bc248d99d95ca321f2c666f/pytest_bdd-8.1.0-py3-none-any.whl", hash = "sha256:2124051e71a05ad7db15296e39013593f72ebf96796e1b023a40e5453c47e5fb", size = 49149, upload-time = "2024-12-05T21:45:56.184Z" },
]
[[package]]
name = "pytest-cov"
version = "6.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
[[package]]
name = "python-dotenv"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" },
]
[[package]]
name = "pytz"
version = "2025.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
]
[[package]]
name = "pyyaml-env-tag"
version = "1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
]
[[package]]
name = "referencing"
version = "0.36.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
]
[[package]]
name = "regex"
version = "2024.11.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" },
{ url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" },
{ url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" },
{ url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" },
{ url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" },
{ url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" },
{ url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" },
{ url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" },
{ url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" },
{ url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" },
{ url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" },
{ url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" },
{ url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" },
{ url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" },
{ url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" },
]
[[package]]
name = "requests"
version = "2.32.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
]
[[package]]
name = "rich"
version = "14.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" },
]
[[package]]
name = "rpds-py"
version = "0.25.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/60184b7fc00dd3ca80ac635dd5b8577d444c57e8e8742cecabfacb829921/rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3", size = 27304, upload-time = "2025-05-21T12:46:12.502Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/da/323848a2b62abe6a0fec16ebe199dc6889c5d0a332458da8985b2980dffe/rpds_py-0.25.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:659d87430a8c8c704d52d094f5ba6fa72ef13b4d385b7e542a08fc240cb4a559", size = 364498, upload-time = "2025-05-21T12:43:54.841Z" },
{ url = "https://files.pythonhosted.org/packages/1f/b4/4d3820f731c80fd0cd823b3e95b9963fec681ae45ba35b5281a42382c67d/rpds_py-0.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68f6f060f0bbdfb0245267da014d3a6da9be127fe3e8cc4a68c6f833f8a23bb1", size = 350083, upload-time = "2025-05-21T12:43:56.428Z" },
{ url = "https://files.pythonhosted.org/packages/d5/b1/3a8ee1c9d480e8493619a437dec685d005f706b69253286f50f498cbdbcf/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:083a9513a33e0b92cf6e7a6366036c6bb43ea595332c1ab5c8ae329e4bcc0a9c", size = 389023, upload-time = "2025-05-21T12:43:57.995Z" },
{ url = "https://files.pythonhosted.org/packages/3b/31/17293edcfc934dc62c3bf74a0cb449ecd549531f956b72287203e6880b87/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:816568614ecb22b18a010c7a12559c19f6fe993526af88e95a76d5a60b8b75fb", size = 403283, upload-time = "2025-05-21T12:43:59.546Z" },
{ url = "https://files.pythonhosted.org/packages/d1/ca/e0f0bc1a75a8925024f343258c8ecbd8828f8997ea2ac71e02f67b6f5299/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c6564c0947a7f52e4792983f8e6cf9bac140438ebf81f527a21d944f2fd0a40", size = 524634, upload-time = "2025-05-21T12:44:01.087Z" },
{ url = "https://files.pythonhosted.org/packages/3e/03/5d0be919037178fff33a6672ffc0afa04ea1cfcb61afd4119d1b5280ff0f/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c4a128527fe415d73cf1f70a9a688d06130d5810be69f3b553bf7b45e8acf79", size = 416233, upload-time = "2025-05-21T12:44:02.604Z" },
{ url = "https://files.pythonhosted.org/packages/05/7c/8abb70f9017a231c6c961a8941403ed6557664c0913e1bf413cbdc039e75/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e1d7a4978ed554f095430b89ecc23f42014a50ac385eb0c4d163ce213c325", size = 390375, upload-time = "2025-05-21T12:44:04.162Z" },
{ url = "https://files.pythonhosted.org/packages/7a/ac/a87f339f0e066b9535074a9f403b9313fd3892d4a164d5d5f5875ac9f29f/rpds_py-0.25.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d74ec9bc0e2feb81d3f16946b005748119c0f52a153f6db6a29e8cd68636f295", size = 424537, upload-time = "2025-05-21T12:44:06.175Z" },
{ url = "https://files.pythonhosted.org/packages/1f/8f/8d5c1567eaf8c8afe98a838dd24de5013ce6e8f53a01bd47fe8bb06b5533/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3af5b4cc10fa41e5bc64e5c198a1b2d2864337f8fcbb9a67e747e34002ce812b", size = 566425, upload-time = "2025-05-21T12:44:08.242Z" },
{ url = "https://files.pythonhosted.org/packages/95/33/03016a6be5663b389c8ab0bbbcca68d9e96af14faeff0a04affcb587e776/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79dc317a5f1c51fd9c6a0c4f48209c6b8526d0524a6904fc1076476e79b00f98", size = 595197, upload-time = "2025-05-21T12:44:10.449Z" },
{ url = "https://files.pythonhosted.org/packages/33/8d/da9f4d3e208c82fda311bff0cf0a19579afceb77cf456e46c559a1c075ba/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1521031351865e0181bc585147624d66b3b00a84109b57fcb7a779c3ec3772cd", size = 561244, upload-time = "2025-05-21T12:44:12.387Z" },
{ url = "https://files.pythonhosted.org/packages/e2/b3/39d5dcf7c5f742ecd6dbc88f6f84ae54184b92f5f387a4053be2107b17f1/rpds_py-0.25.1-cp313-cp313-win32.whl", hash = "sha256:5d473be2b13600b93a5675d78f59e63b51b1ba2d0476893415dfbb5477e65b31", size = 222254, upload-time = "2025-05-21T12:44:14.261Z" },
{ url = "https://files.pythonhosted.org/packages/5f/19/2d6772c8eeb8302c5f834e6d0dfd83935a884e7c5ce16340c7eaf89ce925/rpds_py-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7b74e92a3b212390bdce1d93da9f6488c3878c1d434c5e751cbc202c5e09500", size = 234741, upload-time = "2025-05-21T12:44:16.236Z" },
{ url = "https://files.pythonhosted.org/packages/5b/5a/145ada26cfaf86018d0eb304fe55eafdd4f0b6b84530246bb4a7c4fb5c4b/rpds_py-0.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:dd326a81afe332ede08eb39ab75b301d5676802cdffd3a8f287a5f0b694dc3f5", size = 224830, upload-time = "2025-05-21T12:44:17.749Z" },
{ url = "https://files.pythonhosted.org/packages/4b/ca/d435844829c384fd2c22754ff65889c5c556a675d2ed9eb0e148435c6690/rpds_py-0.25.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:a58d1ed49a94d4183483a3ce0af22f20318d4a1434acee255d683ad90bf78129", size = 359668, upload-time = "2025-05-21T12:44:19.322Z" },
{ url = "https://files.pythonhosted.org/packages/1f/01/b056f21db3a09f89410d493d2f6614d87bb162499f98b649d1dbd2a81988/rpds_py-0.25.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f251bf23deb8332823aef1da169d5d89fa84c89f67bdfb566c49dea1fccfd50d", size = 345649, upload-time = "2025-05-21T12:44:20.962Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0f/e0d00dc991e3d40e03ca36383b44995126c36b3eafa0ccbbd19664709c88/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbd586bfa270c1103ece2109314dd423df1fa3d9719928b5d09e4840cec0d72", size = 384776, upload-time = "2025-05-21T12:44:22.516Z" },
{ url = "https://files.pythonhosted.org/packages/9f/a2/59374837f105f2ca79bde3c3cd1065b2f8c01678900924949f6392eab66d/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d273f136e912aa101a9274c3145dcbddbe4bac560e77e6d5b3c9f6e0ed06d34", size = 395131, upload-time = "2025-05-21T12:44:24.147Z" },
{ url = "https://files.pythonhosted.org/packages/9c/dc/48e8d84887627a0fe0bac53f0b4631e90976fd5d35fff8be66b8e4f3916b/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:666fa7b1bd0a3810a7f18f6d3a25ccd8866291fbbc3c9b912b917a6715874bb9", size = 520942, upload-time = "2025-05-21T12:44:25.915Z" },
{ url = "https://files.pythonhosted.org/packages/7c/f5/ee056966aeae401913d37befeeab57a4a43a4f00099e0a20297f17b8f00c/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:921954d7fbf3fccc7de8f717799304b14b6d9a45bbeec5a8d7408ccbf531faf5", size = 411330, upload-time = "2025-05-21T12:44:27.638Z" },
{ url = "https://files.pythonhosted.org/packages/ab/74/b2cffb46a097cefe5d17f94ede7a174184b9d158a0aeb195f39f2c0361e8/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d86373ff19ca0441ebeb696ef64cb58b8b5cbacffcda5a0ec2f3911732a194", size = 387339, upload-time = "2025-05-21T12:44:29.292Z" },
{ url = "https://files.pythonhosted.org/packages/7f/9a/0ff0b375dcb5161c2b7054e7d0b7575f1680127505945f5cabaac890bc07/rpds_py-0.25.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8980cde3bb8575e7c956a530f2c217c1d6aac453474bf3ea0f9c89868b531b6", size = 418077, upload-time = "2025-05-21T12:44:30.877Z" },
{ url = "https://files.pythonhosted.org/packages/0d/a1/fda629bf20d6b698ae84c7c840cfb0e9e4200f664fc96e1f456f00e4ad6e/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8eb8c84ecea987a2523e057c0d950bcb3f789696c0499290b8d7b3107a719d78", size = 562441, upload-time = "2025-05-21T12:44:32.541Z" },
{ url = "https://files.pythonhosted.org/packages/20/15/ce4b5257f654132f326f4acd87268e1006cc071e2c59794c5bdf4bebbb51/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e43a005671a9ed5a650f3bc39e4dbccd6d4326b24fb5ea8be5f3a43a6f576c72", size = 590750, upload-time = "2025-05-21T12:44:34.557Z" },
{ url = "https://files.pythonhosted.org/packages/fb/ab/e04bf58a8d375aeedb5268edcc835c6a660ebf79d4384d8e0889439448b0/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58f77c60956501a4a627749a6dcb78dac522f249dd96b5c9f1c6af29bfacfb66", size = 558891, upload-time = "2025-05-21T12:44:37.358Z" },
{ url = "https://files.pythonhosted.org/packages/90/82/cb8c6028a6ef6cd2b7991e2e4ced01c854b6236ecf51e81b64b569c43d73/rpds_py-0.25.1-cp313-cp313t-win32.whl", hash = "sha256:2cb9e5b5e26fc02c8a4345048cd9998c2aca7c2712bd1b36da0c72ee969a3523", size = 218718, upload-time = "2025-05-21T12:44:38.969Z" },
{ url = "https://files.pythonhosted.org/packages/b6/97/5a4b59697111c89477d20ba8a44df9ca16b41e737fa569d5ae8bff99e650/rpds_py-0.25.1-cp313-cp313t-win_amd64.whl", hash = "sha256:401ca1c4a20cc0510d3435d89c069fe0a9ae2ee6495135ac46bdd49ec0495763", size = 232218, upload-time = "2025-05-21T12:44:40.512Z" },
]
[[package]]
name = "ruff"
version = "0.11.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289, upload-time = "2025-05-29T13:31:40.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597, upload-time = "2025-05-29T13:30:57.539Z" },
{ url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154, upload-time = "2025-05-29T13:31:00.865Z" },
{ url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048, upload-time = "2025-05-29T13:31:03.413Z" },
{ url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062, upload-time = "2025-05-29T13:31:05.539Z" },
{ url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152, upload-time = "2025-05-29T13:31:07.986Z" },
{ url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067, upload-time = "2025-05-29T13:31:10.57Z" },
{ url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807, upload-time = "2025-05-29T13:31:12.88Z" },
{ url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261, upload-time = "2025-05-29T13:31:15.236Z" },
{ url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601, upload-time = "2025-05-29T13:31:18.68Z" },
{ url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186, upload-time = "2025-05-29T13:31:21.216Z" },
{ url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032, upload-time = "2025-05-29T13:31:23.417Z" },
{ url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370, upload-time = "2025-05-29T13:31:25.777Z" },
{ url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529, upload-time = "2025-05-29T13:31:28.396Z" },
{ url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642, upload-time = "2025-05-29T13:31:30.647Z" },
{ url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511, upload-time = "2025-05-29T13:31:32.917Z" },
{ url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573, upload-time = "2025-05-29T13:31:35.782Z" },
{ url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" },
]
[[package]]
name = "scalene"
version = "1.5.51"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cloudpickle" },
{ name = "jinja2" },
{ name = "numpy" },
{ name = "nvidia-ml-py", marker = "sys_platform != 'darwin'" },
{ name = "psutil" },
{ name = "pydantic" },
{ name = "rich" },
{ name = "wheel" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4c/a4/e35a4e22a309ad6a886f0f3a66fd25ae1d0317d1aa81d21b79b3fe1b7cb9/scalene-1.5.51.tar.gz", hash = "sha256:ad33b6ce79239b5a6aff4ec78fa576fe2076b46f78c4c7e5fbc78a927b83374d", size = 9168270, upload-time = "2025-01-27T22:26:31.834Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/33/b385ffba87420535353977d8f5cafe3c0678430681720f61c2bad677589b/scalene-1.5.51-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:2a194c427fabff8e0a6b7fe2f3827b6e24db84f830c4bff8a0742d59fb2804ed", size = 973797, upload-time = "2025-01-27T22:27:31.56Z" },
{ url = "https://files.pythonhosted.org/packages/b4/a4/bc99cdab5309e5143d0c0780c44fa3bf747b550af2beaa9cbc2622a844bc/scalene-1.5.51-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:d563356192ff59a4d5e80a921db6d960ebbda6a4e36765cebd2b57b7ca341cc4", size = 973208, upload-time = "2025-01-27T22:27:11.631Z" },
{ url = "https://files.pythonhosted.org/packages/23/98/587b1f21598fc4bba2195c9061ed76886419a1ec168e77865d0139de26eb/scalene-1.5.51-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:839c22aad181345ee19fdf8a7c91d9e232583f471df318e1683e381f8d7b28f0", size = 1247714, upload-time = "2025-01-27T22:22:39.376Z" },
{ url = "https://files.pythonhosted.org/packages/09/62/a3ea6afe4498a3cc2d45c7e544a766ff44449cc25596cbbea43df6c56d01/scalene-1.5.51-cp313-cp313-win_amd64.whl", hash = "sha256:bdb261a2f7f17724fe27e4c7d703136a75dd45da940c18db7c6cbe8937d5315f", size = 862381, upload-time = "2025-01-27T22:23:03.93Z" },
]
[[package]]
name = "sentry-sdk"
version = "2.29.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/22/67/d552a5f8e5a6a56b2feea6529e2d8ccd54349084c84176d5a1f7295044bc/sentry_sdk-2.29.1.tar.gz", hash = "sha256:8d4a0206b95fa5fe85e5e7517ed662e3888374bdc342c00e435e10e6d831aa6d", size = 325518, upload-time = "2025-05-19T14:27:38.512Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f0/e5/da07b0bd832cefd52d16f2b9bbbe31624d57552602c06631686b93ccb1bd/sentry_sdk-2.29.1-py2.py3-none-any.whl", hash = "sha256:90862fe0616ded4572da6c9dadb363121a1ae49a49e21c418f0634e9d10b4c19", size = 341553, upload-time = "2025-05-19T14:27:36.882Z" },
]
[[package]]
name = "setproctitle"
version = "1.3.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889, upload-time = "2025-04-29T13:35:00.184Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/76/f1a2fdbf9b9602945a7489ba5c52e9863de37381ef1a85a2b9ed0ff8bc79/setproctitle-1.3.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2a9e62647dc040a76d55563580bf3bb8fe1f5b6ead08447c2ed0d7786e5e794", size = 17392, upload-time = "2025-04-29T13:33:30.925Z" },
{ url = "https://files.pythonhosted.org/packages/5c/5b/4e0db8b10b4543afcb3dbc0827793d46e43ec1de6b377e313af3703d08e0/setproctitle-1.3.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:751ba352ed922e0af60458e961167fa7b732ac31c0ddd1476a2dfd30ab5958c5", size = 11951, upload-time = "2025-04-29T13:33:32.296Z" },
{ url = "https://files.pythonhosted.org/packages/dc/fe/d5d00aaa700fe1f6160b6e95c225b29c01f4d9292176d48fd968815163ea/setproctitle-1.3.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7890e291bf4708e3b61db9069ea39b3ab0651e42923a5e1f4d78a7b9e4b18301", size = 32087, upload-time = "2025-04-29T13:33:33.469Z" },
{ url = "https://files.pythonhosted.org/packages/9f/b3/894b827b93ef813c082479bebf88185860f01ac243df737823dd705e7fff/setproctitle-1.3.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2b17855ed7f994f3f259cf2dfbfad78814538536fa1a91b50253d84d87fd88d", size = 33502, upload-time = "2025-04-29T13:33:34.831Z" },
{ url = "https://files.pythonhosted.org/packages/b2/cd/5330734cca1a4cfcb721432c22cb7899ff15a4101ba868b2ef452ffafea1/setproctitle-1.3.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e51ec673513465663008ce402171192a053564865c2fc6dc840620871a9bd7c", size = 30713, upload-time = "2025-04-29T13:33:36.739Z" },
{ url = "https://files.pythonhosted.org/packages/fa/d3/c2590c5daa2e9a008d3f2b16c0f4a351826193be55f147cb32af49c6d814/setproctitle-1.3.6-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63cc10352dc6cf35a33951656aa660d99f25f574eb78132ce41a85001a638aa7", size = 31792, upload-time = "2025-04-29T13:33:37.974Z" },
{ url = "https://files.pythonhosted.org/packages/e6/b1/c553ed5af8cfcecd5ae7737e63af58a17a03d26f3d61868c7eb20bf7e3cf/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dba8faee2e4a96e934797c9f0f2d093f8239bf210406a99060b3eabe549628e", size = 31927, upload-time = "2025-04-29T13:33:39.203Z" },
{ url = "https://files.pythonhosted.org/packages/70/78/2d5385206540127a3dca0ff83225b1ac66873f5cc89d4a6d3806c92f5ae2/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e3e44d08b61de0dd6f205528498f834a51a5c06689f8fb182fe26f3a3ce7dca9", size = 30981, upload-time = "2025-04-29T13:33:40.431Z" },
{ url = "https://files.pythonhosted.org/packages/31/62/e3e4a4e006d0e549748e53cded4ff3b667be0602860fc61b7de8b412b667/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:de004939fc3fd0c1200d26ea9264350bfe501ffbf46c8cf5dc7f345f2d87a7f1", size = 33244, upload-time = "2025-04-29T13:33:41.817Z" },
{ url = "https://files.pythonhosted.org/packages/aa/05/4b223fd4ef94e105dc7aff27fa502fb7200cf52be2bb0c064bd2406b5611/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f8194b4d631b003a1176a75d1acd545e04b1f54b821638e098a93e6e62830ef", size = 31630, upload-time = "2025-04-29T13:33:43.093Z" },
{ url = "https://files.pythonhosted.org/packages/1b/ba/5f68eb969f7336f54b54a599fd3ffbd7662f9733b080bc8598705971b3dd/setproctitle-1.3.6-cp313-cp313-win32.whl", hash = "sha256:d714e002dd3638170fe7376dc1b686dbac9cb712cde3f7224440af722cc9866a", size = 11480, upload-time = "2025-04-29T13:34:01.257Z" },
{ url = "https://files.pythonhosted.org/packages/ba/f5/7f47f0ca35c9c357f16187cee9229f3eda0237bc6fdd3061441336f361c0/setproctitle-1.3.6-cp313-cp313-win_amd64.whl", hash = "sha256:b70c07409d465f3a8b34d52f863871fb8a00755370791d2bd1d4f82b3cdaf3d5", size = 12198, upload-time = "2025-04-29T13:34:02.293Z" },
{ url = "https://files.pythonhosted.org/packages/39/ad/c3941b8fc6b32a976c9e2d9615a90ae793b69cd010ca8c3575dbc822104f/setproctitle-1.3.6-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:23a57d3b8f1549515c2dbe4a2880ebc1f27780dc126c5e064167563e015817f5", size = 17401, upload-time = "2025-04-29T13:33:44.186Z" },
{ url = "https://files.pythonhosted.org/packages/04/38/a184f857b988d3a9c401e470a4e38182a5c99ee77bf90432d7665e9d35a3/setproctitle-1.3.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81c443310831e29fabbd07b75ebbfa29d0740b56f5907c6af218482d51260431", size = 11959, upload-time = "2025-04-29T13:33:45.71Z" },
{ url = "https://files.pythonhosted.org/packages/b7/b9/4878ef9d8483adfd1edf6bf95151362aaec0d05aac306a97ff0383f491b5/setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d88c63bd395c787b0aa81d8bbc22c1809f311032ce3e823a6517b711129818e4", size = 33463, upload-time = "2025-04-29T13:33:46.913Z" },
{ url = "https://files.pythonhosted.org/packages/cc/60/3ef49d1931aff2a36a7324a49cca10d77ef03e0278452fd468c33a52d7e3/setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73f14b86d0e2858ece6bf5807c9889670e392c001d414b4293d0d9b291942c3", size = 34959, upload-time = "2025-04-29T13:33:48.216Z" },
{ url = "https://files.pythonhosted.org/packages/81/c6/dee0a973acecefb0db6c9c2e0ea7f18b7e4db773a72e534741ebdee8bbb8/setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3393859eb8f19f5804049a685bf286cb08d447e28ba5c6d8543c7bf5500d5970", size = 32055, upload-time = "2025-04-29T13:33:49.443Z" },
{ url = "https://files.pythonhosted.org/packages/ea/a5/5dd5c4192cf18d16349a32a07f728a9a48a2a05178e16966cabd6645903e/setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785cd210c0311d9be28a70e281a914486d62bfd44ac926fcd70cf0b4d65dff1c", size = 32986, upload-time = "2025-04-29T13:33:51.519Z" },
{ url = "https://files.pythonhosted.org/packages/df/a6/1508d37eb8008670d33f13fcdb91cbd8ef54697276469abbfdd3d4428c59/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c051f46ed1e13ba8214b334cbf21902102807582fbfaf0fef341b9e52f0fafbf", size = 32736, upload-time = "2025-04-29T13:33:52.852Z" },
{ url = "https://files.pythonhosted.org/packages/1a/73/c84ec8880d543766a12fcd6b65dbd013770974a40577889f357409b0441e/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:49498ebf68ca3e75321ffe634fcea5cc720502bfaa79bd6b03ded92ce0dc3c24", size = 31945, upload-time = "2025-04-29T13:33:54.665Z" },
{ url = "https://files.pythonhosted.org/packages/95/0a/126b9ff7a406a69a62825fe5bd6d1ba8671919a7018c4f9e2c63f49bfcb6/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4431629c178193f23c538cb1de3da285a99ccc86b20ee91d81eb5f1a80e0d2ba", size = 34333, upload-time = "2025-04-29T13:33:56.101Z" },
{ url = "https://files.pythonhosted.org/packages/9a/fd/5474b04f1c013ff460129d2bc774557dd6e186da4667865efef9a83bf378/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d136fbf8ad4321716e44d6d6b3d8dffb4872626010884e07a1db54b7450836cf", size = 32508, upload-time = "2025-04-29T13:33:57.43Z" },
{ url = "https://files.pythonhosted.org/packages/32/21/2503e38520cb076a7ecaef6a35d6a6fa89cf02af3541c84c811fd7500d20/setproctitle-1.3.6-cp313-cp313t-win32.whl", hash = "sha256:d483cc23cc56ab32911ea0baa0d2d9ea7aa065987f47de847a0a93a58bf57905", size = 11482, upload-time = "2025-04-29T13:33:58.602Z" },
{ url = "https://files.pythonhosted.org/packages/65/23/7833d75a27fba25ddc5cd3b54cd03c4bf8e18b8e2dbec622eb6326278ce8/setproctitle-1.3.6-cp313-cp313t-win_amd64.whl", hash = "sha256:74973aebea3543ad033b9103db30579ec2b950a466e09f9c2180089e8346e0ec", size = 12209, upload-time = "2025-04-29T13:33:59.727Z" },
]
[[package]]
name = "setuptools"
version = "80.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "smmap"
version = "5.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "streamlit"
version = "1.45.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altair" },
{ name = "blinker" },
{ name = "cachetools" },
{ name = "click" },
{ name = "gitpython" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "pandas" },
{ name = "pillow" },
{ name = "protobuf" },
{ name = "pyarrow" },
{ name = "pydeck" },
{ name = "requests" },
{ name = "tenacity" },
{ name = "toml" },
{ name = "tornado" },
{ name = "typing-extensions" },
{ name = "watchdog", marker = "sys_platform != 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/46/9b3f73886f82d27849ce1e7a74ae7c39f5323e46da0b6e8847ad4c25f44c/streamlit-1.45.1.tar.gz", hash = "sha256:e37d56c0af5240dbc240976880e81366689c290a559376417246f9b3f51b4217", size = 9463953, upload-time = "2025-05-12T20:40:30.562Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/e6/69fcbae3dd2fcb2f54283a7cbe03c8b944b79997f1b526984f91d4796a02/streamlit-1.45.1-py3-none-any.whl", hash = "sha256:9ab6951585e9444672dd650850f81767b01bba5d87c8dac9bc2e1c859d6cc254", size = 9856294, upload-time = "2025-05-12T20:40:27.875Z" },
]
[[package]]
name = "tavily-python"
version = "0.7.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "requests" },
{ name = "tiktoken" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a7/ee/8a451c724d814d97c1fda8c6203bc9563c7fe104e7c0214f83574326525e/tavily_python-0.7.3.tar.gz", hash = "sha256:f091be8bfed28ab42ff239cf2c46f2ab3715071bf19951900157b3e4a60151c4", size = 16657, upload-time = "2025-05-25T15:15:29.017Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/82/0beda7ea25f1f586b1ae55b1b5399f999171ab12f87db1159fa6f00c2641/tavily_python-0.7.3-py3-none-any.whl", hash = "sha256:ed92e49e5be75a790ff7e36571bdb2982f179b34ca19f91d764ac7368219a8e5", size = 15022, upload-time = "2025-05-25T15:15:26.61Z" },
]
[[package]]
name = "tenacity"
version = "9.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
]
[[package]]
name = "termcolor"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/10/56/d7d66a84f96d804155f6ff2873d065368b25a07222a6fd51c4f24ef6d764/termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a", size = 12664, upload-time = "2023-12-01T11:04:51.66Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/5f/8c716e47b3a50cbd7c146f45881e11d9414def768b7cd9c5e6650ec2a80a/termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63", size = 7719, upload-time = "2023-12-01T11:04:50.019Z" },
]
[[package]]
name = "tiktoken"
version = "0.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "regex" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991, upload-time = "2025-02-14T06:03:01.003Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/11/09d936d37f49f4f494ffe660af44acd2d99eb2429d60a57c71318af214e0/tiktoken-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b0e8e05a26eda1249e824156d537015480af7ae222ccb798e5234ae0285dbdb", size = 1064919, upload-time = "2025-02-14T06:02:37.494Z" },
{ url = "https://files.pythonhosted.org/packages/80/0e/f38ba35713edb8d4197ae602e80837d574244ced7fb1b6070b31c29816e0/tiktoken-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27d457f096f87685195eea0165a1807fae87b97b2161fe8c9b1df5bd74ca6f63", size = 1007877, upload-time = "2025-02-14T06:02:39.516Z" },
{ url = "https://files.pythonhosted.org/packages/fe/82/9197f77421e2a01373e27a79dd36efdd99e6b4115746ecc553318ecafbf0/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf8ded49cddf825390e36dd1ad35cd49589e8161fdcb52aa25f0583e90a3e01", size = 1140095, upload-time = "2025-02-14T06:02:41.791Z" },
{ url = "https://files.pythonhosted.org/packages/f2/bb/4513da71cac187383541facd0291c4572b03ec23c561de5811781bbd988f/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc156cb314119a8bb9748257a2eaebd5cc0753b6cb491d26694ed42fc7cb3139", size = 1195649, upload-time = "2025-02-14T06:02:43Z" },
{ url = "https://files.pythonhosted.org/packages/fa/5c/74e4c137530dd8504e97e3a41729b1103a4ac29036cbfd3250b11fd29451/tiktoken-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cd69372e8c9dd761f0ab873112aba55a0e3e506332dd9f7522ca466e817b1b7a", size = 1258465, upload-time = "2025-02-14T06:02:45.046Z" },
{ url = "https://files.pythonhosted.org/packages/de/a8/8f499c179ec900783ffe133e9aab10044481679bb9aad78436d239eee716/tiktoken-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ea0edb6f83dc56d794723286215918c1cde03712cbbafa0348b33448faf5b95", size = 894669, upload-time = "2025-02-14T06:02:47.341Z" },
]
[[package]]
name = "toml"
version = "0.10.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
]
[[package]]
name = "tornado"
version = "6.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934, upload-time = "2025-05-22T18:15:38.788Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948, upload-time = "2025-05-22T18:15:20.862Z" },
{ url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112, upload-time = "2025-05-22T18:15:22.591Z" },
{ url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672, upload-time = "2025-05-22T18:15:24.027Z" },
{ url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019, upload-time = "2025-05-22T18:15:25.735Z" },
{ url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252, upload-time = "2025-05-22T18:15:27.499Z" },
{ url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930, upload-time = "2025-05-22T18:15:29.299Z" },
{ url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351, upload-time = "2025-05-22T18:15:31.038Z" },
{ url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328, upload-time = "2025-05-22T18:15:32.426Z" },
{ url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396, upload-time = "2025-05-22T18:15:34.205Z" },
{ url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840, upload-time = "2025-05-22T18:15:36.1Z" },
{ url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596, upload-time = "2025-05-22T18:15:37.433Z" },
]
[[package]]
name = "tqdm"
version = "4.67.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
]
[[package]]
name = "typing-extensions"
version = "4.13.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
]
[[package]]
name = "tzdata"
version = "2025.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
]
[[package]]
name = "urllib3"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
]
[[package]]
name = "uuid-utils"
version = "0.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/24/7f/7d83b937889d65682d95b40c94ba226b353d3f532290ee3acb17c8746e49/uuid_utils-0.11.0.tar.gz", hash = "sha256:18cf2b7083da7f3cca0517647213129eb16d20d7ed0dd74b3f4f8bff2aa334ea", size = 18854, upload-time = "2025-05-22T11:23:15.596Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/20/4a34f2a6e77b1f0f3334b111e4d2411fc8646ab2987892a36507e2d6a498/uuid_utils-0.11.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:094445ccd323bc5507e28e9d6d86b983513efcf19ab59c2dd75239cef765631a", size = 593779, upload-time = "2025-05-22T11:22:41.36Z" },
{ url = "https://files.pythonhosted.org/packages/a1/a1/1897cd3d37144f698392ec8aae89da2c00c6d34acd77f75312477f4510ab/uuid_utils-0.11.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6430b53d343215f85269ffd74e1d1f4b25ae1031acf0ac24ff3d5721f6a06f48", size = 300848, upload-time = "2025-05-22T11:22:43.221Z" },
{ url = "https://files.pythonhosted.org/packages/d4/36/3ae8896de8a5320a9e7529452ed29af0082daf8c3787f17c5cbf9defc651/uuid_utils-0.11.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be2e6e4318d23195887fa74fa1d64565a34f7127fdcf22918954981d79765f68", size = 336053, upload-time = "2025-05-22T11:22:44.741Z" },
{ url = "https://files.pythonhosted.org/packages/fe/b6/751e84cd056074a40ca9ac21db6ca4802e31d78207309c0d9c8ff69cd43b/uuid_utils-0.11.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d37289ab72aa30b5550bfa64d91431c62c89e4969bdf989988aa97f918d5f803", size = 338529, upload-time = "2025-05-22T11:22:46.303Z" },
{ url = "https://files.pythonhosted.org/packages/3b/c2/f6a1c00a1b067a886fc57c24da46bb0bcb753c92afb898871c6df3ae606f/uuid_utils-0.11.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1012595220f945fe09641f1365a8a06915bf432cac1b31ebd262944934a9b787", size = 480378, upload-time = "2025-05-22T11:22:47.482Z" },
{ url = "https://files.pythonhosted.org/packages/60/ea/cefc0521e07a35e85416d145382ac4817957cdec037271d0c9e27cbc7d45/uuid_utils-0.11.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35cd3fc718a673e4516e87afb9325558969eca513aa734515b9031d1b651bbb1", size = 332220, upload-time = "2025-05-22T11:22:48.55Z" },
{ url = "https://files.pythonhosted.org/packages/03/91/5929f209bd4660a7e3b4d47d26189d3cf33e14297312a5f51f5451805fec/uuid_utils-0.11.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed325e0c40e0f59ae82b347f534df954b50cedf12bf60d025625538530e1965d", size = 359052, upload-time = "2025-05-22T11:22:49.617Z" },
{ url = "https://files.pythonhosted.org/packages/d8/0d/32034d5b13bc07dd95f23122cb743b4eeca8e6d88173ea3c7100c67b6269/uuid_utils-0.11.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5c8b7cf201990ee3140956e541967bd556a7365ec738cb504b04187ad89c757a", size = 515186, upload-time = "2025-05-22T11:22:50.808Z" },
{ url = "https://files.pythonhosted.org/packages/e7/43/ccf2474f723d6de5e214c22999ffb34219acf83d1e3fff6a4734172e10c0/uuid_utils-0.11.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9966df55bed5d538ba2e9cc40115796480f437f9007727116ef99dc2f42bd5fa", size = 535318, upload-time = "2025-05-22T11:22:52.304Z" },
{ url = "https://files.pythonhosted.org/packages/fb/05/f668b4ad2b3542cd021c4b27d1ff4e425f854f299bcf7ee36f304399a58c/uuid_utils-0.11.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb04b6c604968424b7e6398d54debbdd5b771b39fc1e648c6eabf3f1dc20582e", size = 502691, upload-time = "2025-05-22T11:22:53.483Z" },
{ url = "https://files.pythonhosted.org/packages/9e/0b/b906301638eef837c89b19206989dbe27794c591d794ecc06167d9a47c41/uuid_utils-0.11.0-cp39-abi3-win32.whl", hash = "sha256:18420eb3316bb514f09f2da15750ac135478c3a12a704e2c5fb59eab642bb255", size = 180147, upload-time = "2025-05-22T11:22:54.598Z" },
{ url = "https://files.pythonhosted.org/packages/56/99/ad24ee5ecfc5fbd4a4490bb59c0e72ce604d5eef08683d345546ff6a6f2d/uuid_utils-0.11.0-cp39-abi3-win_amd64.whl", hash = "sha256:37c4805af61a7cce899597d34e7c3dd5cb6a8b4b93a90fbca3826b071ba544df", size = 183574, upload-time = "2025-05-22T11:22:55.581Z" },
{ url = "https://files.pythonhosted.org/packages/0e/76/2301b1d34defc8c234596ffb6e6d456cd7ef061d108e10a14ceda5ec5d4b/uuid_utils-0.11.0-cp39-abi3-win_arm64.whl", hash = "sha256:4065cf17bbe97f6d8ccc7dc6a0bae7d28fd4797d7f32028a5abd979aeb7bf7c9", size = 181014, upload-time = "2025-05-22T11:22:56.575Z" },
]
[[package]]
name = "wandb"
version = "0.19.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "docker-pycreds" },
{ name = "gitpython" },
{ name = "platformdirs" },
{ name = "protobuf" },
{ name = "psutil" },
{ name = "pydantic" },
{ name = "pyyaml" },
{ name = "requests" },
{ name = "sentry-sdk" },
{ name = "setproctitle" },
{ name = "setuptools" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/39/98/0ff2925a21b998d4b84731429f4554ca3d9b5cad42c09c075e7306c3aca0/wandb-0.19.11.tar.gz", hash = "sha256:3f50a27dfadbb25946a513ffe856c0e8e538b5626ef207aa50b00c3b0356bff8", size = 39511477, upload-time = "2025-05-07T20:50:01.341Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4f/2c/f8bab58c73fdde4442f1baffd9ea5d1bb3113906a97a27e8d9ab72db7a69/wandb-0.19.11-py3-none-any.whl", hash = "sha256:ff3bf050ba25ebae7aedc9a775ffab90c28068832edfe5458423f488c2558f82", size = 6481327, upload-time = "2025-05-07T20:49:33.461Z" },
{ url = "https://files.pythonhosted.org/packages/45/4a/34b364280f690f4c6d7660f528fba9f13bdecabc4c869d266a4632cf836e/wandb-0.19.11-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:0823fd9aa6343f40c04e01959997ca8c6d6adf1bd81c8d45261fa4915f1c6b67", size = 20555751, upload-time = "2025-05-07T20:49:36.392Z" },
{ url = "https://files.pythonhosted.org/packages/d8/e6/a27868fdb83a60df37b9d15e52c3353dd88d74442f27ae48cf765c6b9554/wandb-0.19.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c758ef5439599d9023db5b3cf1698477055d82f9fae48af2779f63f1d289167c", size = 20377587, upload-time = "2025-05-07T20:49:39.126Z" },
{ url = "https://files.pythonhosted.org/packages/21/f7/d5cf5b58c2b3015364c7b2b6af6a440cbeda4103b67332e1e64b30f6252d/wandb-0.19.11-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:de2dfd4911e7691735e271654c735e7b90cdee9d29a3796fbf06e9e92d48f3d7", size = 20985041, upload-time = "2025-05-07T20:49:41.571Z" },
{ url = "https://files.pythonhosted.org/packages/68/06/8b827f16a0b8f18002d2fffa7c5a7fd447946e0d0c68aeec0dd7eb18cdd3/wandb-0.19.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfff738850770d26b13f8f3fe400a6456f1e39e87f3f29d5aa241b249476df95", size = 20017696, upload-time = "2025-05-07T20:49:44.04Z" },
{ url = "https://files.pythonhosted.org/packages/f9/31/eeb2878b26566c04c3e9b8b20b3ec3c54a2be50535088d36a37c008e07a3/wandb-0.19.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ff673007448df11cc69379ae0df28ead866800dc1ec7bc151b402db0bbcf40", size = 21425857, upload-time = "2025-05-07T20:49:46.347Z" },
{ url = "https://files.pythonhosted.org/packages/10/30/08988360678ae78334bb16625c28260fcaba49f500b89f8766807cb74d71/wandb-0.19.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:858bc5023fa1b3285d89d15f62be78afdb28301064daa49ea3f4ebde5dcedad2", size = 20023145, upload-time = "2025-05-07T20:49:48.965Z" },
{ url = "https://files.pythonhosted.org/packages/c8/e9/a639c42c8ca517c4d25e8970d64d0c5a9bd35b784faed5f47d9cca3dcd12/wandb-0.19.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90e4b57649896acb16c3dd41b3093df1a169c2f1d94ff15d76af86b8a60dcdac", size = 21504842, upload-time = "2025-05-07T20:49:51.628Z" },
{ url = "https://files.pythonhosted.org/packages/44/74/dbe9277dd935b77dd16939cdf15357766fec0813a6e336cf5f1d07eb016e/wandb-0.19.11-py3-none-win32.whl", hash = "sha256:38dea43c7926d8800405a73b80b9adfe81eb315fc6f2ac6885c77eb966634421", size = 20767584, upload-time = "2025-05-07T20:49:56.629Z" },
{ url = "https://files.pythonhosted.org/packages/36/d5/215cac3edec5c5ac6e7231beb9d22466d5d4e4a132fa3a1d044f7d682c15/wandb-0.19.11-py3-none-win_amd64.whl", hash = "sha256:73402003c56ddc2198878492ab2bff55bb49bce5587eae5960e737d27c0c48f7", size = 20767588, upload-time = "2025-05-07T20:49:58.85Z" },
]
[[package]]
name = "watchdog"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
{ url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
{ url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
]
[[package]]
name = "wcmatch"
version = "10.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bracex" },
]
sdist = { url = "https://files.pythonhosted.org/packages/41/ab/b3a52228538ccb983653c446c1656eddf1d5303b9cb8b9aef6a91299f862/wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a", size = 115578, upload-time = "2024-09-26T18:39:52.505Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/df/4ee467ab39cc1de4b852c212c1ed3becfec2e486a51ac1ce0091f85f38d7/wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a", size = 39347, upload-time = "2024-09-26T18:39:51.002Z" },
]
[[package]]
name = "weave"
version = "0.51.49"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "diskcache" },
{ name = "emoji" },
{ name = "gql", extra = ["aiohttp", "requests"] },
{ name = "jsonschema" },
{ name = "nest-asyncio" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "rich" },
{ name = "tenacity" },
{ name = "uuid-utils" },
{ name = "wandb" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cb/1a/d48615af7598e84134f89786238d693350d311b4977607e9f4bf0db5d5cf/weave-0.51.49.tar.gz", hash = "sha256:ab833f88024ddde5efebe98f6d16fa9ec6421d6fcaaa287355dff695b1f1f594", size = 406894, upload-time = "2025-05-28T22:59:53.993Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/2d/d9307210f9c323d8538c5bdaf9ded156da656ea46b1dde5de9802fa06987/weave-0.51.49-py3-none-any.whl", hash = "sha256:1971f01c90fe92b60a2f4b2c728868586af4519e27e7a6d20965da2943581152", size = 518761, upload-time = "2025-05-28T22:59:51.679Z" },
]
[[package]]
name = "wheel"
version = "0.45.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" },
]
[[package]]
name = "win32-setctime"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
]
[[package]]
name = "wrapt"
version = "1.17.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload-time = "2025-01-14T10:34:21.571Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload-time = "2025-01-14T10:34:22.999Z" },
{ url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload-time = "2025-01-14T10:34:25.386Z" },
{ url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690, upload-time = "2025-01-14T10:34:28.058Z" },
{ url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861, upload-time = "2025-01-14T10:34:29.167Z" },
{ url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174, upload-time = "2025-01-14T10:34:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721, upload-time = "2025-01-14T10:34:32.91Z" },
{ url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763, upload-time = "2025-01-14T10:34:34.903Z" },
{ url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585, upload-time = "2025-01-14T10:34:36.13Z" },
{ url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676, upload-time = "2025-01-14T10:34:37.962Z" },
{ url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871, upload-time = "2025-01-14T10:34:39.13Z" },
{ url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312, upload-time = "2025-01-14T10:34:40.604Z" },
{ url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062, upload-time = "2025-01-14T10:34:45.011Z" },
{ url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155, upload-time = "2025-01-14T10:34:47.25Z" },
{ url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471, upload-time = "2025-01-14T10:34:50.934Z" },
{ url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208, upload-time = "2025-01-14T10:34:52.297Z" },
{ url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339, upload-time = "2025-01-14T10:34:53.489Z" },
{ url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232, upload-time = "2025-01-14T10:34:55.327Z" },
{ url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476, upload-time = "2025-01-14T10:34:58.055Z" },
{ url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377, upload-time = "2025-01-14T10:34:59.3Z" },
{ url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986, upload-time = "2025-01-14T10:35:00.498Z" },
{ url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload-time = "2025-01-14T10:35:03.378Z" },
{ url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" },
]
[[package]]
name = "yarl"
version = "1.20.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "multidict" },
{ name = "propcache" },
]
sdist = { url = "https://files.pythonhosted.org/packages/62/51/c0edba5219027f6eab262e139f73e2417b0f4efffa23bf562f6e18f76ca5/yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307", size = 185258, upload-time = "2025-04-17T00:45:14.661Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0f/6f/514c9bff2900c22a4f10e06297714dbaf98707143b37ff0bcba65a956221/yarl-1.20.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2137810a20b933b1b1b7e5cf06a64c3ed3b4747b0e5d79c9447c00db0e2f752f", size = 145030, upload-time = "2025-04-17T00:43:15.083Z" },
{ url = "https://files.pythonhosted.org/packages/4e/9d/f88da3fa319b8c9c813389bfb3463e8d777c62654c7168e580a13fadff05/yarl-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:447c5eadd750db8389804030d15f43d30435ed47af1313303ed82a62388176d3", size = 96894, upload-time = "2025-04-17T00:43:17.372Z" },
{ url = "https://files.pythonhosted.org/packages/cd/57/92e83538580a6968b2451d6c89c5579938a7309d4785748e8ad42ddafdce/yarl-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42fbe577272c203528d402eec8bf4b2d14fd49ecfec92272334270b850e9cd7d", size = 94457, upload-time = "2025-04-17T00:43:19.431Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ee/7ee43bd4cf82dddd5da97fcaddb6fa541ab81f3ed564c42f146c83ae17ce/yarl-1.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e321617de4ab170226cd15006a565d0fa0d908f11f724a2c9142d6b2812ab0", size = 343070, upload-time = "2025-04-17T00:43:21.426Z" },
{ url = "https://files.pythonhosted.org/packages/4a/12/b5eccd1109e2097bcc494ba7dc5de156e41cf8309fab437ebb7c2b296ce3/yarl-1.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4345f58719825bba29895011e8e3b545e6e00257abb984f9f27fe923afca2501", size = 337739, upload-time = "2025-04-17T00:43:23.634Z" },
{ url = "https://files.pythonhosted.org/packages/7d/6b/0eade8e49af9fc2585552f63c76fa59ef469c724cc05b29519b19aa3a6d5/yarl-1.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d9b980d7234614bc4674468ab173ed77d678349c860c3af83b1fffb6a837ddc", size = 351338, upload-time = "2025-04-17T00:43:25.695Z" },
{ url = "https://files.pythonhosted.org/packages/45/cb/aaaa75d30087b5183c7b8a07b4fb16ae0682dd149a1719b3a28f54061754/yarl-1.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af4baa8a445977831cbaa91a9a84cc09debb10bc8391f128da2f7bd070fc351d", size = 353636, upload-time = "2025-04-17T00:43:27.876Z" },
{ url = "https://files.pythonhosted.org/packages/98/9d/d9cb39ec68a91ba6e66fa86d97003f58570327d6713833edf7ad6ce9dde5/yarl-1.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123393db7420e71d6ce40d24885a9e65eb1edefc7a5228db2d62bcab3386a5c0", size = 348061, upload-time = "2025-04-17T00:43:29.788Z" },
{ url = "https://files.pythonhosted.org/packages/72/6b/103940aae893d0cc770b4c36ce80e2ed86fcb863d48ea80a752b8bda9303/yarl-1.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab47acc9332f3de1b39e9b702d9c916af7f02656b2a86a474d9db4e53ef8fd7a", size = 334150, upload-time = "2025-04-17T00:43:31.742Z" },
{ url = "https://files.pythonhosted.org/packages/ef/b2/986bd82aa222c3e6b211a69c9081ba46484cffa9fab2a5235e8d18ca7a27/yarl-1.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4a34c52ed158f89876cba9c600b2c964dfc1ca52ba7b3ab6deb722d1d8be6df2", size = 362207, upload-time = "2025-04-17T00:43:34.099Z" },
{ url = "https://files.pythonhosted.org/packages/14/7c/63f5922437b873795d9422cbe7eb2509d4b540c37ae5548a4bb68fd2c546/yarl-1.20.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:04d8cfb12714158abf2618f792c77bc5c3d8c5f37353e79509608be4f18705c9", size = 361277, upload-time = "2025-04-17T00:43:36.202Z" },
{ url = "https://files.pythonhosted.org/packages/81/83/450938cccf732466953406570bdb42c62b5ffb0ac7ac75a1f267773ab5c8/yarl-1.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7dc63ad0d541c38b6ae2255aaa794434293964677d5c1ec5d0116b0e308031f5", size = 364990, upload-time = "2025-04-17T00:43:38.551Z" },
{ url = "https://files.pythonhosted.org/packages/b4/de/af47d3a47e4a833693b9ec8e87debb20f09d9fdc9139b207b09a3e6cbd5a/yarl-1.20.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d02b591a64e4e6ca18c5e3d925f11b559c763b950184a64cf47d74d7e41877", size = 374684, upload-time = "2025-04-17T00:43:40.481Z" },
{ url = "https://files.pythonhosted.org/packages/62/0b/078bcc2d539f1faffdc7d32cb29a2d7caa65f1a6f7e40795d8485db21851/yarl-1.20.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:95fc9876f917cac7f757df80a5dda9de59d423568460fe75d128c813b9af558e", size = 382599, upload-time = "2025-04-17T00:43:42.463Z" },
{ url = "https://files.pythonhosted.org/packages/74/a9/4fdb1a7899f1fb47fd1371e7ba9e94bff73439ce87099d5dd26d285fffe0/yarl-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb769ae5760cd1c6a712135ee7915f9d43f11d9ef769cb3f75a23e398a92d384", size = 378573, upload-time = "2025-04-17T00:43:44.797Z" },
{ url = "https://files.pythonhosted.org/packages/fd/be/29f5156b7a319e4d2e5b51ce622b4dfb3aa8d8204cd2a8a339340fbfad40/yarl-1.20.0-cp313-cp313-win32.whl", hash = "sha256:70e0c580a0292c7414a1cead1e076c9786f685c1fc4757573d2967689b370e62", size = 86051, upload-time = "2025-04-17T00:43:47.076Z" },
{ url = "https://files.pythonhosted.org/packages/52/56/05fa52c32c301da77ec0b5f63d2d9605946fe29defacb2a7ebd473c23b81/yarl-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:4c43030e4b0af775a85be1fa0433119b1565673266a70bf87ef68a9d5ba3174c", size = 92742, upload-time = "2025-04-17T00:43:49.193Z" },
{ url = "https://files.pythonhosted.org/packages/d4/2f/422546794196519152fc2e2f475f0e1d4d094a11995c81a465faf5673ffd/yarl-1.20.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b6c4c3d0d6a0ae9b281e492b1465c72de433b782e6b5001c8e7249e085b69051", size = 163575, upload-time = "2025-04-17T00:43:51.533Z" },
{ url = "https://files.pythonhosted.org/packages/90/fc/67c64ddab6c0b4a169d03c637fb2d2a212b536e1989dec8e7e2c92211b7f/yarl-1.20.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8681700f4e4df891eafa4f69a439a6e7d480d64e52bf460918f58e443bd3da7d", size = 106121, upload-time = "2025-04-17T00:43:53.506Z" },
{ url = "https://files.pythonhosted.org/packages/6d/00/29366b9eba7b6f6baed7d749f12add209b987c4cfbfa418404dbadc0f97c/yarl-1.20.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:84aeb556cb06c00652dbf87c17838eb6d92cfd317799a8092cee0e570ee11229", size = 103815, upload-time = "2025-04-17T00:43:55.41Z" },
{ url = "https://files.pythonhosted.org/packages/28/f4/a2a4c967c8323c03689383dff73396281ced3b35d0ed140580825c826af7/yarl-1.20.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f166eafa78810ddb383e930d62e623d288fb04ec566d1b4790099ae0f31485f1", size = 408231, upload-time = "2025-04-17T00:43:57.825Z" },
{ url = "https://files.pythonhosted.org/packages/0f/a1/66f7ffc0915877d726b70cc7a896ac30b6ac5d1d2760613603b022173635/yarl-1.20.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5d3d6d14754aefc7a458261027a562f024d4f6b8a798adb472277f675857b1eb", size = 390221, upload-time = "2025-04-17T00:44:00.526Z" },
{ url = "https://files.pythonhosted.org/packages/41/15/cc248f0504610283271615e85bf38bc014224122498c2016d13a3a1b8426/yarl-1.20.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a8f64df8ed5d04c51260dbae3cc82e5649834eebea9eadfd829837b8093eb00", size = 411400, upload-time = "2025-04-17T00:44:02.853Z" },
{ url = "https://files.pythonhosted.org/packages/5c/af/f0823d7e092bfb97d24fce6c7269d67fcd1aefade97d0a8189c4452e4d5e/yarl-1.20.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d9949eaf05b4d30e93e4034a7790634bbb41b8be2d07edd26754f2e38e491de", size = 411714, upload-time = "2025-04-17T00:44:04.904Z" },
{ url = "https://files.pythonhosted.org/packages/83/70/be418329eae64b9f1b20ecdaac75d53aef098797d4c2299d82ae6f8e4663/yarl-1.20.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c366b254082d21cc4f08f522ac201d0d83a8b8447ab562732931d31d80eb2a5", size = 404279, upload-time = "2025-04-17T00:44:07.721Z" },
{ url = "https://files.pythonhosted.org/packages/19/f5/52e02f0075f65b4914eb890eea1ba97e6fd91dd821cc33a623aa707b2f67/yarl-1.20.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91bc450c80a2e9685b10e34e41aef3d44ddf99b3a498717938926d05ca493f6a", size = 384044, upload-time = "2025-04-17T00:44:09.708Z" },
{ url = "https://files.pythonhosted.org/packages/6a/36/b0fa25226b03d3f769c68d46170b3e92b00ab3853d73127273ba22474697/yarl-1.20.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c2aa4387de4bc3a5fe158080757748d16567119bef215bec643716b4fbf53f9", size = 416236, upload-time = "2025-04-17T00:44:11.734Z" },
{ url = "https://files.pythonhosted.org/packages/cb/3a/54c828dd35f6831dfdd5a79e6c6b4302ae2c5feca24232a83cb75132b205/yarl-1.20.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d2cbca6760a541189cf87ee54ff891e1d9ea6406079c66341008f7ef6ab61145", size = 402034, upload-time = "2025-04-17T00:44:13.975Z" },
{ url = "https://files.pythonhosted.org/packages/10/97/c7bf5fba488f7e049f9ad69c1b8fdfe3daa2e8916b3d321aa049e361a55a/yarl-1.20.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:798a5074e656f06b9fad1a162be5a32da45237ce19d07884d0b67a0aa9d5fdda", size = 407943, upload-time = "2025-04-17T00:44:16.052Z" },
{ url = "https://files.pythonhosted.org/packages/fd/a4/022d2555c1e8fcff08ad7f0f43e4df3aba34f135bff04dd35d5526ce54ab/yarl-1.20.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f106e75c454288472dbe615accef8248c686958c2e7dd3b8d8ee2669770d020f", size = 423058, upload-time = "2025-04-17T00:44:18.547Z" },
{ url = "https://files.pythonhosted.org/packages/4c/f6/0873a05563e5df29ccf35345a6ae0ac9e66588b41fdb7043a65848f03139/yarl-1.20.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3b60a86551669c23dc5445010534d2c5d8a4e012163218fc9114e857c0586fdd", size = 423792, upload-time = "2025-04-17T00:44:20.639Z" },
{ url = "https://files.pythonhosted.org/packages/9e/35/43fbbd082708fa42e923f314c24f8277a28483d219e049552e5007a9aaca/yarl-1.20.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e429857e341d5e8e15806118e0294f8073ba9c4580637e59ab7b238afca836f", size = 422242, upload-time = "2025-04-17T00:44:22.851Z" },
{ url = "https://files.pythonhosted.org/packages/ed/f7/f0f2500cf0c469beb2050b522c7815c575811627e6d3eb9ec7550ddd0bfe/yarl-1.20.0-cp313-cp313t-win32.whl", hash = "sha256:65a4053580fe88a63e8e4056b427224cd01edfb5f951498bfefca4052f0ce0ac", size = 93816, upload-time = "2025-04-17T00:44:25.491Z" },
{ url = "https://files.pythonhosted.org/packages/3f/93/f73b61353b2a699d489e782c3f5998b59f974ec3156a2050a52dfd7e8946/yarl-1.20.0-cp313-cp313t-win_amd64.whl", hash = "sha256:53b2da3a6ca0a541c1ae799c349788d480e5144cac47dba0266c7cb6c76151fe", size = 101093, upload-time = "2025-04-17T00:44:27.418Z" },
{ url = "https://files.pythonhosted.org/packages/ea/1f/70c57b3d7278e94ed22d85e09685d3f0a38ebdd8c5c73b65ba4c0d0fe002/yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124", size = 46124, upload-time = "2025-04-17T00:45:12.199Z" },
]
[[package]]
name = "zipp"
version = "3.22.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/12/b6/7b3d16792fdf94f146bed92be90b4eb4563569eca91513c8609aebf0c167/zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5", size = 25257, upload-time = "2025-05-26T14:46:32.217Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ad/da/f64669af4cae46f17b90798a827519ce3737d31dbafad65d391e49643dc4/zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343", size = 9796, upload-time = "2025-05-26T14:46:30.775Z" },
]
================================================
FILE: .env.example
================================================
# inference EP
ANTHROPIC_API_KEY="sk-abc-xyz"
GEMINI_API_KEY="xyz"
GITHUB_API_KEY="ghp_xyz"
GROK_API_KEY="xai-xyz"
HUGGINGFACE_API_KEY="hf_xyz"
OPENROUTER_API_KEY="sk-or-v1-xyz"
PERPLEXITY_API_KEY=""
RESTACK_API_KEY="xyz"
TOGETHER_API_KEY="xyz"
# tools
TAVILY_API_KEY=""
# log/mon/trace
AGENTOPS_API_KEY="x-y-z-x-y"
LOGFIRE_API_KEY="pylf_v1_xx_y" # LOGFIRE_TOKEN
WANDB_API_KEY="xyz"
# eval
================================================
FILE: .gitmessage
================================================
#<--- 72 characters --------------------------------------------------->
#
# Conventional Commits, semantic commit messages for humans and machines
# https://www.conventionalcommits.org/en/v1.0.0/
# Lint your conventional commits
# https://github.com/conventional-changelog/commitlint/tree/master/%40 \
# commitlint/config-conventional
# Common types can be (based on Angular convention)
# build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test
# https://github.com/conventional-changelog/commitlint/tree/master/%40
# Footer
# https://git-scm.com/docs/git-interpret-trailers
#
#<--- pattern --------------------------------------------------------->
#
# [(Scope)][!]: \
#
# short description: [()]:
#
# ! after scope in header indicates breaking change
#
# [optional body]
#
# - with bullets points
#
# [optional footer(s)]
#
# [BREAKING CHANGE:, Refs:, Resolves:, Addresses:, Reviewed by:]
#
#<--- usage ----------------------------------------------------------->
#
# Set locally (in the repository)
# `git config commit.template .gitmessage`
#
# Set globally
# `git config --global commit.template .gitmessage`
#
#<--- 72 characters --------------------------------------------------->
================================================
FILE: context/PRPs/coordination_quality.md
================================================
# Coordination Quality Feature PRP
## Goal
Implement a comprehensive coordination quality measurement and monitoring system for the multi-agent evaluation framework to assess how effectively agents collaborate, delegate tasks, and maintain workflow integrity.
## Why
- **Evaluation Completeness**: The coordination_quality metric is defined in `config_eval.json` (0.167 weight) but not implemented
- **System Reliability**: Need to measure and improve agent coordination failures and bottlenecks
- **Performance Optimization**: Identify coordination inefficiencies that impact overall system performance
- **Research Value**: Provide quantitative data on multi-agent coordination patterns for evaluation research
## What
A coordination quality monitoring system that measures:
- Task delegation success rates between agents
- Inter-agent communication efficiency and latency
- Workflow completion rates and error recovery
- Resource utilization across agent interactions
- Coordination failure detection and analysis
### Success Criteria
- [ ] Coordination quality metric implemented and functional in evaluation system
- [ ] Real-time coordination monitoring dashboard
- [ ] Coordination failure detection and alerting
- [ ] Performance metrics collection and analysis
- [ ] Integration with existing evaluation pipeline
## All Needed Context
### Documentation & References
```yaml
- file: /workspaces/Agents-eval/src/app/agents/agent_system.py
why: Core coordination logic, delegation patterns, tool-based coordination
critical: Lines 91-99 show delegation pattern, _validate_model_return validation
- file: /workspaces/Agents-eval/src/app/config/data_models.py
why: Data contracts for coordination, Pydantic models for agent communication
critical: ResearchResult, AnalysisResult, ResearchSummary models
- file: /workspaces/Agents-eval/src/app/config/config_eval.json
why: Coordination quality metric weight (0.167) defined but not implemented
critical: Need to implement the missing coordination_quality metric
- file: /workspaces/Agents-eval/src/app/evals/metrics.py
why: Evaluation metrics implementation patterns
critical: How other metrics are implemented and integrated
- file: /workspaces/Agents-eval/src/app/config/config_chat.json
why: Agent prompts defining coordination behavior and approval workflows
critical: Manager agent orchestration prompts
```
### Current Codebase Tree
```bash
src/app/
├── agents/
│ ├── agent_system.py # Core coordination logic
│ └── llm_model_funs.py # Model management
├── config/
│ ├── config_app.py # Common app configuration
│ ├── data_models.py # Coordination data contracts
│ ├── config_chat.json # Agent coordination prompts
│ └── config_eval.json # Evaluation metrics (coordination_quality: 0.167)
├── evals/
│ └── metrics.py # Evaluation metrics implementation
├── utils/
│ ├── error_messages.py # Error handling patterns
│ └── log.py # Logging utilities
└── main.py # Entry point
```
### Desired Codebase Tree
```bash
src/app/
├── evals/
│ ├── [existing folders unchanged]
│ ├── coordination_quality/
│ │ ├── __init__.py
│ │ ├── quality_metrics.py # Coordination quality measurement
│ │ ├── monitoring.py # Real-time coordination monitoring
│ │ └── analyzer.py # Coordination pattern analysis
│ └── metrics.py # Updated with coordination_quality implementation
└── [existing files unchanged]
```
### Known Gotchas & Library Quirks
```python
# CRITICAL: PydanticAI coordination patterns
# - Tool-based delegation via @agent.tool decorator
# - Usage tracking shared via RunContext
# - Streaming with Pydantic models has NotImplementedError in agent_system.py
# GOTCHA: Validation requirements
# - All agent communication must use _validate_model_return()
# - Pydantic models required for type safety
# - Error handling must follow utils/error_messages.py patterns
# LIBRARY QUIRK: PydanticAI Usage Limits
# - UsageLimits shared across agents via RunContext
# - Coordination can fail if usage limits exceeded
# - Need to track usage per coordination step
```
## Implementation Blueprint
### Data Models and Structure
```python
# coordination/quality_metrics.py
class CoordinationMetrics(BaseModel):
"""Coordination quality metrics data model."""
delegation_success_rate: float
communication_latency: float
workflow_completion_rate: float
error_recovery_rate: float
resource_utilization: float
coordination_score: float
class CoordinationEvent(BaseModel):
"""Individual coordination event tracking."""
timestamp: datetime
source_agent: str
target_agent: str
event_type: str # delegation, response, error, retry
success: bool
latency_ms: float
error_message: str | None = None
```
### List of Tasks
```yaml
Task 1:
CREATE src/app/coordination/__init__.py:
- EMPTY file for Python package
Task 2:
CREATE src/app/coordination/quality_metrics.py:
- IMPLEMENT CoordinationMetrics and CoordinationEvent models
- IMPLEMENT calculate_coordination_quality() function
- PATTERN: Follow existing Pydantic models in data_models.py
Task 3:
CREATE src/app/coordination/monitoring.py:
- IMPLEMENT CoordinationMonitor class
- TRACK delegation events, latency, success rates
- PATTERN: Use existing logging patterns from utils/log.py
Task 4:
CREATE src/app/coordination/analyzer.py:
- IMPLEMENT coordination pattern analysis
- DETECT coordination failures and bottlenecks
- GENERATE coordination quality reports
Task 5:
MODIFY src/app/agents/agent_system.py:
- FIND _add_tools_to_manager_agent function
- INJECT coordination monitoring into delegation tools
- PRESERVE existing delegation patterns
Task 6:
MODIFY src/app/evals/metrics.py:
- IMPLEMENT coordination_quality metric function
- INTEGRATE with existing metrics calculation
- MIRROR pattern from other metric implementations
Task 7:
CREATE tests/test_coordination_quality.py:
- TEST coordination metrics calculation
- TEST monitoring functionality
- TEST integration with evaluation pipeline
```
### Per Task Pseudocode
```python
# Task 2: quality_metrics.py
class CoordinationMetrics(BaseModel):
delegation_success_rate: float = Field(ge=0.0, le=1.0)
communication_latency: float = Field(ge=0.0)
workflow_completion_rate: float = Field(ge=0.0, le=1.0)
error_recovery_rate: float = Field(ge=0.0, le=1.0)
resource_utilization: float = Field(ge=0.0, le=1.0)
coordination_score: float = Field(ge=0.0, le=1.0)
def calculate_coordination_quality(events: list[CoordinationEvent]) -> CoordinationMetrics:
"""Calculate coordination quality from event history."""
# PATTERN: Weighted average of coordination dimensions
# CRITICAL: Handle empty events list gracefully
if not events:
return CoordinationMetrics(...)
# Calculate individual metrics
success_rate = sum(e.success for e in events) / len(events)
avg_latency = sum(e.latency_ms for e in events) / len(events)
# ... other calculations
# Weighted coordination score
coordination_score = (
success_rate * 0.3 +
normalized_latency * 0.2 +
completion_rate * 0.3 +
recovery_rate * 0.2
)
return CoordinationMetrics(
coordination_score=coordination_score,
# ... other metrics
)
# Task 3: monitoring.py
class CoordinationMonitor:
def __init__(self):
self.events: list[CoordinationEvent] = []
self.logger = logger # From utils/log.py
async def track_delegation(self, source: str, target: str, func: Callable):
"""Track delegation with timing and success monitoring."""
start_time = time.time()
try:
result = await func()
# PATTERN: Log successful coordination
self.logger.info(f"Delegation {source} -> {target} successful")
# Record successful event
self._record_event(
source_agent=source,
target_agent=target,
event_type="delegation",
success=True,
latency_ms=(time.time() - start_time) * 1000
)
return result
except Exception as e:
# PATTERN: Log coordination failures
self.logger.error(f"Delegation {source} -> {target} failed: {str(e)}")
# Record failed event
self._record_event(
source_agent=source,
target_agent=target,
event_type="delegation",
success=False,
latency_ms=(time.time() - start_time) * 1000,
error_message=str(e)
)
raise
# Task 5: agent_system.py integration
# MODIFY delegate_research function
@manager_agent.tool
async def delegate_research(ctx: RunContext[None], query: str) -> ResearchResult:
"""Delegate research task to ResearchAgent."""
# INJECT: Coordination monitoring
monitor = CoordinationMonitor()
async def _research_task():
result = await research_agent.run(query, usage=ctx.usage)
return _validate_model_return(str(result.output), ResearchResult)
# PATTERN: Track delegation with monitoring
return await monitor.track_delegation("manager", "researcher", _research_task)
```
### Integration Points
```yaml
EVALUATION_SYSTEM:
- modify: src/app/evals/metrics.py
- pattern: "def coordination_quality(result: Any) -> float:"
- integration: "Add to evaluation pipeline alongside existing metrics"
CONFIGURATION:
- modify: src/app/config/config_eval.json
- pattern: "coordination_quality metric already defined with weight 0.167"
- validation: "Ensure metric returns float between 0.0 and 1.0"
LOGGING:
- integrate: src/app/utils/log.py
- pattern: "Use existing logger for coordination events"
- level: "INFO for successful coordination, ERROR for failures"
```
## Validation Loop
### Level 1: Syntax & Style
```bash
# Run these FIRST - fix any errors before proceeding
make ruff # Format and fix linting issues
make type_check # Type checking with mypy
# Expected: No errors. If errors, READ the error and fix.
```
### Level 2: Unit Tests
```python
# CREATE tests/test_coordination_quality.py
def test_coordination_metrics_calculation():
"""Test coordination quality calculation with sample events."""
events = [
CoordinationEvent(
timestamp=datetime.now(),
source_agent="manager",
target_agent="researcher",
event_type="delegation",
success=True,
latency_ms=150.0
),
# ... more test events
]
metrics = calculate_coordination_quality(events)
assert 0.0 <= metrics.coordination_score <= 1.0
assert metrics.delegation_success_rate >= 0.0
def test_coordination_monitoring():
"""Test coordination monitoring functionality."""
monitor = CoordinationMonitor()
# Test successful delegation tracking
async def dummy_task():
return "success"
result = await monitor.track_delegation("manager", "researcher", dummy_task)
assert result == "success"
assert len(monitor.events) == 1
assert monitor.events[0].success is True
def test_coordination_quality_metric():
"""Test integration with evaluation metrics."""
# PATTERN: Test similar to other metrics in the evaluation system
sample_result = {"coordination_events": [...]}
quality_score = coordination_quality(sample_result)
assert isinstance(quality_score, float)
assert 0.0 <= quality_score <= 1.0
```
```bash
# Run and iterate until passing:
make test_all
# If failing: Read error, understand root cause, fix code, re-run
```
### Level 3: Integration Test
```bash
# Test the coordination quality in full evaluation
make run_cli ARGS="--query 'test coordination quality' --eval"
# Expected: Coordination quality metric appears in evaluation results
# If error: Check logs for coordination monitoring issues
```
## Final Validation Checklist
- [ ] All tests pass: `make test_all`
- [ ] No linting errors: `make ruff`
- [ ] No type errors: `make type_check`
- [ ] Coordination quality metric integrated in evaluation pipeline
- [ ] Coordination monitoring tracks delegation events
- [ ] Error cases handled gracefully with proper logging
- [ ] Performance impact minimal (< 5% overhead)
- [ ] Documentation updated in AGENTS.md if needed
## Anti-Patterns to Avoid
- ❌ Don't break existing delegation patterns in agent_system.py
- ❌ Don't ignore coordination failures - log and track them
- ❌ Don't add excessive monitoring overhead that slows coordination
- ❌ Don't hardcode coordination thresholds - make them configurable
- ❌ Don't skip validation of coordination metrics calculation
- ❌ Don't assume all coordination events are successful - handle failures gracefully
================================================
FILE: context/PRPs/tool_efficiency.md
================================================
# Tool Efficiency Feature PRP
## Goal
Implement a comprehensive tool efficiency measurement and optimization system for the multi-agent evaluation framework to assess and improve how effectively agents use tools, manage tool resources, and optimize tool performance.
## Why
- **Evaluation Completeness**: The tool_efficiency metric is defined in `config_eval.json` (0.167 weight) but not implemented
- **Resource Optimization**: Need to optimize tool usage costs, especially for expensive operations like web search
- **Performance Monitoring**: Identify slow or inefficient tools that impact overall system performance
- **Usage Analytics**: Provide insights into tool usage patterns for research and optimization
## What
A tool efficiency monitoring and optimization system that measures:
- Tool usage frequency and success rates
- Tool execution times and performance metrics
- Tool resource consumption and cost analysis
- Tool caching effectiveness and hit rates
- Tool failure recovery and fallback mechanisms
### Success Criteria
- [ ] Tool efficiency metric implemented and functional in evaluation system
- [ ] Tool usage tracking and analytics dashboard
- [ ] Tool result caching system for expensive operations
- [ ] Tool timeout and retry mechanism
- [ ] Tool usage quotas and cost management
- [ ] Integration with existing evaluation pipeline
## All Needed Context
### Documentation & References
```yaml
- file: /workspaces/Agents-eval/src/app/agents/agent_system.py
why: Core tool integration, delegation tools, tool assignment patterns
critical: Lines 195 (DuckDuckGo tool), 91-99 (delegation tools), tool validation
- file: /workspaces/Agents-eval/src/app/config/data_models.py
why: Tool validation patterns, AgentConfig model with tools field
critical: Lines 85-100 tool validation, arbitrary_types_allowed config
- file: /workspaces/Agents-eval/src/app/config/config_eval.json
why: Tool efficiency metric weight (0.167) defined but not implemented
critical: Need to implement missing tool_efficiency metric
- file: /workspaces/Agents-eval/src/app/evals/metrics.py
why: Evaluation metrics implementation patterns, time_taken metric
critical: Pattern for implementing new metrics in evaluation system
- file: /workspaces/Agents-eval/src/examples/utils/tools.py
why: Example tool implementations and patterns
critical: roll_die and get_player_name tool examples
```
### Current Codebase Tree
```bash
src/app/
├── agents/
│ ├── agent_system.py # Core tool integration and delegation
│ └── llm_model_funs.py # Model management
├── config/
│ ├── config_app.py # Common app configuration
│ ├── data_models.py # Tool validation patterns
│ ├── config_chat.json # Agent configuration
│ └── config_eval.json # Evaluation metrics (tool_efficiency: 0.167)
├── evals/
│ └── metrics.py # Evaluation metrics (missing tool_efficiency)
├── utils/
│ ├── error_messages.py # Error handling patterns
│ └── log.py # Logging utilities
└── examples/
└── utils/
└── tools.py # Example tool implementations
```
### Desired Codebase Tree
```bash
src/app/
├── evals/
│ ├── [existing folders unchanged]
│ ├── tool_efficiency/
│ │ ├── __init__.py
│ │ ├── efficiency_monitor.py # Tool efficiency monitoring
│ │ ├── cache_manager.py # Tool result caching
│ │ ├── usage_tracker.py # Tool usage analytics
│ │ └── optimizer.py # Tool performance optimization
│ └── metrics.py # Updated with tool_efficiency implementation
└── [existing files unchanged]
```
### Known Gotchas & Library Quirks
```python
# CRITICAL: PydanticAI tool patterns
# - Tools are assigned using @agent.tool decorator
# - Tools must be validated through field_validator in AgentConfig
# - Tools require RunContext for accessing usage limits and dependencies
# GOTCHA: Tool validation requirements
# - Tools are stored as list[Any] but must be Tool instances
# - AgentConfig uses arbitrary_types_allowed=True for tool validation
# - _validate_model_return() required for tool result validation
# LIBRARY QUIRK: Usage limits and context
# - Usage limits are shared across agents via RunContext
# - Tools access context via ctx parameter
# - Usage tracking is cumulative across tool calls
# PERFORMANCE CONSIDERATION: DuckDuckGo search tool
# - Every research query triggers new search requests
# - No built-in caching mechanism
# - Can be expensive and slow for repeated queries
```
## Implementation Blueprint
### Data Models and Structure
```python
# tools/efficiency_monitor.py
class ToolUsageEvent(BaseModel):
"""Individual tool usage event tracking."""
timestamp: datetime
tool_name: str
agent_name: str
execution_time_ms: float
success: bool
input_size: int
output_size: int
cache_hit: bool = False
error_message: str | None = None
cost_estimate: float | None = None
class ToolEfficiencyMetrics(BaseModel):
"""Tool efficiency metrics data model."""
tool_name: str
usage_count: int
success_rate: float
avg_execution_time: float
cache_hit_rate: float
cost_per_usage: float
efficiency_score: float
class ToolCacheEntry(BaseModel):
"""Tool result cache entry."""
tool_name: str
input_hash: str
result: Any
timestamp: datetime
access_count: int
ttl_seconds: int
```
### List of Tasks
```yaml
Task 1:
CREATE src/app/tools/__init__.py:
- EMPTY file for Python package
Task 2:
CREATE src/app/tools/efficiency_monitor.py:
- IMPLEMENT ToolUsageEvent and ToolEfficiencyMetrics models
- IMPLEMENT ToolEfficiencyMonitor class
- PATTERN: Follow existing Pydantic models in data_models.py
Task 3:
CREATE src/app/tools/cache_manager.py:
- IMPLEMENT ToolCacheManager class
- CACHE expensive tool operations (especially search)
- IMPLEMENT cache invalidation and TTL management
Task 4:
CREATE src/app/tools/usage_tracker.py:
- IMPLEMENT ToolUsageTracker class
- TRACK tool usage statistics and patterns
- GENERATE tool usage reports and analytics
Task 5:
CREATE src/app/tools/optimizer.py:
- IMPLEMENT ToolOptimizer class
- OPTIMIZE tool execution and resource usage
- IMPLEMENT tool timeout and retry logic
Task 6:
MODIFY src/app/agents/agent_system.py:
- FIND tool assignment and delegation patterns
- INJECT tool efficiency monitoring
- PRESERVE existing tool functionality
Task 7:
MODIFY src/app/evals/metrics.py:
- IMPLEMENT tool_efficiency metric function
- INTEGRATE with existing metrics calculation
- MIRROR pattern from time_taken metric
Task 8:
CREATE tests/test_tool_efficiency.py:
- TEST tool efficiency monitoring
- TEST tool caching mechanisms
- TEST integration with evaluation pipeline
```
### Per Task Pseudocode
```python
# Task 2: efficiency_monitor.py
class ToolEfficiencyMonitor:
def __init__(self):
self.events: list[ToolUsageEvent] = []
self.logger = logger # From utils/log.py
async def monitor_tool_execution(
self,
tool_name: str,
agent_name: str,
func: Callable,
*args,
**kwargs
) -> Any:
"""Monitor tool execution with timing and success tracking."""
start_time = time.time()
input_size = len(str(args) + str(kwargs))
try:
result = await func(*args, **kwargs)
execution_time = (time.time() - start_time) * 1000
# PATTERN: Log successful tool usage
self.logger.info(f"Tool {tool_name} executed successfully in {execution_time:.2f}ms")
# Record successful event
self._record_event(
tool_name=tool_name,
agent_name=agent_name,
execution_time_ms=execution_time,
success=True,
input_size=input_size,
output_size=len(str(result))
)
return result
except Exception as e:
execution_time = (time.time() - start_time) * 1000
# PATTERN: Log tool failures
self.logger.error(f"Tool {tool_name} failed after {execution_time:.2f}ms: {str(e)}")
# Record failed event
self._record_event(
tool_name=tool_name,
agent_name=agent_name,
execution_time_ms=execution_time,
success=False,
input_size=input_size,
output_size=0,
error_message=str(e)
)
raise
# Task 3: cache_manager.py
class ToolCacheManager:
def __init__(self, max_size: int = 1000, default_ttl: int = 3600):
self.cache: dict[str, ToolCacheEntry] = {}
self.max_size = max_size
self.default_ttl = default_ttl
self.logger = logger
def _generate_cache_key(self, tool_name: str, args: tuple, kwargs: dict) -> str:
"""Generate cache key from tool name and arguments."""
import hashlib
key_data = f"{tool_name}:{str(args)}:{str(sorted(kwargs.items()))}"
return hashlib.md5(key_data.encode()).hexdigest()
async def get_or_execute(
self,
tool_name: str,
func: Callable,
*args,
**kwargs
) -> tuple[Any, bool]: # Returns (result, cache_hit)
"""Get result from cache or execute function."""
cache_key = self._generate_cache_key(tool_name, args, kwargs)
# Check cache
if cache_key in self.cache:
entry = self.cache[cache_key]
if self._is_valid(entry):
entry.access_count += 1
self.logger.info(f"Cache hit for tool {tool_name}")
return entry.result, True
# Execute function
result = await func(*args, **kwargs)
# Cache result
self._cache_result(cache_key, tool_name, result)
self.logger.info(f"Cache miss for tool {tool_name}, result cached")
return result, False
# Task 5: optimizer.py
class ToolOptimizer:
def __init__(self, timeout_seconds: int = 30, max_retries: int = 3):
self.timeout_seconds = timeout_seconds
self.max_retries = max_retries
self.logger = logger
async def execute_with_optimization(
self,
tool_name: str,
func: Callable,
*args,
**kwargs
) -> Any:
"""Execute tool with timeout and retry logic."""
for attempt in range(self.max_retries):
try:
# PATTERN: Asyncio timeout for tool execution
result = await asyncio.wait_for(
func(*args, **kwargs),
timeout=self.timeout_seconds
)
if attempt > 0:
self.logger.info(f"Tool {tool_name} succeeded on attempt {attempt + 1}")
return result
except asyncio.TimeoutError:
self.logger.warning(f"Tool {tool_name} timed out on attempt {attempt + 1}")
if attempt == self.max_retries - 1:
raise TimeoutError(f"Tool {tool_name} timed out after {self.max_retries} attempts")
except Exception as e:
self.logger.warning(f"Tool {tool_name} failed on attempt {attempt + 1}: {str(e)}")
if attempt == self.max_retries - 1:
raise
# Exponential backoff
await asyncio.sleep(2 ** attempt)
# Task 7: metrics.py integration
def tool_efficiency(tool_events: list[ToolUsageEvent]) -> float:
"""Calculate tool efficiency score from usage events."""
if not tool_events:
return 0.0
# Calculate efficiency dimensions
success_rate = sum(1 for event in tool_events if event.success) / len(tool_events)
avg_execution_time = sum(event.execution_time_ms for event in tool_events) / len(tool_events)
cache_hit_rate = sum(1 for event in tool_events if event.cache_hit) / len(tool_events)
# Normalize execution time (lower is better)
normalized_time = max(0.0, 1.0 - (avg_execution_time / 10000.0)) # 10s baseline
# Weighted efficiency score
efficiency_score = (
success_rate * 0.4 +
normalized_time * 0.3 +
cache_hit_rate * 0.3
)
return min(1.0, max(0.0, efficiency_score))
```
### Integration Points
```yaml
EVALUATION_SYSTEM:
- modify: src/app/evals/metrics.py
- pattern: "def tool_efficiency(tool_events: list[ToolUsageEvent]) -> float:"
- integration: "Add to evaluation pipeline alongside existing metrics"
AGENT_SYSTEM:
- modify: src/app/agents/agent_system.py
- pattern: "Wrap tool execution with efficiency monitoring"
- integration: "Inject monitoring into existing delegation tools"
CONFIGURATION:
- modify: src/app/config/config_eval.json
- pattern: "tool_efficiency metric already defined with weight 0.167"
- validation: "Ensure metric returns float between 0.0 and 1.0"
CACHING:
- integrate: Tool result caching for expensive operations
- pattern: "Especially important for DuckDuckGo search tool"
- storage: "In-memory cache with TTL and size limits"
```
## Validation Loop
### Level 1: Syntax & Style
```bash
# Run these FIRST - fix any errors before proceeding
make ruff # Format and fix linting issues
make type_check # Type checking with mypy
# Expected: No errors. If errors, READ the error and fix.
```
### Level 2: Unit Tests
```python
# CREATE tests/test_tool_efficiency.py
def test_tool_efficiency_monitoring():
"""Test tool efficiency monitoring functionality."""
monitor = ToolEfficiencyMonitor()
# Test successful tool execution
async def dummy_tool():
await asyncio.sleep(0.1) # Simulate work
return "success"
result = await monitor.monitor_tool_execution(
"dummy_tool", "test_agent", dummy_tool
)
assert result == "success"
assert len(monitor.events) == 1
assert monitor.events[0].success is True
assert monitor.events[0].execution_time_ms > 0
def test_tool_caching():
"""Test tool result caching functionality."""
cache_manager = ToolCacheManager()
call_count = 0
async def expensive_tool():
nonlocal call_count
call_count += 1
return f"result_{call_count}"
# First call - cache miss
result1, cache_hit1 = await cache_manager.get_or_execute(
"expensive_tool", expensive_tool
)
assert result1 == "result_1"
assert cache_hit1 is False
# Second call - cache hit
result2, cache_hit2 = await cache_manager.get_or_execute(
"expensive_tool", expensive_tool
)
assert result2 == "result_1" # Same result from cache
assert cache_hit2 is True
assert call_count == 1 # Function only called once
def test_tool_efficiency_metric():
"""Test tool efficiency metric calculation."""
events = [
ToolUsageEvent(
timestamp=datetime.now(),
tool_name="test_tool",
agent_name="test_agent",
execution_time_ms=100.0,
success=True,
input_size=10,
output_size=20,
cache_hit=False
),
ToolUsageEvent(
timestamp=datetime.now(),
tool_name="test_tool",
agent_name="test_agent",
execution_time_ms=50.0,
success=True,
input_size=10,
output_size=20,
cache_hit=True
)
]
efficiency_score = tool_efficiency(events)
assert isinstance(efficiency_score, float)
assert 0.0 <= efficiency_score <= 1.0
```
```bash
# Run and iterate until passing:
make test_all
# If failing: Read error, understand root cause, fix code, re-run
```
### Level 3: Integration Test
```bash
# Test tool efficiency in full evaluation
make run_cli ARGS="--query 'test tool efficiency' --eval"
# Expected: Tool efficiency metric appears in evaluation results
# Test caching by running same query twice and checking logs
```
## Final Validation Checklist
- [ ] All tests pass: `make test_all`
- [ ] No linting errors: `make ruff`
- [ ] No type errors: `make type_check`
- [ ] Tool efficiency metric integrated in evaluation pipeline
- [ ] Tool caching working for expensive operations
- [ ] Tool monitoring tracks usage events
- [ ] Tool timeout and retry mechanisms functional
- [ ] Performance impact minimal (< 10% overhead)
- [ ] Documentation updated in AGENTS.md if needed
## Anti-Patterns to Avoid
- ❌ Don't break existing tool functionality when adding monitoring
- ❌ Don't cache results that should be fresh (time-sensitive data)
- ❌ Don't add excessive monitoring overhead that slows tools
- ❌ Don't hardcode cache sizes or TTL values - make them configurable
- ❌ Don't ignore tool failures - track and analyze them
- ❌ Don't assume all tools benefit from caching - evaluate per tool type
- ❌ Don't make caching mandatory - allow tools to opt-out if needed
================================================
FILE: context/PRPs/features/coordination_quality.md
================================================
# Feature description for: coordination_quality
As put forward by [context-engineering-intro](https://github.com/qte77/context-engineering-intro).
## FEATURE
coordination_quality
## EXAMPLES
[Provide and explain examples that you have in the `PRPs/examples/` folder]
## DOCUMENTATION
[List out any documentation (web pages, sources for an MCP server like Crawl4AI RAG, etc.) that will need to be referenced during development]
## OTHER CONSIDERATIONS
[Any other considerations or specific requirements - great place to include gotchas that you see AI coding assistants miss with your projects a lot]
================================================
FILE: context/PRPs/features/tool_efficiency.md
================================================
# Feature description for: tool_efficiency
As put forward by [context-engineering-intro](https://github.com/qte77/context-engineering-intro).
## FEATURE
tool_efficiency
## EXAMPLES
[Provide and explain examples that you have in the `PRPs/examples/` folder]
## DOCUMENTATION
[List out any documentation (web pages, sources for an MCP server like Crawl4AI RAG, etc.) that will need to be referenced during development]
## OTHER CONSIDERATIONS
[Any other considerations or specific requirements - great place to include gotchas that you see AI coding assistants miss with your projects a lot]
================================================
FILE: context/PRPs/templates/feature_base.md
================================================
# Feature description for: [ Initial template for new features ]
As put forward by [context-engineering-intro](https://github.com/qte77/context-engineering-intro).
## FEATURE
[Insert your feature here]
## EXAMPLES
[Provide and explain examples that you have in the `PRPs/examples/` folder]
## DOCUMENTATION
[List out any documentation (web pages, sources for an MCP server like Crawl4AI RAG, etc.) that will need to be referenced during development]
## OTHER CONSIDERATIONS
[Any other considerations or specific requirements - great place to include gotchas that you see AI coding assistants miss with your projects a lot]
================================================
FILE: context/PRPs/templates/prp_base.md
================================================
# "Base PRP Template v2 - Context-Rich with Validation Loops"
## Purpose
Template optimized for AI agents to implement features with sufficient context and self-validation capabilities to achieve working code through iterative refinement.
## Core Principles
1. **Context is King**: Include ALL necessary documentation, examples, and caveats
2. **Validation Loops**: Provide executable tests/lints the AI can run and fix
3. **Information Dense**: Use keywords and patterns from the codebase
4. **Progressive Success**: Start simple, validate, then enhance
5. **Global rules**: Be sure to follow all rules in CLAUDE.md
---
## Goal
[What needs to be built - be specific about the end state and desires]
## Why
- [Business value and user impact]
- [Integration with existing features]
- [Problems this solves and for whom]
## What
[User-visible behavior and technical requirements]
### Success Criteria
- [ ] [Specific measurable outcomes]
## All Needed Context
### Documentation & References (list all context needed to implement the feature)
```yaml
# MUST READ - Include these in your context window
- url: [Official API docs URL]
why: [Specific sections/methods you'll need]
- file: [path/to/example.py]
why: [Pattern to follow, gotchas to avoid]
- doc: [Library documentation URL]
section: [Specific section about common pitfalls]
critical: [Key insight that prevents common errors]
- docfile: [PRPs/ai_docs/file.md]
why: [docs that the user has pasted in to the project]
```
### Current Codebase tree (run `tree` in the root of the project) to get an overview of the codebase
```bash
```
### Desired Codebase tree with files to be added and responsibility of file
```bash
```
### Known Gotchas of our codebase & Library Quirks
```python
# CRITICAL: [Library name] requires [specific setup]
# Example: FastAPI requires async functions for endpoints
# Example: This ORM doesn't support batch inserts over 1000 records
# Example: We use pydantic v2 and
```
## Implementation Blueprint
### Data models and structure
Create the core data models, we ensure type safety and consistency.
```python
Examples:
- orm models
- pydantic models
- pydantic schemas
- pydantic validators
```
### list of tasks to be completed to fullfill the PRP in the order they should be completed
```yaml
Task 1:
MODIFY src/existing_module.py:
- FIND pattern: "class OldImplementation"
- INJECT after line containing "def __init__"
- PRESERVE existing method signatures
CREATE src/new_feature.py:
- MIRROR pattern from: src/similar_feature.py
- MODIFY class name and core logic
- KEEP error handling pattern identical
...(...)
Task N:
...
```
### Per task pseudocode as needed added to each task
```python
# Task 1
# Pseudocode with CRITICAL details dont write entire code
async def new_feature(param: str) -> Result:
# PATTERN: Always validate input first (see src/validators.py)
validated = validate_input(param) # raises ValidationError
# GOTCHA: This library requires connection pooling
async with get_connection() as conn: # see src/db/pool.py
# PATTERN: Use existing retry decorator
@retry(attempts=3, backoff=exponential)
async def _inner():
# CRITICAL: API returns 429 if >10 req/sec
await rate_limiter.acquire()
return await external_api.call(validated)
result = await _inner()
# PATTERN: Standardized response format
return format_response(result) # see src/utils/responses.py
```
### Integration Points
```yaml
DATABASE:
- migration: "Add column 'feature_enabled' to users table"
- index: "CREATE INDEX idx_feature_lookup ON users(feature_id)"
CONFIG:
- add to: config/settings.py
- pattern: "FEATURE_TIMEOUT = int(os.getenv('FEATURE_TIMEOUT', '30'))"
ROUTES:
- add to: src/api/routes.py
- pattern: "router.include_router(feature_router, prefix='/feature')"
```
## Validation Loop
### Level 1: Syntax & Style
```bash
# Run these FIRST - fix any errors before proceeding
ruff check src/new_feature.py --fix # Auto-fix what's possible
mypy src/new_feature.py # Type checking
# Expected: No errors. If errors, READ the error and fix.
```
### Level 2: Unit Tests each new feature/file/function use existing test patterns
```python
# CREATE test_new_feature.py with these test cases:
def test_happy_path():
"""Basic functionality works"""
result = new_feature("valid_input")
assert result.status == "success"
def test_validation_error():
"""Invalid input raises ValidationError"""
with pytest.raises(ValidationError):
new_feature("")
def test_external_api_timeout():
"""Handles timeouts gracefully"""
with mock.patch('external_api.call', side_effect=TimeoutError):
result = new_feature("valid")
assert result.status == "error"
assert "timeout" in result.message
```
```bash
# Run and iterate until passing:
uv run pytest test_new_feature.py -v
# If failing: Read error, understand root cause, fix code, re-run (never mock to pass)
```
### Level 3: Integration Test
```bash
# Start the service
uv run python -m src.main --dev
# Test the endpoint
curl -X POST http://localhost:8000/feature \
-H "Content-Type: application/json" \
-d '{"param": "test_value"}'
# Expected: {"status": "success", "data": {...}}
# If error: Check logs at logs/app.log for stack trace
```
## Final validation Checklist
- [ ] All tests pass: `uv run pytest tests/ -v`
- [ ] No linting errors: `uv run ruff check src/`
- [ ] No type errors: `uv run mypy src/`
- [ ] Manual test successful: [specific curl/command]
- [ ] Error cases handled gracefully
- [ ] Logs are informative but not verbose
- [ ] Documentation updated if needed
---
## Anti-Patterns to Avoid
- ❌ Don't create new patterns when existing ones work
- ❌ Don't skip validation because "it should work"
- ❌ Don't ignore failing tests - fix them
- ❌ Don't use sync functions in async context
- ❌ Don't hardcode values that should be config
- ❌ Don't catch all exceptions - be specific
================================================
FILE: docs/llms.txt
================================================
├── .claude
├── commands
│ ├── execute-prp.md
│ └── generate-prp.md
└── settings.local.json
├── .devcontainer
├── setup_dev
│ └── devcontainer.json
├── setup_dev_claude
│ └── devcontainer.json
└── setup_dev_ollama
│ └── devcontainer.json
├── .env.example
├── .github
├── dependabot.yaml
├── scripts
│ ├── create_pr.sh
│ └── delete_branch_pr_tag.sh
└── workflows
│ ├── bump-my-version.yaml
│ ├── codeql.yaml
│ ├── generate-deploy-mkdocs-ghpages.yaml
│ ├── links-fail-fast.yaml
│ ├── pytest.yaml
│ ├── ruff.yaml
│ ├── summarize-jobs-reusable.yaml
│ └── write-llms-txt.yaml
├── .gitignore
├── .gitmessage
├── .streamlit
└── config.toml
├── .vscode
├── extensions.json
└── settings.json
├── AGENTS.md
├── CHANGELOG.md
├── CLAUDE.md
├── Dockerfile
├── LICENSE.md
├── Makefile
├── README.md
├── assets
└── images
│ ├── c4-multi-agent-system.png
│ ├── customer-journey-activity-dark.png
│ ├── customer-journey-activity-light.png
│ └── metrics-eval-sweep.png
├── context
└── PRPs
│ ├── coordination_quality.md
│ ├── features
│ ├── coordination_quality.md
│ └── tool_efficiency.md
│ ├── templates
│ ├── feature_base.md
│ └── prp_base.md
│ └── tool_efficiency.md
├── docs
├── PRD.md
├── SprintPlan.md
├── UserStory.md
├── architecture
│ ├── c4-multi-agent-system.plantuml
│ ├── customer-journey-activity-dark
│ ├── customer-journey-activity-light.plantuml
│ └── metrics-eval-sweep.plantuml
└── llms.txt
├── mkdocs.yaml
├── pyproject.toml
├── src
├── app
│ ├── __init__.py
│ ├── agents
│ │ ├── __init__.py
│ │ ├── agent_system.py
│ │ └── llm_model_funs.py
│ ├── config
│ │ ├── __init__.py
│ │ ├── config_app.py
│ │ ├── config_chat.json
│ │ ├── config_eval.json
│ │ └── data_models.py
│ ├── evals
│ │ ├── __init__.py
│ │ └── metrics.py
│ ├── main.py
│ ├── py.typed
│ └── utils
│ │ ├── __init__.py
│ │ ├── error_messages.py
│ │ ├── load_configs.py
│ │ ├── load_settings.py
│ │ ├── log.py
│ │ ├── login.py
│ │ └── utils.py
├── examples
│ ├── config.json
│ ├── run_simple_agent_no_tools.py
│ ├── run_simple_agent_system.py
│ ├── run_simple_agent_tools.py
│ └── utils
│ │ ├── agent_simple_no_tools.py
│ │ ├── agent_simple_system.py
│ │ ├── agent_simple_tools.py
│ │ ├── data_models.py
│ │ ├── tools.py
│ │ └── utils.py
├── gui
│ ├── components
│ │ ├── footer.py
│ │ ├── header.py
│ │ ├── output.py
│ │ ├── prompts.py
│ │ └── sidebar.py
│ ├── config
│ │ ├── config.py
│ │ ├── styling.py
│ │ └── text.py
│ └── pages
│ │ ├── home.py
│ │ ├── prompts.py
│ │ ├── run_app.py
│ │ └── settings.py
└── run_gui.py
├── tests
├── test_agent_system.py
├── test_env.py
├── test_metrics_output_similarity.py
├── test_metrics_time_taken.py
└── test_provider_config.py
└── uv.lock
/.claude/commands/execute-prp.md:
--------------------------------------------------------------------------------
1 | # Execute Product Requirements Prompt (PRP)
2 |
3 | Implement a feature using using the PRP file.
4 |
5 | ## PRP File: $ARGUMENTS
6 |
7 | ## Execution Process
8 |
9 | 1. **Load PRP**
10 | - Read the specified PRP file
11 | - Understand all context and requirements
12 | - Follow all instructions in the PRP and extend the research if needed
13 | - Ensure you have all needed context to implement the PRP fully
14 | - Do more web searches and codebase exploration as needed
15 |
16 | 2. **ULTRATHINK**
17 | - Think hard before you execute the plan. Create a comprehensive plan addressing all requirements.
18 | - Break down complex tasks into smaller, manageable steps using your todos tools.
19 | - Use the TodoWrite tool to create and track your implementation plan.
20 | - Identify implementation patterns from existing code to follow.
21 |
22 | 3. **Execute the plan**
23 | - Execute the PRP
24 | - Implement all the code
25 |
26 | 4. **Validate**
27 | - Run each validation command
28 | - Fix any failures
29 | - Re-run until all pass
30 |
31 | 5. **Complete**
32 | - Ensure all checklist items done
33 | - Run final validation suite
34 | - Report completion status
35 | - Read the PRP again to ensure you have implemented everything
36 |
37 | 6. **Reference the PRP**
38 | - You can always reference the PRP again if needed
39 |
40 | Note: If validation fails, use error patterns in PRP to fix and retry.
41 |
--------------------------------------------------------------------------------
/.claude/commands/generate-prp.md:
--------------------------------------------------------------------------------
1 | # Create Product Requirements Prompt (PRP)
2 |
3 | ## Feature file: $ARGUMENTS
4 |
5 | Generate a complete PRP (Product Requirements Prompt) for general feature implementation with thorough research. Ensure context is passed to the AI agent to enable self-validation and iterative refinement. Read the feature file first to understand what needs to be created, how the examples provided help, and any other considerations.
6 |
7 | The AI agent only gets the context you are appending to the PRP and training data. Assume the AI agent has access to the codebase and the same knowledge cutoff as you, so its important that your research findings are included or referenced in the PRP. The Agent has Websearch capabilities, so pass urls to documentation and examples.
8 |
9 | - Use `/context/PRPs` as `$base_path`
10 | - Extract only the filename from `$ARGUMENTS` into `$file_name`
11 |
12 | ## Research Process
13 |
14 | 1. **Codebase Analysis**
15 | - Search for similar features/patterns in the codebase
16 | - Identify files to reference in PRP
17 | - Note existing conventions to follow
18 | - Check test patterns for validation approach
19 |
20 | 2. **External Research**
21 | - Search for similar features/patterns online
22 | - Library documentation (include specific URLs)
23 | - Implementation examples (GitHub/StackOverflow/blogs)
24 | - Best practices and common pitfalls
25 |
26 | 3. **User Clarification** (if needed)
27 | - Specific patterns to mirror and where to find them?
28 | - Integration requirements and where to find them?
29 |
30 | ## PRP Generation
31 |
32 | - Use `${base_path}/templates/prp_base.md` in the base folder as template
33 |
34 | ### Critical Context to Include and pass to the AI agent as part of the PRP
35 |
36 | - **Documentation**: URLs with specific sections
37 | - **Code Examples**: Real snippets from codebase
38 | - **Gotchas**: Library quirks, version issues
39 | - **Patterns**: Existing approaches to follow
40 |
41 | ### Implementation Blueprint
42 |
43 | - Start with pseudocode showing approach
44 | - Reference real files for patterns
45 | - Include error handling strategy
46 | - list tasks to be completed to fullfill the PRP in the order they should be completed
47 |
48 | ### Validation Gates (Must be Executable) eg for python
49 |
50 | ```bash
51 | # Syntax/Style
52 | make ruff
53 | make type_check
54 |
55 | # Unit Tests
56 | make coverage_all
57 | ```
58 |
59 | ***CRITICAL AFTER YOU ARE DONE RESEARCHING AND EXPLORING THE CODEBASE BEFORE YOU START WRITING THE PRP***
60 |
61 | ***ULTRATHINK ABOUT THE PRP AND PLAN YOUR APPROACH THEN START WRITING THE PRP***
62 |
63 | ## Output
64 |
65 | - Save the result to `${base_path}/${file_name}`
66 |
67 | ## Quality Checklist
68 |
69 | - [ ] All necessary context included
70 | - [ ] Validation gates are executable by AI
71 | - [ ] References existing patterns
72 | - [ ] Clear implementation path
73 | - [ ] Error handling documented
74 |
75 | Score the PRP on a scale of 1-10 (confidence level to succeed in one-pass implementation using claude codes)
76 |
77 | Remember: The goal is one-pass implementation success through comprehensive context.
78 |
--------------------------------------------------------------------------------
/.claude/settings.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "permissions": {
3 | "allow": [
4 | "Bash(cat:*)",
5 | "Bash(find:*)",
6 | "Bash(git:diff*)",
7 | "Bash(git:status*)",
8 | "Bash(grep:*)",
9 | "Bash(ls:*)",
10 | "Bash(mkdir:*)",
11 | "Bash(source:*)",
12 | "Bash(touch:*)",
13 | "Bash(tree:*)",
14 | "Bash(uv run:*)",
15 | "Edit(AGENTS.md)",
16 | "Edit(docs/**/*.md)",
17 | "Edit(src/**/*.py)",
18 | "Edit(src/**/*.json)",
19 | "Edit(tests/**/*.py)",
20 | "WebFetch(domain:docs.anthropic.com)"
21 | ],
22 | "deny": [
23 | "Bash(mv:*)",
24 | "Bash(rm:*)"
25 | ]
26 | }
27 | }
--------------------------------------------------------------------------------
/.devcontainer/setup_dev/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "make setup_dev",
3 | "image": "mcr.microsoft.com/vscode/devcontainers/python:3.13",
4 | "postCreateCommand": "make setup_dev"
5 | }
--------------------------------------------------------------------------------
/.devcontainer/setup_dev_claude/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "make setup_dev_claude",
3 | "image": "mcr.microsoft.com/vscode/devcontainers/python:3.13",
4 | "features": {
5 | "ghcr.io/devcontainers/features/node:1": {}
6 | },
7 | "customizations": {
8 | "vscode": {
9 | "extensions": [
10 | "anthropic.claude-code"
11 | ]
12 | }
13 | },
14 | "postCreateCommand": "make setup_dev_claude"
15 | }
--------------------------------------------------------------------------------
/.devcontainer/setup_dev_ollama/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "make setup_dev_ollama",
3 | "image": "mcr.microsoft.com/vscode/devcontainers/python:3.13",
4 | "postCreateCommand": "make setup_dev_ollama"
5 | }
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # inference EP
2 | ANTHROPIC_API_KEY="sk-abc-xyz"
3 | GEMINI_API_KEY="xyz"
4 | GITHUB_API_KEY="ghp_xyz"
5 | GROK_API_KEY="xai-xyz"
6 | HUGGINGFACE_API_KEY="hf_xyz"
7 | OPENROUTER_API_KEY="sk-or-v1-xyz"
8 | PERPLEXITY_API_KEY=""
9 | RESTACK_API_KEY="xyz"
10 | TOGETHER_API_KEY="xyz"
11 |
12 | # tools
13 | TAVILY_API_KEY=""
14 |
15 | # log/mon/trace
16 | AGENTOPS_API_KEY="x-y-z-x-y"
17 | LOGFIRE_API_KEY="pylf_v1_xx_y" # LOGFIRE_TOKEN
18 | WANDB_API_KEY="xyz"
19 |
20 | # eval
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3 | version: 2
4 | updates:
5 | - package-ecosystem: "pip"
6 | directory: "/"
7 | schedule:
8 | interval: "weekly"
9 | ...
10 |
--------------------------------------------------------------------------------
/.github/scripts/create_pr.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # 1 base ref, 2 target ref, 3 title suffix
3 | # 4 current version, 5 bumped
4 |
5 | pr_title="PR $2 $3"
6 | pr_body="PR automatically created from \`$1\` to bump from \`$4\` to \`$5\` on \`$2\`. Tag \`v$5\` will be created and has to be deleted manually if PR gets closed without merge."
7 |
8 | gh pr create \
9 | --base $1 \
10 | --head $2 \
11 | --title "${pr_title}" \
12 | --body "${pr_body}"
13 | # --label "bump"
14 |
--------------------------------------------------------------------------------
/.github/scripts/delete_branch_pr_tag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # 1 repo, 2 target ref, 3 current version
3 |
4 | tag_to_delete="v$3"
5 | branch_del_api_call="repos/$1/git/refs/heads/$2"
6 | del_msg="'$2' force deletion attempted."
7 | close_msg="Closing PR '$2' to rollback after failure"
8 |
9 | echo "Tag $tag_to_delete for $del_msg"
10 | git tag -d "$tag_to_delete"
11 | echo "PR for $del_msg"
12 | gh pr close "$2" --comment "$close_msg"
13 | echo "Branch $del_msg"
14 | gh api "$branch_del_api_call" -X DELETE && \
15 | echo "Branch without error return deleted."
--------------------------------------------------------------------------------
/.github/workflows/bump-my-version.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: bump-my-version
3 |
4 | on:
5 | # pull_request:
6 | # types: [closed]
7 | # branches: [main]
8 | workflow_dispatch:
9 | inputs:
10 | bump_type:
11 | description: '[major|minor|patch]'
12 | required: true
13 | default: 'patch'
14 | type: choice
15 | options:
16 | - 'major'
17 | - 'minor'
18 | - 'patch'
19 |
20 | env:
21 | BRANCH_NEW: "bump-${{ github.run_number }}-${{ github.ref_name }}"
22 | SKIP_PR_HINT: "[skip ci bump]"
23 | SCRIPT_PATH: ".github/scripts"
24 |
25 | jobs:
26 | bump_my_version:
27 | # TODO bug? currently resulting in: Unrecognized named-value: 'env'.
28 | # https://stackoverflow.com/questions/61238849/github-actions-if-contains-function-not-working-with-env-variable/61240761
29 | # if: !contains(
30 | # github.event.pull_request.title,
31 | # ${{ env.SKIP_PR_HINT }}
32 | # )
33 | # TODO check for PR closed by bot to avoid PR creation loop
34 | # github.actor != 'github-actions'
35 | if: >
36 | github.event_name == 'workflow_dispatch' ||
37 | ( github.event.pull_request.merged == true &&
38 | github.event.pull_request.closed_by != 'github-actions' )
39 | runs-on: ubuntu-latest
40 | outputs:
41 | branch_new: ${{ steps.create_branch.outputs.branch_new }}
42 | summary_data: ${{ steps.set_summary.outputs.summary_data }}
43 | permissions:
44 | actions: read
45 | checks: write
46 | contents: write
47 | pull-requests: write
48 | steps:
49 |
50 | - name: Checkout repo
51 | uses: actions/checkout@v4
52 | with:
53 | fetch-depth: 1
54 |
55 | - name: Set git cfg and create branch
56 | id: create_branch
57 | run: |
58 | git config user.email "bumped@qte77.gha"
59 | git config user.name "bump-my-version"
60 | git checkout -b "${{ env.BRANCH_NEW }}"
61 | echo "branch_new=${{ env.BRANCH_NEW }}" >> $GITHUB_OUTPUT
62 |
63 | - name: Bump version
64 | id: bump
65 | uses: callowayproject/bump-my-version@0.29.0
66 | env:
67 | BUMPVERSION_TAG: "true"
68 | with:
69 | args: ${{ inputs.bump_type }}
70 | branch: ${{ env.BRANCH_NEW }}
71 |
72 | - name: "Create PR '${{ env.BRANCH_NEW }}'"
73 | if: steps.bump.outputs.bumped == 'true'
74 | env:
75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76 | run: |
77 | src="${{ env.SCRIPT_PATH }}/create_pr.sh"
78 | chmod +x "$src"
79 | $src "${{ github.ref_name }}" "${{ env.BRANCH_NEW }}" "${{ env.SKIP_PR_HINT }}" "${{ steps.bump.outputs.previous-version }}" "${{ steps.bump.outputs.current-version }}"
80 |
81 | - name: Delete branch, PR and tag in case of failure or cancel
82 | if: failure() || cancelled()
83 | env:
84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
85 | run: |
86 | src="${{ env.SCRIPT_PATH }}/delete_branch_pr_tag.sh"
87 | chmod +x "$src"
88 | $src "${{ github.repository }}" "${{ env.BRANCH_NEW }}" "${{ steps.bump.outputs.current-version }}"
89 |
90 | - name: Set summary data
91 | id: set_summary
92 | if: ${{ always() }}
93 | run: echo "summary_data=${GITHUB_STEP_SUMMARY}" >> $GITHUB_OUTPUT
94 |
95 | generate_summary:
96 | name: Generate Summary Report
97 | if: ${{ always() }}
98 | needs: bump_my_version
99 | uses: ./.github/workflows/summarize-jobs-reusable.yaml
100 | with:
101 | branch_to_summarize: ${{ needs.bump_my_version.outputs.branch_new }}
102 | summary_data: ${{ needs.bump_my_version.outputs.summary_data }}
103 | ...
104 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://github.blog/changelog/2023-01-18-code-scanning-codeql-action-v1-is-now-deprecated/
3 | name: "CodeQL"
4 |
5 | on:
6 | push:
7 | pull_request:
8 | types: [closed]
9 | branches: [ main ]
10 | schedule:
11 | - cron: '27 11 * * 0'
12 | workflow_dispatch:
13 |
14 | jobs:
15 | analyze:
16 | name: Analyze
17 | runs-on: ubuntu-latest
18 | permissions:
19 | actions: read
20 | contents: read
21 | security-events: write
22 |
23 | steps:
24 | - name: Checkout repository
25 | uses: actions/checkout@v4
26 |
27 | - name: Initialize CodeQL
28 | uses: github/codeql-action/init@v3
29 | with:
30 | languages: python
31 |
32 | - name: Autobuild
33 | uses: github/codeql-action/autobuild@v3
34 | # if autobuild fails
35 | #- run: |
36 | # make bootstrap
37 | # make release
38 |
39 | - name: Perform CodeQL Analysis
40 | uses: github/codeql-action/analyze@v3
41 | #- name: sarif
42 | # uses: github/codeql-action/upload-sarif@v2
43 | ...
44 |
--------------------------------------------------------------------------------
/.github/workflows/generate-deploy-mkdocs-ghpages.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Deploy Docs
3 |
4 | on:
5 | pull_request:
6 | types: [closed]
7 | branches: [main]
8 | workflow_dispatch:
9 |
10 | env:
11 | DOCSTRINGS_FILE: "docstrings.md"
12 | DOC_DIR: "docs"
13 | SRC_DIR: "src"
14 | SITE_DIR: "site"
15 | IMG_DIR: "assets/images"
16 |
17 | jobs:
18 | build-and-deploy:
19 | runs-on: ubuntu-latest
20 | permissions:
21 | contents: read
22 | pages: write
23 | id-token: write
24 | environment:
25 | name: github-pages
26 | steps:
27 |
28 | - name: Checkout the repository
29 | uses: actions/checkout@v4.0.0
30 | with:
31 | ref:
32 | ${{
33 | github.event.pull_request.merged == true &&
34 | 'main' ||
35 | github.ref_name
36 | }}
37 | fetch-depth: 0
38 |
39 | - uses: actions/configure-pages@v5.0.0
40 |
41 | # caching instead of actions/cache@v4.0.0
42 | # https://docs.astral.sh/uv/guides/integration/github/#caching
43 | - name: Install uv with cache dependency glob
44 | uses: astral-sh/setup-uv@v5.0.0
45 | with:
46 | enable-cache: true
47 | cache-dependency-glob: "uv.lock"
48 |
49 | # setup python from pyproject.toml using uv
50 | # instead of using actions/setup-python@v5.0.0
51 | # https://docs.astral.sh/uv/guides/integration/github/#setting-up-python
52 | - name: "Set up Python"
53 | run: uv python install
54 |
55 | - name: Install only doc deps
56 | run: uv sync --only-group docs # --frozen
57 |
58 | - name: Get repo info and stream into mkdocs.yaml
59 | id: repo_info
60 | run: |
61 | REPO_INFO=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
62 | -H "Accept: application/vnd.github.v3+json" \
63 | https://api.github.com/repos/${{ github.repository }})
64 | REPO_URL="${{ github.server_url }}/${{ github.repository }}"
65 | REPO_URL=$(echo ${REPO_URL} | sed 's|/|\\/|g')
66 | SITE_NAME=$(sed '1!d' README.md | sed '0,/# /{s/# //}')
67 | SITE_DESC=$(echo $REPO_INFO | jq -r .description)
68 | sed -i "s//${REPO_URL}/g" mkdocs.yaml
69 | sed -i "s//${SITE_NAME}/g" mkdocs.yaml
70 | sed -i "s//${SITE_DESC}/g" mkdocs.yaml
71 |
72 | - name: Copy text files to be included
73 | run: |
74 | CFG_PATH="src/app/config"
75 | mkdir -p "${DOC_DIR}/${CFG_PATH}"
76 | cp README.md "${DOC_DIR}/index.md"
77 | cp {CHANGELOG,LICENSE}.md "${DOC_DIR}"
78 | # Auxiliary files
79 | cp .env.example "${DOC_DIR}"
80 | cp "${CFG_PATH}/config_chat.json" "${DOC_DIR}/${CFG_PATH}"
81 |
82 | - name: Generate code docstrings concat file
83 | run: |
84 | PREFIX="::: "
85 | find "${SRC_DIR}" -type f -name "*.py" \
86 | -type f -not -name "__*__*" -printf "%P\n" | \
87 | sed 's/\//./g' | sed 's/\.py$//' | \
88 | sed "s/^/${PREFIX}/" | sort > \
89 | "${DOC_DIR}/${DOCSTRINGS_FILE}"
90 |
91 | - name: Build documentation
92 | run: uv run --locked --only-group docs mkdocs build
93 |
94 | - name: Copy image files to be included
95 | run: |
96 | # copy images, mkdocs does not by default
97 | # mkdocs also overwrites pre-made directories
98 | dir="${{ env.SITE_DIR }}/${{ env.IMG_DIR }}"
99 | if [ -d "${{ env.IMG_DIR }}" ]; then
100 | mkdir -p "${dir}"
101 | cp "${{ env.IMG_DIR }}"/* "${dir}"
102 | fi
103 |
104 | # - name: Push to gh-pages
105 | # run: uv run mkdocs gh-deploy --force
106 |
107 | - name: Upload artifact
108 | uses: actions/upload-pages-artifact@v3.0.0
109 | with:
110 | path: "${{ env.SITE_DIR }}"
111 |
112 | - name: Deploy to GitHub Pages
113 | id: deployment
114 | uses: actions/deploy-pages@v4.0.0
115 | ...
116 |
--------------------------------------------------------------------------------
/.github/workflows/links-fail-fast.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://github.com/lycheeverse/lychee-action
3 | # https://github.com/marketplace/actions/lychee-broken-link-checker
4 | name: "Link Checker"
5 |
6 | on:
7 | workflow_dispatch:
8 | push:
9 | branches-ignore: [main]
10 | pull_request:
11 | types: [closed]
12 | branches: [main]
13 | schedule:
14 | - cron: "00 00 * * 0"
15 |
16 | jobs:
17 | linkChecker:
18 | runs-on: ubuntu-latest
19 | permissions:
20 | issues: write
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 |
25 | - name: Link Checker
26 | id: lychee
27 | uses: lycheeverse/lychee-action@v2
28 |
29 | - name: Create Issue From File
30 | if: steps.lychee.outputs.exit_code != 0
31 | uses: peter-evans/create-issue-from-file@v5
32 | with:
33 | title: lychee Link Checker Report
34 | content-filepath: ./lychee/out.md
35 | labels: report, automated issue
36 | ...
37 |
--------------------------------------------------------------------------------
/.github/workflows/pytest.yaml:
--------------------------------------------------------------------------------
1 | name: pytest
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | test:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout repository
11 | uses: actions/checkout@v4
12 |
13 | - name: Set up Python
14 | uses: actions/setup-python@v4
15 | with:
16 | python-version: '3.12'
17 |
18 | - name: Install dependencies
19 | run: |
20 | python -m pip install --upgrade pip
21 | pip install pytest
22 |
23 | - name: Run tests
24 | run: pytest
25 |
--------------------------------------------------------------------------------
/.github/workflows/ruff.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://github.com/astral-sh/ruff-action
3 | # https://github.com/astral-sh/ruff
4 | name: ruff
5 | on:
6 | push:
7 | pull_request:
8 | types: [closed]
9 | branches: [main]
10 | schedule:
11 | - cron: "0 0 * * 0"
12 | workflow_dispatch:
13 | jobs:
14 | ruff:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v4
18 | - uses: astral-sh/ruff-action@v3
19 | ...
20 |
--------------------------------------------------------------------------------
/.github/workflows/summarize-jobs-reusable.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://ecanarys.com/supercharging-github-actions-with-job-summaries-and-pull-request-comments/
3 | # FIXME currently bug in gha summaries ? $GITHUB_STEP_SUMMARY files are empty
4 | # https://github.com/orgs/community/discussions/110283
5 | # https://github.com/orgs/community/discussions/67991
6 | # Possible workaround
7 | # echo ${{ fromJSON(step).name }}" >> $GITHUB_STEP_SUMMARY
8 | # echo ${{ fromJSON(step).outcome }}" >> $GITHUB_STEP_SUMMARY
9 | # echo ${{ fromJSON(step).conclusion }}"
10 |
11 | name: Summarize workflow jobs
12 |
13 | on:
14 | workflow_call:
15 | outputs:
16 | summary:
17 | description: "Outputs summaries of jobs in a workflow"
18 | value: ${{ jobs.generate_summary.outputs.summary }}
19 | inputs:
20 | branch_to_summarize:
21 | required: false
22 | default: 'main'
23 | type: string
24 | summary_data:
25 | required: false
26 | type: string
27 |
28 | jobs:
29 | generate_summary:
30 | name: Generate Summary
31 | runs-on: ubuntu-latest
32 | permissions:
33 | contents: read
34 | actions: read
35 | checks: read
36 | pull-requests: none
37 | outputs:
38 | summary: ${{ steps.add_changed_files.outputs.summary }}
39 | steps:
40 |
41 | - name: Add general information
42 | id: general_info
43 | run: |
44 | echo "# Job Summaries" >> $GITHUB_STEP_SUMMARY
45 | echo "Job: `${{ github.job }}`" >> $GITHUB_STEP_SUMMARY
46 | echo "Date: $(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_STEP_SUMMARY
47 |
48 | - name: Add step states
49 | id: step_states
50 | run: |
51 | echo "### Steps:" >> $GITHUB_STEP_SUMMARY
52 | # loop summary_data if valid json
53 | if jq -e . >/dev/null 2>&1 <<< "${{ inputs.summary_data }}"; then
54 | jq -r '
55 | .steps[]
56 | | select(.conclusion != null)
57 | | "- **\(.name)**: \(
58 | if .conclusion == "success" then ":white_check_mark:"
59 | elif .conclusion == "failure" then ":x:"
60 | else ":warning:" end
61 | )"
62 | ' <<< "${{ inputs.summary_data }}" >> $GITHUB_STEP_SUMMARY
63 | else
64 | echo "Invalid JSON in summary data." >> $GITHUB_STEP_SUMMARY
65 | fi
66 |
67 | - name: Checkout repo
68 | uses: actions/checkout@v4
69 | with:
70 | ref: "${{ inputs.branch_to_summarize }}"
71 | fetch-depth: 0
72 |
73 | - name: Add changed files since last push
74 | id: add_changed_files
75 | run: |
76 | # Get the tags
77 | # Use disabled lines to get last two commits
78 | # current=$(git show -s --format=%ci HEAD)
79 | # previous=$(git show -s --format=%ci HEAD~1)
80 | # git diff --name-only HEAD^ HEAD >> $GITHUB_STEP_SUMMARY
81 | version_tag_regex="^v[0-9]+\.[0-9]+\.[0-9]+$" # v0.0.0
82 | tags=$(git tag --sort=-version:refname | \
83 | grep -E "${version_tag_regex}" || echo "")
84 |
85 | # Get latest and previous tags
86 | latest_tag=$(echo "${tags}" | head -n 1)
87 | previous_tag=$(echo "${tags}" | head -n 2 | tail -n 1)
88 |
89 | echo "tags: latest '${latest_tag}', previous '${previous_tag}'"
90 |
91 | # Write to summary
92 | error_msg="No files to output. Tag not found:"
93 | echo ${{ steps.step_states.outputs.summary }} >> $GITHUB_STEP_SUMMARY
94 | echo "## Changed files on '${{ inputs.branch_to_summarize }}'" >> $GITHUB_STEP_SUMMARY
95 |
96 | if [ -z "${latest_tag}" ]; then
97 | echo "${error_msg} latest" >> $GITHUB_STEP_SUMMARY
98 | elif [ -z "${previous_tag}" ]; then
99 | echo "${error_msg} previous" >> $GITHUB_STEP_SUMMARY
100 | elif [ "${latest_tag}" == "${previous_tag}" ]; then
101 | echo "Latest and previous tags are the same: '${latest_tag}'" >> $GITHUB_STEP_SUMMARY
102 | else
103 | # Get commit dates and hashes
104 | latest_date=$(git log -1 --format=%ci $latest_tag)
105 | previous_date=$(git log -1 --format=%ci $previous_tag)
106 | current_hash=$(git rev-parse --short $latest_tag)
107 | previous_hash=$(git rev-parse --short $previous_tag)
108 |
109 | # Append summary to the job summary
110 | echo "Latest Tag Commit: '${latest_tag}' (${current_hash}) ${latest_date}" >> $GITHUB_STEP_SUMMARY
111 | echo "Previous Tag Commit: '${previous_tag}' (${previous_hash}) ${previous_date}" >> $GITHUB_STEP_SUMMARY
112 | echo "Files changed:" >> $GITHUB_STEP_SUMMARY
113 | echo '```' >> $GITHUB_STEP_SUMMARY
114 | git diff --name-only $previous_tag..$latest_tag >> $GITHUB_STEP_SUMMARY
115 | echo '```' >> $GITHUB_STEP_SUMMARY
116 | fi
117 |
118 | - name: Output error message in case of failure or cancel
119 | if: failure() || cancelled()
120 | run: |
121 | if [ "${{ job.status }}" == "cancelled" ]; then
122 | out_msg="## Workflow was cancelled"
123 | else
124 | out_msg="## Error in previous step"
125 | fi
126 | echo $out_msg >> $GITHUB_STEP_SUMMARY
127 | ...
--------------------------------------------------------------------------------
/.github/workflows/write-llms-txt.yaml:
--------------------------------------------------------------------------------
1 | # TODO use local installation of repo to text
2 | # https://github.com/itsitgroup/repo2txt
3 | name: Write repo llms.txt
4 |
5 | on:
6 | push:
7 | branches: [main]
8 | workflow_dispatch:
9 | inputs:
10 | LLMS_TXT_PATH:
11 | description: 'Path to the directory to save llsm.txt'
12 | required: true
13 | default: 'docs'
14 | type: string
15 | LLMS_TXT_NAME:
16 | description: 'Path to the directory to save llsm.txt'
17 | required: true
18 | default: 'llms.txt'
19 | type: string
20 | CONVERTER_URL:
21 | description: '[uithub|gittodoc]' # |repo2txt
22 | required: true
23 | default: 'uithub.com'
24 | type: choice
25 | options:
26 | - 'uithub.com'
27 | - 'gittodoc.com'
28 | # - 'repo2txt.com'
29 |
30 | jobs:
31 | generate-file:
32 | runs-on: ubuntu-latest
33 |
34 | steps:
35 | - name: Checkout repo
36 | uses: actions/checkout@v4
37 |
38 | - name: Construct and create llms.txt path
39 | id: construct_and_create_llms_txt_path
40 | run: |
41 | LLMS_TXT_PATH="${{ inputs.LLMS_TXT_PATH }}"
42 | LLMS_TXT_PATH="${LLMS_TXT_PATH:-docs}"
43 | LLMS_TXT_NAME="${{ inputs.LLMS_TXT_NAME }}"
44 | LLMS_TXT_NAME="${LLMS_TXT_NAME:-llms.txt}"
45 | echo "LLMS_TXT_FULL=${LLMS_TXT_PATH}/${LLMS_TXT_NAME}" >> $GITHUB_OUTPUT
46 | mkdir -p "${LLMS_TXT_PATH}"
47 |
48 | - name: Fetch TXT from URL
49 | run: |
50 | LLMS_TXT_FULL=${{ steps.construct_and_create_llms_txt_path.outputs.LLMS_TXT_FULL }}
51 | URL="https://${{ inputs.CONVERTER_URL }}/${{ github.repository }}"
52 | echo "Fetching content from: ${URL}"
53 | echo "Saving content to: ${LLMS_TXT_FULL}"
54 | curl -s "${URL}" > "${LLMS_TXT_FULL}"
55 |
56 | - name: Commit and push file
57 | run: |
58 | LLMS_TXT_FULL=${{ steps.construct_and_create_llms_txt_path.outputs.LLMS_TXT_FULL }}
59 | commit_msg="feat(docs): Add/Update ${LLMS_TXT_FULL}, a flattened repo as single text file, inspired by [llmstxt.org](https://llmstxt.org/)."
60 | git config user.name "github-actions"
61 | git config user.email "github-actions@github.com"
62 | git add "${LLMS_TXT_FULL}"
63 | git commit -m "${commit_msg}"
64 | git push
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python bytecode
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # environment
6 | .venv/
7 | *.env
8 | unset_env.sh
9 |
10 | # Distribution / packaging
11 | build/
12 | dist/
13 | *.egg-info/
14 |
15 | # Testing
16 | .pytest_cache/
17 | .coverage
18 |
19 | # Logs
20 | *.log
21 | /logs
22 |
23 | # Traces
24 | scalene-profiles
25 | profile.html
26 | profile.json
27 |
28 | # OS generated files
29 | .DS_Store
30 | Thumbs.db
31 |
32 | # IDE specific files (adjust as needed)
33 | # .vscode/
34 | # .idea/
35 |
36 | # mkdocs
37 | reference/
38 | site/
39 |
40 | # linting
41 | .ruff_cache
42 |
43 | # type checking
44 | .mypy_cache/
45 |
46 | # project specific
47 | wandb/
48 |
--------------------------------------------------------------------------------
/.gitmessage:
--------------------------------------------------------------------------------
1 | #<--- 72 characters --------------------------------------------------->
2 | #
3 | # Conventional Commits, semantic commit messages for humans and machines
4 | # https://www.conventionalcommits.org/en/v1.0.0/
5 | # Lint your conventional commits
6 | # https://github.com/conventional-changelog/commitlint/tree/master/%40 \
7 | # commitlint/config-conventional
8 | # Common types can be (based on Angular convention)
9 | # build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test
10 | # https://github.com/conventional-changelog/commitlint/tree/master/%40
11 | # Footer
12 | # https://git-scm.com/docs/git-interpret-trailers
13 | #
14 | #<--- pattern --------------------------------------------------------->
15 | #
16 | # [(Scope)][!]: \
17 | #
18 | # short description: [()]:
19 | #
20 | # ! after scope in header indicates breaking change
21 | #
22 | # [optional body]
23 | #
24 | # - with bullets points
25 | #
26 | # [optional footer(s)]
27 | #
28 | # [BREAKING CHANGE:, Refs:, Resolves:, Addresses:, Reviewed by:]
29 | #
30 | #<--- usage ----------------------------------------------------------->
31 | #
32 | # Set locally (in the repository)
33 | # `git config commit.template .gitmessage`
34 | #
35 | # Set globally
36 | # `git config --global commit.template .gitmessage`
37 | #
38 | #<--- 72 characters --------------------------------------------------->
--------------------------------------------------------------------------------
/.streamlit/config.toml:
--------------------------------------------------------------------------------
1 | [theme]
2 | primaryColor="#f92aad"
3 | backgroundColor="#0b0c10"
4 | secondaryBackgroundColor="#1f2833"
5 | textColor="#66fcf1"
6 | font="monospace"
7 |
8 | [server]
9 | # enableCORS = false
10 | enableXsrfProtection = true
11 |
12 | [browser]
13 | gatherUsageStats = false
14 |
15 | [client]
16 | # toolbarMode = "minimal"
17 | showErrorDetails = true
18 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "charliermarsh.ruff",
4 | "davidanson.vscode-markdownlint",
5 | "donjayamanne.githistory",
6 | "editorconfig.editorconfig",
7 | "gruntfuggly.todo-tree",
8 | "mhutchie.git-graph",
9 | "PKief.material-icon-theme",
10 | "redhat.vscode-yaml",
11 | "tamasfe.even-better-toml",
12 | "yzhang.markdown-all-in-one",
13 |
14 | "github.copilot",
15 | "github.copilot-chat",
16 | "github.vscode-github-actions",
17 | "ms-azuretools.vscode-docker",
18 | "ms-python.debugpy",
19 | "ms-python.python",
20 | "ms-python.vscode-pylance",
21 | "ms-vscode.makefile-tools",
22 | ]
23 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.lineNumbers": "on",
3 | "editor.wordWrap": "on",
4 | "explorer.confirmDelete": true,
5 | "files.autoSave": "onFocusChange",
6 | "git.autofetch": true,
7 | "git.enableSmartCommit": true,
8 | "makefile.configureOnOpen": false,
9 | "markdownlint.config": {
10 | "MD024": false,
11 | "MD033": false
12 | },
13 | "python.analysis.extraPaths": ["./venv/lib/python3.13/site-packages"],
14 | "python.defaultInterpreterPath": "./.venv/bin/python",
15 | "python.analysis.typeCheckingMode": "strict",
16 | "python.analysis.diagnosticSeverityOverrides": {
17 | "reportMissingTypeStubs": "none",
18 | "reportUnknownMemberType": "none",
19 | "reportUnknownVariableType": "none"
20 | },
21 | "redhat.telemetry.enabled": false
22 | }
--------------------------------------------------------------------------------
/AGENTS.md:
--------------------------------------------------------------------------------
1 | # Agent instructions for `Agents-eval` repository
2 |
3 | As proposed by [agentsmd.net](https://agentsmd.net/) and used by [wandb weave AGENTS.md](https://github.com/wandb/weave/blob/master/AGENTS.md).
4 |
5 | ## Core Rules & AI Behavior
6 |
7 | * When you learn something new about the codebase or introduce a new concept, **update this file (`AGENTS.md`)** to reflect the new knowledge. This is YOUR FILE! It should grow and evolve with you.
8 | * If something doesn't make sense architecturally, from a developer experience standpoint, or product-wise, please add it to the **`Requests to Humans`** section below.
9 | * Always follow the established coding patterns, conventions, and architectural decisions documented here and in the `docs/` directory.
10 | * **Never assume missing context.** Ask questions if you are uncertain about requirements or implementation details.
11 | * **Never hallucinate libraries or functions.** Only use known, verified Python packages listed in `pyproject.toml`.
12 | * **Always confirm file paths and module names** exist before referencing them in code or tests.
13 | * **Never delete or overwrite existing code** unless explicitly instructed to or as part of a documented refactoring task.
14 |
15 | ## Architecture Overview
16 |
17 | This is a multi-agent evaluation system for assessing agentic AI systems. The project uses **PydanticAI** as the core framework for agent orchestration and is designed for evaluation purposes, not for production agent deployment.
18 |
19 | ### Data Flow
20 |
21 | 1. User input → Manager Agent
22 | 2. Manager delegates to Researcher Agent (with DuckDuckGo search)
23 | 3. Researcher results → Analyst Agent for validation
24 | 4. Validated data → Synthesizer Agent for report generation
25 | 5. Results evaluated using configurable metrics
26 |
27 | ### Key Dependencies
28 |
29 | * **PydanticAI**: Agent framework and orchestration
30 | * **uv**: Fast Python dependency management
31 | * **Streamlit**: GUI framework
32 | * **Ruff**: Code formatting and linting
33 | * **MyPy**: Static type checking
34 |
35 | ## Codebase Structure & Modularity
36 |
37 | ### Main Components
38 |
39 | * `src/app/`: The core application logic. This is where most of your work will be.
40 | * `main.py`: The main entry point for the CLI application.
41 | * `agents/agent_system.py`: Defines the multi-agent system, their interactions, and orchestration. **This is the central logic for agent behavior.**
42 | * `config/data_models.py`: Contains all **Pydantic** models that define the data contracts. This is a critical file for understanding data flow.
43 | * `config/config_chat.json`: Holds provider settings and system prompts for agents.
44 | * `config/config_eval.json`: Defines evaluation metrics and their weights.
45 | * `evals/metrics.py`: Implements the evaluation metrics.
46 | * `src/gui/`: Contains the source code for the Streamlit GUI.
47 | * `docs/`: Contains project documentation, including the Product Requirements Document (`PRD.md`) and the C4 architecture model.
48 | * `tests/`: Contains all tests for the project, written using **pytest**.
49 |
50 | ### Code Organization Rules
51 |
52 | * **Never create a file longer than 500 lines of code.** If a file approaches this limit, refactor by splitting it into smaller, more focused modules or helper files.
53 | * Organize code into clearly separated modules grouped by feature.
54 | * Use clear, consistent, and absolute imports within packages.
55 |
56 | ## Development Commands & Environment
57 |
58 | ### Environment Setup
59 |
60 | The project requirements are stated in `pyproject.toml`. Your development environment should be set up automatically using the provided `Makefile`, which configures the virtual environment.
61 |
62 | * `make setup_dev`: Install all dev dependencies.
63 | * `make setup_dev_claude`: Setup dev environment with Claude Code CLI.
64 | * `make setup_dev_ollama`: Setup dev environment with Ollama local LLM.
65 |
66 | ### Running the Application
67 |
68 | * `make run_cli`: Run the CLI application.
69 | * `make run_cli ARGS="--help"`: Run CLI with specific arguments.
70 | * `make run_gui`: Run the Streamlit GUI.
71 |
72 | ### Testing and Code Quality
73 |
74 | * `make test_all`: Run all tests with pytest.
75 | * `make coverage_all`: Run tests and generate a coverage report.
76 | * `make ruff`: Format code and fix linting issues with Ruff.
77 | * `make type_check`: Run mypy static type checking on `src/app/`.
78 |
79 | ## Testing & Reliability
80 |
81 | * **Always create Pytest unit tests** for new features (functions, classes, etc.).
82 | * Tests must live in the `tests/` folder, mirroring the `src/app` structure.
83 | * After updating any logic, check whether existing unit tests need to be updated. If so, do it.
84 | * For each new feature, include at least:
85 | * 1 test for the expected use case (happy path).
86 | * 1 test for a known edge case.
87 | * 1 test for an expected failure case (e.g., invalid input).
88 | * **To run a specific test file or function, use `uv run pytest` directly:**
89 | * `uv run pytest tests/test_specific_file.py`
90 | * `uv run pytest tests/test_specific_file.py::test_function`
91 |
92 | ## Style, Patterns & Documentation
93 |
94 | ### Coding Style
95 |
96 | * **Use Pydantic** models in `src/app/config/data_models.py` for all data validation and data contracts. **Always use or update these models** when modifying data flows.
97 | * Use the predefined error message functions from `src/app/utils/error_messages.py` for consistency.
98 | * When writing complex logic, **add an inline `# Reason:` comment** explaining the *why*, not just the *what*.
99 | * Comment non-obvious code to ensure it is understandable to a mid-level developer.
100 |
101 | ### Documentation
102 |
103 | * Write **docstrings for every function, class, and method** using the Google style format. This is critical as the documentation site is built automatically from docstrings.
104 |
105 | ```python
106 | def example_function(param1: int) -> str:
107 | """A brief summary of the function.
108 |
109 | Args:
110 | param1 (int): A description of the first parameter.
111 |
112 | Returns:
113 | str: A description of the return value.
114 | """
115 | return "example"
116 | ```
117 |
118 | * Update this `AGENTS.md` file when introducing new patterns or concepts.
119 | * Document significant architectural decisions in `docs/ADR.md`.
120 | * Document all significant changes, features, and bug fixes in `docs/CHANGELOG.md`.
121 |
122 | ## Code Review & PR Guidelines
123 |
124 | ### PR Requirements
125 |
126 | * **Title Format**: Commit messages and PR titles must follow the **Conventional Commits** specification, as outlined in the `.gitmessage` template.
127 | * Provide detailed PR summaries including the purpose of the changes and the testing performed.
128 |
129 | ### Pre-commit Checklist
130 |
131 | 1. Run the linter and formatter: `make ruff`.
132 | 2. Ensure all tests pass: `make test_all`.
133 | 3. Ensure static type checks pass: `make type_check`.
134 | 4. Update documentation as described below.
135 |
136 | ## Requests to Humans
137 |
138 | This section contains a list of questions, clarifications, or tasks that AI agents wish to have humans complete or elaborate on.
139 |
140 | * [ ] The `agent_system.py` module has a `NotImplementedError` for streaming with Pydantic model outputs. Please clarify the intended approach for streaming structured data.
141 | * [ ] The `llm_model_funs.py` module has `NotImplementedError` for the Gemini and HuggingFace providers. Please provide the correct implementation or remove them if they are not supported.
142 | * [ ] The `agent_system.py` module contains a `FIXME` note regarding the use of a try-catch context manager. Please review and implement the intended error handling.
143 | * [ ] Add TypeScript testing guidelines (if a TypeScript frontend is planned for the future).
144 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## Guiding Principles
9 |
10 | - Changelogs are for humans, not machines.
11 | - There should be an entry for every single version.
12 | - The same types of changes should be grouped.
13 | - Versions and sections should be linkable.
14 | - The latest version comes first.
15 | - The release date of each version is displayed.
16 | - Mention whether you follow Semantic Versioning.
17 |
18 | ## Types of changes
19 |
20 | - `Added` for new features.
21 | - `Changed` for changes in existing functionality.
22 | - `Deprecated` for soon-to-be removed features.
23 | - `Removed` for now removed features.
24 | - `Fixed` for any bug fixes.
25 | - `Security` in case of vulnerabilities.
26 |
27 | ## [Unreleased]
28 |
29 | ### Added
30 |
31 | - Claude code functionality, commands and settings
32 |
33 | ## [1.1.0] - 2025-07-05
34 |
35 | ### Added
36 |
37 | - Makefile command and devcontainer.json for claude code usage
38 |
39 | ### Changed
40 |
41 | - Moved streamlit_gui and examples to /src
42 | - Moved app to /src/app
43 |
44 | ## [1.0.0] - 2025-03-18
45 |
46 | ### 2025-03-18
47 |
48 | - refactor(agent,streamlit): Convert main and run_manager functions again to async for streamli output
49 | - fix(prompts): Update system prompts for manager,researcher and synthesiser roles to remove complexity
50 | - chore(workflows): Update action versions in GitHub workflows for consistency
51 | - chore(workflows): Update action versions for deploy docs to pgh-pages
52 | - docs(deps): Add documentation dependencies for MkDocs and related plugins to pyproject.toml
53 |
54 | ### 2025-03-17
55 |
56 | - feat(main,agent): refactor entry point to support async execution and enhance login handling
57 | - feat(cli,login,log): refactor entry point to integrate Typer, enhance logging, added login every run
58 | - feat(streamlit): replace load_config with load_app_config, enhance sidebar rendering, and improve output rendering with type support
59 | - feat(streamlit): enhance render_output function with detailed docstring and improve query handling in run_app
60 | - feat(streamlit): enhance render_output function with additional info parameter and improve output handling in run_app
61 | - feat(streamlit,app): add Typer dependency, update main entry point for async execution, add streamlit provider input
62 | - feat(agent): update configuration and improve agent system setup with enhanced error handling and new environment variables
63 | - feat(config,login,catch): add inference settings with usage limits and result retries, enhance login function to initialize environment and handle exceptions, comment out raise in error handling context to prevent unintended crashes
64 | - feat(login,catch): integrate logfire configuration in login function and improve error handling context
65 |
66 | ### 2025-03-16
67 |
68 | - feta(devconatiner): Refactor devcontainer setup: remove old configurations and add new setup targets for development and Ollama
69 | - feat(devcontainer): Changed from vscode to astral-sh devcontainer
70 | - feat(devcontainer): Changed to vscode container, added postcreatecommand make setup_env
71 | - feat(devcontainer): restructure environment setup with new devcontainer configurations
72 | - feat(devcontainer): update environment names for clarity in devcontainer configurations
73 | - refactor(agent): Added AgentConfig class for better agent configuration management, Refactored main function for streamlined agent initialization.
74 | - feat(config,agents): Update model providers and enhance configuration management, examples: Added new model providers: Gemini and OpenRouter, src: Enabled streaming responses in the agent system
75 | - chore: Remove unused prompt files, update configuration, and enhance logging setup
76 | - refactor(exception,logfire): Enhance error handling and update model configurations in agent system
77 |
78 | ### 2025-03-14
79 |
80 | - feat(scalene): Add profiling support and update dependencies
81 | - refactor(Makefile): Improve target descriptions and organization
82 |
83 | ### 2025-03-13
84 |
85 | - refactor(API,except): .env.example, add OpenRouter configuration, enhance error handling in run_simple_agent_system.py, and update ModelConfig to allow optional API key.
86 | - feat(streamlit): add Streamlit app structure with header, footer, sidebar, and main content components
87 | - feat(streamlit): enhance Streamlit app with detailed docstrings, improved header/footer, and refined main content layout
88 | - feat(makefile,streamlit): update Makefile commands for CLI and GUI execution, and modify README for usage instructions, add streamlit config.toml
89 | - feat(streamlit): restructure Streamlit app by removing unused components, adding new header, footer, sidebar, and output components, and updating configuration settings
90 | - chore: replace app entrypoint with main, remove unused tools and tests, and update makefile for linting and type checking
91 | - chore: Enhance makefile with coverage and help commands, update mkdocs.yaml and pyproject.toml for improved project structure and documentation
92 | - test: Update makefile for coverage reporting, modify pyproject.toml to include pytest-cov, and adjust dependency settings
93 | - test: Add coverage support with pytest-cov and update makefile for coverage reporting
94 | - test: makefile for coverage reporting, update dependencies in pyproject.toml for improved testing and coverage support
95 | - chore: Remove redundant help command from makefile
96 | - refactor(agent,async): Refactor agent tests to use async fixtures and update verification methods for async results
97 | - fix(Dockerfile): Remove unnecessary user creation and pip install commands from Dockerfile
98 | - feat(agent): Update dependencies and add new example structures; remove obsolete files
99 | - chore(structure): simplified agents.py
100 | - fix(pyproject): Replace pydantic-ai with pydantic-ai-slim and update dependencies
101 | - feat(examples): add new examples and data models; update configuration structure
102 | - feat(agent): update dependencies, enhance examples, and introduce new data models for research and analysis agents
103 | - feat(examples): enhance prompts structure and refactor research agent integration
104 | - feat(examples): improve documentation and enhance error handling in agent examples
105 | - feat(agent): Added data models and configuration for research and analysis agents, Added System C4 plantuml
106 | - feat(weave,dependencies): update dependencies and integrate Weave for enhanced functionality in the agent system
107 | - feat(agent): initialize agentops with API key and default tags for enhanced agent functionality
108 | - feat(agent): integrate logfire for logging and configure initial logging settings
109 | - feat(agent): adjust usage limits for ollama provider to enhance performance
110 | - feat(agent): refine system prompts and enhance data model structure for improved agent interactions
111 | - feat(agent): update system prompts for improved clarity and accuracy; add example environment configuration
112 | - feat(agent): enhance agent system with synthesiser functionality and update prompts for improved coordination
113 | - feat(agent): add Grok and Gemini API configurations; initialize logging and agent operations
114 | - feat(agent): improve documentation and refactor model configuration handling for agent system
115 | - feat(agent): update environment configuration, enhance logging, and refine agent management functionality
116 | - feat(agent): refactor login handling, update model retrieval, and enhance agent configuration
117 |
118 | ## [0.0.2] - 2025-01-20
119 |
120 | ### Added
121 |
122 | - PRD.md
123 | - C4 architecture diagrams: system context, code
124 | - tests: basic agent evals, config.json
125 |
126 | ### Changed
127 |
128 | - make recipes
129 |
130 | ## [0.0.1] - 2025-01-20
131 |
132 | ### Added
133 |
134 | - Makefile: setup, test, ruff
135 | - devcontainer: python only, w/o Jetbrains clutter from default devcontainer
136 | - ollama: server and model download successful
137 | - agent: tools use full run red
138 | - pytest: e2e runm final result red
139 | - Readme: basic project info
140 | - pyproject.toml
141 |
--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------
1 | @AGENTS.md
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG APP_ROOT="/src"
2 | ARG PYTHON_VERSION="3.12"
3 | ARG USER="appuser"
4 |
5 |
6 | # Stage 1: Builder Image
7 | FROM python:${PYTHON_VERSION}-slim AS builder
8 | LABEL author="qte77"
9 | LABEL builder=true
10 | ENV PYTHONDONTWRITEBYTECODE=1 \
11 | PYTHONUNBUFFERED=1
12 | COPY pyproject.toml uv.lock /
13 | RUN set -xe \
14 | && pip install --no-cache-dir uv \
15 | && uv sync --frozen
16 |
17 |
18 | # Stage 2: Runtime Image
19 | FROM python:${PYTHON_VERSION}-slim AS runtime
20 | LABEL author="qte77"
21 | LABEL runtime=true
22 |
23 | ARG APP_ROOT
24 | ARG USER
25 | ENV PYTHONDONTWRITEBYTECODE=1 \
26 | PYTHONUNBUFFERED=1 \
27 | PYTHONPATH=${APP_ROOT} \
28 | PATH="${APP_ROOT}:${PATH}"
29 | # WANDB_KEY=${WANDB_KEY} \
30 | # WANDB_DISABLE_CODE=true
31 |
32 | USER ${USER}
33 | WORKDIR ${APP_ROOT}
34 | COPY --from=builder /.venv .venv
35 | COPY --chown=${USER}:${USER} ${APP_ROOT} .
36 |
37 | CMD [ \
38 | "uv", "run", \
39 | "--locked", "--no-sync", \
40 | "python", "-m", "." \
41 | ]
42 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # BSD 3-Clause License
2 |
3 | Copyright (c) 2025 qte77
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # This Makefile automates the build, test, and clean processes for the project.
2 | # It provides a convenient way to run common tasks using the 'make' command.
3 | # It is designed to work with the 'uv' tool for managing Python environments and dependencies.
4 | # Run `make help` to see all available recipes.
5 |
6 | .SILENT:
7 | .ONESHELL:
8 | .PHONY: all setup_prod setup_dev setup_prod_ollama setup_dev_ollama setup_dev_claude setup_claude_code setup_ollama start_ollama stop_ollama clean_ollama ruff run_cli run_gui run_profile prp_gen_claude prp_exe_claude test_all coverage_all type_check output_unset_app_env_sh help
9 | # .DEFAULT: setup_dev_ollama
10 | .DEFAULT_GOAL := setup_dev_ollama
11 |
12 | SRC_PATH := src
13 | APP_PATH := $(SRC_PATH)/app
14 | GUI_PATH_ST := $(SRC_PATH)/run_gui.py
15 | CHAT_CFG_FILE := $(APP_PATH)/config_chat.json
16 | OLLAMA_SETUP_URL := https://ollama.com/install.sh
17 | OLLAMA_MODEL_NAME := $$(jq -r '.providers.ollama.model_name' $(CHAT_CFG_FILE))
18 | PRP_DEF_PATH := /context/PRPs/features
19 | PRP_CLAUDE_GEN_CMD := generate-prp
20 | PRP_CLAUDE_EXE_CMD := execute-prp
21 |
22 | # construct the full path to the PRP definition file
23 | define CLAUDE_PRP_RUNNER
24 | echo "Starting Claude Code PRP runner ..."
25 | # 1. Extract arguments and validate that they are not empty.
26 | prp_file=$(firstword $(strip $(1)))
27 | cmd_prp=$(firstword $(strip $(2)))
28 | if [ -z "$${prp_file}" ]; then
29 | echo "Error: ARGS for PRP filename is empty. Please provide a PRP filename."
30 | exit 1
31 | fi
32 | if [ -z "$${cmd_prp}" ]; then
33 | echo "Error: ARGS for command is empty. Please provide a command."
34 | exit 2
35 | fi
36 | cmd_prp="/project:$${cmd_prp} $(PRP_DEF_PATH)/$${prp_file}"
37 | cmd_cost="/cost"
38 | echo "Executing command '$${cmd_prp}' ..."
39 | claude -p "$${cmd_prp}" 2>&1
40 | claude -p "$${cmd_cost}" 2>&1
41 | endef
42 |
43 | setup_prod: ## Install uv and deps, Download and start Ollama
44 | echo "Setting up prod environment ..."
45 | pip install uv -q
46 | uv sync --frozen
47 |
48 | setup_dev: ## Install uv and deps, Download and start Ollama
49 | echo "Setting up dev environment ..."
50 | pip install uv -q
51 | uv sync --all-groups
52 |
53 | setup_prod_ollama:
54 | $(MAKE) -s setup_prod
55 | $(MAKE) -s setup_ollama
56 | $(MAKE) -s start_ollama
57 |
58 | setup_dev_ollama:
59 | $(MAKE) -s setup_dev
60 | $(MAKE) -s setup_ollama
61 | $(MAKE) -s start_ollama
62 |
63 | setup_dev_claude:
64 | $(MAKE) -s setup_dev
65 | $(MAKE) -s setup_claude_code
66 |
67 | setup_claude_code: ## Setup claude code CLI, node.js and npm have to be present
68 | echo "Setting up claude code ..."
69 | npm install -g @anthropic-ai/claude-code
70 | claude config set --global preferredNotifChannel terminal_bell
71 | echo "npm version: $$(npm --version)"
72 | claude --version
73 |
74 | # Ollama BINDIR in /usr/local/bin /usr/bin /bin
75 | setup_ollama: ## Download Ollama, script does start local Ollama server
76 | echo "Downloading Ollama binary... Using '$(OLLAMA_SETUP_URL)'."
77 | # script does start server but not consistently
78 | curl -fsSL $(OLLAMA_SETUP_URL) | sh
79 | echo "Pulling model '$(OLLAMA_MODEL_NAME)' ..."
80 | ollama pull $(OLLAMA_MODEL_NAME)
81 |
82 | start_ollama: ## Start local Ollama server, default 127.0.0.1:11434
83 | ollama serve
84 |
85 | stop_ollama: ## Stop local Ollama server
86 | echo "Stopping Ollama server..."
87 | pkill ollama
88 |
89 | clean_ollama: ## Remove local Ollama from system
90 | echo "Searching for Ollama binary..."
91 | for BINDIR in /usr/local/bin /usr/bin /bin; do
92 | if echo $$PATH | grep -q $$BINDIR; then
93 | echo "Ollama binary found in '$$BINDIR'"
94 | BIN="$$BINDIR/ollama"
95 | break
96 | fi
97 | done
98 | echo "Cleaning up..."
99 | rm -f $(BIN)
100 |
101 | ruff: ## Lint: Format and check with ruff
102 | uv run ruff format
103 | uv run ruff check --fix
104 |
105 | run_cli: ## Run app on CLI only
106 | path=$$(echo "$(APP_PATH)" | tr '/' '.')
107 | uv run python -m $${path}.main $(ARGS)
108 |
109 | run_gui: ## Run app with Streamlit GUI
110 | uv run streamlit run $(GUI_PATH_ST)
111 |
112 | run_profile: ## Profile app with scalene
113 | uv run scalene --outfile \
114 | "$(APP_PATH)/scalene-profiles/profile-$(date +%Y%m%d-%H%M%S)" \
115 | "$(APP_PATH)/main.py"
116 |
117 | prp_gen_claude: ## generates the PRP from the file passed in ARGS
118 | $(call CLAUDE_PRP_RUNNER, $(ARGS), $(PRP_CLAUDE_GEN_CMD))
119 |
120 | prp_exe_claude: ## executes the PRP from the file passed in ARGS
121 | $(call CLAUDE_PRP_RUNNER, $(ARGS), $(PRP_CLAUDE_EXE_CMD))
122 |
123 | test_all: ## Run all tests
124 | uv run pytest
125 |
126 | coverage_all: ## Get test coverage
127 | uv run coverage run -m pytest || true
128 | uv run coverage report -m
129 |
130 | type_check: ## Check for static typing errors
131 | uv run mypy $(APP_PATH)
132 |
133 | output_unset_app_env_sh: ## Unset app environment variables
134 | uf="./unset_env.sh"
135 | echo "Outputing '$${uf}' ..."
136 | printenv | awk -F= '/_API_KEY=/ {print "unset " $$1}' > $$uf
137 |
138 | help: ## Displays this message with available recipes
139 | # TODO add stackoverflow source
140 | echo "Usage: make [recipe]"
141 | echo "Recipes:"
142 | awk '/^[a-zA-Z0-9_-]+:.*?##/ {
143 | helpMessage = match($$0, /## (.*)/)
144 | if (helpMessage) {
145 | recipe = $$1
146 | sub(/:/, "", recipe)
147 | printf " \033[36m%-20s\033[0m %s\n", recipe, substr($$0, RSTART + 3, RLENGTH)
148 | }
149 | }' $(MAKEFILE_LIST)
150 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Agents-eval
2 |
3 | This project aims to implement an evaluation pipeline to assess the effectiveness of open-source agentic AI systems across various use cases, focusing on use case agnostic metrics that measure core capabilities such as task decomposition, tool integration, adaptability, and overall performance.
4 |
5 | 
6 | 
7 | [](https://github.com/qte77/Agents-eval/actions/workflows/codeql.yaml)
8 | [](https://www.codefactor.io/repository/github/qte77/Agents-eval)
9 | [](https://github.com/qte77/Agents-eval/actions/workflows/ruff.yaml)
10 | [](https://github.com/qte77/Agents-eval/actions/workflows/pytest.yaml)
11 | [](https://github.com/qte77/Agents-eval/actions/workflows/links-fail-fast.yaml)
12 | [](https://github.com/qte77/Agents-eval/actions/workflows/generate-deploy-mkdocs-ghpages.yaml)
13 |
14 | **DevEx** [](https://vscode.dev/github/qte77/Agents-eval)
15 | [](https://github.com/codespaces/new?repo=qte77/Agents-eval&devcontainer_path=.devcontainer/setup_dev/devcontainer.json)
16 | [](https://github.com/codespaces/new?repo=qte77/Agents-eval&devcontainer_path=.devcontainer/setup_dev_claude/devcontainer.json)
17 | [](https://github.com/codespaces/new?repo=qte77/Agents-eval&devcontainer_path=.devcontainer/setup_dev_ollama/devcontainer.json)
18 | [](https://talktogithub.com/qte77/Agents-eval)
19 | [](https://github.com/qte77/Agents-eval)
20 | [](https://gittodoc.com/qte77/Agents-eval)
21 |
22 | ## Status
23 |
24 | (DRAFT) (WIP) ----> Not fully implemented yet
25 |
26 | For version history have a look at the [CHANGELOG](CHANGELOG.md).
27 |
28 | ## Setup and Usage
29 |
30 | - `make setup_prod`
31 | - `make setup_dev` or `make setup_dev_claude` or `make setup_dev_ollama`
32 | - `make run_cli` or `make run_cli ARGS="--help"`
33 | - `make run_gui`
34 | - `make test_all`
35 |
36 | ### Configuration
37 |
38 | - [config_app.py](src/app/config/config_app.py) contains configuration constants for the application.
39 | - [config_chat.json](src/app/config/config_chat.json) contains inference provider configuration and prompts. inference endpoints used should adhere to [OpenAI Model Spec 2024-05-08](https://cdn.openai.com/spec/model-spec-2024-05-08.html) which is used by [pydantic-ai OpenAI-compatible Models](https://ai.pydantic.dev/models/#openai-compatible-models).
40 | - [config_eval.json](src/app/config/config_eval.json) contains evaluation metrics and their weights.
41 | - [data_models.py](src/app/config/data_models.py) contains the pydantic data models for agent system configuration and results.
42 |
43 | ### Environment
44 |
45 | [.env.example](.env.example) contains examples for usage of API keys and variables.
46 |
47 | ```text
48 | # inference EP
49 | GEMINI_API_KEY="xyz"
50 |
51 | # tools
52 | TAVILY_API_KEY=""
53 |
54 | # log/mon/trace
55 | WANDB_API_KEY="xyz"
56 | ```
57 |
58 | ### Customer Journey
59 |
60 |
61 | Show Customer Journey
62 |
63 |
64 |
65 |
66 | ### Note
67 |
68 | 1. The contained chat configuration uses free inference endpoints which are subject to change by the providers. See lists such as [free-llm-api-resources](https://github.com/cheahjs/free-llm-api-resources) to find other providers.
69 | 2. The contained chat configuration uses models which are also subject to change by the providers and have to be updated from time to time.
70 | 3. LLM-as-judge is also subject to the chat configuration.
71 |
72 | ## Documentation
73 |
74 | [Agents-eval](https://qte77.github.io/Agents-eval)
75 |
76 | ### Project Outline
77 |
78 | `# TODO`
79 |
80 | ### Agents
81 |
82 | #### Manager Agent
83 |
84 | - **Description**: Oversees research and analysis tasks, coordinating the efforts of the research, analysis, and synthesizer agents to provide comprehensive answers to user queries. Delegates tasks and ensures the accuracy of the information.
85 | - **Responsibilities**:
86 | - Coordinates the research, analysis, and synthesis agents.
87 | - Delegates research tasks to the Research Agent.
88 | - Delegates analysis tasks to the Analysis Agent.
89 | - Delegates synthesis tasks to the Synthesizer Agent.
90 | - Ensures the accuracy of the information.
91 | - **Location**: [src/app/agents/agent_system.py](https://github.com/qte77/Agents-eval/blob/main/src/app/agents/agent_system.py)
92 |
93 | #### Researcher Agent
94 |
95 | - **Description**: Gathers and analyzes data relevant to a given topic, utilizing search tools to collect data and verifying the accuracy of assumptions, facts, and conclusions.
96 | - **Responsibilities**:
97 | - Gathers and analyzes data relevant to the topic.
98 | - Uses search tools to collect data.
99 | - Checks the accuracy of assumptions, facts, and conclusions.
100 | - **Tools**:
101 | - [DuckDuckGo Search Tool](https://ai.pydantic.dev/common-tools/#duckduckgo-search-tool)
102 | - **Location**: [src/app/agents/agent_system.py](https://github.com/qte77/Agents-eval/blob/main/src/app/agents/agent_system.py)
103 |
104 | #### Analyst Agent
105 |
106 | - **Description**: Checks the accuracy of assumptions, facts, and conclusions in the provided data, providing relevant feedback and ensuring data integrity.
107 | - **Responsibilities**:
108 | - Checks the accuracy of assumptions, facts, and conclusions.
109 | - Provides relevant feedback if the result is not approved.
110 | - Ensures data integrity.
111 | - **Location**: [src/app/agents/agent_system.py](https://github.com/qte77/Agents-eval/blob/main/src/app/agents/agent_system.py)
112 |
113 | #### Synthesizer Agent
114 |
115 | - **Description**: Outputs a well-formatted scientific report using the data provided, maintaining the original facts, conclusions, and sources.
116 | - **Responsibilities**:
117 | - Outputs a well-formatted scientific report using the provided data.
118 | - Maintains the original facts, conclusions, and sources.
119 | - **Location**: [src/app/agents/agent_system.py](https://github.com/qte77/Agents-eval/blob/main/src/app/agents/agent_system.py)
120 |
121 | ### Datasets used
122 |
123 | `# TODO`
124 |
125 | ### Evalutions metrics
126 |
127 | `# TODO`
128 |
129 | - Time to complete task (time_taken)
130 | - Task success rate (task_success)
131 | - Agent coordination (coordination_quality)
132 | - Tool usage efficiency (tool_efficiency)
133 | - Plan coherence (planning_rational)
134 | - Text response quality (text_similarity)
135 | - Autonomy vs. human intervention (HITL, user feedback)
136 | - Reactivity (adapt to changes of tasks and environments)
137 | - Memory consistency
138 |
139 | ### Evaluations Metrics Baseline
140 |
141 | As configured in [config_eval.json](src/app/config/config_eval.json).
142 |
143 | ```json
144 | {
145 | "evaluators_and_weights": {
146 | "planning_rational": "1/6",
147 | "task_success": "1/6",
148 | "tool_efficiency": "1/6",
149 | "coordination_quality": "1/6",
150 | "time_taken": "1/6",
151 | "text_similarity": "1/6"
152 | }
153 | }
154 | ```
155 |
156 | ### Eval Metrics Sweep
157 |
158 |
159 | Eval Metrics Sweep
160 |
161 |
162 |
163 | ### Tools available
164 |
165 | Other pydantic-ai agents and [pydantic-ai DuckDuckGo Search Tool](https://ai.pydantic.dev/common-tools/#duckduckgo-search-tool).
166 |
167 | ### Agentic System Architecture
168 |
169 |
170 | Show Agentic System Architecture
171 |
172 |
173 |
174 | ### Project Repo Structure
175 |
176 |
177 | Show Repo Structure
178 | ```sh
179 | |- .claude # claude code config and commands
180 | |- .devcontainer # pre-configured dev env
181 | |- .github # workflows
182 | |- .streamlit # config.toml
183 | |- .vscode # extensions, settings
184 | |- assets/images
185 | |- docs
186 | |- src # source code
187 | |- app
188 | |- agents
189 | |- config
190 | |- evals
191 | |- utils
192 | |- __init__.py
193 | |- main.py
194 | \- py.typed
195 | |- examples
196 | |- gui
197 | \- run_gui.py
198 | |- tests
199 | |- .env.example # example env vars
200 | |- .gitignore
201 | |- .gitmessage
202 | |- AGENTS.md # common file like agentsmd.com
203 | |- CHANGEOG.md # short project history
204 | |- CLAUDE.md # points to AGENTS.md
205 | |- Dockerfile # create app image
206 | |- LICENSE.md
207 | |- Makefile # helper scripts
208 | |- mkdocs.yaml # docu from docstrings
209 | |- pyproject.toml # project settings
210 | |- README.md # project description
211 | \- uv.lock # resolved package versions
212 | ```
213 |
214 |
215 | ## Landscape overview
216 |
217 | ### Agentic System Frameworks
218 |
219 | - [PydanticAI](https://github.com/pydantic/pydantic-ai)
220 | - [restack](https://www.restack.io/)
221 | - [smolAgents](https://github.com/huggingface/smolagents)
222 | - [AutoGen](https://github.com/microsoft/autogen)
223 | - [Semantic Kernel](https://github.com/microsoft/semantic-kernel)
224 | - [CrewAI](https://github.com/crewAIInc/crewAI)
225 | - [Langchain](https://github.com/langchain-ai/langchain)
226 | - [Langflow](https://github.com/langflow-ai/langflow)
227 |
228 | ### Agent-builder
229 |
230 | - [Archon](https://github.com/coleam00/Archon)
231 | - [Agentstack](https://github.com/AgentOps-AI/AgentStack)
232 |
233 | ### Evaluation
234 |
235 | - Focusing on agentic systems
236 | - [AgentNeo](https://github.com/raga-ai-hub/agentneo)
237 | - [AutoGenBench](https://github.com/microsoft/autogen/blob/0.2/samples/tools/autogenbench)
238 | - [Langchain AgentEvals](https://github.com/langchain-ai/agentevals)
239 | - [Mosaic AI Agent Evaluation](https://docs.databricks.com/en/generative-ai/agent-evaluation/index.html)
240 | - [RagaAI-Catalyst](https://github.com/raga-ai-hub/RagaAI-Catalyst)
241 | - [AgentBench](https://github.com/THUDM/AgentBench)
242 | - RAG oriented
243 | - [RAGAs](https://github.com/explodinggradients/ragas)
244 | - LLM apps
245 | - [DeepEval](https://github.com/confident-ai/deepeval)
246 | - [Langchain OpenEvals](https://github.com/langchain-ai/openevals)
247 | - [MLFlow LLM Evaluate](https://mlflow.org/docs/latest/llms/llm-evaluate/index.html)
248 | - [DeepEval (DeepSeek)]( github.com/confident-ai/deepeval)
249 |
250 | ### Observation, Monitoring, Tracing
251 |
252 | - [AgentOps - Agency](https://www.agentops.ai/)
253 | - [arize](https://arize.com/)
254 | - [Langtrace](https://www.langtrace.ai/)
255 | - [LangSmith - Langchain](https://www.langchain.com/langsmith)
256 | - [Weave - Weights & Biases](https://wandb.ai/site/weave/)
257 | - [Pydantic- Logfire](https://pydantic.dev/logfire)
258 |
259 | ### Datasets
260 |
261 | - [awesome-reasoning - Collection of datasets](https://github.com/neurallambda/awesome-reasoning)
262 |
263 | #### Scientific
264 |
265 | - [SWIF2T](https://arxiv.org/abs/2405.20477), Automated Focused Feedback Generation for Scientific Writing Assistance, 2024, 300 peer reviews citing weaknesses in scientific papers and conduct human evaluation
266 | - [PeerRead](https://github.com/allenai/PeerRead), A Dataset of Peer Reviews (PeerRead): Collection, Insights and NLP Applications, 2018, 14K paper drafts and the corresponding accept/reject decisions, over 10K textual peer reviews written by experts for a subset of the papers, structured JSONL, clear labels
267 | - [BigSurvey](https://www.ijcai.org/proceedings/2022/0591.pdf), Generating a Structured Summary of Numerous Academic Papers: Dataset and Method, 2022, 7K survey papers and 430K referenced papers abstracts
268 | - [SciXGen](https://arxiv.org/abs/2110.10774), A Scientific Paper Dataset for Context-Aware Text Generation, 2021, 205k papers
269 | - [scientific_papers](https://huggingface.co/datasets/armanc/scientific_papers), 2018, two sets of long and structured documents, obtained from ArXiv and PubMed OpenAccess, 300k+ papers, total disk 7GB
270 |
271 | #### Reasoning, Deduction, Commonsense, Logic
272 |
273 | - [LIAR](https://www.cs.ucsb.edu/~william/data/liar_dataset.zip), fake news detection, only 12.8k records, single label
274 | - [X-Fact](https://github.com/utahnlp/x-fact/), Benchmark Dataset for Multilingual Fact Checking, 31.1k records, large, multilingual
275 | - [MultiFC](https://www.copenlu.com/publication/2019_emnlp_augenstein/), A Real-World Multi-Domain Dataset for Evidence-Based Fact Checking of Claims, 34.9k records
276 | - [FEVER](https://fever.ai/dataset/fever.html), Fact Extraction and VERification, 185.4k records
277 | - TODO GSM8K, bAbI, CommonsenseQA, DROP, LogiQA, MNLI
278 |
279 | #### Planning, Execution
280 |
281 | - [Plancraft](https://arxiv.org/abs/2412.21033), an evaluation dataset for planning with LLM agents, both a text-only and multi-modal interface
282 | - [IDAT](https://arxiv.org/abs/2407.08898), A Multi-Modal Dataset and Toolkit for Building and Evaluating Interactive Task-Solving Agents
283 | - [PDEBench](https://github.com/pdebench/PDEBench), set of benchmarks for scientific machine learning
284 | - [MatSci-NLP](https://arxiv.org/abs/2305.08264), evaluating the performance of natural language processing (NLP) models on materials science text
285 | - TODO BigBench Hard, FSM Game
286 |
287 | #### Tool Use, Function Invocation
288 |
289 | - [Trelis Function Calling](https://huggingface.co/datasets/Trelis/function_calling_v3)
290 | - [KnowLM Tool](https://huggingface.co/datasets/zjunlp/KnowLM-Tool)
291 | - [StatLLM](https://arxiv.org/abs/2502.17657), statistical analysis tasks, LLM-generated SAS code, and human evaluation scores
292 | - TODO ToolComp
293 |
294 | ### Benchmarks
295 |
296 | - [SciArena: A New Platform for Evaluating Foundation Models in Scientific Literature Tasks](https://allenai.org/blog/sciarena)
297 | - [AgentEvals CORE-Bench Leaderboard](https://huggingface.co/spaces/agent-evals/core_leaderboard)
298 | - [Berkeley Function-Calling Leaderboard](https://gorilla.cs.berkeley.edu/leaderboard.html)
299 | - [Chatbot Arena LLM Leaderboard](https://lmsys.org/projects/)
300 | - [GAIA Leaderboard](https://gaia-benchmark-leaderboard.hf.space/)
301 | - [GalileoAI Agent Leaderboard](https://huggingface.co/spaces/galileo-ai/agent-leaderboard)
302 | - [WebDev Arena Leaderboard](https://web.lmarena.ai/leaderboard)
303 | - [MiniWoB++: a web interaction benchmark for reinforcement learning](https://miniwob.farama.org/)
304 |
305 | ### Research Agents
306 |
307 | - [Ai2 Scholar QA](https://qa.allen.ai/chat)
308 |
309 | ## Further Reading
310 |
311 | - [[2506.18096] Deep Research Agents: A Systematic Examination And Roadmap](https://arxiv.org/abs/2506.18096), [gh / ai-agents-2030 / awesome-deep-research-agent](https://github.com/ai-agents-2030/awesome-deep-research-agent)
312 | - [[2504.19678] From LLM Reasoning to Autonomous AI Agents: A Comprehensive Review](https://arxiv.org/abs/2504.19678)
313 | - [[2503.21460] Large Language Model Agent: A Survey on Methodology, Applications and Challenges](https://arxiv.org/abs/2503.21460)
314 | - [[2503.16416] Survey on Evaluation of LLM-based Agents](https://arxiv.org/abs/2503.16416)
315 | - [[2503.13657] Why Do Multi-Agent LLM Systems Fail?](https://arxiv.org/abs/2503.13657)
316 | - [[2502.14776] SurveyX: Academic Survey Automation via Large Language Models](https://arxiv.org/abs/2502.14776)
317 | - [[2502.05957] AutoAgent: A Fully-Automated and Zero-Code Framework for LLM Agents](https://arxiv.org/abs/2502.05957)
318 | - [[2502.02649] Fully Autonomous AI Agents Should Not be Developed](https://arxiv.org/abs/2502.02649)
319 | - [[2501.16150] AI Agents for Computer Use: A Review of Instruction-based Computer Control, GUI Automation, and Operator Assistants](https://arxiv.org/abs/2501.16150)
320 | - [[2501.06590] ChemAgent](https://arxiv.org/abs/2501.06590)
321 | - [[2501.06322] Multi-Agent Collaboration Mechanisms: A Survey of LLMs](https://arxiv.org/abs/2501.06322)
322 | - [[2501.04227] Agent Laboratory: Using LLM Agents as Research Assitants](https://arxiv.org/abs/2501.04227), [AgentRxiv:Towards Collaborative Autonomous Research](https://agentrxiv.github.io/)
323 | - [[2501.00881] Agentic Systems: A Guide to Transforming Industries with Vertical AI Agents](https://arxiv.org/abs/2501.00881)
324 | - [[2412.04093] Practical Considerations for Agentic LLM Systems](https://arxiv.org/abs/2412.04093)
325 | - [[2411.13768] Evaluation-driven Approach to LLM Agents](https://arxiv.org/abs/2411.13768)
326 | - [[2411.10478] Large Language Models for Constructing and Optimizing Machine Learning Workflows: A Survey](https://arxiv.org/abs/2411.10478)
327 | - [[2411.05285] A taxonomy of agentops for enabling observability of foundation model based agents](https://arxiv.org/abs/2411.05285)
328 | - [[2410.22457] Advancing Agentic Systems: Dynamic Task Decomposition, Tool Integration and Evaluation using Novel Metrics and Dataset](https://arxiv.org/abs/2410.22457)
329 | - [[2408.06361] Large Language Model Agent in Financial Trading: A Survey](https://arxiv.org/abs/2408.06361)
330 | - [[2408.06292] The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery](https://arxiv.org/abs/2408.06292)
331 | - [[2404.13501] A Survey on the Memory Mechanism of Large Language Model based Agents](https://arxiv.org/pdf/2404.13501)
332 | - [[2402.06360] CoSearchAgent: A Lightweight Collaborative Search Agent with Large Language Models](https://arxiv.org/abs/2402.06360)
333 | - [[2402.02716] Understanding the planning of LLM agents: A survey](https://arxiv.org/abs/2402.02716)
334 | - [[2402.01030] Executable Code Actions Elicit Better LLM Agents](https://arxiv.org/abs/2402.01030)
335 | - [[2308.11432] A Survey on Large Language Model based Autonomous Agents](https://arxiv.org/abs/2308.11432)
336 |
--------------------------------------------------------------------------------
/assets/images/c4-multi-agent-system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qte77/Agents-eval/7401b21b53bd8307e7fe8465b466595a7687f8c8/assets/images/c4-multi-agent-system.png
--------------------------------------------------------------------------------
/assets/images/customer-journey-activity-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qte77/Agents-eval/7401b21b53bd8307e7fe8465b466595a7687f8c8/assets/images/customer-journey-activity-dark.png
--------------------------------------------------------------------------------
/assets/images/customer-journey-activity-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qte77/Agents-eval/7401b21b53bd8307e7fe8465b466595a7687f8c8/assets/images/customer-journey-activity-light.png
--------------------------------------------------------------------------------
/assets/images/metrics-eval-sweep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qte77/Agents-eval/7401b21b53bd8307e7fe8465b466595a7687f8c8/assets/images/metrics-eval-sweep.png
--------------------------------------------------------------------------------
/context/PRPs/coordination_quality.md:
--------------------------------------------------------------------------------
1 | # Coordination Quality Feature PRP
2 |
3 | ## Goal
4 |
5 | Implement a comprehensive coordination quality measurement and monitoring system for the multi-agent evaluation framework to assess how effectively agents collaborate, delegate tasks, and maintain workflow integrity.
6 |
7 | ## Why
8 |
9 | - **Evaluation Completeness**: The coordination_quality metric is defined in `config_eval.json` (0.167 weight) but not implemented
10 | - **System Reliability**: Need to measure and improve agent coordination failures and bottlenecks
11 | - **Performance Optimization**: Identify coordination inefficiencies that impact overall system performance
12 | - **Research Value**: Provide quantitative data on multi-agent coordination patterns for evaluation research
13 |
14 | ## What
15 |
16 | A coordination quality monitoring system that measures:
17 |
18 | - Task delegation success rates between agents
19 | - Inter-agent communication efficiency and latency
20 | - Workflow completion rates and error recovery
21 | - Resource utilization across agent interactions
22 | - Coordination failure detection and analysis
23 |
24 | ### Success Criteria
25 |
26 | - [ ] Coordination quality metric implemented and functional in evaluation system
27 | - [ ] Real-time coordination monitoring dashboard
28 | - [ ] Coordination failure detection and alerting
29 | - [ ] Performance metrics collection and analysis
30 | - [ ] Integration with existing evaluation pipeline
31 |
32 | ## All Needed Context
33 |
34 | ### Documentation & References
35 |
36 | ```yaml
37 | - file: /workspaces/Agents-eval/src/app/agents/agent_system.py
38 | why: Core coordination logic, delegation patterns, tool-based coordination
39 | critical: Lines 91-99 show delegation pattern, _validate_model_return validation
40 |
41 | - file: /workspaces/Agents-eval/src/app/config/data_models.py
42 | why: Data contracts for coordination, Pydantic models for agent communication
43 | critical: ResearchResult, AnalysisResult, ResearchSummary models
44 |
45 | - file: /workspaces/Agents-eval/src/app/config/config_eval.json
46 | why: Coordination quality metric weight (0.167) defined but not implemented
47 | critical: Need to implement the missing coordination_quality metric
48 |
49 | - file: /workspaces/Agents-eval/src/app/evals/metrics.py
50 | why: Evaluation metrics implementation patterns
51 | critical: How other metrics are implemented and integrated
52 |
53 | - file: /workspaces/Agents-eval/src/app/config/config_chat.json
54 | why: Agent prompts defining coordination behavior and approval workflows
55 | critical: Manager agent orchestration prompts
56 | ```
57 |
58 | ### Current Codebase Tree
59 |
60 | ```bash
61 | src/app/
62 | ├── agents/
63 | │ ├── agent_system.py # Core coordination logic
64 | │ └── llm_model_funs.py # Model management
65 | ├── config/
66 | │ ├── config_app.py # Common app configuration
67 | │ ├── data_models.py # Coordination data contracts
68 | │ ├── config_chat.json # Agent coordination prompts
69 | │ └── config_eval.json # Evaluation metrics (coordination_quality: 0.167)
70 | ├── evals/
71 | │ └── metrics.py # Evaluation metrics implementation
72 | ├── utils/
73 | │ ├── error_messages.py # Error handling patterns
74 | │ └── log.py # Logging utilities
75 | └── main.py # Entry point
76 | ```
77 |
78 | ### Desired Codebase Tree
79 |
80 | ```bash
81 | src/app/
82 | ├── evals/
83 | │ ├── [existing folders unchanged]
84 | │ ├── coordination_quality/
85 | │ │ ├── __init__.py
86 | │ │ ├── quality_metrics.py # Coordination quality measurement
87 | │ │ ├── monitoring.py # Real-time coordination monitoring
88 | │ │ └── analyzer.py # Coordination pattern analysis
89 | │ └── metrics.py # Updated with coordination_quality implementation
90 | └── [existing files unchanged]
91 | ```
92 |
93 | ### Known Gotchas & Library Quirks
94 |
95 | ```python
96 | # CRITICAL: PydanticAI coordination patterns
97 | # - Tool-based delegation via @agent.tool decorator
98 | # - Usage tracking shared via RunContext
99 | # - Streaming with Pydantic models has NotImplementedError in agent_system.py
100 |
101 | # GOTCHA: Validation requirements
102 | # - All agent communication must use _validate_model_return()
103 | # - Pydantic models required for type safety
104 | # - Error handling must follow utils/error_messages.py patterns
105 |
106 | # LIBRARY QUIRK: PydanticAI Usage Limits
107 | # - UsageLimits shared across agents via RunContext
108 | # - Coordination can fail if usage limits exceeded
109 | # - Need to track usage per coordination step
110 | ```
111 |
112 | ## Implementation Blueprint
113 |
114 | ### Data Models and Structure
115 |
116 | ```python
117 | # coordination/quality_metrics.py
118 | class CoordinationMetrics(BaseModel):
119 | """Coordination quality metrics data model."""
120 |
121 | delegation_success_rate: float
122 | communication_latency: float
123 | workflow_completion_rate: float
124 | error_recovery_rate: float
125 | resource_utilization: float
126 | coordination_score: float
127 |
128 | class CoordinationEvent(BaseModel):
129 | """Individual coordination event tracking."""
130 |
131 | timestamp: datetime
132 | source_agent: str
133 | target_agent: str
134 | event_type: str # delegation, response, error, retry
135 | success: bool
136 | latency_ms: float
137 | error_message: str | None = None
138 | ```
139 |
140 | ### List of Tasks
141 |
142 | ```yaml
143 | Task 1:
144 | CREATE src/app/coordination/__init__.py:
145 | - EMPTY file for Python package
146 |
147 | Task 2:
148 | CREATE src/app/coordination/quality_metrics.py:
149 | - IMPLEMENT CoordinationMetrics and CoordinationEvent models
150 | - IMPLEMENT calculate_coordination_quality() function
151 | - PATTERN: Follow existing Pydantic models in data_models.py
152 |
153 | Task 3:
154 | CREATE src/app/coordination/monitoring.py:
155 | - IMPLEMENT CoordinationMonitor class
156 | - TRACK delegation events, latency, success rates
157 | - PATTERN: Use existing logging patterns from utils/log.py
158 |
159 | Task 4:
160 | CREATE src/app/coordination/analyzer.py:
161 | - IMPLEMENT coordination pattern analysis
162 | - DETECT coordination failures and bottlenecks
163 | - GENERATE coordination quality reports
164 |
165 | Task 5:
166 | MODIFY src/app/agents/agent_system.py:
167 | - FIND _add_tools_to_manager_agent function
168 | - INJECT coordination monitoring into delegation tools
169 | - PRESERVE existing delegation patterns
170 |
171 | Task 6:
172 | MODIFY src/app/evals/metrics.py:
173 | - IMPLEMENT coordination_quality metric function
174 | - INTEGRATE with existing metrics calculation
175 | - MIRROR pattern from other metric implementations
176 |
177 | Task 7:
178 | CREATE tests/test_coordination_quality.py:
179 | - TEST coordination metrics calculation
180 | - TEST monitoring functionality
181 | - TEST integration with evaluation pipeline
182 | ```
183 |
184 | ### Per Task Pseudocode
185 |
186 | ```python
187 | # Task 2: quality_metrics.py
188 | class CoordinationMetrics(BaseModel):
189 | delegation_success_rate: float = Field(ge=0.0, le=1.0)
190 | communication_latency: float = Field(ge=0.0)
191 | workflow_completion_rate: float = Field(ge=0.0, le=1.0)
192 | error_recovery_rate: float = Field(ge=0.0, le=1.0)
193 | resource_utilization: float = Field(ge=0.0, le=1.0)
194 | coordination_score: float = Field(ge=0.0, le=1.0)
195 |
196 | def calculate_coordination_quality(events: list[CoordinationEvent]) -> CoordinationMetrics:
197 | """Calculate coordination quality from event history."""
198 | # PATTERN: Weighted average of coordination dimensions
199 | # CRITICAL: Handle empty events list gracefully
200 | if not events:
201 | return CoordinationMetrics(...)
202 |
203 | # Calculate individual metrics
204 | success_rate = sum(e.success for e in events) / len(events)
205 | avg_latency = sum(e.latency_ms for e in events) / len(events)
206 | # ... other calculations
207 |
208 | # Weighted coordination score
209 | coordination_score = (
210 | success_rate * 0.3 +
211 | normalized_latency * 0.2 +
212 | completion_rate * 0.3 +
213 | recovery_rate * 0.2
214 | )
215 |
216 | return CoordinationMetrics(
217 | coordination_score=coordination_score,
218 | # ... other metrics
219 | )
220 |
221 | # Task 3: monitoring.py
222 | class CoordinationMonitor:
223 | def __init__(self):
224 | self.events: list[CoordinationEvent] = []
225 | self.logger = logger # From utils/log.py
226 |
227 | async def track_delegation(self, source: str, target: str, func: Callable):
228 | """Track delegation with timing and success monitoring."""
229 | start_time = time.time()
230 |
231 | try:
232 | result = await func()
233 | # PATTERN: Log successful coordination
234 | self.logger.info(f"Delegation {source} -> {target} successful")
235 |
236 | # Record successful event
237 | self._record_event(
238 | source_agent=source,
239 | target_agent=target,
240 | event_type="delegation",
241 | success=True,
242 | latency_ms=(time.time() - start_time) * 1000
243 | )
244 |
245 | return result
246 |
247 | except Exception as e:
248 | # PATTERN: Log coordination failures
249 | self.logger.error(f"Delegation {source} -> {target} failed: {str(e)}")
250 |
251 | # Record failed event
252 | self._record_event(
253 | source_agent=source,
254 | target_agent=target,
255 | event_type="delegation",
256 | success=False,
257 | latency_ms=(time.time() - start_time) * 1000,
258 | error_message=str(e)
259 | )
260 |
261 | raise
262 |
263 | # Task 5: agent_system.py integration
264 | # MODIFY delegate_research function
265 | @manager_agent.tool
266 | async def delegate_research(ctx: RunContext[None], query: str) -> ResearchResult:
267 | """Delegate research task to ResearchAgent."""
268 | # INJECT: Coordination monitoring
269 | monitor = CoordinationMonitor()
270 |
271 | async def _research_task():
272 | result = await research_agent.run(query, usage=ctx.usage)
273 | return _validate_model_return(str(result.output), ResearchResult)
274 |
275 | # PATTERN: Track delegation with monitoring
276 | return await monitor.track_delegation("manager", "researcher", _research_task)
277 | ```
278 |
279 | ### Integration Points
280 |
281 | ```yaml
282 | EVALUATION_SYSTEM:
283 | - modify: src/app/evals/metrics.py
284 | - pattern: "def coordination_quality(result: Any) -> float:"
285 | - integration: "Add to evaluation pipeline alongside existing metrics"
286 |
287 | CONFIGURATION:
288 | - modify: src/app/config/config_eval.json
289 | - pattern: "coordination_quality metric already defined with weight 0.167"
290 | - validation: "Ensure metric returns float between 0.0 and 1.0"
291 |
292 | LOGGING:
293 | - integrate: src/app/utils/log.py
294 | - pattern: "Use existing logger for coordination events"
295 | - level: "INFO for successful coordination, ERROR for failures"
296 | ```
297 |
298 | ## Validation Loop
299 |
300 | ### Level 1: Syntax & Style
301 |
302 | ```bash
303 | # Run these FIRST - fix any errors before proceeding
304 | make ruff # Format and fix linting issues
305 | make type_check # Type checking with mypy
306 |
307 | # Expected: No errors. If errors, READ the error and fix.
308 | ```
309 |
310 | ### Level 2: Unit Tests
311 |
312 | ```python
313 | # CREATE tests/test_coordination_quality.py
314 | def test_coordination_metrics_calculation():
315 | """Test coordination quality calculation with sample events."""
316 | events = [
317 | CoordinationEvent(
318 | timestamp=datetime.now(),
319 | source_agent="manager",
320 | target_agent="researcher",
321 | event_type="delegation",
322 | success=True,
323 | latency_ms=150.0
324 | ),
325 | # ... more test events
326 | ]
327 |
328 | metrics = calculate_coordination_quality(events)
329 | assert 0.0 <= metrics.coordination_score <= 1.0
330 | assert metrics.delegation_success_rate >= 0.0
331 |
332 | def test_coordination_monitoring():
333 | """Test coordination monitoring functionality."""
334 | monitor = CoordinationMonitor()
335 |
336 | # Test successful delegation tracking
337 | async def dummy_task():
338 | return "success"
339 |
340 | result = await monitor.track_delegation("manager", "researcher", dummy_task)
341 | assert result == "success"
342 | assert len(monitor.events) == 1
343 | assert monitor.events[0].success is True
344 |
345 | def test_coordination_quality_metric():
346 | """Test integration with evaluation metrics."""
347 | # PATTERN: Test similar to other metrics in the evaluation system
348 | sample_result = {"coordination_events": [...]}
349 | quality_score = coordination_quality(sample_result)
350 | assert isinstance(quality_score, float)
351 | assert 0.0 <= quality_score <= 1.0
352 | ```
353 |
354 | ```bash
355 | # Run and iterate until passing:
356 | make test_all
357 | # If failing: Read error, understand root cause, fix code, re-run
358 | ```
359 |
360 | ### Level 3: Integration Test
361 |
362 | ```bash
363 | # Test the coordination quality in full evaluation
364 | make run_cli ARGS="--query 'test coordination quality' --eval"
365 |
366 | # Expected: Coordination quality metric appears in evaluation results
367 | # If error: Check logs for coordination monitoring issues
368 | ```
369 |
370 | ## Final Validation Checklist
371 |
372 | - [ ] All tests pass: `make test_all`
373 | - [ ] No linting errors: `make ruff`
374 | - [ ] No type errors: `make type_check`
375 | - [ ] Coordination quality metric integrated in evaluation pipeline
376 | - [ ] Coordination monitoring tracks delegation events
377 | - [ ] Error cases handled gracefully with proper logging
378 | - [ ] Performance impact minimal (< 5% overhead)
379 | - [ ] Documentation updated in AGENTS.md if needed
380 |
381 | ## Anti-Patterns to Avoid
382 |
383 | - ❌ Don't break existing delegation patterns in agent_system.py
384 | - ❌ Don't ignore coordination failures - log and track them
385 | - ❌ Don't add excessive monitoring overhead that slows coordination
386 | - ❌ Don't hardcode coordination thresholds - make them configurable
387 | - ❌ Don't skip validation of coordination metrics calculation
388 | - ❌ Don't assume all coordination events are successful - handle failures gracefully
389 |
--------------------------------------------------------------------------------
/context/PRPs/features/coordination_quality.md:
--------------------------------------------------------------------------------
1 | # Feature description for: coordination_quality
2 |
3 | As put forward by [context-engineering-intro](https://github.com/qte77/context-engineering-intro).
4 |
5 | ## FEATURE
6 |
7 | coordination_quality
8 |
9 | ## EXAMPLES
10 |
11 | [Provide and explain examples that you have in the `PRPs/examples/` folder]
12 |
13 | ## DOCUMENTATION
14 |
15 | [List out any documentation (web pages, sources for an MCP server like Crawl4AI RAG, etc.) that will need to be referenced during development]
16 |
17 | ## OTHER CONSIDERATIONS
18 |
19 | [Any other considerations or specific requirements - great place to include gotchas that you see AI coding assistants miss with your projects a lot]
20 |
--------------------------------------------------------------------------------
/context/PRPs/features/tool_efficiency.md:
--------------------------------------------------------------------------------
1 | # Feature description for: tool_efficiency
2 |
3 | As put forward by [context-engineering-intro](https://github.com/qte77/context-engineering-intro).
4 |
5 | ## FEATURE
6 |
7 | tool_efficiency
8 |
9 | ## EXAMPLES
10 |
11 | [Provide and explain examples that you have in the `PRPs/examples/` folder]
12 |
13 | ## DOCUMENTATION
14 |
15 | [List out any documentation (web pages, sources for an MCP server like Crawl4AI RAG, etc.) that will need to be referenced during development]
16 |
17 | ## OTHER CONSIDERATIONS
18 |
19 | [Any other considerations or specific requirements - great place to include gotchas that you see AI coding assistants miss with your projects a lot]
20 |
--------------------------------------------------------------------------------
/context/PRPs/templates/feature_base.md:
--------------------------------------------------------------------------------
1 | # Feature description for: [ Initial template for new features ]
2 |
3 | As put forward by [context-engineering-intro](https://github.com/qte77/context-engineering-intro).
4 |
5 | ## FEATURE
6 |
7 | [Insert your feature here]
8 |
9 | ## EXAMPLES
10 |
11 | [Provide and explain examples that you have in the `PRPs/examples/` folder]
12 |
13 | ## DOCUMENTATION
14 |
15 | [List out any documentation (web pages, sources for an MCP server like Crawl4AI RAG, etc.) that will need to be referenced during development]
16 |
17 | ## OTHER CONSIDERATIONS
18 |
19 | [Any other considerations or specific requirements - great place to include gotchas that you see AI coding assistants miss with your projects a lot]
20 |
--------------------------------------------------------------------------------
/context/PRPs/templates/prp_base.md:
--------------------------------------------------------------------------------
1 | # "Base PRP Template v2 - Context-Rich with Validation Loops"
2 |
3 | ## Purpose
4 |
5 | Template optimized for AI agents to implement features with sufficient context and self-validation capabilities to achieve working code through iterative refinement.
6 |
7 | ## Core Principles
8 |
9 | 1. **Context is King**: Include ALL necessary documentation, examples, and caveats
10 | 2. **Validation Loops**: Provide executable tests/lints the AI can run and fix
11 | 3. **Information Dense**: Use keywords and patterns from the codebase
12 | 4. **Progressive Success**: Start simple, validate, then enhance
13 | 5. **Global rules**: Be sure to follow all rules in CLAUDE.md
14 |
15 | ---
16 |
17 | ## Goal
18 |
19 | [What needs to be built - be specific about the end state and desires]
20 |
21 | ## Why
22 |
23 | - [Business value and user impact]
24 | - [Integration with existing features]
25 | - [Problems this solves and for whom]
26 |
27 | ## What
28 |
29 | [User-visible behavior and technical requirements]
30 |
31 | ### Success Criteria
32 |
33 | - [ ] [Specific measurable outcomes]
34 |
35 | ## All Needed Context
36 |
37 | ### Documentation & References (list all context needed to implement the feature)
38 |
39 | ```yaml
40 | # MUST READ - Include these in your context window
41 | - url: [Official API docs URL]
42 | why: [Specific sections/methods you'll need]
43 |
44 | - file: [path/to/example.py]
45 | why: [Pattern to follow, gotchas to avoid]
46 |
47 | - doc: [Library documentation URL]
48 | section: [Specific section about common pitfalls]
49 | critical: [Key insight that prevents common errors]
50 |
51 | - docfile: [PRPs/ai_docs/file.md]
52 | why: [docs that the user has pasted in to the project]
53 | ```
54 |
55 | ### Current Codebase tree (run `tree` in the root of the project) to get an overview of the codebase
56 |
57 | ```bash
58 |
59 | ```
60 |
61 | ### Desired Codebase tree with files to be added and responsibility of file
62 |
63 | ```bash
64 |
65 | ```
66 |
67 | ### Known Gotchas of our codebase & Library Quirks
68 |
69 | ```python
70 | # CRITICAL: [Library name] requires [specific setup]
71 | # Example: FastAPI requires async functions for endpoints
72 | # Example: This ORM doesn't support batch inserts over 1000 records
73 | # Example: We use pydantic v2 and
74 | ```
75 |
76 | ## Implementation Blueprint
77 |
78 | ### Data models and structure
79 |
80 | Create the core data models, we ensure type safety and consistency.
81 |
82 | ```python
83 | Examples:
84 | - orm models
85 | - pydantic models
86 | - pydantic schemas
87 | - pydantic validators
88 |
89 | ```
90 |
91 | ### list of tasks to be completed to fullfill the PRP in the order they should be completed
92 |
93 | ```yaml
94 | Task 1:
95 | MODIFY src/existing_module.py:
96 | - FIND pattern: "class OldImplementation"
97 | - INJECT after line containing "def __init__"
98 | - PRESERVE existing method signatures
99 |
100 | CREATE src/new_feature.py:
101 | - MIRROR pattern from: src/similar_feature.py
102 | - MODIFY class name and core logic
103 | - KEEP error handling pattern identical
104 |
105 | ...(...)
106 |
107 | Task N:
108 | ...
109 |
110 | ```
111 |
112 | ### Per task pseudocode as needed added to each task
113 |
114 | ```python
115 |
116 | # Task 1
117 | # Pseudocode with CRITICAL details dont write entire code
118 | async def new_feature(param: str) -> Result:
119 | # PATTERN: Always validate input first (see src/validators.py)
120 | validated = validate_input(param) # raises ValidationError
121 |
122 | # GOTCHA: This library requires connection pooling
123 | async with get_connection() as conn: # see src/db/pool.py
124 | # PATTERN: Use existing retry decorator
125 | @retry(attempts=3, backoff=exponential)
126 | async def _inner():
127 | # CRITICAL: API returns 429 if >10 req/sec
128 | await rate_limiter.acquire()
129 | return await external_api.call(validated)
130 |
131 | result = await _inner()
132 |
133 | # PATTERN: Standardized response format
134 | return format_response(result) # see src/utils/responses.py
135 | ```
136 |
137 | ### Integration Points
138 |
139 | ```yaml
140 | DATABASE:
141 | - migration: "Add column 'feature_enabled' to users table"
142 | - index: "CREATE INDEX idx_feature_lookup ON users(feature_id)"
143 |
144 | CONFIG:
145 | - add to: config/settings.py
146 | - pattern: "FEATURE_TIMEOUT = int(os.getenv('FEATURE_TIMEOUT', '30'))"
147 |
148 | ROUTES:
149 | - add to: src/api/routes.py
150 | - pattern: "router.include_router(feature_router, prefix='/feature')"
151 | ```
152 |
153 | ## Validation Loop
154 |
155 | ### Level 1: Syntax & Style
156 |
157 | ```bash
158 | # Run these FIRST - fix any errors before proceeding
159 | ruff check src/new_feature.py --fix # Auto-fix what's possible
160 | mypy src/new_feature.py # Type checking
161 |
162 | # Expected: No errors. If errors, READ the error and fix.
163 | ```
164 |
165 | ### Level 2: Unit Tests each new feature/file/function use existing test patterns
166 |
167 | ```python
168 | # CREATE test_new_feature.py with these test cases:
169 | def test_happy_path():
170 | """Basic functionality works"""
171 | result = new_feature("valid_input")
172 | assert result.status == "success"
173 |
174 | def test_validation_error():
175 | """Invalid input raises ValidationError"""
176 | with pytest.raises(ValidationError):
177 | new_feature("")
178 |
179 | def test_external_api_timeout():
180 | """Handles timeouts gracefully"""
181 | with mock.patch('external_api.call', side_effect=TimeoutError):
182 | result = new_feature("valid")
183 | assert result.status == "error"
184 | assert "timeout" in result.message
185 | ```
186 |
187 | ```bash
188 | # Run and iterate until passing:
189 | uv run pytest test_new_feature.py -v
190 | # If failing: Read error, understand root cause, fix code, re-run (never mock to pass)
191 | ```
192 |
193 | ### Level 3: Integration Test
194 |
195 | ```bash
196 | # Start the service
197 | uv run python -m src.main --dev
198 |
199 | # Test the endpoint
200 | curl -X POST http://localhost:8000/feature \
201 | -H "Content-Type: application/json" \
202 | -d '{"param": "test_value"}'
203 |
204 | # Expected: {"status": "success", "data": {...}}
205 | # If error: Check logs at logs/app.log for stack trace
206 | ```
207 |
208 | ## Final validation Checklist
209 |
210 | - [ ] All tests pass: `uv run pytest tests/ -v`
211 | - [ ] No linting errors: `uv run ruff check src/`
212 | - [ ] No type errors: `uv run mypy src/`
213 | - [ ] Manual test successful: [specific curl/command]
214 | - [ ] Error cases handled gracefully
215 | - [ ] Logs are informative but not verbose
216 | - [ ] Documentation updated if needed
217 |
218 | ---
219 |
220 | ## Anti-Patterns to Avoid
221 |
222 | - ❌ Don't create new patterns when existing ones work
223 | - ❌ Don't skip validation because "it should work"
224 | - ❌ Don't ignore failing tests - fix them
225 | - ❌ Don't use sync functions in async context
226 | - ❌ Don't hardcode values that should be config
227 | - ❌ Don't catch all exceptions - be specific
228 |
--------------------------------------------------------------------------------
/docs/PRD.md:
--------------------------------------------------------------------------------
1 | # Product Requirements Document (PRD) for Agents-eval
2 |
3 | ## Overview
4 |
5 | **Agents-eval** is a project aimed at evaluating the effectiveness of open-source agentic AI systems across various use cases. The focus is on use case agnostic metrics that measure core capabilities such as task decomposition, tool integration, adaptability, and overall performance.
6 |
7 | ## Goals
8 |
9 | - **Evaluate Agentic AI Systems:** Provide a comprehensive evaluation pipeline to assess the performance of agentic AI systems.
10 | - **Metric Development:** Develop and implement metrics that are agnostic to specific use cases but measure core agentic capabilities.
11 | - **Continuous Improvement:** Promote continuous improvement through automated testing, version control, and documentation.
12 |
13 | ## Functional Requirements
14 |
15 | ### CLI
16 |
17 | - **Command Line Interface:**
18 | - Commands to start, stop, and check the status of the Ollama server or remote inference endpoints.
19 | - Commands to download or call models and run tests.
20 |
21 | ### Frontend (Streamlit)
22 |
23 | - **User Interface:**
24 | - Display test results and system performance metrics.
25 |
26 | ### (Optional) Backend (FastAPI)
27 |
28 | - **Agentic System Integration:**
29 | - Support for adding tools to agents using Pydantic-AI.
30 | - Ensure agents can use tools effectively and return expected results.
31 | - **Model Management:**
32 | - Ability to download, list, and manage models using the `ollama` Python package.
33 | - **API Endpoints:**
34 | - Endpoint to start and check the status of the Ollama server.
35 | - Endpoint to download and manage models.
36 | - Endpoint to run tests and return results.
37 |
38 | ## Non-Functional Requirements
39 |
40 | - **Maintainability:**
41 | - Use modular design patterns for easy updates and maintenance.
42 | - Implement logging and error handling for debugging and monitoring.
43 | - **Documentation:**
44 | - Comprehensive documentation for setup, usage, and testing.
45 | - **Scalability:**
46 | - Design the system to handle multiple concurrent requests.
47 | - **Performance:**
48 | - Ensure low latency in server responses and model downloads.
49 | - Optimize for memory usage and CPU/GPU utilization.
50 | - **Security:**
51 | - Implement secure communication between components.
52 | - Use environment variables for sensitive information.
53 |
54 | ## Assumptions
55 |
56 | - **Remote Inference Endpoints:** The project can use remote inference endpoints provided within a `config.json` and using API keys from `.env`.
57 | - **Local Ollama Server:** The project can make use of a local Ollama server for model hosting and inference.
58 | - **Python Environment:** The project uses Python 3.12 and related tools like `uv` for dependency management.
59 | - **GitHub Actions:** CI/CD pipelines are set up using GitHub Actions for automated testing, version bumping, and documentation deployment.
60 |
61 | ## Constraints
62 |
63 | - **Hardware:** The project assumes access to appropriate hardwareif running the Ollama server and models, including sufficient RAM and GPU capabilities.
64 | - **Software:** Requires Python 3.12, `uv`, and other dependencies listed in `pyproject.toml`.
65 |
66 | ## Main Dependencies
67 |
68 | - **Pydantic-AI:** For agent and tool management.
69 | - **Pytest:** For testing.
70 | - **Ollama:** For local model hosting and inference.
71 | - **Streamlit:** For frontend dashboard.
72 | - **Ruff:** For code linting.
73 | - **MkDocs:** For documentation generation.
74 |
75 | ## Future Enhancements
76 |
77 | - **Additional Metrics:** Develop more metrics to evaluate agentic systems.
78 | - **Integration with More Frameworks:** Expand compatibility with other agentic system frameworks. Meaning other popular agentic system frameworks like LangChain, AutoGen, CrewAI, LangGraph, Semantic Kernel, and smolAgents.
79 | - **Performance Optimization:** Further optimize for latency and resource usage.
80 | - **User Feedback:** Implement a feedback loop for users to report issues or suggest improvements.
81 |
--------------------------------------------------------------------------------
/docs/SprintPlan.md:
--------------------------------------------------------------------------------
1 | # Project Plan Outline
2 |
3 | ## Week 1 starting 2025-03-31: Metric Development and CLI Enhancements
4 |
5 | ### Milestones
6 |
7 | - Metric Development: Implement at least three new metrics for evaluating agentic AI systems.
8 | - CLI Streaming: Enhance the CLI to stream Pydantic-AI output.
9 |
10 | ### Tasks and Sequence
11 |
12 | - [ ] Research and Design New Metrics
13 | - Task Definition: Conduct literature review and design three new metrics that are agnostic to specific use cases but measure core agentic capabilities.
14 | - Sequence: Before implementing any code changes.
15 | - Definition of Done: A detailed document outlining the metrics, their mathematical formulations, and how they will be integrated into the evaluation pipeline.
16 | - [ ] Implement New Metrics
17 | - Task Definition: Write Python code to implement the new metrics, ensuring they are modular and easily integratable with existing evaluation logic.
18 | - Sequence: After completing the design document.
19 | - Definition of Done: Unit tests for each metric pass, and they are successfully integrated into the evaluation pipeline.
20 | - [ ] Enhance CLI for Streaming
21 | - Task Definition: Modify the CLI to stream Pydantic-AI output using asynchronous functions.
22 | - Sequence: Concurrently with metric implementation.
23 | - Definition of Done: The CLI can stream output from Pydantic-AI models without blocking, and tests demonstrate successful streaming.
24 | - [ ] Update Documentation
25 | - Task Definition: Update PRD.md and README.md to reflect new metrics and CLI enhancements.
26 | - Sequence: After completing metric implementation and CLI enhancements.
27 | - Definition of Done: PRD.md includes detailed descriptions of new metrics, and README.md provides instructions on how to use the enhanced CLI.
28 |
29 | ## Week 2 starting 2025-03-07: Streamlit GUI Enhancements and Testing
30 |
31 | ### Milestones
32 |
33 | - Streamlit GUI Output: Enhance the Streamlit GUI to display streamed output from Pydantic-AI.
34 | - Comprehensive Testing: Perform thorough testing of the entire system with new metrics and GUI enhancements.
35 |
36 | ### Tasks and Sequence
37 |
38 | - [ ] Enhance Streamlit GUI
39 | - Task Definition: Modify the Streamlit GUI to display the streamed output from Pydantic-AI models.
40 | - Sequence: Start of Week 2.
41 | - Definition of Done: The GUI can display streamed output without errors, and user interactions (e.g., selecting models, inputting queries) work as expected.
42 | - [ ] Integrate New Metrics into GUI
43 | - Task Definition: Ensure the Streamlit GUI can display results from the new metrics.
44 | - Sequence: After enhancing the GUI for streamed output.
45 | - Definition of Done: The GUI displays metric results clearly, and users can easily interpret the output.
46 | - [ ] Comprehensive System Testing
47 | - Task Definition: Perform end-to-end testing of the system, including new metrics and GUI enhancements.
48 | - Sequence: After integrating new metrics into the GUI.
49 | - Definition of Done: All tests pass without errors, and the system functions as expected in various scenarios.
50 | - [ ] Finalize Documentation and Deployment
51 | - Task Definition: Update MkDocs documentation to reflect all changes and deploy it to GitHub Pages.
52 | - Sequence: After completing system testing.
53 | - Definition of Done: Documentation is updated, and the latest version is live on GitHub Pages.
54 |
55 | ## Additional Considerations
56 |
57 | - Code Reviews: Schedule regular code reviews to ensure quality and adherence to project standards.
58 | - Feedback Loop: Establish a feedback loop with stakeholders to gather input on the new metrics and GUI enhancements.
59 |
--------------------------------------------------------------------------------
/docs/UserStory.md:
--------------------------------------------------------------------------------
1 | # User Story for Agents-eval
2 |
3 | ## Introduction
4 |
5 | Agents-eval is designed to evaluate the effectiveness of open-source agentic AI systems across various use cases. This user story focuses on the perspective of Gez, an AI researcher who aims to assess and improve these systems using Agents-eval.
6 |
7 | ## User Profile
8 |
9 | - **Name:** Gez
10 | - **Role:** AI Researcher
11 | - **Goals:**
12 | - Evaluate the performance of agentic AI systems.
13 | - Identify areas for improvement in these systems.
14 | - Develop and integrate new metrics for evaluation.
15 |
16 | ## User Story
17 |
18 | **As** an AI researcher,
19 | **I want** to use Agents-eval to evaluate the effectiveness of agentic AI systems,
20 | **so that** I can assess their performance across different use cases and improve their capabilities.
21 |
22 | ### Acceptance Criteria
23 |
24 | 1. **Evaluation Pipeline:**
25 | - The system should provide a comprehensive evaluation pipeline that measures core agentic capabilities such as task decomposition, tool integration, adaptability, and overall performance.
26 | - The pipeline should support multiple agentic AI frameworks (e.g., Pydantic-AI, LangChain).
27 |
28 | 2. **Metric Development:**
29 | - The system should allow for the development and integration of new metrics that are agnostic to specific use cases.
30 | - These metrics should be modular and easily integratable with existing evaluation logic.
31 |
32 | 3. **CLI and GUI Interactions:**
33 | - The system should offer both a CLI and a Streamlit GUI for user interaction.
34 | - The CLI should support streaming output from Pydantic-AI models.
35 | - The Streamlit GUI should display streamed output and provide an intuitive interface for setting up and running evaluations.
36 |
37 | 4. **Documentation and Feedback:**
38 | - The system should include comprehensive documentation for setup, usage, and testing.
39 | - There should be a feedback loop for users to report issues or suggest improvements.
40 |
41 | ## Example Scenario
42 |
43 | - **Scenario:** Gez wants to evaluate a research agent system using Agents-eval.
44 | - **Steps:**
45 | 1. She sets up the environment using the CLI or devcontainer.
46 | 2. She configures the agent system with the desired models and tools.
47 | 3. She runs the evaluation using the CLI or Streamlit GUI.
48 | 4. She views the results and metrics displayed by the system.
49 | 5. She provides feedback on the system's performance and suggests improvements.
50 |
51 | ## Benefits
52 |
53 | - **Improved Evaluation Capabilities:** Agents-eval provides a structured approach to evaluating agentic AI systems, allowing researchers to focus on improving these systems.
54 | - **Flexibility and Customization:** The system supports multiple frameworks and allows for the development of new metrics, making it adaptable to various research needs.
55 | - **Enhanced User Experience:** The combination of CLI and GUI interfaces offers flexibility in how users interact with the system, catering to different preferences and workflows.
56 |
--------------------------------------------------------------------------------
/docs/architecture/c4-multi-agent-system.plantuml:
--------------------------------------------------------------------------------
1 | @startuml "Multi-Agent Research System - C4 System Context"
2 | !theme plain
3 |
4 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
5 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
6 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
7 |
8 | LAYOUT_WITH_LEGEND()
9 |
10 | title "Multi-Agent Research System"
11 | Person(user, "User", "Submits research queries")
12 |
13 | System_Boundary(research_system, "Supporting System") {
14 | Container(main_module, "Main Module", "Python", "Entry point that configures and runs the agent system")
15 | Container(utils, "Utilities", "Python", "Helper functions and data models")
16 | Container(config, "Configuration", "JSON", "Provider and model settings")
17 | }
18 |
19 | System_Boundary(agent_system, "Agent System") {
20 | Container(manager_agent, "Manager Agent", "pydantic-ai", "Coordinates research and analysis tasks")
21 | Container(research_agent, "Research Agent", "pydantic-ai", "Gathers information on topics")
22 | Container(analysis_agent, "Analysis Agent", "pydantic-ai", "Analyzes research")
23 | Container(synthesiser_agent, "Synthesiser Agent", "pydantic-ai", "Produces scientific reports")
24 | }
25 |
26 | System_Ext(llm_provider, "LLM Provider", "External inference service for AI models")
27 | System_Ext(search_api, "DuckDuckGo Search", "External search API")
28 |
29 | Rel(user, main_module, "Submits query", "CLI Input or GUI")
30 | Rel(main_module, config, "Loads", "Reads JSON config")
31 | Rel(main_module, agent_system, "Initializes and runs")
32 |
33 | Rel(manager_agent, research_agent, "Delegates research tasks to", "Optional Tool call")
34 | Rel(manager_agent, analysis_agent, "Delegates analysis tasks to", "Optional Tool call")
35 | Rel(manager_agent, synthesiser_agent, "Delegates synthesis tasks to", "Optional Tool call")
36 |
37 | Rel(research_agent, search_api, "Searches for information", "API call")
38 |
39 | Rel(manager_agent, llm_provider, "Generates responses", "API call")
40 | Rel(research_agent, llm_provider, "Generates responses", "API call")
41 | Rel(analysis_agent, llm_provider, "Generates responses", "API call")
42 | Rel(synthesiser_agent, llm_provider, "Generates responses", "API call")
43 |
44 | Rel(agent_system, utils, "Uses", "Import")
45 | Rel(main_module, utils, "Uses", "Import")
46 |
47 | @enduml
48 |
--------------------------------------------------------------------------------
/docs/architecture/customer-journey-activity-dark:
--------------------------------------------------------------------------------
1 | @startuml
2 | !theme amiga
3 | skinparam monochrome true
4 |
5 | title Customer Journey Activity Diagram for CLI and Streamlit
6 |
7 | start
8 | :Discover Agents-eval;
9 | if (Choose CLI?) then (yes)
10 | :Run CLI with `make run_cli`;
11 | :Interact with CLI for agent setup and execution;
12 | :View results and metrics in CLI output;
13 | else (no)
14 | :Run Streamlit GUI with `make run_gui`;
15 | :Interact with Streamlit for agent setup and execution;
16 | :View results and metrics in Streamlit dashboard;
17 | endif
18 | :Continue using and provide feedback;
19 | :Improve based on feedback;
20 |
21 | stop
22 | @enduml
--------------------------------------------------------------------------------
/docs/architecture/customer-journey-activity-light.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | !theme plain
3 |
4 | title Customer Journey Activity Diagram for CLI and Streamlit
5 |
6 | start
7 | :Discover Agents-eval;
8 | if (Choose CLI?) then (yes)
9 | :Run CLI with `make run_cli`;
10 | :Interact with CLI for agent setup and execution;
11 | :View results and metrics in CLI output;
12 | else (no)
13 | :Run Streamlit GUI with `make run_gui`;
14 | :Interact with Streamlit for agent setup and execution;
15 | :View results and metrics in Streamlit dashboard;
16 | endif
17 | :Continue using and provide feedback;
18 | :Improve based on feedback;
19 |
20 | stop
21 | @enduml
--------------------------------------------------------------------------------
/docs/architecture/metrics-eval-sweep.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | !theme plain
3 | skinparam ConditionEndStyle diamond
4 | skinparam ParticipantPadding 20
5 | skinparam BoxPadding 20
6 |
7 | participant "Sweep Engine" as SE
8 | participant "Agentic System" as AS
9 | participant "Evaluation Engine" as EE
10 |
11 | SE -> EE: Set baseline parameters
12 |
13 | group Sweep over parameter variations [Independent runs]
14 |
15 | group Vary number of runs [ numbers of runs ]
16 | loop for each run_number
17 | SE -> AS: Start runs
18 | AS -> EE: Execute runs
19 | EE--> SE: Send results
20 | end
21 | end
22 |
23 | group Sweep metrics weights [ metrics weights ]
24 | loop for each weight_config
25 | SE -> AS: Set weights and start runs
26 | AS -> EE: Execute runs
27 | EE--> SE: Send results
28 | end
29 | end
30 |
31 | end
32 | @enduml
33 |
--------------------------------------------------------------------------------
/mkdocs.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://github.com/james-willett/mkdocs-material-youtube-tutorial
3 | # https://mkdocstrings.github.io/recipes/
4 | # site info set in workflow
5 | site_name: ''
6 | site_description: ''
7 | repo_url: ''
8 | edit_uri: edit/main
9 | theme:
10 | name: material
11 | language: en
12 | features:
13 | - content.code.annotation
14 | - content.code.copy
15 | - content.tabs.link
16 | - navigation.footer
17 | - navigation.sections
18 | - navigation.tabs
19 | - navigation.top
20 | - toc.integrate
21 | - search.suggest
22 | - search.highlight
23 | palette:
24 | - media: "(prefers-color-scheme: light)"
25 | scheme: default
26 | toggle:
27 | # icon: material/brightness-7
28 | icon: material/toggle-switch-off-outline
29 | name: "Toggle Dark Mode"
30 | - media: "(prefers-color-scheme: dark)"
31 | scheme: slate
32 | toggle:
33 | # icon: material/brightness-4
34 | icon: material/toggle-switch
35 | name: "Toggle Light Mode"
36 | nav:
37 | - Home: index.md
38 | - PRD: PRD.md
39 | - User Story: UserStory.md
40 | - Sprint Plan: SprintPlan.md
41 | - Code: docstrings.md
42 | - Change Log: CHANGELOG.md
43 | - License: LICENSE.md
44 | - llms.txt: llms.txt
45 | plugins:
46 | - search:
47 | lang: en
48 | - autorefs
49 | - mkdocstrings:
50 | handlers:
51 | python:
52 | paths: [src]
53 | options:
54 | show_root_heading: true
55 | show_root_full_path: true
56 | show_object_full_path: false
57 | show_root_members_full_path: false
58 | show_category_heading: true
59 | show_submodules: true
60 | markdown_extensions:
61 | - attr_list
62 | - pymdownx.magiclink
63 | - pymdownx.tabbed
64 | - pymdownx.highlight:
65 | anchor_linenums: true
66 | - pymdownx.superfences
67 | - pymdownx.snippets:
68 | check_paths: true
69 | - pymdownx.tasklist:
70 | custom_checkbox: true
71 | - sane_lists
72 | - smarty
73 | - toc:
74 | permalink: true
75 | validation:
76 | links:
77 | not_found: warn
78 | anchors: warn
79 | # builds only if validation succeeds while
80 | # threating warnings as errors
81 | # also checks for broken links
82 | # strict: true
83 | ...
84 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | version = "1.1.0"
7 | name = "Agents-eval"
8 | description = "Assess the effectiveness of agentic AI systems across various use cases focusing on agnostic metrics that measure core agentic capabilities."
9 | authors = [
10 | {name = "qte77", email = "qte@77.gh"}
11 | ]
12 | readme = "README.md"
13 | requires-python = "==3.13.*"
14 | license = "bsd-3-clause"
15 | dependencies = [
16 | "agentops>=0.4.14",
17 | "logfire>=3.16.1",
18 | "loguru>=0.7.3",
19 | "pydantic>=2.10.6",
20 | # "pydantic-ai>=0.0.36",
21 | "pydantic-ai-slim[duckduckgo,openai,tavily]>=0.2.12",
22 | "pydantic-settings>=2.9.1",
23 | "scalene>=1.5.51",
24 | "weave>=0.51.49",
25 | ]
26 |
27 | # [project.urls]
28 | # Documentation = ""
29 |
30 | [dependency-groups]
31 | dev = [
32 | # "commitizen>=4.4.1",
33 | "mypy>=1.16.0",
34 | "ruff>=0.11.12",
35 | ]
36 | gui = [
37 | "streamlit>=1.43.1",
38 | ]
39 | test = [
40 | "pytest>=8.3.4",
41 | "pytest-cov>=6.0.0",
42 | "pytest-asyncio>=0.25.3",
43 | "pytest-bdd>=8.1.0",
44 | "requests>=2.32.3",
45 | "ruff>=0.9.2",
46 | ]
47 | docs = [
48 | "griffe>=1.5.1",
49 | "mkdocs>=1.6.1",
50 | "mkdocs-awesome-pages-plugin>=2.9.3",
51 | "mkdocs-gen-files>=0.5.0",
52 | "mkdocs-literate-nav>=0.6.1",
53 | "mkdocs-material>=9.5.44",
54 | "mkdocs-section-index>=0.3.8",
55 | "mkdocstrings[python]>=0.27.0",
56 | ]
57 |
58 | [tool.uv]
59 | package = true
60 | exclude-newer = "2025-05-31T00:00:00Z"
61 |
62 | [tool.hatch.build.targets.wheel]
63 | only-include = ["/README.md"]
64 |
65 | [tool.hatch.build.targets.sdist]
66 | include = ["/README.md", "/Makefile", "/tests"]
67 |
68 | [tool.logfire]
69 | ignore_no_config=true
70 | send_to_logfire="if-token-present"
71 |
72 | [[tool.mypy.overrides]]
73 | module = "agentops"
74 | ignore_missing_imports = true
75 |
76 | [tool.ruff]
77 | target-version = "py313"
78 | src = ["src", "tests"]
79 |
80 | [tool.ruff.format]
81 | docstring-code-format = true
82 |
83 | [tool.ruff.lint]
84 | # ignore = ["E203"] # Whitespace before ':'
85 | unfixable = ["B"]
86 | select = [
87 | # pycodestyle
88 | "E",
89 | # Pyflakes
90 | "F",
91 | # pyupgrade
92 | "UP",
93 | # isort
94 | "I",
95 | ]
96 |
97 | [tool.ruff.lint.isort]
98 | known-first-party = ["src", "tests"]
99 |
100 | [tool.ruff.lint.pydocstyle]
101 | convention = "google"
102 |
103 | [tool.pytest.ini_options]
104 | addopts = "--strict-markers"
105 | # "function", "class", "module", "package", "session"
106 | asyncio_default_fixture_loop_scope = "function"
107 | pythonpath = ["src"]
108 | testpaths = ["tests/"]
109 |
110 | [tool.coverage]
111 | [tool.coverage.run]
112 | include = [
113 | "tests/**/*.py",
114 | ]
115 | # omit = []
116 | # branch = true
117 |
118 | [tool.coverage.report]
119 | show_missing = true
120 | exclude_lines = [
121 | # 'pragma: no cover',
122 | 'raise AssertionError',
123 | 'raise NotImplementedError',
124 | ]
125 | omit = [
126 | 'env/*',
127 | 'venv/*',
128 | '.venv/*',
129 | '*/virtualenv/*',
130 | '*/virtualenvs/*',
131 | '*/tests/*',
132 | ]
133 |
134 | [tool.bumpversion]
135 | current_version = "1.1.0"
136 | parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)"
137 | serialize = ["{major}.{minor}.{patch}"]
138 | commit = true
139 | tag = true
140 | allow_dirty = false
141 | ignore_missing_version = false
142 | sign_tags = false
143 | tag_name = "v{new_version}"
144 | tag_message = "Bump version: {current_version} → {new_version}"
145 | message = "Bump version: {current_version} → {new_version}"
146 | commit_args = ""
147 |
148 | [[tool.bumpversion.files]]
149 | filename = "pyproject.toml"
150 | search = 'version = "{current_version}"'
151 | replace = 'version = "{new_version}"'
152 |
153 | [[tool.bumpversion.files]]
154 | filename = "src/app/__init__.py"
155 | search = '__version__ = "{current_version}"'
156 | replace = '__version__ = "{new_version}"'
157 |
158 | [[tool.bumpversion.files]]
159 | filename = "README.md"
160 | search = "version-{current_version}-58f4c2"
161 | replace = "version-{new_version}-58f4c2"
162 |
163 | [[tool.bumpversion.files]]
164 | filename = "CHANGELOG.md"
165 | search = """
166 | ## [Unreleased]
167 | """
168 | replace = """
169 | ## [Unreleased]
170 |
171 | ## [{new_version}] - {now:%Y-%m-%d}
172 | """
173 |
--------------------------------------------------------------------------------
/src/app/__init__.py:
--------------------------------------------------------------------------------
1 | """Defines the application version."""
2 |
3 | __version__ = "1.1.0"
4 |
--------------------------------------------------------------------------------
/src/app/agents/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qte77/Agents-eval/7401b21b53bd8307e7fe8465b466595a7687f8c8/src/app/agents/__init__.py
--------------------------------------------------------------------------------
/src/app/agents/agent_system.py:
--------------------------------------------------------------------------------
1 | """
2 | Agent system utilities for orchestrating multi-agent workflows.
3 |
4 | This module provides functions and helpers to create, configure, and run agent
5 | systems using Pydantic AI. It supports delegation of tasks to research, analysis, and
6 | synthesis agents, and manages agent configuration, environment setup, and execution.
7 | Args:
8 | provider (str): The name of the provider. provider_config (ProviderConfig):
9 | Configuration settings for the provider.
10 | api_key (str): API key for authentication with the provider.
11 | prompts (dict[str, str]): Configuration for prompts.
12 | include_researcher (bool): Flag to include the researcher agent.
13 | include_analyst (bool): Flag to include the analyst agent.
14 | include_synthesiser (bool): Flag to include the synthesiser agent.
15 | query (str | list[dict[str, str]]): The query or messages for the agent.
16 | chat_config (ChatConfig): The configuration object for agents and providers.
17 | usage_limits (UsageLimits): Usage limits for agent execution.
18 | pydantic_ai_stream (bool): Whether to use Pydantic AI streaming.
19 |
20 | Functions:
21 | get_manager: Initializes and returns a manager agent with the specified
22 | configuration.
23 | run_manager: Asynchronously runs the manager agent with the given query and
24 | provider.
25 | setup_agent_env: Sets up the environment for an agent by configuring provider
26 | settings, prompts, API key, and usage limits.
27 | """
28 |
29 | from pydantic import BaseModel, ValidationError
30 | from pydantic_ai import Agent, RunContext
31 | from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool
32 | from pydantic_ai.messages import ModelRequest
33 | from pydantic_ai.usage import UsageLimits
34 |
35 | from app.agents.llm_model_funs import get_api_key, get_models, get_provider_config
36 | from app.config.data_models import (
37 | AgentConfig,
38 | AnalysisResult,
39 | AppEnv,
40 | ChatConfig,
41 | EndpointConfig,
42 | ModelDict,
43 | ProviderConfig,
44 | ResearchResult,
45 | ResearchSummary,
46 | ResultBaseType,
47 | UserPromptType,
48 | )
49 | from app.utils.error_messages import generic_exception, invalid_data_model_format
50 | from app.utils.log import logger
51 |
52 |
53 | def _add_tools_to_manager_agent(
54 | manager_agent: Agent[None, BaseModel],
55 | research_agent: Agent[None, BaseModel] | None = None,
56 | analysis_agent: Agent[None, BaseModel] | None = None,
57 | synthesis_agent: Agent[None, BaseModel] | None = None,
58 | ):
59 | """
60 | Adds tools to the manager agent for delegating tasks to research, analysis, and
61 | synthesis agents.
62 | Args:
63 | manager_agent (Agent): The manager agent to which tools will be added.
64 | research_agent (Agent): The agent responsible for handling research tasks.
65 | analysis_agent (Agent, optional): The agent responsible for handling
66 | analysis tasks. Defaults to None.
67 | synthesis_agent (Agent, optional): The agent responsible for handling
68 | synthesis tasks. Defaults to None.
69 | Returns:
70 | None
71 | """
72 |
73 | def _validate_model_return(
74 | result_output: str,
75 | result_model: type[ResultBaseType],
76 | ) -> ResultBaseType:
77 | """Validates the output against the expected model."""
78 | try:
79 | return result_model.model_validate(result_output)
80 | except ValidationError as e:
81 | msg = invalid_data_model_format(str(e))
82 | logger.error(msg)
83 | raise ValidationError(msg)
84 | except Exception as e:
85 | msg = generic_exception(str(e))
86 | logger.exception(msg)
87 | raise Exception(msg)
88 |
89 | if research_agent is not None:
90 |
91 | @manager_agent.tool
92 | # TODO remove redundant tool creation
93 | # ignore "delegate_research" is not accessed because of decorator
94 | async def delegate_research( # type: ignore[reportUnusedFunction]
95 | ctx: RunContext[None], query: str
96 | ) -> ResearchResult:
97 | """Delegate research task to ResearchAgent."""
98 | result = await research_agent.run(query, usage=ctx.usage)
99 | return _validate_model_return(str(result.output), ResearchResult)
100 |
101 | if analysis_agent is not None:
102 |
103 | @manager_agent.tool
104 | # ignore "delegate_research" is not accessed because of decorator
105 | async def delegate_analysis( # type: ignore[reportUnusedFunction]
106 | ctx: RunContext[None], query: str
107 | ) -> AnalysisResult:
108 | """Delegate analysis task to AnalysisAgent."""
109 | result = await analysis_agent.run(query, usage=ctx.usage)
110 | return _validate_model_return(str(result.output), AnalysisResult)
111 |
112 | if synthesis_agent is not None:
113 |
114 | @manager_agent.tool
115 | # ignore "delegate_research" is not accessed because of decorator
116 | async def delegate_synthesis( # type: ignore[reportUnusedFunction]
117 | ctx: RunContext[None], query: str
118 | ) -> ResearchSummary:
119 | """Delegate synthesis task to AnalysisAgent."""
120 | result = await synthesis_agent.run(query, usage=ctx.usage)
121 | return _validate_model_return(str(result.output), ResearchSummary)
122 |
123 |
124 | def _create_agent(agent_config: AgentConfig) -> Agent[None, BaseModel]:
125 | """Factory for creating configured agents"""
126 |
127 | return Agent(
128 | model=agent_config.model,
129 | output_type=agent_config.output_type,
130 | system_prompt=agent_config.system_prompt,
131 | tools=agent_config.tools,
132 | retries=agent_config.retries,
133 | )
134 |
135 |
136 | def _create_manager(
137 | prompts: dict[str, str],
138 | models: ModelDict,
139 | ) -> Agent[None, BaseModel]:
140 | """
141 | Creates and configures a manager Agent with associated researcher, analyst,
142 | and optionally synthesiser agents.
143 | Args:
144 | prompts (Dict[str, str]): Dictionary containing system prompts for each agent.
145 | model_manager (GeminiModel | OpenAIModel): Model to be used by the manager
146 | agent.
147 | model_researcher (GeminiModel | OpenAIModel | None, optional): Model to be used
148 | by the researcher agent.
149 | model_analyst (GeminiModel | OpenAIModel | None, optional): Model to be used by
150 | the analyst agent. Defaults to None.
151 | model_synthesiser (GeminiModel | OpenAIModel | None, optional): Model to be used
152 | by the synthesiser agent. Defaults to None.
153 | Returns:
154 | Agent: Configured manager agent with associated tools and agents.
155 | """
156 |
157 | status = f"Creating manager({models.model_manager.model_name})"
158 | active_agents = [
159 | agent
160 | for agent in [
161 | f"researcher({models.model_researcher.model_name})"
162 | if models.model_researcher
163 | else None,
164 | f"analyst({models.model_analyst.model_name})"
165 | if models.model_analyst
166 | else None,
167 | f"synthesiser({models.model_synthesiser.model_name})"
168 | if models.model_synthesiser
169 | else None,
170 | ]
171 | if agent
172 | ]
173 | status += f" with agents: {', '.join(active_agents)}" if active_agents else ""
174 | logger.info(status)
175 |
176 | manager = _create_agent(
177 | AgentConfig.model_validate(
178 | {
179 | "model": models.model_manager,
180 | "output_type": ResearchResult,
181 | "system_prompt": prompts["system_prompt_manager"],
182 | }
183 | )
184 | )
185 |
186 | if models.model_researcher is None:
187 | researcher = None
188 | else:
189 | researcher = _create_agent(
190 | AgentConfig.model_validate(
191 | {
192 | "model": models.model_researcher,
193 | "output_type": ResearchResult,
194 | "system_prompt": prompts["system_prompt_researcher"],
195 | "tools": [duckduckgo_search_tool()],
196 | }
197 | )
198 | )
199 |
200 | if models.model_analyst is None:
201 | analyst = None
202 | else:
203 | analyst = _create_agent(
204 | AgentConfig.model_validate(
205 | {
206 | "model": models.model_analyst,
207 | "output_type": AnalysisResult,
208 | "system_prompt": prompts["system_prompt_analyst"],
209 | }
210 | )
211 | )
212 |
213 | if models.model_synthesiser is None:
214 | synthesiser = None
215 | else:
216 | synthesiser = _create_agent(
217 | AgentConfig.model_validate(
218 | {
219 | "model": models.model_synthesiser,
220 | "output_type": AnalysisResult,
221 | "system_prompt": prompts["system_prompt_synthesiser"],
222 | }
223 | )
224 | )
225 |
226 | _add_tools_to_manager_agent(manager, researcher, analyst, synthesiser)
227 | return manager
228 |
229 |
230 | def get_manager(
231 | provider: str,
232 | provider_config: ProviderConfig,
233 | api_key: str | None,
234 | prompts: dict[str, str],
235 | include_researcher: bool = False,
236 | include_analyst: bool = False,
237 | include_synthesiser: bool = False,
238 | ) -> Agent[None, BaseModel]:
239 | """
240 | Initializes and returns a Agent manager with the specified configuration.
241 | Args:
242 | provider (str): The name of the provider.
243 | provider_config (ProviderConfig): Configuration settings for the provider.
244 | api_key (str): API key for authentication with the provider.
245 | prompts (PromptsConfig): Configuration for prompts.
246 | include_researcher (bool, optional): Flag to include analyst model.
247 | Defaults to False.
248 | include_analyst (bool, optional): Flag to include analyst model.
249 | Defaults to False.
250 | include_synthesiser (bool, optional): Flag to include synthesiser model.
251 | Defaults to False.
252 | Returns:
253 | Agent: The initialized Agent manager.
254 | """
255 |
256 | # FIXME context manager try-catch
257 | # with error_handling_context("get_manager()"):
258 | model_config = EndpointConfig.model_validate(
259 | {
260 | "provider": provider,
261 | "prompts": prompts,
262 | "api_key": api_key,
263 | "provider_config": provider_config,
264 | }
265 | )
266 | models = get_models(
267 | model_config, include_researcher, include_analyst, include_synthesiser
268 | )
269 | return _create_manager(prompts, models)
270 |
271 |
272 | async def run_manager(
273 | manager: Agent[None, BaseModel],
274 | query: UserPromptType,
275 | provider: str,
276 | usage_limits: UsageLimits | None,
277 | pydantic_ai_stream: bool = False,
278 | ) -> None:
279 | """
280 | Asynchronously runs the manager with the given query and provider, handling errors
281 | and printing results.
282 | Args:
283 | manager (Agent): The system agent responsible for running the query.
284 | query (str): The query to be processed by the manager.
285 | provider (str): The provider to be used for the query.
286 | usage_limits (UsageLimits): The usage limits to be applied during the query
287 | execution.
288 | pydantic_ai_stream (bool, optional): Flag to enable or disable Pydantic AI
289 | stream. Defaults to False.
290 | Returns:
291 | None
292 | """
293 |
294 | # FIXME context manager try-catch
295 | # with out ? error_handling_context("run_manager()"):
296 | model_name = getattr(manager, "model")._model_name
297 | mgr_cfg = {"user_prompt": query, "usage_limits": usage_limits}
298 | logger.info(f"Researching with {provider}({model_name}) and Topic: {query} ...")
299 |
300 | if pydantic_ai_stream:
301 | raise NotImplementedError(
302 | "Streaming currently only possible for Agents with "
303 | "output_type str not pydantic model"
304 | )
305 | # logger.info("Streaming model response ...")
306 | # result = await manager.run(**mgr_cfg)
307 | # aync for chunk in result.stream_text(): # .run(**mgr_cfg) as result:
308 | # async with manager.run_stream(user_prompt=query) as stream:
309 | # async for chunk in stream.stream_text():
310 | # logger.info(str(chunk))
311 | # result = await stream.get_result()
312 | else:
313 | logger.info("Waiting for model response ...")
314 | # FIXME deprecated warning manager.run(), query unknown type
315 | # FIXME [call-overload] error: No overload variant of "run" of "Agent"
316 | # matches argument type "dict[str, list[dict[str, str]] |
317 | # Sequence[str | ImageUrl | AudioUrl | DocumentUrl | VideoUrl |
318 | # BinaryContent] | UsageLimits | None]"
319 | result = await manager.run(**mgr_cfg) # type: ignore[reportDeprecated,reportUnknownArgumentType,reportCallOverload,call-overload]
320 |
321 | logger.info(f"Result: {result}")
322 | logger.info(f"Usage statistics: {result.usage()}")
323 |
324 |
325 | def setup_agent_env(
326 | provider: str,
327 | query: UserPromptType,
328 | chat_config: ChatConfig | BaseModel,
329 | chat_env_config: AppEnv,
330 | ) -> EndpointConfig:
331 | """
332 | Sets up the environment for an agent by configuring provider settings, prompts,
333 | API key, and usage limits.
334 |
335 | Args:
336 | provider (str): The name of the provider.
337 | query (UserPromptType): The messages or queries to be sent to the agent.
338 | chat_config (ChatConfig | BaseModel): The configuration object containing
339 | provider and prompt settings.
340 | chat_env_config (AppEnv): The application environment configuration
341 | containing API keys.
342 |
343 | Returns:
344 | EndpointConfig: The configuration object for the agent.
345 | """
346 |
347 | if not isinstance(chat_config, ChatConfig):
348 | raise TypeError("'chat_config' of invalid type: ChatConfig expected")
349 | msg: str | None
350 | # FIXME context manager try-catch
351 | # with error_handling_context("setup_agent_env()"):
352 | provider_config = get_provider_config(provider, chat_config.providers)
353 |
354 | prompts = chat_config.prompts
355 | api_key = get_api_key(provider, chat_env_config)
356 |
357 | if provider.lower() == "ollama":
358 | # TODO move usage limits to config
359 | usage_limits = UsageLimits(request_limit=10, total_tokens_limit=100000)
360 | else:
361 | if api_key is None:
362 | msg = f"API key for provider '{provider}' is not set."
363 | logger.error(msg)
364 | raise ValueError(msg)
365 | # TODO Separate Gemini request into function
366 | if provider.lower() == "gemini":
367 | if isinstance(query, str):
368 | query = ModelRequest.user_text_prompt(query)
369 | elif isinstance(query, list): # type: ignore[reportUnnecessaryIsInstance]
370 | # query = [
371 | # ModelRequest.user_text_prompt(
372 | # str(msg.get("content", ""))
373 | # ) # type: ignore[reportUnknownArgumentType]
374 | # if isinstance(msg, dict)
375 | # else msg
376 | # for msg in query
377 | # ]
378 | raise NotImplementedError("Currently conflicting with UserPromptType")
379 | else:
380 | msg = f"Unsupported query type for Gemini: {type(query)}"
381 | logger.error(msg)
382 | raise TypeError(msg)
383 | # TODO move usage limits to config
384 | usage_limits = UsageLimits(request_limit=10, total_tokens_limit=10000)
385 |
386 | return EndpointConfig.model_validate(
387 | {
388 | "provider": provider,
389 | "query": query,
390 | "api_key": api_key,
391 | "prompts": prompts,
392 | "provider_config": provider_config,
393 | "usage_limits": usage_limits,
394 | }
395 | )
396 |
--------------------------------------------------------------------------------
/src/app/agents/llm_model_funs.py:
--------------------------------------------------------------------------------
1 | """
2 | Utility functions and classes for managing and instantiating LLM models and providers.
3 |
4 | This module provides functions to retrieve API keys, provider configurations, and
5 | to create model instances for supported LLM providers such as Gemini and OpenAI.
6 | It also includes logic for assembling model dictionaries for system agents.
7 | """
8 |
9 | from pydantic import HttpUrl
10 | from pydantic_ai.models.gemini import GeminiModel
11 | from pydantic_ai.models.openai import OpenAIModel
12 | from pydantic_ai.providers.openai import OpenAIProvider
13 |
14 | from app.config.config_app import API_SUFFIX
15 | from app.config.data_models import AppEnv, EndpointConfig, ModelDict, ProviderConfig
16 | from app.utils.error_messages import generic_exception, get_key_error
17 | from app.utils.log import logger
18 |
19 |
20 | def get_api_key(
21 | provider: str,
22 | chat_env_config: AppEnv,
23 | ) -> str | None:
24 | """Retrieve API key from chat env config variable."""
25 |
26 | provider = provider.upper()
27 | if provider == "OLLAMA":
28 | return None
29 | else:
30 | key_name = f"{provider}{API_SUFFIX}"
31 | if hasattr(chat_env_config, key_name):
32 | logger.info(f"Found API key for provider '{provider}'")
33 | return getattr(chat_env_config, key_name)
34 | else:
35 | raise KeyError(
36 | f"API key for provider '{provider}' not found in configuration."
37 | )
38 |
39 |
40 | def get_provider_config(
41 | provider: str, providers: dict[str, ProviderConfig]
42 | ) -> dict[str, str | HttpUrl]:
43 | """Retrieve configuration settings for the specified provider."""
44 |
45 | try:
46 | model_name = providers[provider].model_name
47 | base_url = providers[provider].base_url
48 | except KeyError as e:
49 | msg = get_key_error(str(e))
50 | logger.error(msg)
51 | raise KeyError(msg)
52 | except Exception as e:
53 | msg = generic_exception(str(e))
54 | logger.exception(msg)
55 | raise Exception(msg)
56 | else:
57 | return {
58 | "model_name": model_name,
59 | "base_url": base_url,
60 | }
61 |
62 |
63 | def _create_model(endpoint_config: EndpointConfig) -> GeminiModel | OpenAIModel:
64 | """Create a model that uses model_name and base_url for inference API"""
65 |
66 | if endpoint_config.provider.lower() == "gemini":
67 | # FIXME EndpointConfig: TypeError: 'ModelRequest' object is not iterable.
68 | raise NotImplementedError(
69 | "Current typing raises TypeError: 'ModelRequest' object is not iterable."
70 | )
71 | elif endpoint_config.provider.lower() == "huggingface":
72 | # FIXME HF not working with pydantic-ai OpenAI model
73 | raise NotImplementedError(
74 | "Hugging Face provider is not implemented yet. Please use Gemini or OpenAI."
75 | " https://huggingface.co/docs/inference-providers/providers/hf-inference"
76 | )
77 | # headers = {
78 | # "Authorization": f"Bearer {endpoint_config.api_key}",
79 | # }
80 | # def query(payload):
81 | # response = requests.post(API_URL, headers=headers, json=payload)
82 | # return response.json()
83 | # query({"inputs": "", "parameters": {},})
84 | else:
85 | base_url_str = str(endpoint_config.provider_config.base_url)
86 | return OpenAIModel(
87 | model_name=endpoint_config.provider_config.model_name,
88 | provider=OpenAIProvider(
89 | base_url=base_url_str,
90 | api_key=endpoint_config.api_key,
91 | ),
92 | )
93 |
94 |
95 | def get_models(
96 | endpoint_config: EndpointConfig,
97 | include_researcher: bool = False,
98 | include_analyst: bool = False,
99 | include_synthesiser: bool = False,
100 | ) -> ModelDict:
101 | """
102 | Get the models for the system agents.
103 | Args:
104 | endpoint_config (EndpointConfig): Configuration for the model.
105 | include_analyist (Optional[bool]): Whether to include the analyst model.
106 | Defaults to False.
107 | include_synthesiser (Optional[bool]): Whether to include the synthesiser model.
108 | Defaults to False.
109 | Returns:
110 | Dict[str, GeminiModel | OpenAIModel]: A dictionary containing the models for the
111 | system agents.
112 | """
113 |
114 | model = _create_model(endpoint_config)
115 | return ModelDict.model_validate(
116 | {
117 | "model_manager": model,
118 | "model_researcher": model if include_researcher else None,
119 | "model_analyst": model if include_analyst else None,
120 | "model_synthesiser": model if include_synthesiser else None,
121 | }
122 | )
123 |
--------------------------------------------------------------------------------
/src/app/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qte77/Agents-eval/7401b21b53bd8307e7fe8465b466595a7687f8c8/src/app/config/__init__.py
--------------------------------------------------------------------------------
/src/app/config/config_app.py:
--------------------------------------------------------------------------------
1 | """Configuration constants for the application."""
2 |
3 | # MARK: chat env
4 | API_SUFFIX = "_API_KEY"
5 | CHAT_DEFAULT_PROVIDER = "github"
6 |
7 |
8 | # MARK: project
9 | PROJECT_NAME = "rd-mas-example"
10 |
11 |
12 | # MARK: paths
13 | CHAT_CONFIG_FILE = "config/config_chat.json"
14 | LOGS_PATH = "logs"
15 | EVAL_CONFIG_FILE = "config/config_eval.json"
16 |
--------------------------------------------------------------------------------
/src/app/config/config_chat.json:
--------------------------------------------------------------------------------
1 | {
2 | "providers": {
3 | "huggingface": {
4 | "model_name": "facebook/bart-large-mnli",
5 | "base_url": "https://router.huggingface.co/hf-inference/models"
6 | },
7 | "gemini": {
8 | "model_name": "gemini-1.5-flash-8b",
9 | "base_url": "https://generativelanguage.googleapis.com/v1beta"
10 | },
11 | "github": {
12 | "model_name": "GPT-4o",
13 | "base_url": "https://models.inference.ai.azure.com"
14 | },
15 | "grok": {
16 | "model_name": "grok-2-1212",
17 | "base_url": "https://api.x.ai/v1"
18 | },
19 | "ollama": {
20 | "model_name": "granite3-dense",
21 | "base_url": "http://localhost:11434/v1"
22 | },
23 | "openrouter": {
24 | "model_name": "google/gemini-2.0-flash-exp:free",
25 | "base_url": "https://openrouter.ai/api/v1"
26 | },
27 | "perplexity": {
28 | "model_name": "sonar",
29 | "base_url": "https://api.perplexity.ai"
30 | },
31 | "restack": {
32 | "model_name": "deepseek-chat",
33 | "base_url": "https://ai.restack.io"
34 | },
35 | "together": {
36 | "model_name": "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free",
37 | "base_url": "https://api.together.xyz/v1"
38 | }
39 | },
40 | "inference": {
41 | "usage_limits": 10000,
42 | "usage_limits_ollama": 10000,
43 | "result_retries": 3,
44 | "result_retries_ollama": 3
45 | },
46 | "prompts": {
47 | "system_prompt_manager": "You are a manager overseeing research and analysis tasks. Your role is to coordinate the efforts of the research, analysis and synthesiser agents to provide comprehensive answers to user queries. The researcher should gather and analyze data relevant to the topic. The whole result must be handed to the analyst, who will check it for accuracy of the assumptions, facts, and conclusions. If an analyst is present the researchers output has to be approved by the analyst. If the analyst does not approve of the researcher's result, all of the analyst's response and the topic must be handed back to the researcher to be refined. Repeat this loop until the analyst approves. If a sysnthesiser is present and once the analyst approves, the synthesiser should output a well formatted scientific report using the data given.",
48 | "system_prompt_researcher": "You are a researcher. Gather and analyze data relevant to the topic. Use the search tool to gather data. Always check accuracy of assumptions, facts, and conclusions.",
49 | "system_prompt_analyst": "You are a research analyst. Use your analytical skills to check the accuracy of assumptions, facts, and conclusions in the data provided. Provide relevant feedback if you do not approve. Only approve if you do not have any feedback to give.",
50 | "system_prompt_synthesiser": "You are a scientific writing assistant. Your task is to output a well formatted scientific report using the data given. Leave the privided facts, conclusions and sources unchanged."
51 | }
52 | }
--------------------------------------------------------------------------------
/src/app/config/config_eval.json:
--------------------------------------------------------------------------------
1 | {
2 | "metrics_and_weights": {
3 | "time_taken": 0.167,
4 | "task_success": 0.167,
5 | "coordination_quality": 0.167,
6 | "tool_efficiency": 0.167,
7 | "planning_rational": 0.167,
8 | "output_similarity": 0.167
9 | }
10 | }
--------------------------------------------------------------------------------
/src/app/config/data_models.py:
--------------------------------------------------------------------------------
1 | """
2 | Data models for agent system configuration and results.
3 |
4 | This module defines Pydantic models for representing research and analysis results,
5 | summaries, provider and agent configurations, and model dictionaries used throughout
6 | the application. These models ensure type safety and validation for data exchanged
7 | between agents and system components.
8 | """
9 |
10 | from typing import Any, TypeVar
11 |
12 | from pydantic import BaseModel, ConfigDict, HttpUrl, field_validator
13 | from pydantic_ai.messages import ModelRequest
14 | from pydantic_ai.models import Model
15 | from pydantic_ai.tools import Tool
16 | from pydantic_ai.usage import UsageLimits
17 | from pydantic_settings import BaseSettings, SettingsConfigDict
18 |
19 | type UserPromptType = (
20 | str | list[dict[str, str]] | ModelRequest | None
21 | ) # (1) Input validation
22 | ResultBaseType = TypeVar(
23 | "ResultBaseType", bound=BaseModel
24 | ) # (2) Generic type for model results
25 |
26 |
27 | class ResearchResult(BaseModel):
28 | """Research results from the research agent."""
29 |
30 | topic: str | dict[str, str]
31 | findings: list[str] | dict[str, str | list[str]]
32 | sources: list[str] | dict[str, str | list[str]]
33 |
34 |
35 | class AnalysisResult(BaseModel):
36 | """Analysis results from the analysis agent."""
37 |
38 | insights: list[str]
39 | recommendations: list[str]
40 | approval: bool
41 |
42 |
43 | class ResearchSummary(BaseModel):
44 | """Expected model response of research on a topic"""
45 |
46 | topic: str
47 | key_points: list[str]
48 | key_points_explanation: list[str]
49 | conclusion: str
50 | sources: list[str]
51 |
52 |
53 | class ProviderConfig(BaseModel):
54 | """Configuration for a model provider"""
55 |
56 | model_name: str
57 | base_url: HttpUrl
58 |
59 |
60 | class ChatConfig(BaseModel):
61 | """Configuration settings for agents and model providers"""
62 |
63 | providers: dict[str, ProviderConfig]
64 | inference: dict[str, str | int]
65 | prompts: dict[str, str]
66 |
67 |
68 | class EndpointConfig(BaseModel):
69 | """Configuration for an agent"""
70 |
71 | provider: str
72 | query: UserPromptType = None
73 | api_key: str | None
74 | prompts: dict[str, str]
75 | provider_config: ProviderConfig
76 | usage_limits: UsageLimits | None = None
77 |
78 |
79 | class AgentConfig(BaseModel):
80 | """Configuration for an agent"""
81 |
82 | model: Model # (1) Instance expected
83 | output_type: type[BaseModel] # (2) Class expected
84 | system_prompt: str
85 | # FIXME tools: list[Callable[..., Awaitable[Any]]]
86 | tools: list[Any] = [] # (3) List of tools will be validated at creation
87 | retries: int = 3
88 |
89 | # Avoid pydantic.errors.PydanticSchemaGenerationError:
90 | # Unable to generate pydantic-core schema for .
91 | # Avoid Pydantic errors related to non-Pydantic types
92 | model_config = ConfigDict(
93 | arbitrary_types_allowed=True
94 | ) # (4) Suppress Error non-Pydantic types caused by
95 |
96 | @field_validator("tools", mode="before")
97 | def validate_tools(cls, v: list[Any]) -> list[Tool | None]:
98 | """Validate that all tools are instances of Tool."""
99 | if not v:
100 | return []
101 | if not all(isinstance(t, Tool) for t in v):
102 | raise ValueError("All tools must be Tool instances")
103 | return v
104 |
105 |
106 | class ModelDict(BaseModel):
107 | """Dictionary of models used to create agent systems"""
108 |
109 | model_manager: Model
110 | model_researcher: Model | None
111 | model_analyst: Model | None
112 | model_synthesiser: Model | None
113 | model_config = ConfigDict(arbitrary_types_allowed=True)
114 |
115 |
116 | class EvalConfig(BaseModel):
117 | metrics_and_weights: dict[str, float]
118 |
119 |
120 | class AppEnv(BaseSettings):
121 | """
122 | Application environment settings loaded from environment variables or .env file.
123 |
124 | This class uses Pydantic's BaseSettings to manage API keys and configuration
125 | for various inference endpoints, tools, and logging/monitoring services.
126 | Environment variables are loaded from a .env file by default.
127 | """
128 |
129 | # Inference endpoints
130 | GEMINI_API_KEY: str = ""
131 | GITHUB_API_KEY: str = ""
132 | GROK_API_KEY: str = ""
133 | HUGGINGFACE_API_KEY: str = ""
134 | OPENROUTER_API_KEY: str = ""
135 | PERPLEXITY_API_KEY: str = ""
136 | RESTACK_API_KEY: str = ""
137 | TOGETHER_API_KEY: str = ""
138 |
139 | # Tools
140 | TAVILY_API_KEY: str = ""
141 |
142 | # Logging/Monitoring/Tracing
143 | AGENTOPS_API_KEY: str = ""
144 | LOGFIRE_API_KEY: str = ""
145 | WANDB_API_KEY: str = ""
146 |
147 | model_config = SettingsConfigDict(
148 | env_file=".env", env_file_encoding="utf-8", extra="ignore"
149 | )
150 |
--------------------------------------------------------------------------------
/src/app/evals/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qte77/Agents-eval/7401b21b53bd8307e7fe8465b466595a7687f8c8/src/app/evals/__init__.py
--------------------------------------------------------------------------------
/src/app/evals/metrics.py:
--------------------------------------------------------------------------------
1 | def time_taken(start_time: float, end_time: float) -> float:
2 | """Calculate duration between start and end timestamps
3 |
4 | Args:
5 | start_time: Timestamp when execution started
6 | end_time: Timestamp when execution completed
7 |
8 | Returns:
9 | Duration in seconds with microsecond precision
10 | """
11 |
12 | # TODO implement
13 | return end_time - start_time
14 |
15 |
16 | def output_similarity(agent_output: str, expected_answer: str) -> bool:
17 | """
18 | Determine to what degree the agent's output matches the expected answer.
19 |
20 | Args:
21 | agent_output (str): The output produced by the agent.
22 | expected_answer (str): The correct or expected answer.
23 |
24 | Returns:
25 | bool: True if the output matches the expected answer, False otherwise.
26 | """
27 |
28 | # TODO score instead of bool
29 | return agent_output.strip() == expected_answer.strip()
30 |
--------------------------------------------------------------------------------
/src/app/main.py:
--------------------------------------------------------------------------------
1 | """
2 | Main entry point for the Agents-eval application.
3 |
4 | This module initializes the agentic system, loads configuration files,
5 | handles user input, and orchestrates the multi-agent workflow using
6 | asynchronous execution. It integrates logging, tracing, and authentication,
7 | and supports both CLI and programmatic execution.
8 | """
9 |
10 | from asyncio import run
11 | from pathlib import Path
12 | from sys import argv
13 |
14 | from logfire import span
15 | from weave import op
16 |
17 | from app.__init__ import __version__
18 | from app.agents.agent_system import get_manager, run_manager, setup_agent_env
19 | from app.config.config_app import (
20 | CHAT_CONFIG_FILE,
21 | CHAT_DEFAULT_PROVIDER,
22 | EVAL_CONFIG_FILE,
23 | PROJECT_NAME,
24 | )
25 | from app.config.data_models import AppEnv, ChatConfig, EvalConfig
26 | from app.utils.error_messages import generic_exception
27 | from app.utils.load_configs import load_config
28 | from app.utils.log import logger
29 | from app.utils.login import login
30 | from app.utils.utils import parse_args
31 |
32 |
33 | @op()
34 | async def main(
35 | chat_provider: str = CHAT_DEFAULT_PROVIDER,
36 | query: str = "",
37 | include_researcher: bool = False,
38 | include_analyst: bool = False,
39 | include_synthesiser: bool = False,
40 | pydantic_ai_stream: bool = False,
41 | chat_config_file: str = CHAT_CONFIG_FILE,
42 | ) -> None:
43 | """
44 | Main entry point for the application.
45 |
46 | Args:
47 | chat_provider (str): The inference chat_provider to be used.
48 | query (str): The query to be processed by the agent.
49 | include_researcher (bool): Whether to include the researcher in the process.
50 | include_analyst (bool): Whether to include the analyst in the process.
51 | include_synthesiser (bool): Whether to include the synthesiser in the process.
52 | pydantic_ai_stream (bool): Whether to use Pydantic AI streaming.
53 | chat_config_file (str): Full path to the configuration file.
54 |
55 | Returns:
56 | None
57 | """
58 |
59 | logger.info(f"Starting app '{PROJECT_NAME}' v{__version__}")
60 | try:
61 | with span("main()"):
62 | if not chat_provider:
63 | chat_provider = input("Which inference chat_provider to use? ")
64 | if not query:
65 | query = input("What would you like to research? ")
66 |
67 | chat_config_path = Path(__file__).parent / CHAT_CONFIG_FILE
68 | eval_config_path = Path(__file__).parent / EVAL_CONFIG_FILE
69 | chat_config = load_config(chat_config_path, ChatConfig)
70 | eval_config = load_config(eval_config_path, EvalConfig)
71 | chat_env_config = AppEnv()
72 | agent_env = setup_agent_env(
73 | chat_provider, query, chat_config, chat_env_config
74 | )
75 | # TODO remove noqa and type ignore for unused variable
76 | metrics_and_weights = eval_config.metrics_and_weights # noqa: F841 # type: ignore[reportUnusedVariable]
77 |
78 | # FIXME enhance login, not every run?
79 | login(PROJECT_NAME, chat_env_config)
80 |
81 | manager = get_manager(
82 | agent_env.provider,
83 | agent_env.provider_config,
84 | agent_env.api_key,
85 | agent_env.prompts,
86 | include_researcher,
87 | include_analyst,
88 | include_synthesiser,
89 | )
90 | await run_manager(
91 | manager,
92 | agent_env.query,
93 | agent_env.provider,
94 | agent_env.usage_limits,
95 | pydantic_ai_stream,
96 | )
97 | logger.info(f"Exiting app '{PROJECT_NAME}'")
98 |
99 | except Exception as e:
100 | msg = generic_exception(f"Aborting app '{PROJECT_NAME}' with: {e}")
101 | logger.exception(msg)
102 | raise Exception(msg) from e
103 |
104 |
105 | if __name__ == "__main__":
106 | args = parse_args(argv[1:])
107 | run(main(**args))
108 |
--------------------------------------------------------------------------------
/src/app/py.typed:
--------------------------------------------------------------------------------
1 | # PEP 561 – Distributing and Packaging Type Information
2 | # https://peps.python.org/pep-0561/
--------------------------------------------------------------------------------
/src/app/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qte77/Agents-eval/7401b21b53bd8307e7fe8465b466595a7687f8c8/src/app/utils/__init__.py
--------------------------------------------------------------------------------
/src/app/utils/error_messages.py:
--------------------------------------------------------------------------------
1 | """
2 | Error message utilities for the Agents-eval application.
3 |
4 | This module provides concise helper functions for generating standardized
5 | error messages related to configuration loading and validation.
6 | """
7 |
8 | from pathlib import Path
9 |
10 |
11 | def api_connection_error(error: str) -> str:
12 | """
13 | Generate a error message for API connection error.
14 | """
15 | return f"API connection error: {error}"
16 |
17 |
18 | def failed_to_load_config(error: str) -> str:
19 | """
20 | Generate a error message for configuration loading failure.
21 | """
22 | return f"Failed to load config: {error}"
23 |
24 |
25 | def file_not_found(file_path: str | Path) -> str:
26 | """
27 | Generate an error message for a missing configuration file.
28 | """
29 | return f"File not found: {file_path}"
30 |
31 |
32 | def generic_exception(error: str) -> str:
33 | """
34 | Generate a generic error message.
35 | """
36 | return f"Exception: {error}"
37 |
38 |
39 | def invalid_data_model_format(error: str) -> str:
40 | """
41 | Generate an error message for invalid pydantic data model format.
42 | """
43 | return f"Invalid pydantic data model format: {error}"
44 |
45 |
46 | def invalid_json(error: str) -> str:
47 | """
48 | Generate an error message for invalid JSON in a configuration file.
49 | """
50 | return f"Invalid JSON: {error}"
51 |
52 |
53 | def invalid_type(expected_type: str, actual_type: str) -> str:
54 | """
55 | Generate an error message for invalid Type.
56 | """
57 | return f"Type Error: Expected {expected_type}, got {actual_type} instead."
58 |
59 |
60 | def get_key_error(error: str) -> str:
61 | """
62 | Generate a generic error message.
63 | """
64 | return f"Key Error: {error}"
65 |
--------------------------------------------------------------------------------
/src/app/utils/load_configs.py:
--------------------------------------------------------------------------------
1 | """
2 | Configuration loading utilities.
3 |
4 | Provides a generic function for loading and validating JSON configuration
5 | files against Pydantic models, with error handling and logging support.
6 | """
7 |
8 | import json
9 | from pathlib import Path
10 |
11 | from pydantic import BaseModel, ValidationError
12 |
13 | from app.utils.error_messages import (
14 | failed_to_load_config,
15 | file_not_found,
16 | invalid_data_model_format,
17 | invalid_json,
18 | )
19 | from app.utils.log import logger
20 |
21 |
22 | def load_config(config_path: str | Path, data_model: type[BaseModel]) -> BaseModel:
23 | """
24 | Generic configuration loader that validates against any Pydantic model.
25 |
26 | Args:
27 | config_path: Path to the JSON configuration file
28 | model: Pydantic model class for validation
29 |
30 | Returns:
31 | Validated configuration instance
32 | """
33 |
34 | try:
35 | with open(config_path, encoding="utf-8") as f:
36 | data = json.load(f)
37 | return data_model.model_validate(data)
38 | except FileNotFoundError as e:
39 | msg = file_not_found(config_path)
40 | logger.error(msg)
41 | raise FileNotFoundError(msg) from e
42 | except json.JSONDecodeError as e:
43 | msg = invalid_json(str(e))
44 | logger.error(msg)
45 | raise ValueError(msg) from e
46 | except ValidationError as e:
47 | msg = invalid_data_model_format(str(e))
48 | logger.error(msg)
49 | raise ValidationError(msg) from e
50 | except Exception as e:
51 | msg = failed_to_load_config(str(e))
52 | logger.exception(msg)
53 | raise Exception(msg) from e
54 |
--------------------------------------------------------------------------------
/src/app/utils/load_settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Utility functions and classes for loading application settings and configuration.
3 |
4 | This module defines the AppEnv class for managing environment variables using Pydantic,
5 | and provides a function to load and validate application configuration from a JSON file.
6 | """
7 |
8 | import json
9 | from pathlib import Path
10 |
11 | from pydantic_settings import BaseSettings, SettingsConfigDict
12 |
13 | from app.config.data_models import ChatConfig
14 | from app.utils.error_messages import (
15 | failed_to_load_config,
16 | file_not_found,
17 | invalid_json,
18 | )
19 | from app.utils.log import logger
20 |
21 |
22 | class AppEnv(BaseSettings):
23 | """
24 | Application environment settings loaded from environment variables or .env file.
25 |
26 | This class uses Pydantic's BaseSettings to manage API keys and configuration
27 | for various inference endpoints, tools, and logging/monitoring services.
28 | Environment variables are loaded from a .env file by default.
29 | """
30 |
31 | # Inference endpoints
32 | GEMINI_API_KEY: str = ""
33 | GITHUB_API_KEY: str = ""
34 | GROK_API_KEY: str = ""
35 | HUGGINGFACE_API_KEY: str = ""
36 | OPENROUTER_API_KEY: str = ""
37 | PERPLEXITY_API_KEY: str = ""
38 | RESTACK_API_KEY: str = ""
39 | TOGETHER_API_KEY: str = ""
40 |
41 | # Tools
42 | TAVILY_API_KEY: str = ""
43 |
44 | # Logging/Monitoring/Tracing
45 | AGENTOPS_API_KEY: str = ""
46 | LOGFIRE_TOKEN: str = ""
47 | WANDB_API_KEY: str = ""
48 |
49 | model_config = SettingsConfigDict(
50 | env_file=".env", env_file_encoding="utf-8", extra="ignore"
51 | )
52 |
53 |
54 | chat_config = AppEnv()
55 |
56 |
57 | def load_config(config_path: str | Path) -> ChatConfig:
58 | """
59 | Load and validate application configuration from a JSON file.
60 |
61 | Args:
62 | config_path (str): Path to the JSON configuration file.
63 |
64 | Returns:
65 | ChatConfig: An instance of ChatConfig with validated configuration data.
66 |
67 | Raises:
68 | FileNotFoundError: If the configuration file does not exist.
69 | json.JSONDecodeError: If the file contains invalid JSON.
70 | Exception: For any other unexpected errors during loading or validation.
71 | """
72 |
73 | try:
74 | with open(config_path) as f:
75 | config_data = json.load(f)
76 | except FileNotFoundError as e:
77 | msg = file_not_found(config_path)
78 | logger.error(msg)
79 | raise FileNotFoundError(msg) from e
80 | except json.JSONDecodeError as e:
81 | msg = invalid_json(str(e))
82 | logger.error(msg)
83 | raise json.JSONDecodeError(msg, str(config_path), 0) from e
84 | except Exception as e:
85 | msg = failed_to_load_config(str(e))
86 | logger.exception(msg)
87 | raise Exception(msg) from e
88 |
89 | return ChatConfig.model_validate(config_data)
90 |
--------------------------------------------------------------------------------
/src/app/utils/log.py:
--------------------------------------------------------------------------------
1 | """
2 | Set up the logger with custom settings.
3 | Logs are written to a file with automatic rotation.
4 | """
5 |
6 | from loguru import logger
7 |
8 | from app.config.config_app import LOGS_PATH
9 |
10 | logger.add(
11 | f"{LOGS_PATH}/{{time}}.log",
12 | rotation="1 MB",
13 | # level="DEBUG",
14 | retention="7 days",
15 | compression="zip",
16 | )
17 |
--------------------------------------------------------------------------------
/src/app/utils/login.py:
--------------------------------------------------------------------------------
1 | """
2 | This module provides utility functions for managing login state and initializing
3 | the environment for a given project. It includes functionality to load and save
4 | login state, perform a one-time login, and check if the user is logged in.
5 | """
6 |
7 | from os import environ
8 |
9 | from agentops import init as agentops_init
10 | from logfire import configure as logfire_conf
11 | from wandb import login as wandb_login
12 | from weave import init as weave_init
13 |
14 | from app.agents.llm_model_funs import get_api_key
15 | from app.config.data_models import AppEnv
16 | from app.utils.error_messages import generic_exception
17 | from app.utils.log import logger
18 |
19 |
20 | def login(project_name: str, chat_env_config: AppEnv):
21 | """
22 | Logs in to the workspace and initializes the environment for the given project.
23 | Args:
24 | project_name (str): The name of the project to initialize.
25 | chat_env_config (AppEnv): The application environment configuration
26 | containing the API keys.
27 | Returns:
28 | None
29 | """
30 |
31 | try:
32 | logger.info(f"Logging in to the workspaces for project: {project_name}")
33 | environ["AGENTOPS_LOGGING_TO_FILE"] = "FALSE"
34 | agentops_init(
35 | default_tags=[project_name],
36 | api_key=get_api_key("AGENTOPS", chat_env_config),
37 | )
38 | logfire_conf(token=get_api_key("LOGFIRE", chat_env_config))
39 | wandb_login(key=get_api_key("WANDB", chat_env_config))
40 | weave_init(project_name)
41 | except Exception as e:
42 | msg = generic_exception(str(e))
43 | logger.exception(e)
44 | raise Exception(msg) from e
45 |
--------------------------------------------------------------------------------
/src/app/utils/utils.py:
--------------------------------------------------------------------------------
1 | """
2 | This module provides utility functions and context managers for handling configurations,
3 | error handling, and setting up agent environments.
4 |
5 | Functions:
6 | load_config(config_path: str) -> Config:
7 | Load and validate configuration from a JSON file.
8 |
9 | print_research_Result(summary: Dict, usage: Usage) -> None:
10 | Output structured summary of the research topic.
11 |
12 | error_handling_context(operation_name: str, console: Console = None):
13 | Context manager for handling errors during operations.
14 |
15 | setup_agent_env(config: Config, console: Console = None) -> AgentConfig:
16 | Set up the agent environment based on the provided configuration.
17 | """
18 |
19 | from pydantic_ai.usage import Usage
20 |
21 | from app.config.data_models import ResearchSummary
22 | from app.utils.log import logger
23 |
24 |
25 | def log_research_result(summary: ResearchSummary, usage: Usage) -> None:
26 | """
27 | Prints the research summary and usage details in a formatted manner.
28 |
29 | Args:
30 | summary (Dict): A dictionary containing the research summary with keys 'topic',
31 | 'key_points', 'key_points_explanation', and 'conclusion'.
32 | usage (Usage): An object containing usage details to be printed.
33 | """
34 |
35 | logger.info(f"\n=== Research Summary: {summary.topic} ===")
36 | logger.info("\nKey Points:")
37 | for i, point in enumerate(summary.key_points, 1):
38 | logger.info(f"{i}. {point}")
39 | logger.info("\nKey Points Explanation:")
40 | for i, point in enumerate(summary.key_points_explanation, 1):
41 | logger.info(f"{i}. {point}")
42 | logger.info(f"\nConclusion: {summary.conclusion}")
43 | logger.info(f"\nResponse structure: {list(dict(summary).keys())}")
44 | logger.info(usage)
45 |
46 |
47 | def parse_args(argv: list[str]) -> dict[str, str | bool]:
48 | """
49 | Parse command line arguments into a dictionary.
50 |
51 | This function processes a list of command-line arguments,
52 | extracting recognized options and their values.
53 | Supported arguments include flags (e.g., --help, --include-researcher
54 | and key-value pairs (e.g., `--chat-provider=ollama`).
55 | If the `--help` flag is present, a list of available commands and their
56 | descriptions is printed, and an empty dictionary is returned.
57 |
58 | Recognized arguments as list[str]
59 | ```
60 | --help Display help information and exit.
61 | --version Display version information.
62 | --chat-provider= Specify the chat provider to use.
63 | --query= Specify the query to process.
64 | --include-researcher Include the researcher agent.
65 | --include-analyst Include the analyst agent.
66 | --include-synthesiser Include the synthesiser agent.
67 | --no-stream Disable streaming output.
68 | --chat-config-file= Specify the path to the chat configuration file.
69 | ```
70 |
71 | Returns:
72 | `dict[str, str | bool]`: A dictionary mapping argument names
73 | (with leading '--' removed and hyphens replaced by underscores)
74 | to their values (`str` for key-value pairs, `bool` for flags).
75 | Returns an empty dict if `--help` is specified.
76 |
77 | Example:
78 | >>> `parse_args(['--chat-provider=ollama', '--include-researcher'])`
79 | returns `{'chat_provider': 'ollama', 'include_researcher': True}`
80 | """
81 |
82 | commands = {
83 | "--help": "Display help information",
84 | "--version": "Display version information",
85 | "--chat-provider": "Specify the chat provider to use",
86 | "--query": "Specify the query to process",
87 | "--include-researcher": "Include the researcher agent",
88 | "--include-analyst": "Include the analyst agent",
89 | "--include-synthesiser": "Include the synthesiser agent",
90 | "--no-stream": "Disable streaming output",
91 | "--chat-config-file": "Specify the path to the chat configuration file",
92 | }
93 | parsed_args: dict[str, str | bool] = {}
94 |
95 | if "--help" in argv:
96 | print("Available commands:")
97 | for cmd, desc in commands.items():
98 | print(f"{cmd}: {desc}")
99 | return parsed_args
100 |
101 | for arg in argv:
102 | if arg.split("=", 1)[0] in commands.keys():
103 | key, value = arg.split("=", 1) if "=" in arg else (arg, True)
104 | key = key.lstrip("--").replace("-", "_")
105 | parsed_args[key] = value
106 |
107 | if parsed_args:
108 | logger.info(f"Used arguments: {parsed_args}")
109 |
110 | return parsed_args
111 |
--------------------------------------------------------------------------------
/src/examples/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "providers": {
3 | "gemini": {
4 | "model_name": "gemini-1.5-flash-8b",
5 | "base_url": "https://generativelanguage.googleapis.com/v1beta"
6 | },
7 | "github": {
8 | "model_name": "GPT-4o",
9 | "base_url": "https://models.inference.ai.azure.com"
10 | },
11 | "huggingface": {
12 | "model_name": "Qwen/QwQ-32B-Preview",
13 | "base_url": "https://api-inference.huggingface.co/v1"
14 | },
15 | "ollama": {
16 | "model_name": "granite3-dense",
17 | "base_url": "http://localhost:11434/v1"
18 | },
19 | "openrouter": {
20 | "model_name": "google/gemini-2.0-flash-lite-preview-02-05:free",
21 | "base_url": "https://openrouter.ai/api/v1"
22 | },
23 | "restack": {
24 | "model_name": "deepseek-chat",
25 | "base_url": "https://ai.restack.io"
26 | }
27 | },
28 | "prompts": {
29 | "system_prompt": "You are a helpful research assistant. Extract key information about the topic and provide a structured summary.",
30 | "user_prompt": "Provide a research summary about",
31 | "system_prompt_researcher": "You are a manager overseeing research and analysis tasks. Your role is to coordinate the efforts of the research and analysis agents to provide comprehensive answers to user queries.",
32 | "system_prompt_manager": "You are a research assistant. Your task is to find relevant information about the topic provided. Use the search tool to gather data and synthesize it into a concise summary.",
33 | "system_prompt_analyst": "You are a data scientist. Your task is to analyze the data provided and extract meaningful insights. Use your analytical skills to identify trends, patterns, and correlations."
34 | }
35 | }
--------------------------------------------------------------------------------
/src/examples/run_simple_agent_no_tools.py:
--------------------------------------------------------------------------------
1 | """
2 | A simple example of using a Pydantic AI agent to generate a structured summary of a
3 | research topic.
4 | """
5 |
6 | from os import path
7 |
8 | from .utils.agent_simple_no_tools import get_research
9 | from .utils.utils import (
10 | get_api_key,
11 | get_provider_config,
12 | load_config,
13 | print_research_Result,
14 | )
15 |
16 | CONFIG_FILE = "config.json"
17 |
18 |
19 | def main():
20 | """Main function to run the research agent."""
21 |
22 | config_path = path.join(path.dirname(__file__), CONFIG_FILE)
23 | config = load_config(config_path)
24 |
25 | provider = input("Which inference provider to use? ")
26 | topic = input("What topic would you like to research? ")
27 |
28 | api_key = get_api_key(provider)
29 | provider_config = get_provider_config(provider, config)
30 |
31 | result = get_research(topic, config.prompts, provider, provider_config, api_key)
32 | print_research_Result(result.data, result.usage())
33 |
34 |
35 | if __name__ == "__main__":
36 | main()
37 |
--------------------------------------------------------------------------------
/src/examples/run_simple_agent_system.py:
--------------------------------------------------------------------------------
1 | """
2 | This example demonstrates how to run a simple agent system that consists of a manager
3 | agent, a research agent, and an analysis agent. The manager agent delegates research
4 | and analysis tasks to the corresponding agents and combines the results to provide a
5 | comprehensive answer to the user query.
6 | https://ai.pydantic.dev/multi-agent-applications/#agent-delegation
7 | """
8 |
9 | from asyncio import run
10 | from os import path
11 |
12 | from openai import UnprocessableEntityError
13 | from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool
14 | from pydantic_ai.exceptions import UnexpectedModelBehavior, UsageLimitExceeded
15 | from pydantic_ai.models.openai import OpenAIModel
16 | from pydantic_ai.usage import UsageLimits
17 |
18 | from .utils.agent_simple_system import SystemAgent, add_tools_to_manager_agent
19 | from .utils.data_models import AnalysisResult, ResearchResult
20 | from .utils.utils import create_model, get_api_key, get_provider_config, load_config
21 |
22 | CONFIG_FILE = "config.json"
23 |
24 |
25 | def get_models(model_config: dict) -> tuple[OpenAIModel]:
26 | """Get the models for the system agents."""
27 | model_researcher = create_model(**model_config)
28 | model_analyst = create_model(**model_config)
29 | model_manager = create_model(**model_config)
30 | return model_researcher, model_analyst, model_manager
31 |
32 |
33 | def get_manager(
34 | model_manager: OpenAIModel,
35 | model_researcher: OpenAIModel,
36 | model_analyst: OpenAIModel,
37 | prompts: dict[str, str],
38 | ) -> SystemAgent:
39 | """Get the agents for the system."""
40 | researcher = SystemAgent(
41 | model_researcher,
42 | ResearchResult,
43 | prompts["system_prompt_researcher"],
44 | [duckduckgo_search_tool()],
45 | )
46 | analyst = SystemAgent(
47 | model_analyst, AnalysisResult, prompts["system_prompt_analyst"]
48 | )
49 | manager = SystemAgent(
50 | model_manager, ResearchResult, prompts["system_prompt_manager"]
51 | )
52 | add_tools_to_manager_agent(manager, researcher, analyst)
53 | return manager
54 |
55 |
56 | async def main():
57 | """Main function to run the research system."""
58 |
59 | provider = input("Which inference provider to use? ")
60 | query = input("What would you like to research? ")
61 |
62 | config_path = path.join(path.dirname(__file__), CONFIG_FILE)
63 | config = load_config(config_path)
64 |
65 | api_key = get_api_key(provider)
66 | provider_config = get_provider_config(provider, config)
67 | usage_limits = UsageLimits(request_limit=10, total_tokens_limit=4000)
68 |
69 | model_config = {
70 | "base_url": provider_config["base_url"],
71 | "model_name": provider_config["model_name"],
72 | "api_key": api_key,
73 | "provider": provider,
74 | }
75 | manager = get_manager(*get_models(model_config), config.prompts)
76 |
77 | print(f"\nResearching: {query}...")
78 |
79 | try:
80 | result = await manager.run(query, usage_limits=usage_limits)
81 | except (UnexpectedModelBehavior, UnprocessableEntityError) as e:
82 | print(f"Error: Model returned unexpected result: {e}")
83 | except UsageLimitExceeded as e:
84 | print(f"Usage limit exceeded: {e}")
85 | else:
86 | print("\nFindings:", {result.data.findings})
87 | print(f"Sources: {result.data.sources}")
88 | print("\nUsage statistics:")
89 | print(result.usage())
90 |
91 |
92 | if __name__ == "__main__":
93 | run(main())
94 |
--------------------------------------------------------------------------------
/src/examples/run_simple_agent_tools.py:
--------------------------------------------------------------------------------
1 | """Run the dice game agent using simple tools."""
2 |
3 | from os import path
4 |
5 | from .utils.agent_simple_tools import get_dice
6 | from .utils.utils import (
7 | get_api_key,
8 | get_provider_config,
9 | load_config,
10 | )
11 |
12 | CONFIG_FILE = "config.json"
13 | system_prompt = (
14 | "You're a dice game, you should roll the die and see if the number "
15 | "you get back matches the user's guess. If so, tell them they're a winner. "
16 | "Use the player's name in the response."
17 | )
18 |
19 |
20 | def main():
21 | """Run the dice game agent."""
22 |
23 | provider = input("Which inference provider to use? ")
24 | player_name = input("Enter your name: ")
25 | guess = input("Guess a number between 1 and 6: ")
26 |
27 | config_path = path.join(path.dirname(__file__), CONFIG_FILE)
28 | config = load_config(config_path)
29 |
30 | api_key = get_api_key(provider)
31 | provider_config = get_provider_config(provider, config)
32 |
33 | result = get_dice(
34 | player_name, guess, system_prompt, provider, api_key, provider_config
35 | )
36 | print(result.data)
37 | print(f"{result._result_tool_name=}")
38 | print(result.usage())
39 |
40 |
41 | if __name__ == "__main__":
42 | main()
43 |
--------------------------------------------------------------------------------
/src/examples/utils/agent_simple_no_tools.py:
--------------------------------------------------------------------------------
1 | """
2 | This module contains a function to create a research agent with the specified model,
3 | result type, and system prompt.
4 | """
5 |
6 | from sys import exit
7 |
8 | from openai import APIConnectionError
9 | from pydantic_ai import Agent
10 | from pydantic_ai.agent import AgentRunResult
11 | from pydantic_ai.models.openai import OpenAIModel
12 |
13 | from .data_models import Config, ResearchSummary
14 | from .utils import create_model
15 |
16 |
17 | def _create_research_agent(
18 | model: OpenAIModel, result_type: ResearchSummary, system_prompt: str
19 | ) -> Agent:
20 | """
21 | Create a research agent with the specified model, result type, and system prompt.
22 | """
23 |
24 | return Agent(model=model, result_type=result_type, system_prompt=system_prompt)
25 |
26 |
27 | def get_research(
28 | topic: str,
29 | prompts: dict[str, str],
30 | provider: str,
31 | provider_config: Config,
32 | api_key: str,
33 | ) -> AgentRunResult:
34 | """Run the research agent to generate a structured summary of a research topic."""
35 |
36 | model = create_model(
37 | provider_config["base_url"], provider_config["model_name"], api_key, provider
38 | )
39 | agent = _create_research_agent(model, ResearchSummary, prompts["system_prompt"])
40 |
41 | print(f"\nResearching {topic}...")
42 | try:
43 | result = agent.run_sync(f"{prompts['user_prompt']} {topic}")
44 | except APIConnectionError as e:
45 | print(f"Error connecting to API: {e}")
46 | exit()
47 | except Exception as e:
48 | print(f"Error connecting to API: {e}")
49 | exit()
50 | else:
51 | return result
52 |
--------------------------------------------------------------------------------
/src/examples/utils/agent_simple_system.py:
--------------------------------------------------------------------------------
1 | """
2 | This module contains a simple system of agents that can be used to research and analyze
3 | data.
4 | """
5 |
6 | from pydantic_ai import Agent, RunContext
7 | from pydantic_ai.models.openai import OpenAIModel
8 |
9 | from .data_models import AnalysisResult, ResearchResult
10 |
11 |
12 | class SystemAgent(Agent):
13 | """A generic system agent that can be used to research and analyze data."""
14 |
15 | def __init__(
16 | self,
17 | model: OpenAIModel,
18 | result_type: ResearchResult | AnalysisResult,
19 | system_prompt: str,
20 | result_retries: int = 3,
21 | tools: list | None = [],
22 | ):
23 | super().__init__(
24 | model,
25 | result_type=result_type,
26 | system_prompt=system_prompt,
27 | result_retries=result_retries,
28 | tools=tools,
29 | )
30 |
31 |
32 | def add_tools_to_manager_agent(
33 | manager_agent: SystemAgent, research_agent: SystemAgent, analysis_agent: SystemAgent
34 | ) -> None:
35 | """Create and configure the joke generation agent."""
36 |
37 | @manager_agent.tool
38 | async def delegate_research(ctx: RunContext[None], query: str) -> ResearchResult:
39 | """Delegate research task to ResearchAgent."""
40 | result = await research_agent.run(query, usage=ctx.usage)
41 | return result.data
42 |
43 | @manager_agent.tool
44 | async def delegate_analysis(ctx: RunContext[None], data: str) -> AnalysisResult:
45 | """Delegate analysis task to AnalysisAgent."""
46 | result = await analysis_agent.run(data, usage=ctx.usage)
47 | return result.data
48 |
--------------------------------------------------------------------------------
/src/examples/utils/agent_simple_tools.py:
--------------------------------------------------------------------------------
1 | """Simple agent for the dice game example."""
2 |
3 | from openai import APIConnectionError
4 | from pydantic_ai import Agent, Tool
5 | from pydantic_ai.agent import AgentRunResult
6 | from pydantic_ai.models.openai import OpenAIModel
7 |
8 | from .tools import get_player_name, roll_die
9 | from .utils import create_model
10 |
11 |
12 | class _DiceGameAgent(Agent):
13 | """Dice game agent."""
14 |
15 | def __init__(self, model: OpenAIModel, system_prompt: str):
16 | super().__init__(
17 | model=model,
18 | deps_type=str,
19 | system_prompt=system_prompt,
20 | tools=[ # (1)!
21 | Tool(roll_die, takes_ctx=False),
22 | Tool(get_player_name, takes_ctx=True),
23 | ],
24 | )
25 |
26 |
27 | def get_dice(
28 | player_name: str,
29 | guess: str,
30 | system_prompt: str,
31 | provider: str,
32 | api_key: str,
33 | config: dict,
34 | ) -> AgentRunResult:
35 | """Run the dice game agent."""
36 |
37 | model = create_model(config["base_url"], config["model_name"], api_key, provider)
38 | agent = _DiceGameAgent(model, system_prompt)
39 |
40 | try:
41 | # usage_limits=UsageLimits(request_limit=5, total_tokens_limit=300),
42 | result = agent.run_sync(f"Player is guessing {guess}...", deps=player_name)
43 | except APIConnectionError as e:
44 | print(f"Error connecting to API: {e}")
45 | exit()
46 | except Exception as e:
47 | print(f"Error connecting to API: {e}")
48 | exit()
49 | else:
50 | return result
51 |
--------------------------------------------------------------------------------
/src/examples/utils/data_models.py:
--------------------------------------------------------------------------------
1 | """Example of a module with data models"""
2 |
3 | from pydantic import BaseModel
4 |
5 |
6 | class ResearchResult(BaseModel):
7 | """Research results from the research agent."""
8 |
9 | topic: str
10 | findings: list[str]
11 | sources: list[str]
12 |
13 |
14 | class AnalysisResult(BaseModel):
15 | """Analysis results from the analysis agent."""
16 |
17 | insights: list[str]
18 | recommendations: list[str]
19 |
20 |
21 | class ResearchSummary(BaseModel):
22 | """Expected model response of research on a topic"""
23 |
24 | topic: str
25 | key_points: list[str]
26 | key_points_explanation: list[str]
27 | conclusion: str
28 |
29 |
30 | class ProviderConfig(BaseModel):
31 | """Configuration for a model provider"""
32 |
33 | model_name: str
34 | base_url: str
35 |
36 |
37 | class Config(BaseModel):
38 | """Configuration settings for the research agent and model providers"""
39 |
40 | providers: dict[str, ProviderConfig]
41 | prompts: dict[str, str]
42 |
--------------------------------------------------------------------------------
/src/examples/utils/tools.py:
--------------------------------------------------------------------------------
1 | """Example tools for the utils example."""
2 |
3 | from random import randint
4 |
5 | from pydantic_ai import RunContext
6 |
7 |
8 | def roll_die() -> str:
9 | """Tool to roll a die."""
10 |
11 | async def _execute(self) -> str:
12 | """Roll the die and return the result."""
13 | return str(randint(1, 6))
14 |
15 |
16 | def get_player_name(ctx: RunContext[str]) -> str:
17 | """Get the player's name from the context."""
18 | return ctx.deps
19 |
--------------------------------------------------------------------------------
/src/examples/utils/utils.py:
--------------------------------------------------------------------------------
1 | """Utility functions for running the research agent example."""
2 |
3 | from json import load
4 | from os import getenv
5 | from sys import exit
6 |
7 | from dotenv import load_dotenv
8 | from pydantic import ValidationError
9 | from pydantic_ai.models.openai import OpenAIModel
10 | from pydantic_ai.providers.openai import OpenAIProvider
11 | from pydantic_ai.usage import Usage
12 |
13 | from .data_models import Config
14 |
15 | API_SUFFIX = "_API_KEY"
16 |
17 |
18 | def load_config(config_path: str) -> Config:
19 | """Load and validate configuration from a JSON file."""
20 |
21 | try:
22 | with open(config_path) as file:
23 | config_data = load(file)
24 | config = Config.model_validate(config_data)
25 | except FileNotFoundError:
26 | raise FileNotFoundError(f"Configuration file not found: {config_path}")
27 | exit()
28 | except ValidationError as e:
29 | raise ValueError(f"Invalid configuration format: {e}")
30 | exit()
31 | except Exception as e:
32 | raise Exception(f"Error loading configuration: {e}")
33 | exit()
34 | else:
35 | return config
36 |
37 |
38 | def get_api_key(provider: str) -> str | None:
39 | """Retrieve API key from environment variable."""
40 |
41 | # TODO replace with pydantic-settings ?
42 | load_dotenv()
43 |
44 | if provider.lower() == "ollama":
45 | return None
46 | else:
47 | return getenv(f"{provider.upper()}{API_SUFFIX}")
48 |
49 |
50 | def get_provider_config(provider: str, config: Config) -> dict[str, str]:
51 | """Retrieve configuration settings for the specified provider."""
52 |
53 | try:
54 | model_name = config.providers[provider].model_name
55 | base_url = config.providers[provider].base_url
56 | except KeyError as e:
57 | raise ValueError(f"Missing configuration for {provider}: {e}.")
58 | exit()
59 | except Exception as e:
60 | raise Exception(f"Error loading provider configuration: {e}")
61 | exit()
62 | else:
63 | return {
64 | "model_name": model_name,
65 | "base_url": base_url,
66 | }
67 |
68 |
69 | def create_model(
70 | base_url: str,
71 | model_name: str,
72 | api_key: str | None = None,
73 | provider: str | None = None,
74 | ) -> OpenAIModel:
75 | """Create a model that uses base_url as inference API"""
76 |
77 | if api_key is None and not provider.lower() == "ollama":
78 | raise ValueError("API key is required for model.")
79 | exit()
80 | else:
81 | return OpenAIModel(
82 | model_name, provider=OpenAIProvider(base_url=base_url, api_key=api_key)
83 | )
84 |
85 |
86 | def print_research_Result(summary: dict, usage: Usage) -> None:
87 | """Output structured summary of the research topic."""
88 |
89 | print(f"\n=== Research Summary: {summary.topic} ===")
90 | print("\nKey Points:")
91 | for i, point in enumerate(summary.key_points, 1):
92 | print(f"{i}. {point}")
93 | print("\nKey Points Explanation:")
94 | for i, point in enumerate(summary.key_points_explanation, 1):
95 | print(f"{i}. {point}")
96 | print(f"\nConclusion: {summary.conclusion}")
97 |
98 | print(f"\nResponse structure: {list(dict(summary).keys())}")
99 | print(usage)
100 |
--------------------------------------------------------------------------------
/src/gui/components/footer.py:
--------------------------------------------------------------------------------
1 | from streamlit import caption, divider
2 |
3 |
4 | def render_footer(footer_caption: str):
5 | """Render the page footer."""
6 | divider()
7 | caption(footer_caption)
8 |
--------------------------------------------------------------------------------
/src/gui/components/header.py:
--------------------------------------------------------------------------------
1 | from streamlit import divider, title
2 |
3 |
4 | def render_header(header_title: str):
5 | """Render the page header with title."""
6 | title(header_title)
7 | divider()
8 |
--------------------------------------------------------------------------------
/src/gui/components/output.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | from streamlit import empty, info
4 |
5 |
6 | def render_output(
7 | result: Any = None, info_str: str | None = None, type: str | None = None
8 | ):
9 | """
10 | Renders the output in a Streamlit app based on the provided type.
11 |
12 | Args:
13 | result (Any, optional): The content to be displayed. Can be JSON, code
14 | markdown, or plain text.
15 | info (str, optional): The information message to be displayed if result is None.
16 | type (str, optional): The type of the result content. Can be 'json', 'code',
17 | 'md', or other for plain text.
18 |
19 | Returns:
20 | Out: None
21 | """
22 |
23 | if result:
24 | output_container = empty()
25 | output_container.write(result)
26 | # match type:
27 | # case "json":
28 | # json(result)
29 | # case "code":
30 | # code(result)
31 | # case "md":
32 | # markdown(result)
33 | # case _:
34 | # text(result)
35 | # # st.write(result)
36 | else:
37 | info(info_str)
38 |
--------------------------------------------------------------------------------
/src/gui/components/prompts.py:
--------------------------------------------------------------------------------
1 | from streamlit import text_area
2 |
3 |
4 | def render_prompt_editor(
5 | prompt_name: str, prompt_value: str, height: int = 150
6 | ) -> str | None:
7 | return text_area(
8 | f"{prompt_name.replace('_', ' ').title()}", value=prompt_value, height=height
9 | )
10 |
--------------------------------------------------------------------------------
/src/gui/components/sidebar.py:
--------------------------------------------------------------------------------
1 | from streamlit import sidebar
2 |
3 | from gui.config.config import PAGES
4 |
5 |
6 | def render_sidebar(sidebar_title: str):
7 | sidebar.title(sidebar_title)
8 | selected_page = sidebar.radio(" ", PAGES)
9 |
10 | # st.sidebar.divider()
11 | # st.sidebar.info(" ")
12 | return selected_page
13 |
--------------------------------------------------------------------------------
/src/gui/config/config.py:
--------------------------------------------------------------------------------
1 | APP_PATH = "app"
2 | PAGES = ["Home", "Settings", "Prompts", "App"]
3 | PROMPTS_DEFAULT = {
4 | "system_prompt_manager": (
5 | "You are a manager overseeing research and analysis tasks..."
6 | ),
7 | "system_prompt_researcher": ("You are a researcher. Gather and analyze data..."),
8 | "system_prompt_analyst": (
9 | "You are a research analyst. Use your analytical skills..."
10 | ),
11 | "system_prompt_synthesiser": (
12 | "You are a research synthesiser. Use your analytical skills..."
13 | ),
14 | }
15 |
--------------------------------------------------------------------------------
/src/gui/config/styling.py:
--------------------------------------------------------------------------------
1 | from streamlit import markdown, set_page_config
2 |
3 |
4 | def add_custom_styling(page_title: str):
5 | set_page_config(
6 | page_title=f"{page_title}",
7 | page_icon="🤖",
8 | layout="wide",
9 | initial_sidebar_state="expanded",
10 | )
11 |
12 | custom_css = """
13 |
19 | """
20 | markdown(custom_css, unsafe_allow_html=True)
21 |
--------------------------------------------------------------------------------
/src/gui/config/text.py:
--------------------------------------------------------------------------------
1 | HOME_INFO = "Select 'App' to start using the system"
2 | HOME_HEADER = "Welcome to the Multi-Agent Research System"
3 | HOME_DESCRIPTION = """
4 | This system allows you to:
5 |
6 | - Run research queries using multiple specialized agents
7 | - Configure agent settings and prompts
8 | - View detailed results from your research
9 |
10 | Use the sidebar to navigate between different sections of the application.
11 | """
12 | PAGE_TITLE = "MAS Eval 👾⚗️🧠💡"
13 | PROMPTS_WARNING = "No prompts found. Using default prompts."
14 | PROMPTS_HEADER = "Agent Prompts"
15 | RUN_APP_HEADER = "Run Research App"
16 | RUN_APP_QUERY_PLACEHOLDER = "What would you like to research?"
17 | RUN_APP_PROVIDER_PLACEHOLDER = "Provider?"
18 | RUN_APP_BUTTON = "Run Query"
19 | RUN_APP_OUTPUT_PLACEHOLDER = "Run the agent to see results here"
20 | RUN_APP_QUERY_WARNING = "Please enter a query"
21 | RUN_APP_QUERY_RUN_INFO = "Running query: "
22 | SETTINGS_HEADER = "Settings"
23 | SETTINGS_PROVIDER_LABEL = "Select Provider"
24 | SETTINGS_PROVIDER_PLACEHOLDER = "Select Provider"
25 | SETTINGS_ADD_PROVIDER = "Add New Provider"
26 | SETTINGS_API_KEY_LABEL = "API Key"
27 | OUTPUT_SUBHEADER = "Output"
28 |
--------------------------------------------------------------------------------
/src/gui/pages/home.py:
--------------------------------------------------------------------------------
1 | from streamlit import header, info, markdown
2 |
3 | from gui.config.text import HOME_DESCRIPTION, HOME_HEADER, HOME_INFO
4 |
5 |
6 | def render_home():
7 | header(HOME_HEADER)
8 | markdown(HOME_DESCRIPTION)
9 | info(HOME_INFO)
10 |
--------------------------------------------------------------------------------
/src/gui/pages/prompts.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit component for editing agent system prompts.
3 |
4 | This module provides a function to render and edit prompt configurations
5 | for agent roles using a Streamlit-based UI. It validates the input configuration,
6 | displays warnings if prompts are missing, and allows interactive editing of each prompt.
7 | """
8 |
9 | from pydantic import BaseModel
10 | from streamlit import error, header, warning
11 |
12 | from app.config.data_models import ChatConfig
13 | from app.utils.error_messages import invalid_type
14 | from app.utils.log import logger
15 | from gui.components.prompts import render_prompt_editor
16 | from gui.config.config import PROMPTS_DEFAULT
17 | from gui.config.text import PROMPTS_HEADER, PROMPTS_WARNING
18 |
19 |
20 | def render_prompts(chat_config: ChatConfig | BaseModel): # -> dict[str, str]:
21 | """
22 | Render and edit the prompt configuration for agent roles in the Streamlit UI.
23 | """
24 |
25 | header(PROMPTS_HEADER)
26 |
27 | if not isinstance(chat_config, ChatConfig):
28 | msg = invalid_type("ChatConfig", type(chat_config).__name__)
29 | logger.error(msg)
30 | error(msg)
31 | return None
32 |
33 | # updated = False
34 | prompts = chat_config.prompts
35 |
36 | if not prompts:
37 | warning(PROMPTS_WARNING)
38 | prompts = PROMPTS_DEFAULT
39 |
40 | updated_prompts = prompts.copy()
41 |
42 | # Edit prompts
43 | for prompt_key, prompt_value in prompts.items():
44 | new_value = render_prompt_editor(prompt_key, prompt_value, height=200)
45 | if new_value != prompt_value and new_value is not None:
46 | updated_prompts[prompt_key] = new_value
47 | # updated = True
48 |
49 | # return updated_prompts if updated else prompts
50 |
--------------------------------------------------------------------------------
/src/gui/pages/run_app.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit interface for running the agentic system interactively.
3 |
4 | This module defines the render_app function, which provides a Streamlit-based UI
5 | for users to select a provider, enter a query, and execute the main agent workflow.
6 | Results and errors are displayed in real time, supporting asynchronous execution.
7 | """
8 |
9 | from streamlit import button, exception, header, info, subheader, text_input, warning
10 |
11 | from app.main import main
12 | from app.utils.log import logger
13 | from gui.components.output import render_output
14 | from gui.config.text import (
15 | OUTPUT_SUBHEADER,
16 | RUN_APP_BUTTON,
17 | RUN_APP_HEADER,
18 | RUN_APP_OUTPUT_PLACEHOLDER,
19 | RUN_APP_PROVIDER_PLACEHOLDER,
20 | RUN_APP_QUERY_PLACEHOLDER,
21 | RUN_APP_QUERY_RUN_INFO,
22 | RUN_APP_QUERY_WARNING,
23 | )
24 |
25 |
26 | async def render_app(provider: str | None = None):
27 | """
28 | Render the main app interface for running agentic queries via Streamlit.
29 |
30 | Displays input fields for provider and query, a button to trigger execution,
31 | and an area for output or error messages. Handles async invocation of the
32 | main agent workflow and logs any exceptions.
33 | """
34 |
35 | header(RUN_APP_HEADER)
36 | if provider is None:
37 | provider = text_input(RUN_APP_PROVIDER_PLACEHOLDER)
38 | query = text_input(RUN_APP_QUERY_PLACEHOLDER)
39 |
40 | subheader(OUTPUT_SUBHEADER)
41 | if button(RUN_APP_BUTTON):
42 | if query:
43 | info(f"{RUN_APP_QUERY_RUN_INFO} {query}")
44 | try:
45 | result = await main(chat_provider=provider, query=query)
46 | render_output(result)
47 | except Exception as e:
48 | render_output(None)
49 | exception(e)
50 | logger.exception(e)
51 | else:
52 | warning(RUN_APP_QUERY_WARNING)
53 | else:
54 | render_output(RUN_APP_OUTPUT_PLACEHOLDER)
55 |
--------------------------------------------------------------------------------
/src/gui/pages/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit settings UI for provider and agent configuration.
3 |
4 | This module provides a function to render and edit agent system settings,
5 | including provider selection and related options, within the Streamlit GUI.
6 | It validates the input configuration and ensures correct typing before rendering.
7 | """
8 |
9 | from streamlit import error, header, selectbox
10 |
11 | from app.config.data_models import BaseModel, ChatConfig
12 | from app.utils.error_messages import invalid_type
13 | from app.utils.log import logger
14 | from gui.config.text import SETTINGS_HEADER, SETTINGS_PROVIDER_LABEL
15 |
16 |
17 | def render_settings(chat_config: ChatConfig | BaseModel) -> str:
18 | """
19 | Render and edit agent system settings in the Streamlit UI.
20 |
21 | Displays a header and a selectbox for choosing the inference provider.
22 | Validates that the input is a ChatConfig instance and displays an error if not.
23 | """
24 | header(SETTINGS_HEADER)
25 |
26 | # updated = False
27 | # updated_config = config.copy()
28 |
29 | if not isinstance(chat_config, ChatConfig):
30 | msg = invalid_type("ChatConfig", type(chat_config).__name__)
31 | logger.error(msg)
32 | error(msg)
33 | return msg
34 |
35 | provider = selectbox(
36 | label=SETTINGS_PROVIDER_LABEL,
37 | options=chat_config.providers.keys(),
38 | )
39 |
40 | # Run options
41 | # col1, col2 = st.columns(2)
42 | # with col1:
43 | # streamed_output = st.checkbox(
44 | # "Stream Output", value=config.get("streamed_output", False)
45 | # )
46 | # with col2:
47 | # st.checkbox("Include Sources", value=True) # include_sources
48 |
49 | # Allow adding new providers
50 | # new_provider = st.text_input("Add New Provider")
51 | # api_key = st.text_input(f"{provider} API Key", type="password")
52 | # if st.button("Add Provider") and new_provider and new_provider not in providers:
53 | # providers.append(new_provider)
54 | # updated_config["providers"] = providers
55 | # updated_config["api_key"] = api_key
56 | # updated = True
57 | # st.success(f"Added provider: {new_provider}")
58 |
59 | # # Update config if changed
60 | # if (
61 | # include_a != config.get("include_a", False)
62 | # or include_b != config.get("include_b", False)
63 | # or streamed_output != config.get("streamed_output", False)
64 | # ):
65 | # updated_config["include_a"] = include_a
66 | # updated_config["include_b"] = include_b
67 | # updated_config["streamed_output"] = streamed_output
68 | # updated = True
69 |
70 | return provider
71 |
--------------------------------------------------------------------------------
/src/run_gui.py:
--------------------------------------------------------------------------------
1 | """
2 | This module sets up and runs a Streamlit application for a Multi-Agent System.
3 |
4 | The application includes the following components:
5 | - Header
6 | - Sidebar for configuration options
7 | - Main content area for prompts
8 | - Footer
9 |
10 | The main function loads the configuration, renders the UI components, and handles the
11 | execution of the Multi-Agent System based on user input.
12 |
13 | Functions:
14 | - run_app(): Placeholder function to run the main application logic.
15 | - main(): Main function to set up and run the Streamlit application.
16 | """
17 |
18 | from asyncio import run
19 | from pathlib import Path
20 |
21 | from app.config.config_app import CHAT_CONFIG_FILE, CHAT_DEFAULT_PROVIDER
22 | from app.config.data_models import ChatConfig
23 | from app.utils.load_configs import load_config
24 | from app.utils.log import logger
25 | from gui.components.sidebar import render_sidebar
26 | from gui.config.config import APP_PATH
27 | from gui.config.styling import add_custom_styling
28 | from gui.config.text import PAGE_TITLE
29 | from gui.pages.home import render_home
30 | from gui.pages.prompts import render_prompts
31 | from gui.pages.run_app import render_app
32 | from gui.pages.settings import render_settings
33 |
34 | # TODO create sidebar tabs, move settings to page,
35 | # set readme.md as home, separate prompts into page
36 |
37 | chat_config_pfile = Path(__file__).parent / APP_PATH / CHAT_CONFIG_FILE
38 | chat_config = load_config(chat_config_pfile, ChatConfig)
39 | provider = CHAT_DEFAULT_PROVIDER
40 | logger.info(f"Default provider: {CHAT_DEFAULT_PROVIDER}")
41 |
42 |
43 | async def main():
44 | add_custom_styling(PAGE_TITLE)
45 | selected_page = render_sidebar(PAGE_TITLE)
46 |
47 | if selected_page == "Home":
48 | render_home()
49 | elif selected_page == "Settings":
50 | # TODO temp save settings to be used in gui
51 | provider = render_settings(chat_config)
52 | logger.info(f"Page 'Settings' provider: {provider}")
53 | elif selected_page == "Prompts":
54 | render_prompts(chat_config)
55 | elif selected_page == "App":
56 | logger.info(f"Page 'App' provider: {CHAT_DEFAULT_PROVIDER}")
57 | await render_app(CHAT_DEFAULT_PROVIDER)
58 |
59 |
60 | if __name__ == "__main__":
61 | run(main())
62 |
--------------------------------------------------------------------------------
/tests/test_agent_system.py:
--------------------------------------------------------------------------------
1 | from app.agents.agent_system import get_manager
2 | from app.config.data_models import ProviderConfig
3 |
4 |
5 | def test_get_manager_minimal():
6 | provider = "github"
7 | provider_config = ProviderConfig.model_validate(
8 | {"model_name": "test-model", "base_url": "http://test.com"}
9 | )
10 | api_key = "test"
11 | prompts = {"system_prompt_manager": "test"}
12 | agent = get_manager(provider, provider_config, api_key, prompts)
13 | assert hasattr(agent, "run")
14 |
--------------------------------------------------------------------------------
/tests/test_env.py:
--------------------------------------------------------------------------------
1 | from pytest import MonkeyPatch
2 |
3 | from app.config.data_models import AppEnv
4 |
5 |
6 | def test_app_env_loads_env_vars(monkeypatch: MonkeyPatch):
7 | monkeypatch.setenv("GEMINI_API_KEY", "test-gemini")
8 | env = AppEnv()
9 | assert env.GEMINI_API_KEY == "test-gemini"
10 |
--------------------------------------------------------------------------------
/tests/test_metrics_output_similarity.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests for the output_similarity metric.
3 |
4 | This module verifies that the output_similarity metric correctly identifies when
5 | an agent's output matches the expected answer.
6 | """
7 |
8 | from app.evals.metrics import output_similarity
9 |
10 |
11 | def test_output_similarity_exact_match():
12 | assert output_similarity("42", "42") is True
13 |
14 |
15 | def test_output_similarity_whitespace():
16 | assert output_similarity(" answer ", "answer") is True
17 |
18 |
19 | def test_output_similarity_incorrect():
20 | assert output_similarity("foo", "bar") is False
21 |
--------------------------------------------------------------------------------
/tests/test_metrics_time_taken.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests for the time_taken metric.
3 |
4 | This module verifies that the time_taken metric correctly computes the elapsed
5 | time between two timestamps, ensuring accurate measurement of agent execution
6 | duration for evaluation purposes.
7 | """
8 |
9 | import asyncio
10 | import time
11 |
12 | import pytest
13 |
14 | from app.evals.metrics import time_taken
15 |
16 |
17 | @pytest.mark.asyncio
18 | async def test_time_taken_metric():
19 | """Scenario: Calculate time taken for agent execution"""
20 |
21 | # Given: Start and end timestamps
22 | start_time = time.perf_counter()
23 | await asyncio.sleep(0.1)
24 | end_time = time.perf_counter()
25 |
26 | # When: Calculating time taken
27 | result = time_taken(start_time, end_time)
28 |
29 | # Then: Verify correct duration calculation
30 | assert result == pytest.approx(0.1, abs=0.05)
31 |
--------------------------------------------------------------------------------
/tests/test_provider_config.py:
--------------------------------------------------------------------------------
1 | from pytest import MonkeyPatch
2 |
3 | from app.config.data_models import ProviderConfig
4 |
5 |
6 | def test_provider_config_parsing(monkeypatch: MonkeyPatch):
7 | pcfg = ProviderConfig.model_validate(
8 | {"model_name": "foo", "base_url": "https://foo.bar"}
9 | )
10 | assert pcfg.model_name == "foo"
11 | assert pcfg.base_url == "bar"
12 |
--------------------------------------------------------------------------------
================================================
FILE: docs/PRD.md
================================================
# Product Requirements Document (PRD) for Agents-eval
## Overview
**Agents-eval** is a project aimed at evaluating the effectiveness of open-source agentic AI systems across various use cases. The focus is on use case agnostic metrics that measure core capabilities such as task decomposition, tool integration, adaptability, and overall performance.
## Goals
- **Evaluate Agentic AI Systems:** Provide a comprehensive evaluation pipeline to assess the performance of agentic AI systems.
- **Metric Development:** Develop and implement metrics that are agnostic to specific use cases but measure core agentic capabilities.
- **Continuous Improvement:** Promote continuous improvement through automated testing, version control, and documentation.
## Functional Requirements
### CLI
- **Command Line Interface:**
- Commands to start, stop, and check the status of the Ollama server or remote inference endpoints.
- Commands to download or call models and run tests.
### Frontend (Streamlit)
- **User Interface:**
- Display test results and system performance metrics.
### (Optional) Backend (FastAPI)
- **Agentic System Integration:**
- Support for adding tools to agents using Pydantic-AI.
- Ensure agents can use tools effectively and return expected results.
- **Model Management:**
- Ability to download, list, and manage models using the `ollama` Python package.
- **API Endpoints:**
- Endpoint to start and check the status of the Ollama server.
- Endpoint to download and manage models.
- Endpoint to run tests and return results.
## Non-Functional Requirements
- **Maintainability:**
- Use modular design patterns for easy updates and maintenance.
- Implement logging and error handling for debugging and monitoring.
- **Documentation:**
- Comprehensive documentation for setup, usage, and testing.
- **Scalability:**
- Design the system to handle multiple concurrent requests.
- **Performance:**
- Ensure low latency in server responses and model downloads.
- Optimize for memory usage and CPU/GPU utilization.
- **Security:**
- Implement secure communication between components.
- Use environment variables for sensitive information.
## Assumptions
- **Remote Inference Endpoints:** The project can use remote inference endpoints provided within a `config.json` and using API keys from `.env`.
- **Local Ollama Server:** The project can make use of a local Ollama server for model hosting and inference.
- **Python Environment:** The project uses Python 3.12 and related tools like `uv` for dependency management.
- **GitHub Actions:** CI/CD pipelines are set up using GitHub Actions for automated testing, version bumping, and documentation deployment.
## Constraints
- **Hardware:** The project assumes access to appropriate hardwareif running the Ollama server and models, including sufficient RAM and GPU capabilities.
- **Software:** Requires Python 3.12, `uv`, and other dependencies listed in `pyproject.toml`.
## Main Dependencies
- **Pydantic-AI:** For agent and tool management.
- **Pytest:** For testing.
- **Ollama:** For local model hosting and inference.
- **Streamlit:** For frontend dashboard.
- **Ruff:** For code linting.
- **MkDocs:** For documentation generation.
## Future Enhancements
- **Additional Metrics:** Develop more metrics to evaluate agentic systems.
- **Integration with More Frameworks:** Expand compatibility with other agentic system frameworks. Meaning other popular agentic system frameworks like LangChain, AutoGen, CrewAI, LangGraph, Semantic Kernel, and smolAgents.
- **Performance Optimization:** Further optimize for latency and resource usage.
- **User Feedback:** Implement a feedback loop for users to report issues or suggest improvements.
================================================
FILE: docs/SprintPlan.md
================================================
# Project Plan Outline
## Week 1 starting 2025-03-31: Metric Development and CLI Enhancements
### Milestones
- Metric Development: Implement at least three new metrics for evaluating agentic AI systems.
- CLI Streaming: Enhance the CLI to stream Pydantic-AI output.
### Tasks and Sequence
- [ ] Research and Design New Metrics
- Task Definition: Conduct literature review and design three new metrics that are agnostic to specific use cases but measure core agentic capabilities.
- Sequence: Before implementing any code changes.
- Definition of Done: A detailed document outlining the metrics, their mathematical formulations, and how they will be integrated into the evaluation pipeline.
- [ ] Implement New Metrics
- Task Definition: Write Python code to implement the new metrics, ensuring they are modular and easily integratable with existing evaluation logic.
- Sequence: After completing the design document.
- Definition of Done: Unit tests for each metric pass, and they are successfully integrated into the evaluation pipeline.
- [ ] Enhance CLI for Streaming
- Task Definition: Modify the CLI to stream Pydantic-AI output using asynchronous functions.
- Sequence: Concurrently with metric implementation.
- Definition of Done: The CLI can stream output from Pydantic-AI models without blocking, and tests demonstrate successful streaming.
- [ ] Update Documentation
- Task Definition: Update PRD.md and README.md to reflect new metrics and CLI enhancements.
- Sequence: After completing metric implementation and CLI enhancements.
- Definition of Done: PRD.md includes detailed descriptions of new metrics, and README.md provides instructions on how to use the enhanced CLI.
## Week 2 starting 2025-03-07: Streamlit GUI Enhancements and Testing
### Milestones
- Streamlit GUI Output: Enhance the Streamlit GUI to display streamed output from Pydantic-AI.
- Comprehensive Testing: Perform thorough testing of the entire system with new metrics and GUI enhancements.
### Tasks and Sequence
- [ ] Enhance Streamlit GUI
- Task Definition: Modify the Streamlit GUI to display the streamed output from Pydantic-AI models.
- Sequence: Start of Week 2.
- Definition of Done: The GUI can display streamed output without errors, and user interactions (e.g., selecting models, inputting queries) work as expected.
- [ ] Integrate New Metrics into GUI
- Task Definition: Ensure the Streamlit GUI can display results from the new metrics.
- Sequence: After enhancing the GUI for streamed output.
- Definition of Done: The GUI displays metric results clearly, and users can easily interpret the output.
- [ ] Comprehensive System Testing
- Task Definition: Perform end-to-end testing of the system, including new metrics and GUI enhancements.
- Sequence: After integrating new metrics into the GUI.
- Definition of Done: All tests pass without errors, and the system functions as expected in various scenarios.
- [ ] Finalize Documentation and Deployment
- Task Definition: Update MkDocs documentation to reflect all changes and deploy it to GitHub Pages.
- Sequence: After completing system testing.
- Definition of Done: Documentation is updated, and the latest version is live on GitHub Pages.
## Additional Considerations
- Code Reviews: Schedule regular code reviews to ensure quality and adherence to project standards.
- Feedback Loop: Establish a feedback loop with stakeholders to gather input on the new metrics and GUI enhancements.
================================================
FILE: docs/UserStory.md
================================================
# User Story for Agents-eval
## Introduction
Agents-eval is designed to evaluate the effectiveness of open-source agentic AI systems across various use cases. This user story focuses on the perspective of Gez, an AI researcher who aims to assess and improve these systems using Agents-eval.
## User Profile
- **Name:** Gez
- **Role:** AI Researcher
- **Goals:**
- Evaluate the performance of agentic AI systems.
- Identify areas for improvement in these systems.
- Develop and integrate new metrics for evaluation.
## User Story
**As** an AI researcher,
**I want** to use Agents-eval to evaluate the effectiveness of agentic AI systems,
**so that** I can assess their performance across different use cases and improve their capabilities.
### Acceptance Criteria
1. **Evaluation Pipeline:**
- The system should provide a comprehensive evaluation pipeline that measures core agentic capabilities such as task decomposition, tool integration, adaptability, and overall performance.
- The pipeline should support multiple agentic AI frameworks (e.g., Pydantic-AI, LangChain).
2. **Metric Development:**
- The system should allow for the development and integration of new metrics that are agnostic to specific use cases.
- These metrics should be modular and easily integratable with existing evaluation logic.
3. **CLI and GUI Interactions:**
- The system should offer both a CLI and a Streamlit GUI for user interaction.
- The CLI should support streaming output from Pydantic-AI models.
- The Streamlit GUI should display streamed output and provide an intuitive interface for setting up and running evaluations.
4. **Documentation and Feedback:**
- The system should include comprehensive documentation for setup, usage, and testing.
- There should be a feedback loop for users to report issues or suggest improvements.
## Example Scenario
- **Scenario:** Gez wants to evaluate a research agent system using Agents-eval.
- **Steps:**
1. She sets up the environment using the CLI or devcontainer.
2. She configures the agent system with the desired models and tools.
3. She runs the evaluation using the CLI or Streamlit GUI.
4. She views the results and metrics displayed by the system.
5. She provides feedback on the system's performance and suggests improvements.
## Benefits
- **Improved Evaluation Capabilities:** Agents-eval provides a structured approach to evaluating agentic AI systems, allowing researchers to focus on improving these systems.
- **Flexibility and Customization:** The system supports multiple frameworks and allows for the development of new metrics, making it adaptable to various research needs.
- **Enhanced User Experience:** The combination of CLI and GUI interfaces offers flexibility in how users interact with the system, catering to different preferences and workflows.
================================================
FILE: docs/architecture/c4-multi-agent-system.plantuml
================================================
@startuml "Multi-Agent Research System - C4 System Context"
!theme plain
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
LAYOUT_WITH_LEGEND()
title "Multi-Agent Research System"
Person(user, "User", "Submits research queries")
System_Boundary(research_system, "Supporting System") {
Container(main_module, "Main Module", "Python", "Entry point that configures and runs the agent system")
Container(utils, "Utilities", "Python", "Helper functions and data models")
Container(config, "Configuration", "JSON", "Provider and model settings")
}
System_Boundary(agent_system, "Agent System") {
Container(manager_agent, "Manager Agent", "pydantic-ai", "Coordinates research and analysis tasks")
Container(research_agent, "Research Agent", "pydantic-ai", "Gathers information on topics")
Container(analysis_agent, "Analysis Agent", "pydantic-ai", "Analyzes research")
Container(synthesiser_agent, "Synthesiser Agent", "pydantic-ai", "Produces scientific reports")
}
System_Ext(llm_provider, "LLM Provider", "External inference service for AI models")
System_Ext(search_api, "DuckDuckGo Search", "External search API")
Rel(user, main_module, "Submits query", "CLI Input or GUI")
Rel(main_module, config, "Loads", "Reads JSON config")
Rel(main_module, agent_system, "Initializes and runs")
Rel(manager_agent, research_agent, "Delegates research tasks to", "Optional Tool call")
Rel(manager_agent, analysis_agent, "Delegates analysis tasks to", "Optional Tool call")
Rel(manager_agent, synthesiser_agent, "Delegates synthesis tasks to", "Optional Tool call")
Rel(research_agent, search_api, "Searches for information", "API call")
Rel(manager_agent, llm_provider, "Generates responses", "API call")
Rel(research_agent, llm_provider, "Generates responses", "API call")
Rel(analysis_agent, llm_provider, "Generates responses", "API call")
Rel(synthesiser_agent, llm_provider, "Generates responses", "API call")
Rel(agent_system, utils, "Uses", "Import")
Rel(main_module, utils, "Uses", "Import")
@enduml
================================================
FILE: docs/architecture/customer-journey-activity-dark
================================================
@startuml
!theme amiga
skinparam monochrome true
title Customer Journey Activity Diagram for CLI and Streamlit
start
:Discover Agents-eval;
if (Choose CLI?) then (yes)
:Run CLI with `make run_cli`;
:Interact with CLI for agent setup and execution;
:View results and metrics in CLI output;
else (no)
:Run Streamlit GUI with `make run_gui`;
:Interact with Streamlit for agent setup and execution;
:View results and metrics in Streamlit dashboard;
endif
:Continue using and provide feedback;
:Improve based on feedback;
stop
@enduml
================================================
FILE: docs/architecture/customer-journey-activity-light.plantuml
================================================
@startuml
!theme plain
title Customer Journey Activity Diagram for CLI and Streamlit
start
:Discover Agents-eval;
if (Choose CLI?) then (yes)
:Run CLI with `make run_cli`;
:Interact with CLI for agent setup and execution;
:View results and metrics in CLI output;
else (no)
:Run Streamlit GUI with `make run_gui`;
:Interact with Streamlit for agent setup and execution;
:View results and metrics in Streamlit dashboard;
endif
:Continue using and provide feedback;
:Improve based on feedback;
stop
@enduml
================================================
FILE: docs/architecture/metrics-eval-sweep.plantuml
================================================
@startuml
!theme plain
skinparam ConditionEndStyle diamond
skinparam ParticipantPadding 20
skinparam BoxPadding 20
participant "Sweep Engine" as SE
participant "Agentic System" as AS
participant "Evaluation Engine" as EE
SE -> EE: Set baseline parameters
group Sweep over parameter variations [Independent runs]
group Vary number of runs [ numbers of runs ]
loop for each run_number
SE -> AS: Start runs
AS -> EE: Execute runs
EE--> SE: Send results
end
end
group Sweep metrics weights [ metrics weights ]
loop for each weight_config
SE -> AS: Set weights and start runs
AS -> EE: Execute runs
EE--> SE: Send results
end
end
end
@enduml
================================================
FILE: src/run_gui.py
================================================
"""
This module sets up and runs a Streamlit application for a Multi-Agent System.
The application includes the following components:
- Header
- Sidebar for configuration options
- Main content area for prompts
- Footer
The main function loads the configuration, renders the UI components, and handles the
execution of the Multi-Agent System based on user input.
Functions:
- run_app(): Placeholder function to run the main application logic.
- main(): Main function to set up and run the Streamlit application.
"""
from asyncio import run
from pathlib import Path
from app.config.config_app import CHAT_CONFIG_FILE, CHAT_DEFAULT_PROVIDER
from app.config.data_models import ChatConfig
from app.utils.load_configs import load_config
from app.utils.log import logger
from gui.components.sidebar import render_sidebar
from gui.config.config import APP_PATH
from gui.config.styling import add_custom_styling
from gui.config.text import PAGE_TITLE
from gui.pages.home import render_home
from gui.pages.prompts import render_prompts
from gui.pages.run_app import render_app
from gui.pages.settings import render_settings
# TODO create sidebar tabs, move settings to page,
# set readme.md as home, separate prompts into page
chat_config_pfile = Path(__file__).parent / APP_PATH / CHAT_CONFIG_FILE
chat_config = load_config(chat_config_pfile, ChatConfig)
provider = CHAT_DEFAULT_PROVIDER
logger.info(f"Default provider: {CHAT_DEFAULT_PROVIDER}")
async def main():
add_custom_styling(PAGE_TITLE)
selected_page = render_sidebar(PAGE_TITLE)
if selected_page == "Home":
render_home()
elif selected_page == "Settings":
# TODO temp save settings to be used in gui
provider = render_settings(chat_config)
logger.info(f"Page 'Settings' provider: {provider}")
elif selected_page == "Prompts":
render_prompts(chat_config)
elif selected_page == "App":
logger.info(f"Page 'App' provider: {CHAT_DEFAULT_PROVIDER}")
await render_app(CHAT_DEFAULT_PROVIDER)
if __name__ == "__main__":
run(main())
================================================
FILE: src/app/__init__.py
================================================
"""Defines the application version."""
__version__ = "2.0.0"
================================================
FILE: src/app/main.py
================================================
"""
Main entry point for the Agents-eval application.
This module initializes the agentic system, loads configuration files,
handles user input, and orchestrates the multi-agent workflow using
asynchronous execution. It integrates logging, tracing, and authentication,
and supports both CLI and programmatic execution.
"""
from asyncio import run
from pathlib import Path
from sys import argv
from logfire import span
from weave import op
from app.__init__ import __version__
from app.agents.agent_system import get_manager, run_manager, setup_agent_env
from app.config.config_app import (
CHAT_CONFIG_FILE,
CHAT_DEFAULT_PROVIDER,
EVAL_CONFIG_FILE,
PROJECT_NAME,
)
from app.config.data_models import AppEnv, ChatConfig, EvalConfig
from app.utils.error_messages import generic_exception
from app.utils.load_configs import load_config
from app.utils.log import logger
from app.utils.login import login
from app.utils.utils import parse_args
@op()
async def main(
chat_provider: str = CHAT_DEFAULT_PROVIDER,
query: str = "",
include_researcher: bool = False,
include_analyst: bool = False,
include_synthesiser: bool = False,
pydantic_ai_stream: bool = False,
chat_config_file: str = CHAT_CONFIG_FILE,
) -> None:
"""
Main entry point for the application.
Args:
chat_provider (str): The inference chat_provider to be used.
query (str): The query to be processed by the agent.
include_researcher (bool): Whether to include the researcher in the process.
include_analyst (bool): Whether to include the analyst in the process.
include_synthesiser (bool): Whether to include the synthesiser in the process.
pydantic_ai_stream (bool): Whether to use Pydantic AI streaming.
chat_config_file (str): Full path to the configuration file.
Returns:
None
"""
logger.info(f"Starting app '{PROJECT_NAME}' v{__version__}")
try:
with span("main()"):
if not chat_provider:
chat_provider = input("Which inference chat_provider to use? ")
if not query:
query = input("What would you like to research? ")
chat_config_path = Path(__file__).parent / CHAT_CONFIG_FILE
eval_config_path = Path(__file__).parent / EVAL_CONFIG_FILE
chat_config = load_config(chat_config_path, ChatConfig)
eval_config = load_config(eval_config_path, EvalConfig)
chat_env_config = AppEnv()
agent_env = setup_agent_env(
chat_provider, query, chat_config, chat_env_config
)
# TODO remove noqa and type ignore for unused variable
metrics_and_weights = eval_config.metrics_and_weights # noqa: F841 # type: ignore[reportUnusedVariable]
# FIXME enhance login, not every run?
login(PROJECT_NAME, chat_env_config)
manager = get_manager(
agent_env.provider,
agent_env.provider_config,
agent_env.api_key,
agent_env.prompts,
include_researcher,
include_analyst,
include_synthesiser,
)
await run_manager(
manager,
agent_env.query,
agent_env.provider,
agent_env.usage_limits,
pydantic_ai_stream,
)
logger.info(f"Exiting app '{PROJECT_NAME}'")
except Exception as e:
msg = generic_exception(f"Aborting app '{PROJECT_NAME}' with: {e}")
logger.exception(msg)
raise Exception(msg) from e
if __name__ == "__main__":
args = parse_args(argv[1:])
run(main(**args))
================================================
FILE: src/app/py.typed
================================================
# PEP 561 – Distributing and Packaging Type Information
# https://peps.python.org/pep-0561/
================================================
FILE: src/app/agents/__init__.py
================================================
================================================
FILE: src/app/agents/agent_system.py
================================================
"""
Agent system utilities for orchestrating multi-agent workflows.
This module provides functions and helpers to create, configure, and run agent
systems using Pydantic AI. It supports delegation of tasks to research, analysis, and
synthesis agents, and manages agent configuration, environment setup, and execution.
Args:
provider (str): The name of the provider. provider_config (ProviderConfig):
Configuration settings for the provider.
api_key (str): API key for authentication with the provider.
prompts (dict[str, str]): Configuration for prompts.
include_researcher (bool): Flag to include the researcher agent.
include_analyst (bool): Flag to include the analyst agent.
include_synthesiser (bool): Flag to include the synthesiser agent.
query (str | list[dict[str, str]]): The query or messages for the agent.
chat_config (ChatConfig): The configuration object for agents and providers.
usage_limits (UsageLimits): Usage limits for agent execution.
pydantic_ai_stream (bool): Whether to use Pydantic AI streaming.
Functions:
get_manager: Initializes and returns a manager agent with the specified
configuration.
run_manager: Asynchronously runs the manager agent with the given query and
provider.
setup_agent_env: Sets up the environment for an agent by configuring provider
settings, prompts, API key, and usage limits.
"""
from pydantic import BaseModel, ValidationError
from pydantic_ai import Agent, RunContext
from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool
from pydantic_ai.messages import ModelRequest
from pydantic_ai.usage import UsageLimits
from app.agents.llm_model_funs import get_api_key, get_models, get_provider_config
from app.config.data_models import (
AgentConfig,
AnalysisResult,
AppEnv,
ChatConfig,
EndpointConfig,
ModelDict,
ProviderConfig,
ResearchResult,
ResearchSummary,
ResultBaseType,
UserPromptType,
)
from app.utils.error_messages import generic_exception, invalid_data_model_format
from app.utils.log import logger
def _add_tools_to_manager_agent(
manager_agent: Agent[None, BaseModel],
research_agent: Agent[None, BaseModel] | None = None,
analysis_agent: Agent[None, BaseModel] | None = None,
synthesis_agent: Agent[None, BaseModel] | None = None,
):
"""
Adds tools to the manager agent for delegating tasks to research, analysis, and
synthesis agents.
Args:
manager_agent (Agent): The manager agent to which tools will be added.
research_agent (Agent): The agent responsible for handling research tasks.
analysis_agent (Agent, optional): The agent responsible for handling
analysis tasks. Defaults to None.
synthesis_agent (Agent, optional): The agent responsible for handling
synthesis tasks. Defaults to None.
Returns:
None
"""
def _validate_model_return(
result_output: str,
result_model: type[ResultBaseType],
) -> ResultBaseType:
"""Validates the output against the expected model."""
try:
return result_model.model_validate(result_output)
except ValidationError as e:
msg = invalid_data_model_format(str(e))
logger.error(msg)
raise ValidationError(msg)
except Exception as e:
msg = generic_exception(str(e))
logger.exception(msg)
raise Exception(msg)
if research_agent is not None:
@manager_agent.tool
# TODO remove redundant tool creation
# ignore "delegate_research" is not accessed because of decorator
async def delegate_research( # type: ignore[reportUnusedFunction]
ctx: RunContext[None], query: str
) -> ResearchResult:
"""Delegate research task to ResearchAgent."""
result = await research_agent.run(query, usage=ctx.usage)
return _validate_model_return(str(result.output), ResearchResult)
if analysis_agent is not None:
@manager_agent.tool
# ignore "delegate_research" is not accessed because of decorator
async def delegate_analysis( # type: ignore[reportUnusedFunction]
ctx: RunContext[None], query: str
) -> AnalysisResult:
"""Delegate analysis task to AnalysisAgent."""
result = await analysis_agent.run(query, usage=ctx.usage)
return _validate_model_return(str(result.output), AnalysisResult)
if synthesis_agent is not None:
@manager_agent.tool
# ignore "delegate_research" is not accessed because of decorator
async def delegate_synthesis( # type: ignore[reportUnusedFunction]
ctx: RunContext[None], query: str
) -> ResearchSummary:
"""Delegate synthesis task to AnalysisAgent."""
result = await synthesis_agent.run(query, usage=ctx.usage)
return _validate_model_return(str(result.output), ResearchSummary)
def _create_agent(agent_config: AgentConfig) -> Agent[None, BaseModel]:
"""Factory for creating configured agents"""
return Agent(
model=agent_config.model,
output_type=agent_config.output_type,
system_prompt=agent_config.system_prompt,
tools=agent_config.tools,
retries=agent_config.retries,
)
def _create_manager(
prompts: dict[str, str],
models: ModelDict,
) -> Agent[None, BaseModel]:
"""
Creates and configures a manager Agent with associated researcher, analyst,
and optionally synthesiser agents.
Args:
prompts (Dict[str, str]): Dictionary containing system prompts for each agent.
model_manager (GeminiModel | OpenAIModel): Model to be used by the manager
agent.
model_researcher (GeminiModel | OpenAIModel | None, optional): Model to be used
by the researcher agent.
model_analyst (GeminiModel | OpenAIModel | None, optional): Model to be used by
the analyst agent. Defaults to None.
model_synthesiser (GeminiModel | OpenAIModel | None, optional): Model to be used
by the synthesiser agent. Defaults to None.
Returns:
Agent: Configured manager agent with associated tools and agents.
"""
status = f"Creating manager({models.model_manager.model_name})"
active_agents = [
agent
for agent in [
f"researcher({models.model_researcher.model_name})"
if models.model_researcher
else None,
f"analyst({models.model_analyst.model_name})"
if models.model_analyst
else None,
f"synthesiser({models.model_synthesiser.model_name})"
if models.model_synthesiser
else None,
]
if agent
]
status += f" with agents: {', '.join(active_agents)}" if active_agents else ""
logger.info(status)
manager = _create_agent(
AgentConfig.model_validate(
{
"model": models.model_manager,
"output_type": ResearchResult,
"system_prompt": prompts["system_prompt_manager"],
}
)
)
if models.model_researcher is None:
researcher = None
else:
researcher = _create_agent(
AgentConfig.model_validate(
{
"model": models.model_researcher,
"output_type": ResearchResult,
"system_prompt": prompts["system_prompt_researcher"],
"tools": [duckduckgo_search_tool()],
}
)
)
if models.model_analyst is None:
analyst = None
else:
analyst = _create_agent(
AgentConfig.model_validate(
{
"model": models.model_analyst,
"output_type": AnalysisResult,
"system_prompt": prompts["system_prompt_analyst"],
}
)
)
if models.model_synthesiser is None:
synthesiser = None
else:
synthesiser = _create_agent(
AgentConfig.model_validate(
{
"model": models.model_synthesiser,
"output_type": AnalysisResult,
"system_prompt": prompts["system_prompt_synthesiser"],
}
)
)
_add_tools_to_manager_agent(manager, researcher, analyst, synthesiser)
return manager
def get_manager(
provider: str,
provider_config: ProviderConfig,
api_key: str | None,
prompts: dict[str, str],
include_researcher: bool = False,
include_analyst: bool = False,
include_synthesiser: bool = False,
) -> Agent[None, BaseModel]:
"""
Initializes and returns a Agent manager with the specified configuration.
Args:
provider (str): The name of the provider.
provider_config (ProviderConfig): Configuration settings for the provider.
api_key (str): API key for authentication with the provider.
prompts (PromptsConfig): Configuration for prompts.
include_researcher (bool, optional): Flag to include analyst model.
Defaults to False.
include_analyst (bool, optional): Flag to include analyst model.
Defaults to False.
include_synthesiser (bool, optional): Flag to include synthesiser model.
Defaults to False.
Returns:
Agent: The initialized Agent manager.
"""
# FIXME context manager try-catch
# with error_handling_context("get_manager()"):
model_config = EndpointConfig.model_validate(
{
"provider": provider,
"prompts": prompts,
"api_key": api_key,
"provider_config": provider_config,
}
)
models = get_models(
model_config, include_researcher, include_analyst, include_synthesiser
)
return _create_manager(prompts, models)
async def run_manager(
manager: Agent[None, BaseModel],
query: UserPromptType,
provider: str,
usage_limits: UsageLimits | None,
pydantic_ai_stream: bool = False,
) -> None:
"""
Asynchronously runs the manager with the given query and provider, handling errors
and printing results.
Args:
manager (Agent): The system agent responsible for running the query.
query (str): The query to be processed by the manager.
provider (str): The provider to be used for the query.
usage_limits (UsageLimits): The usage limits to be applied during the query
execution.
pydantic_ai_stream (bool, optional): Flag to enable or disable Pydantic AI
stream. Defaults to False.
Returns:
None
"""
# FIXME context manager try-catch
# with out ? error_handling_context("run_manager()"):
model_name = getattr(manager, "model")._model_name
mgr_cfg = {"user_prompt": query, "usage_limits": usage_limits}
logger.info(f"Researching with {provider}({model_name}) and Topic: {query} ...")
if pydantic_ai_stream:
raise NotImplementedError(
"Streaming currently only possible for Agents with "
"output_type str not pydantic model"
)
# logger.info("Streaming model response ...")
# result = await manager.run(**mgr_cfg)
# aync for chunk in result.stream_text(): # .run(**mgr_cfg) as result:
# async with manager.run_stream(user_prompt=query) as stream:
# async for chunk in stream.stream_text():
# logger.info(str(chunk))
# result = await stream.get_result()
else:
logger.info("Waiting for model response ...")
# FIXME deprecated warning manager.run(), query unknown type
# FIXME [call-overload] error: No overload variant of "run" of "Agent"
# matches argument type "dict[str, list[dict[str, str]] |
# Sequence[str | ImageUrl | AudioUrl | DocumentUrl | VideoUrl |
# BinaryContent] | UsageLimits | None]"
result = await manager.run(**mgr_cfg) # type: ignore[reportDeprecated,reportUnknownArgumentType,reportCallOverload,call-overload]
logger.info(f"Result: {result}")
logger.info(f"Usage statistics: {result.usage()}")
def setup_agent_env(
provider: str,
query: UserPromptType,
chat_config: ChatConfig | BaseModel,
chat_env_config: AppEnv,
) -> EndpointConfig:
"""
Sets up the environment for an agent by configuring provider settings, prompts,
API key, and usage limits.
Args:
provider (str): The name of the provider.
query (UserPromptType): The messages or queries to be sent to the agent.
chat_config (ChatConfig | BaseModel): The configuration object containing
provider and prompt settings.
chat_env_config (AppEnv): The application environment configuration
containing API keys.
Returns:
EndpointConfig: The configuration object for the agent.
"""
if not isinstance(chat_config, ChatConfig):
raise TypeError("'chat_config' of invalid type: ChatConfig expected")
msg: str | None
# FIXME context manager try-catch
# with error_handling_context("setup_agent_env()"):
provider_config = get_provider_config(provider, chat_config.providers)
prompts = chat_config.prompts
api_key = get_api_key(provider, chat_env_config)
if provider.lower() == "ollama":
# TODO move usage limits to config
usage_limits = UsageLimits(request_limit=10, total_tokens_limit=100000)
else:
if api_key is None:
msg = f"API key for provider '{provider}' is not set."
logger.error(msg)
raise ValueError(msg)
# TODO Separate Gemini request into function
if provider.lower() == "gemini":
if isinstance(query, str):
query = ModelRequest.user_text_prompt(query)
elif isinstance(query, list): # type: ignore[reportUnnecessaryIsInstance]
# query = [
# ModelRequest.user_text_prompt(
# str(msg.get("content", ""))
# ) # type: ignore[reportUnknownArgumentType]
# if isinstance(msg, dict)
# else msg
# for msg in query
# ]
raise NotImplementedError("Currently conflicting with UserPromptType")
else:
msg = f"Unsupported query type for Gemini: {type(query)}"
logger.error(msg)
raise TypeError(msg)
# TODO move usage limits to config
usage_limits = UsageLimits(request_limit=10, total_tokens_limit=10000)
return EndpointConfig.model_validate(
{
"provider": provider,
"query": query,
"api_key": api_key,
"prompts": prompts,
"provider_config": provider_config,
"usage_limits": usage_limits,
}
)
================================================
FILE: src/app/agents/llm_model_funs.py
================================================
"""
Utility functions and classes for managing and instantiating LLM models and providers.
This module provides functions to retrieve API keys, provider configurations, and
to create model instances for supported LLM providers such as Gemini and OpenAI.
It also includes logic for assembling model dictionaries for system agents.
"""
from pydantic import HttpUrl
from pydantic_ai.models.gemini import GeminiModel
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider
from app.config.config_app import API_SUFFIX
from app.config.data_models import AppEnv, EndpointConfig, ModelDict, ProviderConfig
from app.utils.error_messages import generic_exception, get_key_error
from app.utils.log import logger
def get_api_key(
provider: str,
chat_env_config: AppEnv,
) -> str | None:
"""Retrieve API key from chat env config variable."""
provider = provider.upper()
if provider == "OLLAMA":
return None
else:
key_name = f"{provider}{API_SUFFIX}"
if hasattr(chat_env_config, key_name):
logger.info(f"Found API key for provider '{provider}'")
return getattr(chat_env_config, key_name)
else:
raise KeyError(
f"API key for provider '{provider}' not found in configuration."
)
def get_provider_config(
provider: str, providers: dict[str, ProviderConfig]
) -> dict[str, str | HttpUrl]:
"""Retrieve configuration settings for the specified provider."""
try:
model_name = providers[provider].model_name
base_url = providers[provider].base_url
except KeyError as e:
msg = get_key_error(str(e))
logger.error(msg)
raise KeyError(msg)
except Exception as e:
msg = generic_exception(str(e))
logger.exception(msg)
raise Exception(msg)
else:
return {
"model_name": model_name,
"base_url": base_url,
}
def _create_model(endpoint_config: EndpointConfig) -> GeminiModel | OpenAIModel:
"""Create a model that uses model_name and base_url for inference API"""
if endpoint_config.provider.lower() == "gemini":
# FIXME EndpointConfig: TypeError: 'ModelRequest' object is not iterable.
raise NotImplementedError(
"Current typing raises TypeError: 'ModelRequest' object is not iterable."
)
elif endpoint_config.provider.lower() == "huggingface":
# FIXME HF not working with pydantic-ai OpenAI model
raise NotImplementedError(
"Hugging Face provider is not implemented yet. Please use Gemini or OpenAI."
" https://huggingface.co/docs/inference-providers/providers/hf-inference"
)
# headers = {
# "Authorization": f"Bearer {endpoint_config.api_key}",
# }
# def query(payload):
# response = requests.post(API_URL, headers=headers, json=payload)
# return response.json()
# query({"inputs": "", "parameters": {},})
else:
base_url_str = str(endpoint_config.provider_config.base_url)
return OpenAIModel(
model_name=endpoint_config.provider_config.model_name,
provider=OpenAIProvider(
base_url=base_url_str,
api_key=endpoint_config.api_key,
),
)
def get_models(
endpoint_config: EndpointConfig,
include_researcher: bool = False,
include_analyst: bool = False,
include_synthesiser: bool = False,
) -> ModelDict:
"""
Get the models for the system agents.
Args:
endpoint_config (EndpointConfig): Configuration for the model.
include_analyist (Optional[bool]): Whether to include the analyst model.
Defaults to False.
include_synthesiser (Optional[bool]): Whether to include the synthesiser model.
Defaults to False.
Returns:
Dict[str, GeminiModel | OpenAIModel]: A dictionary containing the models for the
system agents.
"""
model = _create_model(endpoint_config)
return ModelDict.model_validate(
{
"model_manager": model,
"model_researcher": model if include_researcher else None,
"model_analyst": model if include_analyst else None,
"model_synthesiser": model if include_synthesiser else None,
}
)
================================================
FILE: src/app/config/__init__.py
================================================
================================================
FILE: src/app/config/config_app.py
================================================
"""Configuration constants for the application."""
# MARK: chat env
API_SUFFIX = "_API_KEY"
CHAT_DEFAULT_PROVIDER = "github"
# MARK: project
PROJECT_NAME = "rd-mas-example"
# MARK: paths
CHAT_CONFIG_FILE = "config/config_chat.json"
LOGS_PATH = "logs"
EVAL_CONFIG_FILE = "config/config_eval.json"
================================================
FILE: src/app/config/config_chat.json
================================================
{
"providers": {
"huggingface": {
"model_name": "facebook/bart-large-mnli",
"base_url": "https://router.huggingface.co/hf-inference/models"
},
"gemini": {
"model_name": "gemini-1.5-flash-8b",
"base_url": "https://generativelanguage.googleapis.com/v1beta"
},
"github": {
"model_name": "GPT-4o",
"base_url": "https://models.inference.ai.azure.com"
},
"grok": {
"model_name": "grok-2-1212",
"base_url": "https://api.x.ai/v1"
},
"ollama": {
"model_name": "granite3-dense",
"base_url": "http://localhost:11434/v1"
},
"openrouter": {
"model_name": "google/gemini-2.0-flash-exp:free",
"base_url": "https://openrouter.ai/api/v1"
},
"perplexity": {
"model_name": "sonar",
"base_url": "https://api.perplexity.ai"
},
"restack": {
"model_name": "deepseek-chat",
"base_url": "https://ai.restack.io"
},
"together": {
"model_name": "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free",
"base_url": "https://api.together.xyz/v1"
}
},
"inference": {
"usage_limits": 10000,
"usage_limits_ollama": 10000,
"result_retries": 3,
"result_retries_ollama": 3
},
"prompts": {
"system_prompt_manager": "You are a manager overseeing research and analysis tasks. Your role is to coordinate the efforts of the research, analysis and synthesiser agents to provide comprehensive answers to user queries. The researcher should gather and analyze data relevant to the topic. The whole result must be handed to the analyst, who will check it for accuracy of the assumptions, facts, and conclusions. If an analyst is present the researchers output has to be approved by the analyst. If the analyst does not approve of the researcher's result, all of the analyst's response and the topic must be handed back to the researcher to be refined. Repeat this loop until the analyst approves. If a sysnthesiser is present and once the analyst approves, the synthesiser should output a well formatted scientific report using the data given.",
"system_prompt_researcher": "You are a researcher. Gather and analyze data relevant to the topic. Use the search tool to gather data. Always check accuracy of assumptions, facts, and conclusions.",
"system_prompt_analyst": "You are a research analyst. Use your analytical skills to check the accuracy of assumptions, facts, and conclusions in the data provided. Provide relevant feedback if you do not approve. Only approve if you do not have any feedback to give.",
"system_prompt_synthesiser": "You are a scientific writing assistant. Your task is to output a well formatted scientific report using the data given. Leave the privided facts, conclusions and sources unchanged."
}
}
================================================
FILE: src/app/config/config_eval.json
================================================
{
"metrics_and_weights": {
"time_taken": 0.167,
"task_success": 0.167,
"coordination_quality": 0.167,
"tool_efficiency": 0.167,
"planning_rational": 0.167,
"output_similarity": 0.167
}
}
================================================
FILE: src/app/config/data_models.py
================================================
"""
Data models for agent system configuration and results.
This module defines Pydantic models for representing research and analysis results,
summaries, provider and agent configurations, and model dictionaries used throughout
the application. These models ensure type safety and validation for data exchanged
between agents and system components.
"""
from typing import Any, TypeVar
from pydantic import BaseModel, ConfigDict, HttpUrl, field_validator
from pydantic_ai.messages import ModelRequest
from pydantic_ai.models import Model
from pydantic_ai.tools import Tool
from pydantic_ai.usage import UsageLimits
from pydantic_settings import BaseSettings, SettingsConfigDict
type UserPromptType = (
str | list[dict[str, str]] | ModelRequest | None
) # (1) Input validation
ResultBaseType = TypeVar(
"ResultBaseType", bound=BaseModel
) # (2) Generic type for model results
class ResearchResult(BaseModel):
"""Research results from the research agent."""
topic: str | dict[str, str]
findings: list[str] | dict[str, str | list[str]]
sources: list[str] | dict[str, str | list[str]]
class AnalysisResult(BaseModel):
"""Analysis results from the analysis agent."""
insights: list[str]
recommendations: list[str]
approval: bool
class ResearchSummary(BaseModel):
"""Expected model response of research on a topic"""
topic: str
key_points: list[str]
key_points_explanation: list[str]
conclusion: str
sources: list[str]
class ProviderConfig(BaseModel):
"""Configuration for a model provider"""
model_name: str
base_url: HttpUrl
class ChatConfig(BaseModel):
"""Configuration settings for agents and model providers"""
providers: dict[str, ProviderConfig]
inference: dict[str, str | int]
prompts: dict[str, str]
class EndpointConfig(BaseModel):
"""Configuration for an agent"""
provider: str
query: UserPromptType = None
api_key: str | None
prompts: dict[str, str]
provider_config: ProviderConfig
usage_limits: UsageLimits | None = None
class AgentConfig(BaseModel):
"""Configuration for an agent"""
model: Model # (1) Instance expected
output_type: type[BaseModel] # (2) Class expected
system_prompt: str
# FIXME tools: list[Callable[..., Awaitable[Any]]]
tools: list[Any] = [] # (3) List of tools will be validated at creation
retries: int = 3
# Avoid pydantic.errors.PydanticSchemaGenerationError:
# Unable to generate pydantic-core schema for .
# Avoid Pydantic errors related to non-Pydantic types
model_config = ConfigDict(
arbitrary_types_allowed=True
) # (4) Suppress Error non-Pydantic types caused by
@field_validator("tools", mode="before")
def validate_tools(cls, v: list[Any]) -> list[Tool | None]:
"""Validate that all tools are instances of Tool."""
if not v:
return []
if not all(isinstance(t, Tool) for t in v):
raise ValueError("All tools must be Tool instances")
return v
class ModelDict(BaseModel):
"""Dictionary of models used to create agent systems"""
model_manager: Model
model_researcher: Model | None
model_analyst: Model | None
model_synthesiser: Model | None
model_config = ConfigDict(arbitrary_types_allowed=True)
class EvalConfig(BaseModel):
metrics_and_weights: dict[str, float]
class AppEnv(BaseSettings):
"""
Application environment settings loaded from environment variables or .env file.
This class uses Pydantic's BaseSettings to manage API keys and configuration
for various inference endpoints, tools, and logging/monitoring services.
Environment variables are loaded from a .env file by default.
"""
# Inference endpoints
GEMINI_API_KEY: str = ""
GITHUB_API_KEY: str = ""
GROK_API_KEY: str = ""
HUGGINGFACE_API_KEY: str = ""
OPENROUTER_API_KEY: str = ""
PERPLEXITY_API_KEY: str = ""
RESTACK_API_KEY: str = ""
TOGETHER_API_KEY: str = ""
# Tools
TAVILY_API_KEY: str = ""
# Logging/Monitoring/Tracing
AGENTOPS_API_KEY: str = ""
LOGFIRE_API_KEY: str = ""
WANDB_API_KEY: str = ""
model_config = SettingsConfigDict(
env_file=".env", env_file_encoding="utf-8", extra="ignore"
)
================================================
FILE: src/app/evals/__init__.py
================================================
================================================
FILE: src/app/evals/metrics.py
================================================
def time_taken(start_time: float, end_time: float) -> float:
"""Calculate duration between start and end timestamps
Args:
start_time: Timestamp when execution started
end_time: Timestamp when execution completed
Returns:
Duration in seconds with microsecond precision
"""
# TODO implement
return end_time - start_time
def output_similarity(agent_output: str, expected_answer: str) -> bool:
"""
Determine to what degree the agent's output matches the expected answer.
Args:
agent_output (str): The output produced by the agent.
expected_answer (str): The correct or expected answer.
Returns:
bool: True if the output matches the expected answer, False otherwise.
"""
# TODO score instead of bool
return agent_output.strip() == expected_answer.strip()
================================================
FILE: src/app/utils/__init__.py
================================================
================================================
FILE: src/app/utils/error_messages.py
================================================
"""
Error message utilities for the Agents-eval application.
This module provides concise helper functions for generating standardized
error messages related to configuration loading and validation.
"""
from pathlib import Path
def api_connection_error(error: str) -> str:
"""
Generate a error message for API connection error.
"""
return f"API connection error: {error}"
def failed_to_load_config(error: str) -> str:
"""
Generate a error message for configuration loading failure.
"""
return f"Failed to load config: {error}"
def file_not_found(file_path: str | Path) -> str:
"""
Generate an error message for a missing configuration file.
"""
return f"File not found: {file_path}"
def generic_exception(error: str) -> str:
"""
Generate a generic error message.
"""
return f"Exception: {error}"
def invalid_data_model_format(error: str) -> str:
"""
Generate an error message for invalid pydantic data model format.
"""
return f"Invalid pydantic data model format: {error}"
def invalid_json(error: str) -> str:
"""
Generate an error message for invalid JSON in a configuration file.
"""
return f"Invalid JSON: {error}"
def invalid_type(expected_type: str, actual_type: str) -> str:
"""
Generate an error message for invalid Type.
"""
return f"Type Error: Expected {expected_type}, got {actual_type} instead."
def get_key_error(error: str) -> str:
"""
Generate a generic error message.
"""
return f"Key Error: {error}"
================================================
FILE: src/app/utils/load_configs.py
================================================
"""
Configuration loading utilities.
Provides a generic function for loading and validating JSON configuration
files against Pydantic models, with error handling and logging support.
"""
import json
from pathlib import Path
from pydantic import BaseModel, ValidationError
from app.utils.error_messages import (
failed_to_load_config,
file_not_found,
invalid_data_model_format,
invalid_json,
)
from app.utils.log import logger
def load_config(config_path: str | Path, data_model: type[BaseModel]) -> BaseModel:
"""
Generic configuration loader that validates against any Pydantic model.
Args:
config_path: Path to the JSON configuration file
model: Pydantic model class for validation
Returns:
Validated configuration instance
"""
try:
with open(config_path, encoding="utf-8") as f:
data = json.load(f)
return data_model.model_validate(data)
except FileNotFoundError as e:
msg = file_not_found(config_path)
logger.error(msg)
raise FileNotFoundError(msg) from e
except json.JSONDecodeError as e:
msg = invalid_json(str(e))
logger.error(msg)
raise ValueError(msg) from e
except ValidationError as e:
msg = invalid_data_model_format(str(e))
logger.error(msg)
raise ValidationError(msg) from e
except Exception as e:
msg = failed_to_load_config(str(e))
logger.exception(msg)
raise Exception(msg) from e
================================================
FILE: src/app/utils/load_settings.py
================================================
"""
Utility functions and classes for loading application settings and configuration.
This module defines the AppEnv class for managing environment variables using Pydantic,
and provides a function to load and validate application configuration from a JSON file.
"""
import json
from pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict
from app.config.data_models import ChatConfig
from app.utils.error_messages import (
failed_to_load_config,
file_not_found,
invalid_json,
)
from app.utils.log import logger
class AppEnv(BaseSettings):
"""
Application environment settings loaded from environment variables or .env file.
This class uses Pydantic's BaseSettings to manage API keys and configuration
for various inference endpoints, tools, and logging/monitoring services.
Environment variables are loaded from a .env file by default.
"""
# Inference endpoints
GEMINI_API_KEY: str = ""
GITHUB_API_KEY: str = ""
GROK_API_KEY: str = ""
HUGGINGFACE_API_KEY: str = ""
OPENROUTER_API_KEY: str = ""
PERPLEXITY_API_KEY: str = ""
RESTACK_API_KEY: str = ""
TOGETHER_API_KEY: str = ""
# Tools
TAVILY_API_KEY: str = ""
# Logging/Monitoring/Tracing
AGENTOPS_API_KEY: str = ""
LOGFIRE_TOKEN: str = ""
WANDB_API_KEY: str = ""
model_config = SettingsConfigDict(
env_file=".env", env_file_encoding="utf-8", extra="ignore"
)
chat_config = AppEnv()
def load_config(config_path: str | Path) -> ChatConfig:
"""
Load and validate application configuration from a JSON file.
Args:
config_path (str): Path to the JSON configuration file.
Returns:
ChatConfig: An instance of ChatConfig with validated configuration data.
Raises:
FileNotFoundError: If the configuration file does not exist.
json.JSONDecodeError: If the file contains invalid JSON.
Exception: For any other unexpected errors during loading or validation.
"""
try:
with open(config_path) as f:
config_data = json.load(f)
except FileNotFoundError as e:
msg = file_not_found(config_path)
logger.error(msg)
raise FileNotFoundError(msg) from e
except json.JSONDecodeError as e:
msg = invalid_json(str(e))
logger.error(msg)
raise json.JSONDecodeError(msg, str(config_path), 0) from e
except Exception as e:
msg = failed_to_load_config(str(e))
logger.exception(msg)
raise Exception(msg) from e
return ChatConfig.model_validate(config_data)
================================================
FILE: src/app/utils/log.py
================================================
"""
Set up the logger with custom settings.
Logs are written to a file with automatic rotation.
"""
from loguru import logger
from app.config.config_app import LOGS_PATH
logger.add(
f"{LOGS_PATH}/{{time}}.log",
rotation="1 MB",
# level="DEBUG",
retention="7 days",
compression="zip",
)
================================================
FILE: src/app/utils/login.py
================================================
"""
This module provides utility functions for managing login state and initializing
the environment for a given project. It includes functionality to load and save
login state, perform a one-time login, and check if the user is logged in.
"""
from os import environ
from agentops import init as agentops_init
from logfire import configure as logfire_conf
from wandb import login as wandb_login
from weave import init as weave_init
from app.agents.llm_model_funs import get_api_key
from app.config.data_models import AppEnv
from app.utils.error_messages import generic_exception
from app.utils.log import logger
def login(project_name: str, chat_env_config: AppEnv):
"""
Logs in to the workspace and initializes the environment for the given project.
Args:
project_name (str): The name of the project to initialize.
chat_env_config (AppEnv): The application environment configuration
containing the API keys.
Returns:
None
"""
try:
logger.info(f"Logging in to the workspaces for project: {project_name}")
environ["AGENTOPS_LOGGING_TO_FILE"] = "FALSE"
agentops_init(
default_tags=[project_name],
api_key=get_api_key("AGENTOPS", chat_env_config),
)
logfire_conf(token=get_api_key("LOGFIRE", chat_env_config))
wandb_login(key=get_api_key("WANDB", chat_env_config))
weave_init(project_name)
except Exception as e:
msg = generic_exception(str(e))
logger.exception(e)
raise Exception(msg) from e
================================================
FILE: src/app/utils/utils.py
================================================
"""
This module provides utility functions and context managers for handling configurations,
error handling, and setting up agent environments.
Functions:
load_config(config_path: str) -> Config:
Load and validate configuration from a JSON file.
print_research_Result(summary: Dict, usage: Usage) -> None:
Output structured summary of the research topic.
error_handling_context(operation_name: str, console: Console = None):
Context manager for handling errors during operations.
setup_agent_env(config: Config, console: Console = None) -> AgentConfig:
Set up the agent environment based on the provided configuration.
"""
from pydantic_ai.usage import Usage
from app.config.data_models import ResearchSummary
from app.utils.log import logger
def log_research_result(summary: ResearchSummary, usage: Usage) -> None:
"""
Prints the research summary and usage details in a formatted manner.
Args:
summary (Dict): A dictionary containing the research summary with keys 'topic',
'key_points', 'key_points_explanation', and 'conclusion'.
usage (Usage): An object containing usage details to be printed.
"""
logger.info(f"\n=== Research Summary: {summary.topic} ===")
logger.info("\nKey Points:")
for i, point in enumerate(summary.key_points, 1):
logger.info(f"{i}. {point}")
logger.info("\nKey Points Explanation:")
for i, point in enumerate(summary.key_points_explanation, 1):
logger.info(f"{i}. {point}")
logger.info(f"\nConclusion: {summary.conclusion}")
logger.info(f"\nResponse structure: {list(dict(summary).keys())}")
logger.info(usage)
def parse_args(argv: list[str]) -> dict[str, str | bool]:
"""
Parse command line arguments into a dictionary.
This function processes a list of command-line arguments,
extracting recognized options and their values.
Supported arguments include flags (e.g., --help, --include-researcher
and key-value pairs (e.g., `--chat-provider=ollama`).
If the `--help` flag is present, a list of available commands and their
descriptions is printed, and an empty dictionary is returned.
Recognized arguments as list[str]
```
--help Display help information and exit.
--version Display version information.
--chat-provider= Specify the chat provider to use.
--query= Specify the query to process.
--include-researcher Include the researcher agent.
--include-analyst Include the analyst agent.
--include-synthesiser Include the synthesiser agent.
--no-stream Disable streaming output.
--chat-config-file= Specify the path to the chat configuration file.
```
Returns:
`dict[str, str | bool]`: A dictionary mapping argument names
(with leading '--' removed and hyphens replaced by underscores)
to their values (`str` for key-value pairs, `bool` for flags).
Returns an empty dict if `--help` is specified.
Example:
>>> `parse_args(['--chat-provider=ollama', '--include-researcher'])`
returns `{'chat_provider': 'ollama', 'include_researcher': True}`
"""
commands = {
"--help": "Display help information",
"--version": "Display version information",
"--chat-provider": "Specify the chat provider to use",
"--query": "Specify the query to process",
"--include-researcher": "Include the researcher agent",
"--include-analyst": "Include the analyst agent",
"--include-synthesiser": "Include the synthesiser agent",
"--no-stream": "Disable streaming output",
"--chat-config-file": "Specify the path to the chat configuration file",
}
parsed_args: dict[str, str | bool] = {}
if "--help" in argv:
print("Available commands:")
for cmd, desc in commands.items():
print(f"{cmd}: {desc}")
return parsed_args
for arg in argv:
if arg.split("=", 1)[0] in commands.keys():
key, value = arg.split("=", 1) if "=" in arg else (arg, True)
key = key.lstrip("--").replace("-", "_")
parsed_args[key] = value
if parsed_args:
logger.info(f"Used arguments: {parsed_args}")
return parsed_args
================================================
FILE: src/examples/config.json
================================================
{
"providers": {
"gemini": {
"model_name": "gemini-1.5-flash-8b",
"base_url": "https://generativelanguage.googleapis.com/v1beta"
},
"github": {
"model_name": "GPT-4o",
"base_url": "https://models.inference.ai.azure.com"
},
"huggingface": {
"model_name": "Qwen/QwQ-32B-Preview",
"base_url": "https://api-inference.huggingface.co/v1"
},
"ollama": {
"model_name": "granite3-dense",
"base_url": "http://localhost:11434/v1"
},
"openrouter": {
"model_name": "google/gemini-2.0-flash-lite-preview-02-05:free",
"base_url": "https://openrouter.ai/api/v1"
},
"restack": {
"model_name": "deepseek-chat",
"base_url": "https://ai.restack.io"
}
},
"prompts": {
"system_prompt": "You are a helpful research assistant. Extract key information about the topic and provide a structured summary.",
"user_prompt": "Provide a research summary about",
"system_prompt_researcher": "You are a manager overseeing research and analysis tasks. Your role is to coordinate the efforts of the research and analysis agents to provide comprehensive answers to user queries.",
"system_prompt_manager": "You are a research assistant. Your task is to find relevant information about the topic provided. Use the search tool to gather data and synthesize it into a concise summary.",
"system_prompt_analyst": "You are a data scientist. Your task is to analyze the data provided and extract meaningful insights. Use your analytical skills to identify trends, patterns, and correlations."
}
}
================================================
FILE: src/examples/run_simple_agent_no_tools.py
================================================
"""
A simple example of using a Pydantic AI agent to generate a structured summary of a
research topic.
"""
from os import path
from .utils.agent_simple_no_tools import get_research
from .utils.utils import (
get_api_key,
get_provider_config,
load_config,
print_research_Result,
)
CONFIG_FILE = "config.json"
def main():
"""Main function to run the research agent."""
config_path = path.join(path.dirname(__file__), CONFIG_FILE)
config = load_config(config_path)
provider = input("Which inference provider to use? ")
topic = input("What topic would you like to research? ")
api_key = get_api_key(provider)
provider_config = get_provider_config(provider, config)
result = get_research(topic, config.prompts, provider, provider_config, api_key)
print_research_Result(result.data, result.usage())
if __name__ == "__main__":
main()
================================================
FILE: src/examples/run_simple_agent_system.py
================================================
"""
This example demonstrates how to run a simple agent system that consists of a manager
agent, a research agent, and an analysis agent. The manager agent delegates research
and analysis tasks to the corresponding agents and combines the results to provide a
comprehensive answer to the user query.
https://ai.pydantic.dev/multi-agent-applications/#agent-delegation
"""
from asyncio import run
from os import path
from openai import UnprocessableEntityError
from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool
from pydantic_ai.exceptions import UnexpectedModelBehavior, UsageLimitExceeded
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.usage import UsageLimits
from .utils.agent_simple_system import SystemAgent, add_tools_to_manager_agent
from .utils.data_models import AnalysisResult, ResearchResult
from .utils.utils import create_model, get_api_key, get_provider_config, load_config
CONFIG_FILE = "config.json"
def get_models(model_config: dict) -> tuple[OpenAIModel]:
"""Get the models for the system agents."""
model_researcher = create_model(**model_config)
model_analyst = create_model(**model_config)
model_manager = create_model(**model_config)
return model_researcher, model_analyst, model_manager
def get_manager(
model_manager: OpenAIModel,
model_researcher: OpenAIModel,
model_analyst: OpenAIModel,
prompts: dict[str, str],
) -> SystemAgent:
"""Get the agents for the system."""
researcher = SystemAgent(
model_researcher,
ResearchResult,
prompts["system_prompt_researcher"],
[duckduckgo_search_tool()],
)
analyst = SystemAgent(
model_analyst, AnalysisResult, prompts["system_prompt_analyst"]
)
manager = SystemAgent(
model_manager, ResearchResult, prompts["system_prompt_manager"]
)
add_tools_to_manager_agent(manager, researcher, analyst)
return manager
async def main():
"""Main function to run the research system."""
provider = input("Which inference provider to use? ")
query = input("What would you like to research? ")
config_path = path.join(path.dirname(__file__), CONFIG_FILE)
config = load_config(config_path)
api_key = get_api_key(provider)
provider_config = get_provider_config(provider, config)
usage_limits = UsageLimits(request_limit=10, total_tokens_limit=4000)
model_config = {
"base_url": provider_config["base_url"],
"model_name": provider_config["model_name"],
"api_key": api_key,
"provider": provider,
}
manager = get_manager(*get_models(model_config), config.prompts)
print(f"\nResearching: {query}...")
try:
result = await manager.run(query, usage_limits=usage_limits)
except (UnexpectedModelBehavior, UnprocessableEntityError) as e:
print(f"Error: Model returned unexpected result: {e}")
except UsageLimitExceeded as e:
print(f"Usage limit exceeded: {e}")
else:
print("\nFindings:", {result.data.findings})
print(f"Sources: {result.data.sources}")
print("\nUsage statistics:")
print(result.usage())
if __name__ == "__main__":
run(main())
================================================
FILE: src/examples/run_simple_agent_tools.py
================================================
"""Run the dice game agent using simple tools."""
from os import path
from .utils.agent_simple_tools import get_dice
from .utils.utils import (
get_api_key,
get_provider_config,
load_config,
)
CONFIG_FILE = "config.json"
system_prompt = (
"You're a dice game, you should roll the die and see if the number "
"you get back matches the user's guess. If so, tell them they're a winner. "
"Use the player's name in the response."
)
def main():
"""Run the dice game agent."""
provider = input("Which inference provider to use? ")
player_name = input("Enter your name: ")
guess = input("Guess a number between 1 and 6: ")
config_path = path.join(path.dirname(__file__), CONFIG_FILE)
config = load_config(config_path)
api_key = get_api_key(provider)
provider_config = get_provider_config(provider, config)
result = get_dice(
player_name, guess, system_prompt, provider, api_key, provider_config
)
print(result.data)
print(f"{result._result_tool_name=}")
print(result.usage())
if __name__ == "__main__":
main()
================================================
FILE: src/examples/utils/agent_simple_no_tools.py
================================================
"""
This module contains a function to create a research agent with the specified model,
result type, and system prompt.
"""
from sys import exit
from openai import APIConnectionError
from pydantic_ai import Agent
from pydantic_ai.agent import AgentRunResult
from pydantic_ai.models.openai import OpenAIModel
from .data_models import Config, ResearchSummary
from .utils import create_model
def _create_research_agent(
model: OpenAIModel, result_type: ResearchSummary, system_prompt: str
) -> Agent:
"""
Create a research agent with the specified model, result type, and system prompt.
"""
return Agent(model=model, result_type=result_type, system_prompt=system_prompt)
def get_research(
topic: str,
prompts: dict[str, str],
provider: str,
provider_config: Config,
api_key: str,
) -> AgentRunResult:
"""Run the research agent to generate a structured summary of a research topic."""
model = create_model(
provider_config["base_url"], provider_config["model_name"], api_key, provider
)
agent = _create_research_agent(model, ResearchSummary, prompts["system_prompt"])
print(f"\nResearching {topic}...")
try:
result = agent.run_sync(f"{prompts['user_prompt']} {topic}")
except APIConnectionError as e:
print(f"Error connecting to API: {e}")
exit()
except Exception as e:
print(f"Error connecting to API: {e}")
exit()
else:
return result
================================================
FILE: src/examples/utils/agent_simple_system.py
================================================
"""
This module contains a simple system of agents that can be used to research and analyze
data.
"""
from pydantic_ai import Agent, RunContext
from pydantic_ai.models.openai import OpenAIModel
from .data_models import AnalysisResult, ResearchResult
class SystemAgent(Agent):
"""A generic system agent that can be used to research and analyze data."""
def __init__(
self,
model: OpenAIModel,
result_type: ResearchResult | AnalysisResult,
system_prompt: str,
result_retries: int = 3,
tools: list | None = [],
):
super().__init__(
model,
result_type=result_type,
system_prompt=system_prompt,
result_retries=result_retries,
tools=tools,
)
def add_tools_to_manager_agent(
manager_agent: SystemAgent, research_agent: SystemAgent, analysis_agent: SystemAgent
) -> None:
"""Create and configure the joke generation agent."""
@manager_agent.tool
async def delegate_research(ctx: RunContext[None], query: str) -> ResearchResult:
"""Delegate research task to ResearchAgent."""
result = await research_agent.run(query, usage=ctx.usage)
return result.data
@manager_agent.tool
async def delegate_analysis(ctx: RunContext[None], data: str) -> AnalysisResult:
"""Delegate analysis task to AnalysisAgent."""
result = await analysis_agent.run(data, usage=ctx.usage)
return result.data
================================================
FILE: src/examples/utils/agent_simple_tools.py
================================================
"""Simple agent for the dice game example."""
from openai import APIConnectionError
from pydantic_ai import Agent, Tool
from pydantic_ai.agent import AgentRunResult
from pydantic_ai.models.openai import OpenAIModel
from .tools import get_player_name, roll_die
from .utils import create_model
class _DiceGameAgent(Agent):
"""Dice game agent."""
def __init__(self, model: OpenAIModel, system_prompt: str):
super().__init__(
model=model,
deps_type=str,
system_prompt=system_prompt,
tools=[ # (1)!
Tool(roll_die, takes_ctx=False),
Tool(get_player_name, takes_ctx=True),
],
)
def get_dice(
player_name: str,
guess: str,
system_prompt: str,
provider: str,
api_key: str,
config: dict,
) -> AgentRunResult:
"""Run the dice game agent."""
model = create_model(config["base_url"], config["model_name"], api_key, provider)
agent = _DiceGameAgent(model, system_prompt)
try:
# usage_limits=UsageLimits(request_limit=5, total_tokens_limit=300),
result = agent.run_sync(f"Player is guessing {guess}...", deps=player_name)
except APIConnectionError as e:
print(f"Error connecting to API: {e}")
exit()
except Exception as e:
print(f"Error connecting to API: {e}")
exit()
else:
return result
================================================
FILE: src/examples/utils/data_models.py
================================================
"""Example of a module with data models"""
from pydantic import BaseModel
class ResearchResult(BaseModel):
"""Research results from the research agent."""
topic: str
findings: list[str]
sources: list[str]
class AnalysisResult(BaseModel):
"""Analysis results from the analysis agent."""
insights: list[str]
recommendations: list[str]
class ResearchSummary(BaseModel):
"""Expected model response of research on a topic"""
topic: str
key_points: list[str]
key_points_explanation: list[str]
conclusion: str
class ProviderConfig(BaseModel):
"""Configuration for a model provider"""
model_name: str
base_url: str
class Config(BaseModel):
"""Configuration settings for the research agent and model providers"""
providers: dict[str, ProviderConfig]
prompts: dict[str, str]
================================================
FILE: src/examples/utils/tools.py
================================================
"""Example tools for the utils example."""
from random import randint
from pydantic_ai import RunContext
def roll_die() -> str:
"""Tool to roll a die."""
async def _execute(self) -> str:
"""Roll the die and return the result."""
return str(randint(1, 6))
def get_player_name(ctx: RunContext[str]) -> str:
"""Get the player's name from the context."""
return ctx.deps
================================================
FILE: src/examples/utils/utils.py
================================================
"""Utility functions for running the research agent example."""
from json import load
from os import getenv
from sys import exit
from dotenv import load_dotenv
from pydantic import ValidationError
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider
from pydantic_ai.usage import Usage
from .data_models import Config
API_SUFFIX = "_API_KEY"
def load_config(config_path: str) -> Config:
"""Load and validate configuration from a JSON file."""
try:
with open(config_path) as file:
config_data = load(file)
config = Config.model_validate(config_data)
except FileNotFoundError:
raise FileNotFoundError(f"Configuration file not found: {config_path}")
exit()
except ValidationError as e:
raise ValueError(f"Invalid configuration format: {e}")
exit()
except Exception as e:
raise Exception(f"Error loading configuration: {e}")
exit()
else:
return config
def get_api_key(provider: str) -> str | None:
"""Retrieve API key from environment variable."""
# TODO replace with pydantic-settings ?
load_dotenv()
if provider.lower() == "ollama":
return None
else:
return getenv(f"{provider.upper()}{API_SUFFIX}")
def get_provider_config(provider: str, config: Config) -> dict[str, str]:
"""Retrieve configuration settings for the specified provider."""
try:
model_name = config.providers[provider].model_name
base_url = config.providers[provider].base_url
except KeyError as e:
raise ValueError(f"Missing configuration for {provider}: {e}.")
exit()
except Exception as e:
raise Exception(f"Error loading provider configuration: {e}")
exit()
else:
return {
"model_name": model_name,
"base_url": base_url,
}
def create_model(
base_url: str,
model_name: str,
api_key: str | None = None,
provider: str | None = None,
) -> OpenAIModel:
"""Create a model that uses base_url as inference API"""
if api_key is None and not provider.lower() == "ollama":
raise ValueError("API key is required for model.")
exit()
else:
return OpenAIModel(
model_name, provider=OpenAIProvider(base_url=base_url, api_key=api_key)
)
def print_research_Result(summary: dict, usage: Usage) -> None:
"""Output structured summary of the research topic."""
print(f"\n=== Research Summary: {summary.topic} ===")
print("\nKey Points:")
for i, point in enumerate(summary.key_points, 1):
print(f"{i}. {point}")
print("\nKey Points Explanation:")
for i, point in enumerate(summary.key_points_explanation, 1):
print(f"{i}. {point}")
print(f"\nConclusion: {summary.conclusion}")
print(f"\nResponse structure: {list(dict(summary).keys())}")
print(usage)
================================================
FILE: src/gui/components/footer.py
================================================
from streamlit import caption, divider
def render_footer(footer_caption: str):
"""Render the page footer."""
divider()
caption(footer_caption)
================================================
FILE: src/gui/components/header.py
================================================
from streamlit import divider, title
def render_header(header_title: str):
"""Render the page header with title."""
title(header_title)
divider()
================================================
FILE: src/gui/components/output.py
================================================
from typing import Any
from streamlit import empty, info
def render_output(
result: Any = None, info_str: str | None = None, type: str | None = None
):
"""
Renders the output in a Streamlit app based on the provided type.
Args:
result (Any, optional): The content to be displayed. Can be JSON, code
markdown, or plain text.
info (str, optional): The information message to be displayed if result is None.
type (str, optional): The type of the result content. Can be 'json', 'code',
'md', or other for plain text.
Returns:
Out: None
"""
if result:
output_container = empty()
output_container.write(result)
# match type:
# case "json":
# json(result)
# case "code":
# code(result)
# case "md":
# markdown(result)
# case _:
# text(result)
# # st.write(result)
else:
info(info_str)
================================================
FILE: src/gui/components/prompts.py
================================================
from streamlit import text_area
def render_prompt_editor(
prompt_name: str, prompt_value: str, height: int = 150
) -> str | None:
return text_area(
f"{prompt_name.replace('_', ' ').title()}", value=prompt_value, height=height
)
================================================
FILE: src/gui/components/sidebar.py
================================================
from streamlit import sidebar
from gui.config.config import PAGES
def render_sidebar(sidebar_title: str):
sidebar.title(sidebar_title)
selected_page = sidebar.radio(" ", PAGES)
# st.sidebar.divider()
# st.sidebar.info(" ")
return selected_page
================================================
FILE: src/gui/config/config.py
================================================
APP_PATH = "app"
PAGES = ["Home", "Settings", "Prompts", "App"]
PROMPTS_DEFAULT = {
"system_prompt_manager": (
"You are a manager overseeing research and analysis tasks..."
),
"system_prompt_researcher": ("You are a researcher. Gather and analyze data..."),
"system_prompt_analyst": (
"You are a research analyst. Use your analytical skills..."
),
"system_prompt_synthesiser": (
"You are a research synthesiser. Use your analytical skills..."
),
}
================================================
FILE: src/gui/config/styling.py
================================================
from streamlit import markdown, set_page_config
def add_custom_styling(page_title: str):
set_page_config(
page_title=f"{page_title}",
page_icon="🤖",
layout="wide",
initial_sidebar_state="expanded",
)
custom_css = """
"""
markdown(custom_css, unsafe_allow_html=True)
================================================
FILE: src/gui/config/text.py
================================================
HOME_INFO = "Select 'App' to start using the system"
HOME_HEADER = "Welcome to the Multi-Agent Research System"
HOME_DESCRIPTION = """
This system allows you to:
- Run research queries using multiple specialized agents
- Configure agent settings and prompts
- View detailed results from your research
Use the sidebar to navigate between different sections of the application.
"""
PAGE_TITLE = "MAS Eval 👾⚗️🧠💡"
PROMPTS_WARNING = "No prompts found. Using default prompts."
PROMPTS_HEADER = "Agent Prompts"
RUN_APP_HEADER = "Run Research App"
RUN_APP_QUERY_PLACEHOLDER = "What would you like to research?"
RUN_APP_PROVIDER_PLACEHOLDER = "Provider?"
RUN_APP_BUTTON = "Run Query"
RUN_APP_OUTPUT_PLACEHOLDER = "Run the agent to see results here"
RUN_APP_QUERY_WARNING = "Please enter a query"
RUN_APP_QUERY_RUN_INFO = "Running query: "
SETTINGS_HEADER = "Settings"
SETTINGS_PROVIDER_LABEL = "Select Provider"
SETTINGS_PROVIDER_PLACEHOLDER = "Select Provider"
SETTINGS_ADD_PROVIDER = "Add New Provider"
SETTINGS_API_KEY_LABEL = "API Key"
OUTPUT_SUBHEADER = "Output"
================================================
FILE: src/gui/pages/home.py
================================================
from streamlit import header, info, markdown
from gui.config.text import HOME_DESCRIPTION, HOME_HEADER, HOME_INFO
def render_home():
header(HOME_HEADER)
markdown(HOME_DESCRIPTION)
info(HOME_INFO)
================================================
FILE: src/gui/pages/prompts.py
================================================
"""
Streamlit component for editing agent system prompts.
This module provides a function to render and edit prompt configurations
for agent roles using a Streamlit-based UI. It validates the input configuration,
displays warnings if prompts are missing, and allows interactive editing of each prompt.
"""
from pydantic import BaseModel
from streamlit import error, header, warning
from app.config.data_models import ChatConfig
from app.utils.error_messages import invalid_type
from app.utils.log import logger
from gui.components.prompts import render_prompt_editor
from gui.config.config import PROMPTS_DEFAULT
from gui.config.text import PROMPTS_HEADER, PROMPTS_WARNING
def render_prompts(chat_config: ChatConfig | BaseModel): # -> dict[str, str]:
"""
Render and edit the prompt configuration for agent roles in the Streamlit UI.
"""
header(PROMPTS_HEADER)
if not isinstance(chat_config, ChatConfig):
msg = invalid_type("ChatConfig", type(chat_config).__name__)
logger.error(msg)
error(msg)
return None
# updated = False
prompts = chat_config.prompts
if not prompts:
warning(PROMPTS_WARNING)
prompts = PROMPTS_DEFAULT
updated_prompts = prompts.copy()
# Edit prompts
for prompt_key, prompt_value in prompts.items():
new_value = render_prompt_editor(prompt_key, prompt_value, height=200)
if new_value != prompt_value and new_value is not None:
updated_prompts[prompt_key] = new_value
# updated = True
# return updated_prompts if updated else prompts
================================================
FILE: src/gui/pages/run_app.py
================================================
"""
Streamlit interface for running the agentic system interactively.
This module defines the render_app function, which provides a Streamlit-based UI
for users to select a provider, enter a query, and execute the main agent workflow.
Results and errors are displayed in real time, supporting asynchronous execution.
"""
from streamlit import button, exception, header, info, subheader, text_input, warning
from app.main import main
from app.utils.log import logger
from gui.components.output import render_output
from gui.config.text import (
OUTPUT_SUBHEADER,
RUN_APP_BUTTON,
RUN_APP_HEADER,
RUN_APP_OUTPUT_PLACEHOLDER,
RUN_APP_PROVIDER_PLACEHOLDER,
RUN_APP_QUERY_PLACEHOLDER,
RUN_APP_QUERY_RUN_INFO,
RUN_APP_QUERY_WARNING,
)
async def render_app(provider: str | None = None):
"""
Render the main app interface for running agentic queries via Streamlit.
Displays input fields for provider and query, a button to trigger execution,
and an area for output or error messages. Handles async invocation of the
main agent workflow and logs any exceptions.
"""
header(RUN_APP_HEADER)
if provider is None:
provider = text_input(RUN_APP_PROVIDER_PLACEHOLDER)
query = text_input(RUN_APP_QUERY_PLACEHOLDER)
subheader(OUTPUT_SUBHEADER)
if button(RUN_APP_BUTTON):
if query:
info(f"{RUN_APP_QUERY_RUN_INFO} {query}")
try:
result = await main(chat_provider=provider, query=query)
render_output(result)
except Exception as e:
render_output(None)
exception(e)
logger.exception(e)
else:
warning(RUN_APP_QUERY_WARNING)
else:
render_output(RUN_APP_OUTPUT_PLACEHOLDER)
================================================
FILE: src/gui/pages/settings.py
================================================
"""
Streamlit settings UI for provider and agent configuration.
This module provides a function to render and edit agent system settings,
including provider selection and related options, within the Streamlit GUI.
It validates the input configuration and ensures correct typing before rendering.
"""
from streamlit import error, header, selectbox
from app.config.data_models import BaseModel, ChatConfig
from app.utils.error_messages import invalid_type
from app.utils.log import logger
from gui.config.text import SETTINGS_HEADER, SETTINGS_PROVIDER_LABEL
def render_settings(chat_config: ChatConfig | BaseModel) -> str:
"""
Render and edit agent system settings in the Streamlit UI.
Displays a header and a selectbox for choosing the inference provider.
Validates that the input is a ChatConfig instance and displays an error if not.
"""
header(SETTINGS_HEADER)
# updated = False
# updated_config = config.copy()
if not isinstance(chat_config, ChatConfig):
msg = invalid_type("ChatConfig", type(chat_config).__name__)
logger.error(msg)
error(msg)
return msg
provider = selectbox(
label=SETTINGS_PROVIDER_LABEL,
options=chat_config.providers.keys(),
)
# Run options
# col1, col2 = st.columns(2)
# with col1:
# streamed_output = st.checkbox(
# "Stream Output", value=config.get("streamed_output", False)
# )
# with col2:
# st.checkbox("Include Sources", value=True) # include_sources
# Allow adding new providers
# new_provider = st.text_input("Add New Provider")
# api_key = st.text_input(f"{provider} API Key", type="password")
# if st.button("Add Provider") and new_provider and new_provider not in providers:
# providers.append(new_provider)
# updated_config["providers"] = providers
# updated_config["api_key"] = api_key
# updated = True
# st.success(f"Added provider: {new_provider}")
# # Update config if changed
# if (
# include_a != config.get("include_a", False)
# or include_b != config.get("include_b", False)
# or streamed_output != config.get("streamed_output", False)
# ):
# updated_config["include_a"] = include_a
# updated_config["include_b"] = include_b
# updated_config["streamed_output"] = streamed_output
# updated = True
return provider
================================================
FILE: tests/test_agent_system.py
================================================
from app.agents.agent_system import get_manager
from app.config.data_models import ProviderConfig
def test_get_manager_minimal():
provider = "github"
provider_config = ProviderConfig.model_validate(
{"model_name": "test-model", "base_url": "http://test.com"}
)
api_key = "test"
prompts = {"system_prompt_manager": "test"}
agent = get_manager(provider, provider_config, api_key, prompts)
assert hasattr(agent, "run")
================================================
FILE: tests/test_env.py
================================================
from pytest import MonkeyPatch
from app.config.data_models import AppEnv
def test_app_env_loads_env_vars(monkeypatch: MonkeyPatch):
monkeypatch.setenv("GEMINI_API_KEY", "test-gemini")
env = AppEnv()
assert env.GEMINI_API_KEY == "test-gemini"
================================================
FILE: tests/test_metrics_output_similarity.py
================================================
"""
Tests for the output_similarity metric.
This module verifies that the output_similarity metric correctly identifies when
an agent's output matches the expected answer.
"""
from app.evals.metrics import output_similarity
def test_output_similarity_exact_match():
assert output_similarity("42", "42") is True
def test_output_similarity_whitespace():
assert output_similarity(" answer ", "answer") is True
def test_output_similarity_incorrect():
assert output_similarity("foo", "bar") is False
================================================
FILE: tests/test_metrics_time_taken.py
================================================
"""
Tests for the time_taken metric.
This module verifies that the time_taken metric correctly computes the elapsed
time between two timestamps, ensuring accurate measurement of agent execution
duration for evaluation purposes.
"""
import asyncio
import time
import pytest
from app.evals.metrics import time_taken
@pytest.mark.asyncio
async def test_time_taken_metric():
"""Scenario: Calculate time taken for agent execution"""
# Given: Start and end timestamps
start_time = time.perf_counter()
await asyncio.sleep(0.1)
end_time = time.perf_counter()
# When: Calculating time taken
result = time_taken(start_time, end_time)
# Then: Verify correct duration calculation
assert result == pytest.approx(0.1, abs=0.05)
================================================
FILE: tests/test_provider_config.py
================================================
from pytest import MonkeyPatch
from app.config.data_models import ProviderConfig
def test_provider_config_parsing(monkeypatch: MonkeyPatch):
pcfg = ProviderConfig.model_validate(
{"model_name": "foo", "base_url": "https://foo.bar"}
)
assert pcfg.model_name == "foo"
assert pcfg.base_url == "bar"
================================================
FILE: .claude/settings.local.json
================================================
{
"permissions": {
"allow": [
"Bash(cat:*)",
"Bash(find:*)",
"Bash(git:diff*)",
"Bash(git:log*)",
"Bash(git:status*)",
"Bash(grep:*)",
"Bash(ls:*)",
"Bash(mkdir:*)",
"Bash(source:*)",
"Bash(touch:*)",
"Bash(tree:*)",
"Bash(uv:run*)",
"Edit(AGENTS.md)",
"Edit(docs/**/*.md)",
"Edit(src/**/*.py)",
"Edit(src/**/*.json)",
"Edit(tests/**/*.py)",
"WebFetch(domain:docs.anthropic.com)"
],
"deny": [
"Bash(mv:*)",
"Bash(rm:*)",
"Edit(CLAUDE.md)"
]
}
}
================================================
FILE: .claude/commands/execute-prp.md
================================================
# Execute Product Requirements Prompt (PRP)
Implement a feature using using the PRP file.
## PRP File: $ARGUMENTS
## Execution Process
1. **Load PRP**
- Read the specified PRP file
- Understand all context and requirements
- Follow all instructions in the PRP and extend the research if needed
- Ensure you have all needed context to implement the PRP fully
- Do more web searches and codebase exploration as needed
2. **ULTRATHINK**
- Think hard before you execute the plan. Create a comprehensive plan addressing all requirements.
- Break down complex tasks into smaller, manageable steps using your todos tools.
- Use the TodoWrite tool to create and track your implementation plan.
- Identify implementation patterns from existing code to follow.
3. **Execute the plan**
- Execute the PRP
- Implement all the code
4. **Validate**
- Run each validation command
- Fix any failures
- Re-run until all pass
5. **Complete**
- Ensure all checklist items done
- Run final validation suite
- Report completion status
- Read the PRP again to ensure you have implemented everything
6. **Reference the PRP**
- You can always reference the PRP again if needed
Note: If validation fails, use error patterns in PRP to fix and retry.
================================================
FILE: .claude/commands/generate-prp.md
================================================
# Create Product Requirements Prompt (PRP)
## Feature file: $ARGUMENTS
Generate a complete PRP (Product Requirements Prompt) for general feature implementation with thorough research. Ensure context is passed to the AI agent to enable self-validation and iterative refinement. Read the feature file first to understand what needs to be created, how the examples provided help, and any other considerations.
The AI agent only gets the context you are appending to the PRP and training data. Assume the AI agent has access to the codebase and the same knowledge cutoff as you, so its important that your research findings are included or referenced in the PRP. The Agent has Websearch capabilities, so pass urls to documentation and examples.
- Use `/context/PRPs` as `$base_path`
- Extract only the filename from `$ARGUMENTS` into `$file_name`
## Research Process
1. **Codebase Analysis**
- Search for similar features/patterns in the codebase
- Identify files to reference in PRP
- Note existing conventions to follow
- Check test patterns for validation approach
2. **External Research**
- Search for similar features/patterns online
- Library documentation (include specific URLs)
- Implementation examples (GitHub/StackOverflow/blogs)
- Best practices and common pitfalls
3. **User Clarification** (if needed)
- Specific patterns to mirror and where to find them?
- Integration requirements and where to find them?
## PRP Generation
- Use `${base_path}/templates/prp_base.md` in the base folder as template
### Critical Context to Include and pass to the AI agent as part of the PRP
- **Documentation**: URLs with specific sections
- **Code Examples**: Real snippets from codebase
- **Gotchas**: Library quirks, version issues
- **Patterns**: Existing approaches to follow
### Implementation Blueprint
- Start with pseudocode showing approach
- Reference real files for patterns
- Include error handling strategy
- list tasks to be completed to fullfill the PRP in the order they should be completed
### Validation Gates (Must be Executable) eg for python
```bash
# Syntax/Style
make ruff
make type_check
# Unit Tests
make coverage_all
```
***CRITICAL AFTER YOU ARE DONE RESEARCHING AND EXPLORING THE CODEBASE BEFORE YOU START WRITING THE PRP***
***ULTRATHINK ABOUT THE PRP AND PLAN YOUR APPROACH THEN START WRITING THE PRP***
## Output
- Save the result to `${base_path}/${file_name}`
## Quality Checklist
- [ ] All necessary context included
- [ ] Validation gates are executable by AI
- [ ] References existing patterns
- [ ] Clear implementation path
- [ ] Error handling documented
Score the PRP on a scale of 1-10 (confidence level to succeed in one-pass implementation using claude codes)
Remember: The goal is one-pass implementation success through comprehensive context.
================================================
FILE: .devcontainer/setup_dev/devcontainer.json
================================================
{
"name": "make setup_dev",
"image": "mcr.microsoft.com/vscode/devcontainers/python:3.13",
"postCreateCommand": "make setup_dev"
}
================================================
FILE: .devcontainer/setup_dev_claude/devcontainer.json
================================================
{
"name": "make setup_dev_claude",
"image": "mcr.microsoft.com/vscode/devcontainers/python:3.13",
"features": {
"ghcr.io/devcontainers/features/node:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"anthropic.claude-code"
]
}
},
"postCreateCommand": "make setup_dev_claude"
}
================================================
FILE: .devcontainer/setup_dev_ollama/devcontainer.json
================================================
{
"name": "make setup_dev_ollama",
"image": "mcr.microsoft.com/vscode/devcontainers/python:3.13",
"postCreateCommand": "make setup_dev_ollama"
}
================================================
FILE: .github/dependabot.yaml
================================================
---
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
...
================================================
FILE: .github/scripts/create_pr.sh
================================================
#!/bin/bash
# 1 base ref, 2 target ref, 3 title suffix
# 4 current version, 5 bumped
pr_title="PR $2 $3"
pr_body="PR automatically created from \`$1\` to bump from \`$4\` to \`$5\` on \`$2\`. Tag \`v$5\` will be created and has to be deleted manually if PR gets closed without merge."
gh pr create \
--base $1 \
--head $2 \
--title "${pr_title}" \
--body "${pr_body}"
# --label "bump"
================================================
FILE: .github/scripts/delete_branch_pr_tag.sh
================================================
#!/bin/bash
# 1 repo, 2 target ref, 3 current version
tag_to_delete="v$3"
branch_del_api_call="repos/$1/git/refs/heads/$2"
del_msg="'$2' force deletion attempted."
close_msg="Closing PR '$2' to rollback after failure"
echo "Tag $tag_to_delete for $del_msg"
git tag -d "$tag_to_delete"
echo "PR for $del_msg"
gh pr close "$2" --comment "$close_msg"
echo "Branch $del_msg"
gh api "$branch_del_api_call" -X DELETE && \
echo "Branch without error return deleted."
================================================
FILE: .github/workflows/bump-my-version.yaml
================================================
---
name: bump-my-version
on:
# pull_request:
# types: [closed]
# branches: [main]
workflow_dispatch:
inputs:
bump_type:
description: '[major|minor|patch]'
required: true
default: 'patch'
type: choice
options:
- 'major'
- 'minor'
- 'patch'
env:
BRANCH_NEW: "bump-${{ github.run_number }}-${{ github.ref_name }}"
SKIP_PR_HINT: "[skip ci bump]"
SCRIPT_PATH: ".github/scripts"
jobs:
bump_my_version:
# TODO bug? currently resulting in: Unrecognized named-value: 'env'.
# https://stackoverflow.com/questions/61238849/github-actions-if-contains-function-not-working-with-env-variable/61240761
# if: !contains(
# github.event.pull_request.title,
# ${{ env.SKIP_PR_HINT }}
# )
# TODO check for PR closed by bot to avoid PR creation loop
# github.actor != 'github-actions'
if: >
github.event_name == 'workflow_dispatch' ||
( github.event.pull_request.merged == true &&
github.event.pull_request.closed_by != 'github-actions' )
runs-on: ubuntu-latest
outputs:
branch_new: ${{ steps.create_branch.outputs.branch_new }}
summary_data: ${{ steps.set_summary.outputs.summary_data }}
permissions:
actions: read
checks: write
contents: write
pull-requests: write
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Set git cfg and create branch
id: create_branch
run: |
git config user.email "bumped@qte77.gha"
git config user.name "bump-my-version"
git checkout -b "${{ env.BRANCH_NEW }}"
echo "branch_new=${{ env.BRANCH_NEW }}" >> $GITHUB_OUTPUT
- name: Bump version
id: bump
uses: callowayproject/bump-my-version@0.29.0
env:
BUMPVERSION_TAG: "true"
with:
args: ${{ inputs.bump_type }}
branch: ${{ env.BRANCH_NEW }}
- name: "Create PR '${{ env.BRANCH_NEW }}'"
if: steps.bump.outputs.bumped == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
src="${{ env.SCRIPT_PATH }}/create_pr.sh"
chmod +x "$src"
$src "${{ github.ref_name }}" "${{ env.BRANCH_NEW }}" "${{ env.SKIP_PR_HINT }}" "${{ steps.bump.outputs.previous-version }}" "${{ steps.bump.outputs.current-version }}"
- name: Delete branch, PR and tag in case of failure or cancel
if: failure() || cancelled()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
src="${{ env.SCRIPT_PATH }}/delete_branch_pr_tag.sh"
chmod +x "$src"
$src "${{ github.repository }}" "${{ env.BRANCH_NEW }}" "${{ steps.bump.outputs.current-version }}"
- name: Set summary data
id: set_summary
if: ${{ always() }}
run: echo "summary_data=${GITHUB_STEP_SUMMARY}" >> $GITHUB_OUTPUT
generate_summary:
name: Generate Summary Report
if: ${{ always() }}
needs: bump_my_version
uses: ./.github/workflows/summarize-jobs-reusable.yaml
with:
branch_to_summarize: ${{ needs.bump_my_version.outputs.branch_new }}
summary_data: ${{ needs.bump_my_version.outputs.summary_data }}
...
================================================
FILE: .github/workflows/codeql.yaml
================================================
---
# https://github.blog/changelog/2023-01-18-code-scanning-codeql-action-v1-is-now-deprecated/
name: "CodeQL"
on:
push:
pull_request:
types: [closed]
branches: [ main ]
schedule:
- cron: '27 11 * * 0'
workflow_dispatch:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: python
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# if autobuild fails
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
#- name: sarif
# uses: github/codeql-action/upload-sarif@v2
...
================================================
FILE: .github/workflows/generate-deploy-mkdocs-ghpages.yaml
================================================
---
name: Deploy Docs
on:
pull_request:
types: [closed]
branches: [main]
workflow_dispatch:
env:
DOCSTRINGS_FILE: "docstrings.md"
DOC_DIR: "docs"
SRC_DIR: "src"
SITE_DIR: "site"
IMG_DIR: "assets/images"
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
steps:
- name: Checkout the repository
uses: actions/checkout@v4.0.0
with:
ref:
${{
github.event.pull_request.merged == true &&
'main' ||
github.ref_name
}}
fetch-depth: 0
- uses: actions/configure-pages@v5.0.0
# caching instead of actions/cache@v4.0.0
# https://docs.astral.sh/uv/guides/integration/github/#caching
- name: Install uv with cache dependency glob
uses: astral-sh/setup-uv@v5.0.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
# setup python from pyproject.toml using uv
# instead of using actions/setup-python@v5.0.0
# https://docs.astral.sh/uv/guides/integration/github/#setting-up-python
- name: "Set up Python"
run: uv python install
- name: Install only doc deps
run: uv sync --only-group docs # --frozen
- name: Get repo info and stream into mkdocs.yaml
id: repo_info
run: |
REPO_INFO=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }})
REPO_URL="${{ github.server_url }}/${{ github.repository }}"
REPO_URL=$(echo ${REPO_URL} | sed 's|/|\\/|g')
SITE_NAME=$(sed '1!d' README.md | sed '0,/# /{s/# //}')
SITE_DESC=$(echo $REPO_INFO | jq -r .description)
sed -i "s//${REPO_URL}/g" mkdocs.yaml
sed -i "s//${SITE_NAME}/g" mkdocs.yaml
sed -i "s//${SITE_DESC}/g" mkdocs.yaml
- name: Copy text files to be included
run: |
CFG_PATH="src/app/config"
mkdir -p "${DOC_DIR}/${CFG_PATH}"
cp README.md "${DOC_DIR}/index.md"
cp {CHANGELOG,LICENSE}.md "${DOC_DIR}"
# Auxiliary files
cp .env.example "${DOC_DIR}"
cp "${CFG_PATH}/config_chat.json" "${DOC_DIR}/${CFG_PATH}"
- name: Generate code docstrings concat file
run: |
PREFIX="::: "
find "${SRC_DIR}" -type f -name "*.py" \
-type f -not -name "__*__*" -printf "%P\n" | \
sed 's/\//./g' | sed 's/\.py$//' | \
sed "s/^/${PREFIX}/" | sort > \
"${DOC_DIR}/${DOCSTRINGS_FILE}"
- name: Build documentation
run: uv run --locked --only-group docs mkdocs build
- name: Copy image files to be included
run: |
# copy images, mkdocs does not by default
# mkdocs also overwrites pre-made directories
dir="${{ env.SITE_DIR }}/${{ env.IMG_DIR }}"
if [ -d "${{ env.IMG_DIR }}" ]; then
mkdir -p "${dir}"
cp "${{ env.IMG_DIR }}"/* "${dir}"
fi
# - name: Push to gh-pages
# run: uv run mkdocs gh-deploy --force
- name: Upload artifact
uses: actions/upload-pages-artifact@v3.0.0
with:
path: "${{ env.SITE_DIR }}"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4.0.0
...
================================================
FILE: .github/workflows/links-fail-fast.yaml
================================================
---
# https://github.com/lycheeverse/lychee-action
# https://github.com/marketplace/actions/lychee-broken-link-checker
name: "Link Checker"
on:
workflow_dispatch:
push:
branches-ignore: [main]
pull_request:
types: [closed]
branches: [main]
schedule:
- cron: "00 00 * * 0"
jobs:
linkChecker:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/checkout@v4
- name: Link Checker
id: lychee
uses: lycheeverse/lychee-action@v2
- name: Create Issue From File
if: steps.lychee.outputs.exit_code != 0
uses: peter-evans/create-issue-from-file@v5
with:
title: lychee Link Checker Report
content-filepath: ./lychee/out.md
labels: report, automated issue
...
================================================
FILE: .github/workflows/pytest.yaml
================================================
name: pytest
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
- name: Run tests
run: pytest
================================================
FILE: .github/workflows/ruff.yaml
================================================
---
# https://github.com/astral-sh/ruff-action
# https://github.com/astral-sh/ruff
name: ruff
on:
push:
pull_request:
types: [closed]
branches: [main]
schedule:
- cron: "0 0 * * 0"
workflow_dispatch:
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
...
================================================
FILE: .github/workflows/summarize-jobs-reusable.yaml
================================================
---
# https://ecanarys.com/supercharging-github-actions-with-job-summaries-and-pull-request-comments/
# FIXME currently bug in gha summaries ? $GITHUB_STEP_SUMMARY files are empty
# https://github.com/orgs/community/discussions/110283
# https://github.com/orgs/community/discussions/67991
# Possible workaround
# echo ${{ fromJSON(step).name }}" >> $GITHUB_STEP_SUMMARY
# echo ${{ fromJSON(step).outcome }}" >> $GITHUB_STEP_SUMMARY
# echo ${{ fromJSON(step).conclusion }}"
name: Summarize workflow jobs
on:
workflow_call:
outputs:
summary:
description: "Outputs summaries of jobs in a workflow"
value: ${{ jobs.generate_summary.outputs.summary }}
inputs:
branch_to_summarize:
required: false
default: 'main'
type: string
summary_data:
required: false
type: string
jobs:
generate_summary:
name: Generate Summary
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
checks: read
pull-requests: none
outputs:
summary: ${{ steps.add_changed_files.outputs.summary }}
steps:
- name: Add general information
id: general_info
run: |
echo "# Job Summaries" >> $GITHUB_STEP_SUMMARY
echo "Job: `${{ github.job }}`" >> $GITHUB_STEP_SUMMARY
echo "Date: $(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_STEP_SUMMARY
- name: Add step states
id: step_states
run: |
echo "### Steps:" >> $GITHUB_STEP_SUMMARY
# loop summary_data if valid json
if jq -e . >/dev/null 2>&1 <<< "${{ inputs.summary_data }}"; then
jq -r '
.steps[]
| select(.conclusion != null)
| "- **\(.name)**: \(
if .conclusion == "success" then ":white_check_mark:"
elif .conclusion == "failure" then ":x:"
else ":warning:" end
)"
' <<< "${{ inputs.summary_data }}" >> $GITHUB_STEP_SUMMARY
else
echo "Invalid JSON in summary data." >> $GITHUB_STEP_SUMMARY
fi
- name: Checkout repo
uses: actions/checkout@v4
with:
ref: "${{ inputs.branch_to_summarize }}"
fetch-depth: 0
- name: Add changed files since last push
id: add_changed_files
run: |
# Get the tags
# Use disabled lines to get last two commits
# current=$(git show -s --format=%ci HEAD)
# previous=$(git show -s --format=%ci HEAD~1)
# git diff --name-only HEAD^ HEAD >> $GITHUB_STEP_SUMMARY
version_tag_regex="^v[0-9]+\.[0-9]+\.[0-9]+$" # v0.0.0
tags=$(git tag --sort=-version:refname | \
grep -E "${version_tag_regex}" || echo "")
# Get latest and previous tags
latest_tag=$(echo "${tags}" | head -n 1)
previous_tag=$(echo "${tags}" | head -n 2 | tail -n 1)
echo "tags: latest '${latest_tag}', previous '${previous_tag}'"
# Write to summary
error_msg="No files to output. Tag not found:"
echo ${{ steps.step_states.outputs.summary }} >> $GITHUB_STEP_SUMMARY
echo "## Changed files on '${{ inputs.branch_to_summarize }}'" >> $GITHUB_STEP_SUMMARY
if [ -z "${latest_tag}" ]; then
echo "${error_msg} latest" >> $GITHUB_STEP_SUMMARY
elif [ -z "${previous_tag}" ]; then
echo "${error_msg} previous" >> $GITHUB_STEP_SUMMARY
elif [ "${latest_tag}" == "${previous_tag}" ]; then
echo "Latest and previous tags are the same: '${latest_tag}'" >> $GITHUB_STEP_SUMMARY
else
# Get commit dates and hashes
latest_date=$(git log -1 --format=%ci $latest_tag)
previous_date=$(git log -1 --format=%ci $previous_tag)
current_hash=$(git rev-parse --short $latest_tag)
previous_hash=$(git rev-parse --short $previous_tag)
# Append summary to the job summary
echo "Latest Tag Commit: '${latest_tag}' (${current_hash}) ${latest_date}" >> $GITHUB_STEP_SUMMARY
echo "Previous Tag Commit: '${previous_tag}' (${previous_hash}) ${previous_date}" >> $GITHUB_STEP_SUMMARY
echo "Files changed:" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
git diff --name-only $previous_tag..$latest_tag >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
- name: Output error message in case of failure or cancel
if: failure() || cancelled()
run: |
if [ "${{ job.status }}" == "cancelled" ]; then
out_msg="## Workflow was cancelled"
else
out_msg="## Error in previous step"
fi
echo $out_msg >> $GITHUB_STEP_SUMMARY
...
================================================
FILE: .github/workflows/write-llms-txt.yaml
================================================
# TODO use local installation of repo to text
# https://github.com/itsitgroup/repo2txt
name: Write repo llms.txt
on:
push:
branches: [main]
workflow_dispatch:
inputs:
LLMS_TXT_PATH:
description: 'Path to the directory to save llsm.txt'
required: true
default: 'docs'
type: string
LLMS_TXT_NAME:
description: 'Path to the directory to save llsm.txt'
required: true
default: 'llms.txt'
type: string
CONVERTER_URL:
description: '[uithub|gittodoc]' # |repo2txt
required: true
default: 'uithub.com'
type: choice
options:
- 'uithub.com'
- 'gittodoc.com'
# - 'repo2txt.com'
jobs:
generate-file:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Construct and create llms.txt path
id: construct_and_create_llms_txt_path
run: |
LLMS_TXT_PATH="${{ inputs.LLMS_TXT_PATH }}"
LLMS_TXT_PATH="${LLMS_TXT_PATH:-docs}"
LLMS_TXT_NAME="${{ inputs.LLMS_TXT_NAME }}"
LLMS_TXT_NAME="${LLMS_TXT_NAME:-llms.txt}"
echo "LLMS_TXT_FULL=${LLMS_TXT_PATH}/${LLMS_TXT_NAME}" >> $GITHUB_OUTPUT
mkdir -p "${LLMS_TXT_PATH}"
- name: Fetch TXT from URL
run: |
LLMS_TXT_FULL=${{ steps.construct_and_create_llms_txt_path.outputs.LLMS_TXT_FULL }}
URL="https://${{ inputs.CONVERTER_URL }}/${{ github.repository }}"
echo "Fetching content from: ${URL}"
echo "Saving content to: ${LLMS_TXT_FULL}"
curl -s "${URL}" > "${LLMS_TXT_FULL}"
- name: Commit and push file
run: |
LLMS_TXT_FULL=${{ steps.construct_and_create_llms_txt_path.outputs.LLMS_TXT_FULL }}
commit_msg="feat(docs): Add/Update ${LLMS_TXT_FULL}, a flattened repo as single text file, inspired by [llmstxt.org](https://llmstxt.org/)."
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add "${LLMS_TXT_FULL}"
git commit -m "${commit_msg}"
git push
================================================
FILE: .streamlit/config.toml
================================================
[theme]
primaryColor="#f92aad"
backgroundColor="#0b0c10"
secondaryBackgroundColor="#1f2833"
textColor="#66fcf1"
font="monospace"
[server]
# enableCORS = false
enableXsrfProtection = true
[browser]
gatherUsageStats = false
[client]
# toolbarMode = "minimal"
showErrorDetails = true