mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-06 06:38:29 +00:00
Compare commits
1 Commits
brandon/cr
...
feat/updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
870a092687 |
@@ -23,9 +23,9 @@ Flows allow you to create structured, event-driven workflows. They provide a sea
|
||||
Let's create a simple Flow where you will use OpenAI to generate a random city in one task and then use that city to generate a fun fact in another task.
|
||||
|
||||
```python Code
|
||||
import asyncio
|
||||
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
from dotenv import load_dotenv
|
||||
from litellm import completion
|
||||
|
||||
|
||||
@@ -67,19 +67,19 @@ class ExampleFlow(Flow):
|
||||
return fun_fact
|
||||
|
||||
|
||||
async def main():
|
||||
flow = ExampleFlow()
|
||||
result = await flow.kickoff()
|
||||
|
||||
flow = ExampleFlow()
|
||||
result = flow.kickoff()
|
||||
print(f"Generated fun fact: {result}")
|
||||
|
||||
print(f"Generated fun fact: {result}")
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
In the above example, we have created a simple Flow that generates a random city using OpenAI and then generates a fun fact about that city. The Flow consists of two tasks: `generate_city` and `generate_fun_fact`. The `generate_city` task is the starting point of the Flow, and the `generate_fun_fact` task listens for the output of the `generate_city` task.
|
||||
|
||||
When you run the Flow, it will generate a random city and then generate a fun fact about that city. The output will be printed to the console.
|
||||
|
||||
**Note:** Ensure you have set up your `.env` file to store your `OPENAI_API_KEY`. This key is necessary for authenticating requests to the OpenAI API.
|
||||
|
||||
### @start()
|
||||
|
||||
The `@start()` decorator is used to mark a method as the starting point of a Flow. When a Flow is started, all the methods decorated with `@start()` are executed in parallel. You can have multiple start methods in a Flow, and they will all be executed when the Flow is started.
|
||||
@@ -119,6 +119,7 @@ Here's how you can access the final output:
|
||||
|
||||
<CodeGroup>
|
||||
```python Code
|
||||
import asyncio
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
|
||||
class OutputExampleFlow(Flow):
|
||||
@@ -130,24 +131,26 @@ class OutputExampleFlow(Flow):
|
||||
def second_method(self, first_output):
|
||||
return f"Second method received: {first_output}"
|
||||
|
||||
async def main():
|
||||
flow = OutputExampleFlow()
|
||||
final_output = await flow.kickoff()
|
||||
print("---- Final Output ----")
|
||||
print(final_output)
|
||||
|
||||
flow = OutputExampleFlow()
|
||||
final_output = flow.kickoff()
|
||||
|
||||
print("---- Final Output ----")
|
||||
print(final_output)
|
||||
````
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
``` text Output
|
||||
---- Final Output ----
|
||||
Second method received: Output from first_method
|
||||
````
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
In this example, the `second_method` is the last method to complete, so its output will be the final output of the Flow.
|
||||
In this example, the `second_method` is the last method to complete, so its output will be the final output of the Flow.
|
||||
The `kickoff()` method will return the final output, which is then printed to the console.
|
||||
|
||||
|
||||
#### Accessing and Updating State
|
||||
|
||||
In addition to retrieving the final output, you can also access and update the state within your Flow. The state can be used to store and share data between different methods in the Flow. After the Flow has run, you can access the state to retrieve any information that was added or updated during the execution.
|
||||
@@ -157,6 +160,7 @@ Here's an example of how to update and access the state:
|
||||
<CodeGroup>
|
||||
|
||||
```python Code
|
||||
import asyncio
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -177,38 +181,42 @@ class StateExampleFlow(Flow[ExampleState]):
|
||||
self.state.counter += 1
|
||||
return self.state.message
|
||||
|
||||
flow = StateExampleFlow()
|
||||
final_output = flow.kickoff()
|
||||
print(f"Final Output: {final_output}")
|
||||
print("Final State:")
|
||||
print(flow.state)
|
||||
async def main():
|
||||
flow = StateExampleFlow()
|
||||
final_output = await flow.kickoff()
|
||||
print(f"Final Output: {final_output}")
|
||||
print("Final State:")
|
||||
print(flow.state)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```text Output
|
||||
``` text Output
|
||||
Final Output: Hello from first_method - updated by second_method
|
||||
Final State:
|
||||
counter=2 message='Hello from first_method - updated by second_method'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
In this example, the state is updated by both `first_method` and `second_method`.
|
||||
In this example, the state is updated by both `first_method` and `second_method`.
|
||||
After the Flow has run, you can access the final state to see the updates made by these methods.
|
||||
|
||||
By ensuring that the final method's output is returned and providing access to the state, CrewAI Flows make it easy to integrate the results of your AI workflows into larger applications or systems,
|
||||
By ensuring that the final method's output is returned and providing access to the state, CrewAI Flows make it easy to integrate the results of your AI workflows into larger applications or systems,
|
||||
while also maintaining and accessing the state throughout the Flow's execution.
|
||||
|
||||
## Flow State Management
|
||||
|
||||
Managing state effectively is crucial for building reliable and maintainable AI workflows. CrewAI Flows provides robust mechanisms for both unstructured and structured state management,
|
||||
Managing state effectively is crucial for building reliable and maintainable AI workflows. CrewAI Flows provides robust mechanisms for both unstructured and structured state management,
|
||||
allowing developers to choose the approach that best fits their application's needs.
|
||||
|
||||
### Unstructured State Management
|
||||
|
||||
In unstructured state management, all state is stored in the `state` attribute of the `Flow` class.
|
||||
In unstructured state management, all state is stored in the `state` attribute of the `Flow` class.
|
||||
This approach offers flexibility, enabling developers to add or modify state attributes on the fly without defining a strict schema.
|
||||
|
||||
```python Code
|
||||
import asyncio
|
||||
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
|
||||
class UntructuredExampleFlow(Flow):
|
||||
@@ -231,8 +239,12 @@ class UntructuredExampleFlow(Flow):
|
||||
print(f"State after third_method: {self.state}")
|
||||
|
||||
|
||||
flow = UntructuredExampleFlow()
|
||||
flow.kickoff()
|
||||
async def main():
|
||||
flow = UntructuredExampleFlow()
|
||||
await flow.kickoff()
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
@@ -242,10 +254,12 @@ flow.kickoff()
|
||||
|
||||
### Structured State Management
|
||||
|
||||
Structured state management leverages predefined schemas to ensure consistency and type safety across the workflow.
|
||||
Structured state management leverages predefined schemas to ensure consistency and type safety across the workflow.
|
||||
By using models like Pydantic's `BaseModel`, developers can define the exact shape of the state, enabling better validation and auto-completion in development environments.
|
||||
|
||||
```python Code
|
||||
import asyncio
|
||||
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -274,8 +288,12 @@ class StructuredExampleFlow(Flow[ExampleState]):
|
||||
print(f"State after third_method: {self.state}")
|
||||
|
||||
|
||||
flow = StructuredExampleFlow()
|
||||
flow.kickoff()
|
||||
async def main():
|
||||
flow = StructuredExampleFlow()
|
||||
await flow.kickoff()
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
@@ -308,6 +326,7 @@ The `or_` function in Flows allows you to listen to multiple methods and trigger
|
||||
<CodeGroup>
|
||||
|
||||
```python Code
|
||||
import asyncio
|
||||
from crewai.flow.flow import Flow, listen, or_, start
|
||||
|
||||
class OrExampleFlow(Flow):
|
||||
@@ -325,19 +344,22 @@ class OrExampleFlow(Flow):
|
||||
print(f"Logger: {result}")
|
||||
|
||||
|
||||
async def main():
|
||||
flow = OrExampleFlow()
|
||||
await flow.kickoff()
|
||||
|
||||
flow = OrExampleFlow()
|
||||
flow.kickoff()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```text Output
|
||||
``` text Output
|
||||
Logger: Hello from the start method
|
||||
Logger: Hello from the second method
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
When you run this Flow, the `logger` method will be triggered by the output of either the `start_method` or the `second_method`.
|
||||
When you run this Flow, the `logger` method will be triggered by the output of either the `start_method` or the `second_method`.
|
||||
The `or_` function is used to listen to multiple methods and trigger the listener method when any of the specified methods emit an output.
|
||||
|
||||
### Conditional Logic: `and`
|
||||
@@ -347,6 +369,7 @@ The `and_` function in Flows allows you to listen to multiple methods and trigge
|
||||
<CodeGroup>
|
||||
|
||||
```python Code
|
||||
import asyncio
|
||||
from crewai.flow.flow import Flow, and_, listen, start
|
||||
|
||||
class AndExampleFlow(Flow):
|
||||
@@ -364,28 +387,34 @@ class AndExampleFlow(Flow):
|
||||
print("---- Logger ----")
|
||||
print(self.state)
|
||||
|
||||
flow = AndExampleFlow()
|
||||
flow.kickoff()
|
||||
|
||||
async def main():
|
||||
flow = AndExampleFlow()
|
||||
await flow.kickoff()
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```text Output
|
||||
``` text Output
|
||||
---- Logger ----
|
||||
{'greeting': 'Hello from the start method', 'joke': 'What do computers eat? Microchips.'}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
When you run this Flow, the `logger` method will be triggered only when both the `start_method` and the `second_method` emit an output.
|
||||
When you run this Flow, the `logger` method will be triggered only when both the `start_method` and the `second_method` emit an output.
|
||||
The `and_` function is used to listen to multiple methods and trigger the listener method only when all the specified methods emit an output.
|
||||
|
||||
### Router
|
||||
|
||||
The `@router()` decorator in Flows allows you to define conditional routing logic based on the output of a method.
|
||||
The `@router()` decorator in Flows allows you to define conditional routing logic based on the output of a method.
|
||||
You can specify different routes based on the output of the method, allowing you to control the flow of execution dynamically.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Code
|
||||
import asyncio
|
||||
import random
|
||||
from crewai.flow.flow import Flow, listen, router, start
|
||||
from pydantic import BaseModel
|
||||
@@ -417,11 +446,15 @@ class RouterFlow(Flow[ExampleState]):
|
||||
print("Fourth method running")
|
||||
|
||||
|
||||
flow = RouterFlow()
|
||||
flow.kickoff()
|
||||
async def main():
|
||||
flow = RouterFlow()
|
||||
await flow.kickoff()
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```text Output
|
||||
``` text Output
|
||||
Starting the structured flow
|
||||
Third method running
|
||||
Fourth method running
|
||||
@@ -429,16 +462,16 @@ Fourth method running
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
In the above example, the `start_method` generates a random boolean value and sets it in the state.
|
||||
The `second_method` uses the `@router()` decorator to define conditional routing logic based on the value of the boolean.
|
||||
If the boolean is `True`, the method returns `"success"`, and if it is `False`, the method returns `"failed"`.
|
||||
In the above example, the `start_method` generates a random boolean value and sets it in the state.
|
||||
The `second_method` uses the `@router()` decorator to define conditional routing logic based on the value of the boolean.
|
||||
If the boolean is `True`, the method returns `"success"`, and if it is `False`, the method returns `"failed"`.
|
||||
The `third_method` and `fourth_method` listen to the output of the `second_method` and execute based on the returned value.
|
||||
|
||||
When you run this Flow, the output will change based on the random boolean value generated by the `start_method`.
|
||||
|
||||
## Adding Crews to Flows
|
||||
|
||||
Creating a flow with multiple crews in CrewAI is straightforward.
|
||||
Creating a flow with multiple crews in CrewAI is straightforward.
|
||||
|
||||
You can generate a new CrewAI project that includes all the scaffolding needed to create a flow with multiple crews by running the following command:
|
||||
|
||||
@@ -452,21 +485,22 @@ This command will generate a new CrewAI project with the necessary folder struct
|
||||
|
||||
After running the `crewai create flow name_of_flow` command, you will see a folder structure similar to the following:
|
||||
|
||||
| Directory/File | Description |
|
||||
| :--------------------- | :----------------------------------------------------------------- |
|
||||
| `name_of_flow/` | Root directory for the flow. |
|
||||
| ├── `crews/` | Contains directories for specific crews. |
|
||||
| │ └── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts. |
|
||||
| │ ├── `config/` | Configuration files directory for the "poem_crew". |
|
||||
| │ │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". |
|
||||
| │ │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". |
|
||||
| │ ├── `poem_crew.py` | Script for "poem_crew" functionality. |
|
||||
| ├── `tools/` | Directory for additional tools used in the flow. |
|
||||
| │ └── `custom_tool.py` | Custom tool implementation. |
|
||||
| ├── `main.py` | Main script for running the flow. |
|
||||
| ├── `README.md` | Project description and instructions. |
|
||||
| ├── `pyproject.toml` | Configuration file for project dependencies and settings. |
|
||||
| └── `.gitignore` | Specifies files and directories to ignore in version control. |
|
||||
| Directory/File | Description |
|
||||
|:---------------------------------|:------------------------------------------------------------------|
|
||||
| `name_of_flow/` | Root directory for the flow. |
|
||||
| ├── `crews/` | Contains directories for specific crews. |
|
||||
| │ └── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts.|
|
||||
| │ ├── `config/` | Configuration files directory for the "poem_crew". |
|
||||
| │ │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". |
|
||||
| │ │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". |
|
||||
| │ ├── `poem_crew.py` | Script for "poem_crew" functionality. |
|
||||
| ├── `tools/` | Directory for additional tools used in the flow. |
|
||||
| │ └── `custom_tool.py` | Custom tool implementation. |
|
||||
| ├── `main.py` | Main script for running the flow. |
|
||||
| ├── `README.md` | Project description and instructions. |
|
||||
| ├── `pyproject.toml` | Configuration file for project dependencies and settings. |
|
||||
| └── `.gitignore` | Specifies files and directories to ignore in version control. |
|
||||
|
||||
|
||||
### Building Your Crews
|
||||
|
||||
@@ -486,6 +520,7 @@ Here's an example of how you can connect the `poem_crew` in the `main.py` file:
|
||||
|
||||
```python Code
|
||||
#!/usr/bin/env python
|
||||
import asyncio
|
||||
from random import randint
|
||||
|
||||
from pydantic import BaseModel
|
||||
@@ -501,12 +536,14 @@ class PoemFlow(Flow[PoemState]):
|
||||
@start()
|
||||
def generate_sentence_count(self):
|
||||
print("Generating sentence count")
|
||||
# Generate a number between 1 and 5
|
||||
self.state.sentence_count = randint(1, 5)
|
||||
|
||||
@listen(generate_sentence_count)
|
||||
def generate_poem(self):
|
||||
print("Generating poem")
|
||||
result = PoemCrew().crew().kickoff(inputs={"sentence_count": self.state.sentence_count})
|
||||
poem_crew = PoemCrew().crew()
|
||||
result = poem_crew.kickoff(inputs={"sentence_count": self.state.sentence_count})
|
||||
|
||||
print("Poem generated", result.raw)
|
||||
self.state.poem = result.raw
|
||||
@@ -517,17 +554,18 @@ class PoemFlow(Flow[PoemState]):
|
||||
with open("poem.txt", "w") as f:
|
||||
f.write(self.state.poem)
|
||||
|
||||
def kickoff():
|
||||
async def run():
|
||||
"""
|
||||
Run the flow.
|
||||
"""
|
||||
poem_flow = PoemFlow()
|
||||
poem_flow.kickoff()
|
||||
await poem_flow.kickoff()
|
||||
|
||||
|
||||
def plot():
|
||||
poem_flow = PoemFlow()
|
||||
poem_flow.plot()
|
||||
def main():
|
||||
asyncio.run(run())
|
||||
|
||||
if __name__ == "__main__":
|
||||
kickoff()
|
||||
main()
|
||||
```
|
||||
|
||||
In this example, the `PoemFlow` class defines a flow that generates a sentence count, uses the `PoemCrew` to generate a poem, and then saves the poem to a file. The flow is kicked off by calling the `kickoff()` method.
|
||||
@@ -549,13 +587,13 @@ source .venv/bin/activate
|
||||
After activating the virtual environment, you can run the flow by executing one of the following commands:
|
||||
|
||||
```bash
|
||||
crewai flow kickoff
|
||||
crewai flow run
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
uv run kickoff
|
||||
uv run run_flow
|
||||
```
|
||||
|
||||
The flow will execute, and you should see the output in the console.
|
||||
@@ -619,13 +657,13 @@ By exploring these examples, you can gain insights into how to leverage CrewAI F
|
||||
|
||||
Also, check out our YouTube video on how to use flows in CrewAI below!
|
||||
|
||||
<iframe
|
||||
width="560"
|
||||
height="315"
|
||||
src="https://www.youtube.com/embed/MTb5my6VOT8"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
<iframe
|
||||
width="560"
|
||||
height="315"
|
||||
src="https://www.youtube.com/embed/MTb5my6VOT8"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "crewai"
|
||||
version = "0.74.2"
|
||||
version = "0.74.0"
|
||||
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
@@ -16,7 +16,7 @@ dependencies = [
|
||||
"opentelemetry-exporter-otlp-proto-http>=1.22.0",
|
||||
"instructor>=1.3.3",
|
||||
"regex>=2024.9.11",
|
||||
"crewai-tools>=0.13.2",
|
||||
"crewai-tools>=0.13.1",
|
||||
"click>=8.1.7",
|
||||
"python-dotenv>=1.0.0",
|
||||
"appdirs>=1.4.4",
|
||||
@@ -25,7 +25,7 @@ dependencies = [
|
||||
"auth0-python>=4.7.1",
|
||||
"litellm>=1.44.22",
|
||||
"pyvis>=0.3.2",
|
||||
"uv>=0.4.25",
|
||||
"uv>=0.4.18",
|
||||
"tomli-w>=1.1.0",
|
||||
"chromadb>=0.4.24",
|
||||
]
|
||||
@@ -36,7 +36,7 @@ Documentation = "https://docs.crewai.com"
|
||||
Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = ["crewai-tools>=0.13.2"]
|
||||
tools = ["crewai-tools>=0.12.1"]
|
||||
agentops = ["agentops>=0.3.0"]
|
||||
|
||||
[tool.uv]
|
||||
@@ -51,7 +51,7 @@ dev-dependencies = [
|
||||
"mkdocs-material-extensions>=1.3.1",
|
||||
"pillow>=10.2.0",
|
||||
"cairosvg>=2.7.1",
|
||||
"crewai-tools>=0.13.2",
|
||||
"crewai-tools>=0.12.1",
|
||||
"pytest>=8.0.0",
|
||||
"pytest-vcr>=1.0.2",
|
||||
"python-dotenv>=1.0.0",
|
||||
|
||||
@@ -14,5 +14,5 @@ warnings.filterwarnings(
|
||||
category=UserWarning,
|
||||
module="pydantic.main",
|
||||
)
|
||||
__version__ = "0.74.2"
|
||||
__version__ = "0.74.0"
|
||||
__all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"]
|
||||
|
||||
@@ -394,7 +394,7 @@ class Agent(BaseAgent):
|
||||
"""
|
||||
tool_strings = []
|
||||
for tool in tools:
|
||||
args_schema = str(tool.model_fields)
|
||||
args_schema = str(tool.args)
|
||||
if hasattr(tool, "func") and tool.func:
|
||||
sig = signature(tool.func)
|
||||
description = (
|
||||
|
||||
@@ -2,7 +2,6 @@ import json
|
||||
import re
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.agents.agent_builder.base_agent_executor_mixin import CrewAgentExecutorMixin
|
||||
from crewai.agents.parser import (
|
||||
FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE,
|
||||
@@ -20,6 +19,7 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
)
|
||||
from crewai.utilities.logger import Logger
|
||||
from crewai.utilities.training_handler import CrewTrainingHandler
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
|
||||
|
||||
class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
@@ -323,9 +323,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
if self.crew is not None and hasattr(self.crew, "_train_iteration"):
|
||||
train_iteration = self.crew._train_iteration
|
||||
if agent_id in training_data and isinstance(train_iteration, int):
|
||||
training_data[agent_id][train_iteration][
|
||||
"improved_output"
|
||||
] = result.output
|
||||
training_data[agent_id][train_iteration]["improved_output"] = (
|
||||
result.output
|
||||
)
|
||||
training_handler.save(training_data)
|
||||
else:
|
||||
self._logger.log(
|
||||
|
||||
@@ -14,11 +14,11 @@ from .authentication.main import AuthenticationCommand
|
||||
from .deploy.main import DeployCommand
|
||||
from .evaluate_crew import evaluate_crew
|
||||
from .install_crew import install_crew
|
||||
from .kickoff_flow import kickoff_flow
|
||||
from .plot_flow import plot_flow
|
||||
from .replay_from_task import replay_task_command
|
||||
from .reset_memories_command import reset_memories_command
|
||||
from .run_crew import run_crew
|
||||
from .run_flow import run_flow
|
||||
from .tools.main import ToolCommand
|
||||
from .train_crew import train_crew
|
||||
from .update_crew import update_crew
|
||||
@@ -304,11 +304,11 @@ def flow():
|
||||
pass
|
||||
|
||||
|
||||
@flow.command(name="kickoff")
|
||||
@flow.command(name="run")
|
||||
def flow_run():
|
||||
"""Kickoff the Flow."""
|
||||
"""Run the Flow."""
|
||||
click.echo("Running the Flow")
|
||||
kickoff_flow()
|
||||
run_flow()
|
||||
|
||||
|
||||
@flow.command(name="plot")
|
||||
|
||||
@@ -3,11 +3,11 @@ import subprocess
|
||||
import click
|
||||
|
||||
|
||||
def kickoff_flow() -> None:
|
||||
def run_flow() -> None:
|
||||
"""
|
||||
Kickoff the flow by running a command in the UV environment.
|
||||
Run the flow by running a command in the UV environment.
|
||||
"""
|
||||
command = ["uv", "run", "kickoff"]
|
||||
command = ["uv", "run", "run_flow"]
|
||||
|
||||
try:
|
||||
result = subprocess.run(command, capture_output=False, text=True, check=True)
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.74.2,<1.0.0"
|
||||
"crewai[tools]>=0.74.0,<1.0.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -1,53 +1,65 @@
|
||||
#!/usr/bin/env python
|
||||
import asyncio
|
||||
from random import randint
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
|
||||
from .crews.poem_crew.poem_crew import PoemCrew
|
||||
|
||||
|
||||
class PoemState(BaseModel):
|
||||
sentence_count: int = 1
|
||||
poem: str = ""
|
||||
|
||||
|
||||
class PoemFlow(Flow[PoemState]):
|
||||
|
||||
@start()
|
||||
def generate_sentence_count(self):
|
||||
print("Generating sentence count")
|
||||
self.state.sentence_count = randint(1, 5)
|
||||
# Generate a number between 1 and 5
|
||||
self.state.sentence_count = randint(1, 5)
|
||||
|
||||
@listen(generate_sentence_count)
|
||||
def generate_poem(self):
|
||||
print("Generating poem")
|
||||
result = (
|
||||
PoemCrew()
|
||||
.crew()
|
||||
.kickoff(inputs={"sentence_count": self.state.sentence_count})
|
||||
)
|
||||
|
||||
print(f"State before poem: {self.state}")
|
||||
result = PoemCrew().crew().kickoff(inputs={"sentence_count": self.state.sentence_count})
|
||||
|
||||
print("Poem generated", result.raw)
|
||||
self.state.poem = result.raw
|
||||
|
||||
print(f"State after generate_poem: {self.state}")
|
||||
|
||||
@listen(generate_poem)
|
||||
def save_poem(self):
|
||||
print("Saving poem")
|
||||
print(f"State before save_poem: {self.state}")
|
||||
with open("poem.txt", "w") as f:
|
||||
f.write(self.state.poem)
|
||||
print(f"State after save_poem: {self.state}")
|
||||
|
||||
|
||||
def kickoff():
|
||||
async def run_flow():
|
||||
"""
|
||||
Run the flow.
|
||||
"""
|
||||
poem_flow = PoemFlow()
|
||||
poem_flow.kickoff()
|
||||
await poem_flow.kickoff()
|
||||
|
||||
|
||||
def plot():
|
||||
async def plot_flow():
|
||||
"""
|
||||
Plot the flow.
|
||||
"""
|
||||
poem_flow = PoemFlow()
|
||||
poem_flow.plot()
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(run_flow())
|
||||
|
||||
|
||||
def plot():
|
||||
asyncio.run(plot_flow())
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
kickoff()
|
||||
main()
|
||||
|
||||
@@ -5,12 +5,14 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.74.2,<1.0.0",
|
||||
"crewai[tools]>=0.74.0,<1.0.0",
|
||||
"asyncio"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
kickoff = "{{folder_name}}.main:kickoff"
|
||||
plot = "{{folder_name}}.main:plot"
|
||||
{{folder_name}} = "{{folder_name}}.main:main"
|
||||
run_flow = "{{folder_name}}.main:main"
|
||||
plot_flow = "{{folder_name}}.main:plot"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
|
||||
@@ -6,7 +6,7 @@ authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10,<=3.13"
|
||||
crewai = { extras = ["tools"], version = ">=0.74.2,<1.0.0" }
|
||||
crewai = { extras = ["tools"], version = ">=0.74.0,<1.0.0" }
|
||||
asyncio = "*"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.74.2,<1.0.0"
|
||||
"crewai[tools]>=0.74.0,<1.0.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.74.2"
|
||||
"crewai[tools]>=0.74.0"
|
||||
]
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
A class to handle tool repository related operations for CrewAI projects.
|
||||
"""
|
||||
|
||||
BASE_URL = "https://app.crewai.com/pypi/"
|
||||
|
||||
def __init__(self):
|
||||
BaseCommand.__init__(self)
|
||||
PlusAPIMixin.__init__(self, telemetry=self._telemetry)
|
||||
@@ -176,14 +178,12 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
def _add_package(self, tool_details):
|
||||
tool_handle = tool_details["handle"]
|
||||
repository_handle = tool_details["repository"]["handle"]
|
||||
repository_url = tool_details["repository"]["url"]
|
||||
index = f"{repository_handle}={repository_url}"
|
||||
|
||||
add_package_command = [
|
||||
"uv",
|
||||
"add",
|
||||
"--index",
|
||||
index,
|
||||
"--extra-index-url",
|
||||
self.BASE_URL + repository_handle,
|
||||
tool_handle,
|
||||
]
|
||||
add_package_result = subprocess.run(
|
||||
|
||||
@@ -190,10 +190,7 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
"""Returns the list of all outputs from executed methods."""
|
||||
return self._method_outputs
|
||||
|
||||
def kickoff(self) -> Any:
|
||||
return asyncio.run(self.kickoff_async())
|
||||
|
||||
async def kickoff_async(self) -> Any:
|
||||
async def kickoff(self) -> Any:
|
||||
if not self._start_methods:
|
||||
raise ValueError("No start method defined")
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ from difflib import SequenceMatcher
|
||||
from textwrap import dedent
|
||||
from typing import Any, List, Union
|
||||
|
||||
import crewai.utilities.events as events
|
||||
from crewai.agents.tools_handler import ToolsHandler
|
||||
from crewai.task import Task
|
||||
from crewai.telemetry import Telemetry
|
||||
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
|
||||
from crewai.tools.tool_usage_events import ToolUsageError, ToolUsageFinished
|
||||
from crewai.utilities import I18N, Converter, ConverterError, Printer
|
||||
import crewai.utilities.events as events
|
||||
|
||||
|
||||
agentops = None
|
||||
if os.environ.get("AGENTOPS_API_KEY"):
|
||||
@@ -299,11 +300,8 @@ class ToolUsage:
|
||||
descriptions = []
|
||||
for tool in self.tools:
|
||||
args = {
|
||||
name: {
|
||||
"description": field.description,
|
||||
"type": field.annotation.__name__,
|
||||
}
|
||||
for name, field in tool.args_schema.model_fields.items()
|
||||
k: {k2: v2 for k2, v2 in v.items() if k2 in ["description", "type"]}
|
||||
for k, v in tool.args.items()
|
||||
}
|
||||
descriptions.append(
|
||||
"\n".join(
|
||||
|
||||
@@ -75,8 +75,8 @@ def test_install_success(mock_get, mock_subprocess_run):
|
||||
[
|
||||
"uv",
|
||||
"add",
|
||||
"--index",
|
||||
"sample-repo=https://example.com/repo",
|
||||
"--extra-index-url",
|
||||
"https://app.crewai.com/pypi/sample-repo",
|
||||
"sample-tool",
|
||||
],
|
||||
capture_output=False,
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import json
|
||||
import random
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from crewai_tools import BaseTool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai import Agent, Crew, Task
|
||||
from crewai.tools.tool_usage import ToolUsage
|
||||
|
||||
|
||||
class RandomNumberToolInput(BaseModel):
|
||||
min_value: int = Field(
|
||||
..., description="The minimum value of the range (inclusive)"
|
||||
)
|
||||
max_value: int = Field(
|
||||
..., description="The maximum value of the range (inclusive)"
|
||||
)
|
||||
|
||||
|
||||
class RandomNumberTool(BaseTool):
|
||||
name: str = "Random Number Generator"
|
||||
description: str = "Generates a random number within a specified range"
|
||||
args_schema: type[BaseModel] = RandomNumberToolInput
|
||||
|
||||
def _run(self, min_value: int, max_value: int) -> int:
|
||||
return random.randint(min_value, max_value)
|
||||
|
||||
|
||||
# Example agent and task
|
||||
example_agent = Agent(
|
||||
role="Number Generator",
|
||||
goal="Generate random numbers for various purposes",
|
||||
backstory="You are an AI agent specialized in generating random numbers within specified ranges.",
|
||||
tools=[RandomNumberTool()],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
example_task = Task(
|
||||
description="Generate a random number between 1 and 100",
|
||||
expected_output="A random number between 1 and 100",
|
||||
agent=example_agent,
|
||||
)
|
||||
|
||||
|
||||
def test_random_number_tool_usage():
|
||||
crew = Crew(
|
||||
agents=[example_agent],
|
||||
tasks=[example_task],
|
||||
)
|
||||
|
||||
with patch.object(random, "randint", return_value=42):
|
||||
result = crew.kickoff()
|
||||
|
||||
assert "42" in result.raw
|
||||
|
||||
|
||||
def test_random_number_tool_range():
|
||||
tool = RandomNumberTool()
|
||||
result = tool._run(1, 10)
|
||||
assert 1 <= result <= 10
|
||||
|
||||
|
||||
def test_random_number_tool_with_crew():
|
||||
crew = Crew(
|
||||
agents=[example_agent],
|
||||
tasks=[example_task],
|
||||
)
|
||||
|
||||
result = crew.kickoff()
|
||||
|
||||
# Check if the result contains a number between 1 and 100
|
||||
assert any(str(num) in result.raw for num in range(1, 101))
|
||||
|
||||
|
||||
def test_random_number_tool_invalid_range():
|
||||
tool = RandomNumberTool()
|
||||
with pytest.raises(ValueError):
|
||||
tool._run(10, 1) # min_value > max_value
|
||||
|
||||
|
||||
def test_random_number_tool_schema():
|
||||
tool = RandomNumberTool()
|
||||
|
||||
# Get the schema using model_json_schema()
|
||||
schema = tool.args_schema.model_json_schema()
|
||||
|
||||
# Convert the schema to a string
|
||||
schema_str = json.dumps(schema)
|
||||
|
||||
# Check if the schema string contains the expected fields
|
||||
assert "min_value" in schema_str
|
||||
assert "max_value" in schema_str
|
||||
|
||||
# Parse the schema string back to a dictionary
|
||||
schema_dict = json.loads(schema_str)
|
||||
|
||||
# Check if the schema contains the correct field types
|
||||
assert schema_dict["properties"]["min_value"]["type"] == "integer"
|
||||
assert schema_dict["properties"]["max_value"]["type"] == "integer"
|
||||
|
||||
# Check if the schema contains the field descriptions
|
||||
assert (
|
||||
"minimum value" in schema_dict["properties"]["min_value"]["description"].lower()
|
||||
)
|
||||
assert (
|
||||
"maximum value" in schema_dict["properties"]["max_value"]["description"].lower()
|
||||
)
|
||||
|
||||
|
||||
def test_tool_usage_render():
|
||||
tool = RandomNumberTool()
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[tool],
|
||||
original_tools=[tool],
|
||||
tools_description="Sample tool for testing",
|
||||
tools_names="random_number_generator",
|
||||
task=MagicMock(),
|
||||
function_calling_llm=MagicMock(),
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
rendered = tool_usage._render()
|
||||
|
||||
# Updated checks to match the actual output
|
||||
assert "Tool Name: random number generator" in rendered
|
||||
assert (
|
||||
"Random Number Generator(min_value: 'integer', max_value: 'integer') - Generates a random number within a specified range min_value: 'The minimum value of the range (inclusive)', max_value: 'The maximum value of the range (inclusive)'"
|
||||
in rendered
|
||||
)
|
||||
assert "Tool Arguments:" in rendered
|
||||
assert (
|
||||
"'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}"
|
||||
in rendered
|
||||
)
|
||||
assert (
|
||||
"'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}"
|
||||
in rendered
|
||||
)
|
||||
54
uv.lock
generated
54
uv.lock
generated
@@ -627,7 +627,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "crewai"
|
||||
version = "0.74.2"
|
||||
version = "0.74.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "appdirs" },
|
||||
@@ -687,8 +687,8 @@ requires-dist = [
|
||||
{ name = "auth0-python", specifier = ">=4.7.1" },
|
||||
{ name = "chromadb", specifier = ">=0.4.24" },
|
||||
{ name = "click", specifier = ">=8.1.7" },
|
||||
{ name = "crewai-tools", specifier = ">=0.13.2" },
|
||||
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.13.2" },
|
||||
{ name = "crewai-tools", specifier = ">=0.13.1" },
|
||||
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.12.1" },
|
||||
{ name = "instructor", specifier = ">=1.3.3" },
|
||||
{ name = "json-repair", specifier = ">=0.25.2" },
|
||||
{ name = "jsonref", specifier = ">=1.1.0" },
|
||||
@@ -703,13 +703,13 @@ requires-dist = [
|
||||
{ name = "pyvis", specifier = ">=0.3.2" },
|
||||
{ name = "regex", specifier = ">=2024.9.11" },
|
||||
{ name = "tomli-w", specifier = ">=1.1.0" },
|
||||
{ name = "uv", specifier = ">=0.4.25" },
|
||||
{ name = "uv", specifier = ">=0.4.18" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "cairosvg", specifier = ">=2.7.1" },
|
||||
{ name = "crewai-tools", specifier = ">=0.13.2" },
|
||||
{ name = "crewai-tools", specifier = ">=0.12.1" },
|
||||
{ name = "mkdocs", specifier = ">=1.4.3" },
|
||||
{ name = "mkdocs-material", specifier = ">=9.5.7" },
|
||||
{ name = "mkdocs-material-extensions", specifier = ">=1.3.1" },
|
||||
@@ -728,7 +728,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "crewai-tools"
|
||||
version = "0.13.2"
|
||||
version = "0.13.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "beautifulsoup4" },
|
||||
@@ -746,9 +746,9 @@ dependencies = [
|
||||
{ name = "requests" },
|
||||
{ name = "selenium" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/02/136f42ed8a7bd706a85663714c615bdcb684e43e95e4719c892aa0ce3d53/crewai_tools-0.13.2.tar.gz", hash = "sha256:c6782f2e868c0e96b25891f1b40fb8c90c01e920bab2fd1388f89ef1d7a4b99b", size = 816250 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/81/b8a0bb984aea2af49b0072e074c87c75a6c4581902b81f3a3d46f95f01c7/crewai_tools-0.13.1.tar.gz", hash = "sha256:363c7ec717f4c6f9b61cec9314a5ec2fbd026d75e8e6278f49f715ed5915cd4d", size = 816254 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/30/df215173b6193b2cfb1902a339443be73056eae89579805b853c6f359761/crewai_tools-0.13.2-py3-none-any.whl", hash = "sha256:8c7583c9559fb625f594349c6553a5251ebd7b21918735ad6fbe8bab7ec3db50", size = 463444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/8a/04c885da3e01d1f11478dd866d3506906bfb60d7587627dd4b132ff10f64/crewai_tools-0.13.1-py3-none-any.whl", hash = "sha256:62067e2502bf66c0ae2e3a833c60b900bd1f793a9a80895a1f10a9cfa1b5dc3c", size = 463444 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4542,27 +4542,27 @@ socks = [
|
||||
|
||||
[[package]]
|
||||
name = "uv"
|
||||
version = "0.4.25"
|
||||
version = "0.4.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/bc/1a013408b7f9f437385705652f404b6b15127ecf108327d13be493bdfb81/uv-0.4.25.tar.gz", hash = "sha256:d39077cdfe3246885fcdf32e7066ae731a166101d063629f9cea08738f79e6a3", size = 2064863 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/60/bf5ad6895740e7269ee2f5cf7515cf2756cc8eb06c07c9783abcf1d7860f/uv-0.4.18.tar.gz", hash = "sha256:954964eff8c7e2bc63dd4beeb8d45bcaddb5149a7ef29a36abd77ec76c8b837e", size = 2008833 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/18/9c9056d373620b1cf5182ce9b2d258e86d117d667cf8883e12870f2a5edf/uv-0.4.25-py3-none-linux_armv6l.whl", hash = "sha256:94fb2b454afa6bdfeeea4b4581c878944ca9cf3a13712e6762f245f5fbaaf952", size = 13028246 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/19/8a3f09aba30ac5433dfecde55d5241a07c96bb12340c3b810bc58188a12e/uv-0.4.25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a7c3a18c20ddb527d296d1222bddf42b78031c50b5b4609d426569b5fb61f5b0", size = 13175265 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/c9/2f924bb29bd53c51b839c1c6126bd2cf4c451d4a7d8f34be078f9e31c57e/uv-0.4.25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:18100f0f36419a154306ed6211e3490bf18384cdf3f1a0950848bf64b62fa251", size = 12255610 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/5a/d8f8971aeb3389679505cf633a786cd72a96ce232f80f14cfe5a693b4c64/uv-0.4.25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:6e981b1465e30102e41946adede9cb08051a5d70c6daf09f91a7ea84f0b75c08", size = 12506511 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/96/8c73520daeba5022cec8749e44afd4ca9ef774bf728af9c258bddec3577f/uv-0.4.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:578ae385fad6bd6f3868828e33d54994c716b315b1bc49106ec1f54c640837e4", size = 12836250 },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/3d/b0e810d365fb154fe1d380a0f43ee35a683cf9162f2501396d711bec2621/uv-0.4.25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d29a78f011ecc2f31c13605acb6574c2894c06d258b0f8d0dbb899986800450", size = 13521303 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/f4/dd3830ec7fc6e7e5237c184f30f2dbfed4f93605e472147eca1373bcc72b/uv-0.4.25-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec181be2bda10651a3558156409ac481549983e0276d0e3645e3b1464e7f8715", size = 14105308 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/4e/0fca02f8681e4870beda172552e747e0424f6e9186546b00a5e92525fea9/uv-0.4.25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50c7d0d9e7f392f81b13bf3b7e37768d1486f2fc9d533a54982aa0ed11e4db23", size = 13859475 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/07/1100e9bc652f2850930f466869515d16ffe9582aaaaa99bac332ebdfe3ea/uv-0.4.25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fc35b5273f1e018aecd66b70e0fd7d2eb6698853dde3e2fc644e7ebf9f825b1", size = 18100840 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/98/ba1cb7dd2aa639a064a9e49721e08f12a3424456d60dde1327e7c6437930/uv-0.4.25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7022a71ff63a3838796f40e954b76bf7820fc27e96fe002c537e75ff8e34f1d", size = 13645464 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/05/b97fb8c828a070e8291826922b2712d1146b11563b4860bc9ba80f5635d1/uv-0.4.25-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e02afb0f6d4b58718347f7d7cfa5a801e985ce42181ba971ed85ef149f6658ca", size = 12694995 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/97/63df050811379130202898f60e735a1a331ba3a93b8aa1e9bb466f533913/uv-0.4.25-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:3d7680795ea78cdbabbcce73d039b2651cf1fa635ddc1aa3082660f6d6255c50", size = 12831737 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/e0/08352dcffa6e8435328861ea60b2c05e8bd030f1e93998443ba66209db7b/uv-0.4.25-py3-none-musllinux_1_1_i686.whl", hash = "sha256:aae9dcafd20d5ba978c8a4939ab942e8e2e155c109e9945207fbbd81d2892c9e", size = 13273529 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/f4/eaf95e5eee4e2e69884df0953d094deae07216f72068ef1df08c0f49841d/uv-0.4.25-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:4c55040e67470f2b73e95e432aba06f103a0b348ea0b9c6689b1029c8d9e89fd", size = 15039860 },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/04/482b1cc9e8d599c7d766c4ba2d7a512ed3989921443792f92f26b8d44fe6/uv-0.4.25-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:bdbfd0c476b9e80a3f89af96aed6dd7d2782646311317a9c72614ccce99bb2ad", size = 13776302 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/7e/3d1cb735cc3df6341ac884b73eeec1f51a29192721be40be8e9b1d82666d/uv-0.4.25-py3-none-win32.whl", hash = "sha256:7d266e02fefef930609328c31c075084295c3cb472bab3f69549fad4fd9d82b3", size = 12970553 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/e9/c00d2bb4a286b13fad0f06488ea9cbe9e76d0efcd81e7a907f72195d5b83/uv-0.4.25-py3-none-win_amd64.whl", hash = "sha256:be2a4fc4fcade9ea5e67e51738c95644360d6e59b6394b74fc579fb617f902f7", size = 14702875 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f9/b3f093abb8f91e2374461b903a4f5e37e96dd04dbf584e34b79bf9a6bbdf/uv-0.4.18-py3-none-linux_armv6l.whl", hash = "sha256:1944c0ee567ca7db60705c5d213a75b25601094b026cc17af3e704651c1e3753", size = 12264752 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/98/3623ca28954953a5abdc988eb68d0460e1decf37b245c84db2d1323b17f8/uv-0.4.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5234d47abe339c15c318e8b1bbd136ea61c4574503eda6944a5aaea91b7f6775", size = 12488345 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/2b/ff62b32b4a7cbfb445156b1d8757f29190f854aa702baa045e8645a19144/uv-0.4.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0c4cb31594cb2ed21bd3b603a207e99dfb9610c3db44da9dbbff0f237270f582", size = 11568639 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/7f/49a724b0c8e09fca03c166e7f18ad48c8962c9be543899a27eecc13b8b86/uv-0.4.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:8af0b60adcfa2e87c77a3008d3ed6e0b577c0535468dc58e06f905ccbd27124f", size = 11812252 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/88/0b20af8d76e7b8e6ae19af6d14180a0a9e3c23ef6f3cd38370a2ba663364/uv-0.4.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f043c3c4514c149a00a86c3bf44df43062416d41002114e60df33895e8511c41", size = 12084699 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/fe/afd83b6ed495fe40a4a738cce0de77465af452f8bd58b254a6cf7544a581/uv-0.4.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b59d742b81c7acf75a3aac71d9b24e07407e044bebcf39d3fc3c87094014e20", size = 12793964 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/54/623029d342f68518c25ed8a3863bc43ced0ad39da4dc83b310db3fe0a727/uv-0.4.18-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fcc606da545d9a5ec5c2209e7eb2a4eb76627ad75df5eb5616c0b40789fe3933", size = 13386984 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/50/eace0e9326318bf278491aafc3d63e8675a3d03472d2bc58ef601564cbb4/uv-0.4.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c3ccee0fd8cf0a9d679407e157b76db1a854638a4ba4fa14f4d116b4e39b03", size = 13137886 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/f5/f21bec94affe10e677ecbc0cc1b89d766c950dbc8e23df87451c71848c3f/uv-0.4.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df225a568da01f3d7e126d886c3694c5a4a7d8b85162a4d6e97822716ca0e7c4", size = 17098535 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/89/77ad3d48f2ea11fd4e416b8cc1be18b26f189a4f0bf7918ac6fdb4255fa6/uv-0.4.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b08564c8c7e8b3665ad1d6c8924d4654451f96c956eb5f3b8ec995c77734163d", size = 12909876 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/29/1f451ef9b2138fdc777e24654da24fa60e42435936d29bcba0fb5bae3c44/uv-0.4.18-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:4be600474db6733078503012f2811c4383f490f77366e66b5f686316db52c870", size = 11976385 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/ea/4ac40da05e070f411edb4e99f01846aa8694071ce85f4eb83313f2cce423/uv-0.4.18-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:3e3ade81af961f48517fcd99318192c9c635ef9a38a7ca65026af0c803c71906", size = 12067581 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/49/f6113c4cea8f7ba9e0a70723e8cb3b042c8cb1288f5671594a6b8de491bd/uv-0.4.18-py3-none-musllinux_1_1_i686.whl", hash = "sha256:4ec60141f92c9667548ebad8daf4c13aabdb58b22c21dcd834641e791e55f289", size = 12559831 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/e7/968414391249660bf4375123dd244eef36fc1c1676dcdc719aea1f319bd7/uv-0.4.18-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:6566448278b6849846b6c586fc86748c66aa53ed70f5568e713122543cc86a50", size = 14181171 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/ec/1fa1cffaa837df4bfd545818779dc608d0465be5c0e57b4328b5ed91b97f/uv-0.4.18-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ade18dbbeb05c8cba4f842cc15b20e59467069183f348844750901227df5008d", size = 13042177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/32/fcd60657f45c072fce9f14916b2fcb876b40d8e3ee0ad1f9f212aecd9bfa/uv-0.4.18-py3-none-win32.whl", hash = "sha256:157e4a2c063b270de348862dd31abfe600d5601183fd2a6efe552840ac179626", size = 12184460 },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/bd/35de80c6ac6d28383d5e7c91e8cea54b4aae8ae144c3411a16e9d28643c8/uv-0.4.18-py3-none-win_amd64.whl", hash = "sha256:8250148484e1b0f89ec19467946e86ee303619985c23228b5a2f2d94d15c6d8b", size = 13893818 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user