mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-19 13:58:30 +00:00
Compare commits
1 Commits
feat/add-i
...
sec_docs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f1b26bde2 |
@@ -18,63 +18,60 @@ Flows allow you to create structured, event-driven workflows. They provide a sea
|
|||||||
|
|
||||||
4. **Flexible Control Flow**: Implement conditional logic, loops, and branching within your workflows.
|
4. **Flexible Control Flow**: Implement conditional logic, loops, and branching within your workflows.
|
||||||
|
|
||||||
5. **Input Flexibility**: Flows can accept inputs to initialize or update their state, with different handling for structured and unstructured state management.
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
### Passing Inputs to Flows
|
```python Code
|
||||||
|
|
||||||
Flows can accept inputs to initialize or update their state before execution. The way inputs are handled depends on whether the flow uses structured or unstructured state management.
|
|
||||||
|
|
||||||
#### Structured State Management
|
|
||||||
|
|
||||||
In structured state management, the flow's state is defined using a Pydantic `BaseModel`. Inputs must match the model's schema, and any updates will overwrite the default values.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from crewai.flow.flow import Flow, listen, start
|
from crewai.flow.flow import Flow, listen, start
|
||||||
from pydantic import BaseModel
|
from dotenv import load_dotenv
|
||||||
|
from litellm import completion
|
||||||
|
|
||||||
class ExampleState(BaseModel):
|
|
||||||
counter: int = 0
|
|
||||||
message: str = ""
|
|
||||||
|
|
||||||
class StructuredExampleFlow(Flow[ExampleState]):
|
class ExampleFlow(Flow):
|
||||||
|
model = "gpt-4o-mini"
|
||||||
|
|
||||||
@start()
|
@start()
|
||||||
def first_method(self):
|
def generate_city(self):
|
||||||
# Implementation
|
print("Starting flow")
|
||||||
|
|
||||||
flow = StructuredExampleFlow()
|
response = completion(
|
||||||
flow.kickoff(inputs={"counter": 10})
|
model=self.model,
|
||||||
```
|
messages=[
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "Return the name of a random city in the world.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
In this example, the `counter` is initialized to `10`, while `message` retains its default value.
|
random_city = response["choices"][0]["message"]["content"]
|
||||||
|
print(f"Random City: {random_city}")
|
||||||
|
|
||||||
#### Unstructured State Management
|
return random_city
|
||||||
|
|
||||||
In unstructured state management, the flow's state is a dictionary. You can pass any dictionary to update the state.
|
@listen(generate_city)
|
||||||
|
def generate_fun_fact(self, random_city):
|
||||||
|
response = completion(
|
||||||
|
model=self.model,
|
||||||
|
messages=[
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": f"Tell me a fun fact about {random_city}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
```python
|
fun_fact = response["choices"][0]["message"]["content"]
|
||||||
from crewai.flow.flow import Flow, listen, start
|
return fun_fact
|
||||||
|
|
||||||
class UnstructuredExampleFlow(Flow):
|
|
||||||
@start()
|
|
||||||
def first_method(self):
|
|
||||||
# Implementation
|
|
||||||
|
|
||||||
flow = UnstructuredExampleFlow()
|
|
||||||
flow.kickoff(inputs={"counter": 5, "message": "Initial message"})
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, both `counter` and `message` are updated based on the provided inputs.
|
flow = ExampleFlow()
|
||||||
|
result = flow.kickoff()
|
||||||
|
|
||||||
**Note:** Ensure that inputs for structured state management adhere to the defined schema to avoid validation errors.
|
print(f"Generated fun fact: {result}")
|
||||||
|
|
||||||
### Example Flow
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Existing example code
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
@@ -97,14 +94,14 @@ The `@listen()` decorator can be used in several ways:
|
|||||||
|
|
||||||
1. **Listening to a Method by Name**: You can pass the name of the method you want to listen to as a string. When that method completes, the listener method will be triggered.
|
1. **Listening to a Method by Name**: You can pass the name of the method you want to listen to as a string. When that method completes, the listener method will be triggered.
|
||||||
|
|
||||||
```python
|
```python Code
|
||||||
@listen("generate_city")
|
@listen("generate_city")
|
||||||
def generate_fun_fact(self, random_city):
|
def generate_fun_fact(self, random_city):
|
||||||
# Implementation
|
# Implementation
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Listening to a Method Directly**: You can pass the method itself. When that method completes, the listener method will be triggered.
|
2. **Listening to a Method Directly**: You can pass the method itself. When that method completes, the listener method will be triggered.
|
||||||
```python
|
```python Code
|
||||||
@listen(generate_city)
|
@listen(generate_city)
|
||||||
def generate_fun_fact(self, random_city):
|
def generate_fun_fact(self, random_city):
|
||||||
# Implementation
|
# Implementation
|
||||||
@@ -121,7 +118,7 @@ When you run a Flow, the final output is determined by the last method that comp
|
|||||||
Here's how you can access the final output:
|
Here's how you can access the final output:
|
||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
```python
|
```python Code
|
||||||
from crewai.flow.flow import Flow, listen, start
|
from crewai.flow.flow import Flow, listen, start
|
||||||
|
|
||||||
class OutputExampleFlow(Flow):
|
class OutputExampleFlow(Flow):
|
||||||
@@ -133,17 +130,18 @@ class OutputExampleFlow(Flow):
|
|||||||
def second_method(self, first_output):
|
def second_method(self, first_output):
|
||||||
return f"Second method received: {first_output}"
|
return f"Second method received: {first_output}"
|
||||||
|
|
||||||
|
|
||||||
flow = OutputExampleFlow()
|
flow = OutputExampleFlow()
|
||||||
final_output = flow.kickoff()
|
final_output = flow.kickoff()
|
||||||
|
|
||||||
print("---- Final Output ----")
|
print("---- Final Output ----")
|
||||||
print(final_output)
|
print(final_output)
|
||||||
```
|
````
|
||||||
|
|
||||||
```text
|
``` text Output
|
||||||
---- Final Output ----
|
---- Final Output ----
|
||||||
Second method received: Output from first_method
|
Second method received: Output from first_method
|
||||||
```
|
````
|
||||||
|
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
@@ -158,7 +156,7 @@ Here's an example of how to update and access the state:
|
|||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
```python
|
```python Code
|
||||||
from crewai.flow.flow import Flow, listen, start
|
from crewai.flow.flow import Flow, listen, start
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -186,7 +184,7 @@ print("Final State:")
|
|||||||
print(flow.state)
|
print(flow.state)
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text Output
|
||||||
Final Output: Hello from first_method - updated by second_method
|
Final Output: Hello from first_method - updated by second_method
|
||||||
Final State:
|
Final State:
|
||||||
counter=2 message='Hello from first_method - updated by second_method'
|
counter=2 message='Hello from first_method - updated by second_method'
|
||||||
@@ -210,10 +208,10 @@ allowing developers to choose the approach that best fits their application's ne
|
|||||||
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.
|
This approach offers flexibility, enabling developers to add or modify state attributes on the fly without defining a strict schema.
|
||||||
|
|
||||||
```python
|
```python Code
|
||||||
from crewai.flow.flow import Flow, listen, start
|
from crewai.flow.flow import Flow, listen, start
|
||||||
|
|
||||||
class UnstructuredExampleFlow(Flow):
|
class UntructuredExampleFlow(Flow):
|
||||||
|
|
||||||
@start()
|
@start()
|
||||||
def first_method(self):
|
def first_method(self):
|
||||||
@@ -232,7 +230,8 @@ class UnstructuredExampleFlow(Flow):
|
|||||||
|
|
||||||
print(f"State after third_method: {self.state}")
|
print(f"State after third_method: {self.state}")
|
||||||
|
|
||||||
flow = UnstructuredExampleFlow()
|
|
||||||
|
flow = UntructuredExampleFlow()
|
||||||
flow.kickoff()
|
flow.kickoff()
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -246,14 +245,16 @@ flow.kickoff()
|
|||||||
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.
|
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
|
```python Code
|
||||||
from crewai.flow.flow import Flow, listen, start
|
from crewai.flow.flow import Flow, listen, start
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class ExampleState(BaseModel):
|
class ExampleState(BaseModel):
|
||||||
counter: int = 0
|
counter: int = 0
|
||||||
message: str = ""
|
message: str = ""
|
||||||
|
|
||||||
|
|
||||||
class StructuredExampleFlow(Flow[ExampleState]):
|
class StructuredExampleFlow(Flow[ExampleState]):
|
||||||
|
|
||||||
@start()
|
@start()
|
||||||
@@ -272,6 +273,7 @@ class StructuredExampleFlow(Flow[ExampleState]):
|
|||||||
|
|
||||||
print(f"State after third_method: {self.state}")
|
print(f"State after third_method: {self.state}")
|
||||||
|
|
||||||
|
|
||||||
flow = StructuredExampleFlow()
|
flow = StructuredExampleFlow()
|
||||||
flow.kickoff()
|
flow.kickoff()
|
||||||
```
|
```
|
||||||
@@ -305,7 +307,7 @@ The `or_` function in Flows allows you to listen to multiple methods and trigger
|
|||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
```python
|
```python Code
|
||||||
from crewai.flow.flow import Flow, listen, or_, start
|
from crewai.flow.flow import Flow, listen, or_, start
|
||||||
|
|
||||||
class OrExampleFlow(Flow):
|
class OrExampleFlow(Flow):
|
||||||
@@ -322,11 +324,13 @@ class OrExampleFlow(Flow):
|
|||||||
def logger(self, result):
|
def logger(self, result):
|
||||||
print(f"Logger: {result}")
|
print(f"Logger: {result}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
flow = OrExampleFlow()
|
flow = OrExampleFlow()
|
||||||
flow.kickoff()
|
flow.kickoff()
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text Output
|
||||||
Logger: Hello from the start method
|
Logger: Hello from the start method
|
||||||
Logger: Hello from the second method
|
Logger: Hello from the second method
|
||||||
```
|
```
|
||||||
@@ -342,7 +346,7 @@ The `and_` function in Flows allows you to listen to multiple methods and trigge
|
|||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
```python
|
```python Code
|
||||||
from crewai.flow.flow import Flow, and_, listen, start
|
from crewai.flow.flow import Flow, and_, listen, start
|
||||||
|
|
||||||
class AndExampleFlow(Flow):
|
class AndExampleFlow(Flow):
|
||||||
@@ -364,7 +368,7 @@ flow = AndExampleFlow()
|
|||||||
flow.kickoff()
|
flow.kickoff()
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text Output
|
||||||
---- Logger ----
|
---- Logger ----
|
||||||
{'greeting': 'Hello from the start method', 'joke': 'What do computers eat? Microchips.'}
|
{'greeting': 'Hello from the start method', 'joke': 'What do computers eat? Microchips.'}
|
||||||
```
|
```
|
||||||
@@ -381,7 +385,7 @@ You can specify different routes based on the output of the method, allowing you
|
|||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
```python
|
```python Code
|
||||||
import random
|
import random
|
||||||
from crewai.flow.flow import Flow, listen, router, start
|
from crewai.flow.flow import Flow, listen, router, start
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -412,11 +416,12 @@ class RouterFlow(Flow[ExampleState]):
|
|||||||
def fourth_method(self):
|
def fourth_method(self):
|
||||||
print("Fourth method running")
|
print("Fourth method running")
|
||||||
|
|
||||||
|
|
||||||
flow = RouterFlow()
|
flow = RouterFlow()
|
||||||
flow.kickoff()
|
flow.kickoff()
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text Output
|
||||||
Starting the structured flow
|
Starting the structured flow
|
||||||
Third method running
|
Third method running
|
||||||
Fourth method running
|
Fourth method running
|
||||||
@@ -479,7 +484,7 @@ The `main.py` file is where you create your flow and connect the crews together.
|
|||||||
|
|
||||||
Here's an example of how you can connect the `poem_crew` in the `main.py` file:
|
Here's an example of how you can connect the `poem_crew` in the `main.py` file:
|
||||||
|
|
||||||
```python
|
```python Code
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
@@ -572,20 +577,18 @@ This command will create a new directory for your crew within the `crews` folder
|
|||||||
|
|
||||||
After adding a new crew, your folder structure will look like this:
|
After adding a new crew, your folder structure will look like this:
|
||||||
|
|
||||||
| Directory/File | Description |
|
name_of_flow/
|
||||||
| :--------------------- | :----------------------------------------------------------------- |
|
├── crews/
|
||||||
| `name_of_flow/` | Root directory for the flow. |
|
│ ├── poem_crew/
|
||||||
| ├── `crews/` | Contains directories for specific crews. |
|
│ │ ├── config/
|
||||||
| │ ├── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts. |
|
│ │ │ ├── agents.yaml
|
||||||
| │ │ ├── `config/` | Configuration files directory for the "poem_crew". |
|
│ │ │ └── tasks.yaml
|
||||||
| │ │ │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". |
|
│ │ └── poem_crew.py
|
||||||
| │ │ │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". |
|
│ └── name_of_crew/
|
||||||
| │ │ └── `poem_crew.py` | Script for "poem_crew" functionality. |
|
│ ├── config/
|
||||||
| └── `name_of_crew/` | Directory for the new crew. |
|
│ │ ├── agents.yaml
|
||||||
| ├── `config/` | Configuration files directory for the new crew. |
|
│ │ └── tasks.yaml
|
||||||
| │ ├── `agents.yaml` | YAML file defining the agents for the new crew. |
|
│ └── name_of_crew.py
|
||||||
| │ └── `tasks.yaml` | YAML file defining the tasks for the new crew. |
|
|
||||||
| └── `name_of_crew.py` | Script for the new crew functionality. |
|
|
||||||
|
|
||||||
You can then customize the `agents.yaml` and `tasks.yaml` files to define the agents and tasks for your new crew. The `name_of_crew.py` file will contain the crew's logic, which you can modify to suit your needs.
|
You can then customize the `agents.yaml` and `tasks.yaml` files to define the agents and tasks for your new crew. The `name_of_crew.py` file will contain the crew's logic, which you can modify to suit your needs.
|
||||||
|
|
||||||
@@ -607,7 +610,7 @@ CrewAI provides two convenient methods to generate plots of your flows:
|
|||||||
|
|
||||||
If you are working directly with a flow instance, you can generate a plot by calling the `plot()` method on your flow object. This method will create an HTML file containing the interactive plot of your flow.
|
If you are working directly with a flow instance, you can generate a plot by calling the `plot()` method on your flow object. This method will create an HTML file containing the interactive plot of your flow.
|
||||||
|
|
||||||
```python
|
```python Code
|
||||||
# Assuming you have a flow instance
|
# Assuming you have a flow instance
|
||||||
flow.plot("my_flow_plot")
|
flow.plot("my_flow_plot")
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -25,55 +25,52 @@ By default, CrewAI uses the `gpt-4o-mini` model. It uses environment variables i
|
|||||||
- `OPENAI_API_BASE`
|
- `OPENAI_API_BASE`
|
||||||
- `OPENAI_API_KEY`
|
- `OPENAI_API_KEY`
|
||||||
|
|
||||||
### 2. Custom LLM Objects
|
### 2. String Identifier
|
||||||
|
|
||||||
|
```python Code
|
||||||
|
agent = Agent(llm="gpt-4o", ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. LLM Instance
|
||||||
|
|
||||||
|
List of [more providers](https://docs.litellm.ai/docs/providers).
|
||||||
|
|
||||||
|
```python Code
|
||||||
|
from crewai import LLM
|
||||||
|
|
||||||
|
llm = LLM(model="gpt-4", temperature=0.7)
|
||||||
|
agent = Agent(llm=llm, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Custom LLM Objects
|
||||||
|
|
||||||
Pass a custom LLM implementation or object from another library.
|
Pass a custom LLM implementation or object from another library.
|
||||||
|
|
||||||
See below for examples.
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="String Identifier">
|
|
||||||
```python Code
|
|
||||||
agent = Agent(llm="gpt-4o", ...)
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab title="LLM Instance">
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(model="gpt-4", temperature=0.7)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Connecting to OpenAI-Compatible LLMs
|
## Connecting to OpenAI-Compatible LLMs
|
||||||
|
|
||||||
You can connect to OpenAI-compatible LLMs using either environment variables or by setting specific attributes on the LLM class:
|
You can connect to OpenAI-compatible LLMs using either environment variables or by setting specific attributes on the LLM class:
|
||||||
|
|
||||||
<Tabs>
|
1. Using environment variables:
|
||||||
<Tab title="Using Environment Variables">
|
|
||||||
```python Code
|
|
||||||
import os
|
|
||||||
|
|
||||||
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
```python Code
|
||||||
os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1"
|
import os
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Using LLM Class Attributes">
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
||||||
model="custom-model-name",
|
os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1"
|
||||||
api_key="your-api-key",
|
```
|
||||||
base_url="https://api.your-provider.com/v1"
|
|
||||||
)
|
2. Using LLM class attributes:
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
```python Code
|
||||||
</Tab>
|
from crewai import LLM
|
||||||
</Tabs>
|
|
||||||
|
llm = LLM(
|
||||||
|
model="custom-model-name",
|
||||||
|
api_key="your-api-key",
|
||||||
|
base_url="https://api.your-provider.com/v1"
|
||||||
|
)
|
||||||
|
agent = Agent(llm=llm, ...)
|
||||||
|
```
|
||||||
|
|
||||||
## LLM Configuration Options
|
## LLM Configuration Options
|
||||||
|
|
||||||
@@ -100,157 +97,55 @@ When configuring an LLM for your agent, you have access to a wide range of param
|
|||||||
| **api_key** | `str` | Your API key for authentication. |
|
| **api_key** | `str` | Your API key for authentication. |
|
||||||
|
|
||||||
|
|
||||||
These are examples of how to configure LLMs for your agent.
|
## OpenAI Example Configuration
|
||||||
|
|
||||||
<AccordionGroup>
|
```python Code
|
||||||
<Accordion title="OpenAI">
|
from crewai import LLM
|
||||||
|
|
||||||
```python Code
|
llm = LLM(
|
||||||
from crewai import LLM
|
model="gpt-4",
|
||||||
|
temperature=0.8,
|
||||||
|
max_tokens=150,
|
||||||
|
top_p=0.9,
|
||||||
|
frequency_penalty=0.1,
|
||||||
|
presence_penalty=0.1,
|
||||||
|
stop=["END"],
|
||||||
|
seed=42,
|
||||||
|
base_url="https://api.openai.com/v1",
|
||||||
|
api_key="your-api-key-here"
|
||||||
|
)
|
||||||
|
agent = Agent(llm=llm, ...)
|
||||||
|
```
|
||||||
|
|
||||||
llm = LLM(
|
## Cerebras Example Configuration
|
||||||
model="gpt-4",
|
|
||||||
temperature=0.8,
|
|
||||||
max_tokens=150,
|
|
||||||
top_p=0.9,
|
|
||||||
frequency_penalty=0.1,
|
|
||||||
presence_penalty=0.1,
|
|
||||||
stop=["END"],
|
|
||||||
seed=42,
|
|
||||||
base_url="https://api.openai.com/v1",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Cerebras">
|
```python Code
|
||||||
|
from crewai import LLM
|
||||||
|
|
||||||
```python Code
|
llm = LLM(
|
||||||
from crewai import LLM
|
model="cerebras/llama-3.1-70b",
|
||||||
|
base_url="https://api.cerebras.ai/v1",
|
||||||
|
api_key="your-api-key-here"
|
||||||
|
)
|
||||||
|
agent = Agent(llm=llm, ...)
|
||||||
|
```
|
||||||
|
|
||||||
llm = LLM(
|
## Using Ollama (Local LLMs)
|
||||||
model="cerebras/llama-3.1-70b",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Ollama (Local LLMs)">
|
|
||||||
|
|
||||||
CrewAI supports using Ollama for running open-source models locally:
|
CrewAI supports using Ollama for running open-source models locally:
|
||||||
|
|
||||||
1. Install Ollama: [ollama.ai](https://ollama.ai/)
|
1. Install Ollama: [ollama.ai](https://ollama.ai/)
|
||||||
2. Run a model: `ollama run llama2`
|
2. Run a model: `ollama run llama2`
|
||||||
3. Configure agent:
|
3. Configure agent:
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai import LLM
|
from crewai import LLM
|
||||||
|
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
llm=LLM(
|
llm=LLM(model="ollama/llama3.1", base_url="http://localhost:11434"),
|
||||||
model="ollama/llama3.1",
|
...
|
||||||
base_url="http://localhost:11434"
|
)
|
||||||
),
|
```
|
||||||
...
|
|
||||||
)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Groq">
|
|
||||||
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
|
||||||
model="groq/llama3-8b-8192",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Anthropic">
|
|
||||||
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
|
||||||
model="anthropic/claude-3-5-sonnet-20241022",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Fireworks AI">
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
|
||||||
model="fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Gemini">
|
|
||||||
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
|
||||||
model="gemini/gemini-1.5-pro-002",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Perplexity AI (pplx-api)">
|
|
||||||
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
|
||||||
model="perplexity/mistral-7b-instruct",
|
|
||||||
base_url="https://api.perplexity.ai/v1",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="IBM watsonx.ai">
|
|
||||||
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
|
||||||
model="watsonx/ibm/granite-13b-chat-v2",
|
|
||||||
base_url="https://api.watsonx.ai/v1",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Hugging Face">
|
|
||||||
|
|
||||||
```python Code
|
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
|
||||||
model="huggingface/meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
||||||
api_key="your-api-key-here",
|
|
||||||
base_url="your_api_endpoint"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
</AccordionGroup>
|
|
||||||
|
|
||||||
## Changing the Base API URL
|
## Changing the Base API URL
|
||||||
|
|
||||||
|
|||||||
@@ -254,31 +254,6 @@ my_crew = Crew(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Watson embeddings
|
|
||||||
|
|
||||||
```python Code
|
|
||||||
from crewai import Crew, Agent, Task, Process
|
|
||||||
|
|
||||||
# Note: Ensure you have installed and imported `ibm_watsonx_ai` for Watson embeddings to work.
|
|
||||||
|
|
||||||
my_crew = Crew(
|
|
||||||
agents=[...],
|
|
||||||
tasks=[...],
|
|
||||||
process=Process.sequential,
|
|
||||||
memory=True,
|
|
||||||
verbose=True,
|
|
||||||
embedder={
|
|
||||||
"provider": "watson",
|
|
||||||
"config": {
|
|
||||||
"model": "<model_name>",
|
|
||||||
"api_url": "<api_url>",
|
|
||||||
"api_key": "<YOUR_API_KEY>",
|
|
||||||
"project_id": "<YOUR_PROJECT_ID>",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Resetting Memory
|
### Resetting Memory
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ icon: screwdriver-wrench
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
CrewAI tools empower agents with capabilities ranging from web searching and data analysis to collaboration and delegating tasks among coworkers.
|
||||||
CrewAI tools empower agents with capabilities ranging from web searching and data analysis to collaboration and delegating tasks among coworkers.
|
|
||||||
This documentation outlines how to create, integrate, and leverage these tools within the CrewAI framework, including a new focus on collaboration tools.
|
This documentation outlines how to create, integrate, and leverage these tools within the CrewAI framework, including a new focus on collaboration tools.
|
||||||
|
|
||||||
## What is a Tool?
|
## What is a Tool?
|
||||||
|
|
||||||
A tool in CrewAI is a skill or function that agents can utilize to perform various actions.
|
A tool in CrewAI is a skill or function that agents can utilize to perform various actions.
|
||||||
This includes tools from the [CrewAI Toolkit](https://github.com/joaomdmoura/crewai-tools) and [LangChain Tools](https://python.langchain.com/docs/integrations/tools),
|
This includes tools from the [CrewAI Toolkit](https://github.com/joaomdmoura/crewai-tools) and [LangChain Tools](https://python.langchain.com/docs/integrations/tools),
|
||||||
enabling everything from simple searches to complex interactions and effective teamwork among agents.
|
enabling everything from simple searches to complex interactions and effective teamwork among agents.
|
||||||
|
|
||||||
## Key Characteristics of Tools
|
## Key Characteristics of Tools
|
||||||
@@ -104,53 +103,57 @@ crew.kickoff()
|
|||||||
|
|
||||||
Here is a list of the available tools and their descriptions:
|
Here is a list of the available tools and their descriptions:
|
||||||
|
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
| :------------------------------- | :--------------------------------------------------------------------------------------------- |
|
| :-------------------------- | :-------------------------------------------------------------------------------------------- |
|
||||||
| **BrowserbaseLoadTool** | A tool for interacting with and extracting data from web browsers. |
|
| **BrowserbaseLoadTool** | A tool for interacting with and extracting data from web browsers. |
|
||||||
| **CodeDocsSearchTool** | A RAG tool optimized for searching through code documentation and related technical documents. |
|
| **CodeDocsSearchTool** | A RAG tool optimized for searching through code documentation and related technical documents. |
|
||||||
| **CodeInterpreterTool** | A tool for interpreting python code. |
|
| **CodeInterpreterTool** | A tool for interpreting python code. |
|
||||||
| **ComposioTool** | Enables use of Composio tools. |
|
| **ComposioTool** | Enables use of Composio tools. |
|
||||||
| **CSVSearchTool** | A RAG tool designed for searching within CSV files, tailored to handle structured data. |
|
| **CSVSearchTool** | A RAG tool designed for searching within CSV files, tailored to handle structured data. |
|
||||||
| **DALL-E Tool** | A tool for generating images using the DALL-E API. |
|
| **DALL-E Tool** | A tool for generating images using the DALL-E API. |
|
||||||
| **DirectorySearchTool** | A RAG tool for searching within directories, useful for navigating through file systems. |
|
| **DirectorySearchTool** | A RAG tool for searching within directories, useful for navigating through file systems. |
|
||||||
| **DOCXSearchTool** | A RAG tool aimed at searching within DOCX documents, ideal for processing Word files. |
|
| **DOCXSearchTool** | A RAG tool aimed at searching within DOCX documents, ideal for processing Word files. |
|
||||||
| **DirectoryReadTool** | Facilitates reading and processing of directory structures and their contents. |
|
| **DirectoryReadTool** | Facilitates reading and processing of directory structures and their contents. |
|
||||||
| **EXASearchTool** | A tool designed for performing exhaustive searches across various data sources. |
|
| **EXASearchTool** | A tool designed for performing exhaustive searches across various data sources. |
|
||||||
| **FileReadTool** | Enables reading and extracting data from files, supporting various file formats. |
|
| **FileReadTool** | Enables reading and extracting data from files, supporting various file formats. |
|
||||||
| **FirecrawlSearchTool** | A tool to search webpages using Firecrawl and return the results. |
|
| **FirecrawlSearchTool** | A tool to search webpages using Firecrawl and return the results. |
|
||||||
| **FirecrawlCrawlWebsiteTool** | A tool for crawling webpages using Firecrawl. |
|
| **FirecrawlCrawlWebsiteTool** | A tool for crawling webpages using Firecrawl. |
|
||||||
| **FirecrawlScrapeWebsiteTool** | A tool for scraping webpages URL using Firecrawl and returning its contents. |
|
| **FirecrawlScrapeWebsiteTool** | A tool for scraping webpages URL using Firecrawl and returning its contents. |
|
||||||
| **GithubSearchTool** | A RAG tool for searching within GitHub repositories, useful for code and documentation search. |
|
| **GithubSearchTool** | A RAG tool for searching within GitHub repositories, useful for code and documentation search.|
|
||||||
| **SerperDevTool** | A specialized tool for development purposes, with specific functionalities under development. |
|
| **SerperDevTool** | A specialized tool for development purposes, with specific functionalities under development. |
|
||||||
| **TXTSearchTool** | A RAG tool focused on searching within text (.txt) files, suitable for unstructured data. |
|
| **TXTSearchTool** | A RAG tool focused on searching within text (.txt) files, suitable for unstructured data. |
|
||||||
| **JSONSearchTool** | A RAG tool designed for searching within JSON files, catering to structured data handling. |
|
| **JSONSearchTool** | A RAG tool designed for searching within JSON files, catering to structured data handling. |
|
||||||
| **LlamaIndexTool** | Enables the use of LlamaIndex tools. |
|
| **LlamaIndexTool** | Enables the use of LlamaIndex tools. |
|
||||||
| **MDXSearchTool** | A RAG tool tailored for searching within Markdown (MDX) files, useful for documentation. |
|
| **MDXSearchTool** | A RAG tool tailored for searching within Markdown (MDX) files, useful for documentation. |
|
||||||
| **PDFSearchTool** | A RAG tool aimed at searching within PDF documents, ideal for processing scanned documents. |
|
| **PDFSearchTool** | A RAG tool aimed at searching within PDF documents, ideal for processing scanned documents. |
|
||||||
| **PGSearchTool** | A RAG tool optimized for searching within PostgreSQL databases, suitable for database queries. |
|
| **PGSearchTool** | A RAG tool optimized for searching within PostgreSQL databases, suitable for database queries. |
|
||||||
| **Vision Tool** | A tool for generating images using the DALL-E API. |
|
| **Vision Tool** | A tool for generating images using the DALL-E API. |
|
||||||
| **RagTool** | A general-purpose RAG tool capable of handling various data sources and types. |
|
| **RagTool** | A general-purpose RAG tool capable of handling various data sources and types. |
|
||||||
| **ScrapeElementFromWebsiteTool** | Enables scraping specific elements from websites, useful for targeted data extraction. |
|
| **ScrapeElementFromWebsiteTool** | Enables scraping specific elements from websites, useful for targeted data extraction. |
|
||||||
| **ScrapeWebsiteTool** | Facilitates scraping entire websites, ideal for comprehensive data collection. |
|
| **ScrapeWebsiteTool** | Facilitates scraping entire websites, ideal for comprehensive data collection. |
|
||||||
| **WebsiteSearchTool** | A RAG tool for searching website content, optimized for web data extraction. |
|
| **WebsiteSearchTool** | A RAG tool for searching website content, optimized for web data extraction. |
|
||||||
| **XMLSearchTool** | A RAG tool designed for searching within XML files, suitable for structured data formats. |
|
| **XMLSearchTool** | A RAG tool designed for searching within XML files, suitable for structured data formats. |
|
||||||
| **YoutubeChannelSearchTool** | A RAG tool for searching within YouTube channels, useful for video content analysis. |
|
| **YoutubeChannelSearchTool**| A RAG tool for searching within YouTube channels, useful for video content analysis. |
|
||||||
| **YoutubeVideoSearchTool** | A RAG tool aimed at searching within YouTube videos, ideal for video data extraction. |
|
| **YoutubeVideoSearchTool** | A RAG tool aimed at searching within YouTube videos, ideal for video data extraction. |
|
||||||
|
|
||||||
## Creating your own Tools
|
## Creating your own Tools
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Developers can craft `custom tools` tailored for their agent’s needs or
|
Developers can craft `custom tools` tailored for their agent’s needs or utilize pre-built options.
|
||||||
utilize pre-built options.
|
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
There are two main ways for one to create a CrewAI tool:
|
To create your own CrewAI tools you will need to install our extra tools package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install 'crewai[tools]'
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you do that there are two main ways for one to create a CrewAI tool:
|
||||||
|
|
||||||
### Subclassing `BaseTool`
|
### Subclassing `BaseTool`
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai.tools import BaseTool
|
from crewai_tools import BaseTool
|
||||||
|
|
||||||
|
|
||||||
class MyCustomTool(BaseTool):
|
class MyCustomTool(BaseTool):
|
||||||
name: str = "Name of my tool"
|
name: str = "Name of my tool"
|
||||||
@@ -164,7 +167,7 @@ class MyCustomTool(BaseTool):
|
|||||||
### Utilizing the `tool` Decorator
|
### Utilizing the `tool` Decorator
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
@tool("Name of my tool")
|
@tool("Name of my tool")
|
||||||
def my_tool(question: str) -> str:
|
def my_tool(question: str) -> str:
|
||||||
"""Clear description for what this tool is useful for, your agent will need this information to use it."""
|
"""Clear description for what this tool is useful for, your agent will need this information to use it."""
|
||||||
@@ -175,13 +178,11 @@ def my_tool(question: str) -> str:
|
|||||||
### Custom Caching Mechanism
|
### Custom Caching Mechanism
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Tools can optionally implement a `cache_function` to fine-tune caching
|
Tools can optionally implement a `cache_function` to fine-tune caching behavior. This function determines when to cache results based on specific conditions, offering granular control over caching logic.
|
||||||
behavior. This function determines when to cache results based on specific
|
|
||||||
conditions, offering granular control over caching logic.
|
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def multiplication_tool(first_number: int, second_number: int) -> str:
|
def multiplication_tool(first_number: int, second_number: int) -> str:
|
||||||
@@ -207,6 +208,6 @@ writer1 = Agent(
|
|||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
Tools are pivotal in extending the capabilities of CrewAI agents, enabling them to undertake a broad spectrum of tasks and collaborate effectively.
|
Tools are pivotal in extending the capabilities of CrewAI agents, enabling them to undertake a broad spectrum of tasks and collaborate effectively.
|
||||||
When building solutions with CrewAI, leverage both custom and existing tools to empower your agents and enhance the AI ecosystem. Consider utilizing error handling,
|
When building solutions with CrewAI, leverage both custom and existing tools to empower your agents and enhance the AI ecosystem. Consider utilizing error handling,
|
||||||
caching mechanisms, and the flexibility of tool arguments to optimize your agents' performance and capabilities.
|
caching mechanisms, and the flexibility of tool arguments to optimize your agents' performance and capabilities.
|
||||||
@@ -6,17 +6,25 @@ icon: hammer
|
|||||||
|
|
||||||
## Creating and Utilizing Tools in CrewAI
|
## Creating and Utilizing Tools in CrewAI
|
||||||
|
|
||||||
This guide provides detailed instructions on creating custom tools for the CrewAI framework and how to efficiently manage and utilize these tools,
|
This guide provides detailed instructions on creating custom tools for the CrewAI framework and how to efficiently manage and utilize these tools,
|
||||||
incorporating the latest functionalities such as tool delegation, error handling, and dynamic tool calling. It also highlights the importance of collaboration tools,
|
incorporating the latest functionalities such as tool delegation, error handling, and dynamic tool calling. It also highlights the importance of collaboration tools,
|
||||||
enabling agents to perform a wide range of actions.
|
enabling agents to perform a wide range of actions.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Before creating your own tools, ensure you have the crewAI extra tools package installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install 'crewai[tools]'
|
||||||
|
```
|
||||||
|
|
||||||
### Subclassing `BaseTool`
|
### Subclassing `BaseTool`
|
||||||
|
|
||||||
To create a personalized tool, inherit from `BaseTool` and define the necessary attributes, including the `args_schema` for input validation, and the `_run` method.
|
To create a personalized tool, inherit from `BaseTool` and define the necessary attributes, including the `args_schema` for input validation, and the `_run` method.
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from crewai.tools import BaseTool
|
from crewai_tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
class MyToolInput(BaseModel):
|
class MyToolInput(BaseModel):
|
||||||
@@ -39,7 +47,7 @@ Alternatively, you can use the tool decorator `@tool`. This approach allows you
|
|||||||
offering a concise and efficient way to create specialized tools tailored to your needs.
|
offering a concise and efficient way to create specialized tools tailored to your needs.
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool("Tool Name")
|
@tool("Tool Name")
|
||||||
def my_simple_tool(question: str) -> str:
|
def my_simple_tool(question: str) -> str:
|
||||||
@@ -65,5 +73,5 @@ def my_cache_strategy(arguments: dict, result: str) -> bool:
|
|||||||
cached_tool.cache_function = my_cache_strategy
|
cached_tool.cache_function = my_cache_strategy
|
||||||
```
|
```
|
||||||
|
|
||||||
By adhering to these guidelines and incorporating new functionalities and collaboration tools into your tool creation and management processes,
|
By adhering to these guidelines and incorporating new functionalities and collaboration tools into your tool creation and management processes,
|
||||||
you can leverage the full capabilities of the CrewAI framework, enhancing both the development experience and the efficiency of your AI agents.
|
you can leverage the full capabilities of the CrewAI framework, enhancing both the development experience and the efficiency of your AI agents.
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ from crewai.agents.agent_builder.base_agent import BaseAgent
|
|||||||
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||||
from crewai.llm import LLM
|
from crewai.llm import LLM
|
||||||
from crewai.memory.contextual.contextual_memory import ContextualMemory
|
from crewai.memory.contextual.contextual_memory import ContextualMemory
|
||||||
from crewai.tools.agent_tools.agent_tools import AgentTools
|
from crewai.tools.agent_tools import AgentTools
|
||||||
from crewai.tools import BaseTool
|
|
||||||
from crewai.utilities import Converter, Prompts
|
from crewai.utilities import Converter, Prompts
|
||||||
from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE
|
from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE
|
||||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||||
@@ -193,7 +192,7 @@ class Agent(BaseAgent):
|
|||||||
self,
|
self,
|
||||||
task: Any,
|
task: Any,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[BaseTool]] = None,
|
tools: Optional[List[Any]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Execute a task with the agent.
|
"""Execute a task with the agent.
|
||||||
|
|
||||||
@@ -260,9 +259,7 @@ class Agent(BaseAgent):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def create_agent_executor(
|
def create_agent_executor(self, tools=None, task=None) -> None:
|
||||||
self, tools: Optional[List[BaseTool]] = None, task=None
|
|
||||||
) -> None:
|
|
||||||
"""Create an agent executor for the agent.
|
"""Create an agent executor for the agent.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -335,7 +332,7 @@ class Agent(BaseAgent):
|
|||||||
tools_list = []
|
tools_list = []
|
||||||
try:
|
try:
|
||||||
# tentatively try to import from crewai_tools import BaseTool as CrewAITool
|
# tentatively try to import from crewai_tools import BaseTool as CrewAITool
|
||||||
from crewai.tools import BaseTool as CrewAITool
|
from crewai_tools import BaseTool as CrewAITool
|
||||||
|
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
if isinstance(tool, CrewAITool):
|
if isinstance(tool, CrewAITool):
|
||||||
@@ -394,7 +391,7 @@ class Agent(BaseAgent):
|
|||||||
|
|
||||||
return description
|
return description
|
||||||
|
|
||||||
def _render_text_description_and_args(self, tools: List[BaseTool]) -> str:
|
def _render_text_description_and_args(self, tools: List[Any]) -> str:
|
||||||
"""Render the tool name, description, and args in plain text.
|
"""Render the tool name, description, and args in plain text.
|
||||||
|
|
||||||
Output will be in the format of:
|
Output will be in the format of:
|
||||||
@@ -407,7 +404,17 @@ class Agent(BaseAgent):
|
|||||||
"""
|
"""
|
||||||
tool_strings = []
|
tool_strings = []
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
tool_strings.append(tool.description)
|
args_schema = {
|
||||||
|
name: {
|
||||||
|
"description": field.description,
|
||||||
|
"type": field.annotation.__name__,
|
||||||
|
}
|
||||||
|
for name, field in tool.args_schema.model_fields.items()
|
||||||
|
}
|
||||||
|
description = (
|
||||||
|
f"Tool Name: {tool.name}\nTool Description: {tool.description}"
|
||||||
|
)
|
||||||
|
tool_strings.append(f"{description}\nTool Arguments: {args_schema}")
|
||||||
|
|
||||||
return "\n".join(tool_strings)
|
return "\n".join(tool_strings)
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from pydantic_core import PydanticCustomError
|
|||||||
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
||||||
from crewai.agents.cache.cache_handler import CacheHandler
|
from crewai.agents.cache.cache_handler import CacheHandler
|
||||||
from crewai.agents.tools_handler import ToolsHandler
|
from crewai.agents.tools_handler import ToolsHandler
|
||||||
from crewai.tools import BaseTool
|
|
||||||
from crewai.utilities import I18N, Logger, RPMController
|
from crewai.utilities import I18N, Logger, RPMController
|
||||||
from crewai.utilities.config import process_config
|
from crewai.utilities.config import process_config
|
||||||
|
|
||||||
@@ -50,11 +49,11 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
execute_task(task: Any, context: Optional[str] = None, tools: Optional[List[BaseTool]] = None) -> str:
|
execute_task(task: Any, context: Optional[str] = None, tools: Optional[List[Any]] = None) -> str:
|
||||||
Abstract method to execute a task.
|
Abstract method to execute a task.
|
||||||
create_agent_executor(tools=None) -> None:
|
create_agent_executor(tools=None) -> None:
|
||||||
Abstract method to create an agent executor.
|
Abstract method to create an agent executor.
|
||||||
_parse_tools(tools: List[BaseTool]) -> List[Any]:
|
_parse_tools(tools: List[Any]) -> List[Any]:
|
||||||
Abstract method to parse tools.
|
Abstract method to parse tools.
|
||||||
get_delegation_tools(agents: List["BaseAgent"]):
|
get_delegation_tools(agents: List["BaseAgent"]):
|
||||||
Abstract method to set the agents task tools for handling delegation and question asking to other agents in crew.
|
Abstract method to set the agents task tools for handling delegation and question asking to other agents in crew.
|
||||||
@@ -106,7 +105,7 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
default=False,
|
default=False,
|
||||||
description="Enable agent to delegate and ask questions among each other.",
|
description="Enable agent to delegate and ask questions among each other.",
|
||||||
)
|
)
|
||||||
tools: Optional[List[BaseTool]] = Field(
|
tools: Optional[List[Any]] = Field(
|
||||||
default_factory=list, description="Tools at agents' disposal"
|
default_factory=list, description="Tools at agents' disposal"
|
||||||
)
|
)
|
||||||
max_iter: Optional[int] = Field(
|
max_iter: Optional[int] = Field(
|
||||||
@@ -189,7 +188,7 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
self,
|
self,
|
||||||
task: Any,
|
task: Any,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[BaseTool]] = None,
|
tools: Optional[List[Any]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -198,11 +197,11 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _parse_tools(self, tools: List[BaseTool]) -> List[BaseTool]:
|
def _parse_tools(self, tools: List[Any]) -> List[Any]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_delegation_tools(self, agents: List["BaseAgent"]) -> List[BaseTool]:
|
def get_delegation_tools(self, agents: List["BaseAgent"]) -> List[Any]:
|
||||||
"""Set the task tools that init BaseAgenTools class."""
|
"""Set the task tools that init BaseAgenTools class."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
from typing import Optional, Union
|
from abc import ABC, abstractmethod
|
||||||
from pydantic import Field
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from crewai.tools.base_tool import BaseTool
|
|
||||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||||
from crewai.task import Task
|
from crewai.task import Task
|
||||||
from crewai.utilities import I18N
|
from crewai.utilities import I18N
|
||||||
|
|
||||||
|
|
||||||
class BaseAgentTool(BaseTool):
|
class BaseAgentTools(BaseModel, ABC):
|
||||||
"""Base class for agent-related tools"""
|
"""Default tools around agent delegation"""
|
||||||
|
|
||||||
agents: list[BaseAgent] = Field(description="List of available agents")
|
agents: List[BaseAgent] = Field(description="List of agents in this crew.")
|
||||||
i18n: I18N = Field(
|
i18n: I18N = Field(default=I18N(), description="Internationalization settings.")
|
||||||
default_factory=I18N, description="Internationalization settings"
|
|
||||||
)
|
@abstractmethod
|
||||||
|
def tools(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def _get_coworker(self, coworker: Optional[str], **kwargs) -> Optional[str]:
|
def _get_coworker(self, coworker: Optional[str], **kwargs) -> Optional[str]:
|
||||||
coworker = coworker or kwargs.get("co_worker") or kwargs.get("coworker")
|
coworker = coworker or kwargs.get("co_worker") or kwargs.get("coworker")
|
||||||
@@ -21,11 +24,27 @@ class BaseAgentTool(BaseTool):
|
|||||||
is_list = coworker.startswith("[") and coworker.endswith("]")
|
is_list = coworker.startswith("[") and coworker.endswith("]")
|
||||||
if is_list:
|
if is_list:
|
||||||
coworker = coworker[1:-1].split(",")[0]
|
coworker = coworker[1:-1].split(",")[0]
|
||||||
|
|
||||||
return coworker
|
return coworker
|
||||||
|
|
||||||
|
def delegate_work(
|
||||||
|
self, task: str, context: str, coworker: Optional[str] = None, **kwargs
|
||||||
|
):
|
||||||
|
"""Useful to delegate a specific task to a coworker passing all necessary context and names."""
|
||||||
|
coworker = self._get_coworker(coworker, **kwargs)
|
||||||
|
return self._execute(coworker, task, context)
|
||||||
|
|
||||||
|
def ask_question(
|
||||||
|
self, question: str, context: str, coworker: Optional[str] = None, **kwargs
|
||||||
|
):
|
||||||
|
"""Useful to ask a question, opinion or take from a coworker passing all necessary context and names."""
|
||||||
|
coworker = self._get_coworker(coworker, **kwargs)
|
||||||
|
return self._execute(coworker, question, context)
|
||||||
|
|
||||||
def _execute(
|
def _execute(
|
||||||
self, agent_name: Union[str, None], task: str, context: Union[str, None]
|
self, agent_name: Union[str, None], task: str, context: Union[str, None]
|
||||||
) -> str:
|
):
|
||||||
|
"""Execute the command."""
|
||||||
try:
|
try:
|
||||||
if agent_name is None:
|
if agent_name is None:
|
||||||
agent_name = ""
|
agent_name = ""
|
||||||
@@ -38,6 +57,7 @@ class BaseAgentTool(BaseTool):
|
|||||||
# when it should look like this:
|
# when it should look like this:
|
||||||
# {"task": "....", "coworker": "...."}
|
# {"task": "....", "coworker": "...."}
|
||||||
agent_name = agent_name.casefold().replace('"', "").replace("\n", "")
|
agent_name = agent_name.casefold().replace('"', "").replace("\n", "")
|
||||||
|
|
||||||
agent = [ # type: ignore # Incompatible types in assignment (expression has type "list[BaseAgent]", variable has type "str | None")
|
agent = [ # type: ignore # Incompatible types in assignment (expression has type "list[BaseAgent]", variable has type "str | None")
|
||||||
available_agent
|
available_agent
|
||||||
for available_agent in self.agents
|
for available_agent in self.agents
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from ..tools.cache_tools.cache_tools import CacheTools
|
from ..tools.cache_tools import CacheTools
|
||||||
from ..tools.tool_calling import InstructorToolCalling, ToolCalling
|
from ..tools.tool_calling import InstructorToolCalling, ToolCalling
|
||||||
from .cache.cache_handler import CacheHandler
|
from .cache.cache_handler import CacheHandler
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
DEFAULT_CONFIG_PATH = Path.home() / ".config" / "crewai" / "settings.json"
|
|
||||||
|
|
||||||
class Settings(BaseModel):
|
|
||||||
tool_repository_username: Optional[str] = Field(None, description="Username for interacting with the Tool Repository")
|
|
||||||
tool_repository_password: Optional[str] = Field(None, description="Password for interacting with the Tool Repository")
|
|
||||||
config_path: Path = Field(default=DEFAULT_CONFIG_PATH, exclude=True)
|
|
||||||
|
|
||||||
def __init__(self, config_path: Path = DEFAULT_CONFIG_PATH, **data):
|
|
||||||
"""Load Settings from config path"""
|
|
||||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
file_data = {}
|
|
||||||
if config_path.is_file():
|
|
||||||
try:
|
|
||||||
with config_path.open("r") as f:
|
|
||||||
file_data = json.load(f)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
file_data = {}
|
|
||||||
|
|
||||||
merged_data = {**file_data, **data}
|
|
||||||
super().__init__(config_path=config_path, **merged_data)
|
|
||||||
|
|
||||||
def dump(self) -> None:
|
|
||||||
"""Save current settings to settings.json"""
|
|
||||||
if self.config_path.is_file():
|
|
||||||
with self.config_path.open("r") as f:
|
|
||||||
existing_data = json.load(f)
|
|
||||||
else:
|
|
||||||
existing_data = {}
|
|
||||||
|
|
||||||
updated_data = {**existing_data, **self.model_dump(exclude_unset=True)}
|
|
||||||
with self.config_path.open("w") as f:
|
|
||||||
json.dump(updated_data, f, indent=4)
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
from crewai.tools import BaseTool
|
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
from crewai_tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class MyCustomToolInput(BaseModel):
|
class MyCustomToolInput(BaseModel):
|
||||||
"""Input schema for MyCustomTool."""
|
"""Input schema for MyCustomTool."""
|
||||||
argument: str = Field(..., description="Description of the argument.")
|
argument: str = Field(..., description="Description of the argument.")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from crewai.tools import BaseTool
|
from crewai_tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
from crewai.tools import BaseTool
|
from crewai_tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class MyCustomToolInput(BaseModel):
|
class MyCustomToolInput(BaseModel):
|
||||||
"""Input schema for MyCustomTool."""
|
"""Input schema for MyCustomTool."""
|
||||||
argument: str = Field(..., description="Description of the argument.")
|
argument: str = Field(..., description="Description of the argument.")
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
from crewai.tools import BaseTool
|
from crewai_tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class MyCustomToolInput(BaseModel):
|
class MyCustomToolInput(BaseModel):
|
||||||
"""Input schema for MyCustomTool."""
|
"""Input schema for MyCustomTool."""
|
||||||
argument: str = Field(..., description="Description of the argument.")
|
argument: str = Field(..., description="Description of the argument.")
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from crewai.tools import BaseTool
|
from crewai_tools import BaseTool
|
||||||
|
|
||||||
|
|
||||||
class {{class_name}}(BaseTool):
|
class {{class_name}}(BaseTool):
|
||||||
name: str = "Name of my tool"
|
name: str = "Name of my tool"
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from netrc import netrc
|
||||||
|
import stat
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from crewai.cli import git
|
from crewai.cli import git
|
||||||
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
||||||
from crewai.cli.config import Settings
|
|
||||||
from crewai.cli.utils import (
|
from crewai.cli.utils import (
|
||||||
get_project_description,
|
get_project_description,
|
||||||
get_project_name,
|
get_project_name,
|
||||||
@@ -151,16 +153,26 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
login_response_json = login_response.json()
|
login_response_json = login_response.json()
|
||||||
|
self._set_netrc_credentials(login_response_json["credential"])
|
||||||
settings = Settings()
|
|
||||||
settings.tool_repository_username = login_response_json["credential"]["username"]
|
|
||||||
settings.tool_repository_password = login_response_json["credential"]["password"]
|
|
||||||
settings.dump()
|
|
||||||
|
|
||||||
console.print(
|
console.print(
|
||||||
"Successfully authenticated to the tool repository.", style="bold green"
|
"Successfully authenticated to the tool repository.", style="bold green"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _set_netrc_credentials(self, credentials, netrc_path=None):
|
||||||
|
if not netrc_path:
|
||||||
|
netrc_filename = "_netrc" if platform.system() == "Windows" else ".netrc"
|
||||||
|
netrc_path = Path.home() / netrc_filename
|
||||||
|
netrc_path.touch(mode=stat.S_IRUSR | stat.S_IWUSR, exist_ok=True)
|
||||||
|
|
||||||
|
netrc_instance = netrc(file=netrc_path)
|
||||||
|
netrc_instance.hosts["app.crewai.com"] = (credentials["username"], "", credentials["password"])
|
||||||
|
|
||||||
|
with open(netrc_path, 'w') as file:
|
||||||
|
file.write(str(netrc_instance))
|
||||||
|
|
||||||
|
console.print(f"Added credentials to {netrc_path}", style="bold green")
|
||||||
|
|
||||||
def _add_package(self, tool_details):
|
def _add_package(self, tool_details):
|
||||||
tool_handle = tool_details["handle"]
|
tool_handle = tool_details["handle"]
|
||||||
repository_handle = tool_details["repository"]["handle"]
|
repository_handle = tool_details["repository"]["handle"]
|
||||||
@@ -175,11 +187,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
tool_handle,
|
tool_handle,
|
||||||
]
|
]
|
||||||
add_package_result = subprocess.run(
|
add_package_result = subprocess.run(
|
||||||
add_package_command,
|
add_package_command, capture_output=False, text=True, check=True
|
||||||
capture_output=False,
|
|
||||||
env=self._build_env_with_credentials(repository_handle),
|
|
||||||
text=True,
|
|
||||||
check=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if add_package_result.stderr:
|
if add_package_result.stderr:
|
||||||
@@ -198,13 +206,3 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
"[bold yellow]Tip:[/bold yellow] Navigate to a different directory and try again."
|
"[bold yellow]Tip:[/bold yellow] Navigate to a different directory and try again."
|
||||||
)
|
)
|
||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
def _build_env_with_credentials(self, repository_handle: str):
|
|
||||||
repository_handle = repository_handle.upper().replace("-", "_")
|
|
||||||
settings = Settings()
|
|
||||||
|
|
||||||
env = os.environ.copy()
|
|
||||||
env[f"UV_INDEX_{repository_handle}_USERNAME"] = str(settings.tool_repository_username or "")
|
|
||||||
env[f"UV_INDEX_{repository_handle}_PASSWORD"] = str(settings.tool_repository_password or "")
|
|
||||||
|
|
||||||
return env
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from crewai.task import Task
|
|||||||
from crewai.tasks.conditional_task import ConditionalTask
|
from crewai.tasks.conditional_task import ConditionalTask
|
||||||
from crewai.tasks.task_output import TaskOutput
|
from crewai.tasks.task_output import TaskOutput
|
||||||
from crewai.telemetry import Telemetry
|
from crewai.telemetry import Telemetry
|
||||||
from crewai.tools.agent_tools.agent_tools import AgentTools
|
from crewai.tools.agent_tools import AgentTools
|
||||||
from crewai.types.usage_metrics import UsageMetrics
|
from crewai.types.usage_metrics import UsageMetrics
|
||||||
from crewai.utilities import I18N, FileHandler, Logger, RPMController
|
from crewai.utilities import I18N, FileHandler, Logger, RPMController
|
||||||
from crewai.utilities.constants import (
|
from crewai.utilities.constants import (
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
from typing import (
|
from typing import Any, Callable, Dict, Generic, List, Set, Type, TypeVar, Union
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
Generic,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Set,
|
|
||||||
Type,
|
|
||||||
TypeVar,
|
|
||||||
Union,
|
|
||||||
cast,
|
|
||||||
)
|
|
||||||
|
|
||||||
from pydantic import BaseModel, ValidationError
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from crewai.flow.flow_visualizer import plot_flow
|
from crewai.flow.flow_visualizer import plot_flow
|
||||||
from crewai.flow.utils import get_possible_return_constants
|
from crewai.flow.utils import get_possible_return_constants
|
||||||
@@ -203,74 +191,10 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
|||||||
"""Returns the list of all outputs from executed methods."""
|
"""Returns the list of all outputs from executed methods."""
|
||||||
return self._method_outputs
|
return self._method_outputs
|
||||||
|
|
||||||
def _initialize_state(self, inputs: Dict[str, Any]) -> None:
|
def kickoff(self) -> Any:
|
||||||
"""
|
|
||||||
Initializes or updates the state with the provided inputs.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
inputs: Dictionary of inputs to initialize or update the state.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If inputs do not match the structured state model.
|
|
||||||
TypeError: If state is neither a BaseModel instance nor a dictionary.
|
|
||||||
"""
|
|
||||||
if isinstance(self._state, BaseModel):
|
|
||||||
# Structured state management
|
|
||||||
try:
|
|
||||||
# Define a function to create the dynamic class
|
|
||||||
def create_model_with_extra_forbid(
|
|
||||||
base_model: Type[BaseModel],
|
|
||||||
) -> Type[BaseModel]:
|
|
||||||
class ModelWithExtraForbid(base_model): # type: ignore
|
|
||||||
model_config = base_model.model_config.copy()
|
|
||||||
model_config["extra"] = "forbid"
|
|
||||||
|
|
||||||
return ModelWithExtraForbid
|
|
||||||
|
|
||||||
# Create the dynamic class
|
|
||||||
ModelWithExtraForbid = create_model_with_extra_forbid(
|
|
||||||
self._state.__class__
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create a new instance using the combined state and inputs
|
|
||||||
self._state = cast(
|
|
||||||
T, ModelWithExtraForbid(**{**self._state.model_dump(), **inputs})
|
|
||||||
)
|
|
||||||
|
|
||||||
except ValidationError as e:
|
|
||||||
raise ValueError(f"Invalid inputs for structured state: {e}") from e
|
|
||||||
elif isinstance(self._state, dict):
|
|
||||||
# Unstructured state management
|
|
||||||
self._state.update(inputs)
|
|
||||||
else:
|
|
||||||
raise TypeError("State must be a BaseModel instance or a dictionary.")
|
|
||||||
|
|
||||||
def kickoff(self, inputs: Optional[Dict[str, Any]] = None) -> Any:
|
|
||||||
"""
|
|
||||||
Starts the execution of the flow synchronously.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
inputs: Optional dictionary of inputs to initialize or update the state.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The final output from the flow execution.
|
|
||||||
"""
|
|
||||||
if inputs is not None:
|
|
||||||
self._initialize_state(inputs)
|
|
||||||
return asyncio.run(self.kickoff_async())
|
return asyncio.run(self.kickoff_async())
|
||||||
|
|
||||||
async def kickoff_async(self, inputs: Optional[Dict[str, Any]] = None) -> Any:
|
async def kickoff_async(self) -> Any:
|
||||||
"""
|
|
||||||
Starts the execution of the flow asynchronously.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
inputs: Optional dictionary of inputs to initialize or update the state.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The final output from the flow execution.
|
|
||||||
"""
|
|
||||||
if inputs is not None:
|
|
||||||
self._initialize_state(inputs)
|
|
||||||
if not self._start_methods:
|
if not self._start_methods:
|
||||||
raise ValueError("No start method defined")
|
raise ValueError("No start method defined")
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ class ContextualMemory:
|
|||||||
formatted_results = "\n".join(
|
formatted_results = "\n".join(
|
||||||
[f"- {result['context']}" for result in stm_results]
|
[f"- {result['context']}" for result in stm_results]
|
||||||
)
|
)
|
||||||
print("formatted_results stm", formatted_results)
|
|
||||||
return f"Recent Insights:\n{formatted_results}" if stm_results else ""
|
return f"Recent Insights:\n{formatted_results}" if stm_results else ""
|
||||||
|
|
||||||
def _fetch_ltm_context(self, task) -> Optional[str]:
|
def _fetch_ltm_context(self, task) -> Optional[str]:
|
||||||
@@ -54,8 +53,6 @@ class ContextualMemory:
|
|||||||
formatted_results = list(dict.fromkeys(formatted_results))
|
formatted_results = list(dict.fromkeys(formatted_results))
|
||||||
formatted_results = "\n".join([f"- {result}" for result in formatted_results]) # type: ignore # Incompatible types in assignment (expression has type "str", variable has type "list[str]")
|
formatted_results = "\n".join([f"- {result}" for result in formatted_results]) # type: ignore # Incompatible types in assignment (expression has type "str", variable has type "list[str]")
|
||||||
|
|
||||||
print("formatted_results ltm", formatted_results)
|
|
||||||
|
|
||||||
return f"Historical Data:\n{formatted_results}" if ltm_results else ""
|
return f"Historical Data:\n{formatted_results}" if ltm_results else ""
|
||||||
|
|
||||||
def _fetch_entity_context(self, query) -> str:
|
def _fetch_entity_context(self, query) -> str:
|
||||||
@@ -67,5 +64,4 @@ class ContextualMemory:
|
|||||||
formatted_results = "\n".join(
|
formatted_results = "\n".join(
|
||||||
[f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice"
|
[f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice"
|
||||||
)
|
)
|
||||||
print("formatted_results em", formatted_results)
|
|
||||||
return f"Entities:\n{formatted_results}" if em_results else ""
|
return f"Entities:\n{formatted_results}" if em_results else ""
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class KickoffTaskOutputsSQLiteStorage:
|
|||||||
task.expected_output,
|
task.expected_output,
|
||||||
json.dumps(output, cls=CrewJSONEncoder),
|
json.dumps(output, cls=CrewJSONEncoder),
|
||||||
task_index,
|
task_index,
|
||||||
json.dumps(inputs, cls=CrewJSONEncoder),
|
json.dumps(inputs),
|
||||||
was_replayed,
|
was_replayed,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Dict, List, Optional, cast
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from chromadb import Documents, EmbeddingFunction, Embeddings
|
|
||||||
from chromadb.api import ClientAPI
|
|
||||||
from chromadb.api.types import validate_embedding_function
|
|
||||||
from crewai.memory.storage.base_rag_storage import BaseRAGStorage
|
from crewai.memory.storage.base_rag_storage import BaseRAGStorage
|
||||||
from crewai.utilities.paths import db_storage_path
|
from crewai.utilities.paths import db_storage_path
|
||||||
|
from chromadb.api import ClientAPI
|
||||||
|
from chromadb.api.types import validate_embedding_function
|
||||||
|
from chromadb import Documents, EmbeddingFunction, Embeddings
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@@ -21,11 +21,9 @@ def suppress_logging(
|
|||||||
logger = logging.getLogger(logger_name)
|
logger = logging.getLogger(logger_name)
|
||||||
original_level = logger.getEffectiveLevel()
|
original_level = logger.getEffectiveLevel()
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
with (
|
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(
|
||||||
contextlib.redirect_stdout(io.StringIO()),
|
io.StringIO()
|
||||||
contextlib.redirect_stderr(io.StringIO()),
|
), contextlib.suppress(UserWarning):
|
||||||
contextlib.suppress(UserWarning),
|
|
||||||
):
|
|
||||||
yield
|
yield
|
||||||
logger.setLevel(original_level)
|
logger.setLevel(original_level)
|
||||||
|
|
||||||
@@ -115,52 +113,12 @@ class RAGStorage(BaseRAGStorage):
|
|||||||
self.embedder_config = embedding_functions.HuggingFaceEmbeddingServer(
|
self.embedder_config = embedding_functions.HuggingFaceEmbeddingServer(
|
||||||
url=config.get("api_url"),
|
url=config.get("api_url"),
|
||||||
)
|
)
|
||||||
elif provider == "watson":
|
|
||||||
try:
|
|
||||||
import ibm_watsonx_ai.foundation_models as watson_models
|
|
||||||
from ibm_watsonx_ai import Credentials
|
|
||||||
from ibm_watsonx_ai.metanames import (
|
|
||||||
EmbedTextParamsMetaNames as EmbedParams,
|
|
||||||
)
|
|
||||||
except ImportError as e:
|
|
||||||
raise ImportError(
|
|
||||||
"IBM Watson dependencies are not installed. Please install them to use Watson embedding."
|
|
||||||
) from e
|
|
||||||
|
|
||||||
class WatsonEmbeddingFunction(EmbeddingFunction):
|
|
||||||
def __call__(self, input: Documents) -> Embeddings:
|
|
||||||
if isinstance(input, str):
|
|
||||||
input = [input]
|
|
||||||
|
|
||||||
embed_params = {
|
|
||||||
EmbedParams.TRUNCATE_INPUT_TOKENS: 3,
|
|
||||||
EmbedParams.RETURN_OPTIONS: {"input_text": True},
|
|
||||||
}
|
|
||||||
|
|
||||||
embedding = watson_models.Embeddings(
|
|
||||||
model_id=config.get("model"),
|
|
||||||
params=embed_params,
|
|
||||||
credentials=Credentials(
|
|
||||||
api_key=config.get("api_key"), url=config.get("api_url")
|
|
||||||
),
|
|
||||||
project_id=config.get("project_id"),
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
embeddings = embedding.embed_documents(input)
|
|
||||||
return cast(Embeddings, embeddings)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("Error during Watson embedding:", e)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
self.embedder_config = WatsonEmbeddingFunction()
|
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"Unsupported embedding provider: {provider}, supported providers: [openai, azure, ollama, vertexai, google, cohere, huggingface, watson]"
|
f"Unsupported embedding provider: {provider}, supported providers: [openai, azure, ollama, vertexai, google, cohere, huggingface]"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
validate_embedding_function(self.embedder_config)
|
validate_embedding_function(self.embedder_config) # type: ignore # used for validating embedder_config if defined a embedding function/class
|
||||||
self.embedder_config = self.embedder_config
|
self.embedder_config = self.embedder_config
|
||||||
|
|
||||||
def _initialize_app(self):
|
def _initialize_app(self):
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from pydantic import (
|
|||||||
from pydantic_core import PydanticCustomError
|
from pydantic_core import PydanticCustomError
|
||||||
|
|
||||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||||
from crewai.tools.base_tool import BaseTool
|
|
||||||
from crewai.tasks.output_format import OutputFormat
|
from crewai.tasks.output_format import OutputFormat
|
||||||
from crewai.tasks.task_output import TaskOutput
|
from crewai.tasks.task_output import TaskOutput
|
||||||
from crewai.telemetry.telemetry import Telemetry
|
from crewai.telemetry.telemetry import Telemetry
|
||||||
@@ -92,7 +91,7 @@ class Task(BaseModel):
|
|||||||
output: Optional[TaskOutput] = Field(
|
output: Optional[TaskOutput] = Field(
|
||||||
description="Task output, it's final result after being executed", default=None
|
description="Task output, it's final result after being executed", default=None
|
||||||
)
|
)
|
||||||
tools: Optional[List[BaseTool]] = Field(
|
tools: Optional[List[Any]] = Field(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
description="Tools the agent is limited to use for this task.",
|
description="Tools the agent is limited to use for this task.",
|
||||||
)
|
)
|
||||||
@@ -186,7 +185,7 @@ class Task(BaseModel):
|
|||||||
self,
|
self,
|
||||||
agent: Optional[BaseAgent] = None,
|
agent: Optional[BaseAgent] = None,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[BaseTool]] = None,
|
tools: Optional[List[Any]] = None,
|
||||||
) -> TaskOutput:
|
) -> TaskOutput:
|
||||||
"""Execute the task synchronously."""
|
"""Execute the task synchronously."""
|
||||||
return self._execute_core(agent, context, tools)
|
return self._execute_core(agent, context, tools)
|
||||||
@@ -203,7 +202,7 @@ class Task(BaseModel):
|
|||||||
self,
|
self,
|
||||||
agent: BaseAgent | None = None,
|
agent: BaseAgent | None = None,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[BaseTool]] = None,
|
tools: Optional[List[Any]] = None,
|
||||||
) -> Future[TaskOutput]:
|
) -> Future[TaskOutput]:
|
||||||
"""Execute the task asynchronously."""
|
"""Execute the task asynchronously."""
|
||||||
future: Future[TaskOutput] = Future()
|
future: Future[TaskOutput] = Future()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ with suppress_warnings():
|
|||||||
|
|
||||||
|
|
||||||
from opentelemetry import trace # noqa: E402
|
from opentelemetry import trace # noqa: E402
|
||||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # noqa: E402
|
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # noqa: E402
|
||||||
from opentelemetry.sdk.resources import SERVICE_NAME, Resource # noqa: E402
|
from opentelemetry.sdk.resources import SERVICE_NAME, Resource # noqa: E402
|
||||||
from opentelemetry.sdk.trace import TracerProvider # noqa: E402
|
from opentelemetry.sdk.trace import TracerProvider # noqa: E402
|
||||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor # noqa: E402
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor # noqa: E402
|
||||||
@@ -48,10 +48,6 @@ class Telemetry:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ready = False
|
self.ready = False
|
||||||
self.trace_set = False
|
self.trace_set = False
|
||||||
|
|
||||||
if os.getenv("OTEL_SDK_DISABLED", "false").lower() == "true":
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
telemetry_endpoint = "https://telemetry.crewai.com:4319"
|
telemetry_endpoint = "https://telemetry.crewai.com:4319"
|
||||||
self.resource = Resource(
|
self.resource = Resource(
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
from .base_tool import BaseTool, tool
|
|
||||||
|
|||||||
25
src/crewai/tools/agent_tools.py
Normal file
25
src/crewai/tools/agent_tools.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from crewai.agents.agent_builder.utilities.base_agent_tool import BaseAgentTools
|
||||||
|
|
||||||
|
|
||||||
|
class AgentTools(BaseAgentTools):
|
||||||
|
"""Default tools around agent delegation"""
|
||||||
|
|
||||||
|
def tools(self):
|
||||||
|
from langchain.tools import StructuredTool
|
||||||
|
|
||||||
|
coworkers = ", ".join([f"{agent.role}" for agent in self.agents])
|
||||||
|
tools = [
|
||||||
|
StructuredTool.from_function(
|
||||||
|
func=self.delegate_work,
|
||||||
|
name="Delegate work to coworker",
|
||||||
|
description=self.i18n.tools("delegate_work").format(
|
||||||
|
coworkers=coworkers
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StructuredTool.from_function(
|
||||||
|
func=self.ask_question,
|
||||||
|
name="Ask question to coworker",
|
||||||
|
description=self.i18n.tools("ask_question").format(coworkers=coworkers),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
return tools
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
from crewai.tools.base_tool import BaseTool
|
|
||||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
|
||||||
from crewai.utilities import I18N
|
|
||||||
|
|
||||||
from .delegate_work_tool import DelegateWorkTool
|
|
||||||
from .ask_question_tool import AskQuestionTool
|
|
||||||
|
|
||||||
|
|
||||||
class AgentTools:
|
|
||||||
"""Manager class for agent-related tools"""
|
|
||||||
|
|
||||||
def __init__(self, agents: list[BaseAgent], i18n: I18N = I18N()):
|
|
||||||
self.agents = agents
|
|
||||||
self.i18n = i18n
|
|
||||||
|
|
||||||
def tools(self) -> list[BaseTool]:
|
|
||||||
"""Get all available agent tools"""
|
|
||||||
coworkers = ", ".join([f"{agent.role}" for agent in self.agents])
|
|
||||||
|
|
||||||
delegate_tool = DelegateWorkTool(
|
|
||||||
agents=self.agents,
|
|
||||||
i18n=self.i18n,
|
|
||||||
description=self.i18n.tools("delegate_work").format(coworkers=coworkers),
|
|
||||||
)
|
|
||||||
|
|
||||||
ask_tool = AskQuestionTool(
|
|
||||||
agents=self.agents,
|
|
||||||
i18n=self.i18n,
|
|
||||||
description=self.i18n.tools("ask_question").format(coworkers=coworkers),
|
|
||||||
)
|
|
||||||
|
|
||||||
return [delegate_tool, ask_tool]
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
|
||||||
from typing import Optional
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
class AskQuestionToolSchema(BaseModel):
|
|
||||||
question: str = Field(..., description="The question to ask")
|
|
||||||
context: str = Field(..., description="The context for the question")
|
|
||||||
coworker: str = Field(..., description="The role/name of the coworker to ask")
|
|
||||||
|
|
||||||
|
|
||||||
class AskQuestionTool(BaseAgentTool):
|
|
||||||
"""Tool for asking questions to coworkers"""
|
|
||||||
|
|
||||||
name: str = "Ask question to coworker"
|
|
||||||
args_schema: type[BaseModel] = AskQuestionToolSchema
|
|
||||||
|
|
||||||
def _run(
|
|
||||||
self,
|
|
||||||
question: str,
|
|
||||||
context: str,
|
|
||||||
coworker: Optional[str] = None,
|
|
||||||
**kwargs,
|
|
||||||
) -> str:
|
|
||||||
coworker = self._get_coworker(coworker, **kwargs)
|
|
||||||
return self._execute(coworker, question, context)
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
class DelegateWorkToolSchema(BaseModel):
|
|
||||||
task: str = Field(..., description="The task to delegate")
|
|
||||||
context: str = Field(..., description="The context for the task")
|
|
||||||
coworker: str = Field(
|
|
||||||
..., description="The role/name of the coworker to delegate to"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DelegateWorkTool(BaseAgentTool):
|
|
||||||
"""Tool for delegating work to coworkers"""
|
|
||||||
|
|
||||||
name: str = "Delegate work to coworker"
|
|
||||||
args_schema: type[BaseModel] = DelegateWorkToolSchema
|
|
||||||
|
|
||||||
def _run(
|
|
||||||
self,
|
|
||||||
task: str,
|
|
||||||
context: str,
|
|
||||||
coworker: Optional[str] = None,
|
|
||||||
**kwargs,
|
|
||||||
) -> str:
|
|
||||||
coworker = self._get_coworker(coworker, **kwargs)
|
|
||||||
return self._execute(coworker, task, context)
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import Any, Callable, Type, get_args, get_origin
|
|
||||||
|
|
||||||
from langchain_core.tools import StructuredTool
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, validator
|
|
||||||
from pydantic import BaseModel as PydanticBaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTool(BaseModel, ABC):
|
|
||||||
class _ArgsSchemaPlaceholder(PydanticBaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
model_config = ConfigDict()
|
|
||||||
|
|
||||||
name: str
|
|
||||||
"""The unique name of the tool that clearly communicates its purpose."""
|
|
||||||
description: str
|
|
||||||
"""Used to tell the model how/when/why to use the tool."""
|
|
||||||
args_schema: Type[PydanticBaseModel] = Field(default_factory=_ArgsSchemaPlaceholder)
|
|
||||||
"""The schema for the arguments that the tool accepts."""
|
|
||||||
description_updated: bool = False
|
|
||||||
"""Flag to check if the description has been updated."""
|
|
||||||
cache_function: Callable = lambda _args=None, _result=None: True
|
|
||||||
"""Function that will be used to determine if the tool should be cached, should return a boolean. If None, the tool will be cached."""
|
|
||||||
result_as_answer: bool = False
|
|
||||||
"""Flag to check if the tool should be the final agent answer."""
|
|
||||||
|
|
||||||
@validator("args_schema", always=True, pre=True)
|
|
||||||
def _default_args_schema(
|
|
||||||
cls, v: Type[PydanticBaseModel]
|
|
||||||
) -> Type[PydanticBaseModel]:
|
|
||||||
if not isinstance(v, cls._ArgsSchemaPlaceholder):
|
|
||||||
return v
|
|
||||||
|
|
||||||
return type(
|
|
||||||
f"{cls.__name__}Schema",
|
|
||||||
(PydanticBaseModel,),
|
|
||||||
{
|
|
||||||
"__annotations__": {
|
|
||||||
k: v for k, v in cls._run.__annotations__.items() if k != "return"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def model_post_init(self, __context: Any) -> None:
|
|
||||||
self._generate_description()
|
|
||||||
|
|
||||||
super().model_post_init(__context)
|
|
||||||
|
|
||||||
def run(
|
|
||||||
self,
|
|
||||||
*args: Any,
|
|
||||||
**kwargs: Any,
|
|
||||||
) -> Any:
|
|
||||||
print(f"Using Tool: {self.name}")
|
|
||||||
return self._run(*args, **kwargs)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _run(
|
|
||||||
self,
|
|
||||||
*args: Any,
|
|
||||||
**kwargs: Any,
|
|
||||||
) -> Any:
|
|
||||||
"""Here goes the actual implementation of the tool."""
|
|
||||||
|
|
||||||
def to_langchain(self) -> StructuredTool:
|
|
||||||
self._set_args_schema()
|
|
||||||
return StructuredTool(
|
|
||||||
name=self.name,
|
|
||||||
description=self.description,
|
|
||||||
args_schema=self.args_schema,
|
|
||||||
func=self._run,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_langchain(cls, tool: StructuredTool) -> "BaseTool":
|
|
||||||
if cls == Tool:
|
|
||||||
if tool.func is None:
|
|
||||||
raise ValueError("StructuredTool must have a callable 'func'")
|
|
||||||
return Tool(
|
|
||||||
name=tool.name,
|
|
||||||
description=tool.description,
|
|
||||||
args_schema=tool.args_schema,
|
|
||||||
func=tool.func,
|
|
||||||
)
|
|
||||||
raise NotImplementedError(f"from_langchain not implemented for {cls.__name__}")
|
|
||||||
|
|
||||||
def _set_args_schema(self):
|
|
||||||
if self.args_schema is None:
|
|
||||||
class_name = f"{self.__class__.__name__}Schema"
|
|
||||||
self.args_schema = type(
|
|
||||||
class_name,
|
|
||||||
(PydanticBaseModel,),
|
|
||||||
{
|
|
||||||
"__annotations__": {
|
|
||||||
k: v
|
|
||||||
for k, v in self._run.__annotations__.items()
|
|
||||||
if k != "return"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def _generate_description(self):
|
|
||||||
args_schema = {
|
|
||||||
name: {
|
|
||||||
"description": field.description,
|
|
||||||
"type": BaseTool._get_arg_annotations(field.annotation),
|
|
||||||
}
|
|
||||||
for name, field in self.args_schema.model_fields.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
self.description = f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nTool Description: {self.description}"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_arg_annotations(annotation: type[Any] | None) -> str:
|
|
||||||
if annotation is None:
|
|
||||||
return "None"
|
|
||||||
|
|
||||||
origin = get_origin(annotation)
|
|
||||||
args = get_args(annotation)
|
|
||||||
|
|
||||||
if origin is None:
|
|
||||||
return (
|
|
||||||
annotation.__name__
|
|
||||||
if hasattr(annotation, "__name__")
|
|
||||||
else str(annotation)
|
|
||||||
)
|
|
||||||
|
|
||||||
if args:
|
|
||||||
args_str = ", ".join(BaseTool._get_arg_annotations(arg) for arg in args)
|
|
||||||
return f"{origin.__name__}[{args_str}]"
|
|
||||||
|
|
||||||
return origin.__name__
|
|
||||||
|
|
||||||
|
|
||||||
class Tool(BaseTool):
|
|
||||||
func: Callable
|
|
||||||
"""The function that will be executed when the tool is called."""
|
|
||||||
|
|
||||||
def _run(self, *args: Any, **kwargs: Any) -> Any:
|
|
||||||
return self.func(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def to_langchain(
|
|
||||||
tools: list[BaseTool | StructuredTool],
|
|
||||||
) -> list[StructuredTool]:
|
|
||||||
return [t.to_langchain() if isinstance(t, BaseTool) else t for t in tools]
|
|
||||||
|
|
||||||
|
|
||||||
def tool(*args):
|
|
||||||
"""
|
|
||||||
Decorator to create a tool from a function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _make_with_name(tool_name: str) -> Callable:
|
|
||||||
def _make_tool(f: Callable) -> BaseTool:
|
|
||||||
if f.__doc__ is None:
|
|
||||||
raise ValueError("Function must have a docstring")
|
|
||||||
if f.__annotations__ is None:
|
|
||||||
raise ValueError("Function must have type annotations")
|
|
||||||
|
|
||||||
class_name = "".join(tool_name.split()).title()
|
|
||||||
args_schema = type(
|
|
||||||
class_name,
|
|
||||||
(PydanticBaseModel,),
|
|
||||||
{
|
|
||||||
"__annotations__": {
|
|
||||||
k: v for k, v in f.__annotations__.items() if k != "return"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return Tool(
|
|
||||||
name=tool_name,
|
|
||||||
description=f.__doc__,
|
|
||||||
func=f,
|
|
||||||
args_schema=args_schema,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _make_tool
|
|
||||||
|
|
||||||
if len(args) == 1 and callable(args[0]):
|
|
||||||
return _make_with_name(args[0].__name__)(args[0])
|
|
||||||
if len(args) == 1 and isinstance(args[0], str):
|
|
||||||
return _make_with_name(args[0])
|
|
||||||
raise ValueError("Invalid arguments")
|
|
||||||
@@ -10,7 +10,6 @@ import crewai.utilities.events as events
|
|||||||
from crewai.agents.tools_handler import ToolsHandler
|
from crewai.agents.tools_handler import ToolsHandler
|
||||||
from crewai.task import Task
|
from crewai.task import Task
|
||||||
from crewai.telemetry import Telemetry
|
from crewai.telemetry import Telemetry
|
||||||
from crewai.tools import BaseTool
|
|
||||||
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
|
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
|
||||||
from crewai.tools.tool_usage_events import ToolUsageError, ToolUsageFinished
|
from crewai.tools.tool_usage_events import ToolUsageError, ToolUsageFinished
|
||||||
from crewai.utilities import I18N, Converter, ConverterError, Printer
|
from crewai.utilities import I18N, Converter, ConverterError, Printer
|
||||||
@@ -50,7 +49,7 @@ class ToolUsage:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tools_handler: ToolsHandler,
|
tools_handler: ToolsHandler,
|
||||||
tools: List[BaseTool],
|
tools: List[Any],
|
||||||
original_tools: List[Any],
|
original_tools: List[Any],
|
||||||
tools_description: str,
|
tools_description: str,
|
||||||
tools_names: str,
|
tools_names: str,
|
||||||
@@ -299,7 +298,22 @@ class ToolUsage:
|
|||||||
"""Render the tool name and description in plain text."""
|
"""Render the tool name and description in plain text."""
|
||||||
descriptions = []
|
descriptions = []
|
||||||
for tool in self.tools:
|
for tool in self.tools:
|
||||||
descriptions.append(tool.description)
|
args = {
|
||||||
|
name: {
|
||||||
|
"description": field.description,
|
||||||
|
"type": field.annotation.__name__,
|
||||||
|
}
|
||||||
|
for name, field in tool.args_schema.model_fields.items()
|
||||||
|
}
|
||||||
|
descriptions.append(
|
||||||
|
"\n".join(
|
||||||
|
[
|
||||||
|
f"Tool Name: {tool.name.lower()}",
|
||||||
|
f"Tool Description: {tool.description}",
|
||||||
|
f"Tool Arguments: {args}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
return "\n--\n".join(descriptions)
|
return "\n--\n".join(descriptions)
|
||||||
|
|
||||||
def _function_calling(self, tool_string: str):
|
def _function_calling(self, tool_string: str):
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ from datetime import datetime, date
|
|||||||
import json
|
import json
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
|
|
||||||
class CrewJSONEncoder(json.JSONEncoder):
|
class CrewJSONEncoder(json.JSONEncoder):
|
||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
if isinstance(obj, BaseModel):
|
if isinstance(obj, BaseModel):
|
||||||
return self._handle_pydantic_model(obj)
|
return self._handle_pydantic_model(obj)
|
||||||
elif isinstance(obj, UUID) or isinstance(obj, Decimal):
|
elif isinstance(obj, UUID):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
|
||||||
elif isinstance(obj, datetime) or isinstance(obj, date):
|
elif isinstance(obj, datetime) or isinstance(obj, date):
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from unittest import mock
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from crewai_tools import tool
|
||||||
|
|
||||||
from crewai import Agent, Crew, Task
|
from crewai import Agent, Crew, Task
|
||||||
from crewai.agents.cache import CacheHandler
|
from crewai.agents.cache import CacheHandler
|
||||||
@@ -13,7 +14,6 @@ from crewai.agents.parser import AgentAction, CrewAgentParser, OutputParserExcep
|
|||||||
from crewai.llm import LLM
|
from crewai.llm import LLM
|
||||||
from crewai.tools.tool_calling import InstructorToolCalling
|
from crewai.tools.tool_calling import InstructorToolCalling
|
||||||
from crewai.tools.tool_usage import ToolUsage
|
from crewai.tools.tool_usage import ToolUsage
|
||||||
from crewai.tools import tool
|
|
||||||
from crewai.tools.tool_usage_events import ToolUsageFinished
|
from crewai.tools.tool_usage_events import ToolUsageFinished
|
||||||
from crewai.utilities import RPMController
|
from crewai.utilities import RPMController
|
||||||
from crewai.utilities.events import Emitter
|
from crewai.utilities.events import Emitter
|
||||||
@@ -277,10 +277,9 @@ def test_cache_hitting():
|
|||||||
"multiplier-{'first_number': 12, 'second_number': 3}": 36,
|
"multiplier-{'first_number': 12, 'second_number': 3}": 36,
|
||||||
}
|
}
|
||||||
|
|
||||||
with (
|
with patch.object(CacheHandler, "read") as read, patch.object(
|
||||||
patch.object(CacheHandler, "read") as read,
|
Emitter, "emit"
|
||||||
patch.object(Emitter, "emit") as emit,
|
) as emit:
|
||||||
):
|
|
||||||
read.return_value = "0"
|
read.return_value = "0"
|
||||||
task = Task(
|
task = Task(
|
||||||
description="What is 2 times 6? Ignore correctness and just return the result of the multiplication tool, you must use the tool.",
|
description="What is 2 times 6? Ignore correctness and just return the result of the multiplication tool, you must use the tool.",
|
||||||
@@ -605,7 +604,7 @@ def test_agent_respect_the_max_rpm_set(capsys):
|
|||||||
def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
|
def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -643,7 +642,7 @@ def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
|
|||||||
def test_agent_without_max_rpm_respet_crew_rpm(capsys):
|
def test_agent_without_max_rpm_respet_crew_rpm(capsys):
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -697,7 +696,7 @@ def test_agent_without_max_rpm_respet_crew_rpm(capsys):
|
|||||||
def test_agent_error_on_parsing_tool(capsys):
|
def test_agent_error_on_parsing_tool(capsys):
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -740,7 +739,7 @@ def test_agent_error_on_parsing_tool(capsys):
|
|||||||
def test_agent_remembers_output_format_after_using_tools_too_many_times():
|
def test_agent_remembers_output_format_after_using_tools_too_many_times():
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -864,16 +863,11 @@ def test_agent_function_calling_llm():
|
|||||||
|
|
||||||
from crewai.tools.tool_usage import ToolUsage
|
from crewai.tools.tool_usage import ToolUsage
|
||||||
|
|
||||||
with (
|
with patch.object(
|
||||||
patch.object(
|
instructor, "from_litellm", wraps=instructor.from_litellm
|
||||||
instructor, "from_litellm", wraps=instructor.from_litellm
|
) as mock_from_litellm, patch.object(
|
||||||
) as mock_from_litellm,
|
ToolUsage, "_original_tool_calling", side_effect=Exception("Forced exception")
|
||||||
patch.object(
|
) as mock_original_tool_calling:
|
||||||
ToolUsage,
|
|
||||||
"_original_tool_calling",
|
|
||||||
side_effect=Exception("Forced exception"),
|
|
||||||
) as mock_original_tool_calling,
|
|
||||||
):
|
|
||||||
crew.kickoff()
|
crew.kickoff()
|
||||||
mock_from_litellm.assert_called()
|
mock_from_litellm.assert_called()
|
||||||
mock_original_tool_calling.assert_called()
|
mock_original_tool_calling.assert_called()
|
||||||
@@ -900,7 +894,7 @@ def test_agent_count_formatting_error():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_tool_result_as_answer_is_the_final_answer_for_the_agent():
|
def test_tool_result_as_answer_is_the_final_answer_for_the_agent():
|
||||||
from crewai.tools import BaseTool
|
from crewai_tools import BaseTool
|
||||||
|
|
||||||
class MyCustomTool(BaseTool):
|
class MyCustomTool(BaseTool):
|
||||||
name: str = "Get Greetings"
|
name: str = "Get Greetings"
|
||||||
@@ -930,7 +924,7 @@ def test_tool_result_as_answer_is_the_final_answer_for_the_agent():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_tool_usage_information_is_appended_to_agent():
|
def test_tool_usage_information_is_appended_to_agent():
|
||||||
from crewai.tools import BaseTool
|
from crewai_tools import BaseTool
|
||||||
|
|
||||||
class MyCustomTool(BaseTool):
|
class MyCustomTool(BaseTool):
|
||||||
name: str = "Decide Greetings"
|
name: str = "Decide Greetings"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from crewai.agent import Agent
|
from crewai.agent import Agent
|
||||||
from crewai.tools.agent_tools.agent_tools import AgentTools
|
from crewai.tools.agent_tools import AgentTools
|
||||||
|
|
||||||
researcher = Agent(
|
researcher = Agent(
|
||||||
role="researcher",
|
role="researcher",
|
||||||
@@ -11,14 +11,12 @@ researcher = Agent(
|
|||||||
backstory="You're an expert researcher, specialized in technology",
|
backstory="You're an expert researcher, specialized in technology",
|
||||||
allow_delegation=False,
|
allow_delegation=False,
|
||||||
)
|
)
|
||||||
tools = AgentTools(agents=[researcher]).tools()
|
tools = AgentTools(agents=[researcher])
|
||||||
delegate_tool = tools[0]
|
|
||||||
ask_tool = tools[1]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_delegate_work():
|
def test_delegate_work():
|
||||||
result = delegate_tool.run(
|
result = tools.delegate_work(
|
||||||
coworker="researcher",
|
coworker="researcher",
|
||||||
task="share your take on AI Agents",
|
task="share your take on AI Agents",
|
||||||
context="I heard you hate them",
|
context="I heard you hate them",
|
||||||
@@ -32,8 +30,8 @@ def test_delegate_work():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_delegate_work_with_wrong_co_worker_variable():
|
def test_delegate_work_with_wrong_co_worker_variable():
|
||||||
result = delegate_tool.run(
|
result = tools.delegate_work(
|
||||||
coworker="researcher",
|
co_worker="researcher",
|
||||||
task="share your take on AI Agents",
|
task="share your take on AI Agents",
|
||||||
context="I heard you hate them",
|
context="I heard you hate them",
|
||||||
)
|
)
|
||||||
@@ -46,7 +44,7 @@ def test_delegate_work_with_wrong_co_worker_variable():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_ask_question():
|
def test_ask_question():
|
||||||
result = ask_tool.run(
|
result = tools.ask_question(
|
||||||
coworker="researcher",
|
coworker="researcher",
|
||||||
question="do you hate AI Agents?",
|
question="do you hate AI Agents?",
|
||||||
context="I heard you LOVE them",
|
context="I heard you LOVE them",
|
||||||
@@ -60,8 +58,8 @@ def test_ask_question():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_ask_question_with_wrong_co_worker_variable():
|
def test_ask_question_with_wrong_co_worker_variable():
|
||||||
result = ask_tool.run(
|
result = tools.ask_question(
|
||||||
coworker="researcher",
|
co_worker="researcher",
|
||||||
question="do you hate AI Agents?",
|
question="do you hate AI Agents?",
|
||||||
context="I heard you LOVE them",
|
context="I heard you LOVE them",
|
||||||
)
|
)
|
||||||
@@ -74,8 +72,8 @@ def test_ask_question_with_wrong_co_worker_variable():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_delegate_work_withwith_coworker_as_array():
|
def test_delegate_work_withwith_coworker_as_array():
|
||||||
result = delegate_tool.run(
|
result = tools.delegate_work(
|
||||||
coworker="[researcher]",
|
co_worker="[researcher]",
|
||||||
task="share your take on AI Agents",
|
task="share your take on AI Agents",
|
||||||
context="I heard you hate them",
|
context="I heard you hate them",
|
||||||
)
|
)
|
||||||
@@ -88,8 +86,8 @@ def test_delegate_work_withwith_coworker_as_array():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_ask_question_with_coworker_as_array():
|
def test_ask_question_with_coworker_as_array():
|
||||||
result = ask_tool.run(
|
result = tools.ask_question(
|
||||||
coworker="[researcher]",
|
co_worker="[researcher]",
|
||||||
question="do you hate AI Agents?",
|
question="do you hate AI Agents?",
|
||||||
context="I heard you LOVE them",
|
context="I heard you LOVE them",
|
||||||
)
|
)
|
||||||
@@ -101,7 +99,7 @@ def test_ask_question_with_coworker_as_array():
|
|||||||
|
|
||||||
|
|
||||||
def test_delegate_work_to_wrong_agent():
|
def test_delegate_work_to_wrong_agent():
|
||||||
result = ask_tool.run(
|
result = tools.ask_question(
|
||||||
coworker="writer",
|
coworker="writer",
|
||||||
question="share your take on AI Agents",
|
question="share your take on AI Agents",
|
||||||
context="I heard you hate them",
|
context="I heard you hate them",
|
||||||
@@ -114,7 +112,7 @@ def test_delegate_work_to_wrong_agent():
|
|||||||
|
|
||||||
|
|
||||||
def test_ask_question_to_wrong_agent():
|
def test_ask_question_to_wrong_agent():
|
||||||
result = ask_tool.run(
|
result = tools.ask_question(
|
||||||
coworker="writer",
|
coworker="writer",
|
||||||
question="do you hate AI Agents?",
|
question="do you hate AI Agents?",
|
||||||
context="I heard you LOVE them",
|
context="I heard you LOVE them",
|
||||||
0
tests/agent_tools/lol.py
Normal file
0
tests/agent_tools/lol.py
Normal file
@@ -2,7 +2,6 @@ import hashlib
|
|||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||||
from crewai.tools.base_tool import BaseTool
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
@@ -11,13 +10,13 @@ class TestAgent(BaseAgent):
|
|||||||
self,
|
self,
|
||||||
task: Any,
|
task: Any,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[BaseTool]] = None,
|
tools: Optional[List[Any]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def create_agent_executor(self, tools=None) -> None: ...
|
def create_agent_executor(self, tools=None) -> None: ...
|
||||||
|
|
||||||
def _parse_tools(self, tools: List[BaseTool]) -> List[BaseTool]:
|
def _parse_tools(self, tools: List[Any]) -> List[Any]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_delegation_tools(self, agents: List["BaseAgent"]): ...
|
def get_delegation_tools(self, agents: List["BaseAgent"]): ...
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import unittest
|
|
||||||
import json
|
|
||||||
import tempfile
|
|
||||||
import shutil
|
|
||||||
from pathlib import Path
|
|
||||||
from crewai.cli.config import Settings
|
|
||||||
|
|
||||||
class TestSettings(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.test_dir = Path(tempfile.mkdtemp())
|
|
||||||
self.config_path = self.test_dir / "settings.json"
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
shutil.rmtree(self.test_dir)
|
|
||||||
|
|
||||||
def test_empty_initialization(self):
|
|
||||||
settings = Settings(config_path=self.config_path)
|
|
||||||
self.assertIsNone(settings.tool_repository_username)
|
|
||||||
self.assertIsNone(settings.tool_repository_password)
|
|
||||||
|
|
||||||
def test_initialization_with_data(self):
|
|
||||||
settings = Settings(
|
|
||||||
config_path=self.config_path,
|
|
||||||
tool_repository_username="user1"
|
|
||||||
)
|
|
||||||
self.assertEqual(settings.tool_repository_username, "user1")
|
|
||||||
self.assertIsNone(settings.tool_repository_password)
|
|
||||||
|
|
||||||
def test_initialization_with_existing_file(self):
|
|
||||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with self.config_path.open("w") as f:
|
|
||||||
json.dump({"tool_repository_username": "file_user"}, f)
|
|
||||||
|
|
||||||
settings = Settings(config_path=self.config_path)
|
|
||||||
self.assertEqual(settings.tool_repository_username, "file_user")
|
|
||||||
|
|
||||||
def test_merge_file_and_input_data(self):
|
|
||||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with self.config_path.open("w") as f:
|
|
||||||
json.dump({
|
|
||||||
"tool_repository_username": "file_user",
|
|
||||||
"tool_repository_password": "file_pass"
|
|
||||||
}, f)
|
|
||||||
|
|
||||||
settings = Settings(
|
|
||||||
config_path=self.config_path,
|
|
||||||
tool_repository_username="new_user"
|
|
||||||
)
|
|
||||||
self.assertEqual(settings.tool_repository_username, "new_user")
|
|
||||||
self.assertEqual(settings.tool_repository_password, "file_pass")
|
|
||||||
|
|
||||||
def test_dump_new_settings(self):
|
|
||||||
settings = Settings(
|
|
||||||
config_path=self.config_path,
|
|
||||||
tool_repository_username="user1"
|
|
||||||
)
|
|
||||||
settings.dump()
|
|
||||||
|
|
||||||
with self.config_path.open("r") as f:
|
|
||||||
saved_data = json.load(f)
|
|
||||||
|
|
||||||
self.assertEqual(saved_data["tool_repository_username"], "user1")
|
|
||||||
|
|
||||||
def test_update_existing_settings(self):
|
|
||||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with self.config_path.open("w") as f:
|
|
||||||
json.dump({"existing_setting": "value"}, f)
|
|
||||||
|
|
||||||
settings = Settings(
|
|
||||||
config_path=self.config_path,
|
|
||||||
tool_repository_username="user1"
|
|
||||||
)
|
|
||||||
settings.dump()
|
|
||||||
|
|
||||||
with self.config_path.open("r") as f:
|
|
||||||
saved_data = json.load(f)
|
|
||||||
|
|
||||||
self.assertEqual(saved_data["existing_setting"], "value")
|
|
||||||
self.assertEqual(saved_data["tool_repository_username"], "user1")
|
|
||||||
|
|
||||||
def test_none_values(self):
|
|
||||||
settings = Settings(
|
|
||||||
config_path=self.config_path,
|
|
||||||
tool_repository_username=None
|
|
||||||
)
|
|
||||||
settings.dump()
|
|
||||||
|
|
||||||
with self.config_path.open("r") as f:
|
|
||||||
saved_data = json.load(f)
|
|
||||||
|
|
||||||
self.assertIsNone(saved_data.get("tool_repository_username"))
|
|
||||||
|
|
||||||
def test_invalid_json_in_config(self):
|
|
||||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with self.config_path.open("w") as f:
|
|
||||||
f.write("invalid json")
|
|
||||||
|
|
||||||
try:
|
|
||||||
settings = Settings(config_path=self.config_path)
|
|
||||||
self.assertIsNone(settings.tool_repository_username)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
self.fail("Settings initialization should handle invalid JSON")
|
|
||||||
|
|
||||||
def test_empty_config_file(self):
|
|
||||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
self.config_path.touch()
|
|
||||||
|
|
||||||
settings = Settings(config_path=self.config_path)
|
|
||||||
self.assertIsNone(settings.tool_repository_username)
|
|
||||||
@@ -82,7 +82,6 @@ def test_install_success(mock_get, mock_subprocess_run):
|
|||||||
capture_output=False,
|
capture_output=False,
|
||||||
text=True,
|
text=True,
|
||||||
check=True,
|
check=True,
|
||||||
env=unittest.mock.ANY
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "Succesfully installed sample-tool" in output
|
assert "Succesfully installed sample-tool" in output
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ def test_crew_verbose_output(capsys):
|
|||||||
def test_cache_hitting_between_agents():
|
def test_cache_hitting_between_agents():
|
||||||
from unittest.mock import call, patch
|
from unittest.mock import call, patch
|
||||||
|
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def multiplier(first_number: int, second_number: int) -> float:
|
def multiplier(first_number: int, second_number: int) -> float:
|
||||||
@@ -499,7 +499,7 @@ def test_cache_hitting_between_agents():
|
|||||||
def test_api_calls_throttling(capsys):
|
def test_api_calls_throttling(capsys):
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -1111,7 +1111,7 @@ def test_dont_set_agents_step_callback_if_already_set():
|
|||||||
def test_crew_function_calling_llm():
|
def test_crew_function_calling_llm():
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
llm = "gpt-4o"
|
llm = "gpt-4o"
|
||||||
|
|
||||||
@@ -1146,7 +1146,7 @@ def test_crew_function_calling_llm():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_task_with_no_arguments():
|
def test_task_with_no_arguments():
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def return_data() -> str:
|
def return_data() -> str:
|
||||||
@@ -1309,9 +1309,8 @@ def test_hierarchical_crew_creation_tasks_with_agents():
|
|||||||
|
|
||||||
assert crew.manager_agent is not None
|
assert crew.manager_agent is not None
|
||||||
assert crew.manager_agent.tools is not None
|
assert crew.manager_agent.tools is not None
|
||||||
assert (
|
assert crew.manager_agent.tools[0].description.startswith(
|
||||||
"Delegate a specific task to one of the following coworkers: Senior Writer\n"
|
"Delegate a specific task to one of the following coworkers: Senior Writer"
|
||||||
in crew.manager_agent.tools[0].description
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1338,9 +1337,8 @@ def test_hierarchical_crew_creation_tasks_with_async_execution():
|
|||||||
crew.kickoff()
|
crew.kickoff()
|
||||||
assert crew.manager_agent is not None
|
assert crew.manager_agent is not None
|
||||||
assert crew.manager_agent.tools is not None
|
assert crew.manager_agent.tools is not None
|
||||||
assert (
|
assert crew.manager_agent.tools[0].description.startswith(
|
||||||
"Delegate a specific task to one of the following coworkers: Senior Writer\n"
|
"Delegate a specific task to one of the following coworkers: Senior Writer\n"
|
||||||
in crew.manager_agent.tools[0].description
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1372,9 +1370,8 @@ def test_hierarchical_crew_creation_tasks_with_sync_last():
|
|||||||
crew.kickoff()
|
crew.kickoff()
|
||||||
assert crew.manager_agent is not None
|
assert crew.manager_agent is not None
|
||||||
assert crew.manager_agent.tools is not None
|
assert crew.manager_agent.tools is not None
|
||||||
assert (
|
assert crew.manager_agent.tools[0].description.startswith(
|
||||||
"Delegate a specific task to one of the following coworkers: Senior Writer, Researcher, CEO\n"
|
"Delegate a specific task to one of the following coworkers: Senior Writer, Researcher, CEO\n"
|
||||||
in crew.manager_agent.tools[0].description
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1497,7 +1494,7 @@ def test_task_callback_on_crew():
|
|||||||
def test_tools_with_custom_caching():
|
def test_tools_with_custom_caching():
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def multiplcation_tool(first_number: int, second_number: int) -> int:
|
def multiplcation_tool(first_number: int, second_number: int) -> int:
|
||||||
@@ -1699,7 +1696,7 @@ def test_manager_agent_in_agents_raises_exception():
|
|||||||
|
|
||||||
|
|
||||||
def test_manager_agent_with_tools_raises_exception():
|
def test_manager_agent_with_tools_raises_exception():
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def testing_tool(first_number: int, second_number: int) -> int:
|
def testing_tool(first_number: int, second_number: int) -> int:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from pydantic_core import ValidationError
|
|||||||
|
|
||||||
|
|
||||||
def test_task_tool_reflect_agent_tools():
|
def test_task_tool_reflect_agent_tools():
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def fake_tool() -> None:
|
def fake_tool() -> None:
|
||||||
@@ -39,7 +39,7 @@ def test_task_tool_reflect_agent_tools():
|
|||||||
|
|
||||||
|
|
||||||
def test_task_tool_takes_precedence_over_agent_tools():
|
def test_task_tool_takes_precedence_over_agent_tools():
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def fake_tool() -> None:
|
def fake_tool() -> None:
|
||||||
@@ -656,7 +656,7 @@ def test_increment_delegations_for_sequential_process():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_increment_tool_errors():
|
def test_increment_tool_errors():
|
||||||
from crewai.tools import tool
|
from crewai_tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def scoring_examples() -> None:
|
def scoring_examples() -> None:
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
from typing import Callable
|
|
||||||
from crewai.tools import BaseTool, tool
|
|
||||||
|
|
||||||
|
|
||||||
def test_creating_a_tool_using_annotation():
|
|
||||||
@tool("Name of my tool")
|
|
||||||
def my_tool(question: str) -> str:
|
|
||||||
"""Clear description for what this tool is useful for, you agent will need this information to use it."""
|
|
||||||
return question
|
|
||||||
|
|
||||||
# Assert all the right attributes were defined
|
|
||||||
assert my_tool.name == "Name of my tool"
|
|
||||||
assert (
|
|
||||||
my_tool.description
|
|
||||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it."
|
|
||||||
)
|
|
||||||
assert my_tool.args_schema.schema()["properties"] == {
|
|
||||||
"question": {"title": "Question", "type": "string"}
|
|
||||||
}
|
|
||||||
assert (
|
|
||||||
my_tool.func("What is the meaning of life?") == "What is the meaning of life?"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Assert the langchain tool conversion worked as expected
|
|
||||||
converted_tool = my_tool.to_langchain()
|
|
||||||
assert converted_tool.name == "Name of my tool"
|
|
||||||
|
|
||||||
assert (
|
|
||||||
converted_tool.description
|
|
||||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it."
|
|
||||||
)
|
|
||||||
assert converted_tool.args_schema.schema()["properties"] == {
|
|
||||||
"question": {"title": "Question", "type": "string"}
|
|
||||||
}
|
|
||||||
assert (
|
|
||||||
converted_tool.func("What is the meaning of life?")
|
|
||||||
== "What is the meaning of life?"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_creating_a_tool_using_baseclass():
|
|
||||||
class MyCustomTool(BaseTool):
|
|
||||||
name: str = "Name of my tool"
|
|
||||||
description: str = (
|
|
||||||
"Clear description for what this tool is useful for, you agent will need this information to use it."
|
|
||||||
)
|
|
||||||
|
|
||||||
def _run(self, question: str) -> str:
|
|
||||||
return question
|
|
||||||
|
|
||||||
my_tool = MyCustomTool()
|
|
||||||
# Assert all the right attributes were defined
|
|
||||||
assert my_tool.name == "Name of my tool"
|
|
||||||
|
|
||||||
assert (
|
|
||||||
my_tool.description
|
|
||||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it."
|
|
||||||
)
|
|
||||||
assert my_tool.args_schema.schema()["properties"] == {
|
|
||||||
"question": {"title": "Question", "type": "string"}
|
|
||||||
}
|
|
||||||
assert my_tool.run("What is the meaning of life?") == "What is the meaning of life?"
|
|
||||||
|
|
||||||
# Assert the langchain tool conversion worked as expected
|
|
||||||
converted_tool = my_tool.to_langchain()
|
|
||||||
assert converted_tool.name == "Name of my tool"
|
|
||||||
|
|
||||||
assert (
|
|
||||||
converted_tool.description
|
|
||||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it."
|
|
||||||
)
|
|
||||||
assert converted_tool.args_schema.schema()["properties"] == {
|
|
||||||
"question": {"title": "Question", "type": "string"}
|
|
||||||
}
|
|
||||||
assert (
|
|
||||||
converted_tool.run("What is the meaning of life?")
|
|
||||||
== "What is the meaning of life?"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_setting_cache_function():
|
|
||||||
class MyCustomTool(BaseTool):
|
|
||||||
name: str = "Name of my tool"
|
|
||||||
description: str = (
|
|
||||||
"Clear description for what this tool is useful for, you agent will need this information to use it."
|
|
||||||
)
|
|
||||||
cache_function: Callable = lambda: False
|
|
||||||
|
|
||||||
def _run(self, question: str) -> str:
|
|
||||||
return question
|
|
||||||
|
|
||||||
my_tool = MyCustomTool()
|
|
||||||
# Assert all the right attributes were defined
|
|
||||||
assert not my_tool.cache_function()
|
|
||||||
|
|
||||||
|
|
||||||
def test_default_cache_function_is_true():
|
|
||||||
class MyCustomTool(BaseTool):
|
|
||||||
name: str = "Name of my tool"
|
|
||||||
description: str = (
|
|
||||||
"Clear description for what this tool is useful for, you agent will need this information to use it."
|
|
||||||
)
|
|
||||||
|
|
||||||
def _run(self, question: str) -> str:
|
|
||||||
return question
|
|
||||||
|
|
||||||
my_tool = MyCustomTool()
|
|
||||||
# Assert all the right attributes were defined
|
|
||||||
assert my_tool.cache_function()
|
|
||||||
@@ -3,11 +3,11 @@ import random
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from crewai_tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from crewai import Agent, Task
|
from crewai import Agent, Task
|
||||||
from crewai.tools.tool_usage import ToolUsage
|
from crewai.tools.tool_usage import ToolUsage
|
||||||
from crewai.tools import BaseTool
|
|
||||||
|
|
||||||
|
|
||||||
class RandomNumberToolInput(BaseModel):
|
class RandomNumberToolInput(BaseModel):
|
||||||
@@ -103,7 +103,11 @@ def test_tool_usage_render():
|
|||||||
rendered = tool_usage._render()
|
rendered = tool_usage._render()
|
||||||
|
|
||||||
# Updated checks to match the actual output
|
# Updated checks to match the actual output
|
||||||
assert "Tool Name: Random Number Generator" in rendered
|
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 "Tool Arguments:" in rendered
|
||||||
assert (
|
assert (
|
||||||
"'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}"
|
"'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}"
|
||||||
@@ -113,11 +117,3 @@ def test_tool_usage_render():
|
|||||||
"'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}"
|
"'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}"
|
||||||
in rendered
|
in rendered
|
||||||
)
|
)
|
||||||
assert (
|
|
||||||
"Tool Description: Generates a random number within a specified range"
|
|
||||||
in rendered
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"Tool Name: Random Number Generator\nTool Arguments: {'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}, 'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}}\nTool Description: Generates a random number within a specified range"
|
|
||||||
in rendered
|
|
||||||
)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user