mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-28 02:08:29 +00:00
Compare commits
102 Commits
devin/1735
...
fix/clone_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2816e97753 | ||
|
|
c3e7a3ec19 | ||
|
|
cba8c9faec | ||
|
|
bcb7fb27d0 | ||
|
|
c310044bec | ||
|
|
c4da244b9a | ||
|
|
6617db78f8 | ||
|
|
fd89c3b896 | ||
|
|
b183aaf51d | ||
|
|
8570461969 | ||
|
|
b92253bb13 | ||
|
|
42769e8b22 | ||
|
|
5263df24b6 | ||
|
|
dea6ed7ef0 | ||
|
|
ac28f7f4bc | ||
|
|
d3a0dad323 | ||
|
|
9b88bcd97e | ||
|
|
1de204eff8 | ||
|
|
f4b7cffb6b | ||
|
|
1cc9c981e4 | ||
|
|
adec0892fa | ||
|
|
cb3865a042 | ||
|
|
d506bdb749 | ||
|
|
319128c90d | ||
|
|
6fb654cccd | ||
|
|
0675a2fe04 | ||
|
|
d438f5a7d4 | ||
|
|
4ff9d4963c | ||
|
|
079692de35 | ||
|
|
65b6ff1cc7 | ||
|
|
4008ba74f8 | ||
|
|
24dbdd5686 | ||
|
|
e4b97e328e | ||
|
|
27e49300f6 | ||
|
|
b87c908434 | ||
|
|
c6d8c75869 | ||
|
|
849908c7ea | ||
|
|
ab8d56de4f | ||
|
|
79aaab99c4 | ||
|
|
65d3837c0d | ||
|
|
67bf4aea56 | ||
|
|
e3e62c16d5 | ||
|
|
f34f53fae2 | ||
|
|
71246e9de1 | ||
|
|
591c4a511b | ||
|
|
c67f75d848 | ||
|
|
8c76bad50f | ||
|
|
e27a15023c | ||
|
|
a836f466f4 | ||
|
|
67f0de1f90 | ||
|
|
c642ebf97e | ||
|
|
a21e310d78 | ||
|
|
aba68da542 | ||
|
|
e254f11933 | ||
|
|
ab2274caf0 | ||
|
|
dc9d1d6b49 | ||
|
|
f3004ffb2b | ||
|
|
3e4f112f39 | ||
|
|
cc018bf128 | ||
|
|
46d3e4d4d9 | ||
|
|
627bb3f5f6 | ||
|
|
4a44245de9 | ||
|
|
30d027158a | ||
|
|
3fecde49b6 | ||
|
|
cc129a0bce | ||
|
|
b5779dca12 | ||
|
|
42311d9c7a | ||
|
|
294f2cc3a9 | ||
|
|
3dc442801f | ||
|
|
c12343a8b8 | ||
|
|
835557e648 | ||
|
|
4185ea688f | ||
|
|
0532089246 | ||
|
|
24b155015c | ||
|
|
8ceeec7d36 | ||
|
|
75e68f6fc8 | ||
|
|
3de81cedd6 | ||
|
|
5dc8dd0e8a | ||
|
|
b8d07fee83 | ||
|
|
be8e33daf6 | ||
|
|
efc8323c63 | ||
|
|
831951efc4 | ||
|
|
2131b94ddb | ||
|
|
b3504e768c | ||
|
|
350457b9b8 | ||
|
|
355bf3b48b | ||
|
|
0e94236735 | ||
|
|
673a38c5d9 | ||
|
|
8f57753656 | ||
|
|
a2f839fada | ||
|
|
440883e9e8 | ||
|
|
d3da73136c | ||
|
|
7272fd15ac | ||
|
|
518800239c | ||
|
|
30bd79390a | ||
|
|
d1e2430aac | ||
|
|
bfe2c44f55 | ||
|
|
845951a0db | ||
|
|
c1172a685a | ||
|
|
4bcc3b532d | ||
|
|
ba89e43b62 | ||
|
|
4469461b38 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,3 +21,4 @@ crew_tasks_output.json
|
||||
.mypy_cache
|
||||
.ruff_cache
|
||||
.venv
|
||||
agentops.log
|
||||
@@ -43,7 +43,7 @@ Think of an agent as a specialized team member with specific skills, expertise,
|
||||
| **Max Retry Limit** _(optional)_ | `max_retry_limit` | `int` | Maximum number of retries when an error occurs. Default is 2. |
|
||||
| **Respect Context Window** _(optional)_ | `respect_context_window` | `bool` | Keep messages under context window size by summarizing. Default is True. |
|
||||
| **Code Execution Mode** _(optional)_ | `code_execution_mode` | `Literal["safe", "unsafe"]` | Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct). Default is 'safe'. |
|
||||
| **Embedder Config** _(optional)_ | `embedder_config` | `Optional[Dict[str, Any]]` | Configuration for the embedder used by the agent. |
|
||||
| **Embedder** _(optional)_ | `embedder` | `Optional[Dict[str, Any]]` | Configuration for the embedder used by the agent. |
|
||||
| **Knowledge Sources** _(optional)_ | `knowledge_sources` | `Optional[List[BaseKnowledgeSource]]` | Knowledge sources available to the agent. |
|
||||
| **Use System Prompt** _(optional)_ | `use_system_prompt` | `Optional[bool]` | Whether to use system prompt (for o1 model support). Default is True. |
|
||||
|
||||
@@ -101,6 +101,8 @@ from crewai_tools import SerperDevTool
|
||||
class LatestAiDevelopmentCrew():
|
||||
"""LatestAiDevelopment crew"""
|
||||
|
||||
agents_config = "config/agents.yaml"
|
||||
|
||||
@agent
|
||||
def researcher(self) -> Agent:
|
||||
return Agent(
|
||||
@@ -150,7 +152,7 @@ agent = Agent(
|
||||
use_system_prompt=True, # Default: True
|
||||
tools=[SerperDevTool()], # Optional: List of tools
|
||||
knowledge_sources=None, # Optional: List of knowledge sources
|
||||
embedder_config=None, # Optional: Custom embedder configuration
|
||||
embedder=None, # Optional: Custom embedder configuration
|
||||
system_template=None, # Optional: Custom system prompt template
|
||||
prompt_template=None, # Optional: Custom prompt template
|
||||
response_template=None, # Optional: Custom response template
|
||||
|
||||
@@ -12,7 +12,7 @@ The CrewAI CLI provides a set of commands to interact with CrewAI, allowing you
|
||||
|
||||
To use the CrewAI CLI, make sure you have CrewAI installed:
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
pip install crewai
|
||||
```
|
||||
|
||||
@@ -20,7 +20,7 @@ pip install crewai
|
||||
|
||||
The basic structure of a CrewAI CLI command is:
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai [COMMAND] [OPTIONS] [ARGUMENTS]
|
||||
```
|
||||
|
||||
@@ -30,7 +30,7 @@ crewai [COMMAND] [OPTIONS] [ARGUMENTS]
|
||||
|
||||
Create a new crew or flow.
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai create [OPTIONS] TYPE NAME
|
||||
```
|
||||
|
||||
@@ -38,7 +38,7 @@ crewai create [OPTIONS] TYPE NAME
|
||||
- `NAME`: Name of the crew or flow
|
||||
|
||||
Example:
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai create crew my_new_crew
|
||||
crewai create flow my_new_flow
|
||||
```
|
||||
@@ -47,14 +47,14 @@ crewai create flow my_new_flow
|
||||
|
||||
Show the installed version of CrewAI.
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai version [OPTIONS]
|
||||
```
|
||||
|
||||
- `--tools`: (Optional) Show the installed version of CrewAI tools
|
||||
|
||||
Example:
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai version
|
||||
crewai version --tools
|
||||
```
|
||||
@@ -63,7 +63,7 @@ crewai version --tools
|
||||
|
||||
Train the crew for a specified number of iterations.
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai train [OPTIONS]
|
||||
```
|
||||
|
||||
@@ -71,7 +71,7 @@ crewai train [OPTIONS]
|
||||
- `-f, --filename TEXT`: Path to a custom file for training (default: "trained_agents_data.pkl")
|
||||
|
||||
Example:
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai train -n 10 -f my_training_data.pkl
|
||||
```
|
||||
|
||||
@@ -79,14 +79,14 @@ crewai train -n 10 -f my_training_data.pkl
|
||||
|
||||
Replay the crew execution from a specific task.
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai replay [OPTIONS]
|
||||
```
|
||||
|
||||
- `-t, --task_id TEXT`: Replay the crew from this task ID, including all subsequent tasks
|
||||
|
||||
Example:
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai replay -t task_123456
|
||||
```
|
||||
|
||||
@@ -94,7 +94,7 @@ crewai replay -t task_123456
|
||||
|
||||
Retrieve your latest crew.kickoff() task outputs.
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai log-tasks-outputs
|
||||
```
|
||||
|
||||
@@ -102,7 +102,7 @@ crewai log-tasks-outputs
|
||||
|
||||
Reset the crew memories (long, short, entity, latest_crew_kickoff_outputs).
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai reset-memories [OPTIONS]
|
||||
```
|
||||
|
||||
@@ -113,7 +113,7 @@ crewai reset-memories [OPTIONS]
|
||||
- `-a, --all`: Reset ALL memories
|
||||
|
||||
Example:
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai reset-memories --long --short
|
||||
crewai reset-memories --all
|
||||
```
|
||||
@@ -122,7 +122,7 @@ crewai reset-memories --all
|
||||
|
||||
Test the crew and evaluate the results.
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai test [OPTIONS]
|
||||
```
|
||||
|
||||
@@ -130,7 +130,7 @@ crewai test [OPTIONS]
|
||||
- `-m, --model TEXT`: LLM Model to run the tests on the Crew (default: "gpt-4o-mini")
|
||||
|
||||
Example:
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai test -n 5 -m gpt-3.5-turbo
|
||||
```
|
||||
|
||||
@@ -138,7 +138,7 @@ crewai test -n 5 -m gpt-3.5-turbo
|
||||
|
||||
Run the crew.
|
||||
|
||||
```shell
|
||||
```shell Terminal
|
||||
crewai run
|
||||
```
|
||||
<Note>
|
||||
@@ -147,7 +147,36 @@ Some commands may require additional configuration or setup within your project
|
||||
</Note>
|
||||
|
||||
|
||||
### 9. API Keys
|
||||
### 9. Chat
|
||||
|
||||
Starting in version `0.98.0`, when you run the `crewai chat` command, you start an interactive session with your crew. The AI assistant will guide you by asking for necessary inputs to execute the crew. Once all inputs are provided, the crew will execute its tasks.
|
||||
|
||||
After receiving the results, you can continue interacting with the assistant for further instructions or questions.
|
||||
|
||||
```shell Terminal
|
||||
crewai chat
|
||||
```
|
||||
<Note>
|
||||
Ensure you execute these commands from your CrewAI project's root directory.
|
||||
</Note>
|
||||
<Note>
|
||||
IMPORTANT: Set the `chat_llm` property in your `crew.py` file to enable this command.
|
||||
|
||||
```python
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
return Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
chat_llm="gpt-4o", # LLM for chat orchestration
|
||||
)
|
||||
```
|
||||
</Note>
|
||||
|
||||
|
||||
### 10. API Keys
|
||||
|
||||
When running ```crewai create crew``` command, the CLI will first show you the top 5 most common LLM providers and ask you to select one.
|
||||
|
||||
@@ -161,6 +190,7 @@ The CLI will initially prompt for API keys for the following services:
|
||||
* Groq
|
||||
* Anthropic
|
||||
* Google Gemini
|
||||
* SambaNova
|
||||
|
||||
When you select a provider, the CLI will prompt you to enter your API key.
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ class ExampleFlow(Flow):
|
||||
@start()
|
||||
def generate_city(self):
|
||||
print("Starting flow")
|
||||
# Each flow state automatically gets a unique ID
|
||||
print(f"Flow State ID: {self.state['id']}")
|
||||
|
||||
response = completion(
|
||||
model=self.model,
|
||||
@@ -47,6 +49,8 @@ class ExampleFlow(Flow):
|
||||
)
|
||||
|
||||
random_city = response["choices"][0]["message"]["content"]
|
||||
# Store the city in our state
|
||||
self.state["city"] = random_city
|
||||
print(f"Random City: {random_city}")
|
||||
|
||||
return random_city
|
||||
@@ -64,6 +68,8 @@ class ExampleFlow(Flow):
|
||||
)
|
||||
|
||||
fun_fact = response["choices"][0]["message"]["content"]
|
||||
# Store the fun fact in our state
|
||||
self.state["fun_fact"] = fun_fact
|
||||
return fun_fact
|
||||
|
||||
|
||||
@@ -76,7 +82,15 @@ print(f"Generated fun fact: {result}")
|
||||
|
||||
In the above example, we have created a simple Flow that generates a random city using OpenAI and then generates a fun fact about that city. The Flow consists of two tasks: `generate_city` and `generate_fun_fact`. The `generate_city` task is the starting point of the Flow, and the `generate_fun_fact` task listens for the output of the `generate_city` task.
|
||||
|
||||
When you run the Flow, it will generate a random city and then generate a fun fact about that city. The output will be printed to the console.
|
||||
Each Flow instance automatically receives a unique identifier (UUID) in its state, which helps track and manage flow executions. The state can also store additional data (like the generated city and fun fact) that persists throughout the flow's execution.
|
||||
|
||||
When you run the Flow, it will:
|
||||
1. Generate a unique ID for the flow state
|
||||
2. Generate a random city and store it in the state
|
||||
3. Generate a fun fact about that city and store it in the state
|
||||
4. Print the results to the console
|
||||
|
||||
The state's unique ID and stored data can be useful for tracking flow executions and maintaining context between tasks.
|
||||
|
||||
**Note:** Ensure you have set up your `.env` file to store your `OPENAI_API_KEY`. This key is necessary for authenticating requests to the OpenAI API.
|
||||
|
||||
@@ -138,7 +152,7 @@ print("---- Final Output ----")
|
||||
print(final_output)
|
||||
````
|
||||
|
||||
``` text Output
|
||||
```text Output
|
||||
---- Final Output ----
|
||||
Second method received: Output from first_method
|
||||
````
|
||||
@@ -207,14 +221,17 @@ 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.
|
||||
This approach offers flexibility, enabling developers to add or modify state attributes on the fly without defining a strict schema.
|
||||
Even with unstructured states, CrewAI Flows automatically generates and maintains a unique identifier (UUID) for each state instance.
|
||||
|
||||
```python Code
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
|
||||
class UntructuredExampleFlow(Flow):
|
||||
class UnstructuredExampleFlow(Flow):
|
||||
|
||||
@start()
|
||||
def first_method(self):
|
||||
# The state automatically includes an 'id' field
|
||||
print(f"State ID: {self.state['id']}")
|
||||
self.state.message = "Hello from structured flow"
|
||||
self.state.counter = 0
|
||||
|
||||
@@ -231,10 +248,12 @@ class UntructuredExampleFlow(Flow):
|
||||
print(f"State after third_method: {self.state}")
|
||||
|
||||
|
||||
flow = UntructuredExampleFlow()
|
||||
flow = UnstructuredExampleFlow()
|
||||
flow.kickoff()
|
||||
```
|
||||
|
||||
**Note:** The `id` field is automatically generated and preserved throughout the flow's execution. You don't need to manage or set it manually, and it will be maintained even when updating the state with new data.
|
||||
|
||||
**Key Points:**
|
||||
|
||||
- **Flexibility:** You can dynamically add attributes to `self.state` without predefined constraints.
|
||||
@@ -245,12 +264,15 @@ flow.kickoff()
|
||||
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.
|
||||
|
||||
Each state in CrewAI Flows automatically receives a unique identifier (UUID) to help track and manage state instances. This ID is automatically generated and managed by the Flow system.
|
||||
|
||||
```python Code
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ExampleState(BaseModel):
|
||||
# Note: 'id' field is automatically added to all states
|
||||
counter: int = 0
|
||||
message: str = ""
|
||||
|
||||
@@ -259,6 +281,8 @@ class StructuredExampleFlow(Flow[ExampleState]):
|
||||
|
||||
@start()
|
||||
def first_method(self):
|
||||
# Access the auto-generated ID if needed
|
||||
print(f"State ID: {self.state.id}")
|
||||
self.state.message = "Hello from structured flow"
|
||||
|
||||
@listen(first_method)
|
||||
@@ -299,6 +323,91 @@ flow.kickoff()
|
||||
|
||||
By providing both unstructured and structured state management options, CrewAI Flows empowers developers to build AI workflows that are both flexible and robust, catering to a wide range of application requirements.
|
||||
|
||||
## Flow Persistence
|
||||
|
||||
The @persist decorator enables automatic state persistence in CrewAI Flows, allowing you to maintain flow state across restarts or different workflow executions. This decorator can be applied at either the class level or method level, providing flexibility in how you manage state persistence.
|
||||
|
||||
### Class-Level Persistence
|
||||
|
||||
When applied at the class level, the @persist decorator automatically persists all flow method states:
|
||||
|
||||
```python
|
||||
@persist # Using SQLiteFlowPersistence by default
|
||||
class MyFlow(Flow[MyState]):
|
||||
@start()
|
||||
def initialize_flow(self):
|
||||
# This method will automatically have its state persisted
|
||||
self.state.counter = 1
|
||||
print("Initialized flow. State ID:", self.state.id)
|
||||
|
||||
@listen(initialize_flow)
|
||||
def next_step(self):
|
||||
# The state (including self.state.id) is automatically reloaded
|
||||
self.state.counter += 1
|
||||
print("Flow state is persisted. Counter:", self.state.counter)
|
||||
```
|
||||
|
||||
### Method-Level Persistence
|
||||
|
||||
For more granular control, you can apply @persist to specific methods:
|
||||
|
||||
```python
|
||||
class AnotherFlow(Flow[dict]):
|
||||
@persist # Persists only this method's state
|
||||
@start()
|
||||
def begin(self):
|
||||
if "runs" not in self.state:
|
||||
self.state["runs"] = 0
|
||||
self.state["runs"] += 1
|
||||
print("Method-level persisted runs:", self.state["runs"])
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Unique State Identification**
|
||||
- Each flow state automatically receives a unique UUID
|
||||
- The ID is preserved across state updates and method calls
|
||||
- Supports both structured (Pydantic BaseModel) and unstructured (dictionary) states
|
||||
|
||||
2. **Default SQLite Backend**
|
||||
- SQLiteFlowPersistence is the default storage backend
|
||||
- States are automatically saved to a local SQLite database
|
||||
- Robust error handling ensures clear messages if database operations fail
|
||||
|
||||
3. **Error Handling**
|
||||
- Comprehensive error messages for database operations
|
||||
- Automatic state validation during save and load
|
||||
- Clear feedback when persistence operations encounter issues
|
||||
|
||||
### Important Considerations
|
||||
|
||||
- **State Types**: Both structured (Pydantic BaseModel) and unstructured (dictionary) states are supported
|
||||
- **Automatic ID**: The `id` field is automatically added if not present
|
||||
- **State Recovery**: Failed or restarted flows can automatically reload their previous state
|
||||
- **Custom Implementation**: You can provide your own FlowPersistence implementation for specialized storage needs
|
||||
|
||||
### Technical Advantages
|
||||
|
||||
1. **Precise Control Through Low-Level Access**
|
||||
- Direct access to persistence operations for advanced use cases
|
||||
- Fine-grained control via method-level persistence decorators
|
||||
- Built-in state inspection and debugging capabilities
|
||||
- Full visibility into state changes and persistence operations
|
||||
|
||||
2. **Enhanced Reliability**
|
||||
- Automatic state recovery after system failures or restarts
|
||||
- Transaction-based state updates for data integrity
|
||||
- Comprehensive error handling with clear error messages
|
||||
- Robust validation during state save and load operations
|
||||
|
||||
3. **Extensible Architecture**
|
||||
- Customizable persistence backend through FlowPersistence interface
|
||||
- Support for specialized storage solutions beyond SQLite
|
||||
- Compatible with both structured (Pydantic) and unstructured (dict) states
|
||||
- Seamless integration with existing CrewAI flow patterns
|
||||
|
||||
The persistence system's architecture emphasizes technical precision and customization options, allowing developers to maintain full control over state management while benefiting from built-in reliability features.
|
||||
|
||||
## Flow Control
|
||||
|
||||
### Conditional Logic: `or`
|
||||
@@ -628,4 +737,4 @@ Also, check out our YouTube video on how to use flows in CrewAI below!
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
></iframe>
|
||||
|
||||
@@ -4,8 +4,6 @@ description: What is knowledge in CrewAI and how to use it.
|
||||
icon: book
|
||||
---
|
||||
|
||||
# Using Knowledge in CrewAI
|
||||
|
||||
## What is Knowledge?
|
||||
|
||||
Knowledge in CrewAI is a powerful system that allows AI agents to access and utilize external information sources during their tasks.
|
||||
@@ -36,7 +34,20 @@ CrewAI supports various types of knowledge sources out of the box:
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Quick Start
|
||||
## Supported Knowledge Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--------------------------- | :---------------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `sources` | **List[BaseKnowledgeSource]** | Yes | List of knowledge sources that provide content to be stored and queried. Can include PDF, CSV, Excel, JSON, text files, or string content. |
|
||||
| `collection_name` | **str** | No | Name of the collection where the knowledge will be stored. Used to identify different sets of knowledge. Defaults to "knowledge" if not provided. |
|
||||
| `storage` | **Optional[KnowledgeStorage]** | No | Custom storage configuration for managing how the knowledge is stored and retrieved. If not provided, a default storage will be created. |
|
||||
|
||||
## Quickstart Example
|
||||
|
||||
<Tip>
|
||||
For file-Based Knowledge Sources, make sure to place your files in a `knowledge` directory at the root of your project.
|
||||
Also, use relative paths from the `knowledge` directory when creating the source.
|
||||
</Tip>
|
||||
|
||||
Here's an example using string-based knowledge:
|
||||
|
||||
@@ -80,7 +91,14 @@ result = crew.kickoff(inputs={"question": "What city does John live in and how o
|
||||
```
|
||||
|
||||
|
||||
Here's another example with the `CrewDoclingSource`
|
||||
Here's another example with the `CrewDoclingSource`. The CrewDoclingSource is actually quite versatile and can handle multiple file formats including TXT, PDF, DOCX, HTML, and more.
|
||||
|
||||
<Note>
|
||||
You need to install `docling` for the following example to work: `uv add docling`
|
||||
</Note>
|
||||
|
||||
|
||||
|
||||
```python Code
|
||||
from crewai import LLM, Agent, Crew, Process, Task
|
||||
from crewai.knowledge.source.crew_docling_source import CrewDoclingSource
|
||||
@@ -128,39 +146,225 @@ result = crew.kickoff(
|
||||
)
|
||||
```
|
||||
|
||||
## More Examples
|
||||
|
||||
Here are examples of how to use different types of knowledge sources:
|
||||
|
||||
### Text File Knowledge Source
|
||||
```python
|
||||
from crewai.knowledge.source.crew_docling_source import CrewDoclingSource
|
||||
|
||||
# Create a text file knowledge source
|
||||
text_source = CrewDoclingSource(
|
||||
file_paths=["document.txt", "another.txt"]
|
||||
)
|
||||
|
||||
# Create crew with text file source on agents or crew level
|
||||
agent = Agent(
|
||||
...
|
||||
knowledge_sources=[text_source]
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
...
|
||||
knowledge_sources=[text_source]
|
||||
)
|
||||
```
|
||||
|
||||
### PDF Knowledge Source
|
||||
```python
|
||||
from crewai.knowledge.source.pdf_knowledge_source import PDFKnowledgeSource
|
||||
|
||||
# Create a PDF knowledge source
|
||||
pdf_source = PDFKnowledgeSource(
|
||||
file_paths=["document.pdf", "another.pdf"]
|
||||
)
|
||||
|
||||
# Create crew with PDF knowledge source on agents or crew level
|
||||
agent = Agent(
|
||||
...
|
||||
knowledge_sources=[pdf_source]
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
...
|
||||
knowledge_sources=[pdf_source]
|
||||
)
|
||||
```
|
||||
|
||||
### CSV Knowledge Source
|
||||
```python
|
||||
from crewai.knowledge.source.csv_knowledge_source import CSVKnowledgeSource
|
||||
|
||||
# Create a CSV knowledge source
|
||||
csv_source = CSVKnowledgeSource(
|
||||
file_paths=["data.csv"]
|
||||
)
|
||||
|
||||
# Create crew with CSV knowledge source or on agent level
|
||||
agent = Agent(
|
||||
...
|
||||
knowledge_sources=[csv_source]
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
...
|
||||
knowledge_sources=[csv_source]
|
||||
)
|
||||
```
|
||||
|
||||
### Excel Knowledge Source
|
||||
```python
|
||||
from crewai.knowledge.source.excel_knowledge_source import ExcelKnowledgeSource
|
||||
|
||||
# Create an Excel knowledge source
|
||||
excel_source = ExcelKnowledgeSource(
|
||||
file_paths=["spreadsheet.xlsx"]
|
||||
)
|
||||
|
||||
# Create crew with Excel knowledge source on agents or crew level
|
||||
agent = Agent(
|
||||
...
|
||||
knowledge_sources=[excel_source]
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
...
|
||||
knowledge_sources=[excel_source]
|
||||
)
|
||||
```
|
||||
|
||||
### JSON Knowledge Source
|
||||
```python
|
||||
from crewai.knowledge.source.json_knowledge_source import JSONKnowledgeSource
|
||||
|
||||
# Create a JSON knowledge source
|
||||
json_source = JSONKnowledgeSource(
|
||||
file_paths=["data.json"]
|
||||
)
|
||||
|
||||
# Create crew with JSON knowledge source on agents or crew level
|
||||
agent = Agent(
|
||||
...
|
||||
knowledge_sources=[json_source]
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
...
|
||||
knowledge_sources=[json_source]
|
||||
)
|
||||
```
|
||||
|
||||
## Knowledge Configuration
|
||||
|
||||
### Chunking Configuration
|
||||
|
||||
Control how content is split for processing by setting the chunk size and overlap.
|
||||
Knowledge sources automatically chunk content for better processing.
|
||||
You can configure chunking behavior in your knowledge sources:
|
||||
|
||||
```python Code
|
||||
knowledge_source = StringKnowledgeSource(
|
||||
content="Long content...",
|
||||
chunk_size=4000, # Characters per chunk (default)
|
||||
chunk_overlap=200 # Overlap between chunks (default)
|
||||
```python
|
||||
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
|
||||
|
||||
source = StringKnowledgeSource(
|
||||
content="Your content here",
|
||||
chunk_size=4000, # Maximum size of each chunk (default: 4000)
|
||||
chunk_overlap=200 # Overlap between chunks (default: 200)
|
||||
)
|
||||
```
|
||||
|
||||
## Embedder Configuration
|
||||
The chunking configuration helps in:
|
||||
- Breaking down large documents into manageable pieces
|
||||
- Maintaining context through chunk overlap
|
||||
- Optimizing retrieval accuracy
|
||||
|
||||
You can also configure the embedder for the knowledge store. This is useful if you want to use a different embedder for the knowledge store than the one used for the agents.
|
||||
### Embeddings Configuration
|
||||
|
||||
```python Code
|
||||
...
|
||||
You can also configure the embedder for the knowledge store.
|
||||
This is useful if you want to use a different embedder for the knowledge store than the one used for the agents.
|
||||
The `embedder` parameter supports various embedding model providers that include:
|
||||
- `openai`: OpenAI's embedding models
|
||||
- `google`: Google's text embedding models
|
||||
- `azure`: Azure OpenAI embeddings
|
||||
- `ollama`: Local embeddings with Ollama
|
||||
- `vertexai`: Google Cloud VertexAI embeddings
|
||||
- `cohere`: Cohere's embedding models
|
||||
- `voyageai`: VoyageAI's embedding models
|
||||
- `bedrock`: AWS Bedrock embeddings
|
||||
- `huggingface`: Hugging Face models
|
||||
- `watson`: IBM Watson embeddings
|
||||
|
||||
Here's an example of how to configure the embedder for the knowledge store using Google's `text-embedding-004` model:
|
||||
<CodeGroup>
|
||||
```python Example
|
||||
from crewai import Agent, Task, Crew, Process, LLM
|
||||
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
|
||||
import os
|
||||
|
||||
# Get the GEMINI API key
|
||||
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
|
||||
|
||||
# Create a knowledge source
|
||||
content = "Users name is John. He is 30 years old and lives in San Francisco."
|
||||
string_source = StringKnowledgeSource(
|
||||
content="Users name is John. He is 30 years old and lives in San Francisco.",
|
||||
content=content,
|
||||
)
|
||||
|
||||
# Create an LLM with a temperature of 0 to ensure deterministic outputs
|
||||
gemini_llm = LLM(
|
||||
model="gemini/gemini-1.5-pro-002",
|
||||
api_key=GEMINI_API_KEY,
|
||||
temperature=0,
|
||||
)
|
||||
|
||||
# Create an agent with the knowledge store
|
||||
agent = Agent(
|
||||
role="About User",
|
||||
goal="You know everything about the user.",
|
||||
backstory="""You are a master at understanding people and their preferences.""",
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
llm=gemini_llm,
|
||||
embedder={
|
||||
"provider": "google",
|
||||
"config": {
|
||||
"model": "models/text-embedding-004",
|
||||
"api_key": GEMINI_API_KEY,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Answer the following questions about the user: {question}",
|
||||
expected_output="An answer to the question.",
|
||||
agent=agent,
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
...
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
verbose=True,
|
||||
process=Process.sequential,
|
||||
knowledge_sources=[string_source],
|
||||
embedder={
|
||||
"provider": "openai",
|
||||
"config": {"model": "text-embedding-3-small"},
|
||||
},
|
||||
"provider": "google",
|
||||
"config": {
|
||||
"model": "models/text-embedding-004",
|
||||
"api_key": GEMINI_API_KEY,
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
result = crew.kickoff(inputs={"question": "What city does John live in and how old is he?"})
|
||||
```
|
||||
```text Output
|
||||
# Agent: About User
|
||||
## Task: Answer the following questions about the user: What city does John live in and how old is he?
|
||||
|
||||
# Agent: About User
|
||||
## Final Answer:
|
||||
John is 30 years old and lives in San Francisco.
|
||||
```
|
||||
</CodeGroup>
|
||||
## Clearing Knowledge
|
||||
|
||||
If you need to clear the knowledge stored in CrewAI, you can use the `crewai reset-memories` command with the `--knowledge` option.
|
||||
|
||||
@@ -146,6 +146,19 @@ Here's a detailed breakdown of supported models and their capabilities, you can
|
||||
Groq is known for its fast inference speeds, making it suitable for real-time applications.
|
||||
</Tip>
|
||||
</Tab>
|
||||
<Tab title="SambaNova">
|
||||
| Model | Context Window | Best For |
|
||||
|-------|---------------|-----------|
|
||||
| Llama 3.1 70B/8B | Up to 131,072 tokens | High-performance, large context tasks |
|
||||
| Llama 3.1 405B | 8,192 tokens | High-performance and output quality |
|
||||
| Llama 3.2 Series | 8,192 tokens | General-purpose tasks, multimodal |
|
||||
| Llama 3.3 70B | Up to 131,072 tokens | High-performance and output quality|
|
||||
| Qwen2 familly | 8,192 tokens | High-performance and output quality |
|
||||
|
||||
<Tip>
|
||||
[SambaNova](https://cloud.sambanova.ai/) has several models with fast inference speed at full precision.
|
||||
</Tip>
|
||||
</Tab>
|
||||
<Tab title="Others">
|
||||
| Provider | Context Window | Key Features |
|
||||
|----------|---------------|--------------|
|
||||
@@ -230,6 +243,9 @@ There are three ways to configure LLMs in CrewAI. Choose the method that best fi
|
||||
# llm: bedrock/amazon.titan-text-express-v1
|
||||
# llm: bedrock/meta.llama2-70b-chat-v1
|
||||
|
||||
# Amazon SageMaker Models - Enterprise-grade
|
||||
# llm: sagemaker/<my-endpoint>
|
||||
|
||||
# Mistral Models - Open source alternative
|
||||
# llm: mistral/mistral-large-latest
|
||||
# llm: mistral/mistral-medium-latest
|
||||
@@ -493,6 +509,21 @@ Learn how to get the most out of your LLM configuration:
|
||||
)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Amazon SageMaker">
|
||||
```python Code
|
||||
AWS_ACCESS_KEY_ID=<your-access-key>
|
||||
AWS_SECRET_ACCESS_KEY=<your-secret-key>
|
||||
AWS_DEFAULT_REGION=<your-region>
|
||||
```
|
||||
|
||||
Example usage:
|
||||
```python Code
|
||||
llm = LLM(
|
||||
model="sagemaker/<my-endpoint>"
|
||||
)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Mistral">
|
||||
```python Code
|
||||
|
||||
@@ -134,6 +134,23 @@ crew = Crew(
|
||||
)
|
||||
```
|
||||
|
||||
## Memory Configuration Options
|
||||
If you want to access a specific organization and project, you can set the `org_id` and `project_id` parameters in the memory configuration.
|
||||
|
||||
```python Code
|
||||
from crewai import Crew
|
||||
|
||||
crew = Crew(
|
||||
agents=[...],
|
||||
tasks=[...],
|
||||
verbose=True,
|
||||
memory=True,
|
||||
memory_config={
|
||||
"provider": "mem0",
|
||||
"config": {"user_id": "john", "org_id": "my_org_id", "project_id": "my_project_id"},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Additional Embedding Providers
|
||||
|
||||
@@ -276,6 +293,26 @@ my_crew = Crew(
|
||||
}
|
||||
)
|
||||
```
|
||||
### Using VoyageAI embeddings
|
||||
|
||||
```python Code
|
||||
from crewai import Crew, Agent, Task, Process
|
||||
|
||||
my_crew = Crew(
|
||||
agents=[...],
|
||||
tasks=[...],
|
||||
process=Process.sequential,
|
||||
memory=True,
|
||||
verbose=True,
|
||||
embedder={
|
||||
"provider": "voyageai",
|
||||
"config": {
|
||||
"api_key": "YOUR_API_KEY",
|
||||
"model_name": "<model_name>"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
### Using HuggingFace embeddings
|
||||
|
||||
```python Code
|
||||
|
||||
@@ -31,7 +31,7 @@ From this point on, your crew will have planning enabled, and the tasks will be
|
||||
|
||||
#### Planning LLM
|
||||
|
||||
Now you can define the LLM that will be used to plan the tasks. You can use any ChatOpenAI LLM model available.
|
||||
Now you can define the LLM that will be used to plan the tasks.
|
||||
|
||||
When running the base case example, you will see something like the output below, which represents the output of the `AgentPlanner`
|
||||
responsible for creating the step-by-step logic to add to the Agents' tasks.
|
||||
@@ -39,7 +39,6 @@ responsible for creating the step-by-step logic to add to the Agents' tasks.
|
||||
<CodeGroup>
|
||||
```python Code
|
||||
from crewai import Crew, Agent, Task, Process
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
# Assemble your crew with planning capabilities and custom LLM
|
||||
my_crew = Crew(
|
||||
@@ -47,7 +46,7 @@ my_crew = Crew(
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
planning=True,
|
||||
planning_llm=ChatOpenAI(model="gpt-4o")
|
||||
planning_llm="gpt-4o"
|
||||
)
|
||||
|
||||
# Run the crew
|
||||
|
||||
@@ -23,9 +23,7 @@ Processes enable individual agents to operate as a cohesive unit, streamlining t
|
||||
To assign a process to a crew, specify the process type upon crew creation to set the execution strategy. For a hierarchical process, ensure to define `manager_llm` or `manager_agent` for the manager agent.
|
||||
|
||||
```python
|
||||
from crewai import Crew
|
||||
from crewai.process import Process
|
||||
from langchain_openai import ChatOpenAI
|
||||
from crewai import Crew, Process
|
||||
|
||||
# Example: Creating a crew with a sequential process
|
||||
crew = Crew(
|
||||
@@ -40,7 +38,7 @@ crew = Crew(
|
||||
agents=my_agents,
|
||||
tasks=my_tasks,
|
||||
process=Process.hierarchical,
|
||||
manager_llm=ChatOpenAI(model="gpt-4")
|
||||
manager_llm="gpt-4o"
|
||||
# or
|
||||
# manager_agent=my_manager_agent
|
||||
)
|
||||
|
||||
@@ -150,15 +150,20 @@ There are two main ways for one to create a CrewAI tool:
|
||||
|
||||
```python Code
|
||||
from crewai.tools import BaseTool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class MyToolInput(BaseModel):
|
||||
"""Input schema for MyCustomTool."""
|
||||
argument: str = Field(..., description="Description of the argument.")
|
||||
|
||||
class MyCustomTool(BaseTool):
|
||||
name: str = "Name of my tool"
|
||||
description: str = "Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
description: str = "What this tool does. It's vital for effective utilization."
|
||||
args_schema: Type[BaseModel] = MyToolInput
|
||||
|
||||
def _run(self, argument: str) -> str:
|
||||
# Implementation goes here
|
||||
return "Result from custom tool"
|
||||
# Your tool's logic here
|
||||
return "Tool's result"
|
||||
```
|
||||
|
||||
### Utilizing the `tool` Decorator
|
||||
|
||||
@@ -73,9 +73,9 @@ result = crew.kickoff()
|
||||
If you're using the hierarchical process and don't want to set a custom manager agent, you can specify the language model for the manager:
|
||||
|
||||
```python Code
|
||||
from langchain_openai import ChatOpenAI
|
||||
from crewai import LLM
|
||||
|
||||
manager_llm = ChatOpenAI(model_name="gpt-4")
|
||||
manager_llm = LLM(model="gpt-4o")
|
||||
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
|
||||
@@ -23,6 +23,7 @@ LiteLLM supports a wide range of providers, including but not limited to:
|
||||
- Azure OpenAI
|
||||
- AWS (Bedrock, SageMaker)
|
||||
- Cohere
|
||||
- VoyageAI
|
||||
- Hugging Face
|
||||
- Ollama
|
||||
- Mistral AI
|
||||
@@ -32,6 +33,7 @@ LiteLLM supports a wide range of providers, including but not limited to:
|
||||
- Cloudflare Workers AI
|
||||
- DeepInfra
|
||||
- Groq
|
||||
- SambaNova
|
||||
- [NVIDIA NIMs](https://docs.api.nvidia.com/nim/reference/models-1)
|
||||
- And many more!
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: Using Multimodal Agents
|
||||
description: Learn how to enable and use multimodal capabilities in your agents for processing images and other non-text content within the CrewAI framework.
|
||||
icon: image
|
||||
icon: video
|
||||
---
|
||||
|
||||
# Using Multimodal Agents
|
||||
## Using Multimodal Agents
|
||||
|
||||
CrewAI supports multimodal agents that can process both text and non-text content like images. This guide will show you how to enable and use multimodal capabilities in your agents.
|
||||
|
||||
## Enabling Multimodal Capabilities
|
||||
### Enabling Multimodal Capabilities
|
||||
|
||||
To create a multimodal agent, simply set the `multimodal` parameter to `True` when initializing your agent:
|
||||
|
||||
@@ -25,7 +25,7 @@ agent = Agent(
|
||||
|
||||
When you set `multimodal=True`, the agent is automatically configured with the necessary tools for handling non-text content, including the `AddImageTool`.
|
||||
|
||||
## Working with Images
|
||||
### Working with Images
|
||||
|
||||
The multimodal agent comes pre-configured with the `AddImageTool`, which allows it to process images. You don't need to manually add this tool - it's automatically included when you enable multimodal capabilities.
|
||||
|
||||
@@ -108,7 +108,7 @@ The multimodal agent will automatically handle the image processing through its
|
||||
- Process image content with optional context or specific questions
|
||||
- Provide analysis and insights based on the visual information and task requirements
|
||||
|
||||
## Best Practices
|
||||
### Best Practices
|
||||
|
||||
When working with multimodal agents, keep these best practices in mind:
|
||||
|
||||
|
||||
202
docs/how-to/portkey-observability.mdx
Normal file
202
docs/how-to/portkey-observability.mdx
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
title: Portkey Observability and Guardrails
|
||||
description: How to use Portkey with CrewAI
|
||||
icon: key
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/siddharthsambharia-portkey/Portkey-Product-Images/main/Portkey-CrewAI.png" alt="Portkey CrewAI Header Image" width="70%" />
|
||||
|
||||
|
||||
[Portkey](https://portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai) is a 2-line upgrade to make your CrewAI agents reliable, cost-efficient, and fast.
|
||||
|
||||
Portkey adds 4 core production capabilities to any CrewAI agent:
|
||||
1. Routing to **200+ LLMs**
|
||||
2. Making each LLM call more robust
|
||||
3. Full-stack tracing & cost, performance analytics
|
||||
4. Real-time guardrails to enforce behavior
|
||||
|
||||
## Getting Started
|
||||
|
||||
<Steps>
|
||||
<Step title="Install CrewAI and Portkey">
|
||||
```bash
|
||||
pip install -qU crewai portkey-ai
|
||||
```
|
||||
</Step>
|
||||
<Step title="Configure the LLM Client">
|
||||
To build CrewAI Agents with Portkey, you'll need two keys:
|
||||
- **Portkey API Key**: Sign up on the [Portkey app](https://app.portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai) and copy your API key
|
||||
- **Virtual Key**: Virtual Keys securely manage your LLM API keys in one place. Store your LLM provider API keys securely in Portkey's vault
|
||||
|
||||
```python
|
||||
from crewai import LLM
|
||||
from portkey_ai import createHeaders, PORTKEY_GATEWAY_URL
|
||||
|
||||
gpt_llm = LLM(
|
||||
model="gpt-4",
|
||||
base_url=PORTKEY_GATEWAY_URL,
|
||||
api_key="dummy", # We are using Virtual key
|
||||
extra_headers=createHeaders(
|
||||
api_key="YOUR_PORTKEY_API_KEY",
|
||||
virtual_key="YOUR_VIRTUAL_KEY", # Enter your Virtual key from Portkey
|
||||
)
|
||||
)
|
||||
```
|
||||
</Step>
|
||||
<Step title="Create and Run Your First Agent">
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
|
||||
# Define your agents with roles and goals
|
||||
coder = Agent(
|
||||
role='Software developer',
|
||||
goal='Write clear, concise code on demand',
|
||||
backstory='An expert coder with a keen eye for software trends.',
|
||||
llm=gpt_llm
|
||||
)
|
||||
|
||||
# Create tasks for your agents
|
||||
task1 = Task(
|
||||
description="Define the HTML for making a simple website with heading- Hello World! Portkey is working!",
|
||||
expected_output="A clear and concise HTML code",
|
||||
agent=coder
|
||||
)
|
||||
|
||||
# Instantiate your crew
|
||||
crew = Crew(
|
||||
agents=[coder],
|
||||
tasks=[task1],
|
||||
)
|
||||
|
||||
result = crew.kickoff()
|
||||
print(result)
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Key Features
|
||||
|
||||
| Feature | Description |
|
||||
|:--------|:------------|
|
||||
| 🌐 Multi-LLM Support | Access OpenAI, Anthropic, Gemini, Azure, and 250+ providers through a unified interface |
|
||||
| 🛡️ Production Reliability | Implement retries, timeouts, load balancing, and fallbacks |
|
||||
| 📊 Advanced Observability | Track 40+ metrics including costs, tokens, latency, and custom metadata |
|
||||
| 🔍 Comprehensive Logging | Debug with detailed execution traces and function call logs |
|
||||
| 🚧 Security Controls | Set budget limits and implement role-based access control |
|
||||
| 🔄 Performance Analytics | Capture and analyze feedback for continuous improvement |
|
||||
| 💾 Intelligent Caching | Reduce costs and latency with semantic or simple caching |
|
||||
|
||||
|
||||
## Production Features with Portkey Configs
|
||||
|
||||
All features mentioned below are through Portkey's Config system. Portkey's Config system allows you to define routing strategies using simple JSON objects in your LLM API calls. You can create and manage Configs directly in your code or through the Portkey Dashboard. Each Config has a unique ID for easy reference.
|
||||
|
||||
<Frame>
|
||||
<img src="https://raw.githubusercontent.com/Portkey-AI/docs-core/refs/heads/main/images/libraries/libraries-3.avif"/>
|
||||
</Frame>
|
||||
|
||||
|
||||
### 1. Use 250+ LLMs
|
||||
Access various LLMs like Anthropic, Gemini, Mistral, Azure OpenAI, and more with minimal code changes. Switch between providers or use them together seamlessly. [Learn more about Universal API](https://portkey.ai/docs/product/ai-gateway/universal-api)
|
||||
|
||||
|
||||
Easily switch between different LLM providers:
|
||||
|
||||
```python
|
||||
# Anthropic Configuration
|
||||
anthropic_llm = LLM(
|
||||
model="claude-3-5-sonnet-latest",
|
||||
base_url=PORTKEY_GATEWAY_URL,
|
||||
api_key="dummy",
|
||||
extra_headers=createHeaders(
|
||||
api_key="YOUR_PORTKEY_API_KEY",
|
||||
virtual_key="YOUR_ANTHROPIC_VIRTUAL_KEY", #You don't need provider when using Virtual keys
|
||||
trace_id="anthropic_agent"
|
||||
)
|
||||
)
|
||||
|
||||
# Azure OpenAI Configuration
|
||||
azure_llm = LLM(
|
||||
model="gpt-4",
|
||||
base_url=PORTKEY_GATEWAY_URL,
|
||||
api_key="dummy",
|
||||
extra_headers=createHeaders(
|
||||
api_key="YOUR_PORTKEY_API_KEY",
|
||||
virtual_key="YOUR_AZURE_VIRTUAL_KEY", #You don't need provider when using Virtual keys
|
||||
trace_id="azure_agent"
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
### 2. Caching
|
||||
Improve response times and reduce costs with two powerful caching modes:
|
||||
- **Simple Cache**: Perfect for exact matches
|
||||
- **Semantic Cache**: Matches responses for requests that are semantically similar
|
||||
[Learn more about Caching](https://portkey.ai/docs/product/ai-gateway/cache-simple-and-semantic)
|
||||
|
||||
```py
|
||||
config = {
|
||||
"cache": {
|
||||
"mode": "semantic", # or "simple" for exact matching
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Production Reliability
|
||||
Portkey provides comprehensive reliability features:
|
||||
- **Automatic Retries**: Handle temporary failures gracefully
|
||||
- **Request Timeouts**: Prevent hanging operations
|
||||
- **Conditional Routing**: Route requests based on specific conditions
|
||||
- **Fallbacks**: Set up automatic provider failovers
|
||||
- **Load Balancing**: Distribute requests efficiently
|
||||
|
||||
[Learn more about Reliability Features](https://portkey.ai/docs/product/ai-gateway/)
|
||||
|
||||
|
||||
|
||||
### 4. Metrics
|
||||
|
||||
Agent runs are complex. Portkey automatically logs **40+ comprehensive metrics** for your AI agents, including cost, tokens used, latency, etc. Whether you need a broad overview or granular insights into your agent runs, Portkey's customizable filters provide the metrics you need.
|
||||
|
||||
|
||||
- Cost per agent interaction
|
||||
- Response times and latency
|
||||
- Token usage and efficiency
|
||||
- Success/failure rates
|
||||
- Cache hit rates
|
||||
|
||||
<img src="https://github.com/siddharthsambharia-portkey/Portkey-Product-Images/blob/main/Portkey-Dashboard.png?raw=true" width="70%" alt="Portkey Dashboard" />
|
||||
|
||||
### 5. Detailed Logging
|
||||
Logs are essential for understanding agent behavior, diagnosing issues, and improving performance. They provide a detailed record of agent activities and tool use, which is crucial for debugging and optimizing processes.
|
||||
|
||||
|
||||
Access a dedicated section to view records of agent executions, including parameters, outcomes, function calls, and errors. Filter logs based on multiple parameters such as trace ID, model, tokens used, and metadata.
|
||||
|
||||
<details>
|
||||
<summary><b>Traces</b></summary>
|
||||
<img src="https://raw.githubusercontent.com/siddharthsambharia-portkey/Portkey-Product-Images/main/Portkey-Traces.png" alt="Portkey Traces" width="70%" />
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Logs</b></summary>
|
||||
<img src="https://raw.githubusercontent.com/siddharthsambharia-portkey/Portkey-Product-Images/main/Portkey-Logs.png" alt="Portkey Logs" width="70%" />
|
||||
</details>
|
||||
|
||||
### 6. Enterprise Security Features
|
||||
- Set budget limit and rate limts per Virtual Key (disposable API keys)
|
||||
- Implement role-based access control
|
||||
- Track system changes with audit logs
|
||||
- Configure data retention policies
|
||||
|
||||
|
||||
|
||||
For detailed information on creating and managing Configs, visit the [Portkey documentation](https://docs.portkey.ai/product/ai-gateway/configs).
|
||||
|
||||
## Resources
|
||||
|
||||
- [📘 Portkey Documentation](https://docs.portkey.ai)
|
||||
- [📊 Portkey Dashboard](https://app.portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai)
|
||||
- [🐦 Twitter](https://twitter.com/portkeyai)
|
||||
- [💬 Discord Community](https://discord.gg/DD7vgKK299)
|
||||
@@ -15,10 +15,48 @@ icon: wrench
|
||||
If you need to update Python, visit [python.org/downloads](https://python.org/downloads)
|
||||
</Note>
|
||||
|
||||
# Setting Up Your Environment
|
||||
|
||||
Before installing CrewAI, it's recommended to set up a virtual environment. This helps isolate your project dependencies and avoid conflicts.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a Virtual Environment">
|
||||
Choose your preferred method to create a virtual environment:
|
||||
|
||||
**Using venv (Python's built-in tool):**
|
||||
```shell Terminal
|
||||
python3 -m venv .venv
|
||||
```
|
||||
|
||||
**Using conda:**
|
||||
```shell Terminal
|
||||
conda create -n crewai-env python=3.12
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Activate the Virtual Environment">
|
||||
Activate your virtual environment based on your platform:
|
||||
|
||||
**On macOS/Linux (venv):**
|
||||
```shell Terminal
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
**On Windows (venv):**
|
||||
```shell Terminal
|
||||
.venv\Scripts\activate
|
||||
```
|
||||
|
||||
**Using conda (all platforms):**
|
||||
```shell Terminal
|
||||
conda activate crewai-env
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
# Installing CrewAI
|
||||
|
||||
CrewAI is a flexible and powerful AI framework that enables you to create and manage AI agents, tools, and tasks efficiently.
|
||||
Let's get you set up! 🚀
|
||||
Now let's get you set up! 🚀
|
||||
|
||||
<Steps>
|
||||
<Step title="Install CrewAI">
|
||||
@@ -72,9 +110,9 @@ Let's get you set up! 🚀
|
||||
|
||||
# Creating a New Project
|
||||
|
||||
<Info>
|
||||
<Tip>
|
||||
We recommend using the YAML Template scaffolding for a structured approach to defining agents and tasks.
|
||||
</Info>
|
||||
</Tip>
|
||||
|
||||
<Steps>
|
||||
<Step title="Generate Project Structure">
|
||||
@@ -104,7 +142,18 @@ Let's get you set up! 🚀
|
||||
└── tasks.yaml
|
||||
```
|
||||
</Frame>
|
||||
</Step>
|
||||
</Step>
|
||||
|
||||
<Step title="Install Additional Tools">
|
||||
You can install additional tools using UV:
|
||||
```shell Terminal
|
||||
uv add <tool-name>
|
||||
```
|
||||
|
||||
<Tip>
|
||||
UV is our preferred package manager as it's significantly faster than pip and provides better dependency resolution.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Customize Your Project">
|
||||
Your project will contain these essential files:
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"how-to/custom-manager-agent",
|
||||
"how-to/llm-connections",
|
||||
"how-to/customizing-agents",
|
||||
"how-to/multimodal-agents",
|
||||
"how-to/coding-agents",
|
||||
"how-to/force-tool-output-as-result",
|
||||
"how-to/human-input-on-execution",
|
||||
@@ -100,7 +101,8 @@
|
||||
"how-to/conditional-tasks",
|
||||
"how-to/agentops-observability",
|
||||
"how-to/langtrace-observability",
|
||||
"how-to/openlit-observability"
|
||||
"how-to/openlit-observability",
|
||||
"how-to/portkey-observability"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -278,7 +278,7 @@ email_summarizer:
|
||||
Summarize emails into a concise and clear summary
|
||||
backstory: >
|
||||
You will create a 5 bullet point summary of the report
|
||||
llm: mixtal_llm
|
||||
llm: openai/gpt-4o
|
||||
```
|
||||
|
||||
<Tip>
|
||||
@@ -301,38 +301,166 @@ Use the annotations to properly reference the agent and task in the `crew.py` fi
|
||||
|
||||
### Annotations include:
|
||||
|
||||
* `@agent`
|
||||
* `@task`
|
||||
* `@crew`
|
||||
* `@tool`
|
||||
* `@before_kickoff`
|
||||
* `@after_kickoff`
|
||||
* `@callback`
|
||||
* `@output_json`
|
||||
* `@output_pydantic`
|
||||
* `@cache_handler`
|
||||
Here are examples of how to use each annotation in your CrewAI project, and when you should use them:
|
||||
|
||||
```python crew.py
|
||||
# ...
|
||||
#### @agent
|
||||
Used to define an agent in your crew. Use this when:
|
||||
- You need to create a specialized AI agent with a specific role
|
||||
- You want the agent to be automatically collected and managed by the crew
|
||||
- You need to reuse the same agent configuration across multiple tasks
|
||||
|
||||
```python
|
||||
@agent
|
||||
def email_summarizer(self) -> Agent:
|
||||
def research_agent(self) -> Agent:
|
||||
return Agent(
|
||||
config=self.agents_config["email_summarizer"],
|
||||
role="Research Analyst",
|
||||
goal="Conduct thorough research on given topics",
|
||||
backstory="Expert researcher with years of experience in data analysis",
|
||||
tools=[SerperDevTool()],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
@task
|
||||
def email_summarizer_task(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config["email_summarizer_task"],
|
||||
)
|
||||
# ...
|
||||
```
|
||||
|
||||
<Tip>
|
||||
In addition to the [sequential process](../how-to/sequential-process), you can use the [hierarchical process](../how-to/hierarchical-process),
|
||||
which automatically assigns a manager to the defined crew to properly coordinate the planning and execution of tasks through delegation and validation of results.
|
||||
You can learn more about the core concepts [here](/concepts).
|
||||
</Tip>
|
||||
#### @task
|
||||
Used to define a task that can be executed by agents. Use this when:
|
||||
- You need to define a specific piece of work for an agent
|
||||
- You want tasks to be automatically sequenced and managed
|
||||
- You need to establish dependencies between different tasks
|
||||
|
||||
```python
|
||||
@task
|
||||
def research_task(self) -> Task:
|
||||
return Task(
|
||||
description="Research the latest developments in AI technology",
|
||||
expected_output="A comprehensive report on AI advancements",
|
||||
agent=self.research_agent(),
|
||||
output_file="output/research.md"
|
||||
)
|
||||
```
|
||||
|
||||
#### @crew
|
||||
Used to define your crew configuration. Use this when:
|
||||
- You want to automatically collect all @agent and @task definitions
|
||||
- You need to specify how tasks should be processed (sequential or hierarchical)
|
||||
- You want to set up crew-wide configurations
|
||||
|
||||
```python
|
||||
@crew
|
||||
def research_crew(self) -> Crew:
|
||||
return Crew(
|
||||
agents=self.agents, # Automatically collected from @agent methods
|
||||
tasks=self.tasks, # Automatically collected from @task methods
|
||||
process=Process.sequential,
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
#### @tool
|
||||
Used to create custom tools for your agents. Use this when:
|
||||
- You need to give agents specific capabilities (like web search, data analysis)
|
||||
- You want to encapsulate external API calls or complex operations
|
||||
- You need to share functionality across multiple agents
|
||||
|
||||
```python
|
||||
@tool
|
||||
def web_search_tool(query: str, max_results: int = 5) -> list[str]:
|
||||
"""
|
||||
Search the web for information.
|
||||
|
||||
Args:
|
||||
query: The search query
|
||||
max_results: Maximum number of results to return
|
||||
|
||||
Returns:
|
||||
List of search results
|
||||
"""
|
||||
# Implement your search logic here
|
||||
return [f"Result {i} for: {query}" for i in range(max_results)]
|
||||
```
|
||||
|
||||
#### @before_kickoff
|
||||
Used to execute logic before the crew starts. Use this when:
|
||||
- You need to validate or preprocess input data
|
||||
- You want to set up resources or configurations before execution
|
||||
- You need to perform any initialization logic
|
||||
|
||||
```python
|
||||
@before_kickoff
|
||||
def validate_inputs(self, inputs: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
"""Validate and preprocess inputs before the crew starts."""
|
||||
if inputs is None:
|
||||
return None
|
||||
|
||||
if 'topic' not in inputs:
|
||||
raise ValueError("Topic is required")
|
||||
|
||||
# Add additional context
|
||||
inputs['timestamp'] = datetime.now().isoformat()
|
||||
inputs['topic'] = inputs['topic'].strip().lower()
|
||||
return inputs
|
||||
```
|
||||
|
||||
#### @after_kickoff
|
||||
Used to process results after the crew completes. Use this when:
|
||||
- You need to format or transform the final output
|
||||
- You want to perform cleanup operations
|
||||
- You need to save or log the results in a specific way
|
||||
|
||||
```python
|
||||
@after_kickoff
|
||||
def process_results(self, result: CrewOutput) -> CrewOutput:
|
||||
"""Process and format the results after the crew completes."""
|
||||
result.raw = result.raw.strip()
|
||||
result.raw = f"""
|
||||
# Research Results
|
||||
Generated on: {datetime.now().isoformat()}
|
||||
|
||||
{result.raw}
|
||||
"""
|
||||
return result
|
||||
```
|
||||
|
||||
#### @callback
|
||||
Used to handle events during crew execution. Use this when:
|
||||
- You need to monitor task progress
|
||||
- You want to log intermediate results
|
||||
- You need to implement custom progress tracking or metrics
|
||||
|
||||
```python
|
||||
@callback
|
||||
def log_task_completion(self, task: Task, output: str):
|
||||
"""Log task completion details for monitoring."""
|
||||
print(f"Task '{task.description}' completed")
|
||||
print(f"Output length: {len(output)} characters")
|
||||
print(f"Agent used: {task.agent.role}")
|
||||
print("-" * 50)
|
||||
```
|
||||
|
||||
#### @cache_handler
|
||||
Used to implement custom caching for task results. Use this when:
|
||||
- You want to avoid redundant expensive operations
|
||||
- You need to implement custom cache storage or expiration logic
|
||||
- You want to persist results between runs
|
||||
|
||||
```python
|
||||
@cache_handler
|
||||
def custom_cache(self, key: str) -> Optional[str]:
|
||||
"""Custom cache implementation for storing task results."""
|
||||
cache_file = f"cache/{key}.json"
|
||||
|
||||
if os.path.exists(cache_file):
|
||||
with open(cache_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
# Check if cache is still valid (e.g., not expired)
|
||||
if datetime.fromisoformat(data['timestamp']) > datetime.now() - timedelta(days=1):
|
||||
return data['result']
|
||||
return None
|
||||
```
|
||||
|
||||
<Note>
|
||||
These decorators are part of the CrewAI framework and help organize your crew's structure by automatically collecting agents, tasks, and handling various lifecycle events.
|
||||
They should be used within a class decorated with `@CrewBase`.
|
||||
</Note>
|
||||
|
||||
### Replay Tasks from Latest Crew Kickoff
|
||||
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
---
|
||||
title: BraveSearchTool
|
||||
description: A tool for performing web searches using the Brave Search API
|
||||
icon: search
|
||||
---
|
||||
|
||||
## BraveSearchTool
|
||||
|
||||
The BraveSearchTool enables web searches using the Brave Search API, providing customizable result counts, country-specific searches, and rate-limited operations. It formats search results with titles, URLs, and snippets for easy consumption.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Set up your Brave Search API key:
|
||||
```bash
|
||||
export BRAVE_API_KEY='your-brave-api-key'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import BraveSearchTool
|
||||
|
||||
# Basic initialization
|
||||
search_tool = BraveSearchTool()
|
||||
|
||||
# Advanced initialization with custom parameters
|
||||
search_tool = BraveSearchTool(
|
||||
country="US", # Country-specific search
|
||||
n_results=5, # Number of results to return
|
||||
save_file=True # Save results to file
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Web Researcher',
|
||||
goal='Search and analyze web content',
|
||||
backstory='Expert at finding relevant information online.',
|
||||
tools=[search_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class BraveSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the internet"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
country: Optional[str] = "",
|
||||
n_results: int = 10,
|
||||
save_file: bool = False,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the Brave search tool.
|
||||
|
||||
Args:
|
||||
country (Optional[str]): Country code for region-specific search
|
||||
n_results (int): Number of results to return (default: 10)
|
||||
save_file (bool): Whether to save results to file (default: False)
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute web search using Brave Search API.
|
||||
|
||||
Args:
|
||||
search_query (str): Query to search
|
||||
save_file (bool, optional): Override save_file setting
|
||||
n_results (int, optional): Override n_results setting
|
||||
|
||||
Returns:
|
||||
str: Formatted search results with titles, URLs, and snippets
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. API Authentication:
|
||||
- Securely store BRAVE_API_KEY
|
||||
- Keep API key confidential
|
||||
- Handle authentication errors
|
||||
|
||||
2. Rate Limiting:
|
||||
- Tool automatically handles rate limiting
|
||||
- Minimum 1-second interval between requests
|
||||
- Consider implementing additional rate limits
|
||||
|
||||
3. Search Optimization:
|
||||
- Use specific search queries
|
||||
- Adjust result count based on needs
|
||||
- Consider regional search requirements
|
||||
|
||||
4. Error Handling:
|
||||
- Handle API request failures
|
||||
- Manage parsing errors
|
||||
- Monitor rate limit errors
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import BraveSearchTool
|
||||
|
||||
# Initialize tool with custom configuration
|
||||
search_tool = BraveSearchTool(
|
||||
country="GB", # UK-specific search
|
||||
n_results=3, # Limit to 3 results
|
||||
save_file=True # Save results to file
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Web Researcher',
|
||||
goal='Research latest AI developments',
|
||||
backstory='Expert at finding and analyzing tech news.',
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Find the latest news about artificial
|
||||
intelligence developments in quantum computing.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "latest quantum computing AI developments"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Country-Specific Search
|
||||
```python
|
||||
# Initialize tools for different regions
|
||||
us_search = BraveSearchTool(country="US")
|
||||
uk_search = BraveSearchTool(country="GB")
|
||||
jp_search = BraveSearchTool(country="JP")
|
||||
|
||||
# Compare results across regions
|
||||
us_results = us_search.run(
|
||||
search_query="local news"
|
||||
)
|
||||
uk_results = uk_search.run(
|
||||
search_query="local news"
|
||||
)
|
||||
jp_results = jp_search.run(
|
||||
search_query="local news"
|
||||
)
|
||||
```
|
||||
|
||||
### Result Management
|
||||
```python
|
||||
# Save results to file
|
||||
archival_search = BraveSearchTool(
|
||||
save_file=True,
|
||||
n_results=20
|
||||
)
|
||||
|
||||
# Search and save
|
||||
results = archival_search.run(
|
||||
search_query="historical events 2023"
|
||||
)
|
||||
# Results saved to search_results_YYYY-MM-DD_HH-MM-SS.txt
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
search_tool = BraveSearchTool()
|
||||
results = search_tool.run(
|
||||
search_query="important topic"
|
||||
)
|
||||
print(results)
|
||||
except ValueError as e: # API key missing
|
||||
print(f"Authentication error: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f"Search error: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires Brave Search API key
|
||||
- Implements automatic rate limiting
|
||||
- Supports country-specific searches
|
||||
- Customizable result count
|
||||
- Optional file saving feature
|
||||
- Thread-safe operations
|
||||
- Efficient result formatting
|
||||
- Handles API errors gracefully
|
||||
- Supports parallel searches
|
||||
- Maintains search context
|
||||
@@ -1,164 +0,0 @@
|
||||
---
|
||||
title: CodeDocsSearchTool
|
||||
description: A semantic search tool for code documentation websites using RAG capabilities
|
||||
icon: book-open
|
||||
---
|
||||
|
||||
## CodeDocsSearchTool
|
||||
|
||||
The CodeDocsSearchTool is a specialized Retrieval-Augmented Generation (RAG) tool that enables semantic search within code documentation websites. It inherits from the base RagTool class and provides both fixed and dynamic documentation URL searching capabilities.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import CodeDocsSearchTool
|
||||
|
||||
# Method 1: Dynamic documentation URL
|
||||
docs_search = CodeDocsSearchTool()
|
||||
|
||||
# Method 2: Fixed documentation URL
|
||||
fixed_docs_search = CodeDocsSearchTool(
|
||||
docs_url="https://docs.example.com"
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Documentation Researcher',
|
||||
goal='Search through code documentation semantically',
|
||||
backstory='Expert at finding relevant information in technical documentation.',
|
||||
tools=[docs_search],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
The tool supports two input schemas depending on initialization:
|
||||
|
||||
### Dynamic URL Schema
|
||||
```python
|
||||
class CodeDocsSearchToolSchema(BaseModel):
|
||||
search_query: str # The semantic search query
|
||||
docs_url: str # URL of the documentation site to search
|
||||
```
|
||||
|
||||
### Fixed URL Schema
|
||||
```python
|
||||
class FixedCodeDocsSearchToolSchema(BaseModel):
|
||||
search_query: str # The semantic search query
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(self, docs_url: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
Initialize the documentation search tool.
|
||||
|
||||
Args:
|
||||
docs_url (Optional[str]): Fixed URL to a documentation site. If provided,
|
||||
the tool will only search this documentation.
|
||||
**kwargs: Additional arguments passed to the parent RagTool
|
||||
"""
|
||||
|
||||
def _run(self, search_query: str, **kwargs: Any) -> Any:
|
||||
"""
|
||||
Perform semantic search on the documentation site.
|
||||
|
||||
Args:
|
||||
search_query (str): The semantic search query
|
||||
**kwargs: Additional arguments (including 'docs_url' for dynamic mode)
|
||||
|
||||
Returns:
|
||||
str: Relevant documentation passages based on semantic search
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Choose initialization method based on use case:
|
||||
- Use fixed URL when repeatedly searching the same documentation
|
||||
- Use dynamic URL when searching different documentation sites
|
||||
2. Write clear, semantic search queries
|
||||
3. Ensure documentation sites are accessible
|
||||
4. Consider documentation structure and size
|
||||
5. Handle potential URL access errors in agent prompts
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import CodeDocsSearchTool
|
||||
|
||||
# Example 1: Fixed documentation search
|
||||
api_docs_search = CodeDocsSearchTool(
|
||||
docs_url="https://api.example.com/docs"
|
||||
)
|
||||
|
||||
# Example 2: Dynamic documentation search
|
||||
flexible_docs_search = CodeDocsSearchTool()
|
||||
|
||||
# Create agents
|
||||
api_analyst = Agent(
|
||||
role='API Documentation Analyst',
|
||||
goal='Find relevant API endpoints and usage examples',
|
||||
backstory='Expert at analyzing API documentation.',
|
||||
tools=[api_docs_search]
|
||||
)
|
||||
|
||||
docs_researcher = Agent(
|
||||
role='Documentation Researcher',
|
||||
goal='Search through various documentation sites',
|
||||
backstory='Specialist in finding information across multiple docs.',
|
||||
tools=[flexible_docs_search]
|
||||
)
|
||||
|
||||
# Define tasks
|
||||
fixed_search_task = Task(
|
||||
description="""Find all authentication-related endpoints
|
||||
in the API documentation.""",
|
||||
agent=api_analyst
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "search_query": "authentication endpoints and methods"
|
||||
# }
|
||||
|
||||
dynamic_search_task = Task(
|
||||
description="""Search through the Python documentation at
|
||||
docs.python.org for information about async/await.""",
|
||||
agent=docs_researcher
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "search_query": "async await syntax and usage",
|
||||
# "docs_url": "https://docs.python.org"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[api_analyst, docs_researcher],
|
||||
tasks=[fixed_search_task, dynamic_search_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool for semantic search capabilities
|
||||
- Supports both fixed and dynamic documentation URLs
|
||||
- Uses embeddings for semantic search
|
||||
- Thread-safe operations
|
||||
- Automatically handles documentation loading and embedding
|
||||
- Optimized for technical documentation search
|
||||
@@ -1,224 +0,0 @@
|
||||
---
|
||||
title: CodeInterpreterTool
|
||||
description: A tool for secure Python code execution in isolated Docker environments
|
||||
icon: code
|
||||
---
|
||||
|
||||
## CodeInterpreterTool
|
||||
|
||||
The CodeInterpreterTool provides secure Python code execution capabilities using Docker containers. It supports dynamic library installation and offers both safe (Docker-based) and unsafe (direct) execution modes.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import CodeInterpreterTool
|
||||
|
||||
# Initialize the tool
|
||||
code_tool = CodeInterpreterTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
programmer = Agent(
|
||||
role='Code Executor',
|
||||
goal='Execute and analyze Python code',
|
||||
backstory='Expert at writing and executing Python code.',
|
||||
tools=[code_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class CodeInterpreterSchema(BaseModel):
|
||||
code: str = Field(
|
||||
description="Python3 code used to be interpreted in the Docker container. ALWAYS PRINT the final result and the output of the code"
|
||||
)
|
||||
libraries_used: List[str] = Field(
|
||||
description="List of libraries used in the code with proper installing names separated by commas. Example: numpy,pandas,beautifulsoup4"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
code: Optional[str] = None,
|
||||
user_dockerfile_path: Optional[str] = None,
|
||||
user_docker_base_url: Optional[str] = None,
|
||||
unsafe_mode: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the code interpreter tool.
|
||||
|
||||
Args:
|
||||
code (Optional[str]): Default code to execute
|
||||
user_dockerfile_path (Optional[str]): Custom Dockerfile path
|
||||
user_docker_base_url (Optional[str]): Custom Docker daemon URL
|
||||
unsafe_mode (bool): Enable direct code execution
|
||||
**kwargs: Additional arguments for base tool
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
code: str,
|
||||
libraries_used: List[str],
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute Python code in Docker container or directly.
|
||||
|
||||
Args:
|
||||
code (str): Python code to execute
|
||||
libraries_used (List[str]): Required libraries
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
str: Execution output or error message
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Security Considerations:
|
||||
- Use Docker mode by default
|
||||
- Validate input code
|
||||
- Control library access
|
||||
- Monitor execution time
|
||||
|
||||
2. Docker Configuration:
|
||||
- Use custom Dockerfile when needed
|
||||
- Handle container lifecycle
|
||||
- Manage resource limits
|
||||
- Clean up after execution
|
||||
|
||||
3. Library Management:
|
||||
- Specify exact versions
|
||||
- Use trusted packages
|
||||
- Handle dependencies
|
||||
- Verify installations
|
||||
|
||||
4. Error Handling:
|
||||
- Catch execution errors
|
||||
- Handle timeouts
|
||||
- Manage Docker errors
|
||||
- Provide clear messages
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import CodeInterpreterTool
|
||||
|
||||
# Initialize tool
|
||||
code_tool = CodeInterpreterTool()
|
||||
|
||||
# Create agent
|
||||
programmer = Agent(
|
||||
role='Code Executor',
|
||||
goal='Execute data analysis code',
|
||||
backstory='Expert Python programmer specializing in data analysis.',
|
||||
tools=[code_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
analysis_task = Task(
|
||||
description="""Analyze the dataset using pandas and
|
||||
create a summary visualization with matplotlib.""",
|
||||
agent=programmer
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "code": """
|
||||
# import pandas as pd
|
||||
# import matplotlib.pyplot as plt
|
||||
#
|
||||
# # Load and analyze data
|
||||
# df = pd.read_csv('data.csv')
|
||||
# summary = df.describe()
|
||||
#
|
||||
# # Create visualization
|
||||
# plt.figure(figsize=(10, 6))
|
||||
# df['column'].hist()
|
||||
# plt.savefig('output.png')
|
||||
#
|
||||
# print(summary)
|
||||
# """,
|
||||
# "libraries_used": "pandas,matplotlib"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[programmer],
|
||||
tasks=[analysis_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Docker Configuration
|
||||
```python
|
||||
# Use custom Dockerfile
|
||||
tool = CodeInterpreterTool(
|
||||
user_dockerfile_path="/path/to/Dockerfile"
|
||||
)
|
||||
|
||||
# Use custom Docker daemon
|
||||
tool = CodeInterpreterTool(
|
||||
user_docker_base_url="tcp://remote-docker:2375"
|
||||
)
|
||||
```
|
||||
|
||||
### Direct Execution Mode
|
||||
```python
|
||||
# Enable unsafe mode (not recommended)
|
||||
tool = CodeInterpreterTool(unsafe_mode=True)
|
||||
|
||||
# Execute code directly
|
||||
result = tool.run(
|
||||
code="print('Hello, World!')",
|
||||
libraries_used=[]
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
code_tool = CodeInterpreterTool()
|
||||
result = code_tool.run(
|
||||
code="""
|
||||
import numpy as np
|
||||
arr = np.array([1, 2, 3])
|
||||
print(f"Array mean: {arr.mean()}")
|
||||
""",
|
||||
libraries_used=["numpy"]
|
||||
)
|
||||
print(result)
|
||||
except Exception as e:
|
||||
print(f"Error executing code: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from BaseTool
|
||||
- Docker-based isolation
|
||||
- Dynamic library installation
|
||||
- Secure code execution
|
||||
- Custom Docker support
|
||||
- Comprehensive error handling
|
||||
- Resource management
|
||||
- Container cleanup
|
||||
- Library dependency handling
|
||||
- Execution output capture
|
||||
@@ -1,78 +1,118 @@
|
||||
---
|
||||
title: Composio Tool
|
||||
description: The `ComposioTool` is a wrapper around the composio set of tools and gives your agent access to a wide variety of tools from the Composio SDK.
|
||||
description: Composio provides 250+ production-ready tools for AI agents with flexible authentication management.
|
||||
icon: gear-code
|
||||
---
|
||||
|
||||
# `ComposioTool`
|
||||
# `ComposioToolSet`
|
||||
|
||||
## Description
|
||||
Composio is an integration platform that allows you to connect your AI agents to 250+ tools. Key features include:
|
||||
|
||||
This tools is a wrapper around the composio set of tools and gives your agent access to a wide variety of tools from the Composio SDK.
|
||||
- **Enterprise-Grade Authentication**: Built-in support for OAuth, API Keys, JWT with automatic token refresh
|
||||
- **Full Observability**: Detailed tool usage logs, execution timestamps, and more
|
||||
|
||||
## Installation
|
||||
|
||||
To incorporate this tool into your project, follow the installation instructions below:
|
||||
To incorporate Composio tools into your project, follow the instructions below:
|
||||
|
||||
```shell
|
||||
pip install composio-core
|
||||
pip install 'crewai[tools]'
|
||||
pip install composio-crewai
|
||||
pip install crewai
|
||||
```
|
||||
|
||||
after the installation is complete, either run `composio login` or export your composio API key as `COMPOSIO_API_KEY`.
|
||||
After the installation is complete, either run `composio login` or export your composio API key as `COMPOSIO_API_KEY`. Get your Composio API key from [here](https://app.composio.dev)
|
||||
|
||||
## Example
|
||||
|
||||
The following example demonstrates how to initialize the tool and execute a github action:
|
||||
|
||||
1. Initialize Composio tools
|
||||
1. Initialize Composio toolset
|
||||
|
||||
```python Code
|
||||
from composio import App
|
||||
from crewai_tools import ComposioTool
|
||||
from crewai import Agent, Task
|
||||
from composio_crewai import ComposioToolSet, App, Action
|
||||
from crewai import Agent, Task, Crew
|
||||
|
||||
|
||||
tools = [ComposioTool.from_action(action=Action.GITHUB_ACTIVITY_STAR_REPO_FOR_AUTHENTICATED_USER)]
|
||||
toolset = ComposioToolSet()
|
||||
```
|
||||
|
||||
If you don't know what action you want to use, use `from_app` and `tags` filter to get relevant actions
|
||||
|
||||
2. Connect your GitHub account
|
||||
<CodeGroup>
|
||||
```shell CLI
|
||||
composio add github
|
||||
```
|
||||
```python Code
|
||||
tools = ComposioTool.from_app(App.GITHUB, tags=["important"])
|
||||
request = toolset.initiate_connection(app=App.GITHUB)
|
||||
print(f"Open this URL to authenticate: {request.redirectUrl}")
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
or use `use_case` to search relevant actions
|
||||
3. Get Tools
|
||||
|
||||
- Retrieving all the tools from an app (not recommended for production):
|
||||
```python Code
|
||||
tools = ComposioTool.from_app(App.GITHUB, use_case="Star a github repository")
|
||||
tools = toolset.get_tools(apps=[App.GITHUB])
|
||||
```
|
||||
|
||||
2. Define agent
|
||||
- Filtering tools based on tags:
|
||||
```python Code
|
||||
tag = "users"
|
||||
|
||||
filtered_action_enums = toolset.find_actions_by_tags(
|
||||
App.GITHUB,
|
||||
tags=[tag],
|
||||
)
|
||||
|
||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||
```
|
||||
|
||||
- Filtering tools based on use case:
|
||||
```python Code
|
||||
use_case = "Star a repository on GitHub"
|
||||
|
||||
filtered_action_enums = toolset.find_actions_by_use_case(
|
||||
App.GITHUB, use_case=use_case, advanced=False
|
||||
)
|
||||
|
||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||
```
|
||||
<Tip>Set `advanced` to True to get actions for complex use cases</Tip>
|
||||
|
||||
- Using specific tools:
|
||||
|
||||
In this demo, we will use the `GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER` action from the GitHub app.
|
||||
```python Code
|
||||
tools = toolset.get_tools(
|
||||
actions=[Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER]
|
||||
)
|
||||
```
|
||||
Learn more about filtering actions [here](https://docs.composio.dev/patterns/tools/use-tools/use-specific-actions)
|
||||
|
||||
4. Define agent
|
||||
|
||||
```python Code
|
||||
crewai_agent = Agent(
|
||||
role="Github Agent",
|
||||
goal="You take action on Github using Github APIs",
|
||||
backstory=(
|
||||
"You are AI agent that is responsible for taking actions on Github "
|
||||
"on users behalf. You need to take action on Github using Github APIs"
|
||||
),
|
||||
role="GitHub Agent",
|
||||
goal="You take action on GitHub using GitHub APIs",
|
||||
backstory="You are AI agent that is responsible for taking actions on GitHub on behalf of users using GitHub APIs",
|
||||
verbose=True,
|
||||
tools=tools,
|
||||
llm= # pass an llm
|
||||
)
|
||||
```
|
||||
|
||||
3. Execute task
|
||||
5. Execute task
|
||||
|
||||
```python Code
|
||||
task = Task(
|
||||
description="Star a repo ComposioHQ/composio on GitHub",
|
||||
description="Star a repo composiohq/composio on GitHub",
|
||||
agent=crewai_agent,
|
||||
expected_output="if the star happened",
|
||||
expected_output="Status of the operation",
|
||||
)
|
||||
|
||||
task.execute()
|
||||
crew = Crew(agents=[crewai_agent], tasks=[task])
|
||||
|
||||
crew.kickoff()
|
||||
```
|
||||
|
||||
* More detailed list of tools can be found [here](https://app.composio.dev)
|
||||
* More detailed list of tools can be found [here](https://app.composio.dev)
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
---
|
||||
title: CSVSearchTool
|
||||
description: A tool for semantic search within CSV files using RAG capabilities
|
||||
icon: table
|
||||
---
|
||||
|
||||
## CSVSearchTool
|
||||
|
||||
The CSVSearchTool enables semantic search capabilities for CSV files using Retrieval-Augmented Generation (RAG). It can process CSV files either specified during initialization or at runtime, making it flexible for various use cases.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import CSVSearchTool
|
||||
|
||||
# Method 1: Initialize with specific CSV file
|
||||
csv_tool = CSVSearchTool(csv="path/to/data.csv")
|
||||
|
||||
# Method 2: Initialize without CSV (specify at runtime)
|
||||
flexible_csv_tool = CSVSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
data_analyst = Agent(
|
||||
role='Data Analyst',
|
||||
goal='Search and analyze CSV data semantically',
|
||||
backstory='Expert at analyzing and extracting insights from CSV data.',
|
||||
tools=[csv_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed CSV Schema (when CSV path provided during initialization)
|
||||
```python
|
||||
class FixedCSVSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the CSV's content"
|
||||
)
|
||||
```
|
||||
|
||||
### Flexible CSV Schema (when CSV path provided at runtime)
|
||||
```python
|
||||
class CSVSearchToolSchema(FixedCSVSearchToolSchema):
|
||||
csv: str = Field(
|
||||
description="Mandatory csv path you want to search"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
csv: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the CSV search tool.
|
||||
|
||||
Args:
|
||||
csv (Optional[str]): Path to CSV file (optional)
|
||||
**kwargs: Additional arguments for RAG tool configuration
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute semantic search on CSV content.
|
||||
|
||||
Args:
|
||||
search_query (str): Query to search in the CSV
|
||||
**kwargs: Additional arguments including csv path if not initialized
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the CSV matching the query
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. CSV File Handling:
|
||||
- Ensure CSV files are properly formatted
|
||||
- Use absolute paths for reliability
|
||||
- Verify file permissions before processing
|
||||
|
||||
2. Search Optimization:
|
||||
- Use specific, focused search queries
|
||||
- Consider column names and data structure
|
||||
- Test with sample queries first
|
||||
|
||||
3. Performance Considerations:
|
||||
- Pre-initialize with CSV for repeated searches
|
||||
- Handle large CSV files appropriately
|
||||
- Monitor memory usage with big datasets
|
||||
|
||||
4. Error Handling:
|
||||
- Verify CSV file existence
|
||||
- Handle malformed CSV data
|
||||
- Manage file access permissions
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import CSVSearchTool
|
||||
|
||||
# Initialize tool with specific CSV
|
||||
csv_tool = CSVSearchTool(csv="/path/to/sales_data.csv")
|
||||
|
||||
# Create agent
|
||||
analyst = Agent(
|
||||
role='Data Analyst',
|
||||
goal='Extract insights from sales data',
|
||||
backstory='Expert at analyzing sales data and trends.',
|
||||
tools=[csv_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
analysis_task = Task(
|
||||
description="""Find all sales records from the CSV
|
||||
that relate to product returns in Q4 2023.""",
|
||||
agent=analyst
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "product returns Q4 2023"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[analyst],
|
||||
tasks=[analysis_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Dynamic CSV Selection
|
||||
```python
|
||||
# Initialize without CSV
|
||||
flexible_tool = CSVSearchTool()
|
||||
|
||||
# Search different CSVs
|
||||
result1 = flexible_tool.run(
|
||||
search_query="revenue 2023",
|
||||
csv="/path/to/finance.csv"
|
||||
)
|
||||
|
||||
result2 = flexible_tool.run(
|
||||
search_query="customer feedback",
|
||||
csv="/path/to/surveys.csv"
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple CSV Analysis
|
||||
```python
|
||||
# Create tools for different CSVs
|
||||
sales_tool = CSVSearchTool(csv="/path/to/sales.csv")
|
||||
inventory_tool = CSVSearchTool(csv="/path/to/inventory.csv")
|
||||
|
||||
# Create agent with multiple tools
|
||||
analyst = Agent(
|
||||
role='Business Analyst',
|
||||
goal='Cross-reference sales and inventory data',
|
||||
tools=[sales_tool, inventory_tool]
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
csv_tool = CSVSearchTool(csv="/path/to/data.csv")
|
||||
result = csv_tool.run(
|
||||
search_query="important metrics"
|
||||
)
|
||||
print(result)
|
||||
except Exception as e:
|
||||
print(f"Error processing CSV: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool for semantic search
|
||||
- Supports dynamic CSV file specification
|
||||
- Uses embedchain for data processing
|
||||
- Maintains search context across queries
|
||||
- Thread-safe operations
|
||||
- Efficient semantic search capabilities
|
||||
- Supports various CSV formats
|
||||
- Handles large datasets effectively
|
||||
- Preserves CSV structure in search
|
||||
- Enables natural language queries
|
||||
@@ -1,217 +0,0 @@
|
||||
---
|
||||
title: Directory Read Tool
|
||||
description: A tool for recursively listing directory contents
|
||||
---
|
||||
|
||||
# Directory Read Tool
|
||||
|
||||
The Directory Read Tool provides functionality to recursively list all files within a directory. It supports both fixed and dynamic directory path modes, allowing you to specify the directory at initialization or runtime.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You can use the Directory Read Tool in two ways:
|
||||
|
||||
### 1. Fixed Directory Path
|
||||
|
||||
Initialize the tool with a specific directory path:
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import DirectoryReadTool
|
||||
|
||||
# Initialize with a fixed directory
|
||||
tool = DirectoryReadTool(directory="/path/to/your/directory")
|
||||
|
||||
# Create an agent with the tool
|
||||
agent = Agent(
|
||||
role='File System Analyst',
|
||||
goal='Analyze directory contents',
|
||||
backstory='I help analyze and organize file systems',
|
||||
tools=[tool]
|
||||
)
|
||||
|
||||
# Use in a task
|
||||
task = Task(
|
||||
description="List all files in the project directory",
|
||||
agent=agent
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Dynamic Directory Path
|
||||
|
||||
Initialize the tool without a specific directory path to provide it at runtime:
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import DirectoryReadTool
|
||||
|
||||
# Initialize without a fixed directory
|
||||
tool = DirectoryReadTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
agent = Agent(
|
||||
role='File System Explorer',
|
||||
goal='Explore different directories',
|
||||
backstory='I analyze various directory structures',
|
||||
tools=[tool]
|
||||
)
|
||||
|
||||
# Use in a task with dynamic directory path
|
||||
task = Task(
|
||||
description="List all files in the specified directory",
|
||||
agent=agent,
|
||||
context={
|
||||
"directory": "/path/to/explore"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed Directory Mode
|
||||
```python
|
||||
class FixedDirectoryReadToolSchema(BaseModel):
|
||||
pass # No additional parameters needed when directory is fixed
|
||||
```
|
||||
|
||||
### Dynamic Directory Mode
|
||||
```python
|
||||
class DirectoryReadToolSchema(BaseModel):
|
||||
directory: str # The path to the directory to list contents
|
||||
```
|
||||
|
||||
## Function Signatures
|
||||
|
||||
```python
|
||||
def __init__(self, directory: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
Initialize the Directory Read Tool.
|
||||
|
||||
Args:
|
||||
directory (Optional[str]): Path to the directory (optional)
|
||||
**kwargs: Additional arguments passed to BaseTool
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""
|
||||
Execute the directory listing.
|
||||
|
||||
Args:
|
||||
**kwargs: Arguments including 'directory' for dynamic mode
|
||||
|
||||
Returns:
|
||||
str: A formatted string containing all file paths in the directory
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Path Handling**:
|
||||
- Use absolute paths to avoid path resolution issues
|
||||
- Handle trailing slashes appropriately
|
||||
- Verify directory existence before listing
|
||||
|
||||
2. **Performance Considerations**:
|
||||
- Be mindful of directory size when listing large directories
|
||||
- Consider implementing pagination for large directories
|
||||
- Handle symlinks appropriately
|
||||
|
||||
3. **Error Handling**:
|
||||
- Handle directory not found errors gracefully
|
||||
- Manage permission issues appropriately
|
||||
- Validate input parameters before processing
|
||||
|
||||
## Example Integration
|
||||
|
||||
Here's a complete example showing how to integrate the Directory Read Tool with CrewAI:
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import DirectoryReadTool
|
||||
|
||||
# Initialize the tool
|
||||
dir_tool = DirectoryReadTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
file_analyst = Agent(
|
||||
role='File System Analyst',
|
||||
goal='Analyze and report on directory structures',
|
||||
backstory='I am an expert at analyzing file system organization',
|
||||
tools=[dir_tool]
|
||||
)
|
||||
|
||||
# Create tasks
|
||||
analysis_task = Task(
|
||||
description="""
|
||||
Analyze the project directory structure:
|
||||
1. List all files recursively
|
||||
2. Identify key file types
|
||||
3. Report on directory organization
|
||||
|
||||
Provide a comprehensive analysis of the findings.
|
||||
""",
|
||||
agent=file_analyst,
|
||||
context={
|
||||
"directory": "/path/to/project"
|
||||
}
|
||||
)
|
||||
|
||||
# Create and run the crew
|
||||
crew = Crew(
|
||||
agents=[file_analyst],
|
||||
tasks=[analysis_task]
|
||||
)
|
||||
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The tool handles various error scenarios:
|
||||
|
||||
1. **Directory Not Found**:
|
||||
```python
|
||||
try:
|
||||
tool = DirectoryReadTool(directory="/nonexistent/path")
|
||||
except FileNotFoundError:
|
||||
print("Directory not found. Please verify the path.")
|
||||
```
|
||||
|
||||
2. **Permission Issues**:
|
||||
```python
|
||||
try:
|
||||
tool = DirectoryReadTool(directory="/restricted/path")
|
||||
except PermissionError:
|
||||
print("Insufficient permissions to access the directory.")
|
||||
```
|
||||
|
||||
3. **Invalid Path**:
|
||||
```python
|
||||
try:
|
||||
result = tool._run(directory="invalid/path")
|
||||
except ValueError:
|
||||
print("Invalid directory path provided.")
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
The tool returns a formatted string containing all file paths in the directory:
|
||||
|
||||
```
|
||||
File paths:
|
||||
- /path/to/directory/file1.txt
|
||||
- /path/to/directory/subdirectory/file2.txt
|
||||
- /path/to/directory/subdirectory/file3.py
|
||||
```
|
||||
|
||||
|
||||
Each file path is listed on a new line with a hyphen prefix, making it easy to parse and read the output.
|
||||
@@ -1,214 +0,0 @@
|
||||
---
|
||||
title: DirectorySearchTool
|
||||
description: A tool for semantic search within directory contents using RAG capabilities
|
||||
icon: folder-search
|
||||
---
|
||||
|
||||
## DirectorySearchTool
|
||||
|
||||
The DirectorySearchTool enables semantic search capabilities for directory contents using Retrieval-Augmented Generation (RAG). It processes files recursively within a directory and allows searching through their contents using natural language queries.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import DirectorySearchTool
|
||||
|
||||
# Method 1: Initialize with specific directory
|
||||
dir_tool = DirectorySearchTool(directory="/path/to/documents")
|
||||
|
||||
# Method 2: Initialize without directory (specify at runtime)
|
||||
flexible_dir_tool = DirectorySearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Directory Researcher',
|
||||
goal='Search and analyze directory contents',
|
||||
backstory='Expert at finding relevant information in document collections.',
|
||||
tools=[dir_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed Directory Schema (when path provided during initialization)
|
||||
```python
|
||||
class FixedDirectorySearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the directory's content"
|
||||
)
|
||||
```
|
||||
|
||||
### Flexible Directory Schema (when path provided at runtime)
|
||||
```python
|
||||
class DirectorySearchToolSchema(FixedDirectorySearchToolSchema):
|
||||
directory: str = Field(
|
||||
description="Mandatory directory you want to search"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
directory: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the directory search tool.
|
||||
|
||||
Args:
|
||||
directory (Optional[str]): Path to directory (optional)
|
||||
**kwargs: Additional arguments for RAG tool configuration
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute semantic search on directory contents.
|
||||
|
||||
Args:
|
||||
search_query (str): Query to search in the directory
|
||||
**kwargs: Additional arguments including directory if not initialized
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the directory matching the query
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Directory Management:
|
||||
- Use absolute paths
|
||||
- Verify directory existence
|
||||
- Handle permissions properly
|
||||
|
||||
2. Search Optimization:
|
||||
- Use specific queries
|
||||
- Consider file types
|
||||
- Test with sample queries
|
||||
|
||||
3. Performance Considerations:
|
||||
- Pre-initialize for repeated searches
|
||||
- Handle large directories
|
||||
- Monitor processing time
|
||||
|
||||
4. Error Handling:
|
||||
- Verify directory access
|
||||
- Handle missing files
|
||||
- Manage permissions
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import DirectorySearchTool
|
||||
|
||||
# Initialize tool with specific directory
|
||||
dir_tool = DirectorySearchTool(
|
||||
directory="/path/to/documents"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Directory Researcher',
|
||||
goal='Extract insights from document collections',
|
||||
backstory='Expert at analyzing document collections.',
|
||||
tools=[dir_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Find all mentions of machine learning
|
||||
applications from the directory contents.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "machine learning applications"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Dynamic Directory Selection
|
||||
```python
|
||||
# Initialize without directory path
|
||||
flexible_tool = DirectorySearchTool()
|
||||
|
||||
# Search different directories
|
||||
docs_results = flexible_tool.run(
|
||||
search_query="technical specifications",
|
||||
directory="/path/to/docs"
|
||||
)
|
||||
|
||||
reports_results = flexible_tool.run(
|
||||
search_query="financial metrics",
|
||||
directory="/path/to/reports"
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple Directory Analysis
|
||||
```python
|
||||
# Create tools for different directories
|
||||
docs_tool = DirectorySearchTool(
|
||||
directory="/path/to/docs"
|
||||
)
|
||||
reports_tool = DirectorySearchTool(
|
||||
directory="/path/to/reports"
|
||||
)
|
||||
|
||||
# Create agent with multiple tools
|
||||
analyst = Agent(
|
||||
role='Content Analyst',
|
||||
goal='Cross-reference multiple document collections',
|
||||
tools=[docs_tool, reports_tool]
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
dir_tool = DirectorySearchTool()
|
||||
results = dir_tool.run(
|
||||
search_query="key concepts",
|
||||
directory="/path/to/documents"
|
||||
)
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"Error processing directory: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool
|
||||
- Uses DirectoryLoader
|
||||
- Supports recursive search
|
||||
- Dynamic directory specification
|
||||
- Efficient content retrieval
|
||||
- Thread-safe operations
|
||||
- Maintains search context
|
||||
- Processes multiple file types
|
||||
- Handles nested directories
|
||||
- Memory-efficient processing
|
||||
@@ -1,224 +0,0 @@
|
||||
---
|
||||
title: DOCXSearchTool
|
||||
description: A tool for semantic search within DOCX documents using RAG capabilities
|
||||
icon: file-text
|
||||
---
|
||||
|
||||
## DOCXSearchTool
|
||||
|
||||
The DOCXSearchTool enables semantic search capabilities for Microsoft Word (DOCX) documents using Retrieval-Augmented Generation (RAG). It supports both fixed and dynamic document selection modes.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import DOCXSearchTool
|
||||
|
||||
# Method 1: Fixed document (specified at initialization)
|
||||
fixed_tool = DOCXSearchTool(
|
||||
docx="path/to/document.docx"
|
||||
)
|
||||
|
||||
# Method 2: Dynamic document (specified at runtime)
|
||||
dynamic_tool = DOCXSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Document Researcher',
|
||||
goal='Search and analyze document contents',
|
||||
backstory='Expert at finding relevant information in documents.',
|
||||
tools=[fixed_tool], # or [dynamic_tool]
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed Document Mode
|
||||
```python
|
||||
class FixedDOCXSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the DOCX's content"
|
||||
)
|
||||
```
|
||||
|
||||
### Dynamic Document Mode
|
||||
```python
|
||||
class DOCXSearchToolSchema(BaseModel):
|
||||
docx: str = Field(
|
||||
description="Mandatory docx path you want to search"
|
||||
)
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the DOCX's content"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
docx: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the DOCX search tool.
|
||||
|
||||
Args:
|
||||
docx (Optional[str]): Path to DOCX file (optional for dynamic mode)
|
||||
**kwargs: Additional arguments for RAG tool configuration
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
docx: Optional[str] = None,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute semantic search on document contents.
|
||||
|
||||
Args:
|
||||
search_query (str): Query to search in the document
|
||||
docx (Optional[str]): Document path (required for dynamic mode)
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the document matching the query
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Document Handling:
|
||||
- Use absolute file paths
|
||||
- Verify file existence
|
||||
- Handle large documents
|
||||
- Monitor memory usage
|
||||
|
||||
2. Query Optimization:
|
||||
- Structure queries clearly
|
||||
- Consider document size
|
||||
- Handle formatting
|
||||
- Monitor performance
|
||||
|
||||
3. Error Handling:
|
||||
- Check file access
|
||||
- Validate file format
|
||||
- Handle corrupted files
|
||||
- Log issues
|
||||
|
||||
4. Mode Selection:
|
||||
- Choose fixed mode for static documents
|
||||
- Use dynamic mode for runtime selection
|
||||
- Consider memory implications
|
||||
- Manage document lifecycle
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import DOCXSearchTool
|
||||
|
||||
# Initialize tool
|
||||
docx_tool = DOCXSearchTool(
|
||||
docx="reports/annual_report_2023.docx"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Document Analyst',
|
||||
goal='Extract insights from annual report',
|
||||
backstory='Expert at analyzing business documents.',
|
||||
tools=[docx_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
analysis_task = Task(
|
||||
description="""Find all mentions of revenue growth
|
||||
and market expansion.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[analysis_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple Document Analysis
|
||||
```python
|
||||
# Create tools for different documents
|
||||
report_tool = DOCXSearchTool(
|
||||
docx="reports/annual_report.docx"
|
||||
)
|
||||
|
||||
policy_tool = DOCXSearchTool(
|
||||
docx="policies/compliance.docx"
|
||||
)
|
||||
|
||||
# Create agent with multiple tools
|
||||
analyst = Agent(
|
||||
role='Document Analyst',
|
||||
goal='Cross-reference reports and policies',
|
||||
tools=[report_tool, policy_tool]
|
||||
)
|
||||
```
|
||||
|
||||
### Dynamic Document Loading
|
||||
```python
|
||||
# Initialize dynamic tool
|
||||
dynamic_tool = DOCXSearchTool()
|
||||
|
||||
# Use with different documents
|
||||
result1 = dynamic_tool.run(
|
||||
docx="document1.docx",
|
||||
search_query="project timeline"
|
||||
)
|
||||
|
||||
result2 = dynamic_tool.run(
|
||||
docx="document2.docx",
|
||||
search_query="budget allocation"
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
docx_tool = DOCXSearchTool(
|
||||
docx="reports/quarterly_report.docx"
|
||||
)
|
||||
results = docx_tool.run(
|
||||
search_query="Q3 performance metrics"
|
||||
)
|
||||
print(results)
|
||||
except FileNotFoundError as e:
|
||||
print(f"Document not found: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f"Error processing document: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool
|
||||
- Supports fixed/dynamic modes
|
||||
- Document path validation
|
||||
- Memory management
|
||||
- Performance optimization
|
||||
- Error handling
|
||||
- Search capabilities
|
||||
- Content extraction
|
||||
- Format handling
|
||||
- Security features
|
||||
@@ -1,193 +0,0 @@
|
||||
---
|
||||
title: FileReadTool
|
||||
description: A tool for reading file contents with flexible path specification
|
||||
icon: file-text
|
||||
---
|
||||
|
||||
## FileReadTool
|
||||
|
||||
The FileReadTool provides functionality to read file contents with support for both fixed and dynamic file path specification. It includes comprehensive error handling for common file operations and maintains clear descriptions of its configured state.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import FileReadTool
|
||||
|
||||
# Method 1: Initialize with specific file
|
||||
reader = FileReadTool(file_path="/path/to/data.txt")
|
||||
|
||||
# Method 2: Initialize without file (specify at runtime)
|
||||
flexible_reader = FileReadTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
file_processor = Agent(
|
||||
role='File Processor',
|
||||
goal='Read and process file contents',
|
||||
backstory='Expert at handling file operations and content processing.',
|
||||
tools=[reader],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class FileReadToolSchema(BaseModel):
|
||||
file_path: str = Field(
|
||||
description="Mandatory file full path to read the file"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
file_path: Optional[str] = None,
|
||||
**kwargs: Any
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the file read tool.
|
||||
|
||||
Args:
|
||||
file_path (Optional[str]): Path to file to read (optional)
|
||||
**kwargs: Additional arguments passed to BaseTool
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Read and return file contents.
|
||||
|
||||
Args:
|
||||
file_path (str, optional): Override default file path
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
str: File contents or error message
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. File Path Management:
|
||||
- Use absolute paths for reliability
|
||||
- Verify file existence before operations
|
||||
- Handle path resolution properly
|
||||
|
||||
2. Error Handling:
|
||||
- Check for file existence
|
||||
- Handle permission issues
|
||||
- Manage encoding errors
|
||||
- Process file access failures
|
||||
|
||||
3. Performance Considerations:
|
||||
- Close files after reading
|
||||
- Handle large files appropriately
|
||||
- Consider memory constraints
|
||||
|
||||
4. Security Practices:
|
||||
- Validate file paths
|
||||
- Check file permissions
|
||||
- Avoid path traversal issues
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import FileReadTool
|
||||
|
||||
# Initialize tool with specific file
|
||||
reader = FileReadTool(file_path="/path/to/config.txt")
|
||||
|
||||
# Create agent
|
||||
processor = Agent(
|
||||
role='File Processor',
|
||||
goal='Process configuration files',
|
||||
backstory='Expert at reading and analyzing configuration files.',
|
||||
tools=[reader]
|
||||
)
|
||||
|
||||
# Define task
|
||||
read_task = Task(
|
||||
description="""Read and analyze the contents of
|
||||
the configuration file.""",
|
||||
agent=processor
|
||||
)
|
||||
|
||||
# The tool will use the default file path
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[processor],
|
||||
tasks=[read_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Dynamic File Selection
|
||||
```python
|
||||
# Initialize without file path
|
||||
flexible_reader = FileReadTool()
|
||||
|
||||
# Read different files
|
||||
config_content = flexible_reader.run(
|
||||
file_path="/path/to/config.txt"
|
||||
)
|
||||
|
||||
log_content = flexible_reader.run(
|
||||
file_path="/path/to/logs.txt"
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple File Processing
|
||||
```python
|
||||
# Create tools for different files
|
||||
config_reader = FileReadTool(file_path="/path/to/config.txt")
|
||||
log_reader = FileReadTool(file_path="/path/to/logs.txt")
|
||||
|
||||
# Create agent with multiple tools
|
||||
processor = Agent(
|
||||
role='File Analyst',
|
||||
goal='Analyze multiple file types',
|
||||
tools=[config_reader, log_reader]
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
reader = FileReadTool()
|
||||
content = reader.run(
|
||||
file_path="/path/to/file.txt"
|
||||
)
|
||||
print(content)
|
||||
except Exception as e:
|
||||
print(f"Error reading file: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from BaseTool
|
||||
- Supports fixed or dynamic file paths
|
||||
- Comprehensive error handling
|
||||
- Thread-safe operations
|
||||
- Clear error messages
|
||||
- Flexible path specification
|
||||
- Maintains tool description
|
||||
- Handles common file errors
|
||||
- Supports various file types
|
||||
- Memory-efficient operations
|
||||
@@ -1,141 +0,0 @@
|
||||
---
|
||||
title: FileWriterTool
|
||||
description: A tool for writing content to files with support for various file formats.
|
||||
icon: file-pen
|
||||
---
|
||||
|
||||
## FileWriterTool
|
||||
|
||||
The FileWriterTool provides agents with the capability to write content to files, supporting various file formats and ensuring proper file handling.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import FileWriterTool
|
||||
|
||||
# Initialize the tool
|
||||
file_writer = FileWriterTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
writer_agent = Agent(
|
||||
role='Content Writer',
|
||||
goal='Write and save content to files',
|
||||
backstory='Expert at creating and managing file content.',
|
||||
tools=[file_writer],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Use in a task
|
||||
task = Task(
|
||||
description='Write a report and save it to report.txt',
|
||||
agent=writer_agent
|
||||
)
|
||||
```
|
||||
|
||||
## Tool Attributes
|
||||
|
||||
| Attribute | Type | Description |
|
||||
| :-------- | :--- | :---------- |
|
||||
| name | str | "File Writer Tool" |
|
||||
| description | str | "A tool that writes content to a file." |
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class FileWriterToolInput(BaseModel):
|
||||
filename: str # Name of the file to write
|
||||
directory: str = "./" # Optional directory path, defaults to current directory
|
||||
overwrite: str = "False" # Whether to overwrite existing file ("True"/"False")
|
||||
content: str # Content to write to the file
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def _run(self, **kwargs: Any) -> str:
|
||||
"""
|
||||
Write content to a file with specified parameters.
|
||||
|
||||
Args:
|
||||
filename (str): Name of the file to write
|
||||
content (str): Content to write to the file
|
||||
directory (str, optional): Directory path. Defaults to "./".
|
||||
overwrite (str, optional): Whether to overwrite existing file. Defaults to "False".
|
||||
|
||||
Returns:
|
||||
str: Success message with filepath or error message
|
||||
"""
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The tool includes error handling for common file operations:
|
||||
- FileExistsError: When file exists and overwrite is not allowed
|
||||
- KeyError: When required parameters are missing
|
||||
- Directory Creation: Automatically creates directories if they don't exist
|
||||
- General Exceptions: Catches and reports any other file operation errors
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always provide absolute file paths
|
||||
2. Ensure proper file permissions
|
||||
3. Handle potential errors in your agent prompts
|
||||
4. Verify file contents after writing
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import FileWriterTool
|
||||
|
||||
# Initialize tool
|
||||
file_writer = FileWriterTool()
|
||||
|
||||
# Create agent
|
||||
writer = Agent(
|
||||
role='Technical Writer',
|
||||
goal='Create and save technical documentation',
|
||||
backstory='Expert technical writer with experience in documentation.',
|
||||
tools=[file_writer]
|
||||
)
|
||||
|
||||
# Define task
|
||||
writing_task = Task(
|
||||
description="""Write a technical guide about Python best practices and save it
|
||||
to the docs directory. The file should be named 'python_guide.md'.
|
||||
Include sections on code style, documentation, and testing.
|
||||
If a file already exists, overwrite it.""",
|
||||
agent=writer
|
||||
)
|
||||
|
||||
# The agent can use the tool with these parameters:
|
||||
# {
|
||||
# "filename": "python_guide.md",
|
||||
# "directory": "docs",
|
||||
# "overwrite": "True",
|
||||
# "content": "# Python Best Practices\n\n## Code Style\n..."
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[writer],
|
||||
tasks=[writing_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The tool automatically creates directories in the file path if they don't exist
|
||||
- Supports various file formats (txt, md, json, etc.)
|
||||
- Returns descriptive error messages for better debugging
|
||||
- Thread-safe file operations
|
||||
@@ -1,181 +0,0 @@
|
||||
---
|
||||
title: FirecrawlCrawlWebsiteTool
|
||||
description: A web crawling tool powered by Firecrawl API for comprehensive website content extraction
|
||||
icon: spider-web
|
||||
---
|
||||
|
||||
## FirecrawlCrawlWebsiteTool
|
||||
|
||||
The FirecrawlCrawlWebsiteTool provides website crawling capabilities using the Firecrawl API. It allows for customizable crawling with options for polling intervals, idempotency, and URL parameters.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
pip install firecrawl-py # Required dependency
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import FirecrawlCrawlWebsiteTool
|
||||
|
||||
# Method 1: Using environment variable
|
||||
# export FIRECRAWL_API_KEY='your-api-key'
|
||||
crawler = FirecrawlCrawlWebsiteTool()
|
||||
|
||||
# Method 2: Providing API key directly
|
||||
crawler = FirecrawlCrawlWebsiteTool(
|
||||
api_key="your-firecrawl-api-key"
|
||||
)
|
||||
|
||||
# Method 3: With custom configuration
|
||||
crawler = FirecrawlCrawlWebsiteTool(
|
||||
api_key="your-firecrawl-api-key",
|
||||
url="https://example.com", # Base URL
|
||||
poll_interval=5, # Custom polling interval
|
||||
idempotency_key="unique-key"
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Web Crawler',
|
||||
goal='Extract and analyze website content',
|
||||
backstory='Expert at crawling and analyzing web content.',
|
||||
tools=[crawler],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class FirecrawlCrawlWebsiteToolSchema(BaseModel):
|
||||
url: str = Field(description="Website URL")
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
poll_interval: Optional[int] = 2,
|
||||
idempotency_key: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the website crawling tool.
|
||||
|
||||
Args:
|
||||
api_key (Optional[str]): Firecrawl API key. If not provided, checks FIRECRAWL_API_KEY env var
|
||||
url (Optional[str]): Base URL to crawl. Can be overridden in _run
|
||||
params (Optional[Dict[str, Any]]): Additional parameters for FirecrawlApp
|
||||
poll_interval (Optional[int]): Poll interval for FirecrawlApp
|
||||
idempotency_key (Optional[str]): Idempotency key for FirecrawlApp
|
||||
**kwargs: Additional arguments for tool creation
|
||||
"""
|
||||
|
||||
def _run(self, url: str) -> Any:
|
||||
"""
|
||||
Crawl a website using Firecrawl.
|
||||
|
||||
Args:
|
||||
url (str): Website URL to crawl (overrides constructor URL if provided)
|
||||
|
||||
Returns:
|
||||
Any: Crawled website content from Firecrawl API
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Set up API authentication:
|
||||
- Use environment variable: `export FIRECRAWL_API_KEY='your-api-key'`
|
||||
- Or provide directly in constructor
|
||||
2. Configure crawling parameters:
|
||||
- Set appropriate poll intervals
|
||||
- Use idempotency keys for retry safety
|
||||
- Customize URL parameters as needed
|
||||
3. Handle rate limits and quotas
|
||||
4. Consider website robots.txt policies
|
||||
5. Handle potential crawling errors in agent prompts
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import FirecrawlCrawlWebsiteTool
|
||||
|
||||
# Initialize crawler with configuration
|
||||
crawler = FirecrawlCrawlWebsiteTool(
|
||||
api_key="your-firecrawl-api-key",
|
||||
poll_interval=5,
|
||||
params={
|
||||
"max_depth": 3,
|
||||
"follow_links": True
|
||||
}
|
||||
)
|
||||
|
||||
# Create agent
|
||||
web_analyst = Agent(
|
||||
role='Web Content Analyst',
|
||||
goal='Extract and analyze website content comprehensively',
|
||||
backstory='Expert at web crawling and content analysis.',
|
||||
tools=[crawler]
|
||||
)
|
||||
|
||||
# Define task
|
||||
crawl_task = Task(
|
||||
description="""Crawl the documentation website at docs.example.com
|
||||
and extract all API-related content.""",
|
||||
agent=web_analyst
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "url": "https://docs.example.com"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[web_analyst],
|
||||
tasks=[crawl_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### URL Parameters
|
||||
```python
|
||||
params = {
|
||||
"max_depth": 3, # Maximum crawl depth
|
||||
"follow_links": True, # Follow internal links
|
||||
"exclude_patterns": [], # URL patterns to exclude
|
||||
"include_patterns": [] # URL patterns to include
|
||||
}
|
||||
```
|
||||
|
||||
### Polling Configuration
|
||||
```python
|
||||
crawler = FirecrawlCrawlWebsiteTool(
|
||||
poll_interval=5, # Poll every 5 seconds
|
||||
idempotency_key="unique-key-123" # For retry safety
|
||||
)
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires valid Firecrawl API key
|
||||
- Supports both environment variable and direct API key configuration
|
||||
- Configurable polling intervals for crawl status
|
||||
- Idempotency support for safe retries
|
||||
- Thread-safe operations
|
||||
- Customizable crawling parameters
|
||||
- Respects robots.txt by default
|
||||
@@ -1,154 +0,0 @@
|
||||
---
|
||||
title: FirecrawlSearchTool
|
||||
description: A web search tool powered by Firecrawl API for comprehensive web search capabilities
|
||||
icon: magnifying-glass-chart
|
||||
---
|
||||
|
||||
## FirecrawlSearchTool
|
||||
|
||||
The FirecrawlSearchTool provides web search capabilities using the Firecrawl API. It allows for customizable search queries with options for result formatting and search parameters.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
pip install firecrawl-py # Required dependency
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import FirecrawlSearchTool
|
||||
|
||||
# Initialize the tool with your API key
|
||||
search_tool = FirecrawlSearchTool(api_key="your-firecrawl-api-key")
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Web Researcher',
|
||||
goal='Find relevant information across the web',
|
||||
backstory='Expert at web research and information gathering.',
|
||||
tools=[search_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class FirecrawlSearchToolSchema(BaseModel):
|
||||
query: str = Field(description="Search query")
|
||||
page_options: Optional[Dict[str, Any]] = Field(
|
||||
default=None,
|
||||
description="Options for result formatting"
|
||||
)
|
||||
search_options: Optional[Dict[str, Any]] = Field(
|
||||
default=None,
|
||||
description="Options for searching"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(self, api_key: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
Initialize the Firecrawl search tool.
|
||||
|
||||
Args:
|
||||
api_key (Optional[str]): Firecrawl API key
|
||||
**kwargs: Additional arguments for tool creation
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
page_options: Optional[Dict[str, Any]] = None,
|
||||
result_options: Optional[Dict[str, Any]] = None,
|
||||
) -> Any:
|
||||
"""
|
||||
Perform a web search using Firecrawl.
|
||||
|
||||
Args:
|
||||
query (str): Search query string
|
||||
page_options (Optional[Dict[str, Any]]): Options for result formatting
|
||||
result_options (Optional[Dict[str, Any]]): Options for search results
|
||||
|
||||
Returns:
|
||||
Any: Search results from Firecrawl API
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always provide a valid API key
|
||||
2. Use specific, focused search queries
|
||||
3. Customize page and result options for better results
|
||||
4. Handle potential API errors in agent prompts
|
||||
5. Consider rate limits and usage quotas
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import FirecrawlSearchTool
|
||||
|
||||
# Initialize tool with API key
|
||||
search_tool = FirecrawlSearchTool(api_key="your-firecrawl-api-key")
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Market Researcher',
|
||||
goal='Research market trends and competitor analysis',
|
||||
backstory='Expert market analyst with deep research skills.',
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Research the latest developments in electric vehicles,
|
||||
focusing on market leaders and emerging technologies. Format the results
|
||||
in a structured way.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "query": "electric vehicle market leaders emerging technologies",
|
||||
# "page_options": {
|
||||
# "format": "structured",
|
||||
# "maxLength": 1000
|
||||
# },
|
||||
# "result_options": {
|
||||
# "limit": 5,
|
||||
# "sortBy": "relevance"
|
||||
# }
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The tool includes error handling for:
|
||||
- Missing API key
|
||||
- Missing firecrawl-py package
|
||||
- API request failures
|
||||
- Invalid options parameters
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires valid Firecrawl API key
|
||||
- Supports customizable search parameters
|
||||
- Provides structured web search results
|
||||
- Thread-safe operations
|
||||
- Efficient for large-scale web searches
|
||||
- Handles rate limiting automatically
|
||||
@@ -1,233 +0,0 @@
|
||||
---
|
||||
title: GithubSearchTool
|
||||
description: A tool for semantic search within GitHub repositories using RAG capabilities
|
||||
icon: github
|
||||
---
|
||||
|
||||
## GithubSearchTool
|
||||
|
||||
The GithubSearchTool enables semantic search capabilities for GitHub repositories using Retrieval-Augmented Generation (RAG). It processes various content types including code, repository information, pull requests, and issues, allowing natural language queries across repository content.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import GithubSearchTool
|
||||
|
||||
# Method 1: Initialize with specific repository
|
||||
github_tool = GithubSearchTool(
|
||||
github_repo="owner/repo",
|
||||
gh_token="your_github_token",
|
||||
content_types=["code", "pr", "issue"]
|
||||
)
|
||||
|
||||
# Method 2: Initialize without repository (specify at runtime)
|
||||
flexible_github_tool = GithubSearchTool(
|
||||
gh_token="your_github_token",
|
||||
content_types=["code", "repo"]
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='GitHub Researcher',
|
||||
goal='Search and analyze repository contents',
|
||||
backstory='Expert at finding relevant information in GitHub repositories.',
|
||||
tools=[github_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed Repository Schema (when repo provided during initialization)
|
||||
```python
|
||||
class FixedGithubSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the github repo's content"
|
||||
)
|
||||
```
|
||||
|
||||
### Flexible Repository Schema (when repo provided at runtime)
|
||||
```python
|
||||
class GithubSearchToolSchema(FixedGithubSearchToolSchema):
|
||||
github_repo: str = Field(
|
||||
description="Mandatory github you want to search"
|
||||
)
|
||||
content_types: List[str] = Field(
|
||||
description="Mandatory content types you want to be included search, options: [code, repo, pr, issue]"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
github_repo: Optional[str] = None,
|
||||
gh_token: str,
|
||||
content_types: List[str],
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the GitHub search tool.
|
||||
|
||||
Args:
|
||||
github_repo (Optional[str]): Repository to search (optional)
|
||||
gh_token (str): GitHub authentication token
|
||||
content_types (List[str]): Content types to search
|
||||
**kwargs: Additional arguments for RAG tool configuration
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute semantic search on repository contents.
|
||||
|
||||
Args:
|
||||
search_query (str): Query to search in the repository
|
||||
**kwargs: Additional arguments including github_repo and content_types if not initialized
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the repository matching the query
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Authentication:
|
||||
- Secure token management
|
||||
- Use environment variables
|
||||
- Handle token expiration
|
||||
|
||||
2. Search Optimization:
|
||||
- Target specific content types
|
||||
- Use focused queries
|
||||
- Consider rate limits
|
||||
|
||||
3. Performance Considerations:
|
||||
- Pre-initialize for repeated searches
|
||||
- Handle large repositories
|
||||
- Monitor API usage
|
||||
|
||||
4. Error Handling:
|
||||
- Verify repository access
|
||||
- Handle API limits
|
||||
- Manage authentication errors
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import GithubSearchTool
|
||||
|
||||
# Initialize tool with specific repository
|
||||
github_tool = GithubSearchTool(
|
||||
github_repo="owner/repo",
|
||||
gh_token="your_github_token",
|
||||
content_types=["code", "pr", "issue"]
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='GitHub Researcher',
|
||||
goal='Extract insights from repository content',
|
||||
backstory='Expert at analyzing GitHub repositories.',
|
||||
tools=[github_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Find all implementations of
|
||||
machine learning algorithms in the codebase.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "machine learning implementation"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Dynamic Repository Selection
|
||||
```python
|
||||
# Initialize without repository
|
||||
flexible_tool = GithubSearchTool(
|
||||
gh_token="your_github_token",
|
||||
content_types=["code", "repo"]
|
||||
)
|
||||
|
||||
# Search different repositories
|
||||
backend_results = flexible_tool.run(
|
||||
search_query="authentication implementation",
|
||||
github_repo="owner/backend-repo"
|
||||
)
|
||||
|
||||
frontend_results = flexible_tool.run(
|
||||
search_query="component architecture",
|
||||
github_repo="owner/frontend-repo"
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple Content Type Analysis
|
||||
```python
|
||||
# Create tool with multiple content types
|
||||
multi_tool = GithubSearchTool(
|
||||
github_repo="owner/repo",
|
||||
gh_token="your_github_token",
|
||||
content_types=["code", "pr", "issue", "repo"]
|
||||
)
|
||||
|
||||
# Search across all content types
|
||||
results = multi_tool.run(
|
||||
search_query="feature implementation status"
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
github_tool = GithubSearchTool(
|
||||
gh_token="your_github_token",
|
||||
content_types=["code"]
|
||||
)
|
||||
results = github_tool.run(
|
||||
search_query="api endpoints",
|
||||
github_repo="owner/repo"
|
||||
)
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"Error searching repository: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool
|
||||
- Uses GithubLoader
|
||||
- Requires authentication
|
||||
- Supports multiple content types
|
||||
- Dynamic repository specification
|
||||
- Efficient content retrieval
|
||||
- Thread-safe operations
|
||||
- Maintains search context
|
||||
- Handles API rate limits
|
||||
- Memory-efficient processing
|
||||
@@ -1,220 +0,0 @@
|
||||
---
|
||||
title: JinaScrapeWebsiteTool
|
||||
description: A tool for scraping website content using Jina.ai's reader service with markdown output
|
||||
icon: globe
|
||||
---
|
||||
|
||||
## JinaScrapeWebsiteTool
|
||||
|
||||
The JinaScrapeWebsiteTool provides website content scraping capabilities using Jina.ai's reader service. It converts web content into clean markdown format and supports both fixed and dynamic URL modes with optional authentication.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import JinaScrapeWebsiteTool
|
||||
|
||||
# Method 1: Fixed URL (specified at initialization)
|
||||
fixed_tool = JinaScrapeWebsiteTool(
|
||||
website_url="https://example.com",
|
||||
api_key="your-jina-api-key" # Optional
|
||||
)
|
||||
|
||||
# Method 2: Dynamic URL (specified at runtime)
|
||||
dynamic_tool = JinaScrapeWebsiteTool(
|
||||
api_key="your-jina-api-key" # Optional
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Web Content Researcher',
|
||||
goal='Extract and analyze website content',
|
||||
backstory='Expert at gathering and processing web information.',
|
||||
tools=[fixed_tool], # or [dynamic_tool]
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class JinaScrapeWebsiteToolInput(BaseModel):
|
||||
website_url: str = Field(
|
||||
description="Mandatory website url to read the file"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
website_url: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
custom_headers: Optional[dict] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the website scraping tool.
|
||||
|
||||
Args:
|
||||
website_url (Optional[str]): URL to scrape (optional for dynamic mode)
|
||||
api_key (Optional[str]): Jina.ai API key for authentication
|
||||
custom_headers (Optional[dict]): Custom HTTP headers
|
||||
**kwargs: Additional arguments for base tool
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
website_url: Optional[str] = None
|
||||
) -> str:
|
||||
"""
|
||||
Execute website scraping.
|
||||
|
||||
Args:
|
||||
website_url (Optional[str]): URL to scrape (required for dynamic mode)
|
||||
|
||||
Returns:
|
||||
str: Markdown-formatted website content
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. URL Handling:
|
||||
- Use complete URLs
|
||||
- Validate URL format
|
||||
- Handle redirects
|
||||
- Monitor timeouts
|
||||
|
||||
2. Authentication:
|
||||
- Secure API key storage
|
||||
- Use environment variables
|
||||
- Manage headers properly
|
||||
- Handle auth errors
|
||||
|
||||
3. Content Processing:
|
||||
- Handle large pages
|
||||
- Process markdown output
|
||||
- Manage encoding
|
||||
- Handle errors
|
||||
|
||||
4. Mode Selection:
|
||||
- Choose fixed mode for static sites
|
||||
- Use dynamic mode for variable URLs
|
||||
- Consider caching
|
||||
- Manage timeouts
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import JinaScrapeWebsiteTool
|
||||
import os
|
||||
|
||||
# Initialize tool with API key
|
||||
scraper_tool = JinaScrapeWebsiteTool(
|
||||
api_key=os.getenv('JINA_API_KEY'),
|
||||
custom_headers={
|
||||
'User-Agent': 'CrewAI Bot 1.0'
|
||||
}
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Web Content Analyst',
|
||||
goal='Extract and analyze website content',
|
||||
backstory='Expert at processing web information.',
|
||||
tools=[scraper_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
analysis_task = Task(
|
||||
description="""Analyze the content of
|
||||
https://example.com/blog for key insights.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[analysis_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple Site Analysis
|
||||
```python
|
||||
# Initialize tool
|
||||
scraper = JinaScrapeWebsiteTool(
|
||||
api_key=os.getenv('JINA_API_KEY')
|
||||
)
|
||||
|
||||
# Analyze multiple sites
|
||||
results = []
|
||||
sites = [
|
||||
"https://site1.com",
|
||||
"https://site2.com",
|
||||
"https://site3.com"
|
||||
]
|
||||
|
||||
for site in sites:
|
||||
content = scraper.run(
|
||||
website_url=site
|
||||
)
|
||||
results.append(content)
|
||||
```
|
||||
|
||||
### Custom Headers Configuration
|
||||
```python
|
||||
# Initialize with custom headers
|
||||
tool = JinaScrapeWebsiteTool(
|
||||
custom_headers={
|
||||
'User-Agent': 'Custom Bot 1.0',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
'Accept': 'text/html,application/xhtml+xml'
|
||||
}
|
||||
)
|
||||
|
||||
# Use the tool
|
||||
content = tool.run(
|
||||
website_url="https://example.com"
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
scraper = JinaScrapeWebsiteTool()
|
||||
content = scraper.run(
|
||||
website_url="https://example.com"
|
||||
)
|
||||
print(content)
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error accessing website: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f"Error processing content: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Uses Jina.ai reader service
|
||||
- Markdown output format
|
||||
- API key authentication
|
||||
- Custom headers support
|
||||
- Error handling
|
||||
- Timeout management
|
||||
- Content processing
|
||||
- URL validation
|
||||
- Redirect handling
|
||||
- Response formatting
|
||||
@@ -1,224 +0,0 @@
|
||||
---
|
||||
title: JSONSearchTool
|
||||
description: A tool for semantic search within JSON files using RAG capabilities
|
||||
icon: braces
|
||||
---
|
||||
|
||||
## JSONSearchTool
|
||||
|
||||
The JSONSearchTool enables semantic search capabilities for JSON files using Retrieval-Augmented Generation (RAG). It supports both fixed and dynamic file path modes, allowing flexible usage patterns.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import JSONSearchTool
|
||||
|
||||
# Method 1: Fixed path (specified at initialization)
|
||||
fixed_tool = JSONSearchTool(
|
||||
json_path="path/to/data.json"
|
||||
)
|
||||
|
||||
# Method 2: Dynamic path (specified at runtime)
|
||||
dynamic_tool = JSONSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='JSON Data Researcher',
|
||||
goal='Search and analyze JSON data',
|
||||
backstory='Expert at finding relevant information in JSON files.',
|
||||
tools=[fixed_tool], # or [dynamic_tool]
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed Path Mode
|
||||
```python
|
||||
class FixedJSONSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the JSON's content"
|
||||
)
|
||||
```
|
||||
|
||||
### Dynamic Path Mode
|
||||
```python
|
||||
class JSONSearchToolSchema(BaseModel):
|
||||
json_path: str = Field(
|
||||
description="Mandatory json path you want to search"
|
||||
)
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the JSON's content"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
json_path: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the JSON search tool.
|
||||
|
||||
Args:
|
||||
json_path (Optional[str]): Path to JSON file (optional for dynamic mode)
|
||||
**kwargs: Additional arguments for RAG tool configuration
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute semantic search on JSON contents.
|
||||
|
||||
Args:
|
||||
search_query (str): Query to search in the JSON
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the JSON matching the query
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. File Handling:
|
||||
- Use absolute file paths
|
||||
- Verify file existence
|
||||
- Handle large JSON files
|
||||
- Monitor memory usage
|
||||
|
||||
2. Query Optimization:
|
||||
- Structure queries clearly
|
||||
- Consider JSON structure
|
||||
- Handle nested data
|
||||
- Monitor performance
|
||||
|
||||
3. Error Handling:
|
||||
- Check file access
|
||||
- Validate JSON format
|
||||
- Handle malformed JSON
|
||||
- Log issues
|
||||
|
||||
4. Mode Selection:
|
||||
- Choose fixed mode for static files
|
||||
- Use dynamic mode for runtime selection
|
||||
- Consider caching
|
||||
- Manage file lifecycle
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import JSONSearchTool
|
||||
|
||||
# Initialize tool
|
||||
json_tool = JSONSearchTool(
|
||||
json_path="data/config.json"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='JSON Data Analyst',
|
||||
goal='Extract insights from JSON configuration',
|
||||
backstory='Expert at analyzing JSON data structures.',
|
||||
tools=[json_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
analysis_task = Task(
|
||||
description="""Find all configuration settings
|
||||
related to security.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[analysis_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple File Analysis
|
||||
```python
|
||||
# Create tools for different JSON files
|
||||
config_tool = JSONSearchTool(
|
||||
json_path="config/settings.json"
|
||||
)
|
||||
|
||||
data_tool = JSONSearchTool(
|
||||
json_path="data/records.json"
|
||||
)
|
||||
|
||||
# Create agent with multiple tools
|
||||
analyst = Agent(
|
||||
role='JSON Data Analyst',
|
||||
goal='Cross-reference configuration and data',
|
||||
tools=[config_tool, data_tool]
|
||||
)
|
||||
```
|
||||
|
||||
### Dynamic File Loading
|
||||
```python
|
||||
# Initialize dynamic tool
|
||||
dynamic_tool = JSONSearchTool()
|
||||
|
||||
# Use with different JSON files
|
||||
result1 = dynamic_tool.run(
|
||||
json_path="file1.json",
|
||||
search_query="security settings"
|
||||
)
|
||||
|
||||
result2 = dynamic_tool.run(
|
||||
json_path="file2.json",
|
||||
search_query="user preferences"
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
json_tool = JSONSearchTool(
|
||||
json_path="config/settings.json"
|
||||
)
|
||||
results = json_tool.run(
|
||||
search_query="encryption settings"
|
||||
)
|
||||
print(results)
|
||||
except FileNotFoundError as e:
|
||||
print(f"JSON file not found: {str(e)}")
|
||||
except ValueError as e:
|
||||
print(f"Invalid JSON format: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f"Error processing JSON: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool
|
||||
- Supports fixed/dynamic modes
|
||||
- JSON path validation
|
||||
- Memory management
|
||||
- Performance optimization
|
||||
- Error handling
|
||||
- Search capabilities
|
||||
- Content extraction
|
||||
- Format validation
|
||||
- Security features
|
||||
@@ -1,184 +0,0 @@
|
||||
---
|
||||
title: LinkupSearchTool
|
||||
description: A search tool powered by Linkup API for retrieving contextual information
|
||||
icon: search
|
||||
---
|
||||
|
||||
## LinkupSearchTool
|
||||
|
||||
The LinkupSearchTool provides search capabilities using the Linkup API. It allows for customizable search depth and output formatting, returning structured results with contextual information.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
pip install linkup # Required dependency
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import LinkupSearchTool
|
||||
|
||||
# Initialize the tool with your API key
|
||||
search_tool = LinkupSearchTool(api_key="your-linkup-api-key")
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Information Researcher',
|
||||
goal='Find relevant contextual information',
|
||||
backstory='Expert at retrieving and analyzing contextual data.',
|
||||
tools=[search_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(self, api_key: str):
|
||||
"""
|
||||
Initialize the Linkup search tool.
|
||||
|
||||
Args:
|
||||
api_key (str): Linkup API key for authentication
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
depth: str = "standard",
|
||||
output_type: str = "searchResults"
|
||||
) -> dict:
|
||||
"""
|
||||
Perform a search using the Linkup API.
|
||||
|
||||
Args:
|
||||
query (str): The search query
|
||||
depth (str): Search depth ("standard" by default)
|
||||
output_type (str): Desired result type ("searchResults" by default)
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"success": bool,
|
||||
"results": List[Dict] | None,
|
||||
"error": str | None
|
||||
}
|
||||
|
||||
On success, results contains list of:
|
||||
{
|
||||
"name": str,
|
||||
"url": str,
|
||||
"content": str
|
||||
}
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always provide a valid API key
|
||||
2. Use specific, focused search queries
|
||||
3. Choose appropriate search depth based on needs
|
||||
4. Handle potential API errors in agent prompts
|
||||
5. Process structured results effectively
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import LinkupSearchTool
|
||||
|
||||
# Initialize tool with API key
|
||||
search_tool = LinkupSearchTool(api_key="your-linkup-api-key")
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Context Researcher',
|
||||
goal='Find detailed contextual information about topics',
|
||||
backstory='Expert at discovering and analyzing contextual data.',
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Research the latest developments in quantum computing,
|
||||
focusing on recent breakthroughs and applications. Use standard depth
|
||||
for comprehensive results.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# query: "quantum computing recent breakthroughs applications"
|
||||
# depth: "standard"
|
||||
# output_type: "searchResults"
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Search Depth Options
|
||||
```python
|
||||
# Quick surface-level search
|
||||
results = search_tool._run(
|
||||
query="quantum computing",
|
||||
depth="basic"
|
||||
)
|
||||
|
||||
# Standard comprehensive search
|
||||
results = search_tool._run(
|
||||
query="quantum computing",
|
||||
depth="standard"
|
||||
)
|
||||
|
||||
# Deep detailed search
|
||||
results = search_tool._run(
|
||||
query="quantum computing",
|
||||
depth="deep"
|
||||
)
|
||||
```
|
||||
|
||||
### Output Type Options
|
||||
```python
|
||||
# Default search results
|
||||
results = search_tool._run(
|
||||
query="quantum computing",
|
||||
output_type="searchResults"
|
||||
)
|
||||
|
||||
# Custom output format
|
||||
results = search_tool._run(
|
||||
query="quantum computing",
|
||||
output_type="customFormat"
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```python
|
||||
results = search_tool._run(query="quantum computing")
|
||||
if results["success"]:
|
||||
for result in results["results"]:
|
||||
print(f"Name: {result['name']}")
|
||||
print(f"URL: {result['url']}")
|
||||
print(f"Content: {result['content']}")
|
||||
else:
|
||||
print(f"Error: {results['error']}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires valid Linkup API key
|
||||
- Returns structured search results
|
||||
- Supports multiple search depths
|
||||
- Configurable output formats
|
||||
- Built-in error handling
|
||||
- Thread-safe operations
|
||||
- Efficient for contextual searches
|
||||
@@ -1,192 +0,0 @@
|
||||
---
|
||||
title: LlamaIndexTool
|
||||
description: A wrapper tool for integrating LlamaIndex tools and query engines with CrewAI
|
||||
icon: link
|
||||
---
|
||||
|
||||
## LlamaIndexTool
|
||||
|
||||
The LlamaIndexTool serves as a bridge between CrewAI and LlamaIndex, allowing you to use LlamaIndex tools and query engines within your CrewAI agents. It supports both direct tool wrapping and query engine integration.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
pip install llama-index # Required for LlamaIndex integration
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Using with LlamaIndex Tools
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import LlamaIndexTool
|
||||
from llama_index.core.tools import BaseTool as LlamaBaseTool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Create a LlamaIndex tool
|
||||
class CustomLlamaSchema(BaseModel):
|
||||
query: str = Field(..., description="Query to process")
|
||||
|
||||
class CustomLlamaTool(LlamaBaseTool):
|
||||
name = "Custom Llama Tool"
|
||||
description = "A custom LlamaIndex tool"
|
||||
|
||||
def __call__(self, query: str) -> str:
|
||||
return f"Processed: {query}"
|
||||
|
||||
# Wrap the LlamaIndex tool
|
||||
llama_tool = CustomLlamaTool()
|
||||
wrapped_tool = LlamaIndexTool.from_tool(llama_tool)
|
||||
|
||||
# Create an agent with the tool
|
||||
agent = Agent(
|
||||
role='LlamaIndex Integration Agent',
|
||||
goal='Process queries using LlamaIndex tools',
|
||||
backstory='Specialist in integrating LlamaIndex capabilities.',
|
||||
tools=[wrapped_tool]
|
||||
)
|
||||
```
|
||||
|
||||
### Using with Query Engines
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import LlamaIndexTool
|
||||
from llama_index.core import VectorStoreIndex, Document
|
||||
|
||||
# Create a query engine
|
||||
documents = [Document(text="Sample document content")]
|
||||
index = VectorStoreIndex.from_documents(documents)
|
||||
query_engine = index.as_query_engine()
|
||||
|
||||
# Create the tool
|
||||
query_tool = LlamaIndexTool.from_query_engine(
|
||||
query_engine,
|
||||
name="Document Search",
|
||||
description="Search through indexed documents"
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
agent = Agent(
|
||||
role='Document Researcher',
|
||||
goal='Find relevant information in documents',
|
||||
backstory='Expert at searching through document collections.',
|
||||
tools=[query_tool]
|
||||
)
|
||||
```
|
||||
|
||||
## Tool Creation Methods
|
||||
|
||||
### From LlamaIndex Tool
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def from_tool(cls, tool: Any, **kwargs: Any) -> "LlamaIndexTool":
|
||||
"""
|
||||
Create a CrewAI tool from a LlamaIndex tool.
|
||||
|
||||
Args:
|
||||
tool (LlamaBaseTool): A LlamaIndex tool to wrap
|
||||
**kwargs: Additional arguments for tool creation
|
||||
|
||||
Returns:
|
||||
LlamaIndexTool: A CrewAI-compatible tool wrapper
|
||||
|
||||
Raises:
|
||||
ValueError: If tool is not a LlamaBaseTool or lacks fn_schema
|
||||
"""
|
||||
```
|
||||
|
||||
### From Query Engine
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def from_query_engine(
|
||||
cls,
|
||||
query_engine: Any,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
return_direct: bool = False,
|
||||
**kwargs: Any
|
||||
) -> "LlamaIndexTool":
|
||||
"""
|
||||
Create a CrewAI tool from a LlamaIndex query engine.
|
||||
|
||||
Args:
|
||||
query_engine (BaseQueryEngine): The query engine to wrap
|
||||
name (Optional[str]): Custom name for the tool
|
||||
description (Optional[str]): Custom description
|
||||
return_direct (bool): Whether to return query engine response directly
|
||||
**kwargs: Additional arguments for tool creation
|
||||
|
||||
Returns:
|
||||
LlamaIndexTool: A CrewAI-compatible tool wrapper
|
||||
|
||||
Raises:
|
||||
ValueError: If query_engine is not a BaseQueryEngine
|
||||
"""
|
||||
```
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import LlamaIndexTool
|
||||
from llama_index.core import VectorStoreIndex, Document
|
||||
from llama_index.core.tools import QueryEngineTool
|
||||
|
||||
# Create documents and index
|
||||
documents = [
|
||||
Document(text="AI is a technology that simulates human intelligence."),
|
||||
Document(text="Machine learning is a subset of AI.")
|
||||
]
|
||||
index = VectorStoreIndex.from_documents(documents)
|
||||
query_engine = index.as_query_engine()
|
||||
|
||||
# Create the tool
|
||||
search_tool = LlamaIndexTool.from_query_engine(
|
||||
query_engine,
|
||||
name="AI Knowledge Base",
|
||||
description="Search through AI-related documents"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='AI Researcher',
|
||||
goal='Research AI concepts',
|
||||
backstory='Expert at finding and explaining AI concepts.',
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Find and explain what AI is and its relationship
|
||||
with machine learning.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "query": "What is AI and how does it relate to machine learning?"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Automatically adapts LlamaIndex tool schemas for CrewAI compatibility
|
||||
- Renames 'input' parameter to 'query' for better integration
|
||||
- Supports both direct tool wrapping and query engine integration
|
||||
- Handles schema validation and error resolution
|
||||
- Thread-safe operations
|
||||
- Compatible with all LlamaIndex tool types and query engines
|
||||
@@ -1,209 +0,0 @@
|
||||
---
|
||||
title: MDX Search Tool
|
||||
description: A tool for semantic searching within MDX files using RAG capabilities
|
||||
---
|
||||
|
||||
# MDX Search Tool
|
||||
|
||||
The MDX Search Tool enables semantic searching within MDX (Markdown with JSX) files using Retrieval-Augmented Generation (RAG) capabilities. It supports both fixed and dynamic file path modes, allowing you to specify the MDX file at initialization or runtime.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You can use the MDX Search Tool in two ways:
|
||||
|
||||
### 1. Fixed MDX File Path
|
||||
|
||||
Initialize the tool with a specific MDX file path:
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import MDXSearchTool
|
||||
|
||||
# Initialize with a fixed MDX file
|
||||
tool = MDXSearchTool(mdx="/path/to/your/document.mdx")
|
||||
|
||||
# Create an agent with the tool
|
||||
agent = Agent(
|
||||
role='Technical Writer',
|
||||
goal='Search through MDX documentation',
|
||||
backstory='I help find relevant information in MDX documentation',
|
||||
tools=[tool]
|
||||
)
|
||||
|
||||
# Use in a task
|
||||
task = Task(
|
||||
description="Find information about API endpoints in the documentation",
|
||||
agent=agent
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Dynamic MDX File Path
|
||||
|
||||
Initialize the tool without a specific file path to provide it at runtime:
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import MDXSearchTool
|
||||
|
||||
# Initialize without a fixed MDX file
|
||||
tool = MDXSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
agent = Agent(
|
||||
role='Documentation Analyst',
|
||||
goal='Search through various MDX files',
|
||||
backstory='I analyze different MDX documentation files',
|
||||
tools=[tool]
|
||||
)
|
||||
|
||||
# Use in a task with dynamic file path
|
||||
task = Task(
|
||||
description="Search for 'authentication' in the API documentation",
|
||||
agent=agent,
|
||||
context={
|
||||
"mdx": "/path/to/api-docs.mdx",
|
||||
"search_query": "authentication"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed MDX File Mode
|
||||
```python
|
||||
class FixedMDXSearchToolSchema(BaseModel):
|
||||
search_query: str # The search query to find content in the MDX file
|
||||
```
|
||||
|
||||
### Dynamic MDX File Mode
|
||||
```python
|
||||
class MDXSearchToolSchema(BaseModel):
|
||||
search_query: str # The search query to find content in the MDX file
|
||||
mdx: str # The path to the MDX file to search
|
||||
```
|
||||
|
||||
## Function Signatures
|
||||
|
||||
```python
|
||||
def __init__(self, mdx: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
Initialize the MDX Search Tool.
|
||||
|
||||
Args:
|
||||
mdx (Optional[str]): Path to the MDX file (optional)
|
||||
**kwargs: Additional arguments passed to RagTool
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""
|
||||
Execute the search on the MDX file.
|
||||
|
||||
Args:
|
||||
search_query (str): The query to search for
|
||||
**kwargs: Additional arguments including 'mdx' for dynamic mode
|
||||
|
||||
Returns:
|
||||
str: The search results from the MDX content
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **File Path Handling**:
|
||||
- Use absolute paths to avoid path resolution issues
|
||||
- Verify file existence before searching
|
||||
- Handle file permissions appropriately
|
||||
|
||||
2. **Query Optimization**:
|
||||
- Use specific, focused search queries
|
||||
- Consider context when formulating queries
|
||||
- Break down complex searches into smaller queries
|
||||
|
||||
3. **Error Handling**:
|
||||
- Handle file not found errors gracefully
|
||||
- Manage permission issues appropriately
|
||||
- Validate input parameters before processing
|
||||
|
||||
## Example Integration
|
||||
|
||||
Here's a complete example showing how to integrate the MDX Search Tool with CrewAI:
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import MDXSearchTool
|
||||
|
||||
# Initialize the tool
|
||||
mdx_tool = MDXSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Documentation Researcher',
|
||||
goal='Find and analyze information in MDX documentation',
|
||||
backstory='I am an expert at finding relevant information in documentation',
|
||||
tools=[mdx_tool]
|
||||
)
|
||||
|
||||
# Create tasks
|
||||
search_task = Task(
|
||||
description="""
|
||||
Search through the API documentation for information about authentication methods.
|
||||
Look for:
|
||||
1. Authentication endpoints
|
||||
2. Security best practices
|
||||
3. Token handling
|
||||
|
||||
Provide a comprehensive summary of the findings.
|
||||
""",
|
||||
agent=researcher,
|
||||
context={
|
||||
"mdx": "/path/to/api-docs.mdx",
|
||||
"search_query": "authentication security tokens"
|
||||
}
|
||||
)
|
||||
|
||||
# Create and run the crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[search_task]
|
||||
)
|
||||
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The tool handles various error scenarios:
|
||||
|
||||
1. **File Not Found**:
|
||||
```python
|
||||
try:
|
||||
tool = MDXSearchTool(mdx="/path/to/nonexistent.mdx")
|
||||
except FileNotFoundError:
|
||||
print("MDX file not found. Please verify the file path.")
|
||||
```
|
||||
|
||||
2. **Permission Issues**:
|
||||
```python
|
||||
try:
|
||||
tool = MDXSearchTool(mdx="/restricted/docs.mdx")
|
||||
except PermissionError:
|
||||
print("Insufficient permissions to access the MDX file.")
|
||||
```
|
||||
|
||||
3. **Invalid Content**:
|
||||
```python
|
||||
try:
|
||||
result = tool._run(search_query="query", mdx="/path/to/invalid.mdx")
|
||||
except ValueError:
|
||||
print("Invalid MDX content or format.")
|
||||
```
|
||||
@@ -1,217 +0,0 @@
|
||||
---
|
||||
title: MySQLSearchTool
|
||||
description: A tool for semantic search within MySQL database tables using RAG capabilities
|
||||
icon: database
|
||||
---
|
||||
|
||||
## MySQLSearchTool
|
||||
|
||||
The MySQLSearchTool enables semantic search capabilities for MySQL database tables using Retrieval-Augmented Generation (RAG). It processes table contents and allows natural language queries to search through the data.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import MySQLSearchTool
|
||||
|
||||
# Initialize the tool
|
||||
mysql_tool = MySQLSearchTool(
|
||||
table_name="users",
|
||||
db_uri="mysql://user:pass@localhost:3306/database"
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Database Researcher',
|
||||
goal='Search and analyze database contents',
|
||||
backstory='Expert at finding relevant information in databases.',
|
||||
tools=[mysql_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class MySQLSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory semantic search query you want to use to search the database's content"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
table_name: str,
|
||||
db_uri: str,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the MySQL search tool.
|
||||
|
||||
Args:
|
||||
table_name (str): Name of the table to search
|
||||
db_uri (str): Database connection URI
|
||||
**kwargs: Additional arguments for RAG tool configuration
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute semantic search on table contents.
|
||||
|
||||
Args:
|
||||
search_query (str): Query to search in the table
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the table matching the query
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Database Connection:
|
||||
- Use secure connection URIs
|
||||
- Handle authentication properly
|
||||
- Manage connection lifecycle
|
||||
- Monitor timeouts
|
||||
|
||||
2. Query Optimization:
|
||||
- Structure queries clearly
|
||||
- Consider table size
|
||||
- Handle large datasets
|
||||
- Monitor performance
|
||||
|
||||
3. Security Considerations:
|
||||
- Protect credentials
|
||||
- Use environment variables
|
||||
- Limit table access
|
||||
- Validate inputs
|
||||
|
||||
4. Error Handling:
|
||||
- Handle connection errors
|
||||
- Manage query timeouts
|
||||
- Provide clear messages
|
||||
- Log issues
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import MySQLSearchTool
|
||||
|
||||
# Initialize tool
|
||||
mysql_tool = MySQLSearchTool(
|
||||
table_name="customers",
|
||||
db_uri="mysql://user:pass@localhost:3306/crm"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Database Analyst',
|
||||
goal='Extract customer insights from database',
|
||||
backstory='Expert at analyzing customer data.',
|
||||
tools=[mysql_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
analysis_task = Task(
|
||||
description="""Find all premium customers
|
||||
with recent purchases.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "premium customers recent purchases"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[analysis_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple Table Analysis
|
||||
```python
|
||||
# Create tools for different tables
|
||||
customers_tool = MySQLSearchTool(
|
||||
table_name="customers",
|
||||
db_uri="mysql://user:pass@localhost:3306/crm"
|
||||
)
|
||||
|
||||
orders_tool = MySQLSearchTool(
|
||||
table_name="orders",
|
||||
db_uri="mysql://user:pass@localhost:3306/crm"
|
||||
)
|
||||
|
||||
# Create agent with multiple tools
|
||||
analyst = Agent(
|
||||
role='Data Analyst',
|
||||
goal='Cross-reference customer and order data',
|
||||
tools=[customers_tool, orders_tool]
|
||||
)
|
||||
```
|
||||
|
||||
### Secure Connection Configuration
|
||||
```python
|
||||
import os
|
||||
|
||||
# Use environment variables for credentials
|
||||
db_uri = (
|
||||
f"mysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}"
|
||||
f"@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}"
|
||||
f"/{os.getenv('DB_NAME')}"
|
||||
)
|
||||
|
||||
tool = MySQLSearchTool(
|
||||
table_name="sensitive_data",
|
||||
db_uri=db_uri
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
mysql_tool = MySQLSearchTool(
|
||||
table_name="users",
|
||||
db_uri="mysql://user:pass@localhost:3306/app"
|
||||
)
|
||||
results = mysql_tool.run(
|
||||
search_query="active users in California"
|
||||
)
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"Error querying database: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool
|
||||
- Uses MySQLLoader
|
||||
- Requires database URI
|
||||
- Table-specific search
|
||||
- Semantic query support
|
||||
- Connection management
|
||||
- Error handling
|
||||
- Performance optimization
|
||||
- Security features
|
||||
- Memory efficiency
|
||||
@@ -1,208 +0,0 @@
|
||||
---
|
||||
title: PDFSearchTool
|
||||
description: A tool for semantic search within PDF documents using RAG capabilities
|
||||
icon: file-search
|
||||
---
|
||||
|
||||
## PDFSearchTool
|
||||
|
||||
The PDFSearchTool enables semantic search capabilities for PDF documents using Retrieval-Augmented Generation (RAG). It leverages embedchain's PDFEmbedchainAdapter for efficient PDF processing and supports both fixed and dynamic PDF path specification.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import PDFSearchTool
|
||||
|
||||
# Method 1: Initialize with specific PDF
|
||||
pdf_tool = PDFSearchTool(pdf="/path/to/document.pdf")
|
||||
|
||||
# Method 2: Initialize without PDF (specify at runtime)
|
||||
flexible_pdf_tool = PDFSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='PDF Researcher',
|
||||
goal='Search and analyze PDF documents',
|
||||
backstory='Expert at finding relevant information in PDFs.',
|
||||
tools=[pdf_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed PDF Schema (when PDF path provided during initialization)
|
||||
```python
|
||||
class FixedPDFSearchToolSchema(BaseModel):
|
||||
query: str = Field(
|
||||
description="Mandatory query you want to use to search the PDF's content"
|
||||
)
|
||||
```
|
||||
|
||||
### Flexible PDF Schema (when PDF path provided at runtime)
|
||||
```python
|
||||
class PDFSearchToolSchema(FixedPDFSearchToolSchema):
|
||||
pdf: str = Field(
|
||||
description="Mandatory pdf path you want to search"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
pdf: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the PDF search tool.
|
||||
|
||||
Args:
|
||||
pdf (Optional[str]): Path to PDF file (optional)
|
||||
**kwargs: Additional arguments for RAG tool configuration
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute semantic search on PDF content.
|
||||
|
||||
Args:
|
||||
query (str): Search query for the PDF
|
||||
**kwargs: Additional arguments including pdf path if not initialized
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the PDF matching the query
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. PDF File Handling:
|
||||
- Use absolute paths for reliability
|
||||
- Verify PDF file existence
|
||||
- Handle large PDFs appropriately
|
||||
|
||||
2. Search Optimization:
|
||||
- Use specific, focused queries
|
||||
- Consider document structure
|
||||
- Test with sample queries first
|
||||
|
||||
3. Performance Considerations:
|
||||
- Pre-initialize with PDF for repeated searches
|
||||
- Handle large documents efficiently
|
||||
- Monitor memory usage
|
||||
|
||||
4. Error Handling:
|
||||
- Verify PDF file existence
|
||||
- Handle malformed PDFs
|
||||
- Manage file access permissions
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import PDFSearchTool
|
||||
|
||||
# Initialize tool with specific PDF
|
||||
pdf_tool = PDFSearchTool(pdf="/path/to/research.pdf")
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='PDF Researcher',
|
||||
goal='Extract insights from research papers',
|
||||
backstory='Expert at analyzing research documents.',
|
||||
tools=[pdf_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Find all mentions of machine learning
|
||||
applications in healthcare from the PDF.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "query": "machine learning applications healthcare"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Dynamic PDF Selection
|
||||
```python
|
||||
# Initialize without PDF
|
||||
flexible_tool = PDFSearchTool()
|
||||
|
||||
# Search different PDFs
|
||||
research_results = flexible_tool.run(
|
||||
query="quantum computing",
|
||||
pdf="/path/to/research.pdf"
|
||||
)
|
||||
|
||||
report_results = flexible_tool.run(
|
||||
query="financial metrics",
|
||||
pdf="/path/to/report.pdf"
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple PDF Analysis
|
||||
```python
|
||||
# Create tools for different PDFs
|
||||
research_tool = PDFSearchTool(pdf="/path/to/research.pdf")
|
||||
report_tool = PDFSearchTool(pdf="/path/to/report.pdf")
|
||||
|
||||
# Create agent with multiple tools
|
||||
analyst = Agent(
|
||||
role='Document Analyst',
|
||||
goal='Cross-reference multiple documents',
|
||||
tools=[research_tool, report_tool]
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
pdf_tool = PDFSearchTool()
|
||||
results = pdf_tool.run(
|
||||
query="important findings",
|
||||
pdf="/path/to/document.pdf"
|
||||
)
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"Error processing PDF: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool
|
||||
- Uses PDFEmbedchainAdapter
|
||||
- Supports semantic search
|
||||
- Dynamic PDF specification
|
||||
- Efficient content retrieval
|
||||
- Thread-safe operations
|
||||
- Maintains search context
|
||||
- Handles large documents
|
||||
- Supports various PDF formats
|
||||
- Memory-efficient processing
|
||||
@@ -1,234 +0,0 @@
|
||||
---
|
||||
title: PDFTextWritingTool
|
||||
description: A tool for adding text to specific positions in PDF documents with custom font support
|
||||
icon: file-pdf
|
||||
---
|
||||
|
||||
## PDFTextWritingTool
|
||||
|
||||
The PDFTextWritingTool allows you to add text to specific positions in PDF documents with support for custom fonts, colors, and positioning. It's particularly useful for adding annotations, watermarks, or any text overlay to existing PDFs.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import PDFTextWritingTool
|
||||
|
||||
# Basic initialization
|
||||
pdf_tool = PDFTextWritingTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
document_processor = Agent(
|
||||
role='Document Processor',
|
||||
goal='Add text annotations to PDF documents',
|
||||
backstory='Expert at PDF document processing and text manipulation.',
|
||||
tools=[pdf_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class PDFTextWritingToolSchema(BaseModel):
|
||||
pdf_path: str = Field(
|
||||
description="Path to the PDF file to modify"
|
||||
)
|
||||
text: str = Field(
|
||||
description="Text to add to the PDF"
|
||||
)
|
||||
position: tuple = Field(
|
||||
description="Tuple of (x, y) coordinates for text placement"
|
||||
)
|
||||
font_size: int = Field(
|
||||
default=12,
|
||||
description="Font size of the text"
|
||||
)
|
||||
font_color: str = Field(
|
||||
default="0 0 0 rg",
|
||||
description="RGB color code for the text"
|
||||
)
|
||||
font_name: Optional[str] = Field(
|
||||
default="F1",
|
||||
description="Font name for standard fonts"
|
||||
)
|
||||
font_file: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Path to a .ttf font file for custom font usage"
|
||||
)
|
||||
page_number: int = Field(
|
||||
default=0,
|
||||
description="Page number to add text to"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def run(
|
||||
self,
|
||||
pdf_path: str,
|
||||
text: str,
|
||||
position: tuple,
|
||||
font_size: int,
|
||||
font_color: str,
|
||||
font_name: str = "F1",
|
||||
font_file: Optional[str] = None,
|
||||
page_number: int = 0,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""
|
||||
Add text to a specific position in a PDF document.
|
||||
|
||||
Args:
|
||||
pdf_path (str): Path to the PDF file to modify
|
||||
text (str): Text to add to the PDF
|
||||
position (tuple): (x, y) coordinates for text placement
|
||||
font_size (int): Font size of the text
|
||||
font_color (str): RGB color code for the text (e.g., "0 0 0 rg" for black)
|
||||
font_name (str, optional): Font name for standard fonts (default: "F1")
|
||||
font_file (str, optional): Path to a .ttf font file for custom font
|
||||
page_number (int, optional): Page number to add text to (default: 0)
|
||||
|
||||
Returns:
|
||||
str: Success message with output file path
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. File Handling:
|
||||
- Ensure PDF files exist before processing
|
||||
- Use absolute paths for reliability
|
||||
- Handle file permissions appropriately
|
||||
|
||||
2. Text Positioning:
|
||||
- Use appropriate coordinates based on PDF dimensions
|
||||
- Consider page orientation and margins
|
||||
- Test positioning with small changes first
|
||||
|
||||
3. Font Usage:
|
||||
- Verify custom font files exist
|
||||
- Use standard fonts when possible
|
||||
- Test font rendering before production use
|
||||
|
||||
4. Error Handling:
|
||||
- Check page numbers are valid
|
||||
- Verify font file accessibility
|
||||
- Handle file writing permissions
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import PDFTextWritingTool
|
||||
|
||||
# Initialize tool
|
||||
pdf_tool = PDFTextWritingTool()
|
||||
|
||||
# Create agent
|
||||
document_processor = Agent(
|
||||
role='Document Processor',
|
||||
goal='Process and annotate PDF documents',
|
||||
backstory='Expert at PDF manipulation and text placement.',
|
||||
tools=[pdf_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
annotation_task = Task(
|
||||
description="""Add a watermark saying 'CONFIDENTIAL' to
|
||||
the center of the first page of the document at
|
||||
'/path/to/document.pdf'.""",
|
||||
agent=document_processor
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "pdf_path": "/path/to/document.pdf",
|
||||
# "text": "CONFIDENTIAL",
|
||||
# "position": (300, 400),
|
||||
# "font_size": 24,
|
||||
# "font_color": "1 0 0 rg", # Red color
|
||||
# "page_number": 0
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[document_processor],
|
||||
tasks=[annotation_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Font Example
|
||||
```python
|
||||
# Using a custom font
|
||||
result = pdf_tool.run(
|
||||
pdf_path="/path/to/input.pdf",
|
||||
text="Custom Font Text",
|
||||
position=(100, 500),
|
||||
font_size=16,
|
||||
font_color="0 0 1 rg", # Blue color
|
||||
font_file="/path/to/custom_font.ttf",
|
||||
page_number=0
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple Text Elements
|
||||
```python
|
||||
# Add multiple text elements
|
||||
positions = [(100, 700), (100, 650), (100, 600)]
|
||||
texts = ["Header", "Subheader", "Body Text"]
|
||||
font_sizes = [18, 14, 12]
|
||||
|
||||
for text, position, size in zip(texts, positions, font_sizes):
|
||||
pdf_tool.run(
|
||||
pdf_path="/path/to/input.pdf",
|
||||
text=text,
|
||||
position=position,
|
||||
font_size=size,
|
||||
font_color="0 0 0 rg" # Black color
|
||||
)
|
||||
```
|
||||
|
||||
### Color Text Example
|
||||
```python
|
||||
# Add colored text
|
||||
colors = {
|
||||
"red": "1 0 0 rg",
|
||||
"green": "0 1 0 rg",
|
||||
"blue": "0 0 1 rg"
|
||||
}
|
||||
|
||||
for y_pos, (color_name, color_code) in enumerate(colors.items()):
|
||||
pdf_tool.run(
|
||||
pdf_path="/path/to/input.pdf",
|
||||
text=f"This text is {color_name}",
|
||||
position=(100, 700 - y_pos * 50),
|
||||
font_size=14,
|
||||
font_color=color_code
|
||||
)
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Supports custom TrueType fonts (.ttf)
|
||||
- Allows RGB color specifications
|
||||
- Handles multi-page PDFs
|
||||
- Preserves original PDF content
|
||||
- Supports text positioning with x,y coordinates
|
||||
- Maintains PDF structure and metadata
|
||||
- Creates new output file for safety
|
||||
- Thread-safe operations
|
||||
- Efficient PDF manipulation
|
||||
- Supports various text attributes
|
||||
@@ -1,181 +0,0 @@
|
||||
---
|
||||
title: PGSearchTool
|
||||
description: A RAG-based semantic search tool for PostgreSQL database content
|
||||
icon: database-search
|
||||
---
|
||||
|
||||
## PGSearchTool
|
||||
|
||||
The PGSearchTool provides semantic search capabilities for PostgreSQL database content using RAG (Retrieval-Augmented Generation). It allows for natural language queries over database table content by leveraging embeddings and semantic search.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
pip install embedchain # Required dependency
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import PGSearchTool
|
||||
|
||||
# Initialize the tool with database configuration
|
||||
search_tool = PGSearchTool(
|
||||
db_uri="postgresql://user:password@localhost:5432/dbname",
|
||||
table_name="your_table"
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Database Researcher',
|
||||
goal='Find relevant information in database content',
|
||||
backstory='Expert at searching and analyzing database content.',
|
||||
tools=[search_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class PGSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory semantic search query for searching the database's content"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(self, table_name: str, **kwargs):
|
||||
"""
|
||||
Initialize the PostgreSQL search tool.
|
||||
|
||||
Args:
|
||||
table_name (str): Name of the table to search
|
||||
db_uri (str): PostgreSQL database URI (required in kwargs)
|
||||
**kwargs: Additional arguments for RagTool initialization
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
**kwargs: Any
|
||||
) -> Any:
|
||||
"""
|
||||
Perform semantic search on database content.
|
||||
|
||||
Args:
|
||||
search_query (str): Semantic search query
|
||||
**kwargs: Additional search parameters
|
||||
|
||||
Returns:
|
||||
Any: Relevant database content based on semantic search
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Secure database credentials:
|
||||
```python
|
||||
# Use environment variables for sensitive data
|
||||
import os
|
||||
|
||||
db_uri = (
|
||||
f"postgresql://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}"
|
||||
f"@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
|
||||
)
|
||||
```
|
||||
|
||||
2. Optimize table selection
|
||||
3. Use specific semantic queries
|
||||
4. Handle database connection errors
|
||||
5. Consider table size and query performance
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import PGSearchTool
|
||||
|
||||
# Initialize tool with database configuration
|
||||
db_search = PGSearchTool(
|
||||
db_uri="postgresql://user:password@localhost:5432/dbname",
|
||||
table_name="customer_feedback"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
analyst = Agent(
|
||||
role='Database Analyst',
|
||||
goal='Analyze customer feedback data',
|
||||
backstory='Expert at finding insights in customer feedback.',
|
||||
tools=[db_search]
|
||||
)
|
||||
|
||||
# Define task
|
||||
analysis_task = Task(
|
||||
description="""Find all customer feedback related to product usability
|
||||
and ease of use. Focus on common patterns and issues.""",
|
||||
agent=analyst
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "product usability feedback ease of use issues"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[analyst],
|
||||
tasks=[analysis_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple Table Search
|
||||
```python
|
||||
# Create tools for different tables
|
||||
customer_search = PGSearchTool(
|
||||
db_uri="postgresql://user:password@localhost:5432/dbname",
|
||||
table_name="customers"
|
||||
)
|
||||
|
||||
orders_search = PGSearchTool(
|
||||
db_uri="postgresql://user:password@localhost:5432/dbname",
|
||||
table_name="orders"
|
||||
)
|
||||
|
||||
# Use both tools in an agent
|
||||
analyst = Agent(
|
||||
role='Multi-table Analyst',
|
||||
goal='Analyze customer and order data',
|
||||
tools=[customer_search, orders_search]
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```python
|
||||
try:
|
||||
results = search_tool._run(
|
||||
search_query="customer satisfaction ratings"
|
||||
)
|
||||
# Process results
|
||||
except Exception as e:
|
||||
print(f"Database search error: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool for semantic search
|
||||
- Uses embedchain's PostgresLoader
|
||||
- Requires valid PostgreSQL connection
|
||||
- Supports semantic natural language queries
|
||||
- Thread-safe operations
|
||||
- Efficient for large tables
|
||||
- Handles connection pooling automatically
|
||||
@@ -1,282 +0,0 @@
|
||||
---
|
||||
title: RagTool
|
||||
description: Base class for Retrieval-Augmented Generation (RAG) tools with flexible adapter support
|
||||
icon: database
|
||||
---
|
||||
|
||||
## RagTool
|
||||
|
||||
The RagTool serves as the base class for all Retrieval-Augmented Generation (RAG) tools in the CrewAI ecosystem. It provides a flexible adapter-based architecture for implementing knowledge base functionality with semantic search capabilities.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import RagTool
|
||||
from crewai_tools.adapters import EmbedchainAdapter
|
||||
from embedchain import App
|
||||
|
||||
# Create custom adapter
|
||||
class CustomAdapter(RagTool.Adapter):
|
||||
def query(self, question: str) -> str:
|
||||
# Implement custom query logic
|
||||
return "Answer based on knowledge base"
|
||||
|
||||
def add(self, *args, **kwargs) -> None:
|
||||
# Implement custom add logic
|
||||
pass
|
||||
|
||||
# Method 1: Use default EmbedchainAdapter
|
||||
rag_tool = RagTool(
|
||||
name="Custom Knowledge Base",
|
||||
description="Specialized knowledge base for domain data",
|
||||
summarize=True
|
||||
)
|
||||
|
||||
# Method 2: Use custom adapter
|
||||
custom_tool = RagTool(
|
||||
name="Custom Knowledge Base",
|
||||
adapter=CustomAdapter(),
|
||||
summarize=False
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Knowledge Base Researcher',
|
||||
goal='Search and analyze knowledge base content',
|
||||
backstory='Expert at finding relevant information in specialized datasets.',
|
||||
tools=[rag_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Adapter Interface
|
||||
|
||||
```python
|
||||
class Adapter(BaseModel, ABC):
|
||||
@abstractmethod
|
||||
def query(self, question: str) -> str:
|
||||
"""
|
||||
Query the knowledge base with a question.
|
||||
|
||||
Args:
|
||||
question (str): Query to search in knowledge base
|
||||
|
||||
Returns:
|
||||
str: Answer based on knowledge base content
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""
|
||||
Add content to the knowledge base.
|
||||
|
||||
Args:
|
||||
*args: Variable length argument list
|
||||
**kwargs: Arbitrary keyword arguments
|
||||
"""
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "Knowledge base",
|
||||
description: str = "A knowledge base that can be used to answer questions.",
|
||||
summarize: bool = False,
|
||||
adapter: Optional[Adapter] = None,
|
||||
config: Optional[dict[str, Any]] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the RAG tool.
|
||||
|
||||
Args:
|
||||
name (str): Tool name
|
||||
description (str): Tool description
|
||||
summarize (bool): Enable answer summarization
|
||||
adapter (Optional[Adapter]): Custom adapter implementation
|
||||
config (Optional[dict]): Configuration for default adapter
|
||||
**kwargs: Additional arguments for base tool
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute query against knowledge base.
|
||||
|
||||
Args:
|
||||
query (str): Question to ask
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
str: Answer from knowledge base
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Adapter Implementation:
|
||||
- Define clear interfaces
|
||||
- Handle edge cases
|
||||
- Implement error handling
|
||||
- Document behavior
|
||||
|
||||
2. Knowledge Base Management:
|
||||
- Organize content logically
|
||||
- Update content regularly
|
||||
- Monitor performance
|
||||
- Handle large datasets
|
||||
|
||||
3. Query Optimization:
|
||||
- Structure queries clearly
|
||||
- Consider context
|
||||
- Handle ambiguity
|
||||
- Validate inputs
|
||||
|
||||
4. Error Handling:
|
||||
- Handle missing data
|
||||
- Manage timeouts
|
||||
- Provide clear messages
|
||||
- Log issues
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import RagTool
|
||||
from embedchain import App
|
||||
|
||||
# Initialize tool with custom configuration
|
||||
rag_tool = RagTool(
|
||||
name="Technical Documentation KB",
|
||||
description="Knowledge base for technical documentation",
|
||||
summarize=True,
|
||||
config={
|
||||
"collection_name": "tech_docs",
|
||||
"chunking": {
|
||||
"chunk_size": 500,
|
||||
"chunk_overlap": 50
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Add content to knowledge base
|
||||
rag_tool.add(
|
||||
"Technical documentation content here...",
|
||||
data_type="text"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Documentation Expert',
|
||||
goal='Extract technical information from documentation',
|
||||
backstory='Expert at analyzing technical documentation.',
|
||||
tools=[rag_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Find all mentions of API endpoints
|
||||
and their authentication requirements.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Adapter Implementation
|
||||
```python
|
||||
from typing import Any
|
||||
from pydantic import BaseModel
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class SpecializedAdapter(RagTool.Adapter):
|
||||
def __init__(self, config: dict):
|
||||
self.config = config
|
||||
self.knowledge_base = {}
|
||||
|
||||
def query(self, question: str) -> str:
|
||||
# Implement specialized query logic
|
||||
return self._process_query(question)
|
||||
|
||||
def add(self, content: str, **kwargs: Any) -> None:
|
||||
# Implement specialized content addition
|
||||
self._process_content(content, **kwargs)
|
||||
|
||||
# Use custom adapter
|
||||
specialized_tool = RagTool(
|
||||
name="Specialized KB",
|
||||
adapter=SpecializedAdapter(config={"mode": "advanced"})
|
||||
)
|
||||
```
|
||||
|
||||
### Configuration Management
|
||||
```python
|
||||
# Configure default EmbedchainAdapter
|
||||
config = {
|
||||
"collection_name": "custom_collection",
|
||||
"embedding": {
|
||||
"model": "sentence-transformers/all-mpnet-base-v2",
|
||||
"dimensions": 768
|
||||
},
|
||||
"chunking": {
|
||||
"chunk_size": 1000,
|
||||
"chunk_overlap": 100
|
||||
}
|
||||
}
|
||||
|
||||
tool = RagTool(config=config)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
rag_tool = RagTool()
|
||||
|
||||
# Add content
|
||||
rag_tool.add(
|
||||
"Documentation content...",
|
||||
data_type="text"
|
||||
)
|
||||
|
||||
# Query content
|
||||
result = rag_tool.run(
|
||||
query="What are the system requirements?"
|
||||
)
|
||||
print(result)
|
||||
except Exception as e:
|
||||
print(f"Error using knowledge base: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Base class for RAG tools
|
||||
- Flexible adapter pattern
|
||||
- Default EmbedchainAdapter
|
||||
- Custom adapter support
|
||||
- Content management
|
||||
- Query processing
|
||||
- Error handling
|
||||
- Configuration options
|
||||
- Performance optimization
|
||||
- Memory management
|
||||
@@ -1,229 +0,0 @@
|
||||
---
|
||||
title: SerpApi Google Search Tool
|
||||
description: A tool for performing Google searches using the SerpApi service
|
||||
---
|
||||
|
||||
# SerpApi Google Search Tool
|
||||
|
||||
The SerpApi Google Search Tool enables performing Google searches using the SerpApi service. It provides location-aware search capabilities with comprehensive result filtering.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
pip install serpapi
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need a SerpApi API key to use this tool. You can get one from [SerpApi's website](https://serpapi.com/manage-api-key).
|
||||
|
||||
Set your API key as an environment variable:
|
||||
```bash
|
||||
export SERPAPI_API_KEY="your_api_key_here"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Here's how to use the SerpApi Google Search Tool:
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import SerpApiGoogleSearchTool
|
||||
|
||||
# Initialize the tool
|
||||
search_tool = SerpApiGoogleSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
search_agent = Agent(
|
||||
role='Web Researcher',
|
||||
goal='Find accurate information online',
|
||||
backstory='I help research and analyze online information',
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
# Use in a task
|
||||
task = Task(
|
||||
description="Research recent AI developments",
|
||||
agent=search_agent,
|
||||
context={
|
||||
"search_query": "latest artificial intelligence breakthroughs 2024",
|
||||
"location": "United States" # Optional
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class SerpApiGoogleSearchToolSchema(BaseModel):
|
||||
search_query: str # The search query for Google Search
|
||||
location: Optional[str] = None # Optional location for localized results
|
||||
```
|
||||
|
||||
## Function Signatures
|
||||
|
||||
### Base Tool Initialization
|
||||
```python
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialize the SerpApi tool with API credentials.
|
||||
|
||||
Raises:
|
||||
ImportError: If serpapi package is not installed
|
||||
ValueError: If SERPAPI_API_KEY environment variable is not set
|
||||
"""
|
||||
```
|
||||
|
||||
### Search Execution
|
||||
```python
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any,
|
||||
) -> dict:
|
||||
"""
|
||||
Execute the Google search.
|
||||
|
||||
Args:
|
||||
search_query (str): The search query
|
||||
location (Optional[str]): Optional location for results
|
||||
|
||||
Returns:
|
||||
dict: Filtered search results from Google
|
||||
|
||||
Raises:
|
||||
HTTPError: If the API request fails
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **API Key Management**:
|
||||
- Store the API key securely in environment variables
|
||||
- Never hardcode the API key in your code
|
||||
- Verify API key validity before making requests
|
||||
|
||||
2. **Search Optimization**:
|
||||
- Use specific, targeted search queries
|
||||
- Include relevant keywords and time frames
|
||||
- Leverage location parameter for regional results
|
||||
|
||||
3. **Error Handling**:
|
||||
- Handle API rate limits gracefully
|
||||
- Implement retry logic for failed requests
|
||||
- Validate input parameters before making requests
|
||||
|
||||
## Example Integration
|
||||
|
||||
Here's a complete example showing how to integrate the SerpApi Google Search Tool with CrewAI:
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import SerpApiGoogleSearchTool
|
||||
|
||||
# Initialize the tool
|
||||
search_tool = SerpApiGoogleSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Research Analyst',
|
||||
goal='Find and analyze current information',
|
||||
backstory="""I am an expert at finding and analyzing
|
||||
information from various online sources.""",
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
# Create tasks
|
||||
research_task = Task(
|
||||
description="""
|
||||
Research the following topic:
|
||||
1. Latest developments in quantum computing
|
||||
2. Focus on practical applications
|
||||
3. Include major company announcements
|
||||
|
||||
Provide a comprehensive analysis of the findings.
|
||||
""",
|
||||
agent=researcher,
|
||||
context={
|
||||
"search_query": "quantum computing breakthroughs applications companies",
|
||||
"location": "United States"
|
||||
}
|
||||
)
|
||||
|
||||
# Create and run the crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The tool handles various error scenarios:
|
||||
|
||||
1. **Missing API Key**:
|
||||
```python
|
||||
try:
|
||||
tool = SerpApiGoogleSearchTool()
|
||||
except ValueError as e:
|
||||
print("API key not found. Set SERPAPI_API_KEY environment variable.")
|
||||
```
|
||||
|
||||
2. **API Request Errors**:
|
||||
```python
|
||||
try:
|
||||
results = tool._run(
|
||||
search_query="quantum computing",
|
||||
location="United States"
|
||||
)
|
||||
except HTTPError as e:
|
||||
print(f"API request failed: {str(e)}")
|
||||
```
|
||||
|
||||
3. **Invalid Parameters**:
|
||||
```python
|
||||
try:
|
||||
results = tool._run(
|
||||
search_query="", # Empty query
|
||||
location="Invalid Location"
|
||||
)
|
||||
except ValueError as e:
|
||||
print("Invalid search parameters provided.")
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
The tool returns a filtered dictionary containing Google search results. Example response structure:
|
||||
|
||||
```python
|
||||
{
|
||||
"organic_results": [
|
||||
{
|
||||
"title": "Page Title",
|
||||
"link": "https://...",
|
||||
"snippet": "Page description or excerpt...",
|
||||
"position": 1
|
||||
}
|
||||
# Additional results...
|
||||
],
|
||||
"knowledge_graph": {
|
||||
"title": "Topic Title",
|
||||
"description": "Topic description...",
|
||||
"source": {
|
||||
"name": "Source Name",
|
||||
"link": "https://..."
|
||||
}
|
||||
},
|
||||
"related_questions": [
|
||||
{
|
||||
"question": "Related question?",
|
||||
"answer": "Answer to related question..."
|
||||
}
|
||||
# Additional related questions...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The response is automatically filtered to remove metadata and unnecessary fields, focusing on the most relevant search information. Fields like search metadata, parameters, and pagination are omitted for clarity.
|
||||
@@ -1,225 +0,0 @@
|
||||
---
|
||||
title: SerpApi Google Shopping Tool
|
||||
description: A tool for searching Google Shopping using the SerpApi service
|
||||
---
|
||||
|
||||
# SerpApi Google Shopping Tool
|
||||
|
||||
The SerpApi Google Shopping Tool enables searching Google Shopping results using the SerpApi service. It provides location-aware shopping search capabilities with comprehensive result filtering.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
pip install serpapi
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need a SerpApi API key to use this tool. You can get one from [SerpApi's website](https://serpapi.com/manage-api-key).
|
||||
|
||||
Set your API key as an environment variable:
|
||||
```bash
|
||||
export SERPAPI_API_KEY="your_api_key_here"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Here's how to use the SerpApi Google Shopping Tool:
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import SerpApiGoogleShoppingTool
|
||||
|
||||
# Initialize the tool
|
||||
shopping_tool = SerpApiGoogleShoppingTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
shopping_agent = Agent(
|
||||
role='Shopping Researcher',
|
||||
goal='Find the best shopping deals',
|
||||
backstory='I help find and analyze shopping options',
|
||||
tools=[shopping_tool]
|
||||
)
|
||||
|
||||
# Use in a task
|
||||
task = Task(
|
||||
description="Find best deals for gaming laptops",
|
||||
agent=shopping_agent,
|
||||
context={
|
||||
"search_query": "gaming laptop deals",
|
||||
"location": "United States" # Optional
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class SerpApiGoogleShoppingToolSchema(BaseModel):
|
||||
search_query: str # The search query for Google Shopping
|
||||
location: Optional[str] = None # Optional location for localized results
|
||||
```
|
||||
|
||||
## Function Signatures
|
||||
|
||||
### Base Tool Initialization
|
||||
```python
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialize the SerpApi tool with API credentials.
|
||||
|
||||
Raises:
|
||||
ImportError: If serpapi package is not installed
|
||||
ValueError: If SERPAPI_API_KEY environment variable is not set
|
||||
"""
|
||||
```
|
||||
|
||||
### Search Execution
|
||||
```python
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any,
|
||||
) -> dict:
|
||||
"""
|
||||
Execute the Google Shopping search.
|
||||
|
||||
Args:
|
||||
search_query (str): The search query for Google Shopping
|
||||
location (Optional[str]): Optional location for results
|
||||
|
||||
Returns:
|
||||
dict: Filtered search results from Google Shopping
|
||||
|
||||
Raises:
|
||||
HTTPError: If the API request fails
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **API Key Management**:
|
||||
- Store the API key securely in environment variables
|
||||
- Never hardcode the API key in your code
|
||||
- Verify API key validity before making requests
|
||||
|
||||
2. **Search Optimization**:
|
||||
- Use specific, targeted search queries
|
||||
- Include relevant product details in queries
|
||||
- Leverage location parameter for regional pricing
|
||||
|
||||
3. **Error Handling**:
|
||||
- Handle API rate limits gracefully
|
||||
- Implement retry logic for failed requests
|
||||
- Validate input parameters before making requests
|
||||
|
||||
## Example Integration
|
||||
|
||||
Here's a complete example showing how to integrate the SerpApi Google Shopping Tool with CrewAI:
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import SerpApiGoogleShoppingTool
|
||||
|
||||
# Initialize the tool
|
||||
shopping_tool = SerpApiGoogleShoppingTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Shopping Analyst',
|
||||
goal='Find and analyze the best shopping deals',
|
||||
backstory="""I am an expert at finding the best shopping deals
|
||||
and analyzing product offerings across different regions.""",
|
||||
tools=[shopping_tool]
|
||||
)
|
||||
|
||||
# Create tasks
|
||||
search_task = Task(
|
||||
description="""
|
||||
Research gaming laptops with the following criteria:
|
||||
1. Price range: $800-$1500
|
||||
2. Released in the last year
|
||||
3. Compare prices across different retailers
|
||||
|
||||
Provide a comprehensive analysis of the findings.
|
||||
""",
|
||||
agent=researcher,
|
||||
context={
|
||||
"search_query": "gaming laptop RTX 4060 2023",
|
||||
"location": "United States"
|
||||
}
|
||||
)
|
||||
|
||||
# Create and run the crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[search_task]
|
||||
)
|
||||
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The tool handles various error scenarios:
|
||||
|
||||
1. **Missing API Key**:
|
||||
```python
|
||||
try:
|
||||
tool = SerpApiGoogleShoppingTool()
|
||||
except ValueError as e:
|
||||
print("API key not found. Set SERPAPI_API_KEY environment variable.")
|
||||
```
|
||||
|
||||
2. **API Request Errors**:
|
||||
```python
|
||||
try:
|
||||
results = tool._run(
|
||||
search_query="gaming laptop",
|
||||
location="United States"
|
||||
)
|
||||
except HTTPError as e:
|
||||
print(f"API request failed: {str(e)}")
|
||||
```
|
||||
|
||||
3. **Invalid Parameters**:
|
||||
```python
|
||||
try:
|
||||
results = tool._run(
|
||||
search_query="", # Empty query
|
||||
location="Invalid Location"
|
||||
)
|
||||
except ValueError as e:
|
||||
print("Invalid search parameters provided.")
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
The tool returns a filtered dictionary containing Google Shopping results. Example response structure:
|
||||
|
||||
```python
|
||||
{
|
||||
"shopping_results": [
|
||||
{
|
||||
"title": "Product Title",
|
||||
"price": "$999.99",
|
||||
"link": "https://...",
|
||||
"source": "Retailer Name",
|
||||
"rating": 4.5,
|
||||
"reviews": 123,
|
||||
"thumbnail": "https://..."
|
||||
}
|
||||
# Additional results...
|
||||
],
|
||||
"organic_results": [
|
||||
{
|
||||
"title": "Related Product",
|
||||
"link": "https://...",
|
||||
"snippet": "Product description..."
|
||||
}
|
||||
# Additional organic results...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The response is automatically filtered to remove metadata and unnecessary fields, focusing on the most relevant shopping information.
|
||||
@@ -1,184 +0,0 @@
|
||||
---
|
||||
title: SerplyJobSearchTool
|
||||
description: A tool for searching US job postings using the Serply API
|
||||
icon: briefcase
|
||||
---
|
||||
|
||||
## SerplyJobSearchTool
|
||||
|
||||
The SerplyJobSearchTool provides job search capabilities using the Serply API. It allows for searching job postings in the US market, returning structured information about positions, employers, locations, and remote work status.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import SerplyJobSearchTool
|
||||
|
||||
# Set environment variable
|
||||
# export SERPLY_API_KEY='your-api-key'
|
||||
|
||||
# Initialize the tool
|
||||
search_tool = SerplyJobSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
job_researcher = Agent(
|
||||
role='Job Market Researcher',
|
||||
goal='Find relevant job opportunities',
|
||||
backstory='Expert at analyzing job market trends and opportunities.',
|
||||
tools=[search_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class SerplyJobSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query for fetching job postings"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialize the job search tool.
|
||||
|
||||
Args:
|
||||
**kwargs: Additional arguments for RagTool initialization
|
||||
|
||||
Note:
|
||||
Requires SERPLY_API_KEY environment variable
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Perform job search using Serply API.
|
||||
|
||||
Args:
|
||||
search_query (str): Job search query
|
||||
**kwargs: Additional search parameters
|
||||
|
||||
Returns:
|
||||
str: Formatted string containing job listings with details:
|
||||
- Position
|
||||
- Employer
|
||||
- Location
|
||||
- Link
|
||||
- Highlights
|
||||
- Remote/Hybrid status
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Set up API authentication:
|
||||
```bash
|
||||
export SERPLY_API_KEY='your-serply-api-key'
|
||||
```
|
||||
|
||||
2. Use specific search queries
|
||||
3. Handle potential API errors
|
||||
4. Process structured results effectively
|
||||
5. Consider rate limits and quotas
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import SerplyJobSearchTool
|
||||
|
||||
# Initialize tool
|
||||
job_search = SerplyJobSearchTool()
|
||||
|
||||
# Create agent
|
||||
recruiter = Agent(
|
||||
role='Technical Recruiter',
|
||||
goal='Find relevant job opportunities in tech',
|
||||
backstory='Expert at identifying promising tech positions.',
|
||||
tools=[job_search]
|
||||
)
|
||||
|
||||
# Define task
|
||||
search_task = Task(
|
||||
description="""Search for senior software engineer positions
|
||||
with remote work options in the US. Focus on positions
|
||||
requiring Python expertise.""",
|
||||
agent=recruiter
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "senior software engineer python remote"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[recruiter],
|
||||
tasks=[search_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Handling Search Results
|
||||
```python
|
||||
# Example of processing structured results
|
||||
results = search_tool._run(
|
||||
search_query="machine learning engineer"
|
||||
)
|
||||
|
||||
# Results format:
|
||||
"""
|
||||
Search results:
|
||||
Position: Senior Machine Learning Engineer
|
||||
Employer: TechCorp Inc
|
||||
Location: San Francisco, CA
|
||||
Link: https://example.com/job/123
|
||||
Highlights: Python, TensorFlow, 5+ years experience
|
||||
Is Remote: True
|
||||
Is Hybrid: False
|
||||
---
|
||||
Position: ML Engineer
|
||||
...
|
||||
"""
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```python
|
||||
try:
|
||||
results = search_tool._run(
|
||||
search_query="data scientist"
|
||||
)
|
||||
if not results:
|
||||
print("No jobs found")
|
||||
else:
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"Job search error: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires valid Serply API key
|
||||
- Currently supports US job market only
|
||||
- Returns structured job information
|
||||
- Includes remote/hybrid status
|
||||
- Thread-safe operations
|
||||
- Efficient job search capabilities
|
||||
- Handles API rate limiting automatically
|
||||
- Provides detailed job highlights
|
||||
@@ -1,209 +0,0 @@
|
||||
---
|
||||
title: SerplyNewsSearchTool
|
||||
description: A news article search tool powered by Serply API with configurable search parameters
|
||||
icon: newspaper
|
||||
---
|
||||
|
||||
## SerplyNewsSearchTool
|
||||
|
||||
The SerplyNewsSearchTool provides news article search capabilities using the Serply API. It allows for customizable search parameters including result limits and proxy location for region-specific news results.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import SerplyNewsSearchTool
|
||||
|
||||
# Set environment variable
|
||||
# export SERPLY_API_KEY='your-api-key'
|
||||
|
||||
# Basic initialization
|
||||
news_tool = SerplyNewsSearchTool()
|
||||
|
||||
# Advanced initialization with custom parameters
|
||||
news_tool = SerplyNewsSearchTool(
|
||||
limit=20, # Return 20 results
|
||||
proxy_location="FR" # Search from France
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
news_researcher = Agent(
|
||||
role='News Researcher',
|
||||
goal='Find relevant news articles',
|
||||
backstory='Expert at news research and information gathering.',
|
||||
tools=[news_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class SerplyNewsSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query for fetching news articles"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
limit: Optional[int] = 10,
|
||||
proxy_location: Optional[str] = "US",
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the news search tool.
|
||||
|
||||
Args:
|
||||
limit (int): Maximum number of results [10-100] (default: 10)
|
||||
proxy_location (str): Region for local news results (default: "US")
|
||||
Options: US, CA, IE, GB, FR, DE, SE, IN, JP, KR, SG, AU, BR
|
||||
**kwargs: Additional arguments for tool creation
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Perform news search using Serply API.
|
||||
|
||||
Args:
|
||||
search_query (str): News search query
|
||||
|
||||
Returns:
|
||||
str: Formatted string containing news results:
|
||||
- Title
|
||||
- Link
|
||||
- Source
|
||||
- Published Date
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Set up API authentication:
|
||||
```bash
|
||||
export SERPLY_API_KEY='your-serply-api-key'
|
||||
```
|
||||
|
||||
2. Configure search parameters appropriately:
|
||||
- Set reasonable result limits
|
||||
- Select relevant proxy location for regional news
|
||||
- Consider time sensitivity of news content
|
||||
|
||||
3. Handle potential API errors
|
||||
4. Process structured results effectively
|
||||
5. Consider rate limits and quotas
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import SerplyNewsSearchTool
|
||||
|
||||
# Initialize tool with custom configuration
|
||||
news_tool = SerplyNewsSearchTool(
|
||||
limit=15, # 15 results
|
||||
proxy_location="US" # US news sources
|
||||
)
|
||||
|
||||
# Create agent
|
||||
news_analyst = Agent(
|
||||
role='News Analyst',
|
||||
goal='Research breaking news and developments',
|
||||
backstory='Expert at analyzing news trends and developments.',
|
||||
tools=[news_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
news_task = Task(
|
||||
description="""Research the latest developments in renewable
|
||||
energy technology and investments, focusing on major
|
||||
announcements and industry trends.""",
|
||||
agent=news_analyst
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "renewable energy technology investments news"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[news_analyst],
|
||||
tasks=[news_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Regional News Configuration
|
||||
```python
|
||||
# French news sources
|
||||
fr_news = SerplyNewsSearchTool(
|
||||
proxy_location="FR",
|
||||
limit=20
|
||||
)
|
||||
|
||||
# Japanese news sources
|
||||
jp_news = SerplyNewsSearchTool(
|
||||
proxy_location="JP",
|
||||
limit=20
|
||||
)
|
||||
```
|
||||
|
||||
### Result Processing
|
||||
```python
|
||||
# Get news results
|
||||
try:
|
||||
results = news_tool._run(
|
||||
search_query="renewable energy investments"
|
||||
)
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"News search error: {str(e)}")
|
||||
```
|
||||
|
||||
### Multiple Region Search
|
||||
```python
|
||||
# Search across multiple regions
|
||||
regions = ["US", "GB", "DE"]
|
||||
all_results = []
|
||||
|
||||
for region in regions:
|
||||
regional_tool = SerplyNewsSearchTool(
|
||||
proxy_location=region,
|
||||
limit=5
|
||||
)
|
||||
results = regional_tool._run(
|
||||
search_query="global tech innovations"
|
||||
)
|
||||
all_results.append(f"Results from {region}:\n{results}")
|
||||
|
||||
combined_results = "\n\n".join(all_results)
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires valid Serply API key
|
||||
- Supports multiple regions for news sources
|
||||
- Configurable result limits (10-100)
|
||||
- Returns structured news article data
|
||||
- Thread-safe operations
|
||||
- Efficient news search capabilities
|
||||
- Handles API rate limiting automatically
|
||||
- Includes source attribution and publication dates
|
||||
- Follows redirects for final article URLs
|
||||
@@ -1,209 +0,0 @@
|
||||
---
|
||||
title: SerplyScholarSearchTool
|
||||
description: A scholarly literature search tool powered by Serply API with configurable search parameters
|
||||
icon: book
|
||||
---
|
||||
|
||||
## SerplyScholarSearchTool
|
||||
|
||||
The SerplyScholarSearchTool provides scholarly literature search capabilities using the Serply API. It allows for customizable search parameters including language and proxy location for region-specific academic results.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import SerplyScholarSearchTool
|
||||
|
||||
# Set environment variable
|
||||
# export SERPLY_API_KEY='your-api-key'
|
||||
|
||||
# Basic initialization
|
||||
scholar_tool = SerplyScholarSearchTool()
|
||||
|
||||
# Advanced initialization with custom parameters
|
||||
scholar_tool = SerplyScholarSearchTool(
|
||||
hl="fr", # French language results
|
||||
proxy_location="FR" # Search from France
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
academic_researcher = Agent(
|
||||
role='Academic Researcher',
|
||||
goal='Find relevant scholarly literature',
|
||||
backstory='Expert at academic research and literature review.',
|
||||
tools=[scholar_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class SerplyScholarSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query for fetching scholarly literature"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
hl: str = "us",
|
||||
proxy_location: Optional[str] = "US",
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the scholar search tool.
|
||||
|
||||
Args:
|
||||
hl (str): Host language code for results (default: "us")
|
||||
Reference: https://developers.google.com/custom-search/docs/xml_results?hl=en#wsInterfaceLanguages
|
||||
proxy_location (str): Region for local results (default: "US")
|
||||
Options: US, CA, IE, GB, FR, DE, SE, IN, JP, KR, SG, AU, BR
|
||||
**kwargs: Additional arguments for tool creation
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Perform scholarly literature search using Serply API.
|
||||
|
||||
Args:
|
||||
search_query (str): Academic search query
|
||||
|
||||
Returns:
|
||||
str: Formatted string containing scholarly results:
|
||||
- Title
|
||||
- Link
|
||||
- Description
|
||||
- Citation
|
||||
- Authors
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Set up API authentication:
|
||||
```bash
|
||||
export SERPLY_API_KEY='your-serply-api-key'
|
||||
```
|
||||
|
||||
2. Configure search parameters appropriately:
|
||||
- Use relevant language codes
|
||||
- Select appropriate proxy location
|
||||
- Provide specific academic search terms
|
||||
|
||||
3. Handle potential API errors
|
||||
4. Process structured results effectively
|
||||
5. Consider rate limits and quotas
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import SerplyScholarSearchTool
|
||||
|
||||
# Initialize tool with custom configuration
|
||||
scholar_tool = SerplyScholarSearchTool(
|
||||
hl="en", # English results
|
||||
proxy_location="US" # US academic sources
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Academic Researcher',
|
||||
goal='Research recent academic publications',
|
||||
backstory='Expert at analyzing academic literature and research trends.',
|
||||
tools=[scholar_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Research recent academic publications on
|
||||
machine learning applications in healthcare, focusing on
|
||||
peer-reviewed articles from the last two years.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "machine learning healthcare applications"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Language and Region Configuration
|
||||
```python
|
||||
# French academic sources
|
||||
fr_scholar = SerplyScholarSearchTool(
|
||||
hl="fr",
|
||||
proxy_location="FR"
|
||||
)
|
||||
|
||||
# German academic sources
|
||||
de_scholar = SerplyScholarSearchTool(
|
||||
hl="de",
|
||||
proxy_location="DE"
|
||||
)
|
||||
```
|
||||
|
||||
### Result Processing
|
||||
```python
|
||||
try:
|
||||
results = scholar_tool._run(
|
||||
search_query="machine learning healthcare applications"
|
||||
)
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"Scholar search error: {str(e)}")
|
||||
```
|
||||
|
||||
### Citation Analysis
|
||||
```python
|
||||
# Extract and analyze citations
|
||||
def analyze_citations(results):
|
||||
citations = []
|
||||
for result in results.split("---"):
|
||||
if "Cite:" in result:
|
||||
citation = result.split("Cite:")[1].split("\n")[0].strip()
|
||||
citations.append(citation)
|
||||
return citations
|
||||
|
||||
results = scholar_tool._run(
|
||||
search_query="artificial intelligence ethics"
|
||||
)
|
||||
citations = analyze_citations(results)
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires valid Serply API key
|
||||
- Supports multiple languages and regions
|
||||
- Returns structured academic article data
|
||||
- Includes citation information
|
||||
- Lists all authors of publications
|
||||
- Thread-safe operations
|
||||
- Efficient scholarly search capabilities
|
||||
- Handles API rate limiting automatically
|
||||
- Supports both direct and document links
|
||||
- Provides comprehensive article metadata
|
||||
@@ -1,213 +0,0 @@
|
||||
---
|
||||
title: SerplyWebSearchTool
|
||||
description: A Google search tool powered by Serply API with configurable search parameters
|
||||
icon: search
|
||||
---
|
||||
|
||||
## SerplyWebSearchTool
|
||||
|
||||
The SerplyWebSearchTool provides Google search capabilities using the Serply API. It allows for customizable search parameters including language, result limits, device type, and proxy location for region-specific results.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import SerplyWebSearchTool
|
||||
|
||||
# Set environment variable
|
||||
# export SERPLY_API_KEY='your-api-key'
|
||||
|
||||
# Basic initialization
|
||||
search_tool = SerplyWebSearchTool()
|
||||
|
||||
# Advanced initialization with custom parameters
|
||||
search_tool = SerplyWebSearchTool(
|
||||
hl="fr", # French language results
|
||||
limit=20, # Return 20 results
|
||||
device_type="mobile", # Mobile search results
|
||||
proxy_location="FR" # Search from France
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Web Researcher',
|
||||
goal='Find relevant information online',
|
||||
backstory='Expert at web research and information gathering.',
|
||||
tools=[search_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class SerplyWebSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query for Google search"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
hl: str = "us",
|
||||
limit: int = 10,
|
||||
device_type: str = "desktop",
|
||||
proxy_location: str = "US",
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the Google search tool.
|
||||
|
||||
Args:
|
||||
hl (str): Host language code for results (default: "us")
|
||||
Reference: https://developers.google.com/custom-search/docs/xml_results?hl=en#wsInterfaceLanguages
|
||||
limit (int): Maximum number of results [10-100] (default: 10)
|
||||
device_type (str): "desktop" or "mobile" results (default: "desktop")
|
||||
proxy_location (str): Region for local results (default: "US")
|
||||
Options: US, CA, IE, GB, FR, DE, SE, IN, JP, KR, SG, AU, BR
|
||||
**kwargs: Additional arguments for tool creation
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Perform Google search using Serply API.
|
||||
|
||||
Args:
|
||||
search_query (str): Search query
|
||||
|
||||
Returns:
|
||||
str: Formatted string containing search results:
|
||||
- Title
|
||||
- Link
|
||||
- Description
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Set up API authentication:
|
||||
```bash
|
||||
export SERPLY_API_KEY='your-serply-api-key'
|
||||
```
|
||||
|
||||
2. Configure search parameters appropriately:
|
||||
- Use relevant language codes
|
||||
- Set reasonable result limits
|
||||
- Choose appropriate device type
|
||||
- Select relevant proxy location
|
||||
|
||||
3. Handle potential API errors
|
||||
4. Process structured results effectively
|
||||
5. Consider rate limits and quotas
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import SerplyWebSearchTool
|
||||
|
||||
# Initialize tool with custom configuration
|
||||
search_tool = SerplyWebSearchTool(
|
||||
hl="en", # English results
|
||||
limit=15, # 15 results
|
||||
device_type="desktop",
|
||||
proxy_location="US"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Web Researcher',
|
||||
goal='Research emerging technology trends',
|
||||
backstory='Expert at finding and analyzing tech trends.',
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Research the latest developments in artificial
|
||||
intelligence and machine learning, focusing on practical
|
||||
applications in business.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "latest AI ML developments business applications"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Language and Region Configuration
|
||||
```python
|
||||
# French search from France
|
||||
fr_search = SerplyWebSearchTool(
|
||||
hl="fr",
|
||||
proxy_location="FR"
|
||||
)
|
||||
|
||||
# Japanese search from Japan
|
||||
jp_search = SerplyWebSearchTool(
|
||||
hl="ja",
|
||||
proxy_location="JP"
|
||||
)
|
||||
```
|
||||
|
||||
### Device-Specific Results
|
||||
```python
|
||||
# Mobile results
|
||||
mobile_search = SerplyWebSearchTool(
|
||||
device_type="mobile",
|
||||
limit=20
|
||||
)
|
||||
|
||||
# Desktop results
|
||||
desktop_search = SerplyWebSearchTool(
|
||||
device_type="desktop",
|
||||
limit=20
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```python
|
||||
try:
|
||||
results = search_tool._run(
|
||||
search_query="artificial intelligence trends"
|
||||
)
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"Search error: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
- Requires valid Serply API key
|
||||
- Supports multiple languages and regions
|
||||
- Configurable result limits (10-100)
|
||||
- Device-specific search results
|
||||
- Thread-safe operations
|
||||
- Efficient search capabilities
|
||||
- Handles API rate limiting automatically
|
||||
- Returns structured search results
|
||||
@@ -1,201 +0,0 @@
|
||||
---
|
||||
title: SerplyWebpageToMarkdownTool
|
||||
description: A tool for converting web pages to markdown format using Serply API
|
||||
icon: markdown
|
||||
---
|
||||
|
||||
## SerplyWebpageToMarkdownTool
|
||||
|
||||
The SerplyWebpageToMarkdownTool converts web pages to markdown format using the Serply API, making it easier for LLMs to process and understand web content. It supports configurable proxy locations for region-specific access.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import SerplyWebpageToMarkdownTool
|
||||
|
||||
# Set environment variable
|
||||
# export SERPLY_API_KEY='your-api-key'
|
||||
|
||||
# Basic initialization
|
||||
markdown_tool = SerplyWebpageToMarkdownTool()
|
||||
|
||||
# Advanced initialization with custom parameters
|
||||
markdown_tool = SerplyWebpageToMarkdownTool(
|
||||
proxy_location="FR" # Access from France
|
||||
)
|
||||
|
||||
# Create an agent with the tool
|
||||
web_processor = Agent(
|
||||
role='Web Content Processor',
|
||||
goal='Convert web content to markdown format',
|
||||
backstory='Expert at processing and formatting web content.',
|
||||
tools=[markdown_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
```python
|
||||
class SerplyWebpageToMarkdownToolSchema(BaseModel):
|
||||
url: str = Field(
|
||||
description="Mandatory URL of the webpage to convert to markdown"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
proxy_location: Optional[str] = "US",
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the webpage to markdown conversion tool.
|
||||
|
||||
Args:
|
||||
proxy_location (str): Region for accessing the webpage (default: "US")
|
||||
Options: US, CA, IE, GB, FR, DE, SE, IN, JP, KR, SG, AU, BR
|
||||
**kwargs: Additional arguments for tool creation
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Convert webpage to markdown using Serply API.
|
||||
|
||||
Args:
|
||||
url (str): URL of the webpage to convert
|
||||
|
||||
Returns:
|
||||
str: Markdown formatted content of the webpage
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Set up API authentication:
|
||||
```bash
|
||||
export SERPLY_API_KEY='your-serply-api-key'
|
||||
```
|
||||
|
||||
2. Configure proxy location appropriately:
|
||||
- Select relevant region for access
|
||||
- Consider content accessibility
|
||||
- Handle region-specific content
|
||||
|
||||
3. Handle potential API errors
|
||||
4. Process markdown output effectively
|
||||
5. Consider rate limits and quotas
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import SerplyWebpageToMarkdownTool
|
||||
|
||||
# Initialize tool with custom configuration
|
||||
markdown_tool = SerplyWebpageToMarkdownTool(
|
||||
proxy_location="US" # US access point
|
||||
)
|
||||
|
||||
# Create agent
|
||||
processor = Agent(
|
||||
role='Content Processor',
|
||||
goal='Convert web content to structured markdown',
|
||||
backstory='Expert at processing web content into structured formats.',
|
||||
tools=[markdown_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
conversion_task = Task(
|
||||
description="""Convert the documentation page at
|
||||
https://example.com/docs into markdown format for
|
||||
further processing.""",
|
||||
agent=processor
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "url": "https://example.com/docs"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[processor],
|
||||
tasks=[conversion_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Regional Access Configuration
|
||||
```python
|
||||
# European access points
|
||||
fr_processor = SerplyWebpageToMarkdownTool(
|
||||
proxy_location="FR"
|
||||
)
|
||||
|
||||
de_processor = SerplyWebpageToMarkdownTool(
|
||||
proxy_location="DE"
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```python
|
||||
try:
|
||||
markdown_content = markdown_tool._run(
|
||||
url="https://example.com/page"
|
||||
)
|
||||
print(markdown_content)
|
||||
except Exception as e:
|
||||
print(f"Conversion error: {str(e)}")
|
||||
```
|
||||
|
||||
### Content Processing
|
||||
```python
|
||||
# Process multiple pages
|
||||
urls = [
|
||||
"https://example.com/page1",
|
||||
"https://example.com/page2",
|
||||
"https://example.com/page3"
|
||||
]
|
||||
|
||||
markdown_contents = []
|
||||
for url in urls:
|
||||
try:
|
||||
content = markdown_tool._run(url=url)
|
||||
markdown_contents.append(content)
|
||||
except Exception as e:
|
||||
print(f"Error processing {url}: {str(e)}")
|
||||
continue
|
||||
|
||||
# Combine contents
|
||||
combined_markdown = "\n\n---\n\n".join(markdown_contents)
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires valid Serply API key
|
||||
- Supports multiple proxy locations
|
||||
- Returns markdown-formatted content
|
||||
- Simplifies web content for LLM processing
|
||||
- Thread-safe operations
|
||||
- Efficient content conversion
|
||||
- Handles API rate limiting automatically
|
||||
- Preserves content structure in markdown
|
||||
- Supports various webpage formats
|
||||
- Makes web content more accessible to AI agents
|
||||
@@ -1,158 +0,0 @@
|
||||
---
|
||||
title: TXTSearchTool
|
||||
description: A semantic search tool for text files using RAG capabilities
|
||||
icon: magnifying-glass-document
|
||||
---
|
||||
|
||||
## TXTSearchTool
|
||||
|
||||
The TXTSearchTool is a specialized Retrieval-Augmented Generation (RAG) tool that enables semantic search within text files. It inherits from the base RagTool class and provides both fixed and dynamic text file searching capabilities.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import TXTSearchTool
|
||||
|
||||
# Method 1: Dynamic file path
|
||||
txt_search = TXTSearchTool()
|
||||
|
||||
# Method 2: Fixed file path
|
||||
fixed_txt_search = TXTSearchTool(txt="path/to/fixed/document.txt")
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Research Assistant',
|
||||
goal='Search through text documents semantically',
|
||||
backstory='Expert at finding relevant information in documents using semantic search.',
|
||||
tools=[txt_search],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
The tool supports two input schemas depending on initialization:
|
||||
|
||||
### Dynamic File Path Schema
|
||||
```python
|
||||
class TXTSearchToolSchema(BaseModel):
|
||||
search_query: str # The semantic search query
|
||||
txt: str # Path to the text file to search
|
||||
```
|
||||
|
||||
### Fixed File Path Schema
|
||||
```python
|
||||
class FixedTXTSearchToolSchema(BaseModel):
|
||||
search_query: str # The semantic search query
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(self, txt: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
Initialize the TXT search tool.
|
||||
|
||||
Args:
|
||||
txt (Optional[str]): Fixed path to a text file. If provided, the tool will only search this file.
|
||||
**kwargs: Additional arguments passed to the parent RagTool
|
||||
"""
|
||||
|
||||
def _run(self, search_query: str, **kwargs: Any) -> Any:
|
||||
"""
|
||||
Perform semantic search on the text file.
|
||||
|
||||
Args:
|
||||
search_query (str): The semantic search query
|
||||
**kwargs: Additional arguments (including 'txt' for dynamic file path)
|
||||
|
||||
Returns:
|
||||
str: Relevant text passages based on semantic search
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Choose initialization method based on use case:
|
||||
- Use fixed file path when repeatedly searching the same document
|
||||
- Use dynamic file path when searching different documents
|
||||
2. Write clear, semantic search queries
|
||||
3. Handle potential file access errors in agent prompts
|
||||
4. Consider memory usage for large text files
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import TXTSearchTool
|
||||
|
||||
# Example 1: Fixed document search
|
||||
documentation_search = TXTSearchTool(txt="api_documentation.txt")
|
||||
|
||||
# Example 2: Dynamic document search
|
||||
flexible_search = TXTSearchTool()
|
||||
|
||||
# Create agents
|
||||
doc_analyst = Agent(
|
||||
role='Documentation Analyst',
|
||||
goal='Find relevant API documentation sections',
|
||||
backstory='Expert at analyzing technical documentation.',
|
||||
tools=[documentation_search]
|
||||
)
|
||||
|
||||
file_analyst = Agent(
|
||||
role='File Analyst',
|
||||
goal='Search through various text files',
|
||||
backstory='Specialist in finding information across multiple documents.',
|
||||
tools=[flexible_search]
|
||||
)
|
||||
|
||||
# Define tasks
|
||||
fixed_search_task = Task(
|
||||
description="""Find all API endpoints related to user authentication
|
||||
in the documentation.""",
|
||||
agent=doc_analyst
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "search_query": "user authentication API endpoints"
|
||||
# }
|
||||
|
||||
dynamic_search_task = Task(
|
||||
description="""Search through the logs.txt file for any database
|
||||
connection errors.""",
|
||||
agent=file_analyst
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "search_query": "database connection errors",
|
||||
# "txt": "logs.txt"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[doc_analyst, file_analyst],
|
||||
tasks=[fixed_search_task, dynamic_search_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool for semantic search capabilities
|
||||
- Supports both fixed and dynamic text file paths
|
||||
- Uses embeddings for semantic search
|
||||
- Optimized for text file analysis
|
||||
- Thread-safe operations
|
||||
- Automatically handles file loading and embedding
|
||||
@@ -1,159 +0,0 @@
|
||||
---
|
||||
title: YoutubeChannelSearchTool
|
||||
description: A semantic search tool for YouTube channel content using RAG capabilities
|
||||
icon: youtube
|
||||
---
|
||||
|
||||
## YoutubeChannelSearchTool
|
||||
|
||||
The YoutubeChannelSearchTool is a specialized Retrieval-Augmented Generation (RAG) tool that enables semantic search within YouTube channel content. It inherits from the base RagTool class and provides both fixed and dynamic YouTube channel searching capabilities.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import YoutubeChannelSearchTool
|
||||
|
||||
# Method 1: Dynamic channel handle
|
||||
youtube_search = YoutubeChannelSearchTool()
|
||||
|
||||
# Method 2: Fixed channel handle
|
||||
fixed_channel_search = YoutubeChannelSearchTool(youtube_channel_handle="@example_channel")
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Content Researcher',
|
||||
goal='Search through YouTube channel content semantically',
|
||||
backstory='Expert at finding relevant information in YouTube content.',
|
||||
tools=[youtube_search],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
The tool supports two input schemas depending on initialization:
|
||||
|
||||
### Dynamic Channel Schema
|
||||
```python
|
||||
class YoutubeChannelSearchToolSchema(BaseModel):
|
||||
search_query: str # The semantic search query
|
||||
youtube_channel_handle: str # YouTube channel handle (with or without @)
|
||||
```
|
||||
|
||||
### Fixed Channel Schema
|
||||
```python
|
||||
class FixedYoutubeChannelSearchToolSchema(BaseModel):
|
||||
search_query: str # The semantic search query
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(self, youtube_channel_handle: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
Initialize the YouTube channel search tool.
|
||||
|
||||
Args:
|
||||
youtube_channel_handle (Optional[str]): Fixed channel handle. If provided,
|
||||
the tool will only search this channel.
|
||||
**kwargs: Additional arguments passed to the parent RagTool
|
||||
"""
|
||||
|
||||
def _run(self, search_query: str, **kwargs: Any) -> Any:
|
||||
"""
|
||||
Perform semantic search on the YouTube channel content.
|
||||
|
||||
Args:
|
||||
search_query (str): The semantic search query
|
||||
**kwargs: Additional arguments (including 'youtube_channel_handle' for dynamic mode)
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the YouTube channel based on semantic search
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Choose initialization method based on use case:
|
||||
- Use fixed channel handle when repeatedly searching the same channel
|
||||
- Use dynamic handle when searching different channels
|
||||
2. Write clear, semantic search queries
|
||||
3. Channel handles can be provided with or without '@' prefix
|
||||
4. Consider content availability and channel size
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import YoutubeChannelSearchTool
|
||||
|
||||
# Example 1: Fixed channel search
|
||||
tech_channel_search = YoutubeChannelSearchTool(youtube_channel_handle="@TechChannel")
|
||||
|
||||
# Example 2: Dynamic channel search
|
||||
flexible_search = YoutubeChannelSearchTool()
|
||||
|
||||
# Create agents
|
||||
tech_analyst = Agent(
|
||||
role='Tech Content Analyst',
|
||||
goal='Find relevant tech tutorials and explanations',
|
||||
backstory='Expert at analyzing technical YouTube content.',
|
||||
tools=[tech_channel_search]
|
||||
)
|
||||
|
||||
content_researcher = Agent(
|
||||
role='Content Researcher',
|
||||
goal='Search across multiple YouTube channels',
|
||||
backstory='Specialist in finding information across various channels.',
|
||||
tools=[flexible_search]
|
||||
)
|
||||
|
||||
# Define tasks
|
||||
fixed_search_task = Task(
|
||||
description="""Find all tutorials related to machine learning
|
||||
basics in the channel.""",
|
||||
agent=tech_analyst
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "search_query": "machine learning basics tutorial"
|
||||
# }
|
||||
|
||||
dynamic_search_task = Task(
|
||||
description="""Search through the @AIResearch channel for
|
||||
content about neural networks.""",
|
||||
agent=content_researcher
|
||||
)
|
||||
|
||||
# The agent will use:
|
||||
# {
|
||||
# "search_query": "neural networks explanation",
|
||||
# "youtube_channel_handle": "@AIResearch"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[tech_analyst, content_researcher],
|
||||
tasks=[fixed_search_task, dynamic_search_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool for semantic search capabilities
|
||||
- Supports both fixed and dynamic YouTube channel handles
|
||||
- Automatically adds '@' prefix to channel handles if missing
|
||||
- Uses embeddings for semantic search
|
||||
- Thread-safe operations
|
||||
- Automatically handles YouTube content loading and embedding
|
||||
@@ -1,216 +0,0 @@
|
||||
---
|
||||
title: YoutubeVideoSearchTool
|
||||
description: A tool for semantic search within YouTube video content using RAG capabilities
|
||||
icon: video
|
||||
---
|
||||
|
||||
## YoutubeVideoSearchTool
|
||||
|
||||
The YoutubeVideoSearchTool enables semantic search capabilities for YouTube video content using Retrieval-Augmented Generation (RAG). It processes video content and allows searching through transcripts and metadata using natural language queries.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install 'crewai[tools]'
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import YoutubeVideoSearchTool
|
||||
|
||||
# Method 1: Initialize with specific video
|
||||
video_tool = YoutubeVideoSearchTool(
|
||||
youtube_video_url="https://www.youtube.com/watch?v=example"
|
||||
)
|
||||
|
||||
# Method 2: Initialize without video (specify at runtime)
|
||||
flexible_video_tool = YoutubeVideoSearchTool()
|
||||
|
||||
# Create an agent with the tool
|
||||
researcher = Agent(
|
||||
role='Video Researcher',
|
||||
goal='Search and analyze video content',
|
||||
backstory='Expert at finding relevant information in videos.',
|
||||
tools=[video_tool],
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Input Schema
|
||||
|
||||
### Fixed Video Schema (when URL provided during initialization)
|
||||
```python
|
||||
class FixedYoutubeVideoSearchToolSchema(BaseModel):
|
||||
search_query: str = Field(
|
||||
description="Mandatory search query you want to use to search the Youtube Video content"
|
||||
)
|
||||
```
|
||||
|
||||
### Flexible Video Schema (when URL provided at runtime)
|
||||
```python
|
||||
class YoutubeVideoSearchToolSchema(FixedYoutubeVideoSearchToolSchema):
|
||||
youtube_video_url: str = Field(
|
||||
description="Mandatory youtube_video_url path you want to search"
|
||||
)
|
||||
```
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
def __init__(
|
||||
self,
|
||||
youtube_video_url: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the YouTube video search tool.
|
||||
|
||||
Args:
|
||||
youtube_video_url (Optional[str]): URL of YouTube video (optional)
|
||||
**kwargs: Additional arguments for RAG tool configuration
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
search_query: str,
|
||||
**kwargs: Any
|
||||
) -> str:
|
||||
"""
|
||||
Execute semantic search on video content.
|
||||
|
||||
Args:
|
||||
search_query (str): Query to search in the video
|
||||
**kwargs: Additional arguments including youtube_video_url if not initialized
|
||||
|
||||
Returns:
|
||||
str: Relevant content from the video matching the query
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Video URL Management:
|
||||
- Use complete YouTube URLs
|
||||
- Verify video accessibility
|
||||
- Handle region restrictions
|
||||
|
||||
2. Search Optimization:
|
||||
- Use specific, focused queries
|
||||
- Consider video context
|
||||
- Test with sample queries first
|
||||
|
||||
3. Performance Considerations:
|
||||
- Pre-initialize for repeated searches
|
||||
- Handle long videos appropriately
|
||||
- Monitor processing time
|
||||
|
||||
4. Error Handling:
|
||||
- Verify video availability
|
||||
- Handle unavailable videos
|
||||
- Manage API limitations
|
||||
|
||||
## Integration Example
|
||||
|
||||
```python
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai_tools import YoutubeVideoSearchTool
|
||||
|
||||
# Initialize tool with specific video
|
||||
video_tool = YoutubeVideoSearchTool(
|
||||
youtube_video_url="https://www.youtube.com/watch?v=example"
|
||||
)
|
||||
|
||||
# Create agent
|
||||
researcher = Agent(
|
||||
role='Video Researcher',
|
||||
goal='Extract insights from video content',
|
||||
backstory='Expert at analyzing video content.',
|
||||
tools=[video_tool]
|
||||
)
|
||||
|
||||
# Define task
|
||||
research_task = Task(
|
||||
description="""Find all mentions of machine learning
|
||||
applications from the video content.""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# The tool will use:
|
||||
# {
|
||||
# "search_query": "machine learning applications"
|
||||
# }
|
||||
|
||||
# Create crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task]
|
||||
)
|
||||
|
||||
# Execute
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Dynamic Video Selection
|
||||
```python
|
||||
# Initialize without video URL
|
||||
flexible_tool = YoutubeVideoSearchTool()
|
||||
|
||||
# Search different videos
|
||||
tech_results = flexible_tool.run(
|
||||
search_query="quantum computing",
|
||||
youtube_video_url="https://youtube.com/watch?v=tech123"
|
||||
)
|
||||
|
||||
science_results = flexible_tool.run(
|
||||
search_query="particle physics",
|
||||
youtube_video_url="https://youtube.com/watch?v=science456"
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple Video Analysis
|
||||
```python
|
||||
# Create tools for different videos
|
||||
tech_tool = YoutubeVideoSearchTool(
|
||||
youtube_video_url="https://youtube.com/watch?v=tech123"
|
||||
)
|
||||
science_tool = YoutubeVideoSearchTool(
|
||||
youtube_video_url="https://youtube.com/watch?v=science456"
|
||||
)
|
||||
|
||||
# Create agent with multiple tools
|
||||
analyst = Agent(
|
||||
role='Content Analyst',
|
||||
goal='Cross-reference multiple videos',
|
||||
tools=[tech_tool, science_tool]
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
```python
|
||||
try:
|
||||
video_tool = YoutubeVideoSearchTool()
|
||||
results = video_tool.run(
|
||||
search_query="key concepts",
|
||||
youtube_video_url="https://youtube.com/watch?v=example"
|
||||
)
|
||||
print(results)
|
||||
except Exception as e:
|
||||
print(f"Error processing video: {str(e)}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Inherits from RagTool
|
||||
- Uses embedchain for processing
|
||||
- Supports semantic search
|
||||
- Dynamic video specification
|
||||
- Efficient content retrieval
|
||||
- Thread-safe operations
|
||||
- Maintains search context
|
||||
- Handles video transcripts
|
||||
- Processes video metadata
|
||||
- Memory-efficient processing
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "crewai"
|
||||
version = "0.86.0"
|
||||
version = "0.100.0"
|
||||
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
@@ -11,27 +11,22 @@ dependencies = [
|
||||
# Core Dependencies
|
||||
"pydantic>=2.4.2",
|
||||
"openai>=1.13.3",
|
||||
"litellm>=1.44.22",
|
||||
"litellm==1.59.8",
|
||||
"instructor>=1.3.3",
|
||||
|
||||
# Text Processing
|
||||
"pdfplumber>=0.11.4",
|
||||
"regex>=2024.9.11",
|
||||
|
||||
# Telemetry and Monitoring
|
||||
"opentelemetry-api>=1.22.0",
|
||||
"opentelemetry-sdk>=1.22.0",
|
||||
"opentelemetry-exporter-otlp-proto-http>=1.22.0",
|
||||
|
||||
# Data Handling
|
||||
"chromadb>=0.5.23",
|
||||
"openpyxl>=3.1.5",
|
||||
"pyvis>=0.3.2",
|
||||
|
||||
# Authentication and Security
|
||||
"auth0-python>=4.7.1",
|
||||
"python-dotenv>=1.0.0",
|
||||
|
||||
# Configuration and Utils
|
||||
"click>=8.1.7",
|
||||
"appdirs>=1.4.4",
|
||||
@@ -41,6 +36,7 @@ dependencies = [
|
||||
"tomli-w>=1.1.0",
|
||||
"tomli>=2.0.2",
|
||||
"blinker>=1.9.0",
|
||||
"json5>=0.10.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -49,7 +45,7 @@ Documentation = "https://docs.crewai.com"
|
||||
Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = ["crewai-tools>=0.17.0"]
|
||||
tools = ["crewai-tools>=0.32.1"]
|
||||
embeddings = [
|
||||
"tiktoken~=0.7.0"
|
||||
]
|
||||
|
||||
@@ -14,7 +14,7 @@ warnings.filterwarnings(
|
||||
category=UserWarning,
|
||||
module="pydantic.main",
|
||||
)
|
||||
__version__ = "0.86.0"
|
||||
__version__ = "0.100.0"
|
||||
__all__ = [
|
||||
"Agent",
|
||||
"Crew",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
@@ -8,7 +7,6 @@ from pydantic import Field, InstanceOf, PrivateAttr, model_validator
|
||||
from crewai.agents import CacheHandler
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||
from crewai.cli.constants import ENV_VARS, LITELLM_PARAMS
|
||||
from crewai.knowledge.knowledge import Knowledge
|
||||
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||
from crewai.knowledge.utils.knowledge_utils import extract_knowledge_context
|
||||
@@ -21,6 +19,7 @@ from crewai.tools.base_tool import Tool
|
||||
from crewai.utilities import Converter, Prompts
|
||||
from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE
|
||||
from crewai.utilities.converter import generate_model_description
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||
from crewai.utilities.training_handler import CrewTrainingHandler
|
||||
|
||||
@@ -62,6 +61,7 @@ class Agent(BaseAgent):
|
||||
tools: Tools at agents disposal
|
||||
step_callback: Callback to be executed after each step of the agent execution.
|
||||
knowledge_sources: Knowledge sources for the agent.
|
||||
embedder: Embedder configuration for the agent.
|
||||
"""
|
||||
|
||||
_times_executed: int = PrivateAttr(default=0)
|
||||
@@ -85,7 +85,7 @@ class Agent(BaseAgent):
|
||||
llm: Union[str, InstanceOf[LLM], Any] = Field(
|
||||
description="Language model that will run the agent.", default=None
|
||||
)
|
||||
function_calling_llm: Optional[Any] = Field(
|
||||
function_calling_llm: Optional[Union[str, InstanceOf[LLM], Any]] = Field(
|
||||
description="Language model that will run the agent.", default=None
|
||||
)
|
||||
system_template: Optional[str] = Field(
|
||||
@@ -123,105 +123,19 @@ class Agent(BaseAgent):
|
||||
default="safe",
|
||||
description="Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution).",
|
||||
)
|
||||
embedder_config: Optional[Dict[str, Any]] = Field(
|
||||
embedder: Optional[Dict[str, Any]] = Field(
|
||||
default=None,
|
||||
description="Embedder configuration for the agent.",
|
||||
)
|
||||
knowledge_sources: Optional[List[BaseKnowledgeSource]] = Field(
|
||||
default=None,
|
||||
description="Knowledge sources for the agent.",
|
||||
)
|
||||
_knowledge: Optional[Knowledge] = PrivateAttr(
|
||||
default=None,
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def post_init_setup(self):
|
||||
self._set_knowledge()
|
||||
self.agent_ops_agent_name = self.role
|
||||
unaccepted_attributes = [
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
"AWS_REGION_NAME",
|
||||
]
|
||||
|
||||
# Handle different cases for self.llm
|
||||
if isinstance(self.llm, str):
|
||||
# If it's a string, create an LLM instance
|
||||
self.llm = LLM(model=self.llm)
|
||||
elif isinstance(self.llm, LLM):
|
||||
# If it's already an LLM instance, keep it as is
|
||||
pass
|
||||
elif self.llm is None:
|
||||
# Determine the model name from environment variables or use default
|
||||
model_name = (
|
||||
os.environ.get("OPENAI_MODEL_NAME")
|
||||
or os.environ.get("MODEL")
|
||||
or "gpt-4o-mini"
|
||||
)
|
||||
llm_params = {"model": model_name}
|
||||
|
||||
api_base = os.environ.get("OPENAI_API_BASE") or os.environ.get(
|
||||
"OPENAI_BASE_URL"
|
||||
)
|
||||
if api_base:
|
||||
llm_params["base_url"] = api_base
|
||||
|
||||
set_provider = model_name.split("/")[0] if "/" in model_name else "openai"
|
||||
|
||||
# Iterate over all environment variables to find matching API keys or use defaults
|
||||
for provider, env_vars in ENV_VARS.items():
|
||||
if provider == set_provider:
|
||||
for env_var in env_vars:
|
||||
# Check if the environment variable is set
|
||||
key_name = env_var.get("key_name")
|
||||
if key_name and key_name not in unaccepted_attributes:
|
||||
env_value = os.environ.get(key_name)
|
||||
if env_value:
|
||||
key_name = key_name.lower()
|
||||
for pattern in LITELLM_PARAMS:
|
||||
if pattern in key_name:
|
||||
key_name = pattern
|
||||
break
|
||||
llm_params[key_name] = env_value
|
||||
# Check for default values if the environment variable is not set
|
||||
elif env_var.get("default", False):
|
||||
for key, value in env_var.items():
|
||||
if key not in ["prompt", "key_name", "default"]:
|
||||
# Only add default if the key is already set in os.environ
|
||||
if key in os.environ:
|
||||
llm_params[key] = value
|
||||
|
||||
self.llm = LLM(**llm_params)
|
||||
else:
|
||||
# For any other type, attempt to extract relevant attributes
|
||||
llm_params = {
|
||||
"model": getattr(self.llm, "model_name", None)
|
||||
or getattr(self.llm, "deployment_name", None)
|
||||
or str(self.llm),
|
||||
"temperature": getattr(self.llm, "temperature", None),
|
||||
"max_tokens": getattr(self.llm, "max_tokens", None),
|
||||
"logprobs": getattr(self.llm, "logprobs", None),
|
||||
"timeout": getattr(self.llm, "timeout", None),
|
||||
"max_retries": getattr(self.llm, "max_retries", None),
|
||||
"api_key": getattr(self.llm, "api_key", None),
|
||||
"base_url": getattr(self.llm, "base_url", None),
|
||||
"organization": getattr(self.llm, "organization", None),
|
||||
}
|
||||
# Remove None values to avoid passing unnecessary parameters
|
||||
llm_params = {k: v for k, v in llm_params.items() if v is not None}
|
||||
self.llm = LLM(**llm_params)
|
||||
|
||||
# Similar handling for function_calling_llm
|
||||
if self.function_calling_llm:
|
||||
if isinstance(self.function_calling_llm, str):
|
||||
self.function_calling_llm = LLM(model=self.function_calling_llm)
|
||||
elif not isinstance(self.function_calling_llm, LLM):
|
||||
self.function_calling_llm = LLM(
|
||||
model=getattr(self.function_calling_llm, "model_name", None)
|
||||
or getattr(self.function_calling_llm, "deployment_name", None)
|
||||
or str(self.function_calling_llm)
|
||||
)
|
||||
self.llm = create_llm(self.llm)
|
||||
if self.function_calling_llm and not isinstance(self.function_calling_llm, LLM):
|
||||
self.function_calling_llm = create_llm(self.function_calling_llm)
|
||||
|
||||
if not self.agent_executor:
|
||||
self._setup_agent_executor()
|
||||
@@ -243,10 +157,11 @@ class Agent(BaseAgent):
|
||||
if isinstance(self.knowledge_sources, list) and all(
|
||||
isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources
|
||||
):
|
||||
self._knowledge = Knowledge(
|
||||
self.knowledge = Knowledge(
|
||||
sources=self.knowledge_sources,
|
||||
embedder_config=self.embedder_config,
|
||||
embedder=self.embedder,
|
||||
collection_name=knowledge_agent_name,
|
||||
storage=self.knowledge_storage or None,
|
||||
)
|
||||
except (TypeError, ValueError) as e:
|
||||
raise ValueError(f"Invalid Knowledge Configuration: {str(e)}")
|
||||
@@ -305,8 +220,8 @@ class Agent(BaseAgent):
|
||||
if memory.strip() != "":
|
||||
task_prompt += self.i18n.slice("memory").format(memory=memory)
|
||||
|
||||
if self._knowledge:
|
||||
agent_knowledge_snippets = self._knowledge.query([task.prompt()])
|
||||
if self.knowledge:
|
||||
agent_knowledge_snippets = self.knowledge.query([task.prompt()])
|
||||
if agent_knowledge_snippets:
|
||||
agent_knowledge_context = extract_knowledge_context(
|
||||
agent_knowledge_snippets
|
||||
@@ -339,6 +254,9 @@ class Agent(BaseAgent):
|
||||
}
|
||||
)["output"]
|
||||
except Exception as e:
|
||||
if e.__class__.__module__.startswith("litellm"):
|
||||
# Do not retry on litellm errors
|
||||
raise e
|
||||
self._times_executed += 1
|
||||
if self._times_executed > self.max_retry_limit:
|
||||
raise e
|
||||
@@ -413,6 +331,7 @@ class Agent(BaseAgent):
|
||||
|
||||
def get_multimodal_tools(self) -> List[Tool]:
|
||||
from crewai.tools.agent_tools.add_image_tool import AddImageTool
|
||||
|
||||
return [AddImageTool()]
|
||||
|
||||
def get_code_execution_tools(self):
|
||||
|
||||
@@ -18,6 +18,8 @@ from pydantic_core import PydanticCustomError
|
||||
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
||||
from crewai.agents.cache.cache_handler import CacheHandler
|
||||
from crewai.agents.tools_handler import ToolsHandler
|
||||
from crewai.knowledge.knowledge import Knowledge
|
||||
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||
from crewai.tools import BaseTool
|
||||
from crewai.tools.base_tool import Tool
|
||||
from crewai.utilities import I18N, Logger, RPMController
|
||||
@@ -48,6 +50,8 @@ class BaseAgent(ABC, BaseModel):
|
||||
cache_handler (InstanceOf[CacheHandler]): An instance of the CacheHandler class.
|
||||
tools_handler (InstanceOf[ToolsHandler]): An instance of the ToolsHandler class.
|
||||
max_tokens: Maximum number of tokens for the agent to generate in a response.
|
||||
knowledge_sources: Knowledge sources for the agent.
|
||||
knowledge_storage: Custom knowledge storage for the agent.
|
||||
|
||||
|
||||
Methods:
|
||||
@@ -130,6 +134,17 @@ class BaseAgent(ABC, BaseModel):
|
||||
max_tokens: Optional[int] = Field(
|
||||
default=None, description="Maximum number of tokens for the agent's execution."
|
||||
)
|
||||
knowledge: Optional[Knowledge] = Field(
|
||||
default=None, description="Knowledge for the agent."
|
||||
)
|
||||
knowledge_sources: Optional[List[BaseKnowledgeSource]] = Field(
|
||||
default=None,
|
||||
description="Knowledge sources for the agent.",
|
||||
)
|
||||
knowledge_storage: Optional[Any] = Field(
|
||||
default=None,
|
||||
description="Custom knowledge storage for the agent.",
|
||||
)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -256,13 +271,44 @@ class BaseAgent(ABC, BaseModel):
|
||||
"tools_handler",
|
||||
"cache_handler",
|
||||
"llm",
|
||||
"knowledge_sources",
|
||||
"knowledge_storage",
|
||||
"knowledge",
|
||||
}
|
||||
|
||||
# Copy llm and clear callbacks
|
||||
# Copy llm
|
||||
existing_llm = shallow_copy(self.llm)
|
||||
copied_knowledge = shallow_copy(self.knowledge)
|
||||
copied_knowledge_storage = shallow_copy(self.knowledge_storage)
|
||||
# Properly copy knowledge sources if they exist
|
||||
existing_knowledge_sources = None
|
||||
if self.knowledge_sources:
|
||||
# Create a shared storage instance for all knowledge sources
|
||||
shared_storage = (
|
||||
self.knowledge_sources[0].storage if self.knowledge_sources else None
|
||||
)
|
||||
|
||||
existing_knowledge_sources = []
|
||||
for source in self.knowledge_sources:
|
||||
copied_source = (
|
||||
source.model_copy()
|
||||
if hasattr(source, "model_copy")
|
||||
else shallow_copy(source)
|
||||
)
|
||||
# Ensure all copied sources use the same storage instance
|
||||
copied_source.storage = shared_storage
|
||||
existing_knowledge_sources.append(copied_source)
|
||||
|
||||
copied_data = self.model_dump(exclude=exclude)
|
||||
copied_data = {k: v for k, v in copied_data.items() if v is not None}
|
||||
copied_agent = type(self)(**copied_data, llm=existing_llm, tools=self.tools)
|
||||
copied_agent = type(self)(
|
||||
**copied_data,
|
||||
llm=existing_llm,
|
||||
tools=self.tools,
|
||||
knowledge_sources=existing_knowledge_sources,
|
||||
knowledge=copied_knowledge,
|
||||
knowledge_storage=copied_knowledge_storage,
|
||||
)
|
||||
|
||||
return copied_agent
|
||||
|
||||
|
||||
@@ -19,15 +19,10 @@ class CrewAgentExecutorMixin:
|
||||
agent: Optional["BaseAgent"]
|
||||
task: Optional["Task"]
|
||||
iterations: int
|
||||
have_forced_answer: bool
|
||||
max_iter: int
|
||||
_i18n: I18N
|
||||
_printer: Printer = Printer()
|
||||
|
||||
def _should_force_answer(self) -> bool:
|
||||
"""Determine if a forced answer is required based on iteration count."""
|
||||
return (self.iterations >= self.max_iter) and not self.have_forced_answer
|
||||
|
||||
def _create_short_term_memory(self, output) -> None:
|
||||
"""Create and save a short-term memory item if conditions are met."""
|
||||
if (
|
||||
|
||||
@@ -25,7 +25,7 @@ class OutputConverter(BaseModel, ABC):
|
||||
llm: Any = Field(description="The language model to be used to convert the text.")
|
||||
model: Any = Field(description="The model to be used to convert the text.")
|
||||
instructions: str = Field(description="Conversion instructions to the LLM.")
|
||||
max_attempts: Optional[int] = Field(
|
||||
max_attempts: int = Field(
|
||||
description="Max number of attempts to try to get the output formatted.",
|
||||
default=3,
|
||||
)
|
||||
|
||||
@@ -2,25 +2,26 @@ from crewai.types.usage_metrics import UsageMetrics
|
||||
|
||||
|
||||
class TokenProcess:
|
||||
total_tokens: int = 0
|
||||
prompt_tokens: int = 0
|
||||
cached_prompt_tokens: int = 0
|
||||
completion_tokens: int = 0
|
||||
successful_requests: int = 0
|
||||
def __init__(self) -> None:
|
||||
self.total_tokens: int = 0
|
||||
self.prompt_tokens: int = 0
|
||||
self.cached_prompt_tokens: int = 0
|
||||
self.completion_tokens: int = 0
|
||||
self.successful_requests: int = 0
|
||||
|
||||
def sum_prompt_tokens(self, tokens: int):
|
||||
self.prompt_tokens = self.prompt_tokens + tokens
|
||||
self.total_tokens = self.total_tokens + tokens
|
||||
def sum_prompt_tokens(self, tokens: int) -> None:
|
||||
self.prompt_tokens += tokens
|
||||
self.total_tokens += tokens
|
||||
|
||||
def sum_completion_tokens(self, tokens: int):
|
||||
self.completion_tokens = self.completion_tokens + tokens
|
||||
self.total_tokens = self.total_tokens + tokens
|
||||
def sum_completion_tokens(self, tokens: int) -> None:
|
||||
self.completion_tokens += tokens
|
||||
self.total_tokens += tokens
|
||||
|
||||
def sum_cached_prompt_tokens(self, tokens: int):
|
||||
self.cached_prompt_tokens = self.cached_prompt_tokens + tokens
|
||||
def sum_cached_prompt_tokens(self, tokens: int) -> None:
|
||||
self.cached_prompt_tokens += tokens
|
||||
|
||||
def sum_successful_requests(self, requests: int):
|
||||
self.successful_requests = self.successful_requests + requests
|
||||
def sum_successful_requests(self, requests: int) -> None:
|
||||
self.successful_requests += requests
|
||||
|
||||
def get_summary(self) -> UsageMetrics:
|
||||
return UsageMetrics(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Union
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.agents.agent_builder.base_agent_executor_mixin import CrewAgentExecutorMixin
|
||||
@@ -13,6 +13,7 @@ from crewai.agents.parser import (
|
||||
OutputParserException,
|
||||
)
|
||||
from crewai.agents.tools_handler import ToolsHandler
|
||||
from crewai.llm import LLM
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.tool_usage import ToolUsage, ToolUsageErrorException
|
||||
from crewai.utilities import I18N, Printer
|
||||
@@ -50,11 +51,11 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
original_tools: List[Any] = [],
|
||||
function_calling_llm: Any = None,
|
||||
respect_context_window: bool = False,
|
||||
request_within_rpm_limit: Any = None,
|
||||
request_within_rpm_limit: Optional[Callable[[], bool]] = None,
|
||||
callbacks: List[Any] = [],
|
||||
):
|
||||
self._i18n: I18N = I18N()
|
||||
self.llm = llm
|
||||
self.llm: LLM = llm
|
||||
self.task = task
|
||||
self.agent = agent
|
||||
self.crew = crew
|
||||
@@ -77,14 +78,11 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
self.messages: List[Dict[str, str]] = []
|
||||
self.iterations = 0
|
||||
self.log_error_after = 3
|
||||
self.have_forced_answer = False
|
||||
self.tool_name_to_tool_map: Dict[str, BaseTool] = {
|
||||
tool.name: tool for tool in self.tools
|
||||
}
|
||||
if self.llm.stop:
|
||||
self.llm.stop = list(set(self.llm.stop + self.stop))
|
||||
else:
|
||||
self.llm.stop = self.stop
|
||||
self.stop = stop_words
|
||||
self.llm.stop = list(set(self.llm.stop + self.stop))
|
||||
|
||||
def invoke(self, inputs: Dict[str, str]) -> Dict[str, Any]:
|
||||
if "system" in self.prompt:
|
||||
@@ -99,7 +97,16 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
self._show_start_logs()
|
||||
|
||||
self.ask_for_human_input = bool(inputs.get("ask_for_human_input", False))
|
||||
formatted_answer = self._invoke_loop()
|
||||
|
||||
try:
|
||||
formatted_answer = self._invoke_loop()
|
||||
except Exception as e:
|
||||
if e.__class__.__module__.startswith("litellm"):
|
||||
# Do not retry on litellm errors
|
||||
raise e
|
||||
else:
|
||||
self._handle_unknown_error(e)
|
||||
raise e
|
||||
|
||||
if self.ask_for_human_input:
|
||||
formatted_answer = self._handle_human_feedback(formatted_answer)
|
||||
@@ -108,106 +115,173 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
self._create_long_term_memory(formatted_answer)
|
||||
return {"output": formatted_answer.output}
|
||||
|
||||
def _invoke_loop(self, formatted_answer=None):
|
||||
try:
|
||||
while not isinstance(formatted_answer, AgentFinish):
|
||||
if not self.request_within_rpm_limit or self.request_within_rpm_limit():
|
||||
answer = self.llm.call(
|
||||
self.messages,
|
||||
callbacks=self.callbacks,
|
||||
def _invoke_loop(self):
|
||||
"""
|
||||
Main loop to invoke the agent's thought process until it reaches a conclusion
|
||||
or the maximum number of iterations is reached.
|
||||
"""
|
||||
formatted_answer = None
|
||||
while not isinstance(formatted_answer, AgentFinish):
|
||||
try:
|
||||
if self._has_reached_max_iterations():
|
||||
formatted_answer = self._handle_max_iterations_exceeded(
|
||||
formatted_answer
|
||||
)
|
||||
break
|
||||
|
||||
self._enforce_rpm_limit()
|
||||
|
||||
answer = self._get_llm_response()
|
||||
formatted_answer = self._process_llm_response(answer)
|
||||
|
||||
if isinstance(formatted_answer, AgentAction):
|
||||
tool_result = self._execute_tool_and_check_finality(
|
||||
formatted_answer
|
||||
)
|
||||
formatted_answer = self._handle_agent_action(
|
||||
formatted_answer, tool_result
|
||||
)
|
||||
|
||||
if answer is None or answer == "":
|
||||
self._printer.print(
|
||||
content="Received None or empty response from LLM call.",
|
||||
color="red",
|
||||
)
|
||||
raise ValueError(
|
||||
"Invalid response from LLM call - None or empty."
|
||||
)
|
||||
self._invoke_step_callback(formatted_answer)
|
||||
self._append_message(formatted_answer.text, role="assistant")
|
||||
|
||||
if not self.use_stop_words:
|
||||
try:
|
||||
self._format_answer(answer)
|
||||
except OutputParserException as e:
|
||||
if (
|
||||
FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE
|
||||
in e.error
|
||||
):
|
||||
answer = answer.split("Observation:")[0].strip()
|
||||
except OutputParserException as e:
|
||||
formatted_answer = self._handle_output_parser_exception(e)
|
||||
|
||||
self.iterations += 1
|
||||
formatted_answer = self._format_answer(answer)
|
||||
|
||||
if isinstance(formatted_answer, AgentAction):
|
||||
tool_result = self._execute_tool_and_check_finality(
|
||||
formatted_answer
|
||||
)
|
||||
|
||||
# Directly append the result to the messages if the
|
||||
# tool is "Add image to content" in case of multimodal
|
||||
# agents
|
||||
if formatted_answer.tool == self._i18n.tools("add_image")["name"]:
|
||||
self.messages.append(tool_result.result)
|
||||
continue
|
||||
|
||||
else:
|
||||
if self.step_callback:
|
||||
self.step_callback(tool_result)
|
||||
|
||||
formatted_answer.text += f"\nObservation: {tool_result.result}"
|
||||
|
||||
formatted_answer.result = tool_result.result
|
||||
if tool_result.result_as_answer:
|
||||
return AgentFinish(
|
||||
thought="",
|
||||
output=tool_result.result,
|
||||
text=formatted_answer.text,
|
||||
)
|
||||
self._show_logs(formatted_answer)
|
||||
|
||||
if self.step_callback:
|
||||
self.step_callback(formatted_answer)
|
||||
|
||||
if self._should_force_answer():
|
||||
if self.have_forced_answer:
|
||||
return AgentFinish(
|
||||
thought="",
|
||||
output=self._i18n.errors(
|
||||
"force_final_answer_error"
|
||||
).format(formatted_answer.text),
|
||||
text=formatted_answer.text,
|
||||
)
|
||||
else:
|
||||
formatted_answer.text += (
|
||||
f'\n{self._i18n.errors("force_final_answer")}'
|
||||
)
|
||||
self.have_forced_answer = True
|
||||
self.messages.append(
|
||||
self._format_msg(formatted_answer.text, role="assistant")
|
||||
)
|
||||
|
||||
except OutputParserException as e:
|
||||
self.messages.append({"role": "user", "content": e.error})
|
||||
if self.iterations > self.log_error_after:
|
||||
self._printer.print(
|
||||
content=f"Error parsing LLM output, agent will retry: {e.error}",
|
||||
color="red",
|
||||
)
|
||||
return self._invoke_loop(formatted_answer)
|
||||
|
||||
except Exception as e:
|
||||
if LLMContextLengthExceededException(str(e))._is_context_limit_error(
|
||||
str(e)
|
||||
):
|
||||
self._handle_context_length()
|
||||
return self._invoke_loop(formatted_answer)
|
||||
else:
|
||||
raise e
|
||||
except Exception as e:
|
||||
if e.__class__.__module__.startswith("litellm"):
|
||||
# Do not retry on litellm errors
|
||||
raise e
|
||||
if self._is_context_length_exceeded(e):
|
||||
self._handle_context_length()
|
||||
continue
|
||||
else:
|
||||
self._handle_unknown_error(e)
|
||||
raise e
|
||||
finally:
|
||||
self.iterations += 1
|
||||
|
||||
self._show_logs(formatted_answer)
|
||||
return formatted_answer
|
||||
|
||||
def _handle_unknown_error(self, exception: Exception) -> None:
|
||||
"""Handle unknown errors by informing the user."""
|
||||
self._printer.print(
|
||||
content="An unknown error occurred. Please check the details below.",
|
||||
color="red",
|
||||
)
|
||||
self._printer.print(
|
||||
content=f"Error details: {exception}",
|
||||
color="red",
|
||||
)
|
||||
|
||||
def _has_reached_max_iterations(self) -> bool:
|
||||
"""Check if the maximum number of iterations has been reached."""
|
||||
return self.iterations >= self.max_iter
|
||||
|
||||
def _enforce_rpm_limit(self) -> None:
|
||||
"""Enforce the requests per minute (RPM) limit if applicable."""
|
||||
if self.request_within_rpm_limit:
|
||||
self.request_within_rpm_limit()
|
||||
|
||||
def _get_llm_response(self) -> str:
|
||||
"""Call the LLM and return the response, handling any invalid responses."""
|
||||
try:
|
||||
answer = self.llm.call(
|
||||
self.messages,
|
||||
callbacks=self.callbacks,
|
||||
)
|
||||
except Exception as e:
|
||||
self._printer.print(
|
||||
content=f"Error during LLM call: {e}",
|
||||
color="red",
|
||||
)
|
||||
raise e
|
||||
|
||||
if not answer:
|
||||
self._printer.print(
|
||||
content="Received None or empty response from LLM call.",
|
||||
color="red",
|
||||
)
|
||||
raise ValueError("Invalid response from LLM call - None or empty.")
|
||||
|
||||
return answer
|
||||
|
||||
def _process_llm_response(self, answer: str) -> Union[AgentAction, AgentFinish]:
|
||||
"""Process the LLM response and format it into an AgentAction or AgentFinish."""
|
||||
if not self.use_stop_words:
|
||||
try:
|
||||
# Preliminary parsing to check for errors.
|
||||
self._format_answer(answer)
|
||||
except OutputParserException as e:
|
||||
if FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE in e.error:
|
||||
answer = answer.split("Observation:")[0].strip()
|
||||
|
||||
return self._format_answer(answer)
|
||||
|
||||
def _handle_agent_action(
|
||||
self, formatted_answer: AgentAction, tool_result: ToolResult
|
||||
) -> Union[AgentAction, AgentFinish]:
|
||||
"""Handle the AgentAction, execute tools, and process the results."""
|
||||
add_image_tool = self._i18n.tools("add_image")
|
||||
if (
|
||||
isinstance(add_image_tool, dict)
|
||||
and formatted_answer.tool.casefold().strip()
|
||||
== add_image_tool.get("name", "").casefold().strip()
|
||||
):
|
||||
self.messages.append(tool_result.result)
|
||||
return formatted_answer # Continue the loop
|
||||
|
||||
if self.step_callback:
|
||||
self.step_callback(tool_result)
|
||||
|
||||
formatted_answer.text += f"\nObservation: {tool_result.result}"
|
||||
formatted_answer.result = tool_result.result
|
||||
|
||||
if tool_result.result_as_answer:
|
||||
return AgentFinish(
|
||||
thought="",
|
||||
output=tool_result.result,
|
||||
text=formatted_answer.text,
|
||||
)
|
||||
|
||||
self._show_logs(formatted_answer)
|
||||
return formatted_answer
|
||||
|
||||
def _invoke_step_callback(self, formatted_answer) -> None:
|
||||
"""Invoke the step callback if it exists."""
|
||||
if self.step_callback:
|
||||
self.step_callback(formatted_answer)
|
||||
|
||||
def _append_message(self, text: str, role: str = "assistant") -> None:
|
||||
"""Append a message to the message list with the given role."""
|
||||
self.messages.append(self._format_msg(text, role=role))
|
||||
|
||||
def _handle_output_parser_exception(self, e: OutputParserException) -> AgentAction:
|
||||
"""Handle OutputParserException by updating messages and formatted_answer."""
|
||||
self.messages.append({"role": "user", "content": e.error})
|
||||
|
||||
formatted_answer = AgentAction(
|
||||
text=e.error,
|
||||
tool="",
|
||||
tool_input="",
|
||||
thought="",
|
||||
)
|
||||
|
||||
if self.iterations > self.log_error_after:
|
||||
self._printer.print(
|
||||
content=f"Error parsing LLM output, agent will retry: {e.error}",
|
||||
color="red",
|
||||
)
|
||||
|
||||
return formatted_answer
|
||||
|
||||
def _is_context_length_exceeded(self, exception: Exception) -> bool:
|
||||
"""Check if the exception is due to context length exceeding."""
|
||||
return LLMContextLengthExceededException(
|
||||
str(exception)
|
||||
)._is_context_limit_error(str(exception))
|
||||
|
||||
def _show_start_logs(self):
|
||||
if self.agent is None:
|
||||
raise ValueError("Agent cannot be None")
|
||||
@@ -272,7 +346,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
agent=self.agent,
|
||||
action=agent_action,
|
||||
)
|
||||
tool_calling = tool_usage.parse(agent_action.text)
|
||||
tool_calling = tool_usage.parse_tool_calling(agent_action.text)
|
||||
|
||||
if isinstance(tool_calling, ToolUsageErrorException):
|
||||
tool_result = tool_calling.message
|
||||
@@ -487,3 +561,45 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
self.ask_for_human_input = False
|
||||
|
||||
return formatted_answer
|
||||
|
||||
def _handle_max_iterations_exceeded(self, formatted_answer):
|
||||
"""
|
||||
Handles the case when the maximum number of iterations is exceeded.
|
||||
Performs one more LLM call to get the final answer.
|
||||
|
||||
Parameters:
|
||||
formatted_answer: The last formatted answer from the agent.
|
||||
|
||||
Returns:
|
||||
The final formatted answer after exceeding max iterations.
|
||||
"""
|
||||
self._printer.print(
|
||||
content="Maximum iterations reached. Requesting final answer.",
|
||||
color="yellow",
|
||||
)
|
||||
|
||||
if formatted_answer and hasattr(formatted_answer, "text"):
|
||||
assistant_message = (
|
||||
formatted_answer.text + f'\n{self._i18n.errors("force_final_answer")}'
|
||||
)
|
||||
else:
|
||||
assistant_message = self._i18n.errors("force_final_answer")
|
||||
|
||||
self.messages.append(self._format_msg(assistant_message, role="assistant"))
|
||||
|
||||
# Perform one more LLM call to get the final answer
|
||||
answer = self.llm.call(
|
||||
self.messages,
|
||||
callbacks=self.callbacks,
|
||||
)
|
||||
|
||||
if answer is None or answer == "":
|
||||
self._printer.print(
|
||||
content="Received None or empty response from LLM call.",
|
||||
color="red",
|
||||
)
|
||||
raise ValueError("Invalid response from LLM call - None or empty.")
|
||||
|
||||
formatted_answer = self._format_answer(answer)
|
||||
# Return the formatted answer, regardless of its type
|
||||
return formatted_answer
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import os
|
||||
from importlib.metadata import version as get_version
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import click
|
||||
|
||||
from crewai.cli.add_crew_to_flow import add_crew_to_flow
|
||||
from crewai.cli.create_crew import create_crew
|
||||
from crewai.cli.create_flow import create_flow
|
||||
from crewai.cli.crew_chat import run_chat
|
||||
from crewai.memory.storage.kickoff_task_outputs_storage import (
|
||||
KickoffTaskOutputsSQLiteStorage,
|
||||
)
|
||||
@@ -342,5 +344,18 @@ def flow_add_crew(crew_name):
|
||||
add_crew_to_flow(crew_name)
|
||||
|
||||
|
||||
@crewai.command()
|
||||
def chat():
|
||||
"""
|
||||
Start a conversation with the Crew, collecting user-supplied inputs,
|
||||
and using the Chat LLM to generate responses.
|
||||
"""
|
||||
click.secho(
|
||||
"\nStarting a conversation with the Crew\n" "Type 'exit' or Ctrl+C to quit.\n",
|
||||
)
|
||||
|
||||
run_chat()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
crewai()
|
||||
|
||||
@@ -17,6 +17,12 @@ ENV_VARS = {
|
||||
"key_name": "GEMINI_API_KEY",
|
||||
}
|
||||
],
|
||||
"nvidia_nim": [
|
||||
{
|
||||
"prompt": "Enter your NVIDIA API key (press Enter to skip)",
|
||||
"key_name": "NVIDIA_NIM_API_KEY",
|
||||
}
|
||||
],
|
||||
"groq": [
|
||||
{
|
||||
"prompt": "Enter your GROQ API key (press Enter to skip)",
|
||||
@@ -85,6 +91,12 @@ ENV_VARS = {
|
||||
"key_name": "CEREBRAS_API_KEY",
|
||||
},
|
||||
],
|
||||
"sambanova": [
|
||||
{
|
||||
"prompt": "Enter your SambaNovaCloud API key (press Enter to skip)",
|
||||
"key_name": "SAMBANOVA_API_KEY",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -92,12 +104,14 @@ PROVIDERS = [
|
||||
"openai",
|
||||
"anthropic",
|
||||
"gemini",
|
||||
"nvidia_nim",
|
||||
"groq",
|
||||
"ollama",
|
||||
"watson",
|
||||
"bedrock",
|
||||
"azure",
|
||||
"cerebras",
|
||||
"sambanova",
|
||||
]
|
||||
|
||||
MODELS = {
|
||||
@@ -114,6 +128,75 @@ MODELS = {
|
||||
"gemini/gemini-gemma-2-9b-it",
|
||||
"gemini/gemini-gemma-2-27b-it",
|
||||
],
|
||||
"nvidia_nim": [
|
||||
"nvidia_nim/nvidia/mistral-nemo-minitron-8b-8k-instruct",
|
||||
"nvidia_nim/nvidia/nemotron-4-mini-hindi-4b-instruct",
|
||||
"nvidia_nim/nvidia/llama-3.1-nemotron-70b-instruct",
|
||||
"nvidia_nim/nvidia/llama3-chatqa-1.5-8b",
|
||||
"nvidia_nim/nvidia/llama3-chatqa-1.5-70b",
|
||||
"nvidia_nim/nvidia/vila",
|
||||
"nvidia_nim/nvidia/neva-22",
|
||||
"nvidia_nim/nvidia/nemotron-mini-4b-instruct",
|
||||
"nvidia_nim/nvidia/usdcode-llama3-70b-instruct",
|
||||
"nvidia_nim/nvidia/nemotron-4-340b-instruct",
|
||||
"nvidia_nim/meta/codellama-70b",
|
||||
"nvidia_nim/meta/llama2-70b",
|
||||
"nvidia_nim/meta/llama3-8b-instruct",
|
||||
"nvidia_nim/meta/llama3-70b-instruct",
|
||||
"nvidia_nim/meta/llama-3.1-8b-instruct",
|
||||
"nvidia_nim/meta/llama-3.1-70b-instruct",
|
||||
"nvidia_nim/meta/llama-3.1-405b-instruct",
|
||||
"nvidia_nim/meta/llama-3.2-1b-instruct",
|
||||
"nvidia_nim/meta/llama-3.2-3b-instruct",
|
||||
"nvidia_nim/meta/llama-3.2-11b-vision-instruct",
|
||||
"nvidia_nim/meta/llama-3.2-90b-vision-instruct",
|
||||
"nvidia_nim/meta/llama-3.1-70b-instruct",
|
||||
"nvidia_nim/google/gemma-7b",
|
||||
"nvidia_nim/google/gemma-2b",
|
||||
"nvidia_nim/google/codegemma-7b",
|
||||
"nvidia_nim/google/codegemma-1.1-7b",
|
||||
"nvidia_nim/google/recurrentgemma-2b",
|
||||
"nvidia_nim/google/gemma-2-9b-it",
|
||||
"nvidia_nim/google/gemma-2-27b-it",
|
||||
"nvidia_nim/google/gemma-2-2b-it",
|
||||
"nvidia_nim/google/deplot",
|
||||
"nvidia_nim/google/paligemma",
|
||||
"nvidia_nim/mistralai/mistral-7b-instruct-v0.2",
|
||||
"nvidia_nim/mistralai/mixtral-8x7b-instruct-v0.1",
|
||||
"nvidia_nim/mistralai/mistral-large",
|
||||
"nvidia_nim/mistralai/mixtral-8x22b-instruct-v0.1",
|
||||
"nvidia_nim/mistralai/mistral-7b-instruct-v0.3",
|
||||
"nvidia_nim/nv-mistralai/mistral-nemo-12b-instruct",
|
||||
"nvidia_nim/mistralai/mamba-codestral-7b-v0.1",
|
||||
"nvidia_nim/microsoft/phi-3-mini-128k-instruct",
|
||||
"nvidia_nim/microsoft/phi-3-mini-4k-instruct",
|
||||
"nvidia_nim/microsoft/phi-3-small-8k-instruct",
|
||||
"nvidia_nim/microsoft/phi-3-small-128k-instruct",
|
||||
"nvidia_nim/microsoft/phi-3-medium-4k-instruct",
|
||||
"nvidia_nim/microsoft/phi-3-medium-128k-instruct",
|
||||
"nvidia_nim/microsoft/phi-3.5-mini-instruct",
|
||||
"nvidia_nim/microsoft/phi-3.5-moe-instruct",
|
||||
"nvidia_nim/microsoft/kosmos-2",
|
||||
"nvidia_nim/microsoft/phi-3-vision-128k-instruct",
|
||||
"nvidia_nim/microsoft/phi-3.5-vision-instruct",
|
||||
"nvidia_nim/databricks/dbrx-instruct",
|
||||
"nvidia_nim/snowflake/arctic",
|
||||
"nvidia_nim/aisingapore/sea-lion-7b-instruct",
|
||||
"nvidia_nim/ibm/granite-8b-code-instruct",
|
||||
"nvidia_nim/ibm/granite-34b-code-instruct",
|
||||
"nvidia_nim/ibm/granite-3.0-8b-instruct",
|
||||
"nvidia_nim/ibm/granite-3.0-3b-a800m-instruct",
|
||||
"nvidia_nim/mediatek/breeze-7b-instruct",
|
||||
"nvidia_nim/upstage/solar-10.7b-instruct",
|
||||
"nvidia_nim/writer/palmyra-med-70b-32k",
|
||||
"nvidia_nim/writer/palmyra-med-70b",
|
||||
"nvidia_nim/writer/palmyra-fin-70b-32k",
|
||||
"nvidia_nim/01-ai/yi-large",
|
||||
"nvidia_nim/deepseek-ai/deepseek-coder-6.7b-instruct",
|
||||
"nvidia_nim/rakuten/rakutenai-7b-instruct",
|
||||
"nvidia_nim/rakuten/rakutenai-7b-chat",
|
||||
"nvidia_nim/baichuan-inc/baichuan2-13b-chat",
|
||||
],
|
||||
"groq": [
|
||||
"groq/llama-3.1-8b-instant",
|
||||
"groq/llama-3.1-70b-versatile",
|
||||
@@ -156,8 +239,23 @@ MODELS = {
|
||||
"bedrock/mistral.mistral-7b-instruct-v0:2",
|
||||
"bedrock/mistral.mixtral-8x7b-instruct-v0:1",
|
||||
],
|
||||
"sambanova": [
|
||||
"sambanova/Meta-Llama-3.3-70B-Instruct",
|
||||
"sambanova/QwQ-32B-Preview",
|
||||
"sambanova/Qwen2.5-72B-Instruct",
|
||||
"sambanova/Qwen2.5-Coder-32B-Instruct",
|
||||
"sambanova/Meta-Llama-3.1-405B-Instruct",
|
||||
"sambanova/Meta-Llama-3.1-70B-Instruct",
|
||||
"sambanova/Meta-Llama-3.1-8B-Instruct",
|
||||
"sambanova/Llama-3.2-90B-Vision-Instruct",
|
||||
"sambanova/Llama-3.2-11B-Vision-Instruct",
|
||||
"sambanova/Meta-Llama-3.2-3B-Instruct",
|
||||
"sambanova/Meta-Llama-3.2-1B-Instruct",
|
||||
],
|
||||
}
|
||||
|
||||
DEFAULT_LLM_MODEL = "gpt-4o-mini"
|
||||
|
||||
JSON_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
|
||||
|
||||
|
||||
|
||||
536
src/crewai/cli/crew_chat.py
Normal file
536
src/crewai/cli/crew_chat.py
Normal file
@@ -0,0 +1,536 @@
|
||||
import json
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
import click
|
||||
import tomli
|
||||
from packaging import version
|
||||
|
||||
from crewai.cli.utils import read_toml
|
||||
from crewai.cli.version import get_crewai_version
|
||||
from crewai.crew import Crew
|
||||
from crewai.llm import LLM
|
||||
from crewai.types.crew_chat import ChatInputField, ChatInputs
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
|
||||
MIN_REQUIRED_VERSION = "0.98.0"
|
||||
|
||||
|
||||
def check_conversational_crews_version(
|
||||
crewai_version: str, pyproject_data: dict
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the installed crewAI version supports conversational crews.
|
||||
|
||||
Args:
|
||||
crewai_version: The current version of crewAI.
|
||||
pyproject_data: Dictionary containing pyproject.toml data.
|
||||
|
||||
Returns:
|
||||
bool: True if version check passes, False otherwise.
|
||||
"""
|
||||
try:
|
||||
if version.parse(crewai_version) < version.parse(MIN_REQUIRED_VERSION):
|
||||
click.secho(
|
||||
"You are using an older version of crewAI that doesn't support conversational crews. "
|
||||
"Run 'uv upgrade crewai' to get the latest version.",
|
||||
fg="red",
|
||||
)
|
||||
return False
|
||||
except version.InvalidVersion:
|
||||
click.secho("Invalid crewAI version format detected.", fg="red")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def run_chat():
|
||||
"""
|
||||
Runs an interactive chat loop using the Crew's chat LLM with function calling.
|
||||
Incorporates crew_name, crew_description, and input fields to build a tool schema.
|
||||
Exits if crew_name or crew_description are missing.
|
||||
"""
|
||||
crewai_version = get_crewai_version()
|
||||
pyproject_data = read_toml()
|
||||
|
||||
if not check_conversational_crews_version(crewai_version, pyproject_data):
|
||||
return
|
||||
|
||||
crew, crew_name = load_crew_and_name()
|
||||
chat_llm = initialize_chat_llm(crew)
|
||||
if not chat_llm:
|
||||
return
|
||||
|
||||
# Indicate that the crew is being analyzed
|
||||
click.secho(
|
||||
"\nAnalyzing crew and required inputs - this may take 3 to 30 seconds "
|
||||
"depending on the complexity of your crew.",
|
||||
fg="white",
|
||||
)
|
||||
|
||||
# Start loading indicator
|
||||
loading_complete = threading.Event()
|
||||
loading_thread = threading.Thread(target=show_loading, args=(loading_complete,))
|
||||
loading_thread.start()
|
||||
|
||||
try:
|
||||
crew_chat_inputs = generate_crew_chat_inputs(crew, crew_name, chat_llm)
|
||||
crew_tool_schema = generate_crew_tool_schema(crew_chat_inputs)
|
||||
system_message = build_system_message(crew_chat_inputs)
|
||||
|
||||
# Call the LLM to generate the introductory message
|
||||
introductory_message = chat_llm.call(
|
||||
messages=[{"role": "system", "content": system_message}]
|
||||
)
|
||||
finally:
|
||||
# Stop loading indicator
|
||||
loading_complete.set()
|
||||
loading_thread.join()
|
||||
|
||||
# Indicate that the analysis is complete
|
||||
click.secho("\nFinished analyzing crew.\n", fg="white")
|
||||
|
||||
click.secho(f"Assistant: {introductory_message}\n", fg="green")
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_message},
|
||||
{"role": "assistant", "content": introductory_message},
|
||||
]
|
||||
|
||||
available_functions = {
|
||||
crew_chat_inputs.crew_name: create_tool_function(crew, messages),
|
||||
}
|
||||
|
||||
chat_loop(chat_llm, messages, crew_tool_schema, available_functions)
|
||||
|
||||
|
||||
def show_loading(event: threading.Event):
|
||||
"""Display animated loading dots while processing."""
|
||||
while not event.is_set():
|
||||
print(".", end="", flush=True)
|
||||
time.sleep(1)
|
||||
print()
|
||||
|
||||
|
||||
def initialize_chat_llm(crew: Crew) -> Optional[LLM]:
|
||||
"""Initializes the chat LLM and handles exceptions."""
|
||||
try:
|
||||
return create_llm(crew.chat_llm)
|
||||
except Exception as e:
|
||||
click.secho(
|
||||
f"Unable to find a Chat LLM. Please make sure you set chat_llm on the crew: {e}",
|
||||
fg="red",
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def build_system_message(crew_chat_inputs: ChatInputs) -> str:
|
||||
"""Builds the initial system message for the chat."""
|
||||
required_fields_str = (
|
||||
", ".join(
|
||||
f"{field.name} (desc: {field.description or 'n/a'})"
|
||||
for field in crew_chat_inputs.inputs
|
||||
)
|
||||
or "(No required fields detected)"
|
||||
)
|
||||
|
||||
return (
|
||||
"You are a helpful AI assistant for the CrewAI platform. "
|
||||
"Your primary purpose is to assist users with the crew's specific tasks. "
|
||||
"You can answer general questions, but should guide users back to the crew's purpose afterward. "
|
||||
"For example, after answering a general question, remind the user of your main purpose, such as generating a research report, and prompt them to specify a topic or task related to the crew's purpose. "
|
||||
"You have a function (tool) you can call by name if you have all required inputs. "
|
||||
f"Those required inputs are: {required_fields_str}. "
|
||||
"Once you have them, call the function. "
|
||||
"Please keep your responses concise and friendly. "
|
||||
"If a user asks a question outside the crew's scope, provide a brief answer and remind them of the crew's purpose. "
|
||||
"After calling the tool, be prepared to take user feedback and make adjustments as needed. "
|
||||
"If you are ever unsure about a user's request or need clarification, ask the user for more information. "
|
||||
"Before doing anything else, introduce yourself with a friendly message like: 'Hey! I'm here to help you with [crew's purpose]. Could you please provide me with [inputs] so we can get started?' "
|
||||
"For example: 'Hey! I'm here to help you with uncovering and reporting cutting-edge developments through thorough research and detailed analysis. Could you please provide me with a topic you're interested in? This will help us generate a comprehensive research report and detailed analysis.'"
|
||||
f"\nCrew Name: {crew_chat_inputs.crew_name}"
|
||||
f"\nCrew Description: {crew_chat_inputs.crew_description}"
|
||||
)
|
||||
|
||||
|
||||
def create_tool_function(crew: Crew, messages: List[Dict[str, str]]) -> Any:
|
||||
"""Creates a wrapper function for running the crew tool with messages."""
|
||||
|
||||
def run_crew_tool_with_messages(**kwargs):
|
||||
return run_crew_tool(crew, messages, **kwargs)
|
||||
|
||||
return run_crew_tool_with_messages
|
||||
|
||||
|
||||
def flush_input():
|
||||
"""Flush any pending input from the user."""
|
||||
if platform.system() == "Windows":
|
||||
# Windows platform
|
||||
import msvcrt
|
||||
|
||||
while msvcrt.kbhit():
|
||||
msvcrt.getch()
|
||||
else:
|
||||
# Unix-like platforms (Linux, macOS)
|
||||
import termios
|
||||
|
||||
termios.tcflush(sys.stdin, termios.TCIFLUSH)
|
||||
|
||||
|
||||
def chat_loop(chat_llm, messages, crew_tool_schema, available_functions):
|
||||
"""Main chat loop for interacting with the user."""
|
||||
while True:
|
||||
try:
|
||||
# Flush any pending input before accepting new input
|
||||
flush_input()
|
||||
|
||||
user_input = get_user_input()
|
||||
handle_user_input(
|
||||
user_input, chat_llm, messages, crew_tool_schema, available_functions
|
||||
)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
click.echo("\nExiting chat. Goodbye!")
|
||||
break
|
||||
except Exception as e:
|
||||
click.secho(f"An error occurred: {e}", fg="red")
|
||||
break
|
||||
|
||||
|
||||
def get_user_input() -> str:
|
||||
"""Collect multi-line user input with exit handling."""
|
||||
click.secho(
|
||||
"\nYou (type your message below. Press 'Enter' twice when you're done):",
|
||||
fg="blue",
|
||||
)
|
||||
user_input_lines = []
|
||||
while True:
|
||||
line = input()
|
||||
if line.strip().lower() == "exit":
|
||||
return "exit"
|
||||
if line == "":
|
||||
break
|
||||
user_input_lines.append(line)
|
||||
return "\n".join(user_input_lines)
|
||||
|
||||
|
||||
def handle_user_input(
|
||||
user_input: str,
|
||||
chat_llm: LLM,
|
||||
messages: List[Dict[str, str]],
|
||||
crew_tool_schema: Dict[str, Any],
|
||||
available_functions: Dict[str, Any],
|
||||
) -> None:
|
||||
if user_input.strip().lower() == "exit":
|
||||
click.echo("Exiting chat. Goodbye!")
|
||||
return
|
||||
|
||||
if not user_input.strip():
|
||||
click.echo("Empty message. Please provide input or type 'exit' to quit.")
|
||||
return
|
||||
|
||||
messages.append({"role": "user", "content": user_input})
|
||||
|
||||
# Indicate that assistant is processing
|
||||
click.echo()
|
||||
click.secho("Assistant is processing your input. Please wait...", fg="green")
|
||||
|
||||
# Process assistant's response
|
||||
final_response = chat_llm.call(
|
||||
messages=messages,
|
||||
tools=[crew_tool_schema],
|
||||
available_functions=available_functions,
|
||||
)
|
||||
|
||||
messages.append({"role": "assistant", "content": final_response})
|
||||
click.secho(f"\nAssistant: {final_response}\n", fg="green")
|
||||
|
||||
|
||||
def generate_crew_tool_schema(crew_inputs: ChatInputs) -> dict:
|
||||
"""
|
||||
Dynamically build a Littellm 'function' schema for the given crew.
|
||||
|
||||
crew_name: The name of the crew (used for the function 'name').
|
||||
crew_inputs: A ChatInputs object containing crew_description
|
||||
and a list of input fields (each with a name & description).
|
||||
"""
|
||||
properties = {}
|
||||
for field in crew_inputs.inputs:
|
||||
properties[field.name] = {
|
||||
"type": "string",
|
||||
"description": field.description or "No description provided",
|
||||
}
|
||||
|
||||
required_fields = [field.name for field in crew_inputs.inputs]
|
||||
|
||||
return {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": crew_inputs.crew_name,
|
||||
"description": crew_inputs.crew_description or "No crew description",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required_fields,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def run_crew_tool(crew: Crew, messages: List[Dict[str, str]], **kwargs):
|
||||
"""
|
||||
Runs the crew using crew.kickoff(inputs=kwargs) and returns the output.
|
||||
|
||||
Args:
|
||||
crew (Crew): The crew instance to run.
|
||||
messages (List[Dict[str, str]]): The chat messages up to this point.
|
||||
**kwargs: The inputs collected from the user.
|
||||
|
||||
Returns:
|
||||
str: The output from the crew's execution.
|
||||
|
||||
Raises:
|
||||
SystemExit: Exits the chat if an error occurs during crew execution.
|
||||
"""
|
||||
try:
|
||||
# Serialize 'messages' to JSON string before adding to kwargs
|
||||
kwargs["crew_chat_messages"] = json.dumps(messages)
|
||||
|
||||
# Run the crew with the provided inputs
|
||||
crew_output = crew.kickoff(inputs=kwargs)
|
||||
|
||||
# Convert CrewOutput to a string to send back to the user
|
||||
result = str(crew_output)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
# Exit the chat and show the error message
|
||||
click.secho("An error occurred while running the crew:", fg="red")
|
||||
click.secho(str(e), fg="red")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def load_crew_and_name() -> Tuple[Crew, str]:
|
||||
"""
|
||||
Loads the crew by importing the crew class from the user's project.
|
||||
|
||||
Returns:
|
||||
Tuple[Crew, str]: A tuple containing the Crew instance and the name of the crew.
|
||||
"""
|
||||
# Get the current working directory
|
||||
cwd = Path.cwd()
|
||||
|
||||
# Path to the pyproject.toml file
|
||||
pyproject_path = cwd / "pyproject.toml"
|
||||
if not pyproject_path.exists():
|
||||
raise FileNotFoundError("pyproject.toml not found in the current directory.")
|
||||
|
||||
# Load the pyproject.toml file using 'tomli'
|
||||
with pyproject_path.open("rb") as f:
|
||||
pyproject_data = tomli.load(f)
|
||||
|
||||
# Get the project name from the 'project' section
|
||||
project_name = pyproject_data["project"]["name"]
|
||||
folder_name = project_name
|
||||
|
||||
# Derive the crew class name from the project name
|
||||
# E.g., if project_name is 'my_project', crew_class_name is 'MyProject'
|
||||
crew_class_name = project_name.replace("_", " ").title().replace(" ", "")
|
||||
|
||||
# Add the 'src' directory to sys.path
|
||||
src_path = cwd / "src"
|
||||
if str(src_path) not in sys.path:
|
||||
sys.path.insert(0, str(src_path))
|
||||
|
||||
# Import the crew module
|
||||
crew_module_name = f"{folder_name}.crew"
|
||||
try:
|
||||
crew_module = __import__(crew_module_name, fromlist=[crew_class_name])
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Failed to import crew module {crew_module_name}: {e}")
|
||||
|
||||
# Get the crew class from the module
|
||||
try:
|
||||
crew_class = getattr(crew_module, crew_class_name)
|
||||
except AttributeError:
|
||||
raise AttributeError(
|
||||
f"Crew class {crew_class_name} not found in module {crew_module_name}"
|
||||
)
|
||||
|
||||
# Instantiate the crew
|
||||
crew_instance = crew_class().crew()
|
||||
return crew_instance, crew_class_name
|
||||
|
||||
|
||||
def generate_crew_chat_inputs(crew: Crew, crew_name: str, chat_llm) -> ChatInputs:
|
||||
"""
|
||||
Generates the ChatInputs required for the crew by analyzing the tasks and agents.
|
||||
|
||||
Args:
|
||||
crew (Crew): The crew object containing tasks and agents.
|
||||
crew_name (str): The name of the crew.
|
||||
chat_llm: The chat language model to use for AI calls.
|
||||
|
||||
Returns:
|
||||
ChatInputs: An object containing the crew's name, description, and input fields.
|
||||
"""
|
||||
# Extract placeholders from tasks and agents
|
||||
required_inputs = fetch_required_inputs(crew)
|
||||
|
||||
# Generate descriptions for each input using AI
|
||||
input_fields = []
|
||||
for input_name in required_inputs:
|
||||
description = generate_input_description_with_ai(input_name, crew, chat_llm)
|
||||
input_fields.append(ChatInputField(name=input_name, description=description))
|
||||
|
||||
# Generate crew description using AI
|
||||
crew_description = generate_crew_description_with_ai(crew, chat_llm)
|
||||
|
||||
return ChatInputs(
|
||||
crew_name=crew_name, crew_description=crew_description, inputs=input_fields
|
||||
)
|
||||
|
||||
|
||||
def fetch_required_inputs(crew: Crew) -> Set[str]:
|
||||
"""
|
||||
Extracts placeholders from the crew's tasks and agents.
|
||||
|
||||
Args:
|
||||
crew (Crew): The crew object.
|
||||
|
||||
Returns:
|
||||
Set[str]: A set of placeholder names.
|
||||
"""
|
||||
placeholder_pattern = re.compile(r"\{(.+?)\}")
|
||||
required_inputs: Set[str] = set()
|
||||
|
||||
# Scan tasks
|
||||
for task in crew.tasks:
|
||||
text = f"{task.description or ''} {task.expected_output or ''}"
|
||||
required_inputs.update(placeholder_pattern.findall(text))
|
||||
|
||||
# Scan agents
|
||||
for agent in crew.agents:
|
||||
text = f"{agent.role or ''} {agent.goal or ''} {agent.backstory or ''}"
|
||||
required_inputs.update(placeholder_pattern.findall(text))
|
||||
|
||||
return required_inputs
|
||||
|
||||
|
||||
def generate_input_description_with_ai(input_name: str, crew: Crew, chat_llm) -> str:
|
||||
"""
|
||||
Generates an input description using AI based on the context of the crew.
|
||||
|
||||
Args:
|
||||
input_name (str): The name of the input placeholder.
|
||||
crew (Crew): The crew object.
|
||||
chat_llm: The chat language model to use for AI calls.
|
||||
|
||||
Returns:
|
||||
str: A concise description of the input.
|
||||
"""
|
||||
# Gather context from tasks and agents where the input is used
|
||||
context_texts = []
|
||||
placeholder_pattern = re.compile(r"\{(.+?)\}")
|
||||
|
||||
for task in crew.tasks:
|
||||
if (
|
||||
f"{{{input_name}}}" in task.description
|
||||
or f"{{{input_name}}}" in task.expected_output
|
||||
):
|
||||
# Replace placeholders with input names
|
||||
task_description = placeholder_pattern.sub(
|
||||
lambda m: m.group(1), task.description or ""
|
||||
)
|
||||
expected_output = placeholder_pattern.sub(
|
||||
lambda m: m.group(1), task.expected_output or ""
|
||||
)
|
||||
context_texts.append(f"Task Description: {task_description}")
|
||||
context_texts.append(f"Expected Output: {expected_output}")
|
||||
for agent in crew.agents:
|
||||
if (
|
||||
f"{{{input_name}}}" in agent.role
|
||||
or f"{{{input_name}}}" in agent.goal
|
||||
or f"{{{input_name}}}" in agent.backstory
|
||||
):
|
||||
# Replace placeholders with input names
|
||||
agent_role = placeholder_pattern.sub(lambda m: m.group(1), agent.role or "")
|
||||
agent_goal = placeholder_pattern.sub(lambda m: m.group(1), agent.goal or "")
|
||||
agent_backstory = placeholder_pattern.sub(
|
||||
lambda m: m.group(1), agent.backstory or ""
|
||||
)
|
||||
context_texts.append(f"Agent Role: {agent_role}")
|
||||
context_texts.append(f"Agent Goal: {agent_goal}")
|
||||
context_texts.append(f"Agent Backstory: {agent_backstory}")
|
||||
|
||||
context = "\n".join(context_texts)
|
||||
if not context:
|
||||
# If no context is found for the input, raise an exception as per instruction
|
||||
raise ValueError(f"No context found for input '{input_name}'.")
|
||||
|
||||
prompt = (
|
||||
f"Based on the following context, write a concise description (15 words or less) of the input '{input_name}'.\n"
|
||||
"Provide only the description, without any extra text or labels. Do not include placeholders like '{topic}' in the description.\n"
|
||||
"Context:\n"
|
||||
f"{context}"
|
||||
)
|
||||
response = chat_llm.call(messages=[{"role": "user", "content": prompt}])
|
||||
description = response.strip()
|
||||
|
||||
return description
|
||||
|
||||
|
||||
def generate_crew_description_with_ai(crew: Crew, chat_llm) -> str:
|
||||
"""
|
||||
Generates a brief description of the crew using AI.
|
||||
|
||||
Args:
|
||||
crew (Crew): The crew object.
|
||||
chat_llm: The chat language model to use for AI calls.
|
||||
|
||||
Returns:
|
||||
str: A concise description of the crew's purpose (15 words or less).
|
||||
"""
|
||||
# Gather context from tasks and agents
|
||||
context_texts = []
|
||||
placeholder_pattern = re.compile(r"\{(.+?)\}")
|
||||
|
||||
for task in crew.tasks:
|
||||
# Replace placeholders with input names
|
||||
task_description = placeholder_pattern.sub(
|
||||
lambda m: m.group(1), task.description or ""
|
||||
)
|
||||
expected_output = placeholder_pattern.sub(
|
||||
lambda m: m.group(1), task.expected_output or ""
|
||||
)
|
||||
context_texts.append(f"Task Description: {task_description}")
|
||||
context_texts.append(f"Expected Output: {expected_output}")
|
||||
for agent in crew.agents:
|
||||
# Replace placeholders with input names
|
||||
agent_role = placeholder_pattern.sub(lambda m: m.group(1), agent.role or "")
|
||||
agent_goal = placeholder_pattern.sub(lambda m: m.group(1), agent.goal or "")
|
||||
agent_backstory = placeholder_pattern.sub(
|
||||
lambda m: m.group(1), agent.backstory or ""
|
||||
)
|
||||
context_texts.append(f"Agent Role: {agent_role}")
|
||||
context_texts.append(f"Agent Goal: {agent_goal}")
|
||||
context_texts.append(f"Agent Backstory: {agent_backstory}")
|
||||
|
||||
context = "\n".join(context_texts)
|
||||
if not context:
|
||||
raise ValueError("No context found for generating crew description.")
|
||||
|
||||
prompt = (
|
||||
"Based on the following context, write a concise, action-oriented description (15 words or less) of the crew's purpose.\n"
|
||||
"Provide only the description, without any extra text or labels. Do not include placeholders like '{topic}' in the description.\n"
|
||||
"Context:\n"
|
||||
f"{context}"
|
||||
)
|
||||
response = chat_llm.call(messages=[{"role": "user", "content": prompt}])
|
||||
crew_description = response.strip()
|
||||
|
||||
return crew_description
|
||||
1
src/crewai/cli/templates/crew/.gitignore
vendored
1
src/crewai/cli/templates/crew/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.env
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
|
||||
@@ -2,7 +2,7 @@ research_task:
|
||||
description: >
|
||||
Conduct a thorough research about {topic}
|
||||
Make sure you find any interesting and relevant information given
|
||||
the current year is 2024.
|
||||
the current year is {current_year}.
|
||||
expected_output: >
|
||||
A list with 10 bullet points of the most relevant information about {topic}
|
||||
agent: researcher
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from {{folder_name}}.crew import {{crew_name}}
|
||||
|
||||
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
||||
@@ -16,9 +18,14 @@ def run():
|
||||
Run the crew.
|
||||
"""
|
||||
inputs = {
|
||||
'topic': 'AI LLMs'
|
||||
'topic': 'AI LLMs',
|
||||
'current_year': str(datetime.now().year)
|
||||
}
|
||||
{{crew_name}}().crew().kickoff(inputs=inputs)
|
||||
|
||||
try:
|
||||
{{crew_name}}().crew().kickoff(inputs=inputs)
|
||||
except Exception as e:
|
||||
raise Exception(f"An error occurred while running the crew: {e}")
|
||||
|
||||
|
||||
def train():
|
||||
@@ -55,4 +62,4 @@ def test():
|
||||
{{crew_name}}().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"An error occurred while replaying the crew: {e}")
|
||||
raise Exception(f"An error occurred while testing the crew: {e}")
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.86.0,<1.0.0"
|
||||
"crewai[tools]>=0.100.0,<1.0.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
1
src/crewai/cli/templates/flow/.gitignore
vendored
1
src/crewai/cli/templates/flow/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.env
|
||||
__pycache__/
|
||||
lib/
|
||||
.DS_Store
|
||||
|
||||
@@ -3,7 +3,7 @@ from random import randint
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
from crewai.flow import Flow, listen, start
|
||||
|
||||
from {{folder_name}}.crews.poem_crew.poem_crew import PoemCrew
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.86.0,<1.0.0",
|
||||
"crewai[tools]>=0.100.0,<1.0.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.86.0"
|
||||
"crewai[tools]>=0.100.0"
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
import warnings
|
||||
from concurrent.futures import Future
|
||||
from copy import copy as shallow_copy
|
||||
from hashlib import md5
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from pydantic import (
|
||||
UUID4,
|
||||
@@ -45,6 +47,7 @@ from crewai.utilities.formatter import (
|
||||
aggregate_raw_outputs_from_task_outputs,
|
||||
aggregate_raw_outputs_from_tasks,
|
||||
)
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
from crewai.utilities.planning_handler import CrewPlanner
|
||||
from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
|
||||
from crewai.utilities.training_handler import CrewTrainingHandler
|
||||
@@ -81,6 +84,7 @@ class Crew(BaseModel):
|
||||
step_callback: Callback to be executed after each step for every agents execution.
|
||||
share_crew: Whether you want to share the complete crew information and execution with crewAI to make the library better, and allow us to train models.
|
||||
planning: Plan the crew execution and add the plan to the crew.
|
||||
chat_llm: The language model used for orchestrating chat interactions with the crew.
|
||||
"""
|
||||
|
||||
__hash__ = object.__hash__ # type: ignore
|
||||
@@ -147,7 +151,7 @@ class Crew(BaseModel):
|
||||
manager_agent: Optional[BaseAgent] = Field(
|
||||
description="Custom agent that will be used as manager.", default=None
|
||||
)
|
||||
function_calling_llm: Optional[Any] = Field(
|
||||
function_calling_llm: Optional[Union[str, InstanceOf[LLM], Any]] = Field(
|
||||
description="Language model that will run the agent.", default=None
|
||||
)
|
||||
config: Optional[Union[Json, Dict[str, Any]]] = Field(default=None)
|
||||
@@ -203,8 +207,13 @@ class Crew(BaseModel):
|
||||
default=None,
|
||||
description="Knowledge sources for the crew. Add knowledge sources to the knowledge object.",
|
||||
)
|
||||
_knowledge: Optional[Knowledge] = PrivateAttr(
|
||||
chat_llm: Optional[Any] = Field(
|
||||
default=None,
|
||||
description="LLM used to handle chatting with the crew.",
|
||||
)
|
||||
knowledge: Optional[Knowledge] = Field(
|
||||
default=None,
|
||||
description="Knowledge for the crew.",
|
||||
)
|
||||
|
||||
@field_validator("id", mode="before")
|
||||
@@ -239,15 +248,9 @@ class Crew(BaseModel):
|
||||
if self.output_log_file:
|
||||
self._file_handler = FileHandler(self.output_log_file)
|
||||
self._rpm_controller = RPMController(max_rpm=self.max_rpm, logger=self._logger)
|
||||
if self.function_calling_llm:
|
||||
if isinstance(self.function_calling_llm, str):
|
||||
self.function_calling_llm = LLM(model=self.function_calling_llm)
|
||||
elif not isinstance(self.function_calling_llm, LLM):
|
||||
self.function_calling_llm = LLM(
|
||||
model=getattr(self.function_calling_llm, "model_name", None)
|
||||
or getattr(self.function_calling_llm, "deployment_name", None)
|
||||
or str(self.function_calling_llm)
|
||||
)
|
||||
if self.function_calling_llm and not isinstance(self.function_calling_llm, LLM):
|
||||
self.function_calling_llm = create_llm(self.function_calling_llm)
|
||||
|
||||
self._telemetry = Telemetry()
|
||||
self._telemetry.set_tracer()
|
||||
return self
|
||||
@@ -288,7 +291,7 @@ class Crew(BaseModel):
|
||||
if isinstance(self.knowledge_sources, list) and all(
|
||||
isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources
|
||||
):
|
||||
self._knowledge = Knowledge(
|
||||
self.knowledge = Knowledge(
|
||||
sources=self.knowledge_sources,
|
||||
embedder_config=self.embedder,
|
||||
collection_name="crew",
|
||||
@@ -512,6 +515,8 @@ class Crew(BaseModel):
|
||||
inputs: Optional[Dict[str, Any]] = None,
|
||||
) -> CrewOutput:
|
||||
for before_callback in self.before_kickoff_callbacks:
|
||||
if inputs is None:
|
||||
inputs = {}
|
||||
inputs = before_callback(inputs)
|
||||
|
||||
"""Starts the crew to work on its assigned tasks."""
|
||||
@@ -673,6 +678,7 @@ class Crew(BaseModel):
|
||||
else:
|
||||
self.manager_llm = (
|
||||
getattr(self.manager_llm, "model_name", None)
|
||||
or getattr(self.manager_llm, "model", None)
|
||||
or getattr(self.manager_llm, "deployment_name", None)
|
||||
or self.manager_llm
|
||||
)
|
||||
@@ -726,11 +732,7 @@ class Crew(BaseModel):
|
||||
|
||||
# Determine which tools to use - task tools take precedence over agent tools
|
||||
tools_for_task = task.tools or agent_to_use.tools or []
|
||||
tools_for_task = self._prepare_tools(
|
||||
agent_to_use,
|
||||
task,
|
||||
tools_for_task
|
||||
)
|
||||
tools_for_task = self._prepare_tools(agent_to_use, task, tools_for_task)
|
||||
|
||||
self._log_task_start(task, agent_to_use.role)
|
||||
|
||||
@@ -797,14 +799,18 @@ class Crew(BaseModel):
|
||||
return skipped_task_output
|
||||
return None
|
||||
|
||||
def _prepare_tools(self, agent: BaseAgent, task: Task, tools: List[Tool]) -> List[Tool]:
|
||||
def _prepare_tools(
|
||||
self, agent: BaseAgent, task: Task, tools: List[Tool]
|
||||
) -> List[Tool]:
|
||||
# Add delegation tools if agent allows delegation
|
||||
if agent.allow_delegation:
|
||||
if self.process == Process.hierarchical:
|
||||
if self.manager_agent:
|
||||
tools = self._update_manager_tools(task, tools)
|
||||
else:
|
||||
raise ValueError("Manager agent is required for hierarchical process.")
|
||||
raise ValueError(
|
||||
"Manager agent is required for hierarchical process."
|
||||
)
|
||||
|
||||
elif agent and agent.allow_delegation:
|
||||
tools = self._add_delegation_tools(task, tools)
|
||||
@@ -823,7 +829,9 @@ class Crew(BaseModel):
|
||||
return self.manager_agent
|
||||
return task.agent
|
||||
|
||||
def _merge_tools(self, existing_tools: List[Tool], new_tools: List[Tool]) -> List[Tool]:
|
||||
def _merge_tools(
|
||||
self, existing_tools: List[Tool], new_tools: List[Tool]
|
||||
) -> List[Tool]:
|
||||
"""Merge new tools into existing tools list, avoiding duplicates by tool name."""
|
||||
if not new_tools:
|
||||
return existing_tools
|
||||
@@ -839,7 +847,9 @@ class Crew(BaseModel):
|
||||
|
||||
return tools
|
||||
|
||||
def _inject_delegation_tools(self, tools: List[Tool], task_agent: BaseAgent, agents: List[BaseAgent]):
|
||||
def _inject_delegation_tools(
|
||||
self, tools: List[Tool], task_agent: BaseAgent, agents: List[BaseAgent]
|
||||
):
|
||||
delegation_tools = task_agent.get_delegation_tools(agents)
|
||||
return self._merge_tools(tools, delegation_tools)
|
||||
|
||||
@@ -856,7 +866,9 @@ class Crew(BaseModel):
|
||||
if len(self.agents) > 1 and len(agents_for_delegation) > 0 and task.agent:
|
||||
if not tools:
|
||||
tools = []
|
||||
tools = self._inject_delegation_tools(tools, task.agent, agents_for_delegation)
|
||||
tools = self._inject_delegation_tools(
|
||||
tools, task.agent, agents_for_delegation
|
||||
)
|
||||
return tools
|
||||
|
||||
def _log_task_start(self, task: Task, role: str = "None"):
|
||||
@@ -870,7 +882,9 @@ class Crew(BaseModel):
|
||||
if task.agent:
|
||||
tools = self._inject_delegation_tools(tools, task.agent, [task.agent])
|
||||
else:
|
||||
tools = self._inject_delegation_tools(tools, self.manager_agent, self.agents)
|
||||
tools = self._inject_delegation_tools(
|
||||
tools, self.manager_agent, self.agents
|
||||
)
|
||||
return tools
|
||||
|
||||
def _get_context(self, task: Task, task_outputs: List[TaskOutput]):
|
||||
@@ -979,10 +993,35 @@ class Crew(BaseModel):
|
||||
return result
|
||||
|
||||
def query_knowledge(self, query: List[str]) -> Union[List[Dict[str, Any]], None]:
|
||||
if self._knowledge:
|
||||
return self._knowledge.query(query)
|
||||
if self.knowledge:
|
||||
return self.knowledge.query(query)
|
||||
return None
|
||||
|
||||
def fetch_inputs(self) -> Set[str]:
|
||||
"""
|
||||
Gathers placeholders (e.g., {something}) referenced in tasks or agents.
|
||||
Scans each task's 'description' + 'expected_output', and each agent's
|
||||
'role', 'goal', and 'backstory'.
|
||||
|
||||
Returns a set of all discovered placeholder names.
|
||||
"""
|
||||
placeholder_pattern = re.compile(r"\{(.+?)\}")
|
||||
required_inputs: Set[str] = set()
|
||||
|
||||
# Scan tasks for inputs
|
||||
for task in self.tasks:
|
||||
# description and expected_output might contain e.g. {topic}, {user_name}, etc.
|
||||
text = f"{task.description or ''} {task.expected_output or ''}"
|
||||
required_inputs.update(placeholder_pattern.findall(text))
|
||||
|
||||
# Scan agents for inputs
|
||||
for agent in self.agents:
|
||||
# role, goal, backstory might have placeholders like {role_detail}, etc.
|
||||
text = f"{agent.role or ''} {agent.goal or ''} {agent.backstory or ''}"
|
||||
required_inputs.update(placeholder_pattern.findall(text))
|
||||
|
||||
return required_inputs
|
||||
|
||||
def copy(self):
|
||||
"""Create a deep copy of the Crew."""
|
||||
|
||||
@@ -999,6 +1038,8 @@ class Crew(BaseModel):
|
||||
"_telemetry",
|
||||
"agents",
|
||||
"tasks",
|
||||
"knowledge_sources",
|
||||
"knowledge",
|
||||
}
|
||||
|
||||
cloned_agents = [agent.copy() for agent in self.agents]
|
||||
@@ -1006,6 +1047,9 @@ class Crew(BaseModel):
|
||||
task_mapping = {}
|
||||
|
||||
cloned_tasks = []
|
||||
existing_knowledge_sources = shallow_copy(self.knowledge_sources)
|
||||
existing_knowledge = shallow_copy(self.knowledge)
|
||||
|
||||
for task in self.tasks:
|
||||
cloned_task = task.copy(cloned_agents, task_mapping)
|
||||
cloned_tasks.append(cloned_task)
|
||||
@@ -1025,7 +1069,13 @@ class Crew(BaseModel):
|
||||
copied_data.pop("agents", None)
|
||||
copied_data.pop("tasks", None)
|
||||
|
||||
copied_crew = Crew(**copied_data, agents=cloned_agents, tasks=cloned_tasks)
|
||||
copied_crew = Crew(
|
||||
**copied_data,
|
||||
agents=cloned_agents,
|
||||
tasks=cloned_tasks,
|
||||
knowledge_sources=existing_knowledge_sources,
|
||||
knowledge=existing_knowledge,
|
||||
)
|
||||
|
||||
return copied_crew
|
||||
|
||||
@@ -1038,7 +1088,7 @@ class Crew(BaseModel):
|
||||
def _interpolate_inputs(self, inputs: Dict[str, Any]) -> None:
|
||||
"""Interpolates the inputs in the tasks and agents."""
|
||||
[
|
||||
task.interpolate_inputs(
|
||||
task.interpolate_inputs_and_add_conversation_history(
|
||||
# type: ignore # "interpolate_inputs" of "Task" does not return a value (it only ever returns None)
|
||||
inputs
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from crewai.flow.flow import Flow
|
||||
from crewai.flow.flow import Flow, start, listen, or_, and_, router
|
||||
from crewai.flow.persistence import persist
|
||||
|
||||
__all__ = ["Flow", "start", "listen", "or_", "and_", "router", "persist"]
|
||||
|
||||
__all__ = ["Flow"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import logging
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
@@ -13,9 +14,10 @@ from typing import (
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
from uuid import uuid4
|
||||
|
||||
from blinker import Signal
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
|
||||
from crewai.flow.flow_events import (
|
||||
FlowFinishedEvent,
|
||||
@@ -24,10 +26,70 @@ from crewai.flow.flow_events import (
|
||||
MethodExecutionStartedEvent,
|
||||
)
|
||||
from crewai.flow.flow_visualizer import plot_flow
|
||||
from crewai.flow.persistence.base import FlowPersistence
|
||||
from crewai.flow.utils import get_possible_return_constants
|
||||
from crewai.telemetry import Telemetry
|
||||
from crewai.utilities.printer import Printer
|
||||
|
||||
T = TypeVar("T", bound=Union[BaseModel, Dict[str, Any]])
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FlowState(BaseModel):
|
||||
"""Base model for all flow states, ensuring each state has a unique ID."""
|
||||
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid4()),
|
||||
description="Unique identifier for the flow state",
|
||||
)
|
||||
|
||||
|
||||
# Type variables with explicit bounds
|
||||
T = TypeVar(
|
||||
"T", bound=Union[Dict[str, Any], BaseModel]
|
||||
) # Generic flow state type parameter
|
||||
StateT = TypeVar(
|
||||
"StateT", bound=Union[Dict[str, Any], BaseModel]
|
||||
) # State validation type parameter
|
||||
|
||||
|
||||
def ensure_state_type(state: Any, expected_type: Type[StateT]) -> StateT:
|
||||
"""Ensure state matches expected type with proper validation.
|
||||
|
||||
Args:
|
||||
state: State instance to validate
|
||||
expected_type: Expected type for the state
|
||||
|
||||
Returns:
|
||||
Validated state instance
|
||||
|
||||
Raises:
|
||||
TypeError: If state doesn't match expected type
|
||||
ValueError: If state validation fails
|
||||
"""
|
||||
"""Ensure state matches expected type with proper validation.
|
||||
|
||||
Args:
|
||||
state: State instance to validate
|
||||
expected_type: Expected type for the state
|
||||
|
||||
Returns:
|
||||
Validated state instance
|
||||
|
||||
Raises:
|
||||
TypeError: If state doesn't match expected type
|
||||
ValueError: If state validation fails
|
||||
"""
|
||||
if expected_type is dict:
|
||||
if not isinstance(state, dict):
|
||||
raise TypeError(f"Expected dict, got {type(state).__name__}")
|
||||
return cast(StateT, state)
|
||||
if isinstance(expected_type, type) and issubclass(expected_type, BaseModel):
|
||||
if not isinstance(state, expected_type):
|
||||
raise TypeError(
|
||||
f"Expected {expected_type.__name__}, got {type(state).__name__}"
|
||||
)
|
||||
return cast(StateT, state)
|
||||
raise TypeError(f"Invalid expected_type: {expected_type}")
|
||||
|
||||
|
||||
def start(condition: Optional[Union[str, dict, Callable]] = None) -> Callable:
|
||||
@@ -71,6 +133,7 @@ def start(condition: Optional[Union[str, dict, Callable]] = None) -> Callable:
|
||||
>>> def complex_start(self):
|
||||
... pass
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
func.__is_start_method__ = True
|
||||
if condition is not None:
|
||||
@@ -95,6 +158,7 @@ def start(condition: Optional[Union[str, dict, Callable]] = None) -> Callable:
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def listen(condition: Union[str, dict, Callable]) -> Callable:
|
||||
"""
|
||||
Creates a listener that executes when specified conditions are met.
|
||||
@@ -131,6 +195,7 @@ def listen(condition: Union[str, dict, Callable]) -> Callable:
|
||||
>>> def handle_completion(self):
|
||||
... pass
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
if isinstance(condition, str):
|
||||
func.__trigger_methods__ = [condition]
|
||||
@@ -195,6 +260,7 @@ def router(condition: Union[str, dict, Callable]) -> Callable:
|
||||
... return CONTINUE
|
||||
... return STOP
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
func.__is_router__ = True
|
||||
if isinstance(condition, str):
|
||||
@@ -218,6 +284,7 @@ def router(condition: Union[str, dict, Callable]) -> Callable:
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def or_(*conditions: Union[str, dict, Callable]) -> dict:
|
||||
"""
|
||||
Combines multiple conditions with OR logic for flow control.
|
||||
@@ -320,21 +387,32 @@ class FlowMeta(type):
|
||||
routers = set()
|
||||
|
||||
for attr_name, attr_value in dct.items():
|
||||
if hasattr(attr_value, "__is_start_method__"):
|
||||
start_methods.append(attr_name)
|
||||
# Check for any flow-related attributes
|
||||
if (
|
||||
hasattr(attr_value, "__is_flow_method__")
|
||||
or hasattr(attr_value, "__is_start_method__")
|
||||
or hasattr(attr_value, "__trigger_methods__")
|
||||
or hasattr(attr_value, "__is_router__")
|
||||
):
|
||||
|
||||
# Register start methods
|
||||
if hasattr(attr_value, "__is_start_method__"):
|
||||
start_methods.append(attr_name)
|
||||
|
||||
# Register listeners and routers
|
||||
if hasattr(attr_value, "__trigger_methods__"):
|
||||
methods = attr_value.__trigger_methods__
|
||||
condition_type = getattr(attr_value, "__condition_type__", "OR")
|
||||
listeners[attr_name] = (condition_type, methods)
|
||||
elif hasattr(attr_value, "__trigger_methods__"):
|
||||
methods = attr_value.__trigger_methods__
|
||||
condition_type = getattr(attr_value, "__condition_type__", "OR")
|
||||
listeners[attr_name] = (condition_type, methods)
|
||||
if hasattr(attr_value, "__is_router__") and attr_value.__is_router__:
|
||||
routers.add(attr_name)
|
||||
possible_returns = get_possible_return_constants(attr_value)
|
||||
if possible_returns:
|
||||
router_paths[attr_name] = possible_returns
|
||||
|
||||
if (
|
||||
hasattr(attr_value, "__is_router__")
|
||||
and attr_value.__is_router__
|
||||
):
|
||||
routers.add(attr_name)
|
||||
possible_returns = get_possible_return_constants(attr_value)
|
||||
if possible_returns:
|
||||
router_paths[attr_name] = possible_returns
|
||||
|
||||
setattr(cls, "_start_methods", start_methods)
|
||||
setattr(cls, "_listeners", listeners)
|
||||
@@ -345,7 +423,12 @@ class FlowMeta(type):
|
||||
|
||||
|
||||
class Flow(Generic[T], metaclass=FlowMeta):
|
||||
"""Base class for all flows.
|
||||
|
||||
Type parameter T must be either Dict[str, Any] or a subclass of BaseModel."""
|
||||
|
||||
_telemetry = Telemetry()
|
||||
_printer = Printer()
|
||||
|
||||
_start_methods: List[str] = []
|
||||
_listeners: Dict[str, tuple[str, List[str]]] = {}
|
||||
@@ -361,30 +444,130 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
_FlowGeneric.__name__ = f"{cls.__name__}[{item.__name__}]"
|
||||
return _FlowGeneric
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
persistence: Optional[FlowPersistence] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize a new Flow instance.
|
||||
|
||||
Args:
|
||||
persistence: Optional persistence backend for storing flow states
|
||||
**kwargs: Additional state values to initialize or override
|
||||
"""
|
||||
# Initialize basic instance attributes
|
||||
self._methods: Dict[str, Callable] = {}
|
||||
self._state: T = self._create_initial_state()
|
||||
self._method_execution_counts: Dict[str, int] = {}
|
||||
self._pending_and_listeners: Dict[str, Set[str]] = {}
|
||||
self._method_outputs: List[Any] = [] # List to store all method outputs
|
||||
self._persistence: Optional[FlowPersistence] = persistence
|
||||
|
||||
# Initialize state with initial values
|
||||
self._state = self._create_initial_state()
|
||||
|
||||
# Apply any additional kwargs
|
||||
if kwargs:
|
||||
self._initialize_state(kwargs)
|
||||
|
||||
self._telemetry.flow_creation_span(self.__class__.__name__)
|
||||
|
||||
# Register all flow-related methods
|
||||
for method_name in dir(self):
|
||||
if callable(getattr(self, method_name)) and not method_name.startswith(
|
||||
"__"
|
||||
):
|
||||
self._methods[method_name] = getattr(self, method_name)
|
||||
if not method_name.startswith("_"):
|
||||
method = getattr(self, method_name)
|
||||
# Check for any flow-related attributes
|
||||
if (
|
||||
hasattr(method, "__is_flow_method__")
|
||||
or hasattr(method, "__is_start_method__")
|
||||
or hasattr(method, "__trigger_methods__")
|
||||
or hasattr(method, "__is_router__")
|
||||
):
|
||||
# Ensure method is bound to this instance
|
||||
if not hasattr(method, "__self__"):
|
||||
method = method.__get__(self, self.__class__)
|
||||
self._methods[method_name] = method
|
||||
|
||||
def _create_initial_state(self) -> T:
|
||||
"""Create and initialize flow state with UUID and default values.
|
||||
|
||||
Returns:
|
||||
New state instance with UUID and default values initialized
|
||||
|
||||
Raises:
|
||||
ValueError: If structured state model lacks 'id' field
|
||||
TypeError: If state is neither BaseModel nor dictionary
|
||||
"""
|
||||
# Handle case where initial_state is None but we have a type parameter
|
||||
if self.initial_state is None and hasattr(self, "_initial_state_T"):
|
||||
return self._initial_state_T() # type: ignore
|
||||
state_type = getattr(self, "_initial_state_T")
|
||||
if isinstance(state_type, type):
|
||||
if issubclass(state_type, FlowState):
|
||||
# Create instance without id, then set it
|
||||
instance = state_type()
|
||||
if not hasattr(instance, "id"):
|
||||
setattr(instance, "id", str(uuid4()))
|
||||
return cast(T, instance)
|
||||
elif issubclass(state_type, BaseModel):
|
||||
# Create a new type that includes the ID field
|
||||
class StateWithId(state_type, FlowState): # type: ignore
|
||||
pass
|
||||
|
||||
instance = StateWithId()
|
||||
if not hasattr(instance, "id"):
|
||||
setattr(instance, "id", str(uuid4()))
|
||||
return cast(T, instance)
|
||||
elif state_type is dict:
|
||||
return cast(T, {"id": str(uuid4())})
|
||||
|
||||
# Handle case where no initial state is provided
|
||||
if self.initial_state is None:
|
||||
return {} # type: ignore
|
||||
elif isinstance(self.initial_state, type):
|
||||
return self.initial_state()
|
||||
else:
|
||||
return self.initial_state
|
||||
return cast(T, {"id": str(uuid4())})
|
||||
|
||||
# Handle case where initial_state is a type (class)
|
||||
if isinstance(self.initial_state, type):
|
||||
if issubclass(self.initial_state, FlowState):
|
||||
return cast(T, self.initial_state()) # Uses model defaults
|
||||
elif issubclass(self.initial_state, BaseModel):
|
||||
# Validate that the model has an id field
|
||||
model_fields = getattr(self.initial_state, "model_fields", None)
|
||||
if not model_fields or "id" not in model_fields:
|
||||
raise ValueError("Flow state model must have an 'id' field")
|
||||
return cast(T, self.initial_state()) # Uses model defaults
|
||||
elif self.initial_state is dict:
|
||||
return cast(T, {"id": str(uuid4())})
|
||||
|
||||
# Handle dictionary instance case
|
||||
if isinstance(self.initial_state, dict):
|
||||
new_state = dict(self.initial_state) # Copy to avoid mutations
|
||||
if "id" not in new_state:
|
||||
new_state["id"] = str(uuid4())
|
||||
return cast(T, new_state)
|
||||
|
||||
# Handle BaseModel instance case
|
||||
if isinstance(self.initial_state, BaseModel):
|
||||
model = cast(BaseModel, self.initial_state)
|
||||
if not hasattr(model, "id"):
|
||||
raise ValueError("Flow state model must have an 'id' field")
|
||||
|
||||
# Create new instance with same values to avoid mutations
|
||||
if hasattr(model, "model_dump"):
|
||||
# Pydantic v2
|
||||
state_dict = model.model_dump()
|
||||
elif hasattr(model, "dict"):
|
||||
# Pydantic v1
|
||||
state_dict = model.dict()
|
||||
else:
|
||||
# Fallback for other BaseModel implementations
|
||||
state_dict = {
|
||||
k: v for k, v in model.__dict__.items() if not k.startswith("_")
|
||||
}
|
||||
|
||||
# Create new instance of the same class
|
||||
model_class = type(model)
|
||||
return cast(T, model_class(**state_dict))
|
||||
raise TypeError(
|
||||
f"Initial state must be dict or BaseModel, got {type(self.initial_state)}"
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self) -> T:
|
||||
@@ -395,34 +578,158 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
"""Returns the list of all outputs from executed methods."""
|
||||
return self._method_outputs
|
||||
|
||||
@property
|
||||
def flow_id(self) -> str:
|
||||
"""Returns the unique identifier of this flow instance.
|
||||
|
||||
This property provides a consistent way to access the flow's unique identifier
|
||||
regardless of the underlying state implementation (dict or BaseModel).
|
||||
|
||||
Returns:
|
||||
str: The flow's unique identifier, or an empty string if not found
|
||||
|
||||
Note:
|
||||
This property safely handles both dictionary and BaseModel state types,
|
||||
returning an empty string if the ID cannot be retrieved rather than raising
|
||||
an exception.
|
||||
|
||||
Example:
|
||||
```python
|
||||
flow = MyFlow()
|
||||
print(f"Current flow ID: {flow.flow_id}") # Safely get flow ID
|
||||
```
|
||||
"""
|
||||
try:
|
||||
if not hasattr(self, '_state'):
|
||||
return ""
|
||||
|
||||
if isinstance(self._state, dict):
|
||||
return str(self._state.get("id", ""))
|
||||
elif isinstance(self._state, BaseModel):
|
||||
return str(getattr(self._state, "id", ""))
|
||||
return ""
|
||||
except (AttributeError, TypeError):
|
||||
return "" # Safely handle any unexpected attribute access issues
|
||||
|
||||
def _initialize_state(self, inputs: Dict[str, Any]) -> None:
|
||||
if isinstance(self._state, BaseModel):
|
||||
# Structured state
|
||||
"""Initialize or update flow state with new inputs.
|
||||
|
||||
Args:
|
||||
inputs: Dictionary of state values to set/update
|
||||
|
||||
Raises:
|
||||
ValueError: If validation fails for structured state
|
||||
TypeError: If state is neither BaseModel nor dictionary
|
||||
"""
|
||||
if isinstance(self._state, dict):
|
||||
# For dict states, preserve existing fields unless overridden
|
||||
current_id = self._state.get("id")
|
||||
# Only update specified fields
|
||||
for k, v in inputs.items():
|
||||
self._state[k] = v
|
||||
# Ensure ID is preserved or generated
|
||||
if current_id:
|
||||
self._state["id"] = current_id
|
||||
elif "id" not in self._state:
|
||||
self._state["id"] = str(uuid4())
|
||||
elif isinstance(self._state, BaseModel):
|
||||
# For BaseModel states, preserve existing fields unless overridden
|
||||
try:
|
||||
model = cast(BaseModel, self._state)
|
||||
# Get current state as dict
|
||||
if hasattr(model, "model_dump"):
|
||||
current_state = model.model_dump()
|
||||
elif hasattr(model, "dict"):
|
||||
current_state = model.dict()
|
||||
else:
|
||||
current_state = {
|
||||
k: v for k, v in model.__dict__.items() if not k.startswith("_")
|
||||
}
|
||||
|
||||
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"
|
||||
# Create new state with preserved fields and updates
|
||||
new_state = {**current_state, **inputs}
|
||||
|
||||
return ModelWithExtraForbid
|
||||
|
||||
ModelWithExtraForbid = create_model_with_extra_forbid(
|
||||
self._state.__class__
|
||||
)
|
||||
self._state = cast(
|
||||
T, ModelWithExtraForbid(**{**self._state.model_dump(), **inputs})
|
||||
)
|
||||
# Create new instance with merged state
|
||||
model_class = type(model)
|
||||
if hasattr(model_class, "model_validate"):
|
||||
# Pydantic v2
|
||||
self._state = cast(T, model_class.model_validate(new_state))
|
||||
elif hasattr(model_class, "parse_obj"):
|
||||
# Pydantic v1
|
||||
self._state = cast(T, model_class.parse_obj(new_state))
|
||||
else:
|
||||
# Fallback for other BaseModel implementations
|
||||
self._state = cast(T, model_class(**new_state))
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"Invalid inputs for structured state: {e}") from e
|
||||
elif isinstance(self._state, dict):
|
||||
self._state.update(inputs)
|
||||
else:
|
||||
raise TypeError("State must be a BaseModel instance or a dictionary.")
|
||||
|
||||
def _restore_state(self, stored_state: Dict[str, Any]) -> None:
|
||||
"""Restore flow state from persistence.
|
||||
|
||||
Args:
|
||||
stored_state: Previously stored state to restore
|
||||
|
||||
Raises:
|
||||
ValueError: If validation fails for structured state
|
||||
TypeError: If state is neither BaseModel nor dictionary
|
||||
"""
|
||||
# When restoring from persistence, use the stored ID
|
||||
stored_id = stored_state.get("id")
|
||||
if not stored_id:
|
||||
raise ValueError("Stored state must have an 'id' field")
|
||||
|
||||
if isinstance(self._state, dict):
|
||||
# For dict states, update all fields from stored state
|
||||
self._state.clear()
|
||||
self._state.update(stored_state)
|
||||
elif isinstance(self._state, BaseModel):
|
||||
# For BaseModel states, create new instance with stored values
|
||||
model = cast(BaseModel, self._state)
|
||||
if hasattr(model, "model_validate"):
|
||||
# Pydantic v2
|
||||
self._state = cast(T, type(model).model_validate(stored_state))
|
||||
elif hasattr(model, "parse_obj"):
|
||||
# Pydantic v1
|
||||
self._state = cast(T, type(model).parse_obj(stored_state))
|
||||
else:
|
||||
# Fallback for other BaseModel implementations
|
||||
self._state = cast(T, type(model)(**stored_state))
|
||||
else:
|
||||
raise TypeError(f"State must be dict or BaseModel, got {type(self._state)}")
|
||||
|
||||
def kickoff(self, inputs: Optional[Dict[str, Any]] = None) -> Any:
|
||||
"""Start the flow execution.
|
||||
|
||||
Args:
|
||||
inputs: Optional dictionary containing input values and potentially a state ID to restore
|
||||
"""
|
||||
# Handle state restoration if ID is provided in inputs
|
||||
if inputs and 'id' in inputs and self._persistence is not None:
|
||||
restore_uuid = inputs['id']
|
||||
stored_state = self._persistence.load_state(restore_uuid)
|
||||
|
||||
# Override the id in the state if it exists in inputs
|
||||
if 'id' in inputs:
|
||||
if isinstance(self._state, dict):
|
||||
self._state['id'] = inputs['id']
|
||||
elif isinstance(self._state, BaseModel):
|
||||
setattr(self._state, 'id', inputs['id'])
|
||||
|
||||
if stored_state:
|
||||
self._log_flow_event(f"Loading flow state from memory for UUID: {restore_uuid}", color="yellow")
|
||||
# Restore the state
|
||||
self._restore_state(stored_state)
|
||||
else:
|
||||
self._log_flow_event(f"No flow state found for UUID: {restore_uuid}", color="red")
|
||||
|
||||
# Apply any additional inputs after restoration
|
||||
filtered_inputs = {k: v for k, v in inputs.items() if k != 'id'}
|
||||
if filtered_inputs:
|
||||
self._initialize_state(filtered_inputs)
|
||||
|
||||
# Start flow execution
|
||||
self.event_emitter.send(
|
||||
self,
|
||||
event=FlowStartedEvent(
|
||||
@@ -430,9 +737,11 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
flow_name=self.__class__.__name__,
|
||||
),
|
||||
)
|
||||
self._log_flow_event(f"Flow started with ID: {self.flow_id}", color="bold_magenta")
|
||||
|
||||
if inputs is not None:
|
||||
if inputs is not None and 'id' not in inputs:
|
||||
self._initialize_state(inputs)
|
||||
|
||||
return asyncio.run(self.kickoff_async())
|
||||
|
||||
async def kickoff_async(self, inputs: Optional[Dict[str, Any]] = None) -> Any:
|
||||
@@ -675,6 +984,30 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
def _log_flow_event(self, message: str, color: str = "yellow", level: str = "info") -> None:
|
||||
"""Centralized logging method for flow events.
|
||||
|
||||
This method provides a consistent interface for logging flow-related events,
|
||||
combining both console output with colors and proper logging levels.
|
||||
|
||||
Args:
|
||||
message: The message to log
|
||||
color: Color to use for console output (default: yellow)
|
||||
Available colors: purple, red, bold_green, bold_purple,
|
||||
bold_blue, yellow, yellow
|
||||
level: Log level to use (default: info)
|
||||
Supported levels: info, warning
|
||||
|
||||
Note:
|
||||
This method uses the Printer utility for colored console output
|
||||
and the standard logging module for log level support.
|
||||
"""
|
||||
self._printer.print(message, color=color)
|
||||
if level == "info":
|
||||
logger.info(message)
|
||||
elif level == "warning":
|
||||
logger.warning(message)
|
||||
|
||||
def plot(self, filename: str = "crewai_flow") -> None:
|
||||
self._telemetry.flow_plotting_span(
|
||||
self.__class__.__name__, list(self._methods.keys())
|
||||
|
||||
18
src/crewai/flow/persistence/__init__.py
Normal file
18
src/crewai/flow/persistence/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
CrewAI Flow Persistence.
|
||||
|
||||
This module provides interfaces and implementations for persisting flow states.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, TypeVar, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.flow.persistence.base import FlowPersistence
|
||||
from crewai.flow.persistence.decorators import persist
|
||||
from crewai.flow.persistence.sqlite import SQLiteFlowPersistence
|
||||
|
||||
__all__ = ["FlowPersistence", "persist", "SQLiteFlowPersistence"]
|
||||
|
||||
StateType = TypeVar('StateType', bound=Union[Dict[str, Any], BaseModel])
|
||||
DictStateType = Dict[str, Any]
|
||||
53
src/crewai/flow/persistence/base.py
Normal file
53
src/crewai/flow/persistence/base.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Base class for flow state persistence."""
|
||||
|
||||
import abc
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class FlowPersistence(abc.ABC):
|
||||
"""Abstract base class for flow state persistence.
|
||||
|
||||
This class defines the interface that all persistence implementations must follow.
|
||||
It supports both structured (Pydantic BaseModel) and unstructured (dict) states.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def init_db(self) -> None:
|
||||
"""Initialize the persistence backend.
|
||||
|
||||
This method should handle any necessary setup, such as:
|
||||
- Creating tables
|
||||
- Establishing connections
|
||||
- Setting up indexes
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def save_state(
|
||||
self,
|
||||
flow_uuid: str,
|
||||
method_name: str,
|
||||
state_data: Union[Dict[str, Any], BaseModel]
|
||||
) -> None:
|
||||
"""Persist the flow state after method completion.
|
||||
|
||||
Args:
|
||||
flow_uuid: Unique identifier for the flow instance
|
||||
method_name: Name of the method that just completed
|
||||
state_data: Current state data (either dict or Pydantic model)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def load_state(self, flow_uuid: str) -> Optional[Dict[str, Any]]:
|
||||
"""Load the most recent state for a given flow UUID.
|
||||
|
||||
Args:
|
||||
flow_uuid: Unique identifier for the flow instance
|
||||
|
||||
Returns:
|
||||
The most recent state as a dictionary, or None if no state exists
|
||||
"""
|
||||
pass
|
||||
252
src/crewai/flow/persistence/decorators.py
Normal file
252
src/crewai/flow/persistence/decorators.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
Decorators for flow state persistence.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from crewai.flow.flow import Flow, start
|
||||
from crewai.flow.persistence import persist, SQLiteFlowPersistence
|
||||
|
||||
class MyFlow(Flow):
|
||||
@start()
|
||||
@persist(SQLiteFlowPersistence())
|
||||
def sync_method(self):
|
||||
# Synchronous method implementation
|
||||
pass
|
||||
|
||||
@start()
|
||||
@persist(SQLiteFlowPersistence())
|
||||
async def async_method(self):
|
||||
# Asynchronous method implementation
|
||||
await some_async_operation()
|
||||
```
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
import logging
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Optional,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.flow.persistence.base import FlowPersistence
|
||||
from crewai.flow.persistence.sqlite import SQLiteFlowPersistence
|
||||
from crewai.utilities.printer import Printer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
T = TypeVar("T")
|
||||
|
||||
# Constants for log messages
|
||||
LOG_MESSAGES = {
|
||||
"save_state": "Saving flow state to memory for ID: {}",
|
||||
"save_error": "Failed to persist state for method {}: {}",
|
||||
"state_missing": "Flow instance has no state",
|
||||
"id_missing": "Flow state must have an 'id' field for persistence"
|
||||
}
|
||||
|
||||
|
||||
class PersistenceDecorator:
|
||||
"""Class to handle flow state persistence with consistent logging."""
|
||||
|
||||
_printer = Printer() # Class-level printer instance
|
||||
|
||||
@classmethod
|
||||
def persist_state(cls, flow_instance: Any, method_name: str, persistence_instance: FlowPersistence) -> None:
|
||||
"""Persist flow state with proper error handling and logging.
|
||||
|
||||
This method handles the persistence of flow state data, including proper
|
||||
error handling and colored console output for status updates.
|
||||
|
||||
Args:
|
||||
flow_instance: The flow instance whose state to persist
|
||||
method_name: Name of the method that triggered persistence
|
||||
persistence_instance: The persistence backend to use
|
||||
|
||||
Raises:
|
||||
ValueError: If flow has no state or state lacks an ID
|
||||
RuntimeError: If state persistence fails
|
||||
AttributeError: If flow instance lacks required state attributes
|
||||
"""
|
||||
try:
|
||||
state = getattr(flow_instance, 'state', None)
|
||||
if state is None:
|
||||
raise ValueError("Flow instance has no state")
|
||||
|
||||
flow_uuid: Optional[str] = None
|
||||
if isinstance(state, dict):
|
||||
flow_uuid = state.get('id')
|
||||
elif isinstance(state, BaseModel):
|
||||
flow_uuid = getattr(state, 'id', None)
|
||||
|
||||
if not flow_uuid:
|
||||
raise ValueError("Flow state must have an 'id' field for persistence")
|
||||
|
||||
# Log state saving with consistent message
|
||||
cls._printer.print(LOG_MESSAGES["save_state"].format(flow_uuid), color="cyan")
|
||||
logger.info(LOG_MESSAGES["save_state"].format(flow_uuid))
|
||||
|
||||
try:
|
||||
persistence_instance.save_state(
|
||||
flow_uuid=flow_uuid,
|
||||
method_name=method_name,
|
||||
state_data=state,
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = LOG_MESSAGES["save_error"].format(method_name, str(e))
|
||||
cls._printer.print(error_msg, color="red")
|
||||
logger.error(error_msg)
|
||||
raise RuntimeError(f"State persistence failed: {str(e)}") from e
|
||||
except AttributeError:
|
||||
error_msg = LOG_MESSAGES["state_missing"]
|
||||
cls._printer.print(error_msg, color="red")
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
except (TypeError, ValueError) as e:
|
||||
error_msg = LOG_MESSAGES["id_missing"]
|
||||
cls._printer.print(error_msg, color="red")
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg) from e
|
||||
|
||||
|
||||
def persist(persistence: Optional[FlowPersistence] = None):
|
||||
"""Decorator to persist flow state.
|
||||
|
||||
This decorator can be applied at either the class level or method level.
|
||||
When applied at the class level, it automatically persists all flow method
|
||||
states. When applied at the method level, it persists only that method's
|
||||
state.
|
||||
|
||||
Args:
|
||||
persistence: Optional FlowPersistence implementation to use.
|
||||
If not provided, uses SQLiteFlowPersistence.
|
||||
|
||||
Returns:
|
||||
A decorator that can be applied to either a class or method
|
||||
|
||||
Raises:
|
||||
ValueError: If the flow state doesn't have an 'id' field
|
||||
RuntimeError: If state persistence fails
|
||||
|
||||
Example:
|
||||
@persist # Class-level persistence with default SQLite
|
||||
class MyFlow(Flow[MyState]):
|
||||
@start()
|
||||
def begin(self):
|
||||
pass
|
||||
"""
|
||||
|
||||
def decorator(target: Union[Type, Callable[..., T]]) -> Union[Type, Callable[..., T]]:
|
||||
"""Decorator that handles both class and method decoration."""
|
||||
actual_persistence = persistence or SQLiteFlowPersistence()
|
||||
|
||||
if isinstance(target, type):
|
||||
# Class decoration
|
||||
original_init = getattr(target, "__init__")
|
||||
|
||||
@functools.wraps(original_init)
|
||||
def new_init(self: Any, *args: Any, **kwargs: Any) -> None:
|
||||
if 'persistence' not in kwargs:
|
||||
kwargs['persistence'] = actual_persistence
|
||||
original_init(self, *args, **kwargs)
|
||||
|
||||
setattr(target, "__init__", new_init)
|
||||
|
||||
# Store original methods to preserve their decorators
|
||||
original_methods = {}
|
||||
|
||||
for name, method in target.__dict__.items():
|
||||
if callable(method) and (
|
||||
hasattr(method, "__is_start_method__") or
|
||||
hasattr(method, "__trigger_methods__") or
|
||||
hasattr(method, "__condition_type__") or
|
||||
hasattr(method, "__is_flow_method__") or
|
||||
hasattr(method, "__is_router__")
|
||||
):
|
||||
original_methods[name] = method
|
||||
|
||||
# Create wrapped versions of the methods that include persistence
|
||||
for name, method in original_methods.items():
|
||||
if asyncio.iscoroutinefunction(method):
|
||||
# Create a closure to capture the current name and method
|
||||
def create_async_wrapper(method_name: str, original_method: Callable):
|
||||
@functools.wraps(original_method)
|
||||
async def method_wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
|
||||
result = await original_method(self, *args, **kwargs)
|
||||
PersistenceDecorator.persist_state(self, method_name, actual_persistence)
|
||||
return result
|
||||
return method_wrapper
|
||||
|
||||
wrapped = create_async_wrapper(name, method)
|
||||
|
||||
# Preserve all original decorators and attributes
|
||||
for attr in ["__is_start_method__", "__trigger_methods__", "__condition_type__", "__is_router__"]:
|
||||
if hasattr(method, attr):
|
||||
setattr(wrapped, attr, getattr(method, attr))
|
||||
setattr(wrapped, "__is_flow_method__", True)
|
||||
|
||||
# Update the class with the wrapped method
|
||||
setattr(target, name, wrapped)
|
||||
else:
|
||||
# Create a closure to capture the current name and method
|
||||
def create_sync_wrapper(method_name: str, original_method: Callable):
|
||||
@functools.wraps(original_method)
|
||||
def method_wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
|
||||
result = original_method(self, *args, **kwargs)
|
||||
PersistenceDecorator.persist_state(self, method_name, actual_persistence)
|
||||
return result
|
||||
return method_wrapper
|
||||
|
||||
wrapped = create_sync_wrapper(name, method)
|
||||
|
||||
# Preserve all original decorators and attributes
|
||||
for attr in ["__is_start_method__", "__trigger_methods__", "__condition_type__", "__is_router__"]:
|
||||
if hasattr(method, attr):
|
||||
setattr(wrapped, attr, getattr(method, attr))
|
||||
setattr(wrapped, "__is_flow_method__", True)
|
||||
|
||||
# Update the class with the wrapped method
|
||||
setattr(target, name, wrapped)
|
||||
|
||||
return target
|
||||
else:
|
||||
# Method decoration
|
||||
method = target
|
||||
setattr(method, "__is_flow_method__", True)
|
||||
|
||||
if asyncio.iscoroutinefunction(method):
|
||||
@functools.wraps(method)
|
||||
async def method_async_wrapper(flow_instance: Any, *args: Any, **kwargs: Any) -> T:
|
||||
method_coro = method(flow_instance, *args, **kwargs)
|
||||
if asyncio.iscoroutine(method_coro):
|
||||
result = await method_coro
|
||||
else:
|
||||
result = method_coro
|
||||
PersistenceDecorator.persist_state(flow_instance, method.__name__, actual_persistence)
|
||||
return result
|
||||
|
||||
for attr in ["__is_start_method__", "__trigger_methods__", "__condition_type__", "__is_router__"]:
|
||||
if hasattr(method, attr):
|
||||
setattr(method_async_wrapper, attr, getattr(method, attr))
|
||||
setattr(method_async_wrapper, "__is_flow_method__", True)
|
||||
return cast(Callable[..., T], method_async_wrapper)
|
||||
else:
|
||||
@functools.wraps(method)
|
||||
def method_sync_wrapper(flow_instance: Any, *args: Any, **kwargs: Any) -> T:
|
||||
result = method(flow_instance, *args, **kwargs)
|
||||
PersistenceDecorator.persist_state(flow_instance, method.__name__, actual_persistence)
|
||||
return result
|
||||
|
||||
for attr in ["__is_start_method__", "__trigger_methods__", "__condition_type__", "__is_router__"]:
|
||||
if hasattr(method, attr):
|
||||
setattr(method_sync_wrapper, attr, getattr(method, attr))
|
||||
setattr(method_sync_wrapper, "__is_flow_method__", True)
|
||||
return cast(Callable[..., T], method_sync_wrapper)
|
||||
|
||||
return decorator
|
||||
123
src/crewai/flow/persistence/sqlite.py
Normal file
123
src/crewai/flow/persistence/sqlite.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
SQLite-based implementation of flow state persistence.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.flow.persistence.base import FlowPersistence
|
||||
|
||||
|
||||
class SQLiteFlowPersistence(FlowPersistence):
|
||||
"""SQLite-based implementation of flow state persistence.
|
||||
|
||||
This class provides a simple, file-based persistence implementation using SQLite.
|
||||
It's suitable for development and testing, or for production use cases with
|
||||
moderate performance requirements.
|
||||
"""
|
||||
|
||||
db_path: str # Type annotation for instance variable
|
||||
|
||||
def __init__(self, db_path: Optional[str] = None):
|
||||
"""Initialize SQLite persistence.
|
||||
|
||||
Args:
|
||||
db_path: Path to the SQLite database file. If not provided, uses
|
||||
db_storage_path() from utilities.paths.
|
||||
|
||||
Raises:
|
||||
ValueError: If db_path is invalid
|
||||
"""
|
||||
from crewai.utilities.paths import db_storage_path
|
||||
# Get path from argument or default location
|
||||
path = db_path or str(Path(db_storage_path()) / "flow_states.db")
|
||||
|
||||
if not path:
|
||||
raise ValueError("Database path must be provided")
|
||||
|
||||
self.db_path = path # Now mypy knows this is str
|
||||
self.init_db()
|
||||
|
||||
def init_db(self) -> None:
|
||||
"""Create the necessary tables if they don't exist."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS flow_states (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
flow_uuid TEXT NOT NULL,
|
||||
method_name TEXT NOT NULL,
|
||||
timestamp DATETIME NOT NULL,
|
||||
state_json TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
# Add index for faster UUID lookups
|
||||
conn.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_flow_states_uuid
|
||||
ON flow_states(flow_uuid)
|
||||
""")
|
||||
|
||||
def save_state(
|
||||
self,
|
||||
flow_uuid: str,
|
||||
method_name: str,
|
||||
state_data: Union[Dict[str, Any], BaseModel],
|
||||
) -> None:
|
||||
"""Save the current flow state to SQLite.
|
||||
|
||||
Args:
|
||||
flow_uuid: Unique identifier for the flow instance
|
||||
method_name: Name of the method that just completed
|
||||
state_data: Current state data (either dict or Pydantic model)
|
||||
"""
|
||||
# Convert state_data to dict, handling both Pydantic and dict cases
|
||||
if isinstance(state_data, BaseModel):
|
||||
state_dict = dict(state_data) # Use dict() for better type compatibility
|
||||
elif isinstance(state_data, dict):
|
||||
state_dict = state_data
|
||||
else:
|
||||
raise ValueError(
|
||||
f"state_data must be either a Pydantic BaseModel or dict, got {type(state_data)}"
|
||||
)
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.execute("""
|
||||
INSERT INTO flow_states (
|
||||
flow_uuid,
|
||||
method_name,
|
||||
timestamp,
|
||||
state_json
|
||||
) VALUES (?, ?, ?, ?)
|
||||
""", (
|
||||
flow_uuid,
|
||||
method_name,
|
||||
datetime.utcnow().isoformat(),
|
||||
json.dumps(state_dict),
|
||||
))
|
||||
|
||||
def load_state(self, flow_uuid: str) -> Optional[Dict[str, Any]]:
|
||||
"""Load the most recent state for a given flow UUID.
|
||||
|
||||
Args:
|
||||
flow_uuid: Unique identifier for the flow instance
|
||||
|
||||
Returns:
|
||||
The most recent state as a dictionary, or None if no state exists
|
||||
"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.execute("""
|
||||
SELECT state_json
|
||||
FROM flow_states
|
||||
WHERE flow_uuid = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
""", (flow_uuid,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
return json.loads(row[0])
|
||||
return None
|
||||
@@ -15,20 +15,20 @@ class Knowledge(BaseModel):
|
||||
Args:
|
||||
sources: List[BaseKnowledgeSource] = Field(default_factory=list)
|
||||
storage: Optional[KnowledgeStorage] = Field(default=None)
|
||||
embedder_config: Optional[Dict[str, Any]] = None
|
||||
embedder: Optional[Dict[str, Any]] = None
|
||||
"""
|
||||
|
||||
sources: List[BaseKnowledgeSource] = Field(default_factory=list)
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
storage: Optional[KnowledgeStorage] = Field(default=None)
|
||||
embedder_config: Optional[Dict[str, Any]] = None
|
||||
embedder: Optional[Dict[str, Any]] = None
|
||||
collection_name: Optional[str] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
collection_name: str,
|
||||
sources: List[BaseKnowledgeSource],
|
||||
embedder_config: Optional[Dict[str, Any]] = None,
|
||||
embedder: Optional[Dict[str, Any]] = None,
|
||||
storage: Optional[KnowledgeStorage] = None,
|
||||
**data,
|
||||
):
|
||||
@@ -37,25 +37,23 @@ class Knowledge(BaseModel):
|
||||
self.storage = storage
|
||||
else:
|
||||
self.storage = KnowledgeStorage(
|
||||
embedder_config=embedder_config, collection_name=collection_name
|
||||
embedder=embedder, collection_name=collection_name
|
||||
)
|
||||
self.sources = sources
|
||||
self.storage.initialize_knowledge_storage()
|
||||
for source in sources:
|
||||
source.storage = self.storage
|
||||
source.add()
|
||||
self._add_sources()
|
||||
|
||||
def query(self, query: List[str], limit: int = 3) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Query across all knowledge sources to find the most relevant information.
|
||||
Returns the top_k most relevant chunks.
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If storage is not initialized.
|
||||
"""
|
||||
if self.storage is None:
|
||||
raise ValueError("Storage is not initialized.")
|
||||
|
||||
|
||||
results = self.storage.search(
|
||||
query,
|
||||
limit,
|
||||
@@ -63,6 +61,9 @@ class Knowledge(BaseModel):
|
||||
return results
|
||||
|
||||
def _add_sources(self):
|
||||
for source in self.sources:
|
||||
source.storage = self.storage
|
||||
source.add()
|
||||
try:
|
||||
for source in self.sources:
|
||||
source.storage = self.storage
|
||||
source.add()
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
@@ -29,7 +29,13 @@ class BaseFileKnowledgeSource(BaseKnowledgeSource, ABC):
|
||||
def validate_file_path(cls, v, info):
|
||||
"""Validate that at least one of file_path or file_paths is provided."""
|
||||
# Single check if both are None, O(1) instead of nested conditions
|
||||
if v is None and info.data.get("file_path" if info.field_name == "file_paths" else "file_paths") is None:
|
||||
if (
|
||||
v is None
|
||||
and info.data.get(
|
||||
"file_path" if info.field_name == "file_paths" else "file_paths"
|
||||
)
|
||||
is None
|
||||
):
|
||||
raise ValueError("Either file_path or file_paths must be provided")
|
||||
return v
|
||||
|
||||
|
||||
@@ -2,11 +2,17 @@ from pathlib import Path
|
||||
from typing import Iterator, List, Optional, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from docling.datamodel.base_models import InputFormat
|
||||
from docling.document_converter import DocumentConverter
|
||||
from docling.exceptions import ConversionError
|
||||
from docling_core.transforms.chunker.hierarchical_chunker import HierarchicalChunker
|
||||
from docling_core.types.doc.document import DoclingDocument
|
||||
try:
|
||||
from docling.datamodel.base_models import InputFormat
|
||||
from docling.document_converter import DocumentConverter
|
||||
from docling.exceptions import ConversionError
|
||||
from docling_core.transforms.chunker.hierarchical_chunker import HierarchicalChunker
|
||||
from docling_core.types.doc.document import DoclingDocument
|
||||
|
||||
DOCLING_AVAILABLE = True
|
||||
except ImportError:
|
||||
DOCLING_AVAILABLE = False
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||
@@ -19,14 +25,22 @@ class CrewDoclingSource(BaseKnowledgeSource):
|
||||
This will auto support PDF, DOCX, and TXT, XLSX, Images, and HTML files without any additional dependencies and follows the docling package as the source of truth.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not DOCLING_AVAILABLE:
|
||||
raise ImportError(
|
||||
"The docling package is required to use CrewDoclingSource. "
|
||||
"Please install it using: uv add docling"
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
_logger: Logger = Logger(verbose=True)
|
||||
|
||||
file_path: Optional[List[Union[Path, str]]] = Field(default=None)
|
||||
file_paths: List[Union[Path, str]] = Field(default_factory=list)
|
||||
chunks: List[str] = Field(default_factory=list)
|
||||
safe_file_paths: List[Union[Path, str]] = Field(default_factory=list)
|
||||
content: List[DoclingDocument] = Field(default_factory=list)
|
||||
document_converter: DocumentConverter = Field(
|
||||
content: List["DoclingDocument"] = Field(default_factory=list)
|
||||
document_converter: "DocumentConverter" = Field(
|
||||
default_factory=lambda: DocumentConverter(
|
||||
allowed_formats=[
|
||||
InputFormat.MD,
|
||||
@@ -52,7 +66,7 @@ class CrewDoclingSource(BaseKnowledgeSource):
|
||||
self.safe_file_paths = self.validate_content()
|
||||
self.content = self._load_content()
|
||||
|
||||
def _load_content(self) -> List[DoclingDocument]:
|
||||
def _load_content(self) -> List["DoclingDocument"]:
|
||||
try:
|
||||
return self._convert_source_to_docling_documents()
|
||||
except ConversionError as e:
|
||||
@@ -74,11 +88,11 @@ class CrewDoclingSource(BaseKnowledgeSource):
|
||||
self.chunks.extend(list(new_chunks_iterable))
|
||||
self._save_documents()
|
||||
|
||||
def _convert_source_to_docling_documents(self) -> List[DoclingDocument]:
|
||||
def _convert_source_to_docling_documents(self) -> List["DoclingDocument"]:
|
||||
conv_results_iter = self.document_converter.convert_all(self.safe_file_paths)
|
||||
return [result.document for result in conv_results_iter]
|
||||
|
||||
def _chunk_doc(self, doc: DoclingDocument) -> Iterator[str]:
|
||||
def _chunk_doc(self, doc: "DoclingDocument") -> Iterator[str]:
|
||||
chunker = HierarchicalChunker()
|
||||
for chunk in chunker.chunk(doc):
|
||||
yield chunk.text
|
||||
|
||||
@@ -48,11 +48,11 @@ class KnowledgeStorage(BaseKnowledgeStorage):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedder_config: Optional[Dict[str, Any]] = None,
|
||||
embedder: Optional[Dict[str, Any]] = None,
|
||||
collection_name: Optional[str] = None,
|
||||
):
|
||||
self.collection_name = collection_name
|
||||
self._set_embedder_config(embedder_config)
|
||||
self._set_embedder_config(embedder)
|
||||
|
||||
def search(
|
||||
self,
|
||||
@@ -99,7 +99,7 @@ class KnowledgeStorage(BaseKnowledgeStorage):
|
||||
)
|
||||
if self.app:
|
||||
self.collection = self.app.get_or_create_collection(
|
||||
name=collection_name, embedding_function=self.embedder_config
|
||||
name=collection_name, embedding_function=self.embedder
|
||||
)
|
||||
else:
|
||||
raise Exception("Vector Database Client not initialized")
|
||||
@@ -187,17 +187,15 @@ class KnowledgeStorage(BaseKnowledgeStorage):
|
||||
api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small"
|
||||
)
|
||||
|
||||
def _set_embedder_config(
|
||||
self, embedder_config: Optional[Dict[str, Any]] = None
|
||||
) -> None:
|
||||
def _set_embedder_config(self, embedder: Optional[Dict[str, Any]] = None) -> None:
|
||||
"""Set the embedding configuration for the knowledge storage.
|
||||
|
||||
Args:
|
||||
embedder_config (Optional[Dict[str, Any]]): Configuration dictionary for the embedder.
|
||||
If None or empty, defaults to the default embedding function.
|
||||
"""
|
||||
self.embedder_config = (
|
||||
EmbeddingConfigurator().configure_embedder(embedder_config)
|
||||
if embedder_config
|
||||
self.embedder = (
|
||||
EmbeddingConfigurator().configure_embedder(embedder)
|
||||
if embedder
|
||||
else self._create_default_embedding_function()
|
||||
)
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing import Any, Dict, List, Optional, Union, cast
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", UserWarning)
|
||||
import litellm
|
||||
from litellm import Choices, get_supported_openai_params
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
import litellm
|
||||
from litellm import get_supported_openai_params
|
||||
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededException,
|
||||
)
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class FilteredStream:
|
||||
def __init__(self, original_stream):
|
||||
@@ -21,6 +30,7 @@ class FilteredStream:
|
||||
|
||||
def write(self, s) -> int:
|
||||
with self._lock:
|
||||
# Filter out extraneous messages from LiteLLM
|
||||
if (
|
||||
"Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new"
|
||||
in s
|
||||
@@ -66,6 +76,18 @@ LLM_CONTEXT_WINDOW_SIZES = {
|
||||
"mixtral-8x7b-32768": 32768,
|
||||
"llama-3.3-70b-versatile": 128000,
|
||||
"llama-3.3-70b-instruct": 128000,
|
||||
# sambanova
|
||||
"Meta-Llama-3.3-70B-Instruct": 131072,
|
||||
"QwQ-32B-Preview": 8192,
|
||||
"Qwen2.5-72B-Instruct": 8192,
|
||||
"Qwen2.5-Coder-32B-Instruct": 8192,
|
||||
"Meta-Llama-3.1-405B-Instruct": 8192,
|
||||
"Meta-Llama-3.1-70B-Instruct": 131072,
|
||||
"Meta-Llama-3.1-8B-Instruct": 131072,
|
||||
"Llama-3.2-90B-Vision-Instruct": 16384,
|
||||
"Llama-3.2-11B-Vision-Instruct": 16384,
|
||||
"Meta-Llama-3.2-3B-Instruct": 4096,
|
||||
"Meta-Llama-3.2-1B-Instruct": 16384,
|
||||
}
|
||||
|
||||
DEFAULT_CONTEXT_WINDOW_SIZE = 8192
|
||||
@@ -76,17 +98,18 @@ CONTEXT_WINDOW_USAGE_RATIO = 0.75
|
||||
def suppress_warnings():
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore")
|
||||
warnings.filterwarnings(
|
||||
"ignore", message="open_text is deprecated*", category=DeprecationWarning
|
||||
)
|
||||
|
||||
# Redirect stdout and stderr
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
sys.stdout = FilteredStream(old_stdout)
|
||||
sys.stderr = FilteredStream(old_stderr)
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
# Restore stdout and stderr
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
|
||||
@@ -107,20 +130,18 @@ class LLM:
|
||||
logit_bias: Optional[Dict[int, float]] = None,
|
||||
response_format: Optional[Dict[str, Any]] = None,
|
||||
seed: Optional[int] = None,
|
||||
logprobs: Optional[bool] = None,
|
||||
logprobs: Optional[int] = None,
|
||||
top_logprobs: Optional[int] = None,
|
||||
base_url: Optional[str] = None,
|
||||
api_version: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
callbacks: List[Any] = [],
|
||||
**kwargs,
|
||||
):
|
||||
self.model = model
|
||||
self.timeout = timeout
|
||||
self.temperature = temperature
|
||||
self.top_p = top_p
|
||||
self.n = n
|
||||
self.stop = stop
|
||||
self.max_completion_tokens = max_completion_tokens
|
||||
self.max_tokens = max_tokens
|
||||
self.presence_penalty = presence_penalty
|
||||
@@ -135,19 +156,66 @@ class LLM:
|
||||
self.api_key = api_key
|
||||
self.callbacks = callbacks
|
||||
self.context_window_size = 0
|
||||
self.kwargs = kwargs
|
||||
|
||||
litellm.drop_params = True
|
||||
litellm.set_verbose = False
|
||||
|
||||
# Normalize self.stop to always be a List[str]
|
||||
if stop is None:
|
||||
self.stop: List[str] = []
|
||||
elif isinstance(stop, str):
|
||||
self.stop = [stop]
|
||||
else:
|
||||
self.stop = stop
|
||||
|
||||
self.set_callbacks(callbacks)
|
||||
self.set_env_callbacks()
|
||||
|
||||
def call(self, messages: List[Dict[str, str]], callbacks: List[Any] = []) -> str:
|
||||
def call(
|
||||
self,
|
||||
messages: Union[str, List[Dict[str, str]]],
|
||||
tools: Optional[List[dict]] = None,
|
||||
callbacks: Optional[List[Any]] = None,
|
||||
available_functions: Optional[Dict[str, Any]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
High-level llm call method that:
|
||||
1) Accepts either a string or a list of messages
|
||||
2) Converts string input to the required message format
|
||||
3) Calls litellm.completion
|
||||
4) Handles function/tool calls if any
|
||||
5) Returns the final text response or tool result
|
||||
|
||||
Parameters:
|
||||
- messages (Union[str, List[Dict[str, str]]]): The input messages for the LLM.
|
||||
- If a string is provided, it will be converted into a message list with a single entry.
|
||||
- If a list of dictionaries is provided, each dictionary should have 'role' and 'content' keys.
|
||||
- tools (Optional[List[dict]]): A list of tool schemas for function calling.
|
||||
- callbacks (Optional[List[Any]]): A list of callback functions to be executed.
|
||||
- available_functions (Optional[Dict[str, Any]]): A dictionary mapping function names to actual Python functions.
|
||||
|
||||
Returns:
|
||||
- str: The final text response from the LLM or the result of a tool function call.
|
||||
|
||||
Examples:
|
||||
---------
|
||||
# Example 1: Using a string input
|
||||
response = llm.call("Return the name of a random city in the world.")
|
||||
print(response)
|
||||
|
||||
# Example 2: Using a list of messages
|
||||
messages = [{"role": "user", "content": "What is the capital of France?"}]
|
||||
response = llm.call(messages)
|
||||
print(response)
|
||||
"""
|
||||
if isinstance(messages, str):
|
||||
messages = [{"role": "user", "content": messages}]
|
||||
|
||||
with suppress_warnings():
|
||||
if callbacks and len(callbacks) > 0:
|
||||
self.set_callbacks(callbacks)
|
||||
|
||||
try:
|
||||
# --- 1) Prepare the parameters for the completion call
|
||||
params = {
|
||||
"model": self.model,
|
||||
"messages": messages,
|
||||
@@ -168,21 +236,72 @@ class LLM:
|
||||
"api_version": self.api_version,
|
||||
"api_key": self.api_key,
|
||||
"stream": False,
|
||||
**self.kwargs,
|
||||
"tools": tools,
|
||||
}
|
||||
|
||||
# Remove None values to avoid passing unnecessary parameters
|
||||
# Remove None values from params
|
||||
params = {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
# --- 2) Make the completion call
|
||||
response = litellm.completion(**params)
|
||||
return response["choices"][0]["message"]["content"]
|
||||
response_message = cast(Choices, cast(ModelResponse, response).choices)[
|
||||
0
|
||||
].message
|
||||
text_response = response_message.content or ""
|
||||
tool_calls = getattr(response_message, "tool_calls", [])
|
||||
|
||||
# --- 3) Handle callbacks with usage info
|
||||
if callbacks and len(callbacks) > 0:
|
||||
for callback in callbacks:
|
||||
if hasattr(callback, "log_success_event"):
|
||||
usage_info = getattr(response, "usage", None)
|
||||
if usage_info:
|
||||
callback.log_success_event(
|
||||
kwargs=params,
|
||||
response_obj={"usage": usage_info},
|
||||
start_time=0,
|
||||
end_time=0,
|
||||
)
|
||||
|
||||
# --- 4) If no tool calls, return the text response
|
||||
if not tool_calls or not available_functions:
|
||||
return text_response
|
||||
|
||||
# --- 5) Handle the tool call
|
||||
tool_call = tool_calls[0]
|
||||
function_name = tool_call.function.name
|
||||
|
||||
if function_name in available_functions:
|
||||
try:
|
||||
function_args = json.loads(tool_call.function.arguments)
|
||||
except json.JSONDecodeError as e:
|
||||
logging.warning(f"Failed to parse function arguments: {e}")
|
||||
return text_response
|
||||
|
||||
fn = available_functions[function_name]
|
||||
try:
|
||||
# Call the actual tool function
|
||||
result = fn(**function_args)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f"Error executing function '{function_name}': {e}"
|
||||
)
|
||||
return text_response
|
||||
|
||||
else:
|
||||
logging.warning(
|
||||
f"Tool call requested unknown function '{function_name}'"
|
||||
)
|
||||
return text_response
|
||||
|
||||
except Exception as e:
|
||||
if not LLMContextLengthExceededException(
|
||||
str(e)
|
||||
)._is_context_limit_error(str(e)):
|
||||
logging.error(f"LiteLLM call failed: {str(e)}")
|
||||
|
||||
raise # Re-raise the exception after logging
|
||||
raise
|
||||
|
||||
def supports_function_calling(self) -> bool:
|
||||
try:
|
||||
@@ -201,7 +320,10 @@ class LLM:
|
||||
return False
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
# Only using 75% of the context window size to avoid cutting the message in the middle
|
||||
"""
|
||||
Returns the context window size, using 75% of the maximum to avoid
|
||||
cutting off messages mid-thread.
|
||||
"""
|
||||
if self.context_window_size != 0:
|
||||
return self.context_window_size
|
||||
|
||||
@@ -214,16 +336,21 @@ class LLM:
|
||||
return self.context_window_size
|
||||
|
||||
def set_callbacks(self, callbacks: List[Any]):
|
||||
callback_types = [type(callback) for callback in callbacks]
|
||||
for callback in litellm.success_callback[:]:
|
||||
if type(callback) in callback_types:
|
||||
litellm.success_callback.remove(callback)
|
||||
"""
|
||||
Attempt to keep a single set of callbacks in litellm by removing old
|
||||
duplicates and adding new ones.
|
||||
"""
|
||||
with suppress_warnings():
|
||||
callback_types = [type(callback) for callback in callbacks]
|
||||
for callback in litellm.success_callback[:]:
|
||||
if type(callback) in callback_types:
|
||||
litellm.success_callback.remove(callback)
|
||||
|
||||
for callback in litellm._async_success_callback[:]:
|
||||
if type(callback) in callback_types:
|
||||
litellm._async_success_callback.remove(callback)
|
||||
for callback in litellm._async_success_callback[:]:
|
||||
if type(callback) in callback_types:
|
||||
litellm._async_success_callback.remove(callback)
|
||||
|
||||
litellm.callbacks = callbacks
|
||||
litellm.callbacks = callbacks
|
||||
|
||||
def set_env_callbacks(self):
|
||||
"""
|
||||
@@ -244,19 +371,20 @@ class LLM:
|
||||
This will set `litellm.success_callback` to ["langfuse", "langsmith"] and
|
||||
`litellm.failure_callback` to ["langfuse"].
|
||||
"""
|
||||
success_callbacks_str = os.environ.get("LITELLM_SUCCESS_CALLBACKS", "")
|
||||
success_callbacks = []
|
||||
if success_callbacks_str:
|
||||
success_callbacks = [
|
||||
callback.strip() for callback in success_callbacks_str.split(",")
|
||||
]
|
||||
with suppress_warnings():
|
||||
success_callbacks_str = os.environ.get("LITELLM_SUCCESS_CALLBACKS", "")
|
||||
success_callbacks = []
|
||||
if success_callbacks_str:
|
||||
success_callbacks = [
|
||||
cb.strip() for cb in success_callbacks_str.split(",") if cb.strip()
|
||||
]
|
||||
|
||||
failure_callbacks_str = os.environ.get("LITELLM_FAILURE_CALLBACKS", "")
|
||||
failure_callbacks = []
|
||||
if failure_callbacks_str:
|
||||
failure_callbacks = [
|
||||
callback.strip() for callback in failure_callbacks_str.split(",")
|
||||
]
|
||||
failure_callbacks_str = os.environ.get("LITELLM_FAILURE_CALLBACKS", "")
|
||||
failure_callbacks = []
|
||||
if failure_callbacks_str:
|
||||
failure_callbacks = [
|
||||
cb.strip() for cb in failure_callbacks_str.split(",") if cb.strip()
|
||||
]
|
||||
|
||||
litellm.success_callback = success_callbacks
|
||||
litellm.failure_callback = failure_callbacks
|
||||
litellm.success_callback = success_callbacks
|
||||
litellm.failure_callback = failure_callbacks
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import json
|
||||
import logging
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from crewai.task import Task
|
||||
from crewai.utilities import Printer
|
||||
from crewai.utilities.crew_json_encoder import CrewJSONEncoder
|
||||
from crewai.utilities.errors import DatabaseError, DatabaseOperationError
|
||||
from crewai.utilities.paths import db_storage_path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KickoffTaskOutputsSQLiteStorage:
|
||||
"""
|
||||
@@ -14,15 +19,24 @@ class KickoffTaskOutputsSQLiteStorage:
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, db_path: str = f"{db_storage_path()}/latest_kickoff_task_outputs.db"
|
||||
self, db_path: Optional[str] = None
|
||||
) -> None:
|
||||
if db_path is None:
|
||||
# Get the parent directory of the default db path and create our db file there
|
||||
db_path = str(Path(db_storage_path()) / "latest_kickoff_task_outputs.db")
|
||||
self.db_path = db_path
|
||||
self._printer: Printer = Printer()
|
||||
self._initialize_db()
|
||||
|
||||
def _initialize_db(self):
|
||||
"""
|
||||
Initializes the SQLite database and creates LTM table
|
||||
def _initialize_db(self) -> None:
|
||||
"""Initialize the SQLite database and create the latest_kickoff_task_outputs table.
|
||||
|
||||
This method sets up the database schema for storing task outputs. It creates
|
||||
a table with columns for task_id, expected_output, output (as JSON),
|
||||
task_index, inputs (as JSON), was_replayed flag, and timestamp.
|
||||
|
||||
Raises:
|
||||
DatabaseOperationError: If database initialization fails due to SQLite errors.
|
||||
"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
@@ -43,10 +57,9 @@ class KickoffTaskOutputsSQLiteStorage:
|
||||
|
||||
conn.commit()
|
||||
except sqlite3.Error as e:
|
||||
self._printer.print(
|
||||
content=f"SAVING KICKOFF TASK OUTPUTS ERROR: An error occurred during database initialization: {e}",
|
||||
color="red",
|
||||
)
|
||||
error_msg = DatabaseError.format_error(DatabaseError.INIT_ERROR, e)
|
||||
logger.error(error_msg)
|
||||
raise DatabaseOperationError(error_msg, e)
|
||||
|
||||
def add(
|
||||
self,
|
||||
@@ -55,9 +68,22 @@ class KickoffTaskOutputsSQLiteStorage:
|
||||
task_index: int,
|
||||
was_replayed: bool = False,
|
||||
inputs: Dict[str, Any] = {},
|
||||
):
|
||||
) -> None:
|
||||
"""Add a new task output record to the database.
|
||||
|
||||
Args:
|
||||
task: The Task object containing task details.
|
||||
output: Dictionary containing the task's output data.
|
||||
task_index: Integer index of the task in the sequence.
|
||||
was_replayed: Boolean indicating if this was a replay execution.
|
||||
inputs: Dictionary of input parameters used for the task.
|
||||
|
||||
Raises:
|
||||
DatabaseOperationError: If saving the task output fails due to SQLite errors.
|
||||
"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.execute("BEGIN TRANSACTION")
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
@@ -76,21 +102,31 @@ class KickoffTaskOutputsSQLiteStorage:
|
||||
)
|
||||
conn.commit()
|
||||
except sqlite3.Error as e:
|
||||
self._printer.print(
|
||||
content=f"SAVING KICKOFF TASK OUTPUTS ERROR: An error occurred during database initialization: {e}",
|
||||
color="red",
|
||||
)
|
||||
error_msg = DatabaseError.format_error(DatabaseError.SAVE_ERROR, e)
|
||||
logger.error(error_msg)
|
||||
raise DatabaseOperationError(error_msg, e)
|
||||
|
||||
def update(
|
||||
self,
|
||||
task_index: int,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Updates an existing row in the latest_kickoff_task_outputs table based on task_index.
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Update an existing task output record in the database.
|
||||
|
||||
Updates fields of a task output record identified by task_index. The fields
|
||||
to update are provided as keyword arguments.
|
||||
|
||||
Args:
|
||||
task_index: Integer index of the task to update.
|
||||
**kwargs: Arbitrary keyword arguments representing fields to update.
|
||||
Values that are dictionaries will be JSON encoded.
|
||||
|
||||
Raises:
|
||||
DatabaseOperationError: If updating the task output fails due to SQLite errors.
|
||||
"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.execute("BEGIN TRANSACTION")
|
||||
cursor = conn.cursor()
|
||||
|
||||
fields = []
|
||||
@@ -110,14 +146,23 @@ class KickoffTaskOutputsSQLiteStorage:
|
||||
conn.commit()
|
||||
|
||||
if cursor.rowcount == 0:
|
||||
self._printer.print(
|
||||
f"No row found with task_index {task_index}. No update performed.",
|
||||
color="red",
|
||||
)
|
||||
logger.warning(f"No row found with task_index {task_index}. No update performed.")
|
||||
except sqlite3.Error as e:
|
||||
self._printer.print(f"UPDATE KICKOFF TASK OUTPUTS ERROR: {e}", color="red")
|
||||
error_msg = DatabaseError.format_error(DatabaseError.UPDATE_ERROR, e)
|
||||
logger.error(error_msg)
|
||||
raise DatabaseOperationError(error_msg, e)
|
||||
|
||||
def load(self) -> Optional[List[Dict[str, Any]]]:
|
||||
def load(self) -> List[Dict[str, Any]]:
|
||||
"""Load all task output records from the database.
|
||||
|
||||
Returns:
|
||||
List of dictionaries containing task output records, ordered by task_index.
|
||||
Each dictionary contains: task_id, expected_output, output, task_index,
|
||||
inputs, was_replayed, and timestamp.
|
||||
|
||||
Raises:
|
||||
DatabaseOperationError: If loading task outputs fails due to SQLite errors.
|
||||
"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
@@ -144,23 +189,26 @@ class KickoffTaskOutputsSQLiteStorage:
|
||||
return results
|
||||
|
||||
except sqlite3.Error as e:
|
||||
self._printer.print(
|
||||
content=f"LOADING KICKOFF TASK OUTPUTS ERROR: An error occurred while querying kickoff task outputs: {e}",
|
||||
color="red",
|
||||
)
|
||||
return None
|
||||
error_msg = DatabaseError.format_error(DatabaseError.LOAD_ERROR, e)
|
||||
logger.error(error_msg)
|
||||
raise DatabaseOperationError(error_msg, e)
|
||||
|
||||
def delete_all(self):
|
||||
"""
|
||||
Deletes all rows from the latest_kickoff_task_outputs table.
|
||||
def delete_all(self) -> None:
|
||||
"""Delete all task output records from the database.
|
||||
|
||||
This method removes all records from the latest_kickoff_task_outputs table.
|
||||
Use with caution as this operation cannot be undone.
|
||||
|
||||
Raises:
|
||||
DatabaseOperationError: If deleting task outputs fails due to SQLite errors.
|
||||
"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.execute("BEGIN TRANSACTION")
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM latest_kickoff_task_outputs")
|
||||
conn.commit()
|
||||
except sqlite3.Error as e:
|
||||
self._printer.print(
|
||||
content=f"ERROR: Failed to delete all kickoff task outputs: {e}",
|
||||
color="red",
|
||||
)
|
||||
error_msg = DatabaseError.format_error(DatabaseError.DELETE_ERROR, e)
|
||||
logger.error(error_msg)
|
||||
raise DatabaseOperationError(error_msg, e)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from crewai.utilities import Printer
|
||||
@@ -12,10 +13,15 @@ class LTMSQLiteStorage:
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, db_path: str = f"{db_storage_path()}/long_term_memory_storage.db"
|
||||
self, db_path: Optional[str] = None
|
||||
) -> None:
|
||||
if db_path is None:
|
||||
# Get the parent directory of the default db path and create our db file there
|
||||
db_path = str(Path(db_storage_path()) / "long_term_memory_storage.db")
|
||||
self.db_path = db_path
|
||||
self._printer: Printer = Printer()
|
||||
# Ensure parent directory exists
|
||||
Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
self._initialize_db()
|
||||
|
||||
def _initialize_db(self):
|
||||
|
||||
@@ -27,10 +27,18 @@ class Mem0Storage(Storage):
|
||||
raise ValueError("User ID is required for user memory type")
|
||||
|
||||
# API key in memory config overrides the environment variable
|
||||
mem0_api_key = self.memory_config.get("config", {}).get("api_key") or os.getenv(
|
||||
"MEM0_API_KEY"
|
||||
)
|
||||
self.memory = MemoryClient(api_key=mem0_api_key)
|
||||
config = self.memory_config.get("config", {})
|
||||
mem0_api_key = config.get("api_key") or os.getenv("MEM0_API_KEY")
|
||||
mem0_org_id = config.get("org_id")
|
||||
mem0_project_id = config.get("project_id")
|
||||
|
||||
# Initialize MemoryClient with available parameters
|
||||
if mem0_org_id and mem0_project_id:
|
||||
self.memory = MemoryClient(
|
||||
api_key=mem0_api_key, org_id=mem0_org_id, project_id=mem0_project_id
|
||||
)
|
||||
else:
|
||||
self.memory = MemoryClient(api_key=mem0_api_key)
|
||||
|
||||
def _sanitize_role(self, role: str) -> str:
|
||||
"""
|
||||
@@ -57,7 +65,7 @@ class Mem0Storage(Storage):
|
||||
metadata={"type": "long_term", **metadata},
|
||||
)
|
||||
elif self.memory_type == "entities":
|
||||
entity_name = None
|
||||
entity_name = self._get_agent_name()
|
||||
self.memory.add(
|
||||
value, user_id=entity_name, metadata={"type": "entity", **metadata}
|
||||
)
|
||||
|
||||
@@ -4,18 +4,23 @@ from typing import Callable
|
||||
from crewai import Crew
|
||||
from crewai.project.utils import memoize
|
||||
|
||||
"""Decorators for defining crew components and their behaviors."""
|
||||
|
||||
|
||||
def before_kickoff(func):
|
||||
"""Marks a method to execute before crew kickoff."""
|
||||
func.is_before_kickoff = True
|
||||
return func
|
||||
|
||||
|
||||
def after_kickoff(func):
|
||||
"""Marks a method to execute after crew kickoff."""
|
||||
func.is_after_kickoff = True
|
||||
return func
|
||||
|
||||
|
||||
def task(func):
|
||||
"""Marks a method as a crew task."""
|
||||
func.is_task = True
|
||||
|
||||
@wraps(func)
|
||||
@@ -29,43 +34,51 @@ def task(func):
|
||||
|
||||
|
||||
def agent(func):
|
||||
"""Marks a method as a crew agent."""
|
||||
func.is_agent = True
|
||||
func = memoize(func)
|
||||
return func
|
||||
|
||||
|
||||
def llm(func):
|
||||
"""Marks a method as an LLM provider."""
|
||||
func.is_llm = True
|
||||
func = memoize(func)
|
||||
return func
|
||||
|
||||
|
||||
def output_json(cls):
|
||||
"""Marks a class as JSON output format."""
|
||||
cls.is_output_json = True
|
||||
return cls
|
||||
|
||||
|
||||
def output_pydantic(cls):
|
||||
"""Marks a class as Pydantic output format."""
|
||||
cls.is_output_pydantic = True
|
||||
return cls
|
||||
|
||||
|
||||
def tool(func):
|
||||
"""Marks a method as a crew tool."""
|
||||
func.is_tool = True
|
||||
return memoize(func)
|
||||
|
||||
|
||||
def callback(func):
|
||||
"""Marks a method as a crew callback."""
|
||||
func.is_callback = True
|
||||
return memoize(func)
|
||||
|
||||
|
||||
def cache_handler(func):
|
||||
"""Marks a method as a cache handler."""
|
||||
func.is_cache_handler = True
|
||||
return memoize(func)
|
||||
|
||||
|
||||
def crew(func) -> Callable[..., Crew]:
|
||||
"""Marks a method as the main crew execution point."""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs) -> Crew:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import inspect
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, TypeVar, cast
|
||||
|
||||
@@ -7,10 +8,16 @@ from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
T = TypeVar("T", bound=type)
|
||||
|
||||
"""Base decorator for creating crew classes with configuration and function management."""
|
||||
|
||||
|
||||
def CrewBase(cls: T) -> T:
|
||||
"""Wraps a class with crew functionality and configuration management."""
|
||||
|
||||
class WrappedClass(cls): # type: ignore
|
||||
is_crew_class: bool = True # type: ignore
|
||||
|
||||
@@ -24,16 +31,9 @@ def CrewBase(cls: T) -> T:
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
agents_config_path = self.base_directory / self.original_agents_config_path
|
||||
tasks_config_path = self.base_directory / self.original_tasks_config_path
|
||||
|
||||
self.agents_config = self.load_yaml(agents_config_path)
|
||||
self.tasks_config = self.load_yaml(tasks_config_path)
|
||||
|
||||
self.load_configurations()
|
||||
self.map_all_agent_variables()
|
||||
self.map_all_task_variables()
|
||||
|
||||
# Preserve all decorated functions
|
||||
self._original_functions = {
|
||||
name: method
|
||||
@@ -49,7 +49,6 @@ def CrewBase(cls: T) -> T:
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
# Store specific function types
|
||||
self._original_tasks = self._filter_functions(
|
||||
self._original_functions, "is_task"
|
||||
@@ -67,6 +66,44 @@ def CrewBase(cls: T) -> T:
|
||||
self._original_functions, "is_kickoff"
|
||||
)
|
||||
|
||||
def load_configurations(self):
|
||||
"""Load agent and task configurations from YAML files."""
|
||||
if isinstance(self.original_agents_config_path, str):
|
||||
agents_config_path = (
|
||||
self.base_directory / self.original_agents_config_path
|
||||
)
|
||||
try:
|
||||
self.agents_config = self.load_yaml(agents_config_path)
|
||||
except FileNotFoundError:
|
||||
logging.warning(
|
||||
f"Agent config file not found at {agents_config_path}. "
|
||||
"Proceeding with empty agent configurations."
|
||||
)
|
||||
self.agents_config = {}
|
||||
else:
|
||||
logging.warning(
|
||||
"No agent configuration path provided. Proceeding with empty agent configurations."
|
||||
)
|
||||
self.agents_config = {}
|
||||
|
||||
if isinstance(self.original_tasks_config_path, str):
|
||||
tasks_config_path = (
|
||||
self.base_directory / self.original_tasks_config_path
|
||||
)
|
||||
try:
|
||||
self.tasks_config = self.load_yaml(tasks_config_path)
|
||||
except FileNotFoundError:
|
||||
logging.warning(
|
||||
f"Task config file not found at {tasks_config_path}. "
|
||||
"Proceeding with empty task configurations."
|
||||
)
|
||||
self.tasks_config = {}
|
||||
else:
|
||||
logging.warning(
|
||||
"No task configuration path provided. Proceeding with empty task configurations."
|
||||
)
|
||||
self.tasks_config = {}
|
||||
|
||||
@staticmethod
|
||||
def load_yaml(config_path: Path):
|
||||
try:
|
||||
@@ -216,5 +253,5 @@ def CrewBase(cls: T) -> T:
|
||||
# Include base class (qual)name in the wrapper class (qual)name.
|
||||
WrappedClass.__name__ = CrewBase.__name__ + "(" + cls.__name__ + ")"
|
||||
WrappedClass.__qualname__ = CrewBase.__qualname__ + "(" + cls.__name__ + ")"
|
||||
|
||||
|
||||
return cast(T, WrappedClass)
|
||||
|
||||
@@ -41,6 +41,7 @@ from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.config import process_config
|
||||
from crewai.utilities.converter import Converter, convert_to_model
|
||||
from crewai.utilities.i18n import I18N
|
||||
from crewai.utilities.printer import Printer
|
||||
|
||||
|
||||
class Task(BaseModel):
|
||||
@@ -127,38 +128,40 @@ class Task(BaseModel):
|
||||
processed_by_agents: Set[str] = Field(default_factory=set)
|
||||
guardrail: Optional[Callable[[TaskOutput], Tuple[bool, Any]]] = Field(
|
||||
default=None,
|
||||
description="Function to validate task output before proceeding to next task"
|
||||
description="Function to validate task output before proceeding to next task",
|
||||
)
|
||||
max_retries: int = Field(
|
||||
default=3,
|
||||
description="Maximum number of retries when guardrail fails"
|
||||
default=3, description="Maximum number of retries when guardrail fails"
|
||||
)
|
||||
retry_count: int = Field(
|
||||
default=0,
|
||||
description="Current number of retries"
|
||||
retry_count: int = Field(default=0, description="Current number of retries")
|
||||
start_time: Optional[datetime.datetime] = Field(
|
||||
default=None, description="Start time of the task execution"
|
||||
)
|
||||
end_time: Optional[datetime.datetime] = Field(
|
||||
default=None, description="End time of the task execution"
|
||||
)
|
||||
|
||||
@field_validator("guardrail")
|
||||
@classmethod
|
||||
def validate_guardrail_function(cls, v: Optional[Callable]) -> Optional[Callable]:
|
||||
"""Validate that the guardrail function has the correct signature and behavior.
|
||||
|
||||
|
||||
While type hints provide static checking, this validator ensures runtime safety by:
|
||||
1. Verifying the function accepts exactly one parameter (the TaskOutput)
|
||||
2. Checking return type annotations match Tuple[bool, Any] if present
|
||||
3. Providing clear, immediate error messages for debugging
|
||||
|
||||
|
||||
This runtime validation is crucial because:
|
||||
- Type hints are optional and can be ignored at runtime
|
||||
- Function signatures need immediate validation before task execution
|
||||
- Clear error messages help users debug guardrail implementation issues
|
||||
|
||||
|
||||
Args:
|
||||
v: The guardrail function to validate
|
||||
|
||||
|
||||
Returns:
|
||||
The validated guardrail function
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If the function signature is invalid or return annotation
|
||||
doesn't match Tuple[bool, Any]
|
||||
@@ -171,8 +174,13 @@ class Task(BaseModel):
|
||||
# Check return annotation if present, but don't require it
|
||||
return_annotation = sig.return_annotation
|
||||
if return_annotation != inspect.Signature.empty:
|
||||
if not (return_annotation == Tuple[bool, Any] or str(return_annotation) == 'Tuple[bool, Any]'):
|
||||
raise ValueError("If return type is annotated, it must be Tuple[bool, Any]")
|
||||
if not (
|
||||
return_annotation == Tuple[bool, Any]
|
||||
or str(return_annotation) == "Tuple[bool, Any]"
|
||||
):
|
||||
raise ValueError(
|
||||
"If return type is annotated, it must be Tuple[bool, Any]"
|
||||
)
|
||||
return v
|
||||
|
||||
_telemetry: Telemetry = PrivateAttr(default_factory=Telemetry)
|
||||
@@ -181,7 +189,6 @@ class Task(BaseModel):
|
||||
_original_expected_output: Optional[str] = PrivateAttr(default=None)
|
||||
_original_output_file: Optional[str] = PrivateAttr(default=None)
|
||||
_thread: Optional[threading.Thread] = PrivateAttr(default=None)
|
||||
_execution_time: Optional[float] = PrivateAttr(default=None)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -206,25 +213,19 @@ class Task(BaseModel):
|
||||
"may_not_set_field", "This field is not to be set by the user.", {}
|
||||
)
|
||||
|
||||
def _set_start_execution_time(self) -> float:
|
||||
return datetime.datetime.now().timestamp()
|
||||
|
||||
def _set_end_execution_time(self, start_time: float) -> None:
|
||||
self._execution_time = datetime.datetime.now().timestamp() - start_time
|
||||
|
||||
@field_validator("output_file")
|
||||
@classmethod
|
||||
def output_file_validation(cls, value: Optional[str]) -> Optional[str]:
|
||||
"""Validate the output file path.
|
||||
|
||||
|
||||
Args:
|
||||
value: The output file path to validate. Can be None or a string.
|
||||
If the path contains template variables (e.g. {var}), leading slashes are preserved.
|
||||
For regular paths, leading slashes are stripped.
|
||||
|
||||
|
||||
Returns:
|
||||
The validated and potentially modified path, or None if no path was provided.
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If the path contains invalid characters, path traversal attempts,
|
||||
or other security concerns.
|
||||
@@ -234,18 +235,24 @@ class Task(BaseModel):
|
||||
|
||||
# Basic security checks
|
||||
if ".." in value:
|
||||
raise ValueError("Path traversal attempts are not allowed in output_file paths")
|
||||
|
||||
raise ValueError(
|
||||
"Path traversal attempts are not allowed in output_file paths"
|
||||
)
|
||||
|
||||
# Check for shell expansion first
|
||||
if value.startswith('~') or value.startswith('$'):
|
||||
raise ValueError("Shell expansion characters are not allowed in output_file paths")
|
||||
|
||||
if value.startswith("~") or value.startswith("$"):
|
||||
raise ValueError(
|
||||
"Shell expansion characters are not allowed in output_file paths"
|
||||
)
|
||||
|
||||
# Then check other shell special characters
|
||||
if any(char in value for char in ['|', '>', '<', '&', ';']):
|
||||
raise ValueError("Shell special characters are not allowed in output_file paths")
|
||||
if any(char in value for char in ["|", ">", "<", "&", ";"]):
|
||||
raise ValueError(
|
||||
"Shell special characters are not allowed in output_file paths"
|
||||
)
|
||||
|
||||
# Don't strip leading slash if it's a template path with variables
|
||||
if "{" in value or "}" in value:
|
||||
if "{" in value or "}" in value:
|
||||
# Validate template variable format
|
||||
template_vars = [part.split("}")[0] for part in value.split("{")[1:]]
|
||||
for var in template_vars:
|
||||
@@ -302,6 +309,12 @@ class Task(BaseModel):
|
||||
|
||||
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
||||
|
||||
@property
|
||||
def execution_duration(self) -> float | None:
|
||||
if not self.start_time or not self.end_time:
|
||||
return None
|
||||
return (self.end_time - self.start_time).total_seconds()
|
||||
|
||||
def execute_async(
|
||||
self,
|
||||
agent: BaseAgent | None = None,
|
||||
@@ -342,7 +355,7 @@ class Task(BaseModel):
|
||||
f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, like hierarchical."
|
||||
)
|
||||
|
||||
start_time = self._set_start_execution_time()
|
||||
self.start_time = datetime.datetime.now()
|
||||
self._execution_span = self._telemetry.task_started(crew=agent.crew, task=self)
|
||||
|
||||
self.prompt_context = context
|
||||
@@ -378,10 +391,14 @@ class Task(BaseModel):
|
||||
)
|
||||
|
||||
self.retry_count += 1
|
||||
context = (
|
||||
f"### Previous attempt failed validation: {guardrail_result.error}\n\n\n"
|
||||
f"### Previous result:\n{task_output.raw}\n\n\n"
|
||||
"Try again, making sure to address the validation error."
|
||||
context = self.i18n.errors("validation_error").format(
|
||||
guardrail_result_error=guardrail_result.error,
|
||||
task_output=task_output.raw,
|
||||
)
|
||||
printer = Printer()
|
||||
printer.print(
|
||||
content=f"Guardrail blocked, retrying, due to: {guardrail_result.error}\n",
|
||||
color="yellow",
|
||||
)
|
||||
return self._execute_core(agent, context, tools)
|
||||
|
||||
@@ -392,15 +409,17 @@ class Task(BaseModel):
|
||||
|
||||
if isinstance(guardrail_result.result, str):
|
||||
task_output.raw = guardrail_result.result
|
||||
pydantic_output, json_output = self._export_output(guardrail_result.result)
|
||||
pydantic_output, json_output = self._export_output(
|
||||
guardrail_result.result
|
||||
)
|
||||
task_output.pydantic = pydantic_output
|
||||
task_output.json_dict = json_output
|
||||
elif isinstance(guardrail_result.result, TaskOutput):
|
||||
task_output = guardrail_result.result
|
||||
|
||||
self.output = task_output
|
||||
self.end_time = datetime.datetime.now()
|
||||
|
||||
self._set_end_execution_time(start_time)
|
||||
if self.callback:
|
||||
self.callback(self.output)
|
||||
|
||||
@@ -432,13 +451,16 @@ class Task(BaseModel):
|
||||
tasks_slices = [self.description, output]
|
||||
return "\n".join(tasks_slices)
|
||||
|
||||
def interpolate_inputs(self, inputs: Dict[str, Union[str, int, float]]) -> None:
|
||||
def interpolate_inputs_and_add_conversation_history(
|
||||
self, inputs: Dict[str, Union[str, int, float]]
|
||||
) -> None:
|
||||
"""Interpolate inputs into the task description, expected output, and output file path.
|
||||
|
||||
Add conversation history if present.
|
||||
|
||||
Args:
|
||||
inputs: Dictionary mapping template variables to their values.
|
||||
Supported value types are strings, integers, and floats.
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If a required template variable is missing from inputs.
|
||||
"""
|
||||
@@ -455,7 +477,9 @@ class Task(BaseModel):
|
||||
try:
|
||||
self.description = self._original_description.format(**inputs)
|
||||
except KeyError as e:
|
||||
raise ValueError(f"Missing required template variable '{e.args[0]}' in description") from e
|
||||
raise ValueError(
|
||||
f"Missing required template variable '{e.args[0]}' in description"
|
||||
) from e
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Error interpolating description: {str(e)}") from e
|
||||
|
||||
@@ -472,22 +496,49 @@ class Task(BaseModel):
|
||||
input_string=self._original_output_file, inputs=inputs
|
||||
)
|
||||
except (KeyError, ValueError) as e:
|
||||
raise ValueError(f"Error interpolating output_file path: {str(e)}") from e
|
||||
raise ValueError(
|
||||
f"Error interpolating output_file path: {str(e)}"
|
||||
) from e
|
||||
|
||||
def interpolate_only(self, input_string: Optional[str], inputs: Dict[str, Union[str, int, float]]) -> str:
|
||||
if "crew_chat_messages" in inputs and inputs["crew_chat_messages"]:
|
||||
conversation_instruction = self.i18n.slice(
|
||||
"conversation_history_instruction"
|
||||
)
|
||||
|
||||
crew_chat_messages_json = str(inputs["crew_chat_messages"])
|
||||
|
||||
try:
|
||||
crew_chat_messages = json.loads(crew_chat_messages_json)
|
||||
except json.JSONDecodeError as e:
|
||||
print("An error occurred while parsing crew chat messages:", e)
|
||||
raise
|
||||
|
||||
conversation_history = "\n".join(
|
||||
f"{msg['role'].capitalize()}: {msg['content']}"
|
||||
for msg in crew_chat_messages
|
||||
if isinstance(msg, dict) and "role" in msg and "content" in msg
|
||||
)
|
||||
|
||||
self.description += (
|
||||
f"\n\n{conversation_instruction}\n\n{conversation_history}"
|
||||
)
|
||||
|
||||
def interpolate_only(
|
||||
self, input_string: Optional[str], inputs: Dict[str, Union[str, int, float]]
|
||||
) -> str:
|
||||
"""Interpolate placeholders (e.g., {key}) in a string while leaving JSON untouched.
|
||||
|
||||
|
||||
Args:
|
||||
input_string: The string containing template variables to interpolate.
|
||||
Can be None or empty, in which case an empty string is returned.
|
||||
inputs: Dictionary mapping template variables to their values.
|
||||
Supported value types are strings, integers, and floats.
|
||||
If input_string is empty or has no placeholders, inputs can be empty.
|
||||
|
||||
|
||||
Returns:
|
||||
The interpolated string with all template variables replaced with their values.
|
||||
Empty string if input_string is None or empty.
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If a required template variable is missing from inputs.
|
||||
KeyError: If a template variable is not found in the inputs dictionary.
|
||||
@@ -497,13 +548,17 @@ class Task(BaseModel):
|
||||
if "{" not in input_string and "}" not in input_string:
|
||||
return input_string
|
||||
if not inputs:
|
||||
raise ValueError("Inputs dictionary cannot be empty when interpolating variables")
|
||||
raise ValueError(
|
||||
"Inputs dictionary cannot be empty when interpolating variables"
|
||||
)
|
||||
|
||||
try:
|
||||
# Validate input types
|
||||
for key, value in inputs.items():
|
||||
if not isinstance(value, (str, int, float)):
|
||||
raise ValueError(f"Value for key '{key}' must be a string, integer, or float, got {type(value).__name__}")
|
||||
raise ValueError(
|
||||
f"Value for key '{key}' must be a string, integer, or float, got {type(value).__name__}"
|
||||
)
|
||||
|
||||
escaped_string = input_string.replace("{", "{{").replace("}", "}}")
|
||||
|
||||
@@ -512,7 +567,9 @@ class Task(BaseModel):
|
||||
|
||||
return escaped_string.format(**inputs)
|
||||
except KeyError as e:
|
||||
raise KeyError(f"Template variable '{e.args[0]}' not found in inputs dictionary") from e
|
||||
raise KeyError(
|
||||
f"Template variable '{e.args[0]}' not found in inputs dictionary"
|
||||
) from e
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Error during string interpolation: {str(e)}") from e
|
||||
|
||||
@@ -597,10 +654,10 @@ class Task(BaseModel):
|
||||
|
||||
def _save_file(self, result: Any) -> None:
|
||||
"""Save task output to a file.
|
||||
|
||||
|
||||
Args:
|
||||
result: The result to save to the file. Can be a dict or any stringifiable object.
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If output_file is not set
|
||||
RuntimeError: If there is an error writing to the file
|
||||
@@ -618,6 +675,7 @@ class Task(BaseModel):
|
||||
with resolved_path.open("w", encoding="utf-8") as file:
|
||||
if isinstance(result, dict):
|
||||
import json
|
||||
|
||||
json.dump(result, file, ensure_ascii=False, indent=2)
|
||||
else:
|
||||
file.write(str(result))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
@@ -54,12 +54,12 @@ class BaseAgentTool(BaseTool):
|
||||
) -> str:
|
||||
"""
|
||||
Execute delegation to an agent with case-insensitive and whitespace-tolerant matching.
|
||||
|
||||
|
||||
Args:
|
||||
agent_name: Name/role of the agent to delegate to (case-insensitive)
|
||||
task: The specific question or task to delegate
|
||||
context: Optional additional context for the task execution
|
||||
|
||||
|
||||
Returns:
|
||||
str: The execution result from the delegated agent or an error message
|
||||
if the agent cannot be found
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from inspect import signature
|
||||
from typing import Any, Callable, Type, get_args, get_origin
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, create_model, validator
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
PydanticDeprecatedSince20,
|
||||
create_model,
|
||||
validator,
|
||||
)
|
||||
from pydantic import BaseModel as PydanticBaseModel
|
||||
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
|
||||
# Ignore all "PydanticDeprecatedSince20" warnings globally
|
||||
warnings.filterwarnings("ignore", category=PydanticDeprecatedSince20)
|
||||
|
||||
|
||||
class BaseTool(BaseModel, ABC):
|
||||
class _ArgsSchemaPlaceholder(PydanticBaseModel):
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import ast
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
from difflib import SequenceMatcher
|
||||
from json import JSONDecodeError
|
||||
from textwrap import dedent
|
||||
from typing import Any, List, Union
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import json5
|
||||
from json_repair import repair_json
|
||||
|
||||
import crewai.utilities.events as events
|
||||
from crewai.agents.tools_handler import ToolsHandler
|
||||
@@ -19,7 +24,15 @@ try:
|
||||
import agentops # type: ignore
|
||||
except ImportError:
|
||||
agentops = None
|
||||
OPENAI_BIGGER_MODELS = ["gpt-4", "gpt-4o", "o1-preview", "o1-mini", "o1", "o3", "o3-mini"]
|
||||
OPENAI_BIGGER_MODELS = [
|
||||
"gpt-4",
|
||||
"gpt-4o",
|
||||
"o1-preview",
|
||||
"o1-mini",
|
||||
"o1",
|
||||
"o3",
|
||||
"o3-mini",
|
||||
]
|
||||
|
||||
|
||||
class ToolUsageErrorException(Exception):
|
||||
@@ -80,7 +93,7 @@ class ToolUsage:
|
||||
self._max_parsing_attempts = 2
|
||||
self._remember_format_after_usages = 4
|
||||
|
||||
def parse(self, tool_string: str):
|
||||
def parse_tool_calling(self, tool_string: str):
|
||||
"""Parse the tool string and return the tool calling."""
|
||||
return self._tool_calling(tool_string)
|
||||
|
||||
@@ -94,7 +107,6 @@ class ToolUsage:
|
||||
self.task.increment_tools_errors()
|
||||
return error
|
||||
|
||||
# BUG? The code below seems to be unreachable
|
||||
try:
|
||||
tool = self._select_tool(calling.tool_name)
|
||||
except Exception as e:
|
||||
@@ -116,7 +128,7 @@ class ToolUsage:
|
||||
self._printer.print(content=f"\n\n{error}\n", color="red")
|
||||
return error
|
||||
|
||||
return f"{self._use(tool_string=tool_string, tool=tool, calling=calling)}" # type: ignore # BUG?: "_use" of "ToolUsage" does not return a value (it only ever returns None)
|
||||
return f"{self._use(tool_string=tool_string, tool=tool, calling=calling)}"
|
||||
|
||||
def _use(
|
||||
self,
|
||||
@@ -169,7 +181,7 @@ class ToolUsage:
|
||||
|
||||
if calling.arguments:
|
||||
try:
|
||||
acceptable_args = tool.args_schema.schema()["properties"].keys() # type: ignore # Item "None" of "type[BaseModel] | None" has no attribute "schema"
|
||||
acceptable_args = tool.args_schema.model_json_schema()["properties"].keys() # type: ignore
|
||||
arguments = {
|
||||
k: v
|
||||
for k, v in calling.arguments.items()
|
||||
@@ -349,13 +361,13 @@ class ToolUsage:
|
||||
tool_name = self.action.tool
|
||||
tool = self._select_tool(tool_name)
|
||||
try:
|
||||
tool_input = self._validate_tool_input(self.action.tool_input)
|
||||
arguments = ast.literal_eval(tool_input)
|
||||
arguments = self._validate_tool_input(self.action.tool_input)
|
||||
|
||||
except Exception:
|
||||
if raise_error:
|
||||
raise
|
||||
else:
|
||||
return ToolUsageErrorException( # type: ignore # Incompatible return value type (got "ToolUsageErrorException", expected "ToolCalling | InstructorToolCalling")
|
||||
return ToolUsageErrorException(
|
||||
f'{self._i18n.errors("tool_arguments_error")}'
|
||||
)
|
||||
|
||||
@@ -363,14 +375,14 @@ class ToolUsage:
|
||||
if raise_error:
|
||||
raise
|
||||
else:
|
||||
return ToolUsageErrorException( # type: ignore # Incompatible return value type (got "ToolUsageErrorException", expected "ToolCalling | InstructorToolCalling")
|
||||
return ToolUsageErrorException(
|
||||
f'{self._i18n.errors("tool_arguments_error")}'
|
||||
)
|
||||
|
||||
return ToolCalling(
|
||||
tool_name=tool.name,
|
||||
arguments=arguments,
|
||||
log=tool_string, # type: ignore
|
||||
log=tool_string,
|
||||
)
|
||||
|
||||
def _tool_calling(
|
||||
@@ -396,57 +408,55 @@ class ToolUsage:
|
||||
)
|
||||
return self._tool_calling(tool_string)
|
||||
|
||||
def _validate_tool_input(self, tool_input: str) -> str:
|
||||
def _validate_tool_input(self, tool_input: Optional[str]) -> Dict[str, Any]:
|
||||
if tool_input is None:
|
||||
return {}
|
||||
|
||||
if not isinstance(tool_input, str) or not tool_input.strip():
|
||||
raise Exception(
|
||||
"Tool input must be a valid dictionary in JSON or Python literal format"
|
||||
)
|
||||
|
||||
# Attempt 1: Parse as JSON
|
||||
try:
|
||||
ast.literal_eval(tool_input)
|
||||
return tool_input
|
||||
except Exception:
|
||||
# Clean and ensure the string is properly enclosed in braces
|
||||
tool_input = tool_input.strip()
|
||||
if not tool_input.startswith("{"):
|
||||
tool_input = "{" + tool_input
|
||||
if not tool_input.endswith("}"):
|
||||
tool_input += "}"
|
||||
arguments = json.loads(tool_input)
|
||||
if isinstance(arguments, dict):
|
||||
return arguments
|
||||
except (JSONDecodeError, TypeError):
|
||||
pass # Continue to the next parsing attempt
|
||||
|
||||
# Manually split the input into key-value pairs
|
||||
entries = tool_input.strip("{} ").split(",")
|
||||
formatted_entries = []
|
||||
# Attempt 2: Parse as Python literal
|
||||
try:
|
||||
arguments = ast.literal_eval(tool_input)
|
||||
if isinstance(arguments, dict):
|
||||
return arguments
|
||||
except (ValueError, SyntaxError):
|
||||
pass # Continue to the next parsing attempt
|
||||
|
||||
for entry in entries:
|
||||
if ":" not in entry:
|
||||
continue # Skip malformed entries
|
||||
key, value = entry.split(":", 1)
|
||||
# Attempt 3: Parse as JSON5
|
||||
try:
|
||||
arguments = json5.loads(tool_input)
|
||||
if isinstance(arguments, dict):
|
||||
return arguments
|
||||
except (JSONDecodeError, ValueError, TypeError):
|
||||
pass # Continue to the next parsing attempt
|
||||
|
||||
# Remove extraneous white spaces and quotes, replace single quotes
|
||||
key = key.strip().strip('"').replace("'", '"')
|
||||
value = value.strip()
|
||||
# Attempt 4: Repair JSON
|
||||
try:
|
||||
repaired_input = repair_json(tool_input)
|
||||
self._printer.print(
|
||||
content=f"Repaired JSON: {repaired_input}", color="blue"
|
||||
)
|
||||
arguments = json.loads(repaired_input)
|
||||
if isinstance(arguments, dict):
|
||||
return arguments
|
||||
except Exception as e:
|
||||
self._printer.print(content=f"Failed to repair JSON: {e}", color="red")
|
||||
|
||||
# Handle replacement of single quotes at the start and end of the value string
|
||||
if value.startswith("'") and value.endswith("'"):
|
||||
value = value[1:-1] # Remove single quotes
|
||||
value = (
|
||||
'"' + value.replace('"', '\\"') + '"'
|
||||
) # Re-encapsulate with double quotes
|
||||
elif value.isdigit(): # Check if value is a digit, hence integer
|
||||
value = value
|
||||
elif value.lower() in [
|
||||
"true",
|
||||
"false",
|
||||
]: # Check for boolean and null values
|
||||
value = value.lower().capitalize()
|
||||
elif value.lower() == "null":
|
||||
value = "None"
|
||||
else:
|
||||
# Assume the value is a string and needs quotes
|
||||
value = '"' + value.replace('"', '\\"') + '"'
|
||||
|
||||
# Rebuild the entry with proper quoting
|
||||
formatted_entry = f'"{key}": {value}'
|
||||
formatted_entries.append(formatted_entry)
|
||||
|
||||
# Reconstruct the JSON string
|
||||
new_json_string = "{" + ", ".join(formatted_entries) + "}"
|
||||
return new_json_string
|
||||
# If all parsing attempts fail, raise an error
|
||||
raise Exception(
|
||||
"Tool input must be a valid dictionary in JSON or Python literal format"
|
||||
)
|
||||
|
||||
def on_tool_error(self, tool: Any, tool_calling: ToolCalling, e: Exception) -> None:
|
||||
event_data = self._prepare_event_data(tool, tool_calling)
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
"task": "\nCurrent Task: {input}\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:",
|
||||
"memory": "\n\n# Useful context: \n{memory}",
|
||||
"role_playing": "You are {role}. {backstory}\nYour personal goal is: {goal}",
|
||||
"tools": "\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\n{tools}\n\nUse the following format:\n\nThought: you should always think about what to do\nAction: the action to take, only one name of [{tool_names}], just the name, exactly as it's written.\nAction Input: the input to the action, just a simple python dictionary, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n\nOnce all necessary information is gathered:\n\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n",
|
||||
"no_tools": "\nTo give my best complete final answer to the task use the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!",
|
||||
"format": "I MUST either use a tool (use one at time) OR give my best final answer not both at the same time. To Use the following format:\n\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action, dictionary enclosed in curly braces\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n",
|
||||
"final_answer_format": "If you don't need to use any more tools, you must give your best complete final answer, make sure it satisfies the expected criteria, use the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer: my best complete final answer to the task.\n\n",
|
||||
"format_without_tools": "\nSorry, I didn't use the right format. I MUST either use a tool (among the available ones), OR give my best final answer.\nI just remembered the expected format I must follow:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n",
|
||||
"tools": "\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\n{tools}\n\nIMPORTANT: Use the following format in your response:\n\n```\nThought: you should always think about what to do\nAction: the action to take, only one name of [{tool_names}], just the name, exactly as it's written.\nAction Input: the input to the action, just a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce all necessary information is gathered, return the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n```",
|
||||
"no_tools": "\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!",
|
||||
"format": "I MUST either use a tool (use one at time) OR give my best final answer not both at the same time. When responding, I must use the following format:\n\n```\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action, dictionary enclosed in curly braces\nObservation: the result of the action\n```\nThis Thought/Action/Action Input/Result can repeat N times. Once I know the final answer, I must return the following format:\n\n```\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n```",
|
||||
"final_answer_format": "If you don't need to use any more tools, you must give your best complete final answer, make sure it satisfies the expected criteria, use the EXACT format below:\n\n```\nThought: I now can give a great answer\nFinal Answer: my best complete final answer to the task.\n\n```",
|
||||
"format_without_tools": "\nSorry, I didn't use the right format. I MUST either use a tool (among the available ones), OR give my best final answer.\nHere is the expected format I must follow:\n\n```\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action\nObservation: the result of the action\n```\n This Thought/Action/Action Input/Result process can repeat N times. Once I know the final answer, I must return the following format:\n\n```\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n```",
|
||||
"task_with_context": "{task}\n\nThis is the context you're working with:\n{context}",
|
||||
"expected_output": "\nThis is the expect criteria for your final answer: {expected_output}\nyou MUST return the actual complete content as the final answer, not a summary.",
|
||||
"human_feedback": "You got human feedback on your work, re-evaluate it and give a new Final Answer when ready.\n {human_feedback}",
|
||||
@@ -23,10 +23,11 @@
|
||||
"summary": "This is a summary of our conversation so far:\n{merged_summary}",
|
||||
"manager_request": "Your best answer to your coworker asking you this, accounting for the context shared.",
|
||||
"formatted_task_instructions": "Ensure your final answer contains only the content in the following format: {output_format}\n\nEnsure the final output does not include any code block markers like ```json or ```python.",
|
||||
"human_feedback_classification": "Determine if the following feedback indicates that the user is satisfied or if further changes are needed. Respond with 'True' if further changes are needed, or 'False' if the user is satisfied. **Important** Do not include any additional commentary outside of your 'True' or 'False' response.\n\nFeedback: \"{feedback}\""
|
||||
"human_feedback_classification": "Determine if the following feedback indicates that the user is satisfied or if further changes are needed. Respond with 'True' if further changes are needed, or 'False' if the user is satisfied. **Important** Do not include any additional commentary outside of your 'True' or 'False' response.\n\nFeedback: \"{feedback}\"",
|
||||
"conversation_history_instruction": "You are a member of a crew collaborating to achieve a common goal. Your task is a specific action that contributes to this larger objective. For additional context, please review the conversation history between you and the user that led to the initiation of this crew. Use any relevant information or feedback from the conversation to inform your task execution and ensure your response aligns with both the immediate task and the crew's overall goals."
|
||||
},
|
||||
"errors": {
|
||||
"force_final_answer_error": "You can't keep going, this was the best you could do.\n {formatted_answer.text}",
|
||||
"force_final_answer_error": "You can't keep going, here is the best final answer you generated:\n\n {formatted_answer}",
|
||||
"force_final_answer": "Now it's time you MUST give your absolute best final answer. You'll ignore all previous instructions, stop using any tools, and just return your absolute BEST Final answer.",
|
||||
"agent_tool_unexisting_coworker": "\nError executing tool. coworker mentioned not found, it must be one of the following options:\n{coworkers}\n",
|
||||
"task_repeated_usage": "I tried reusing the same input, I must stop using this action input. I'll try something else instead.\n\n",
|
||||
@@ -34,14 +35,15 @@
|
||||
"tool_arguments_error": "Error: the Action Input is not a valid key, value dictionary.",
|
||||
"wrong_tool_name": "You tried to use the tool {tool}, but it doesn't exist. You must use one of the following tools, use one at time: {tools}.",
|
||||
"tool_usage_exception": "I encountered an error while trying to use the tool. This was the error: {error}.\n Tool {tool} accepts these inputs: {tool_inputs}",
|
||||
"agent_tool_execution_error": "Error executing task with agent '{agent_role}'. Error: {error}"
|
||||
"agent_tool_execution_error": "Error executing task with agent '{agent_role}'. Error: {error}",
|
||||
"validation_error": "### Previous attempt failed validation: {guardrail_result_error}\n\n\n### Previous result:\n{task_output}\n\n\nTry again, making sure to address the validation error."
|
||||
},
|
||||
"tools": {
|
||||
"delegate_work": "Delegate a specific task to one of the following coworkers: {coworkers}\nThe input to this tool should be the coworker, the task you want them to do, and ALL necessary context to execute the task, they know nothing about the task, so share absolute everything you know, don't reference things but instead explain them.",
|
||||
"ask_question": "Ask a specific question to one of the following coworkers: {coworkers}\nThe input to this tool should be the coworker, the question you have for them, and ALL necessary context to ask the question properly, they know nothing about the question, so share absolute everything you know, don't reference things but instead explain them.",
|
||||
"add_image": {
|
||||
"name": "Add image to content",
|
||||
"description": "See image to understand it's content, you can optionally ask a question about the image",
|
||||
"description": "See image to understand its content, you can optionally ask a question about the image",
|
||||
"default_action": "Please provide a detailed description of this image, including all visual elements, context, and any notable details you can observe."
|
||||
}
|
||||
}
|
||||
|
||||
40
src/crewai/types/crew_chat.py
Normal file
40
src/crewai/types/crew_chat.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ChatInputField(BaseModel):
|
||||
"""
|
||||
Represents a single required input for the crew, with a name and short description.
|
||||
Example:
|
||||
{
|
||||
"name": "topic",
|
||||
"description": "The topic to focus on for the conversation"
|
||||
}
|
||||
"""
|
||||
|
||||
name: str = Field(..., description="The name of the input field")
|
||||
description: str = Field(..., description="A short description of the input field")
|
||||
|
||||
|
||||
class ChatInputs(BaseModel):
|
||||
"""
|
||||
Holds a high-level crew_description plus a list of ChatInputFields.
|
||||
Example:
|
||||
{
|
||||
"crew_name": "topic-based-qa",
|
||||
"crew_description": "Use this crew for topic-based Q&A",
|
||||
"inputs": [
|
||||
{"name": "topic", "description": "The topic to focus on"},
|
||||
{"name": "username", "description": "Name of the user"},
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
crew_name: str = Field(..., description="The name of the crew")
|
||||
crew_description: str = Field(
|
||||
..., description="A description of the crew's purpose"
|
||||
)
|
||||
inputs: List[ChatInputField] = Field(
|
||||
default_factory=list, description="A list of input fields for the crew"
|
||||
)
|
||||
@@ -26,17 +26,24 @@ class Converter(OutputConverter):
|
||||
if self.llm.supports_function_calling():
|
||||
return self._create_instructor().to_pydantic()
|
||||
else:
|
||||
return self.llm.call(
|
||||
response = self.llm.call(
|
||||
[
|
||||
{"role": "system", "content": self.instructions},
|
||||
{"role": "user", "content": self.text},
|
||||
]
|
||||
)
|
||||
return self.model.model_validate_json(response)
|
||||
except ValidationError as e:
|
||||
if current_attempt < self.max_attempts:
|
||||
return self.to_pydantic(current_attempt + 1)
|
||||
raise ConverterError(
|
||||
f"Failed to convert text into a Pydantic model due to the following validation error: {e}"
|
||||
)
|
||||
except Exception as e:
|
||||
if current_attempt < self.max_attempts:
|
||||
return self.to_pydantic(current_attempt + 1)
|
||||
return ConverterError(
|
||||
f"Failed to convert text into a pydantic model due to the following error: {e}"
|
||||
raise ConverterError(
|
||||
f"Failed to convert text into a Pydantic model due to the following error: {e}"
|
||||
)
|
||||
|
||||
def to_json(self, current_attempt=1):
|
||||
@@ -66,7 +73,6 @@ class Converter(OutputConverter):
|
||||
llm=self.llm,
|
||||
model=self.model,
|
||||
content=self.text,
|
||||
instructions=self.instructions,
|
||||
)
|
||||
return inst
|
||||
|
||||
@@ -187,10 +193,15 @@ def convert_with_instructions(
|
||||
|
||||
|
||||
def get_conversion_instructions(model: Type[BaseModel], llm: Any) -> str:
|
||||
instructions = "I'm gonna convert this raw text into valid JSON."
|
||||
instructions = "Please convert the following text into valid JSON."
|
||||
if llm.supports_function_calling():
|
||||
model_schema = PydanticSchemaParser(model=model).get_schema()
|
||||
instructions = f"{instructions}\n\nThe json should have the following structure, with the following keys:\n{model_schema}"
|
||||
instructions += (
|
||||
f"\n\nThe JSON should follow this schema:\n```json\n{model_schema}\n```"
|
||||
)
|
||||
else:
|
||||
model_description = generate_model_description(model)
|
||||
instructions += f"\n\nThe JSON should follow this format:\n{model_description}"
|
||||
return instructions
|
||||
|
||||
|
||||
@@ -230,9 +241,13 @@ def generate_model_description(model: Type[BaseModel]) -> str:
|
||||
origin = get_origin(field_type)
|
||||
args = get_args(field_type)
|
||||
|
||||
if origin is Union and type(None) in args:
|
||||
if origin is Union or (origin is None and len(args) > 0):
|
||||
# Handle both Union and the new '|' syntax
|
||||
non_none_args = [arg for arg in args if arg is not type(None)]
|
||||
return f"Optional[{describe_field(non_none_args[0])}]"
|
||||
if len(non_none_args) == 1:
|
||||
return f"Optional[{describe_field(non_none_args[0])}]"
|
||||
else:
|
||||
return f"Optional[Union[{', '.join(describe_field(arg) for arg in non_none_args)}]]"
|
||||
elif origin is list:
|
||||
return f"List[{describe_field(args[0])}]"
|
||||
elif origin is dict:
|
||||
@@ -241,8 +256,10 @@ def generate_model_description(model: Type[BaseModel]) -> str:
|
||||
return f"Dict[{key_type}, {value_type}]"
|
||||
elif isinstance(field_type, type) and issubclass(field_type, BaseModel):
|
||||
return generate_model_description(field_type)
|
||||
else:
|
||||
elif hasattr(field_type, "__name__"):
|
||||
return field_type.__name__
|
||||
else:
|
||||
return str(field_type)
|
||||
|
||||
fields = model.__annotations__
|
||||
field_descriptions = [
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""JSON encoder for handling CrewAI specific types."""
|
||||
|
||||
import json
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
@@ -8,6 +10,7 @@ from pydantic import BaseModel
|
||||
|
||||
|
||||
class CrewJSONEncoder(json.JSONEncoder):
|
||||
"""Custom JSON encoder for CrewAI objects and special types."""
|
||||
def default(self, obj):
|
||||
if isinstance(obj, BaseModel):
|
||||
return self._handle_pydantic_model(obj)
|
||||
|
||||
@@ -6,9 +6,10 @@ from pydantic import BaseModel, ValidationError
|
||||
|
||||
from crewai.agents.parser import OutputParserException
|
||||
|
||||
"""Parser for converting text outputs into Pydantic models."""
|
||||
|
||||
class CrewPydanticOutputParser:
|
||||
"""Parses the text into pydantic models"""
|
||||
"""Parses text outputs into specified Pydantic models."""
|
||||
|
||||
pydantic_object: Type[BaseModel]
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class EmbeddingConfigurator:
|
||||
"vertexai": self._configure_vertexai,
|
||||
"google": self._configure_google,
|
||||
"cohere": self._configure_cohere,
|
||||
"voyageai": self._configure_voyageai,
|
||||
"bedrock": self._configure_bedrock,
|
||||
"huggingface": self._configure_huggingface,
|
||||
"watson": self._configure_watson,
|
||||
@@ -42,7 +43,6 @@ class EmbeddingConfigurator:
|
||||
raise Exception(
|
||||
f"Unsupported embedding provider: {provider}, supported providers: {list(self.embedding_functions.keys())}"
|
||||
)
|
||||
|
||||
return self.embedding_functions[provider](config, model_name)
|
||||
|
||||
@staticmethod
|
||||
@@ -124,6 +124,17 @@ class EmbeddingConfigurator:
|
||||
api_key=config.get("api_key"),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _configure_voyageai(config, model_name):
|
||||
from chromadb.utils.embedding_functions.voyageai_embedding_function import (
|
||||
VoyageAIEmbeddingFunction,
|
||||
)
|
||||
|
||||
return VoyageAIEmbeddingFunction(
|
||||
model_name=model_name,
|
||||
api_key=config.get("api_key"),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _configure_bedrock(config, model_name):
|
||||
from chromadb.utils.embedding_functions.amazon_bedrock_embedding_function import (
|
||||
|
||||
39
src/crewai/utilities/errors.py
Normal file
39
src/crewai/utilities/errors.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""Error message definitions for CrewAI database operations."""
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class DatabaseOperationError(Exception):
|
||||
"""Base exception class for database operation errors."""
|
||||
|
||||
def __init__(self, message: str, original_error: Optional[Exception] = None):
|
||||
"""Initialize the database operation error.
|
||||
|
||||
Args:
|
||||
message: The error message to display
|
||||
original_error: The original exception that caused this error, if any
|
||||
"""
|
||||
super().__init__(message)
|
||||
self.original_error = original_error
|
||||
|
||||
|
||||
class DatabaseError:
|
||||
"""Standardized error message templates for database operations."""
|
||||
|
||||
INIT_ERROR: str = "Database initialization error: {}"
|
||||
SAVE_ERROR: str = "Error saving task outputs: {}"
|
||||
UPDATE_ERROR: str = "Error updating task outputs: {}"
|
||||
LOAD_ERROR: str = "Error loading task outputs: {}"
|
||||
DELETE_ERROR: str = "Error deleting task outputs: {}"
|
||||
|
||||
@classmethod
|
||||
def format_error(cls, template: str, error: Exception) -> str:
|
||||
"""Format an error message with the given template and error.
|
||||
|
||||
Args:
|
||||
template: The error message template to use
|
||||
error: The exception to format into the template
|
||||
|
||||
Returns:
|
||||
The formatted error message
|
||||
"""
|
||||
return template.format(str(error))
|
||||
@@ -180,12 +180,12 @@ class CrewEvaluator:
|
||||
self._test_result_span = self._telemetry.individual_test_result_span(
|
||||
self.crew,
|
||||
evaluation_result.pydantic.quality,
|
||||
current_task._execution_time,
|
||||
current_task.execution_duration,
|
||||
self.openai_model_name,
|
||||
)
|
||||
self.tasks_scores[self.iteration].append(evaluation_result.pydantic.quality)
|
||||
self.run_execution_times[self.iteration].append(
|
||||
current_task._execution_time
|
||||
current_task.execution_duration
|
||||
)
|
||||
else:
|
||||
raise ValueError("Evaluation result is not in the expected format")
|
||||
|
||||
@@ -96,9 +96,9 @@ class TaskEvaluator:
|
||||
final_aggregated_data = ""
|
||||
for _, data in output_training_data.items():
|
||||
final_aggregated_data += (
|
||||
f"Initial Output:\n{data['initial_output']}\n\n"
|
||||
f"Human Feedback:\n{data['human_feedback']}\n\n"
|
||||
f"Improved Output:\n{data['improved_output']}\n\n"
|
||||
f"Initial Output:\n{data.get('initial_output', '')}\n\n"
|
||||
f"Human Feedback:\n{data.get('human_feedback', '')}\n\n"
|
||||
f"Improved Output:\n{data.get('improved_output', '')}\n\n"
|
||||
)
|
||||
|
||||
evaluation_query = (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user