diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000000..6bbdb82e8ff7 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,94 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '25 17 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: csharp + build-mode: manual + - language: javascript-typescript + build-mode: none + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + working-directory: dotnet + run: | + dotnet workload install aspire + dotnet restore -bl + dotnet build --no-restore --configuration Release -bl /p:SignAssembly=true + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9c287026e729..17996088f515 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -39,8 +39,8 @@ jobs: uv sync --locked --all-extras source .venv/bin/activate poe --directory ./packages/autogen-core docs-build - mkdir -p docs-staging/autogen/dev/ - mv ./packages/autogen-core/docs/build/* docs-staging/autogen/dev/ + mkdir -p docs-staging/dev/ + mv ./packages/autogen-core/docs/build/* docs-staging/dev/ working-directory: ./python - name: generate redirects run: | @@ -99,8 +99,8 @@ jobs: fi - run: | - mkdir -p artifact/autogen/0.2/ - cp -r build/* artifact/autogen/0.2/ + mkdir -p artifact/0.2/ + cp -r build/* artifact/0.2/ - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/single-python-package.yml b/.github/workflows/single-python-package.yml new file mode 100644 index 000000000000..f0c8a01d7fac --- /dev/null +++ b/.github/workflows/single-python-package.yml @@ -0,0 +1,41 @@ +name: Deploy single package + +on: + workflow_dispatch: + inputs: + package: + description: 'Select the package to deploy' + required: true + type: choice + options: + - autogen-agentchat + - autogen-core + - autogen-ext + - agbench + - autogen-magentic-one + - autogen-studio + ref: + description: 'Tag to deploy' + required: true + +jobs: + deploy-package: + environment: + name: package + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.ref }} + # Require ref to be a tag + - run: git show-ref --verify refs/tags/${{ github.event.inputs.ref }} + - run: curl -LsSf https://astral.sh/uv/install.sh | sh + - run: uv build --package ${{ github.event.inputs.package }} --out-dir dist/ + working-directory: python + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: python/dist/ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md deleted file mode 100644 index 4726588453b4..000000000000 --- a/CONTRIBUTORS.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contributors - -## Special thanks to all the people who help this project: -> These individuals dedicate their time and expertise to improve this project. We are deeply grateful for their contributions. - -| Name | GitHub Handle | Organization | Features | Roadmap Lead | Additional Information | -|---|---|---|---|---|---| -| Qingyun Wu | [qingyun-wu](https://github.com/qingyun-wu) | Penn State University | all, alt-models, autobuilder | Yes | Available most of the time (US Eastern Time) | -| Chi Wang | [sonichi](https://github.com/sonichi) | - | all | Yes | | -| Li Jiang | [thinkall](https://github.com/thinkall) | Microsoft | rag, autobuilder, group chat | Yes | [Issue #1657](https://github.com/microsoft/autogen/issues/1657) - Beijing, GMT+8 | -| Mark Sze | [marklysze](https://github.com/marklysze) | - | alt-models, group chat | No | Generally available (Sydney, AU time) - Group Chat "auto" speaker selection | -| Hrushikesh Dokala | [Hk669](https://github.com/Hk669) | - | alt-models, swebench, logging, rag | No | [Issue #2946](https://github.com/microsoft/autogen/issues/2946), [Pull Request #2933](https://github.com/microsoft/autogen/pull/2933) - Available most of the time (India, GMT+5:30) | -| Jiale Liu | [LeoLjl](https://github.com/LeoLjl) | Penn State University | autobuild, group chat | No | | -| Shaokun Zhang | [skzhang1](https://github.com/skzhang1) | Penn State University | AgentOptimizer, Teachability | Yes | [Issue #521](https://github.com/microsoft/autogen/issues/521) | -| Rajan Chari | [rajan-chari](https://github.com/rajan-chari) | Microsoft Research | CAP, Survey of other frameworks | No | | -| Victor Dibia | [victordibia](https://github.com/victordibia) | Microsoft Research | autogenstudio | Yes | [Issue #737](https://github.com/microsoft/autogen/issues/737) | -| Yixuan Zhai | [randombet](https://github.com/randombet) | Meta | group chat, sequential_chats, rag | No | | -| Xiaoyun Zhang | [LittleLittleCloud](https://github.com/LittleLittleCloud) | Microsoft | AutoGen.Net, group chat | Yes | [Backlog - AutoGen.Net](https://github.com/microsoft/autogen/issues) - Available most of the time (PST) | -| Yiran Wu | [yiranwu0](https://github.com/yiranwu0) | Penn State University | alt-models, group chat, logging | Yes | | -| Beibin Li | [BeibinLi](https://github.com/BeibinLi) | Microsoft Research | alt-models | Yes | | -| Gagan Bansal | [gagb](https://github.com/gagb) | Microsoft Research | All | | | -| Adam Fourney | [afourney](https://github.com/afourney) | Microsoft Research | Complex Tasks | | | -| Ricky Loynd | [rickyloynd-microsoft](https://github.com/rickyloynd-microsoft) | Microsoft Research | Teachability | | | -| Eric Zhu | [ekzhu](https://github.com/ekzhu) | Microsoft Research | All, Infra | | | -| Jack Gerrits | [jackgerrits](https://github.com/jackgerrits) | Microsoft Research | All, Infra | | | -| David Luong | [DavidLuong98](https://github.com/DavidLuong98) | Microsoft | AutoGen.Net | | | -| Davor Runje | [davorrunje](https://github.com/davorrunje) | airt.ai | Tool calling, IO | | Available most of the time (Central European Time) | -| Friederike Niedtner | [Friderike](https://www.microsoft.com/en-us/research/people/fniedtner/) | Microsoft Research | PM | | | -| Rafah Hosn | [Rafah](https://www.microsoft.com/en-us/research/people/raaboulh/) | Microsoft Research | PM | | | -| Robin Moeur | [Robin](https://www.linkedin.com/in/rmoeur/) | Microsoft Research | PM | | | -| Jingya Chen | [jingyachen](https://github.com/JingyaChen) | Microsoft | UX Design, AutoGen Studio | | | -| Suff Syed | [suffsyed](https://github.com/suffsyed) | Microsoft | UX Design, AutoGen Studio | | | - -## I would like to join this list. How can I help the project? -> We're always looking for new contributors to join our team and help improve the project. For more information, please refer to our [CONTRIBUTING](https://microsoft.github.io/autogen/docs/contributor-guide/contributing) guide. - - -## Are you missing from this list? -> Please open a PR to help us fix this. - - -## Acknowledgements -This template was adapted from [GitHub Template Guide](https://github.com/cezaraugusto/github-template-guidelines/blob/master/.github/CONTRIBUTORS.md) by [cezaraugusto](https://github.com/cezaraugusto). diff --git a/README.md b/README.md index efcefbd68a76..0bd036a9964f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
-AutoGen Logo +AutoGen Logo [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Follow%20%40pyautogen)](https://twitter.com/pyautogen) @@ -10,7 +10,7 @@ # AutoGen > [!IMPORTANT] -> AutoGen 0.4 is a from-the-ground-up rewrite of AutoGen. Learn more about the history, goals and future at [this blog post](https://microsoft.github.io/autogen/blog). We’re excited to work with the community to gather feedback, refine, and improve the project before we officially release 0.4. This is a big change, so AutoGen 0.2 is still available, maintained, and developed in the [0.2 branch](https://github.com/microsoft/autogen/tree/0.2). +> [AutoGen 0.4](https://microsoft.github.io/autogen/dev) is a from-the-ground-up rewrite of AutoGen. Learn more about the history, goals and future at [this blog post](https://microsoft.github.io/autogen/blog). We’re excited to work with the community to gather feedback, refine, and improve the project before we officially release 0.4. This is a big change, so AutoGen 0.2 is still available, maintained, and developed in the [0.2 branch](https://github.com/microsoft/autogen/tree/0.2). AutoGen is an open-source framework for building AI agent systems. It simplifies the creation of event-driven, distributed, scalable, and resilient agentic applications. @@ -47,14 +47,14 @@ AutoGen offers the following key features: AutoGen has several packages and is built upon a layered architecture. Currently, there are three main APIs your application can target: -- [Core](https://microsoft.github.io/autogen/dev/core-user-guide/index.html) -- [AgentChat](https://microsoft.github.io/autogen/dev/agentchat-user-guide/index.html) +- [Core](https://microsoft.github.io/autogen/dev/user-guide/core-user-guide/index.html) +- [AgentChat](https://microsoft.github.io/autogen/dev/user-guide/agentchat-user-guide/index.html) - [Extensions](https://microsoft.github.io/autogen/dev/reference/python/autogen_ext/autogen_ext.html) ## Core - [Installation](https://microsoft.github.io/autogen/dev/packages/index.html#pkg-info-autogen-core) -- [Quickstart](https://microsoft.github.io/autogen/dev/core-user-guide/guides/quickstart.html) +- [Quickstart](https://microsoft.github.io/autogen/dev/user-guide/core-user-guide/quickstart.html) The core API of AutoGen, `autogen-core`, is built following the [actor model](https://en.wikipedia.org/wiki/Actor_model). @@ -68,7 +68,7 @@ while still communicating with one another. ## AgentChat - [Installation](https://microsoft.github.io/autogen/dev/packages/index.html#pkg-info-autogen-agentchat) -- [Quickstart](https://microsoft.github.io/autogen/dev/agentchat-user-guide/guides/quickstart.html) +- [Quickstart](https://microsoft.github.io/autogen/dev/user-guide/agentchat-user-guide/quickstart.html) The AgentChat API, `autogen-agentchat`, is task driven and at a high level like AutoGen 0.2. It allows you to define conversational agents, compose them into teams and then @@ -106,21 +106,32 @@ The following code uses code execution, you need to have [Docker installed](http and running on your machine. ```python +import asyncio +import logging +from autogen_agentchat import EVENT_LOGGER_NAME from autogen_agentchat.agents import CodeExecutorAgent, CodingAssistantAgent +from autogen_agentchat.logging import ConsoleLogHandler from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination -from autogen_core.components.code_executor import DockerCommandLineCodeExecutor +from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor from autogen_core.components.models import OpenAIChatCompletionClient -async with DockerCommandLineCodeExecutor(work_dir="coding") as code_executor: - code_executor_agent = CodeExecutorAgent("code_executor", code_executor=code_executor) - coding_assistant_agent = CodingAssistantAgent( - "coding_assistant", model_client=OpenAIChatCompletionClient(model="gpt-4o") - ) - group_chat = RoundRobinGroupChat([coding_assistant_agent, code_executor_agent]) - result = await group_chat.run( - task="Create a plot of NVDIA and TSLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.", - termination_condition=StopMessageTermination(), - ) +logger = logging.getLogger(EVENT_LOGGER_NAME) +logger.addHandler(ConsoleLogHandler()) +logger.setLevel(logging.INFO) + +async def main() -> None: + async with DockerCommandLineCodeExecutor(work_dir="coding") as code_executor: + code_executor_agent = CodeExecutorAgent("code_executor", code_executor=code_executor) + coding_assistant_agent = CodingAssistantAgent( + "coding_assistant", model_client=OpenAIChatCompletionClient(model="gpt-4o", api_key="YOUR_API_KEY") + ) + group_chat = RoundRobinGroupChat([coding_assistant_agent, code_executor_agent]) + result = await group_chat.run( + task="Create a plot of NVDIA and TSLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.", + termination_condition=StopMessageTermination(), + ) + +asyncio.run(main()) ``` ### C# diff --git a/dotnet/samples/Hello/Program.cs b/dotnet/samples/Hello/Program.cs index ef55511d044f..26df86295eba 100644 --- a/dotnet/samples/Hello/Program.cs +++ b/dotnet/samples/Hello/Program.cs @@ -47,7 +47,6 @@ public async Task Handle(ConversationClosed item) Message = goodbye }.ToCloudEvent(this.AgentId.Key); await PublishEvent(evt).ConfigureAwait(false); - await Task.Delay(60000); await App.ShutdownAsync(); } public async Task SayHello(string ask) diff --git a/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs b/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs index d5813682030e..5d3c2033435f 100644 --- a/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs +++ b/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs @@ -2,6 +2,7 @@ // FunctionCallGenerator.cs using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; @@ -100,7 +101,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }; var functionSource = functionTT.TransformText(); - var fileName = $"{className}.generated.cs"; + // Avoid conflict with filename for parallel builds targeting several .NET versions + // at once. Without unique filenames, the build will fail with the 'IOException' + // with message 'The process cannot access the file '%TEMP%\{className}.generated.cs' + var fileName = $"{className}_{System.Guid.NewGuid()}.generated.cs"; ctx.AddSource(fileName, SourceText.From(functionSource, System.Text.Encoding.UTF8)); File.WriteAllText(Path.Combine(Path.GetTempPath(), fileName), functionSource); @@ -110,25 +114,29 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { var overallFunctionDefinition = source.Right.SelectMany(x => x!.FunctionContracts.Select(y => new { fullClassName = x.FullClassName, y = y })); var overallFunctionDefinitionObject = overallFunctionDefinition.Select( - x => new + x => { - fullClassName = x.fullClassName, - functionDefinition = new + Debug.Assert(x.y.Parameters != null, "x.y.Parameters != null"); + return new { - x.y.Name, - x.y.Description, - x.y.ReturnType, - Parameters = x.y.Parameters.Select(y => new + fullClassName = x.fullClassName, + functionDefinition = new { - y.Name, - y.Description, - y.JsonType, - y.JsonItemType, - y.Type, - y.IsOptional, - y.DefaultValue, - }), - }, + x.y.Name, + x.y.Description, + x.y.ReturnType, + Parameters = x.y.Parameters.Select(y => new + { + y.Name, + y.Description, + y.JsonType, + y.JsonItemType, + y.Type, + y.IsOptional, + y.DefaultValue, + }), + }, + }; }); var json = JsonConvert.SerializeObject(overallFunctionDefinitionObject, formatting: Formatting.Indented); diff --git a/dotnet/src/Microsoft.AutoGen.Agents/Client/AgentWorkerRuntime.cs b/dotnet/src/Microsoft.AutoGen.Agents/Client/AgentWorkerRuntime.cs index 5483efd72bc2..8fe2d8b1c381 100644 --- a/dotnet/src/Microsoft.AutoGen.Agents/Client/AgentWorkerRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen.Agents/Client/AgentWorkerRuntime.cs @@ -99,11 +99,21 @@ private async Task RunReadPump() } } } - catch (Exception ex) + catch (OperationCanceledException) + { + // Time to shut down. + break; + } + catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) { _logger.LogError(ex, "Error reading from channel."); channel = RecreateChannel(channel); } + catch + { + // Shutdown requested. + break; + } } } @@ -113,34 +123,39 @@ private async Task RunWritePump() var outboundMessages = _outboundMessagesChannel.Reader; while (!_shutdownCts.IsCancellationRequested) { - await outboundMessages.WaitToReadAsync().ConfigureAwait(false); - - // Read the next message if we don't already have an unsent message - // waiting to be sent. - if (!outboundMessages.TryRead(out var message)) + try { - break; - } + await outboundMessages.WaitToReadAsync().ConfigureAwait(false); - while (!_shutdownCts.IsCancellationRequested) - { - try + // Read the next message if we don't already have an unsent message + // waiting to be sent. + if (!outboundMessages.TryRead(out var message)) { - await channel.RequestStream.WriteAsync(message, _shutdownCts.Token).ConfigureAwait(false); break; } - catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) - { - _logger.LogError(ex, "Error writing to channel."); - channel = RecreateChannel(channel); - continue; - } - catch + + while (!_shutdownCts.IsCancellationRequested) { - // Shutdown requested. + await channel.RequestStream.WriteAsync(message, _shutdownCts.Token).ConfigureAwait(false); break; } } + catch (OperationCanceledException) + { + // Time to shut down. + break; + } + catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) + { + _logger.LogError(ex, "Error writing to channel."); + channel = RecreateChannel(channel); + continue; + } + catch + { + // Shutdown requested. + break; + } } } @@ -286,10 +301,6 @@ void StartCore() public async Task StopAsync(CancellationToken cancellationToken) { _shutdownCts.Cancel(); - lock (_channelLock) - { - _channel?.Dispose(); - } _outboundMessagesChannel.Writer.TryComplete(); @@ -302,6 +313,10 @@ public async Task StopAsync(CancellationToken cancellationToken) { await writeTask.ConfigureAwait(false); } + lock (_channelLock) + { + _channel?.Dispose(); + } } public ValueTask SendRequest(RpcRequest request) diff --git a/dotnet/src/Microsoft.AutoGen.Agents/Client/App.cs b/dotnet/src/Microsoft.AutoGen.Agents/Client/App.cs index 65a4abb327f6..cb25863d6f3b 100644 --- a/dotnet/src/Microsoft.AutoGen.Agents/Client/App.cs +++ b/dotnet/src/Microsoft.AutoGen.Agents/Client/App.cs @@ -49,7 +49,7 @@ public static async ValueTask ShutdownAsync() { throw new InvalidOperationException("Client not started"); } - await RuntimeApp!.StopAsync(); await ClientApp.StopAsync(); + await RuntimeApp!.StopAsync(); } } diff --git a/dotnet/src/Microsoft.AutoGen.Agents/Runtime/WorkerProcessConnection.cs b/dotnet/src/Microsoft.AutoGen.Agents/Runtime/WorkerProcessConnection.cs index b4b7e1e1acc0..23febaaa4df9 100644 --- a/dotnet/src/Microsoft.AutoGen.Agents/Runtime/WorkerProcessConnection.cs +++ b/dotnet/src/Microsoft.AutoGen.Agents/Runtime/WorkerProcessConnection.cs @@ -87,6 +87,9 @@ public async Task RunReadPump() _gateway.OnReceivedMessageAsync(this, message).Ignore(); } } + catch (OperationCanceledException) + { + } finally { _shutdownCancellationToken.Cancel(); @@ -104,6 +107,9 @@ public async Task RunWritePump() await ResponseStream.WriteAsync(message); } } + catch (OperationCanceledException) + { + } finally { _shutdownCancellationToken.Cancel(); diff --git a/python/benchmarks/.gitignore b/python/benchmarks/.gitignore deleted file mode 100644 index fc9b39c1efef..000000000000 --- a/python/benchmarks/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*/Results/ -*/Tasks/ \ No newline at end of file diff --git a/python/benchmarks/README.md b/python/benchmarks/README.md deleted file mode 100644 index b4dd6c51f259..000000000000 --- a/python/benchmarks/README.md +++ /dev/null @@ -1 +0,0 @@ -Moved to another repo. diff --git a/python/packages/agbench/.gitignore b/python/packages/agbench/.gitignore index 2eccb6f6c69f..a71b56ef3753 100644 --- a/python/packages/agbench/.gitignore +++ b/python/packages/agbench/.gitignore @@ -1,3 +1,3 @@ scenarios/*/Downloads scenarios/*/Tasks -*/Results +*/Results \ No newline at end of file diff --git a/python/packages/agbench/CONTRIBUTING.md b/python/packages/agbench/CONTRIBUTING.md index ec32906eea2e..0cd4c7cc8ac5 100644 --- a/python/packages/agbench/CONTRIBUTING.md +++ b/python/packages/agbench/CONTRIBUTING.md @@ -6,12 +6,11 @@ As part of the broader AutoGen project, AutoGenBench welcomes community contribu We ask that all contributions to AutoGenBench adhere to the following: - Follow AutoGen's broader [contribution guidelines](https://microsoft.github.io/autogen/docs/Contribute) -- All AutoGenBench benchmarks should live in a subfolder of `/samples/tools/autogenbench/scenarios` alongside `HumanEval`, `GAIA`, etc. +- All AutoGenBench benchmarks should live in a subfolder of `/benchmarks` alongside `HumanEval`, `GAIA`, etc. - Benchmark scenarios should include a detailed README.md, in the root of their folder, describing the benchmark and providing citations where warranted. - Benchmark data (tasks, ground truth, etc.) should be downloaded from their original sources rather than hosted in the AutoGen repository (unless the benchmark is original, and the repository *is* the original source) - You can use the `Scripts/init_tasks.py` file to automate this download. -- Basic scoring should be compatible with the `autogenbench tabulate` command (e.g., by outputting logs compatible with the default tabulation mechanism, or by providing a `Scripts/custom_tabulate.py` file) -- If you wish your benchmark to be compatible with the `autogenbench clone` command, include a `MANIFEST.json` file in the root of your folder. +- Basic scoring should be compatible with the `agbench tabulate` command (e.g., by outputting logs compatible with the default tabulation mechanism, or by providing a `Scripts/custom_tabulate.py` file) These requirements are further detailed below, but if you simply copy the `HumanEval` folder, you will already be off to a great start. @@ -62,16 +61,16 @@ For example: In this example, the string `__MODEL__` will be replaced in the file `scenarios.py`, while the string `__PROMPT__` will be replaced in the `prompt.txt` file. -The `template` field can also take on a list value, but this usage is considered advanced and is not described here. See the `autogenbench/run_cmd.py` code, or the `GAIA` benchmark tasks files for additional information about this option. +The `template` field can also take on a list value, but this usage is considered advanced and is not described here. See the `agbench/run_cmd.py` code, or the `GAIA` benchmark tasks files for additional information about this option. ## Task Instance Expansion Algorithm -Once the tasks have been defined, as per above, they must be "instantiated" before they can be run. This instantiation happens automatically when the user issues the `autogenbench run` command and involves creating a local folder to share with Docker. Each instance and repetition gets its own folder along the path: `./results/[scenario]/[task_id]/[instance_id]`. For the sake of brevity we will refer to this folder as the `DEST_FOLDER`. +Once the tasks have been defined, as per above, they must be "instantiated" before they can be run. This instantiation happens automatically when the user issues the `agbench run` command and involves creating a local folder to share with Docker. Each instance and repetition gets its own folder along the path: `./results/[scenario]/[task_id]/[instance_id]`. For the sake of brevity we will refer to this folder as the `DEST_FOLDER`. The algorithm for populating the `DEST_FOLDER` is as follows: -1. Pre-populate DEST_FOLDER with all the basic starter files for running a scenario (found in `autogenbench/template`). +1. Pre-populate DEST_FOLDER with all the basic starter files for running a scenario (found in `agbench/template`). 2. Recursively copy the template folder specified in the JSONL line to DEST_FOLDER (if the JSON `template` attribute points to a folder) If the JSONs `template` attribute instead points to a file, copy the file, but rename it to `scenario.py` 3. Apply any string replacements, as outlined in the prior section. 4. Write a run.sh file to DEST_FOLDER that will be executed by Docker when it is loaded. The `run.sh` is described below. @@ -139,9 +138,8 @@ echo RUN.SH COMPLETE !#!# Be warned that this listing is provided here for illustration purposes, and may vary over time. The source of truth are the `run.sh` files found in the ``./results/[taskset]/[task_id]/[instance_id]`` folders. -## Integrating with the `tabulate` and `clone` commands. - -The above details are sufficient for defining and running tasks, but if you wish to support the `autogenbench tabulate` and `autogenbench clone` commands, a few additional steps are required. +## Integrating with the `tabulate` +The above details are sufficient for defining and running tasks, but if you wish to support the `agbench tabulate` commands, a few additional steps are required. ### Tabulations @@ -154,35 +152,10 @@ Should you provide a custom tabulation script, please implement `--help` and `-h The `scenarios/GAIA/Scripts/custom_tabulate.py` is a great example of custom tabulation. It also shows how you can reuse some components of the default tabulator to speed up development. -### Cloning - -If you wish your benchmark to be available via the `autogenbench clone` command, you will need to take three additional steps: - -#### Manifest -First, provide a `MANIFEST.json` file in the root of your benchmark. An example is provided below, from which you can see the schema: - -```json -{ - "files": { - "Templates/TwoAgents/prompt.txt": "Templates/TwoAgents/prompt.txt", - "Templates/TwoAgents/coding/my_tests.py": "Templates/TwoAgents/coding/my_tests.py", - "Templates/TwoAgents/scenario.py": "Templates/TwoAgents/scenario.py", - "README.md": "README.md", - "Scripts/init_tasks.py": "Scripts/init_tasks.py", - "Scripts/custom_tabulate.py": "Scripts/custom_tabulate.py" - } -} -``` - -The keys of the `files` dictionary are local paths, relative to your benchmark's root directory. The values are relative paths in the AutoGen GitHub repository (relative to the folder where the MANIFEST.json file is located). In most cases, the keys and values will be identical. - -#### SCENARIOS dictionary -Second, you must add an entry to the `scenarios` dictionary in `autogen/samples/tools/autogenbench/scenarios/MANIFEST.json`. -#### Scripts/init_tasks.py -Finally, you should provide an `Scripts/init_tasks.py` file, in your benchmark folder, and include a `main()` method therein. This method will be loaded and called automatically by `autogenbench clone` after all manifest files have been downloaded. +## Scripts/init_tasks.py +Finally, you should provide an `Scripts/init_tasks.py` file, in your benchmark folder, and include a `main()` method therein. This `init_tasks.py` script is a great place to download benchmarks from their original sources and convert them to the JSONL format required by AutoGenBench: - See `HumanEval/Scripts/init_tasks.py` for an example of how to expand a benchmark from an original GitHub repository. - See `GAIA/Scripts/init_tasks.py` for an example of how to expand a benchmark from `Hugging Face Hub`. -- See `MATH/SCripts/init_tasks.py` for an example of how to expand a benchmark from an author-hosted website. diff --git a/python/packages/agbench/README.md b/python/packages/agbench/README.md index 108895cb7578..e0b9c1c84694 100644 --- a/python/packages/agbench/README.md +++ b/python/packages/agbench/README.md @@ -1,29 +1,35 @@ # AutoGenBench -AutoGenBench is a tool for repeatedly running a set of pre-defined AutoGen tasks in a setting with tightly-controlled initial conditions. With each run, AutoGenBench will start from a blank slate. The agents being evaluated will need to work out what code needs to be written, and what libraries or dependencies to install, to solve tasks. The results of each run are logged, and can be ingested by analysis or metrics scripts (such as `autogenbench tabulate`). By default, all runs are conducted in freshly-initialized docker containers, providing the recommended level of consistency and safety. +AutoGenBench (agbench) is a tool for repeatedly running a set of pre-defined AutoGen tasks in a setting with tightly-controlled initial conditions. With each run, AutoGenBench will start from a blank slate. The agents being evaluated will need to work out what code needs to be written, and what libraries or dependencies to install, to solve tasks. The results of each run are logged, and can be ingested by analysis or metrics scripts (such as `agbench tabulate`). By default, all runs are conducted in freshly-initialized docker containers, providing the recommended level of consistency and safety. AutoGenBench works with all AutoGen 0.1.*, and 0.2.* versions. ## Technical Specifications -If you are already an AutoGenBench pro, and want the full technical specifications, please review the [contributor's guide](CONTRIBUTING.md). - +If you are already an AutoGenBench pro, and want the full technical specifications, please review the [contributor's guide](CONTRIBUTING.md). ## Docker Requirement + AutoGenBench also requires Docker (Desktop or Engine). **It will not run in GitHub codespaces**, unless you opt for native execution (with is strongly discouraged). To install Docker Desktop see [https://www.docker.com/products/docker-desktop/](https://www.docker.com/products/docker-desktop/). +If you are working in WSL, you can follow the instructions below to set up your environment: + +1. Install Docker Desktop. After installation, restart is needed, then open Docker Desktop, in Settings, Ressources, WSL Integration, Enable integration with additional distros – Ubuntu +2. Clone autogen and export `AUTOGEN_REPO_BASE`. This environment variable enables the Docker containers to use the correct version agents. + ```bash + git clone git@github.com:microsoft/autogen.git + export AUTOGEN_REPO_BASE= + ``` + ## Installation and Setup -**To get the most out of AutoGenBench, the `autogenbench` package should be installed**. At present, the easiest way to do this is to install it via `pip`: +[Deprecated currently] **To get the most out of AutoGenBench, the `agbench` package should be installed**. At present, the easiest way to do this is to install it via `pip`. -``` -pip install autogenbench -``` -If you would prefer working from source code (e.g., for development, or to utilize an alternate branch), simply clone the [AutoGen](https://github.com/microsoft/autogen) repository, then install `autogenbench` via: +If you would prefer working from source code (e.g., for development, or to utilize an alternate branch), simply clone the [AutoGen](https://github.com/microsoft/autogen) repository, then install `agbench` via: ``` -pip install -e autogen/samples/tools/autogenbench +pip install -e autogen/python/packages/agbench ``` After installation, you must configure your API keys. As with other AutoGen applications, AutoGenBench will look for the OpenAI keys in the OAI_CONFIG_LIST file in the current working directory, or the OAI_CONFIG_LIST environment variable. This behavior can be overridden using a command-line parameter described later. @@ -36,7 +42,6 @@ export OAI_CONFIG_LIST=$(cat ./OAI_CONFIG_LIST) If an OAI_CONFIG_LIST is *not* provided (by means of file or environment variable), AutoGenBench will use the OPENAI_API_KEY environment variable instead. - For some benchmark scenarios, additional keys may be required (e.g., keys for the Bing Search API). These can be added to an `ENV.json` file in the current working folder. An example `ENV.json` file is provided below: ``` @@ -46,75 +51,106 @@ For some benchmark scenarios, additional keys may be required (e.g., keys for th ``` ## A Typical Session + Once AutoGenBench and necessary keys are installed, a typical session will look as follows: -``` -autogenbench clone HumanEval -cd HumanEval -autogenbench run Tasks/r_human_eval_two_agents.jsonl -autogenbench tabulate results/r_human_eval_two_agents -``` -Where: -- `autogenbench clone HumanEval` downloads and expands the HumanEval benchmark scenario. -- `autogenbench run Tasks/r_human_eval_two_agents.jsonl` runs the tasks defined in `Tasks/r_human_eval_two_agents.jsonl` -- `autogenbench tablue results/r_human_eval_two_agents` tabulates the results of the run -Each of these commands has extensive in-line help via: +Navigate to HumanEval -- `autogenbench --help` -- `autogenbench clone --help` -- `autogenbench run --help` -- `autogenbench tabulate --help` +```bash +cd autogen/python/packages/agbench/benchmarks/HumanEval +``` +**Note:** The following instructions are specific to the HumanEval benchmark. For other benchmarks, please refer to the README in the respective benchmark folder, e.g.,: [AssistantBench](benchmarks/AssistantBench/README.md). -**NOTE:** If you are running `autogenbench` from within the repository, you don’t need to run `autogenbench clone`. Instead, navigate to the appropriate scenario folder (e.g., `scenarios/HumanEval`) and run the `Scripts/init_tasks.py` file. -More details of each command are provided in the sections that follow. +Create a file called ENV.json with the following (required) contents (If you're using MagenticOne), if using Azure: -## Cloning Benchmarks -To clone an existing benchmark, simply run: -``` -autogenbench clone [BENCHMARK] +```json +{ + "CHAT_COMPLETION_KWARGS_JSON": "{}", + "CHAT_COMPLETION_PROVIDER": "azure" +} ``` -For example, +You can also use the openai client by replacing the last two entries in the ENV file by: + +- `CHAT_COMPLETION_PROVIDER='openai'` +- `CHAT_COMPLETION_KWARGS_JSON` with the following JSON structure: +```json +{ + "api_key": "REPLACE_WITH_YOUR_API", + "model": "REPLACE_WITH_YOUR_MODEL" +} ``` -autogenbench clone HumanEval + +Now initialize the tasks. + +```bash +python Scripts/init_tasks.py ``` -To see which existing benchmarks are available to clone, run: +Note: This will attempt to download HumanEval + +Once the script completes, you should now see a folder in your current directory called `Tasks` that contains one JSONL file per template in `Templates`. + +Now to run a specific subset of HumanEval use: + +```bash +agbench run Tasks/human_eval_MagenticOne.jsonl ``` -autogenbench clone --list + +You should see the command line print the raw logs that shows the agents in action To see a summary of the results (e.g., task completion rates), in a new terminal run the following: + +```bash +agbench tabulate Results/human_eval_MagenticOne ``` -> Note: You might need to log in to HuggingFace to access certain datasets like GAIA. To do this, run `huggingface-cli login` in your terminal and follow the prompts. +Where: + +- `agbench run Tasks/human_eval_MagenticOne.jsonl` runs the tasks defined in `Tasks/human_eval_MagenticOne.jsonl` +- `agbench tablue results/human_eval_MagenticOne` tabulates the results of the run + +Each of these commands has extensive in-line help via: + +- `agbench --help` +- `agbench run --help` +- `agbench tabulate --help` +- `agbench remove_missing --help` + +**NOTE:** If you are running `agbench` from within the repository, you need to navigate to the appropriate scenario folder (e.g., `scenarios/HumanEval`) and run the `Scripts/init_tasks.py` file. + +More details of each command are provided in the sections that follow. + ## Running AutoGenBench To run a benchmark (which executes the tasks, but does not compute metrics), simply execute: + ``` cd [BENCHMARK] -autogenbench run Tasks +agbench run Tasks/*.jsonl ``` For example, + ``` cd HumanEval -autogenbench run Tasks +agbench run Tasks/human_eval_MagenticOne.jsonl ``` The default is to run each task once. To run each scenario 10 times, use: ``` -autogenbench run --repeat 10 Tasks +agbench run --repeat 10 Tasks/human_eval_MagenticOne.jsonl ``` -The `autogenbench` command-line tool allows a number of command-line arguments to control various parameters of execution. Type ``autogenbench -h`` to explore these options: +The `agbench` command-line tool allows a number of command-line arguments to control various parameters of execution. Type ``agbench -h`` to explore these options: ``` -'autogenbench run' will run the specified autogen scenarios for a given number of repetitions and record all logs and trace information. When running in a Docker environment (default), each run will begin from a common, tightly controlled, environment. The resultant logs can then be further processed by other scripts to produce metrics. +'agbench run' will run the specified autogen scenarios for a given number of repetitions and record all logs and trace information. When running in a Docker environment (default), each run will begin from a common, tightly controlled, environment. The resultant logs can then be further processed by other scripts to produce metrics. positional arguments: scenario The JSONL scenario file to run. If a directory is specified, @@ -140,7 +176,7 @@ options: The requirements file to pip install before running the scenario. -d DOCKER_IMAGE, --docker-image DOCKER_IMAGE The Docker image to use when running scenarios. Can not be used together with --native. (default: - 'autogenbench:default', which will be created if not present) + 'agbench:default', which will be created if not present) --native Run the scenarios natively rather than in docker. NOTE: This is not advisable, and should be done with great caution. ``` @@ -171,4 +207,4 @@ Within each folder, you will find the following files: ## Contributing or Defining New Tasks or Benchmarks -If you would like to develop -- or even contribute -- your own tasks or benchmarks, please review the [contributor's guide](CONTRIBUTING.md) for complete technical details. +If you would like to develop -- or even contribute -- your own tasks or benchmarks, please review the [contributor's guide](CONTRIBUTING.md) for complete technical details. diff --git a/python/packages/agbench/pyproject.toml b/python/packages/agbench/pyproject.toml index f028152b766b..7cdfc89c0ec9 100644 --- a/python/packages/agbench/pyproject.toml +++ b/python/packages/agbench/pyproject.toml @@ -24,7 +24,8 @@ dependencies = [ "huggingface_hub", "tabulate", "azure-identity", - "pandas" + "pandas", + "scipy" ] [tool.uv] diff --git a/python/packages/agbench/src/agbench/cli.py b/python/packages/agbench/src/agbench/cli.py index e79198e3f47d..bb11c9f932ce 100644 --- a/python/packages/agbench/src/agbench/cli.py +++ b/python/packages/agbench/src/agbench/cli.py @@ -3,6 +3,7 @@ from typing_extensions import TypedDict +from .remove_missing_cmd import remove_missing_cli from .run_cmd import run_cli from .tabulate_cmd import tabulate_cli from .version import __version__ @@ -32,6 +33,11 @@ def main(args: Optional[List[str]] = None) -> None: "description": "tabulate the results of a previous run", "function": tabulate_cli, }, + { + "command": "remove_missing", + "description": "remove folders with missing results", + "function": remove_missing_cli, + }, { "command": "--version", "description": f"print the version of {invocation_cmd}", @@ -68,12 +74,7 @@ def main(args: Optional[List[str]] = None) -> None: {invocation_cmd} is a tool for running and managing AutoGen benchmark scenarios. A typically session might resemble: - {invocation_cmd} clone HumanEval - cd HumanEval - {invocation_cmd} run Tasks/human_eval_two_agents_gpt4.jsonl - -which will download the HumanEval benchmark, expand it, and then run the benchmark once with the `human_eval_two_agents_gpt4` configuration. - +\ Available COMMANDs include: {commands_details} @@ -81,7 +82,6 @@ def main(args: Optional[List[str]] = None) -> None: Additionally, you can use the --help option with any command for further command-specific instructions. E.g., {invocation_cmd} run --help - {invocation_cmd} clone --help """.strip() diff --git a/python/packages/agbench/src/agbench/remove_missing_cmd.py b/python/packages/agbench/src/agbench/remove_missing_cmd.py new file mode 100644 index 000000000000..21c9a6aba572 --- /dev/null +++ b/python/packages/agbench/src/agbench/remove_missing_cmd.py @@ -0,0 +1,123 @@ +import argparse +import os +import shutil +import sys +from typing import Sequence + + +def default_scorer(instance_dir: str) -> bool: + """ + returns True if the instance_dir has the expected ending pattern in the console_log.txt file + """ + console_log = os.path.join(instance_dir, "console_log.txt") + if os.path.isfile(console_log): + with open(console_log, "rt") as fh: + content = fh.read() + # Use a regular expression to match the expected ending pattern + has_final_answer = "FINAL ANSWER:" in content + has_scenario_complete = "SCENARIO.PY COMPLETE !#!#" in content + has_run_complete = "RUN.SH COMPLETE !#!#" in content + # if so, return False + last_10_lines = content.splitlines()[-10:] + last_10_lines_joined = "\n".join(last_10_lines) + has_error_in_last_10_lines = "Error code" in last_10_lines_joined + has_all = has_final_answer and has_scenario_complete and has_run_complete and not has_error_in_last_10_lines + if not has_all: + print(content) + return has_all + return False + + +def delete_folders_with_missing_results(runlogs_path: str, noconfirm: bool = False) -> None: + deleted_folders = 0 + + for task_id in os.listdir(runlogs_path): + task_path = os.path.join(runlogs_path, task_id) + + if not os.path.isdir(task_path): + continue + + instance = 0 + has_missing_results = False + + while True: + instance_dir = os.path.join(task_path, str(instance)) + if not os.path.isdir(instance_dir): + if instance == 0: + print(f"Empty folder: {task_path}") + has_missing_results = True + break + if not default_scorer(instance_dir): + has_missing_results = True + break + + instance += 1 + if has_missing_results: + if not noconfirm: + print(f"Missing Results in : {task_path}") + user_confirmation = input("Press 1 to delete, anything else to skip...") + if user_confirmation == "1": + shutil.rmtree(task_path) + print(f"Deleted folder: {task_path}") + deleted_folders += 1 + else: + print(f"Skipping folder: {task_path}") + else: + shutil.rmtree(task_path) + print(f"Deleted folder: {task_path}") + deleted_folders += 1 + + print(f"Total folders deleted: {deleted_folders}") + + +def remove_missing_cli(args: Sequence[str]) -> None: + invocation_cmd = args[0] + args = args[1:] + runlogs_path = args[0] + + parser = argparse.ArgumentParser( + prog=invocation_cmd, + description=f"{invocation_cmd} will remove folders with missing results.", + ) + + parser.add_argument( + "runlogs", + help="The path where the run's logs are stored.", + ) + parser.add_argument( + "-c", + "--noconfirm", + action="store_true", + help="Disable confirmation prompt before deleting folders.", + ) + + parsed_args = parser.parse_args(args) + print(parsed_args) + if not os.path.isdir(parsed_args.runlogs): + print(f"Error: '{runlogs_path}' is not a valid directory.") + print("Usage: agbench remove_missing ") + + sys.exit(1) + if not parsed_args.noconfirm: + input( + "Did you modify the default_scorer function to match the expected ending pattern? Press Enter to continue..." + ) + + delete_folders_with_missing_results(parsed_args.runlogs, parsed_args.noconfirm) + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python remove_missing_cmd.py [-c]") + sys.exit(1) + + runlogs_path = sys.argv[1] + noconfirm = False + if len(sys.argv) == 3 and sys.argv[2] == "-c": + noconfirm = True + if not os.path.isdir(runlogs_path): + print(f"Error: '{runlogs_path}' is not a valid directory.") + sys.exit(1) + input("Did you modify the default_scorer function to match the expected ending pattern? Press Enter to continue...") + + delete_folders_with_missing_results(runlogs_path, noconfirm) diff --git a/python/packages/agbench/src/agbench/run_cmd.py b/python/packages/agbench/src/agbench/run_cmd.py index e6a3b22697bc..9d94d79170fe 100644 --- a/python/packages/agbench/src/agbench/run_cmd.py +++ b/python/packages/agbench/src/agbench/run_cmd.py @@ -10,7 +10,8 @@ import sys import time import traceback -from typing import Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Union, cast +from multiprocessing import Pool +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Union, cast import docker from azure.core.exceptions import ClientAuthenticationError @@ -244,7 +245,9 @@ def expand_scenario(scenario_dir: str, scenario: ScenarioInstance, output_dir: s fh.write(line) -def get_scenario_env(token_provider: Optional[Callable[[], str]], env_file: str = DEFAULT_ENV_FILE) -> Dict[str, str]: +def get_scenario_env( + token_provider: Optional[Callable[[], str]] = None, env_file: str = DEFAULT_ENV_FILE +) -> Dict[str, str]: """ Return a dictionary of environment variables needed to run a scenario. @@ -269,7 +272,12 @@ def get_scenario_env(token_provider: Optional[Callable[[], str]], env_file: str azure_openai_ad_token = os.environ.get("AZURE_OPENAI_AD_TOKEN") if not azure_openai_ad_token and token_provider: azure_openai_ad_token = token_provider() - + if not azure_openai_ad_token: + azure_token_provider = get_azure_token_provider() + if azure_token_provider: + azure_openai_ad_token = azure_token_provider() + else: + logging.warning("No Azure AD token provider found. Azure AD token not set.") if azure_openai_ad_token is not None and len(azure_openai_ad_token.strip()) > 0: env["AZURE_OPENAI_AD_TOKEN"] = azure_openai_ad_token @@ -305,7 +313,7 @@ def run_scenario_natively(work_dir: str, env: Mapping[str, str], timeout: int = f.write( f"""# echo RUN.SH STARTING !#!# -export AGNEXT_TESTBED_SETTING="Native" +export AUTOGEN_TESTBED_SETTING="Native" echo "agbench version: {__version__}" > timestamp.txt # Create and activate the virtual environment @@ -425,7 +433,7 @@ def run_scenario_in_docker( f.write( f"""# echo RUN.SH STARTING !#!# -export AGNEXT_TESTBED_SETTING="Docker" +export AUTOGEN_TESTBED_SETTING="Docker" umask 000 echo "agbench version: {__version__}" > timestamp.txt @@ -477,20 +485,20 @@ def run_scenario_in_docker( # Figure out what folders to mount volumes = {str(pathlib.Path(work_dir).absolute()): {"bind": "/workspace", "mode": "rw"}} - # Add the autogen_core repo if we can find it - agnext_repo_base = os.environ.get("AGNEXT_REPO_BASE") - if agnext_repo_base is None: - agnext_repo_base = find_agnext_repo(os.getcwd()) - elif not os.path.isdir(agnext_repo_base): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), agnext_repo_base) + # Add the autogen repo if we can find it + autogen_repo_base = os.environ.get("AUTOGEN_REPO_BASE") + if autogen_repo_base is None: + autogen_repo_base = find_autogen_repo(os.getcwd()) + elif not os.path.isdir(autogen_repo_base): + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), autogen_repo_base) - if agnext_repo_base is None: + if autogen_repo_base is None: raise ValueError( - "Could not find AutoGen repo base. Please set the environment variable AGNEXT_REPO_BASE to the correct value." + "Could not find AutoGen repo base. Please set the environment variable AUTOGEN_REPO_BASE to the correct value." ) - agnext_repo_base = os.path.join(agnext_repo_base, "python") - volumes[str(pathlib.Path(agnext_repo_base).absolute())] = {"bind": "/autogen_core", "mode": "rw"} + autogen_repo_base = os.path.join(autogen_repo_base, "python") + volumes[str(pathlib.Path(autogen_repo_base).absolute())] = {"bind": "/autogen_python", "mode": "rw"} print("Mounting:") for k in volumes: @@ -583,7 +591,7 @@ def build_default_docker_image(docker_client: docker.DockerClient, image_tag: st sys.stdout.write(segment["stream"]) -def find_agnext_repo(path: str) -> Optional[str]: +def find_autogen_repo(path: str) -> Optional[str]: """ Utility for identifying if the path is a subdirectory of the autogen_core repo. @@ -611,6 +619,135 @@ def find_agnext_repo(path: str) -> Optional[str]: return None +def split_jsonl(file_path: str, num_parts: int) -> List[List[Dict[str, Any]]]: + """ + Split a JSONL file into num_parts approximately equal parts. + """ + with open(file_path, "r") as f: + data = [json.loads(line) for line in f] + + random.shuffle(data) # Shuffle the data for better distribution + chunk_size = len(data) // num_parts + return [data[i : i + chunk_size] for i in range(0, len(data), chunk_size)] + + +def mkdir_p(path: str) -> None: + """ + Create a directory if it doesn't exist, handling race conditions. + """ + try: + os.makedirs(path, exist_ok=True) + except OSError as exc: + if exc.errno != errno.EEXIST: + raise + + +def run_scenarios_subset( + scenario_name: str, + scenarios: List[Dict[str, Any]], + n_repeats: int, + is_native: bool, + docker_image: Optional[str] = None, + results_dir: str = "Results", + subsample: Union[None, int, float] = None, +) -> None: + """ + Run a subset of agbench scenarios a given number of times. + """ + for instance in scenarios: + # Create a folder to store the results + # Results base + + mkdir_p(results_dir) + + # Results for the scenario + + results_scenario = os.path.join(results_dir, scenario_name) + mkdir_p(results_scenario) + + # Results for the instance + results_instance = os.path.join(results_scenario, instance["id"]) + mkdir_p(results_instance) + + # Results for the repeats + for i in range(0, n_repeats): + results_repetition = os.path.join(results_instance, str(i)) + + # Skip it if it already exists + if os.path.isdir(results_repetition): + print(f"Found folder {results_repetition} ... Skipping.") + continue + print(f"Running scenario {results_repetition}") + + # Expand the scenario + expand_scenario(".", instance, results_repetition) # type: ignore + + # Prepare the environment (keys/values that need to be added) + env = get_scenario_env() + + # Run the scenario + if is_native: + run_scenario_natively(results_repetition, env) + else: + run_scenario_in_docker( + results_repetition, + env, + docker_image=docker_image, + ) + + +def run_parallel(args: argparse.Namespace) -> None: + """ + Run scenarios in parallel. + """ + # Read and split the JSONL file + scenarios = split_jsonl(args.scenario, args.parallel) + scenario_name_parts = os.path.basename(args.scenario).split(".") + scenario_name_parts.pop() + scenario_name = ".".join(scenario_name_parts) + + # Create a pool of worker processes + with Pool(processes=args.parallel) as pool: + # Prepare arguments for each worker + worker_args = [ + ( + scenario_name, + scenario_subset, + args.repeat, + args.native, + args.docker_image, + "Results", + args.subsample, + ) + for scenario_subset in scenarios + ] + + # Run scenarios in parallel + pool.starmap(run_scenarios_subset, worker_args) + + +def get_azure_token_provider() -> Optional[Callable[[], str]]: + """ + Get the Azure bearer token generator if a token wasn't provided and there's any evidence of using Azure. + """ + if not os.environ.get("AZURE_OPENAI_AD_TOKEN") and os.path.isdir(pathlib.Path("~/.azure").expanduser()): + logging.disable(logging.CRITICAL) + try: + azure_token_provider = get_bearer_token_provider( + DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default" + ) + azure_token_provider() # Call it once to warm it up, and make sure it doesn't throw an error + print("Found Azure token provider.") + return azure_token_provider + except ClientAuthenticationError: + error_message = traceback.format_exc() + print( + f"Azure token provider failed loading. Try using 'az login --use-device-code':\n{error_message}\n\nContinuing without Azure token provider..." + ) + logging.disable(logging.NOTSET) + return None + + def run_cli(args: Sequence[str]) -> None: invocation_cmd = args[0] args = args[1:] @@ -639,6 +776,15 @@ def run_cli(args: Sequence[str]) -> None: help='Run on a subsample of the tasks in the JSONL file(s). If a decimal value is specified, then run on the given proportion of tasks in each file. For example "0.7" would run on 70%% of tasks, and "1.0" would run on 100%% of tasks. If an integer value is specified, then randomly select *that* number of tasks from each specified JSONL file. For example "7" would run tasks, while "1" would run only 1 task from each specified JSONL file. (default: 1.0; which is 100%%)', default=None, ) + + parser.add_argument( + "-p", + "--parallel", + type=int, + help="The number of parallel processes to run (default: 1).", + default=1, + ) + parser.add_argument( "-d", "--docker-image", @@ -656,6 +802,10 @@ def run_cli(args: Sequence[str]) -> None: parsed_args = parser.parse_args(args) + # don't support parallel and subsample together + if parsed_args.parallel > 1 and parsed_args.subsample is not None: + sys.exit("The options --parallel and --subsample can not be used together currently. Exiting.") + # Don't allow both --docker-image and --native on the same command if parsed_args.docker_image is not None and parsed_args.native: sys.exit("The options --native and --docker-image can not be used together. Exiting.") @@ -701,29 +851,17 @@ def run_cli(args: Sequence[str]) -> None: ) # Get the Azure bearer token generator if a token wasn't provided and there's any evidence of using Azure - azure_token_provider = None - if not os.environ.get("AZURE_OPENAI_AD_TOKEN") and os.path.isdir(pathlib.Path("~/.azure").expanduser()): - logging.disable(logging.CRITICAL) - try: - azure_token_provider = get_bearer_token_provider( - DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default" - ) - azure_token_provider() # Call it once to warm it up, and make sure it doesn't throw an error - print("Found Azure token provider.") - except ClientAuthenticationError: - error_message = traceback.format_exc() - azure_token_provider = None - print( - f"Azure token provider failed loading. Try using 'az login --use-device-code':\n{error_message}\n\nContinuing without Azure token provider..." - ) - logging.disable(logging.NOTSET) + azure_token_provider = get_azure_token_provider() # Run the scenario - run_scenarios( - scenario=parsed_args.scenario, - n_repeats=parsed_args.repeat, - is_native=True if parsed_args.native else False, - token_provider=azure_token_provider, - docker_image=parsed_args.docker_image, - subsample=subsample, - ) + if parsed_args.parallel > 1: + run_parallel(parsed_args) + else: + run_scenarios( + scenario=parsed_args.scenario, + n_repeats=parsed_args.repeat, + is_native=True if parsed_args.native else False, + token_provider=azure_token_provider, + docker_image=parsed_args.docker_image, + subsample=subsample, + ) diff --git a/python/packages/agbench/src/agbench/tabulate_cmd.py b/python/packages/agbench/src/agbench/tabulate_cmd.py index b946546e9e1b..4aac0e2d03d0 100644 --- a/python/packages/agbench/src/agbench/tabulate_cmd.py +++ b/python/packages/agbench/src/agbench/tabulate_cmd.py @@ -183,7 +183,12 @@ def _count_equals(value: Optional[bool], trial: int) -> int: footer_row = ["Failures"] for i in range(0, max_instances): - footer_row.append(_count_equals(False, i)) + # count how many are not True, and not None, could be False or any other value + failures = 0 + for row in all_results: + if isinstance(row[i + 1], tuple): + failures += row[i + 1][0] != 1 + footer_row.append(failures) footer.append(footer_row) footer_row = ["Missing"] @@ -196,6 +201,21 @@ def _count_equals(value: Optional[bool], trial: int) -> int: footer_row.append(footer[0][i + 1] + footer[1][i + 1] + footer[2][i + 1]) footer.append(footer_row) + footer_row = ["Average Success Rate"] + for i in range(0, max_instances): + footer_row.append(_count_equals(True, i) / (footer[0][i + 1] + footer[1][i + 1] + footer[2][i + 1])) + footer.append(footer_row) + + footer_row = ["Average Score"] + for i in range(0, max_instances): + avg_score_trial = 0.0 + for row in all_results: + if isinstance(row[i + 1], tuple): + avg_score_trial += row[i + 1][0] + avg_score_trial = avg_score_trial / len(all_results) + footer_row.append(avg_score_trial) + footer.append(footer_row) + table = deepcopy(all_results) for row in table: for trial in range(0, max_instances): diff --git a/python/packages/autogen-agentchat/README.md b/python/packages/autogen-agentchat/README.md index 83cec299caac..6d240e6f989a 100644 --- a/python/packages/autogen-agentchat/README.md +++ b/python/packages/autogen-agentchat/README.md @@ -1 +1,9 @@ -# autogen-agentchat +# AutoGen AgentChat + +- [Documentation](https://microsoft.github.io/autogen/dev/user-guide/agentchat-user-guide/index.html) + +## Package structure + +- `agents` are the building blocks for creating agents and built-in agents. +- `teams` are the building blocks for creating teams of agents and built-in teams, such as group chats. +- `logging` contains logging utilities. \ No newline at end of file diff --git a/python/packages/autogen-agentchat/pyproject.toml b/python/packages/autogen-agentchat/pyproject.toml index 6e8baf659c85..aacd5265e0c2 100644 --- a/python/packages/autogen-agentchat/pyproject.toml +++ b/python/packages/autogen-agentchat/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "autogen-agentchat" version = "0.4.0dev0" license = {file = "LICENSE-CODE"} -description = "AutoGen agent and group chat library" +description = "AutoGen agents and teams library" readme = "README.md" requires-python = ">=3.10" classifiers = [ diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_coding_assistant_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_coding_assistant_agent.py index f12658f8074e..36edd0bcd3d4 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_coding_assistant_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_coding_assistant_agent.py @@ -34,7 +34,7 @@ def __init__( If you want the user to save the code in a file before executing it, put # filename: inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user. If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try. When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible. -Reply "TERMINATE" in the end when everything is done.""", +Reply "TERMINATE" in the end when code has been executed and task is complete.""", ): super().__init__(name=name, description=description) self._model_client = model_client diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/logging/_console_log_handler.py b/python/packages/autogen-agentchat/src/autogen_agentchat/logging/_console_log_handler.py index f4badf787a3f..5ff7c689a587 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/logging/_console_log_handler.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/logging/_console_log_handler.py @@ -3,7 +3,6 @@ import sys from datetime import datetime -from .. import EVENT_LOGGER_NAME from ..agents import ChatMessage, StopMessage, TextMessage from ..teams._events import ( ContentPublishEvent, @@ -68,8 +67,3 @@ def emit(self, record: logging.LogRecord) -> None: sys.stdout.flush() else: raise ValueError(f"Unexpected log record: {record.msg}") - - -logger = logging.getLogger(EVENT_LOGGER_NAME) -logger.setLevel(logging.INFO) -logger.addHandler(ConsoleLogHandler()) diff --git a/python/packages/autogen-core/README.md b/python/packages/autogen-core/README.md index 1f05bd9942a1..bafa35dcbbd2 100644 --- a/python/packages/autogen-core/README.md +++ b/python/packages/autogen-core/README.md @@ -1,7 +1,6 @@ # AutoGen Core -- [Documentation](http://microsoft.github.io/autogen) -- [Examples](https://github.com/microsoft/autogen/tree/main/python/packages/autogen-core/samples) +- [Documentation](https://microsoft.github.io/autogen/dev/user-guide/core-user-guide/index.html) ## Package layering diff --git a/python/packages/autogen-core/docs/redirects/redirects.py b/python/packages/autogen-core/docs/redirects/redirects.py index 05bbc5e9455c..6fcca87ed7d7 100644 --- a/python/packages/autogen-core/docs/redirects/redirects.py +++ b/python/packages/autogen-core/docs/redirects/redirects.py @@ -10,21 +10,21 @@ HTML_REDIRECT_TEMPLATE = HTML_PAGE_TEMPLATE_FILE.open("r").read() REDIRECT_URLS_FILE = THIS_FILE_DIR / "redirect_urls.txt" -def generate_redirect(old_url: str, new_url: str, base_dir: Path): +def generate_redirect(file_to_write: str, new_url: str, base_dir: Path): # Create a new redirect page redirect_page = Template(HTML_REDIRECT_TEMPLATE).substitute(to_url=new_url) # If the url ends with /, add index.html - if old_url.endswith("/"): - old_url += "index.html" + if file_to_write.endswith("/"): + file_to_write += "index.html" else: - old_url += "/index.html" + file_to_write += "/index.html" - if old_url.startswith("/"): - old_url = old_url[1:] + if file_to_write.startswith("/"): + file_to_write = file_to_write[1:] # Create the path to the redirect page - redirect_page_path = base_dir / old_url + redirect_page_path = base_dir / file_to_write # Create the directory if it doesn't exist redirect_page_path.parent.mkdir(parents=True, exist_ok=True) @@ -49,7 +49,9 @@ def main(): # Replace /autogen/ with /autogen/0.2/ and generate redirect old_url = line.strip() new_url = old_url.replace("/autogen/", "/autogen/0.2/") - generate_redirect(old_url, new_url, base_dir) + # Deal with pages base path of /autogen/ + file_to_write = old_url.replace("/autogen/", "/") + generate_redirect(file_to_write, new_url, base_dir) if __name__ == '__main__': main() \ No newline at end of file diff --git a/python/packages/autogen-core/docs/src/conf.py b/python/packages/autogen-core/docs/src/conf.py index a5988cd49706..489e6534d9cf 100644 --- a/python/packages/autogen-core/docs/src/conf.py +++ b/python/packages/autogen-core/docs/src/conf.py @@ -98,7 +98,7 @@ }, { "name": "PyPI", - "url": "/packages", + "url": "/autogen/dev/packages", "icon": "fa-custom fa-pypi", }, ], diff --git a/python/packages/autogen-core/docs/src/packages/index.md b/python/packages/autogen-core/docs/src/packages/index.md index 66c51ca8cdcf..88c509cb3803 100644 --- a/python/packages/autogen-core/docs/src/packages/index.md +++ b/python/packages/autogen-core/docs/src/packages/index.md @@ -74,8 +74,8 @@ pip install autogen-ext==0.4.0dev0 A generalist multi-agent softbot utilizing five agents to tackle intricate tasks involving multi-step planning and real-world actions. -```sh -pip install autogen-magentic-one==0.1.0dev0 +```{note} +Not yet available on PyPI. ``` [{fab}`github;pst-color-primary` Source](https://github.com/microsoft/autogen/tree/main/python/packages/autogen-magentic-one) diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/code-execution.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/code-execution.ipynb index a9334e2b0df3..eb9841b2f685 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/code-execution.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/code-execution.ipynb @@ -315,8 +315,8 @@ "source": [ "from autogen_agentchat.agents import CodeExecutorAgent, CodingAssistantAgent\n", "from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination\n", - "from autogen_core.components.code_executor import DockerCommandLineCodeExecutor\n", "from autogen_core.components.models import OpenAIChatCompletionClient\n", + "from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor\n", "\n", "async with DockerCommandLineCodeExecutor(work_dir=\"coding\") as code_executor: # type: ignore[syntax]\n", " code_executor_agent = CodeExecutorAgent(\"code_executor\", code_executor=code_executor)\n", diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/quickstart.md b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/quickstart.md index ab510c4c1e4c..c12030f0bc1e 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/quickstart.md +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/quickstart.md @@ -29,6 +29,9 @@ The following example illustrates creating a simple agent team with two agents t 1. `CodingAssistantAgent` that generates responses using an LLM model. 2. `CodeExecutorAgent` that executes code snippets and returns the output. +Because the `CodeExecutorAgent` uses a Docker command-line code executor to execute code snippets, +you need to have [Docker installed](https://docs.docker.com/engine/install/) and running on your machine. + The task is to "Create a plot of NVIDIA and TESLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'." ```{include} stocksnippet.md diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/stocksnippet.md b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/stocksnippet.md index 0969c77667a0..689aae889587 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/stocksnippet.md +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/stocksnippet.md @@ -2,38 +2,52 @@ `````{tab-item} AgentChat (v0.4x) ```python +import asyncio +import logging +from autogen_agentchat import EVENT_LOGGER_NAME from autogen_agentchat.agents import CodeExecutorAgent, CodingAssistantAgent +from autogen_agentchat.logging import ConsoleLogHandler from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination -from autogen_core.components.code_executor import DockerCommandLineCodeExecutor +from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor from autogen_core.components.models import OpenAIChatCompletionClient -async with DockerCommandLineCodeExecutor(work_dir="coding") as code_executor: - code_executor_agent = CodeExecutorAgent("code_executor", code_executor=code_executor) - coding_assistant_agent = CodingAssistantAgent( - "coding_assistant", model_client=OpenAIChatCompletionClient(model="gpt-4") - ) - group_chat = RoundRobinGroupChat([coding_assistant_agent, code_executor_agent]) - result = await group_chat.run( - task="Create a plot of NVIDIA and TESLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.", - termination_condition=StopMessageTermination(), - ) +logger = logging.getLogger(EVENT_LOGGER_NAME) +logger.addHandler(ConsoleLogHandler()) +logger.setLevel(logging.INFO) + +async def main() -> None: + async with DockerCommandLineCodeExecutor(work_dir="coding") as code_executor: + code_executor_agent = CodeExecutorAgent("code_executor", code_executor=code_executor) + coding_assistant_agent = CodingAssistantAgent( + "coding_assistant", model_client=OpenAIChatCompletionClient(model="gpt-4o", api_key="YOUR_API_KEY") + ) + group_chat = RoundRobinGroupChat([coding_assistant_agent, code_executor_agent]) + result = await group_chat.run( + task="Create a plot of NVDIA and TSLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.", + termination_condition=StopMessageTermination(), + ) + +asyncio.run(main()) ``` ````` `````{tab-item} v0.2x ```python +from autogen.coding import DockerCommandLineCodeExecutor from autogen import AssistantAgent, UserProxyAgent, config_list_from_json -config_list = config_list_from_json(env_or_file="OAI_CONFIG_LIST") -assistant = AssistantAgent("assistant", llm_config={"config_list": config_list}) +llm_config = {"model": "gpt-4o", "api_type": "openai", "api_key": "YOUR_API_KEY"} +code_executor = DockerCommandLineCodeExecutor(work_dir="coding") +assistant = AssistantAgent("assistant", llm_config=llm_config) code_executor_agent = UserProxyAgent( "code_executor_agent", - code_execution_config={"work_dir": "coding", "use_docker": True} + code_execution_config={"executor": code_executor}, ) -code_executor_agent.initiate_chat( +result = code_executor_agent.initiate_chat( assistant, - message="Create a plot of NVIDIA and TESLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'." + message="Create a plot of NVIDIA and TESLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.", ) +code_executor.stop() ``` ````` diff --git a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/cookbook/tool-use-with-intervention.ipynb b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/cookbook/tool-use-with-intervention.ipynb index 1ddee2a0ed15..84a45f84ee8f 100644 --- a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/cookbook/tool-use-with-intervention.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/cookbook/tool-use-with-intervention.ipynb @@ -23,7 +23,6 @@ "from autogen_core.base import AgentId, AgentType, MessageContext\n", "from autogen_core.base.intervention import DefaultInterventionHandler, DropMessage\n", "from autogen_core.components import FunctionCall, RoutedAgent, message_handler\n", - "from autogen_core.components.code_executor import DockerCommandLineCodeExecutor\n", "from autogen_core.components.models import (\n", " ChatCompletionClient,\n", " LLMMessage,\n", @@ -32,7 +31,8 @@ " UserMessage,\n", ")\n", "from autogen_core.components.tool_agent import ToolAgent, ToolException, tool_agent_caller_loop\n", - "from autogen_core.components.tools import PythonCodeExecutionTool, ToolSchema" + "from autogen_core.components.tools import PythonCodeExecutionTool, ToolSchema\n", + "from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor" ] }, { @@ -157,7 +157,7 @@ "source": [ "In this example, we will use a tool for Python code execution.\n", "First, we create a Docker-based command-line code executor\n", - "using {py:class}`~autogen_core.components.code_executor.DockerCommandLineCodeExecutor`,\n", + "using {py:class}`~autogen_core.components.code_executor.docker_executorCommandLineCodeExecutor`,\n", "and then use it to instantiate a built-in Python code execution tool\n", "{py:class}`~autogen_core.components.tools.PythonCodeExecutionTool`\n", "that runs code in a Docker container." diff --git a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/command-line-code-executors.ipynb b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/command-line-code-executors.ipynb index e6e8a3be3095..8cdb529e2120 100644 --- a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/command-line-code-executors.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/command-line-code-executors.ipynb @@ -10,12 +10,12 @@ "Generally speaking, it will save each code block to a file and the execute that file.\n", "This means that each code block is executed in a new process. There are two forms of this executor:\n", "\n", - "- Docker ({py:class}`~autogen_core.components.code_executor.DockerCommandLineCodeExecutor`) - this is where all commands are executed in a Docker container\n", + "- Docker ({py:class}`~autogen_ext.code_executor.docker_executor.DockerCommandLineCodeExecutor`) - this is where all commands are executed in a Docker container\n", "- Local ({py:class}`~autogen_core.components.code_executor.LocalCommandLineCodeExecutor`) - this is where all commands are executed on the host machine\n", "\n", "## Docker\n", "\n", - "The {py:class}`~autogen_core.components.code_executor.DockerCommandLineCodeExecutor` will create a Docker container and run all commands within that container. \n", + "The {py:class}`~autogen_ext.code_executor.docker_executor.DockerCommandLineCodeExecutor` will create a Docker container and run all commands within that container. \n", "The default image that is used is `python:3-slim`, this can be customized by passing the `image` parameter to the constructor. \n", "If the image is not found locally then the class will try to pull it. \n", "Therefore, having built the image locally is enough. The only thing required for this image to be compatible with the executor is to have `sh` and `python` installed. \n", @@ -50,7 +50,8 @@ "from pathlib import Path\n", "\n", "from autogen_core.base import CancellationToken\n", - "from autogen_core.components.code_executor import CodeBlock, DockerCommandLineCodeExecutor\n", + "from autogen_core.components.code_executor import CodeBlock\n", + "from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor\n", "\n", "work_dir = Path(\"coding\")\n", "work_dir.mkdir(exist_ok=True)\n", diff --git a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/tools.ipynb b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/tools.ipynb index dd0d0769af6e..bce62e45e7ce 100644 --- a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/tools.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/tools.ipynb @@ -44,8 +44,8 @@ ], "source": [ "from autogen_core.base import CancellationToken\n", - "from autogen_core.components.code_executor import DockerCommandLineCodeExecutor\n", "from autogen_core.components.tools import PythonCodeExecutionTool\n", + "from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor\n", "\n", "# Create the tool.\n", "code_executor = DockerCommandLineCodeExecutor()\n", @@ -63,7 +63,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The {py:class}`~autogen_core.components.code_executor.DockerCommandLineCodeExecutor`\n", + "The {py:class}`~autogen_core.components.code_executor.docker_executorCommandLineCodeExecutor`\n", "class is a built-in code executor that runs Python code snippets in a subprocess\n", "in the local command line environment.\n", "The {py:class}`~autogen_core.components.tools.PythonCodeExecutionTool` class wraps the code executor\n", diff --git a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/quickstart.ipynb b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/quickstart.ipynb index cc489f6f76ea..034504eb326a 100644 --- a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/quickstart.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/quickstart.ipynb @@ -312,8 +312,8 @@ "import tempfile\n", "\n", "from autogen_core.application import SingleThreadedAgentRuntime\n", - "from autogen_core.components.code_executor import DockerCommandLineCodeExecutor\n", "from autogen_core.components.models import OpenAIChatCompletionClient\n", + "from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor\n", "\n", "work_dir = tempfile.mkdtemp()\n", "\n", diff --git a/python/packages/autogen-core/docs/src/user-guide/extensions-user-guide/index.md b/python/packages/autogen-core/docs/src/user-guide/extensions-user-guide/index.md new file mode 100644 index 000000000000..601b435382f5 --- /dev/null +++ b/python/packages/autogen-core/docs/src/user-guide/extensions-user-guide/index.md @@ -0,0 +1,111 @@ +--- +myst: + html_meta: + "description lang=en": | + User Guide for AutoGen Extensions, a framework for building multi-agent applications with AI agents. +--- + +# Extensions + +Discover community projects: + +::::{grid} 1 2 2 2 +:margin: 4 4 0 0 +:gutter: 1 + +:::{grid-item-card} {fas}`globe;pst-color-primary`
Ecosystem +:link: https://github.com/topics/autogen +:class-item: api-card +:columns: 12 + +Find samples, services and other things that work with AutoGen + +::: + +:::{grid-item-card} {fas}`puzzle-piece;pst-color-primary`
Community Extensions +:link: https://github.com/topics/autogen-extension +:class-item: api-card + +Find AutoGen extensions for 3rd party tools, components and services + +::: + +:::{grid-item-card} {fas}`vial;pst-color-primary`
Community Samples +:link: https://github.com/topics/autogen-samples +:class-item: api-card + +Find community samples and examples of how to use AutoGen + +::: + +:::: + +## Built-in extenions + +Read docs for built in extensions: + +```{note} +WIP +``` + + + + +## Creating your own community extension + +With the new package structure in 0.4, it is easier than ever to create and publish your own extension to the AutoGen ecosystem. This page details some best practices so that your extension package integrates well with the AutoGen ecosystem. + +### Best practices + +#### Naming + +There is no requirement about naming. But prefixing the package name with `autogen-` makes it easier to find. + +#### Common interfaces + +Whenever possible, extensions should implement the provided interfaces from the `autogen_core` package. This will allow for a more consistent experience for users. + +##### Dependency on AutoGen + +To ensure that the extension works with the version of AutoGen that it was designed for, it is recommended to specify the version of AutoGen the dependency section of the `pyproject.toml` with adequate constraints. + +```toml +[project] +# ... +dependencies = [ + "autogen-core>=0.4,<0.5" +] +``` + +#### Usage of typing + +AutoGen embraces the use of type hints to provide a better development experience. Extensions should use type hints whenever possible. + +### Discovery + +To make it easier for users to find your extension, sample, service or package, you can [add the topic](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/classifying-your-repository-with-topics) `autogen` to the GitHub repo. + +More specific topics are also available: + +- `autogen-extension` for extensions +- `autogen-sample` for samples + +### Changes from 0.2 + +In AutoGen 0.2 it was common to merge 3rd party extensions and examples into the main repo. We are super appreciative of all of the users who have contributed to the ecosystem notebooks, modules and pages in 0.2. However, in general we are moving away from this model to allow for more flexibility and to reduce maintenance burden. + +There is the `autogen-ext` package for 1st party supported extensions, but we want to be selective to manage maintenance load. If you would like to see if your extension makes sense to add into `autogen-ext`, please open an issue and let's discuss. Otherwise, we encourage you to publish your extension as a separate package and follow the guidance under [discovery](#discovery) to make it easy for users to find. diff --git a/python/packages/autogen-core/docs/src/user-guide/index.md b/python/packages/autogen-core/docs/src/user-guide/index.md index 9e317d1e1899..347c4fe18592 100644 --- a/python/packages/autogen-core/docs/src/user-guide/index.md +++ b/python/packages/autogen-core/docs/src/user-guide/index.md @@ -6,6 +6,7 @@ agentchat-user-guide/index core-user-guide/index +extensions-user-guide/index ``` ::::{grid} 1 2 2 3 diff --git a/python/packages/autogen-core/pyproject.toml b/python/packages/autogen-core/pyproject.toml index 614842c85110..a83f3e4a01d5 100644 --- a/python/packages/autogen-core/pyproject.toml +++ b/python/packages/autogen-core/pyproject.toml @@ -23,7 +23,6 @@ dependencies = [ "grpcio~=1.62.0", "protobuf~=4.25.1", "tiktoken", - "docker~=7.0", "opentelemetry-api~=1.27.0", "asyncio_atexit" ] diff --git a/python/packages/autogen-core/samples/README.md b/python/packages/autogen-core/samples/README.md index 2b92d803f1bb..fafd29a7f47f 100644 --- a/python/packages/autogen-core/samples/README.md +++ b/python/packages/autogen-core/samples/README.md @@ -6,7 +6,7 @@ See [user guide](../docs/src/core-user-guide/guides/) and See [Running the examples](#running-the-examples) for instructions on how to run the examples. -- [`chest_game.py`](chess_game.py): an example with two chess player agents that executes its own tools to demonstrate tool use and reflection on tool use. +- [`chess_game.py`](chess_game.py): an example with two chess player agents that executes its own tools to demonstrate tool use and reflection on tool use. - [`slow_human_in_loop.py`](slow_human_in_loop.py): an example showing human-in-the-loop which waits for human input before making the tool call. ## Running the examples diff --git a/python/packages/autogen-core/samples/coding_pub_sub.py b/python/packages/autogen-core/samples/coding_pub_sub.py index f7ba249768de..68090d556a6f 100644 --- a/python/packages/autogen-core/samples/coding_pub_sub.py +++ b/python/packages/autogen-core/samples/coding_pub_sub.py @@ -20,7 +20,6 @@ from autogen_core.application import SingleThreadedAgentRuntime from autogen_core.base import MessageContext from autogen_core.components import DefaultSubscription, DefaultTopicId, FunctionCall, RoutedAgent, message_handler -from autogen_core.components.code_executor import DockerCommandLineCodeExecutor from autogen_core.components.models import ( AssistantMessage, ChatCompletionClient, @@ -31,6 +30,7 @@ UserMessage, ) from autogen_core.components.tools import PythonCodeExecutionTool, Tool +from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor from common.utils import get_chat_completion_client_from_envs diff --git a/python/packages/autogen-core/src/autogen_core/application/_single_threaded_agent_runtime.py b/python/packages/autogen-core/src/autogen_core/application/_single_threaded_agent_runtime.py index 38066f1f526e..7feff6babf41 100644 --- a/python/packages/autogen-core/src/autogen_core/application/_single_threaded_agent_runtime.py +++ b/python/packages/autogen-core/src/autogen_core/application/_single_threaded_agent_runtime.py @@ -339,50 +339,50 @@ async def _process_send(self, message_envelope: SendMessageEnvelope) -> None: async def _process_publish(self, message_envelope: PublishMessageEnvelope) -> None: with self._tracer_helper.trace_block("publish", message_envelope.topic_id, parent=message_envelope.metadata): - responses: List[Awaitable[Any]] = [] - recipients = await self._subscription_manager.get_subscribed_recipients(message_envelope.topic_id) - for agent_id in recipients: - # Avoid sending the message back to the sender - if message_envelope.sender is not None and agent_id == message_envelope.sender: - continue - - sender_agent = ( - await self._get_agent(message_envelope.sender) if message_envelope.sender is not None else None - ) - sender_name = str(sender_agent.id) if sender_agent is not None else "Unknown" - logger.info( - f"Calling message handler for {agent_id.type} with message type {type(message_envelope.message).__name__} published by {sender_name}" - ) - # event_logger.info( - # MessageEvent( - # payload=message_envelope.message, - # sender=message_envelope.sender, - # receiver=agent, - # kind=MessageKind.PUBLISH, - # delivery_stage=DeliveryStage.DELIVER, - # ) - # ) - message_context = MessageContext( - sender=message_envelope.sender, - topic_id=message_envelope.topic_id, - is_rpc=False, - cancellation_token=message_envelope.cancellation_token, - ) - agent = await self._get_agent(agent_id) + try: + responses: List[Awaitable[Any]] = [] + recipients = await self._subscription_manager.get_subscribed_recipients(message_envelope.topic_id) + for agent_id in recipients: + # Avoid sending the message back to the sender + if message_envelope.sender is not None and agent_id == message_envelope.sender: + continue + + sender_agent = ( + await self._get_agent(message_envelope.sender) if message_envelope.sender is not None else None + ) + sender_name = str(sender_agent.id) if sender_agent is not None else "Unknown" + logger.info( + f"Calling message handler for {agent_id.type} with message type {type(message_envelope.message).__name__} published by {sender_name}" + ) + # event_logger.info( + # MessageEvent( + # payload=message_envelope.message, + # sender=message_envelope.sender, + # receiver=agent, + # kind=MessageKind.PUBLISH, + # delivery_stage=DeliveryStage.DELIVER, + # ) + # ) + message_context = MessageContext( + sender=message_envelope.sender, + topic_id=message_envelope.topic_id, + is_rpc=False, + cancellation_token=message_envelope.cancellation_token, + ) + agent = await self._get_agent(agent_id) - async def _on_message(agent: Agent, message_context: MessageContext) -> Any: - with self._tracer_helper.trace_block("process", agent.id, parent=None): - with MessageHandlerContext.populate_context(agent.id): - return await agent.on_message( - message_envelope.message, - ctx=message_context, - ) + async def _on_message(agent: Agent, message_context: MessageContext) -> Any: + with self._tracer_helper.trace_block("process", agent.id, parent=None): + with MessageHandlerContext.populate_context(agent.id): + return await agent.on_message( + message_envelope.message, + ctx=message_context, + ) - future = _on_message(agent, message_context) - responses.append(future) + future = _on_message(agent, message_context) + responses.append(future) - try: - _all_responses = await asyncio.gather(*responses) + await asyncio.gather(*responses) except BaseException as e: # Ignore cancelled errors from logs if isinstance(e, CancelledError): diff --git a/python/packages/autogen-core/src/autogen_core/components/code_executor/__init__.py b/python/packages/autogen-core/src/autogen_core/components/code_executor/__init__.py index c75bb080d612..c09ee8b799f9 100644 --- a/python/packages/autogen-core/src/autogen_core/components/code_executor/__init__.py +++ b/python/packages/autogen-core/src/autogen_core/components/code_executor/__init__.py @@ -10,9 +10,8 @@ with_requirements, ) from ._impl.command_line_code_result import CommandLineCodeResult -from ._impl.docker_command_line_code_executor import DockerCommandLineCodeExecutor from ._impl.local_commandline_code_executor import LocalCommandLineCodeExecutor -from ._impl.utils import get_required_packages, lang_to_cmd +from ._impl.utils import get_file_name_from_content, get_required_packages, lang_to_cmd, silence_pip from ._utils import extract_markdown_code_blocks __all__ = [ @@ -31,7 +30,8 @@ "extract_markdown_code_blocks", "get_required_packages", "build_python_functions_file", - "DockerCommandLineCodeExecutor", "get_required_packages", "lang_to_cmd", + "get_file_name_from_content", + "silence_pip", ] diff --git a/python/packages/autogen-core/tests/execution/test_commandline_code_executor.py b/python/packages/autogen-core/tests/execution/test_commandline_code_executor.py index 16582ff5ed27..bb3ff2830958 100644 --- a/python/packages/autogen-core/tests/execution/test_commandline_code_executor.py +++ b/python/packages/autogen-core/tests/execution/test_commandline_code_executor.py @@ -2,7 +2,6 @@ # Credit to original authors import asyncio -import os import sys import tempfile from pathlib import Path @@ -12,48 +11,22 @@ import pytest_asyncio from aiofiles import open from autogen_core.base import CancellationToken -from autogen_core.components.code_executor import CodeBlock, DockerCommandLineCodeExecutor, LocalCommandLineCodeExecutor - - -def docker_tests_enabled() -> bool: - if os.environ.get("SKIP_DOCKER", "unset").lower() == "true": - return False - - try: - import docker - from docker.errors import DockerException - except ImportError: - return False - - try: - client = docker.from_env() - client.ping() # type: ignore - return True - except DockerException: - return False +from autogen_core.components.code_executor import CodeBlock, LocalCommandLineCodeExecutor @pytest_asyncio.fixture(scope="function") # type: ignore async def executor_and_temp_dir( request: pytest.FixtureRequest, -) -> AsyncGenerator[tuple[LocalCommandLineCodeExecutor | DockerCommandLineCodeExecutor, str], None]: - if request.param == "local": - with tempfile.TemporaryDirectory() as temp_dir: - yield LocalCommandLineCodeExecutor(work_dir=temp_dir), temp_dir - elif request.param == "docker": - if not docker_tests_enabled(): - pytest.skip("Docker tests are disabled") - - with tempfile.TemporaryDirectory() as temp_dir: - async with DockerCommandLineCodeExecutor(work_dir=temp_dir) as executor: - yield executor, temp_dir +) -> AsyncGenerator[tuple[LocalCommandLineCodeExecutor, str], None]: + with tempfile.TemporaryDirectory() as temp_dir: + yield LocalCommandLineCodeExecutor(work_dir=temp_dir), temp_dir -ExecutorFixture: TypeAlias = tuple[LocalCommandLineCodeExecutor | DockerCommandLineCodeExecutor, str] +ExecutorFixture: TypeAlias = tuple[LocalCommandLineCodeExecutor, str] @pytest.mark.asyncio -@pytest.mark.parametrize("executor_and_temp_dir", ["local", "docker"], indirect=True) +@pytest.mark.parametrize("executor_and_temp_dir", ["local"], indirect=True) async def test_execute_code(executor_and_temp_dir: ExecutorFixture) -> None: executor, _temp_dir = executor_and_temp_dir cancellation_token = CancellationToken() @@ -101,7 +74,7 @@ async def test_execute_code(executor_and_temp_dir: ExecutorFixture) -> None: @pytest.mark.asyncio -@pytest.mark.parametrize("executor_and_temp_dir", ["local", "docker"], indirect=True) +@pytest.mark.parametrize("executor_and_temp_dir", ["local"], indirect=True) async def test_commandline_code_executor_timeout(executor_and_temp_dir: ExecutorFixture) -> None: executor, temp_dir = executor_and_temp_dir cancellation_token = CancellationToken() @@ -111,7 +84,6 @@ async def test_commandline_code_executor_timeout(executor_and_temp_dir: Executor assert code_result.exit_code and "Timeout" in code_result.output -# TODO: add docker when cancellation is supported @pytest.mark.asyncio async def test_commandline_code_executor_cancellation() -> None: with tempfile.TemporaryDirectory() as temp_dir: @@ -136,7 +108,7 @@ async def test_local_commandline_code_executor_restart() -> None: @pytest.mark.asyncio -@pytest.mark.parametrize("executor_and_temp_dir", ["local", "docker"], indirect=True) +@pytest.mark.parametrize("executor_and_temp_dir", ["local"], indirect=True) async def test_invalid_relative_path(executor_and_temp_dir: ExecutorFixture) -> None: executor, _temp_dir = executor_and_temp_dir cancellation_token = CancellationToken() @@ -151,7 +123,7 @@ async def test_invalid_relative_path(executor_and_temp_dir: ExecutorFixture) -> @pytest.mark.asyncio -@pytest.mark.parametrize("executor_and_temp_dir", ["local", "docker"], indirect=True) +@pytest.mark.parametrize("executor_and_temp_dir", ["local"], indirect=True) async def test_valid_relative_path(executor_and_temp_dir: ExecutorFixture) -> None: executor, temp_dir_str = executor_and_temp_dir @@ -171,24 +143,3 @@ async def test_valid_relative_path(executor_and_temp_dir: ExecutorFixture) -> No assert "test.py" in result.code_file assert (temp_dir / Path("test.py")).resolve() == Path(result.code_file).resolve() assert (temp_dir / Path("test.py")).exists() - - -@pytest.mark.asyncio -async def test_docker_commandline_code_executor_start_stop() -> None: - if not docker_tests_enabled(): - pytest.skip("Docker tests are disabled") - - with tempfile.TemporaryDirectory() as temp_dir: - executor = DockerCommandLineCodeExecutor(work_dir=temp_dir) - await executor.start() - await executor.stop() - - -@pytest.mark.asyncio -async def test_docker_commandline_code_executor_start_stop_context_manager() -> None: - if not docker_tests_enabled(): - pytest.skip("Docker tests are disabled") - - with tempfile.TemporaryDirectory() as temp_dir: - async with DockerCommandLineCodeExecutor(work_dir=temp_dir) as _exec: - pass diff --git a/python/packages/autogen-core/tests/test_runtime.py b/python/packages/autogen-core/tests/test_runtime.py index d1a1a3299053..0f56e42a7e00 100644 --- a/python/packages/autogen-core/tests/test_runtime.py +++ b/python/packages/autogen-core/tests/test_runtime.py @@ -1,4 +1,5 @@ import asyncio +import logging import pytest from autogen_core.application import SingleThreadedAgentRuntime @@ -91,6 +92,26 @@ async def test_register_receives_publish(tracer_provider: TracerProvider) -> Non ] +@pytest.mark.asyncio +async def test_register_receives_publish_with_exception(caplog: pytest.LogCaptureFixture) -> None: + runtime = SingleThreadedAgentRuntime() + + runtime.add_message_serializer(try_get_known_serializers_for_type(MessageType)) + + async def agent_factory() -> LoopbackAgent: + raise ValueError("test") + + await runtime.register_factory(type=AgentType("name"), agent_factory=agent_factory, expected_class=LoopbackAgent) + await runtime.add_subscription(TypeSubscription("default", "name")) + + with caplog.at_level(logging.ERROR): + runtime.start() + await runtime.publish_message(MessageType(), topic_id=TopicId("default", "default")) + await runtime.stop_when_idle() + # Check if logger has the exception. + assert any("Error processing publish message" in e.message for e in caplog.records) + + @pytest.mark.asyncio async def test_register_receives_publish_cascade() -> None: num_agents = 5 diff --git a/python/packages/autogen-ext/pyproject.toml b/python/packages/autogen-ext/pyproject.toml index ee6bcc5e3af6..a290a03eee10 100644 --- a/python/packages/autogen-ext/pyproject.toml +++ b/python/packages/autogen-ext/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ [project.optional-dependencies] langchain-tools = ["langchain >= 0.3.1"] azure-code-executor = ["azure-core"] +docker-code-executor = ["docker~=7.0"] [tool.hatch.build.targets.wheel] packages = ["src/autogen_ext"] @@ -47,3 +48,8 @@ include = "../../shared_tasks.toml" [tool.poe.tasks] test = "pytest -n auto" + +[tool.mypy] +[[tool.mypy.overrides]] +module = "docker.*" +ignore_missing_imports = true \ No newline at end of file diff --git a/python/packages/autogen-ext/src/autogen_ext/code_executor/docker_executor/__init__.py b/python/packages/autogen-ext/src/autogen_ext/code_executor/docker_executor/__init__.py new file mode 100644 index 000000000000..23c859ffe63b --- /dev/null +++ b/python/packages/autogen-ext/src/autogen_ext/code_executor/docker_executor/__init__.py @@ -0,0 +1,3 @@ +from ._impl import DockerCommandLineCodeExecutor + +__all__ = ["DockerCommandLineCodeExecutor"] diff --git a/python/packages/autogen-core/src/autogen_core/components/code_executor/_impl/docker_command_line_code_executor.py b/python/packages/autogen-ext/src/autogen_ext/code_executor/docker_executor/_impl.py similarity index 96% rename from python/packages/autogen-core/src/autogen_core/components/code_executor/_impl/docker_command_line_code_executor.py rename to python/packages/autogen-ext/src/autogen_ext/code_executor/docker_executor/_impl.py index 346d7c070aaf..7b3a194e6f6a 100644 --- a/python/packages/autogen-core/src/autogen_core/components/code_executor/_impl/docker_command_line_code_executor.py +++ b/python/packages/autogen-ext/src/autogen_ext/code_executor/docker_executor/_impl.py @@ -18,16 +18,19 @@ import docker import docker.models import docker.models.containers -from docker.errors import ImageNotFound, NotFound - -from ....base._cancellation_token import CancellationToken -from ....components.code_executor._base import CodeBlock, CodeExecutor -from ....components.code_executor._func_with_reqs import FunctionWithRequirements, FunctionWithRequirementsStr -from ....components.code_executor._impl.command_line_code_result import CommandLineCodeResult -from .._func_with_reqs import ( +from autogen_core.base import CancellationToken +from autogen_core.components.code_executor import ( + CodeBlock, + CodeExecutor, + CommandLineCodeResult, + FunctionWithRequirements, + FunctionWithRequirementsStr, build_python_functions_file, + get_file_name_from_content, + lang_to_cmd, + silence_pip, ) -from .utils import get_file_name_from_content, lang_to_cmd, silence_pip +from docker.errors import ImageNotFound, NotFound if sys.version_info >= (3, 11): from typing import Self diff --git a/python/packages/autogen-ext/tests/code_executors/test_docker_commandline_code_executor.py b/python/packages/autogen-ext/tests/code_executors/test_docker_commandline_code_executor.py new file mode 100644 index 000000000000..641e5e703316 --- /dev/null +++ b/python/packages/autogen-ext/tests/code_executors/test_docker_commandline_code_executor.py @@ -0,0 +1,166 @@ +# mypy: disable-error-code="no-any-unimported" +import os +import sys +import tempfile +from pathlib import Path +from typing import AsyncGenerator, TypeAlias + +import pytest +import pytest_asyncio +from aiofiles import open +from autogen_core.base import CancellationToken +from autogen_core.components.code_executor import CodeBlock +from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor + + +def docker_tests_enabled() -> bool: + if os.environ.get("SKIP_DOCKER", "unset").lower() == "true": + return False + + try: + import docker + from docker.errors import DockerException + except ImportError: + return False + + try: + client = docker.from_env() + client.ping() # type: ignore + return True + except DockerException: + return False + + +@pytest_asyncio.fixture(scope="function") # type: ignore +async def executor_and_temp_dir( + request: pytest.FixtureRequest, +) -> AsyncGenerator[tuple[DockerCommandLineCodeExecutor, str], None]: + if not docker_tests_enabled(): + pytest.skip("Docker tests are disabled") + + with tempfile.TemporaryDirectory() as temp_dir: + async with DockerCommandLineCodeExecutor(work_dir=temp_dir) as executor: + yield executor, temp_dir + + +ExecutorFixture: TypeAlias = tuple[DockerCommandLineCodeExecutor, str] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("executor_and_temp_dir", ["docker"], indirect=True) +async def test_execute_code(executor_and_temp_dir: ExecutorFixture) -> None: + executor, _temp_dir = executor_and_temp_dir + cancellation_token = CancellationToken() + + # Test single code block. + code_blocks = [CodeBlock(code="import sys; print('hello world!')", language="python")] + code_result = await executor.execute_code_blocks(code_blocks, cancellation_token) + assert code_result.exit_code == 0 and "hello world!" in code_result.output and code_result.code_file is not None + + # Test multiple code blocks. + code_blocks = [ + CodeBlock(code="import sys; print('hello world!')", language="python"), + CodeBlock(code="a = 100 + 100; print(a)", language="python"), + ] + code_result = await executor.execute_code_blocks(code_blocks, cancellation_token) + assert ( + code_result.exit_code == 0 + and "hello world!" in code_result.output + and "200" in code_result.output + and code_result.code_file is not None + ) + + # Test bash script. + if sys.platform not in ["win32"]: + code_blocks = [CodeBlock(code="echo 'hello world!'", language="bash")] + code_result = await executor.execute_code_blocks(code_blocks, cancellation_token) + assert code_result.exit_code == 0 and "hello world!" in code_result.output and code_result.code_file is not None + + # Test running code. + file_lines = ["import sys", "print('hello world!')", "a = 100 + 100", "print(a)"] + code_blocks = [CodeBlock(code="\n".join(file_lines), language="python")] + code_result = await executor.execute_code_blocks(code_blocks, cancellation_token) + assert ( + code_result.exit_code == 0 + and "hello world!" in code_result.output + and "200" in code_result.output + and code_result.code_file is not None + ) + + # Check saved code file. + async with open(code_result.code_file) as f: + code_lines = await f.readlines() + for file_line, code_line in zip(file_lines, code_lines, strict=False): + assert file_line.strip() == code_line.strip() + + +@pytest.mark.asyncio +@pytest.mark.parametrize("executor_and_temp_dir", ["docker"], indirect=True) +async def test_commandline_code_executor_timeout(executor_and_temp_dir: ExecutorFixture) -> None: + _executor, temp_dir = executor_and_temp_dir + cancellation_token = CancellationToken() + code_blocks = [CodeBlock(code="import time; time.sleep(10); print('hello world!')", language="python")] + + async with DockerCommandLineCodeExecutor(timeout=1, work_dir=temp_dir) as executor: + code_result = await executor.execute_code_blocks(code_blocks, cancellation_token) + + assert code_result.exit_code and "Timeout" in code_result.output + + +@pytest.mark.asyncio +@pytest.mark.parametrize("executor_and_temp_dir", ["docker"], indirect=True) +async def test_invalid_relative_path(executor_and_temp_dir: ExecutorFixture) -> None: + executor, _temp_dir = executor_and_temp_dir + cancellation_token = CancellationToken() + code = """# filename: /tmp/test.py + +print("hello world") +""" + result = await executor.execute_code_blocks( + [CodeBlock(code=code, language="python")], cancellation_token=cancellation_token + ) + assert result.exit_code == 1 and "Filename is not in the workspace" in result.output + + +@pytest.mark.asyncio +@pytest.mark.parametrize("executor_and_temp_dir", ["docker"], indirect=True) +async def test_valid_relative_path(executor_and_temp_dir: ExecutorFixture) -> None: + executor, temp_dir_str = executor_and_temp_dir + + cancellation_token = CancellationToken() + temp_dir = Path(temp_dir_str) + + code = """# filename: test.py + +print("hello world") +""" + result = await executor.execute_code_blocks( + [CodeBlock(code=code, language="python")], cancellation_token=cancellation_token + ) + assert result.exit_code == 0 + assert "hello world" in result.output + assert result.code_file is not None + assert "test.py" in result.code_file + assert (temp_dir / Path("test.py")).resolve() == Path(result.code_file).resolve() + assert (temp_dir / Path("test.py")).exists() + + +@pytest.mark.asyncio +async def test_docker_commandline_code_executor_start_stop() -> None: + if not docker_tests_enabled(): + pytest.skip("Docker tests are disabled") + + with tempfile.TemporaryDirectory() as temp_dir: + executor = DockerCommandLineCodeExecutor(work_dir=temp_dir) + await executor.start() + await executor.stop() + + +@pytest.mark.asyncio +async def test_docker_commandline_code_executor_start_stop_context_manager() -> None: + if not docker_tests_enabled(): + pytest.skip("Docker tests are disabled") + + with tempfile.TemporaryDirectory() as temp_dir: + async with DockerCommandLineCodeExecutor(work_dir=temp_dir) as _exec: + pass diff --git a/python/packages/autogen-magentic-one/examples/example_coder.py b/python/packages/autogen-magentic-one/examples/example_coder.py index c7721a88f996..b1978a13d2eb 100644 --- a/python/packages/autogen-magentic-one/examples/example_coder.py +++ b/python/packages/autogen-magentic-one/examples/example_coder.py @@ -10,7 +10,8 @@ from autogen_core.application import SingleThreadedAgentRuntime from autogen_core.application.logging import EVENT_LOGGER_NAME from autogen_core.base import AgentId, AgentProxy -from autogen_core.components.code_executor import CodeBlock, DockerCommandLineCodeExecutor +from autogen_core.components.code_executor import CodeBlock +from autogen_ext.code_executor.docker_executor import DockerCommandLineCodeExecutor from autogen_magentic_one.agents.coder import Coder, Executor from autogen_magentic_one.agents.orchestrator import RoundRobinOrchestrator from autogen_magentic_one.agents.user_proxy import UserProxy diff --git a/python/packages/autogen-magentic-one/readme.md b/python/packages/autogen-magentic-one/readme.md index c50c4d9fd8b2..24301f30e4f4 100644 --- a/python/packages/autogen-magentic-one/readme.md +++ b/python/packages/autogen-magentic-one/readme.md @@ -168,21 +168,19 @@ In addition, developers can also handle and process logs generated from the Auto You can install the Magentic-One package using pip and then run the example code to see how the agents work together to accomplish a task. - 1. Clone the code. + ```bash -# clone autogen_core -cd python/teams/autogen-magentic-one +git clone -b staging https://github.com/microsoft/autogen.git +cd autogen/python/packages/autogen-magentic-one pip install -e . ``` 2. Configure the environment variables for the chat completion client. See instructions below. - - -2. Now you can run the example code to see how the agents work together to accomplish a task. +3. Now you can run the example code to see how the agents work together to accomplish a task. ```bash -python examples/example.py +python examples/example_websurfer.py ``` @@ -230,4 +228,4 @@ Some functionalities, such as using web-search requires an API key for Bing. You can set it using: ```bash export BING_API_KEY=xxxxxxx -``` +``` \ No newline at end of file diff --git a/python/packages/autogen-magentic-one/tests/browser_utils/test_bing_markdown_search.py b/python/packages/autogen-magentic-one/tests/browser_utils/test_bing_markdown_search.py index 8b62ea9607ba..3e17b0c739ae 100644 --- a/python/packages/autogen-magentic-one/tests/browser_utils/test_bing_markdown_search.py +++ b/python/packages/autogen-magentic-one/tests/browser_utils/test_bing_markdown_search.py @@ -17,17 +17,6 @@ BING_EXPECTED_RESULT = "https://en.wikipedia.org/wiki/Microsoft" -@pytest.mark.skipif( - skip_all, - reason="do not run if dependency is not installed", -) -def test_bing_markdown_search() -> None: - search_engine = BingMarkdownSearch() - results = search_engine.search(BING_QUERY) - assert BING_STRING in results - assert BING_EXPECTED_RESULT in results - - @pytest.mark.skipif( skip_api, reason="skipping tests that require a Bing API key", @@ -41,5 +30,4 @@ def test_bing_markdown_search_api() -> None: if __name__ == "__main__": """Runs this file's tests from the command line.""" - test_bing_markdown_search() test_bing_markdown_search_api() diff --git a/python/packages/autogen-magentic-one/tests/browser_utils/test_requests_markdown_browser.py b/python/packages/autogen-magentic-one/tests/browser_utils/test_requests_markdown_browser.py index 347d085bce49..4293f4cd23b2 100644 --- a/python/packages/autogen-magentic-one/tests/browser_utils/test_requests_markdown_browser.py +++ b/python/packages/autogen-magentic-one/tests/browser_utils/test_requests_markdown_browser.py @@ -10,7 +10,7 @@ import requests from autogen_magentic_one.markdown_browser import BingMarkdownSearch, RequestsMarkdownBrowser -BLOG_POST_URL = "https://microsoft.github.io/autogen/blog/2023/04/21/LLM-tuning-math" +BLOG_POST_URL = "https://microsoft.github.io/autogen/0.2/blog/2023/04/21/LLM-tuning-math" BLOG_POST_TITLE = "Does Model and Inference Parameter Matter in LLM Applications? - A Case Study for MATH | AutoGen" BLOG_POST_STRING = "Large language models (LLMs) are powerful tools that can generate natural language texts for various applications, such as chatbots, summarization, translation, and more. GPT-4 is currently the state of the art LLM in the world. Is model selection irrelevant? What about inference parameters?" BLOG_POST_FIND_ON_PAGE_QUERY = "an example where high * complex" diff --git a/python/pyproject.toml b/python/pyproject.toml index c24bffd1bd5d..e99f1dc38d8f 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -19,6 +19,7 @@ dev-dependencies = [ "mypy-protobuf", "cookiecutter", "poethepoet", + "tomli", ] [tool.uv.sources] diff --git a/python/run_task_in_pkgs_if_exist.py b/python/run_task_in_pkgs_if_exist.py index 1c0610209a1a..2642194c675e 100644 --- a/python/run_task_in_pkgs_if_exist.py +++ b/python/run_task_in_pkgs_if_exist.py @@ -3,14 +3,14 @@ from pathlib import Path from typing import List -import tomllib +import tomli from poethepoet.app import PoeThePoet from rich import print def discover_projects(workspace_pyproject_file: Path) -> List[Path]: with workspace_pyproject_file.open("rb") as f: - data = tomllib.load(f) + data = tomli.load(f) projects = data["tool"]["uv"]["workspace"]["members"] @@ -28,7 +28,7 @@ def discover_projects(workspace_pyproject_file: Path) -> List[Path]: def extract_poe_tasks(file: Path) -> set[str]: with file.open("rb") as f: - data = tomllib.load(f) + data = tomli.load(f) tasks = set(data.get("tool", {}).get("poe", {}).get("tasks", {}).keys()) diff --git a/python/uv.lock b/python/uv.lock index 7ab6238885bd..8b51d06e7e4a 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -45,6 +45,7 @@ requirements = [ { name = "pytest-xdist" }, { name = "rich" }, { name = "ruff", specifier = "==0.4.8" }, + { name = "tomli" }, { name = "typer" }, ] @@ -70,6 +71,7 @@ dependencies = [ { name = "huggingface-hub" }, { name = "openai" }, { name = "pandas" }, + { name = "scipy" }, { name = "tabulate" }, ] @@ -86,6 +88,7 @@ requires-dist = [ { name = "huggingface-hub" }, { name = "openai" }, { name = "pandas" }, + { name = "scipy" }, { name = "tabulate" }, ] @@ -173,21 +176,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/c2/f7eed4d602f3f224600d03ab2e1a7734999b0901b1c49b94dc5891340433/aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6", size = 1329459 }, { url = "https://files.pythonhosted.org/packages/ce/8f/27f205b76531fc592abe29e1ad265a16bf934a9f609509c02d765e6a8055/aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12", size = 356968 }, { url = "https://files.pythonhosted.org/packages/39/8c/4f6c0b2b3629f6be6c81ab84d9d577590f74f01d4412bfc4067958eaa1e1/aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc", size = 377650 }, - { url = "https://files.pythonhosted.org/packages/7b/b9/03b4327897a5b5d29338fa9b514f1c2f66a3e4fc88a4e40fad478739314d/aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092", size = 576994 }, - { url = "https://files.pythonhosted.org/packages/67/1b/20c2e159cd07b8ed6dde71c2258233902fdf415b2fe6174bd2364ba63107/aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77", size = 390684 }, - { url = "https://files.pythonhosted.org/packages/4d/6b/ff83b34f157e370431d8081c5d1741963f4fb12f9aaddb2cacbf50305225/aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385", size = 386176 }, - { url = "https://files.pythonhosted.org/packages/4d/a1/6e92817eb657de287560962df4959b7ddd22859c4b23a0309e2d3de12538/aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972", size = 1303310 }, - { url = "https://files.pythonhosted.org/packages/04/29/200518dc7a39c30ae6d5bc232d7207446536e93d3d9299b8e95db6e79c54/aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16", size = 1340445 }, - { url = "https://files.pythonhosted.org/packages/8e/20/53f7bba841ba7b5bb5dea580fea01c65524879ba39cb917d08c845524717/aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6", size = 1385121 }, - { url = "https://files.pythonhosted.org/packages/f1/b4/d99354ad614c48dd38fb1ee880a1a54bd9ab2c3bcad3013048d4a1797d3a/aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa", size = 1299669 }, - { url = "https://files.pythonhosted.org/packages/51/39/ca1de675f2a5729c71c327e52ac6344e63f036bd37281686ae5c3fb13bfb/aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689", size = 1252638 }, - { url = "https://files.pythonhosted.org/packages/54/cf/a3ae7ff43138422d477348e309ef8275779701bf305ff6054831ef98b782/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57", size = 1266889 }, - { url = "https://files.pythonhosted.org/packages/6e/7a/c6027ad70d9fb23cf254a26144de2723821dade1a624446aa22cd0b6d012/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f", size = 1266249 }, - { url = "https://files.pythonhosted.org/packages/64/fd/ed136d46bc2c7e3342fed24662b4827771d55ceb5a7687847aae977bfc17/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599", size = 1311036 }, - { url = "https://files.pythonhosted.org/packages/76/9a/43eeb0166f1119256d6f43468f900db1aed7fbe32069d2a71c82f987db4d/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5", size = 1338756 }, - { url = "https://files.pythonhosted.org/packages/d5/bc/d01ff0810b3f5e26896f76d44225ed78b088ddd33079b85cd1a23514318b/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987", size = 1299976 }, - { url = "https://files.pythonhosted.org/packages/3e/c9/50a297c4f7ab57a949f4add2d3eafe5f3e68bb42f739e933f8b32a092bda/aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04", size = 355609 }, - { url = "https://files.pythonhosted.org/packages/65/28/aee9d04fb0b3b1f90622c338a08e54af5198e704a910e20947c473298fd0/aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022", size = 375697 }, ] [[package]] @@ -373,7 +361,6 @@ source = { editable = "packages/autogen-core" } dependencies = [ { name = "aiohttp" }, { name = "asyncio-atexit" }, - { name = "docker" }, { name = "grpcio" }, { name = "openai" }, { name = "opentelemetry-api" }, @@ -429,7 +416,6 @@ dev = [ requires-dist = [ { name = "aiohttp" }, { name = "asyncio-atexit" }, - { name = "docker", specifier = "~=7.0" }, { name = "grpcio", specifier = "~=1.62.0" }, { name = "openai", specifier = ">=1.3" }, { name = "opentelemetry-api", specifier = "~=1.27.0" }, @@ -493,6 +479,9 @@ dependencies = [ azure-code-executor = [ { name = "azure-core" }, ] +docker-code-executor = [ + { name = "docker" }, +] langchain-tools = [ { name = "langchain" }, ] @@ -501,6 +490,7 @@ langchain-tools = [ requires-dist = [ { name = "autogen-core", editable = "packages/autogen-core" }, { name = "azure-core", marker = "extra == 'azure-code-executor'" }, + { name = "docker", marker = "extra == 'docker-code-executor'", specifier = "~=7.0" }, { name = "langchain", marker = "extra == 'langchain-tools'", specifier = ">=0.3.1" }, ] @@ -758,17 +748,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/83/8353e5c9b01bb46332dac3dfb18e6c597a04ceb085c19c814c2f78a8c0d0/cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885", size = 488388 }, { url = "https://files.pythonhosted.org/packages/73/0c/f9d5ca9a095b1fc88ef77d1f8b85d11151c374144e4606da33874e17b65b/cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492", size = 172096 }, { url = "https://files.pythonhosted.org/packages/72/21/8c5d285fe20a6e31d29325f1287bb0e55f7d93630a5a44cafdafb5922495/cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2", size = 181478 }, - { url = "https://files.pythonhosted.org/packages/17/8f/581f2f3c3464d5f7cf87c2f7a5ba9acc6976253e02d73804240964243ec2/cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118", size = 182638 }, - { url = "https://files.pythonhosted.org/packages/8d/1c/c9afa66684b7039f48018eb11b229b659dfb32b7a16b88251bac106dd1ff/cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7", size = 178453 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/1a134d479d3a5a1ff2fabbee551d1d3f1dd70f453e081b5f70d604aae4c0/cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377", size = 454441 }, - { url = "https://files.pythonhosted.org/packages/b1/b4/e1569475d63aad8042b0935dbf62ae2a54d1e9142424e2b0e924d2d4a529/cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb", size = 478543 }, - { url = "https://files.pythonhosted.org/packages/d2/40/a9ad03fbd64309dec5bb70bc803a9a6772602de0ee164d7b9a6ca5a89249/cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555", size = 485463 }, - { url = "https://files.pythonhosted.org/packages/a6/1a/f10be60e006dd9242a24bcc2b1cd55c34c578380100f742d8c610f7a5d26/cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204", size = 470854 }, - { url = "https://files.pythonhosted.org/packages/cc/b3/c035ed21aa3d39432bd749fe331ee90e4bc83ea2dbed1f71c4bc26c41084/cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f", size = 479096 }, - { url = "https://files.pythonhosted.org/packages/00/cb/6f7edde01131de9382c89430b8e253b8c8754d66b63a62059663ceafeab2/cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0", size = 484013 }, - { url = "https://files.pythonhosted.org/packages/b9/83/8e4e8c211ea940210d293e951bf06b1bfb90f2eeee590e9778e99b4a8676/cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4", size = 488119 }, - { url = "https://files.pythonhosted.org/packages/5e/52/3f7cfbc4f444cb4f73ff17b28690d12436dde665f67d68f1e1687908ab6c/cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a", size = 172122 }, - { url = "https://files.pythonhosted.org/packages/94/19/cf5baa07ee0f0e55eab7382459fbddaba0fdb0ba45973dd92556ae0d02db/cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7", size = 181504 }, ] [[package]] @@ -2472,23 +2451,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", size = 5012542 }, { url = "https://files.pythonhosted.org/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", size = 3486454 }, { url = "https://files.pythonhosted.org/packages/fc/82/ace5a5676051e60355bd8fb945df7b1ba4f4fb8447f2010fb816bfd57724/lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", size = 3816857 }, - { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 }, - { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 }, - { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 }, - { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 }, - { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 }, - { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 }, - { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 }, - { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 }, - { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 }, - { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 }, - { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 }, - { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 }, - { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 }, - { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 }, - { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 }, - { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 }, - { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, { url = "https://files.pythonhosted.org/packages/99/f7/b73a431c8500565aa500e99e60b448d305eaf7c0b4c893c7c5a8a69cc595/lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c", size = 3925431 }, { url = "https://files.pythonhosted.org/packages/db/48/4a206623c0d093d0e3b15f415ffb4345b0bdf661a3d0b15a112948c033c7/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a", size = 4216683 }, { url = "https://files.pythonhosted.org/packages/54/47/577820c45dd954523ae8453b632d91e76da94ca6d9ee40d8c98dd86f916b/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005", size = 4326732 }, @@ -2719,17 +2681,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/f0/d4101d4da054f04274995ddc4086c2715d9b93111eb9ed49686c0f7ccc8a/msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b", size = 394254 }, { url = "https://files.pythonhosted.org/packages/1c/12/cf07458f35d0d775ff3a2dc5559fa2e1fcd06c46f1ef510e594ebefdca01/msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b", size = 69085 }, { url = "https://files.pythonhosted.org/packages/73/80/2708a4641f7d553a63bc934a3eb7214806b5b39d200133ca7f7afb0a53e8/msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f", size = 75347 }, - { url = "https://files.pythonhosted.org/packages/c8/b0/380f5f639543a4ac413e969109978feb1f3c66e931068f91ab6ab0f8be00/msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf", size = 151142 }, - { url = "https://files.pythonhosted.org/packages/c8/ee/be57e9702400a6cb2606883d55b05784fada898dfc7fd12608ab1fdb054e/msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330", size = 84523 }, - { url = "https://files.pythonhosted.org/packages/7e/3a/2919f63acca3c119565449681ad08a2f84b2171ddfcff1dba6959db2cceb/msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734", size = 81556 }, - { url = "https://files.pythonhosted.org/packages/7c/43/a11113d9e5c1498c145a8925768ea2d5fce7cbab15c99cda655aa09947ed/msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e", size = 392105 }, - { url = "https://files.pythonhosted.org/packages/2d/7b/2c1d74ca6c94f70a1add74a8393a0138172207dc5de6fc6269483519d048/msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca", size = 399979 }, - { url = "https://files.pythonhosted.org/packages/82/8c/cf64ae518c7b8efc763ca1f1348a96f0e37150061e777a8ea5430b413a74/msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915", size = 383816 }, - { url = "https://files.pythonhosted.org/packages/69/86/a847ef7a0f5ef3fa94ae20f52a4cacf596a4e4a010197fbcc27744eb9a83/msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d", size = 380973 }, - { url = "https://files.pythonhosted.org/packages/aa/90/c74cf6e1126faa93185d3b830ee97246ecc4fe12cf9d2d31318ee4246994/msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434", size = 387435 }, - { url = "https://files.pythonhosted.org/packages/7a/40/631c238f1f338eb09f4acb0f34ab5862c4e9d7eda11c1b685471a4c5ea37/msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c", size = 399082 }, - { url = "https://files.pythonhosted.org/packages/e9/1b/fa8a952be252a1555ed39f97c06778e3aeb9123aa4cccc0fd2acd0b4e315/msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc", size = 69037 }, - { url = "https://files.pythonhosted.org/packages/b6/bc/8bd826dd03e022153bfa1766dcdec4976d6c818865ed54223d71f07862b3/msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f", size = 75140 }, ] [[package]] @@ -3125,12 +3076,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/1a/d11805670c29d3a1b29fc4bd048dc90b094784779690592efe8c9f71249a/orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b", size = 167994 }, { url = "https://files.pythonhosted.org/packages/20/5f/03d89b007f9d6733dc11bc35d64812101c85d6c4e9c53af9fa7e7689cb11/orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb", size = 143130 }, { url = "https://files.pythonhosted.org/packages/c6/9d/9b9fb6c60b8a0e04031ba85414915e19ecea484ebb625402d968ea45b8d5/orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1", size = 137326 }, - { url = "https://files.pythonhosted.org/packages/15/05/121af8a87513c56745d01ad7cf215c30d08356da9ad882ebe2ba890824cd/orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149", size = 251331 }, - { url = "https://files.pythonhosted.org/packages/73/7f/8d6ccd64a6f8bdbfe6c9be7c58aeb8094aa52a01fbbb2cda42ff7e312bd7/orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe", size = 142012 }, - { url = "https://files.pythonhosted.org/packages/04/65/f2a03fd1d4f0308f01d372e004c049f7eb9bc5676763a15f20f383fa9c01/orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c", size = 169920 }, - { url = "https://files.pythonhosted.org/packages/e2/1c/3ef8d83d7c6a619ad3d69a4d5318591b4ce5862e6eda7c26bbe8208652ca/orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad", size = 167916 }, - { url = "https://files.pythonhosted.org/packages/f2/0d/820a640e5a7dfbe525e789c70871ebb82aff73b0c7bf80082653f86b9431/orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2", size = 143089 }, - { url = "https://files.pythonhosted.org/packages/1a/72/a424db9116c7cad2950a8f9e4aeb655a7b57de988eb015acd0fcd1b4609b/orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024", size = 137081 }, ] [[package]] @@ -3289,17 +3234,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690 }, { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951 }, { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427 }, - { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685 }, - { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883 }, - { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837 }, - { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562 }, - { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761 }, - { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767 }, - { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989 }, - { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255 }, - { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603 }, - { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972 }, - { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375 }, { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889 }, { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160 }, { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020 }, @@ -3436,8 +3370,6 @@ 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 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/66/78c9c3020f573c58101dc43a44f6855d01bbbd747e24da2f0c4491200ea3/psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35", size = 249766 }, - { url = "https://files.pythonhosted.org/packages/e1/3f/2403aa9558bea4d3854b0e5e567bc3dd8e9fbc1fc4453c0aa9aafeb75467/psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1", size = 253024 }, { 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 }, { 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 }, { 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 }, @@ -3621,18 +3553,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/5e/6c716810ea20a6419188992973a73c2fb4eb99cd382368d0637ddb6d3c99/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd", size = 2119191 }, { url = "https://files.pythonhosted.org/packages/06/fc/6123b00a9240fbb9ae0babad7a005d51103d9a5d39c957a986f5cdd0c271/pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688", size = 1717788 }, { url = "https://files.pythonhosted.org/packages/d5/36/e61ad5a46607a469e2786f398cd671ebafcd9fb17f09a2359985c7228df5/pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d", size = 1898188 }, - { url = "https://files.pythonhosted.org/packages/49/75/40b0e98b658fdba02a693b3bacb4c875a28bba87796c7b13975976597d8c/pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686", size = 1838688 }, - { url = "https://files.pythonhosted.org/packages/75/02/d8ba2d4a266591a6a623c68b331b96523d4b62ab82a951794e3ed8907390/pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a", size = 1768409 }, - { url = "https://files.pythonhosted.org/packages/91/ae/25ecd9bc4ce4993e99a1a3c9ab111c082630c914260e129572fafed4ecc2/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b", size = 1789317 }, - { url = "https://files.pythonhosted.org/packages/7a/80/72057580681cdbe55699c367963d9c661b569a1d39338b4f6239faf36cdc/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19", size = 1771949 }, - { url = "https://files.pythonhosted.org/packages/a2/be/d9bbabc55b05019013180f141fcaf3b14dbe15ca7da550e95b60c321009a/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac", size = 1974392 }, - { url = "https://files.pythonhosted.org/packages/79/2d/7bcd938c6afb0f40293283f5f09988b61fb0a4f1d180abe7c23a2f665f8e/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703", size = 2625565 }, - { url = "https://files.pythonhosted.org/packages/ac/88/ca758e979457096008a4b16a064509028e3e092a1e85a5ed6c18ced8da88/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c", size = 2098784 }, - { url = "https://files.pythonhosted.org/packages/eb/de/2fad6d63c3c42e472e985acb12ec45b7f56e42e6f4cd6dfbc5e87ee8678c/pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83", size = 1900198 }, - { url = "https://files.pythonhosted.org/packages/fe/50/077c7f35b6488dc369a6d22993af3a37901e198630f38ac43391ca730f5b/pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203", size = 1968005 }, - { url = "https://files.pythonhosted.org/packages/5d/1f/f378631574ead46d636b9a04a80ff878b9365d4b361b1905ef1667d4182a/pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0", size = 2118920 }, - { url = "https://files.pythonhosted.org/packages/7a/ea/e4943f17df7a3031d709481fe4363d4624ae875a6409aec34c28c9e6cf59/pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e", size = 1717397 }, - { url = "https://files.pythonhosted.org/packages/13/63/b95781763e8d84207025071c0cec16d921c0163c7a9033ae4b9a0e020dc7/pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20", size = 1898013 }, { url = "https://files.pythonhosted.org/packages/73/73/0c7265903f66cce39ed7ca939684fba344210cefc91ccc999cfd5b113fd3/pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906", size = 1828190 }, { url = "https://files.pythonhosted.org/packages/27/55/60b8b0e58b49ee3ed36a18562dd7c6bc06a551c390e387af5872a238f2ec/pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94", size = 1715252 }, { url = "https://files.pythonhosted.org/packages/28/3d/d66314bad6bb777a36559195a007b31e916bd9e2c198f7bb8f4ccdceb4fa/pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f", size = 1782641 }, @@ -3920,15 +3840,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] [[package]] @@ -3976,27 +3887,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/18/907134c85c7152f679ed744e73e645b365f3ad571f38bdb62e36f347699a/pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7", size = 575533 }, { url = "https://files.pythonhosted.org/packages/ce/2c/a6f4a20202a4d3c582ad93f95ee78d79bbdc26803495aec2912b17dbbb6c/pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a", size = 637768 }, { url = "https://files.pythonhosted.org/packages/5f/0e/eb16ff731632d30554bf5af4dbba3ffcd04518219d82028aea4ae1b02ca5/pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b", size = 540675 }, - { url = "https://files.pythonhosted.org/packages/04/a7/0f7e2f6c126fe6e62dbae0bc93b1bd3f1099cf7fea47a5468defebe3f39d/pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726", size = 1006564 }, - { url = "https://files.pythonhosted.org/packages/31/b6/a187165c852c5d49f826a690857684333a6a4a065af0a6015572d2284f6a/pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3", size = 1340447 }, - { url = "https://files.pythonhosted.org/packages/68/ba/f4280c58ff71f321602a6e24fd19879b7e79793fb8ab14027027c0fb58ef/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50", size = 665485 }, - { url = "https://files.pythonhosted.org/packages/77/b5/c987a5c53c7d8704216f29fc3d810b32f156bcea488a940e330e1bcbb88d/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb", size = 903484 }, - { url = "https://files.pythonhosted.org/packages/29/c9/07da157d2db18c72a7eccef8e684cefc155b712a88e3d479d930aa9eceba/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187", size = 859981 }, - { url = "https://files.pythonhosted.org/packages/43/09/e12501bd0b8394b7d02c41efd35c537a1988da67fc9c745cae9c6c776d31/pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b", size = 860334 }, - { url = "https://files.pythonhosted.org/packages/eb/ff/f5ec1d455f8f7385cc0a8b2acd8c807d7fade875c14c44b85c1bddabae21/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18", size = 1196179 }, - { url = "https://files.pythonhosted.org/packages/ec/8a/bb2ac43295b1950fe436a81fc5b298be0b96ac76fb029b514d3ed58f7b27/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115", size = 1507668 }, - { url = "https://files.pythonhosted.org/packages/a9/49/dbc284ebcfd2dca23f6349227ff1616a7ee2c4a35fe0a5d6c3deff2b4fed/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e", size = 1406539 }, - { url = "https://files.pythonhosted.org/packages/00/68/093cdce3fe31e30a341d8e52a1ad86392e13c57970d722c1f62a1d1a54b6/pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5", size = 575567 }, - { url = "https://files.pythonhosted.org/packages/92/ae/6cc4657148143412b5819b05e362ae7dd09fb9fe76e2a539dcff3d0386bc/pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad", size = 637551 }, - { url = "https://files.pythonhosted.org/packages/6c/67/fbff102e201688f97c8092e4c3445d1c1068c2f27bbd45a578df97ed5f94/pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797", size = 540378 }, - { url = "https://files.pythonhosted.org/packages/3f/fe/2d998380b6e0122c6c4bdf9b6caf490831e5f5e2d08a203b5adff060c226/pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a", size = 1007378 }, - { url = "https://files.pythonhosted.org/packages/4a/f4/30d6e7157f12b3a0390bde94d6a8567cdb88846ed068a6e17238a4ccf600/pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc", size = 1329532 }, - { url = "https://files.pythonhosted.org/packages/82/86/3fe917870e15ee1c3ad48229a2a64458e36036e64b4afa9659045d82bfa8/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5", size = 653242 }, - { url = "https://files.pythonhosted.org/packages/50/2d/242e7e6ef6c8c19e6cb52d095834508cd581ffb925699fd3c640cdc758f1/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672", size = 888404 }, - { url = "https://files.pythonhosted.org/packages/ac/11/7270566e1f31e4ea73c81ec821a4b1688fd551009a3d2bab11ec66cb1e8f/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797", size = 845858 }, - { url = "https://files.pythonhosted.org/packages/91/d5/72b38fbc69867795c8711bdd735312f9fef1e3d9204e2f63ab57085434b9/pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386", size = 847375 }, - { url = "https://files.pythonhosted.org/packages/dd/9a/10ed3c7f72b4c24e719c59359fbadd1a27556a28b36cdf1cd9e4fb7845d5/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", size = 1183489 }, - { url = "https://files.pythonhosted.org/packages/72/2d/8660892543fabf1fe41861efa222455811adac9f3c0818d6c3170a1153e3/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", size = 1492932 }, - { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, { url = "https://files.pythonhosted.org/packages/53/fb/36b2b2548286e9444e52fcd198760af99fd89102b5be50f0660fcfe902df/pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072", size = 906955 }, { url = "https://files.pythonhosted.org/packages/77/8f/6ce54f8979a01656e894946db6299e2273fcee21c8e5fa57c6295ef11f57/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1", size = 565701 }, { url = "https://files.pythonhosted.org/packages/ee/1c/bf8cd66730a866b16db8483286078892b7f6536f8c389fb46e4beba0a970/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d", size = 794312 }, @@ -4156,19 +4046,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/2d/5536d28c507a4679179ab15aa0049440e4d3dd6752050fa0843ed11e9354/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", size = 528807 }, { url = "https://files.pythonhosted.org/packages/e3/62/7ebe6ec0d3dd6130921f8cffb7e34afb7f71b3819aa0446a24c5e81245ec/rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", size = 200993 }, { url = "https://files.pythonhosted.org/packages/ec/2f/b938864d66b86a6e4acadefdc56de75ef56f7cafdfd568a6464605457bd5/rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", size = 214458 }, - { url = "https://files.pythonhosted.org/packages/99/32/43b919a0a423c270a838ac2726b1c7168b946f2563fd99a51aaa9692d00f/rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", size = 321465 }, - { url = "https://files.pythonhosted.org/packages/58/a9/c4d899cb28e9e47b0ff12462e8f827381f243176036f17bef9c1604667f2/rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", size = 312900 }, - { url = "https://files.pythonhosted.org/packages/8f/90/9e51670575b5dfaa8c823369ef7d943087bfb73d4f124a99ad6ef19a2b26/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", size = 370973 }, - { url = "https://files.pythonhosted.org/packages/fc/c1/523f2a03f853fc0d4c1acbef161747e9ab7df0a8abf6236106e333540921/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", size = 370890 }, - { url = "https://files.pythonhosted.org/packages/51/ca/2458a771f16b0931de4d384decbe43016710bc948036c8f4562d6e063437/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", size = 397174 }, - { url = "https://files.pythonhosted.org/packages/00/7d/6e06807f6305ea2408b364efb0eef83a6e21b5e7b5267ad6b473b9a7e416/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", size = 426449 }, - { url = "https://files.pythonhosted.org/packages/8c/d1/6c9e65260a819a1714510a7d69ac1d68aa23ee9ce8a2d9da12187263c8fc/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", size = 357698 }, - { url = "https://files.pythonhosted.org/packages/5d/fb/ecea8b5286d2f03eec922be7173a03ed17278944f7c124348f535116db15/rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", size = 378530 }, - { url = "https://files.pythonhosted.org/packages/e3/e3/ac72f858957f52a109c588589b73bd2fad4a0fc82387fb55fb34aeb0f9cd/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", size = 545753 }, - { url = "https://files.pythonhosted.org/packages/b2/a4/a27683b519d5fc98e4390a3b130117d80fd475c67aeda8aac83c0e8e326a/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", size = 552443 }, - { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 }, - { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 }, - { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 }, { url = "https://files.pythonhosted.org/packages/06/39/bf1f664c347c946ef56cecaa896e3693d91acc741afa78ebb3fdb7aba08b/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", size = 319444 }, { url = "https://files.pythonhosted.org/packages/c1/71/876135d3cb90d62468540b84e8e83ff4dc92052ab309bfdea7ea0b9221ad/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", size = 311699 }, { url = "https://files.pythonhosted.org/packages/f7/da/8ccaeba6a3dda7467aebaf893de9eafd56275e2c90773c83bf15fb0b8374/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", size = 367825 }, @@ -4219,6 +4096,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/f1/3db1590be946c14d86ac0cc8422e5808500903592b7ca09a097e425b1dba/ruff-0.4.8-py3-none-win_arm64.whl", hash = "sha256:14019a06dbe29b608f6b7cbcec300e3170a8d86efaddb7b23405cb7f7dcaf780", size = 7944828 }, ] +[[package]] +name = "scipy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/68/3bc0cfaf64ff507d82b1e5d5b64521df4c8bf7e22bc0b897827cbee9872c/scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389", size = 39069598 }, + { url = "https://files.pythonhosted.org/packages/43/a5/8d02f9c372790326ad405d94f04d4339482ec082455b9e6e288f7100513b/scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3", size = 29879676 }, + { url = "https://files.pythonhosted.org/packages/07/42/0e0bea9666fcbf2cb6ea0205db42c81b1f34d7b729ba251010edf9c80ebd/scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0", size = 23088696 }, + { url = "https://files.pythonhosted.org/packages/15/47/298ab6fef5ebf31b426560e978b8b8548421d4ed0bf99263e1eb44532306/scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3", size = 25470699 }, + { url = "https://files.pythonhosted.org/packages/d8/df/cdb6be5274bc694c4c22862ac3438cb04f360ed9df0aecee02ce0b798380/scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d", size = 35606631 }, + { url = "https://files.pythonhosted.org/packages/47/78/b0c2c23880dd1e99e938ad49ccfb011ae353758a2dc5ed7ee59baff684c3/scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69", size = 41178528 }, + { url = "https://files.pythonhosted.org/packages/5d/aa/994b45c34b897637b853ec04334afa55a85650a0d11dacfa67232260fb0a/scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad", size = 42784535 }, + { url = "https://files.pythonhosted.org/packages/e7/1c/8daa6df17a945cb1a2a1e3bae3c49643f7b3b94017ff01a4787064f03f84/scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5", size = 44772117 }, + { url = "https://files.pythonhosted.org/packages/b2/ab/070ccfabe870d9f105b04aee1e2860520460ef7ca0213172abfe871463b9/scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675", size = 39076999 }, + { url = "https://files.pythonhosted.org/packages/a7/c5/02ac82f9bb8f70818099df7e86c3ad28dae64e1347b421d8e3adf26acab6/scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2", size = 29894570 }, + { url = "https://files.pythonhosted.org/packages/ed/05/7f03e680cc5249c4f96c9e4e845acde08eb1aee5bc216eff8a089baa4ddb/scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617", size = 23103567 }, + { url = "https://files.pythonhosted.org/packages/5e/fc/9f1413bef53171f379d786aabc104d4abeea48ee84c553a3e3d8c9f96a9c/scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8", size = 25499102 }, + { url = "https://files.pythonhosted.org/packages/c2/4b/b44bee3c2ddc316b0159b3d87a3d467ef8d7edfd525e6f7364a62cd87d90/scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37", size = 35586346 }, + { url = "https://files.pythonhosted.org/packages/93/6b/701776d4bd6bdd9b629c387b5140f006185bd8ddea16788a44434376b98f/scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2", size = 41165244 }, + { url = "https://files.pythonhosted.org/packages/06/57/e6aa6f55729a8f245d8a6984f2855696c5992113a5dc789065020f8be753/scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2", size = 42817917 }, + { url = "https://files.pythonhosted.org/packages/ea/c2/5ecadc5fcccefaece775feadcd795060adf5c3b29a883bff0e678cfe89af/scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94", size = 44781033 }, + { url = "https://files.pythonhosted.org/packages/c0/04/2bdacc8ac6387b15db6faa40295f8bd25eccf33f1f13e68a72dc3c60a99e/scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d", size = 39128781 }, + { url = "https://files.pythonhosted.org/packages/c8/53/35b4d41f5fd42f5781dbd0dd6c05d35ba8aa75c84ecddc7d44756cd8da2e/scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07", size = 29939542 }, + { url = "https://files.pythonhosted.org/packages/66/67/6ef192e0e4d77b20cc33a01e743b00bc9e68fb83b88e06e636d2619a8767/scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5", size = 23148375 }, + { url = "https://files.pythonhosted.org/packages/f6/32/3a6dedd51d68eb7b8e7dc7947d5d841bcb699f1bf4463639554986f4d782/scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc", size = 25578573 }, + { url = "https://files.pythonhosted.org/packages/f0/5a/efa92a58dc3a2898705f1dc9dbaf390ca7d4fba26d6ab8cfffb0c72f656f/scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310", size = 35319299 }, + { url = "https://files.pythonhosted.org/packages/8e/ee/8a26858ca517e9c64f84b4c7734b89bda8e63bec85c3d2f432d225bb1886/scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066", size = 40849331 }, + { url = "https://files.pythonhosted.org/packages/a5/cd/06f72bc9187840f1c99e1a8750aad4216fc7dfdd7df46e6280add14b4822/scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1", size = 42544049 }, + { url = "https://files.pythonhosted.org/packages/aa/7d/43ab67228ef98c6b5dd42ab386eae2d7877036970a0d7e3dd3eb47a0d530/scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f", size = 44521212 }, + { url = "https://files.pythonhosted.org/packages/50/ef/ac98346db016ff18a6ad7626a35808f37074d25796fd0234c2bb0ed1e054/scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79", size = 39091068 }, + { url = "https://files.pythonhosted.org/packages/b9/cc/70948fe9f393b911b4251e96b55bbdeaa8cca41f37c26fd1df0232933b9e/scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e", size = 29875417 }, + { url = "https://files.pythonhosted.org/packages/3b/2e/35f549b7d231c1c9f9639f9ef49b815d816bf54dd050da5da1c11517a218/scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73", size = 23084508 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/b028e3f3e59fae61fb8c0f450db732c43dd1d836223a589a8be9f6377203/scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e", size = 25503364 }, + { url = "https://files.pythonhosted.org/packages/a7/2f/6c142b352ac15967744d62b165537a965e95d557085db4beab2a11f7943b/scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d", size = 35292639 }, + { url = "https://files.pythonhosted.org/packages/56/46/2449e6e51e0d7c3575f289f6acb7f828938eaab8874dbccfeb0cd2b71a27/scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e", size = 40798288 }, + { url = "https://files.pythonhosted.org/packages/32/cd/9d86f7ed7f4497c9fd3e39f8918dd93d9f647ba80d7e34e4946c0c2d1a7c/scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06", size = 42524647 }, + { url = "https://files.pythonhosted.org/packages/f5/1b/6ee032251bf4cdb0cc50059374e86a9f076308c1512b61c4e003e241efb7/scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84", size = 44469524 }, +] + [[package]] name = "selenium" version = "4.24.0" @@ -4278,12 +4198,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/7d/9a57e187cbf2fbbbdfd4044a4f9ce141c8d221f9963750d3b001f0ec080d/shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726", size = 2524835 }, { url = "https://files.pythonhosted.org/packages/6d/0a/f407509ab56825f39bf8cfce1fb410238da96cf096809c3e404e5bc71ea1/shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f", size = 1295613 }, { url = "https://files.pythonhosted.org/packages/7b/b3/857afd9dfbfc554f10d683ac412eac6fa260d1f4cd2967ecb655c57e831a/shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48", size = 1442539 }, - { url = "https://files.pythonhosted.org/packages/34/e8/d164ef5b0eab86088cde06dee8415519ffd5bb0dd1bd9d021e640e64237c/shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013", size = 1445344 }, - { url = "https://files.pythonhosted.org/packages/ce/e2/9fba7ac142f7831757a10852bfa465683724eadbc93d2d46f74a16f9af04/shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7", size = 1296182 }, - { url = "https://files.pythonhosted.org/packages/cf/dc/790d4bda27d196cd56ec66975eaae3351c65614cafd0e16ddde39ec9fb92/shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381", size = 2423426 }, - { url = "https://files.pythonhosted.org/packages/af/b0/f8169f77eac7392d41e231911e0095eb1148b4d40c50ea9e34d999c89a7e/shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805", size = 2513249 }, - { url = "https://files.pythonhosted.org/packages/f6/1d/a8c0e9ab49ff2f8e4dedd71b0122eafb22a18ad7e9d256025e1f10c84704/shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a", size = 1294848 }, - { url = "https://files.pythonhosted.org/packages/23/38/2bc32dd1e7e67a471d4c60971e66df0bdace88656c47a9a728ace0091075/shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2", size = 1441371 }, ] [[package]] @@ -5088,18 +5002,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/81/1f701323a9f70805bc81c74c990137123344a80ea23ab9504a99492907f8/watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444", size = 264109 }, { url = "https://files.pythonhosted.org/packages/b4/0b/32cde5bc2ebd9f351be326837c61bdeb05ad652b793f25c91cac0b48a60b/watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896", size = 277055 }, { url = "https://files.pythonhosted.org/packages/4b/81/daade76ce33d21dbec7a15afd7479de8db786e5f7b7d249263b4ea174e08/watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418", size = 266169 }, - { url = "https://files.pythonhosted.org/packages/30/dc/6e9f5447ae14f645532468a84323a942996d74d5e817837a5c8ce9d16c69/watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48", size = 373764 }, - { url = "https://files.pythonhosted.org/packages/79/c0/c3a9929c372816c7fc87d8149bd722608ea58dc0986d3ef7564c79ad7112/watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90", size = 367873 }, - { url = "https://files.pythonhosted.org/packages/2e/11/ff9a4445a7cfc1c98caf99042df38964af12eed47d496dd5d0d90417349f/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94", size = 438381 }, - { url = "https://files.pythonhosted.org/packages/48/a3/763ba18c98211d7bb6c0f417b2d7946d346cdc359d585cc28a17b48e964b/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e", size = 432809 }, - { url = "https://files.pythonhosted.org/packages/30/4c/616c111b9d40eea2547489abaf4ffc84511e86888a166d3a4522c2ba44b5/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827", size = 451801 }, - { url = "https://files.pythonhosted.org/packages/b6/be/d7da83307863a422abbfeb12903a76e43200c90ebe5d6afd6a59d158edea/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df", size = 468886 }, - { url = "https://files.pythonhosted.org/packages/1d/d3/3dfe131ee59d5e90b932cf56aba5c996309d94dafe3d02d204364c23461c/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab", size = 472973 }, - { url = "https://files.pythonhosted.org/packages/42/6c/279288cc5653a289290d183b60a6d80e05f439d5bfdfaf2d113738d0f932/watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f", size = 425282 }, - { url = "https://files.pythonhosted.org/packages/d6/d7/58afe5e85217e845edf26d8780c2d2d2ae77675eeb8d1b8b8121d799ce52/watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b", size = 612540 }, - { url = "https://files.pythonhosted.org/packages/6d/d5/b96eeb9fe3fda137200dd2f31553670cbc731b1e13164fd69b49870b76ec/watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18", size = 593625 }, - { url = "https://files.pythonhosted.org/packages/c1/e5/c326fe52ee0054107267608d8cea275e80be4455b6079491dfd9da29f46f/watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07", size = 263899 }, - { url = "https://files.pythonhosted.org/packages/a6/8b/8a7755c5e7221bb35fe4af2dc44db9174f90ebf0344fd5e9b1e8b42d381e/watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366", size = 276622 }, { url = "https://files.pythonhosted.org/packages/df/94/1ad200e937ec91b2a9d6b39ae1cf9c2b1a9cc88d5ceb43aa5c6962eb3c11/watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f", size = 376986 }, { url = "https://files.pythonhosted.org/packages/ee/fd/d9e020d687ccf90fe95efc513fbb39a8049cf5a3ff51f53c59fcf4c47a5d/watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b", size = 369445 }, { url = "https://files.pythonhosted.org/packages/43/cb/c0279b35053555d10ef03559c5aebfcb0c703d9c70a7b4e532df74b9b0e8/watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4", size = 439383 }, @@ -5163,17 +5065,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/47/20af68a313b6453d2d094ccc497b7232e8475175d234e3e5bef5088521e5/websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237", size = 157818 }, { url = "https://files.pythonhosted.org/packages/f8/bb/60aaedc80e388e978617dda1ff38788780c6b0f6e462b85368cb934131a5/websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185", size = 151785 }, { url = "https://files.pythonhosted.org/packages/16/2e/e47692f569e1be2e66c1dbc5e85ea4d2cc93b80027fbafa28ae8b0dee52c/websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99", size = 152214 }, - { url = "https://files.pythonhosted.org/packages/46/37/d8ef4b68684d1fa368a5c64be466db07fc58b68163bc2496db2d4cc208ff/websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa", size = 150962 }, - { url = "https://files.pythonhosted.org/packages/95/49/78aeb3af08ec9887a9065e85cef9d7e199d6c6261fcd39eec087f3a62328/websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231", size = 148621 }, - { url = "https://files.pythonhosted.org/packages/31/0d/dc9b7cec8deaee452092a631ccda894bd7098859f71dd7639b4b5b9c615c/websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9", size = 148853 }, - { url = "https://files.pythonhosted.org/packages/16/bf/734cbd815d7bc94cffe35c934f4e08b619bf3b47df1c6c7af21c1d35bcfe/websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75", size = 158741 }, - { url = "https://files.pythonhosted.org/packages/af/9b/756f89b12fee8931785531a314e6f087b21774a7f8c60878e597c684f91b/websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553", size = 157690 }, - { url = "https://files.pythonhosted.org/packages/d3/37/31f97132d2262e666b797e250879ca833eab55115f88043b3952a2840eb8/websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920", size = 158132 }, - { url = "https://files.pythonhosted.org/packages/41/ce/59c8d44e148c002fec506a9527504fb4281676e2e75c2ee5a58180f1b99a/websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329", size = 158490 }, - { url = "https://files.pythonhosted.org/packages/1a/74/5b31ce0f318b902c0d70c031f8e1228ba1a4d95a46b2a24a2a5ac17f9cf0/websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7", size = 157879 }, - { url = "https://files.pythonhosted.org/packages/0d/a7/6eac4f04177644bbc98deb98d11770cc7fbc216f6f67ab187c150540fd52/websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2", size = 157873 }, - { url = "https://files.pythonhosted.org/packages/72/f6/b8b30a3b134dfdb4ccd1694befa48fddd43783957c988a1dab175732af33/websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb", size = 151782 }, - { url = "https://files.pythonhosted.org/packages/3e/88/d94ccc006c69583168aa9dd73b3f1885c8931f2c676f4bdd8cbfae91c7b6/websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b", size = 152212 }, { url = "https://files.pythonhosted.org/packages/ae/d8/9d0e5c836f89147aa769b72e2d82217ae1c17ffd5f375de8d785e1e16870/websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89", size = 148629 }, { url = "https://files.pythonhosted.org/packages/9c/ff/005a440db101d298b42cc7565579ed55a7e12ccc0c6ea0491e53bb073930/websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad", size = 148863 }, { url = "https://files.pythonhosted.org/packages/9f/06/44d7c7d48e0beaecbacaf0020eafccd490741e496622da6b2a5626fe6689/websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e", size = 150226 }, diff --git a/website/blog/2024-10-02-new-autogen-architecture-preview/index.mdx b/website/blog/2024-10-02-new-autogen-architecture-preview/index.mdx deleted file mode 100644 index 42a873d38bb2..000000000000 --- a/website/blog/2024-10-02-new-autogen-architecture-preview/index.mdx +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: New AutoGen Architecture Preview -authors: - - autogen-team -tags: [AutoGen] ---- - -# New AutoGen Architecture Preview - -
- -![What are they doing?](img/robots.jpeg) - -
- -One year ago, we launched AutoGen, a programming framework designed to build -agentic AI systems. The release of AutoGen sparked massive interest within the -developer community. As an early release, it provided us with a unique -opportunity to engage deeply with users, gather invaluable feedback, and learn -from a diverse range of use cases and contributions. By listening and engaging -with the community, we gained insights into what people were building or -attempting to build, how they were approaching the creation of agentic systems, -and where they were struggling. This experience was both humbling and -enlightening, revealing significant opportunities for improvement in our initial -design, especially for power users developing production-level applications with -AutoGen. - -Through engagements with the community, we learned many lessons: - -- Developers value modular and reusable agents. For example, our built-in agents - that could be directly plugged in or easily customized for specific use cases - were particularly popular. At the same time, there was a desire for more - customizability, such as integrating custom agents built using other - programming languages or frameworks. -- Chat-based agent-to-agent communication was an intuitive collaboration - pattern, making it easy for developers to get started and involve humans in - the loop. As developers began to employ agents in a wider range of scenarios, - they sought more flexibility in collaboration patterns. For instance, - developers wanted to build predictable, ordered workflows with agents, and to - integrate them with new user interfaces that are not chat-based. -- Although it was easy for developers to get started with AutoGen, debugging and - scaling agent teams applications proved more challenging. -- There were many opportunities for improving code quality. - -These learnings, along with many others from other agentic efforts across -Microsoft, prompted us to take a step back and lay the groundwork for a new -direction. A few months ago, we started dedicating time to distilling these -learnings into a roadmap for the future of AutoGen. This led to the development -of AutoGen 0.4, a complete redesign of the framework from the foundation up. -AutoGen 0.4 embraces the actor model of computing to support distributed, highly -scalable, event-driven agentic systems. This approach offers many advantages, -such as: - -- **Composability**. Systems designed in this way are more composable, allowing - developers to bring their own agents implemented in different frameworks or - programming languages and to build more powerful systems using complex agentic - patterns. -- **Flexibility**. It allows for the creation of both deterministic, ordered - workflows and event-driven or decentralized workflows, enabling customers to - bring their own orchestration or integrate with other systems more easily. It - also opens more opportunities for human-in-the-loop scenarios, both active and - reactive. -- **Debugging and Observability**. Event-driven communication moves message delivery - away from agents to a centralized component, making it easier to observe and - debug their activities regardless of agent implementation. -- **Scalability**. An event-based architecture enables distributed and - cloud-deployed agents, which is essential for building scalable AI services - and applications. - -Today, we are delighted to share our progress and invite everyone to collaborate -with us and provide feedback to evolve AutoGen and help shape the future of -multi-agent systems. - -As the first step, we are opening a [pull request](https://github.com/microsoft/autogen/pull/3600) into the main branch with the -current state of development of 0.4. After approximately a week, we plan to -merge this into main and continue development. There's still a lot left to do -before 0.4 is ready for release though, so keep in mind this is a work in -progress. - -Starting in AutoGen 0.4, the project will have three main libraries: - -- **Core** - the building blocks for an event-driven agentic system. -- **AgentChat** - a task-driven, high-level API built with core, including group - chat, code execution, pre-built agents, and more. This is the most similar API - to AutoGen [0.2](https://github.com/microsoft/autogen/tree/0.2) and will be the easiest API to migrate to. -- **Extensions** - implementations of core interfaces and third-party integrations - (e.g., Azure code executor and OpenAI model client). - -AutoGen [0.2](https://github.com/microsoft/autogen/tree/0.2) is still available, developed and maintained out of the [0.2 branch](https://github.com/microsoft/autogen/tree/0.2). -For everyone looking for a stable version, we recommend continuing to use [0.2](https://github.com/microsoft/autogen/tree/0.2) -for the time being. It can be installed using: - -```sh -pip install autogen-agentchat~=0.2 -``` - -This new package name was used to align with the new packages that will come with 0.4: -`autogen-core`, `autogen-agentchat`, and `autogen-ext`. - -Lastly, we will be using [GitHub -Discussion](https://github.com/microsoft/autogen/discussions) as the official -community forum for the new version and, going forward, all discussions related -to the AutoGen project. We look forward to meeting you there.