mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-08 16:35:08 +00:00
Compare commits
2 Commits
devin/1747
...
devin/1743
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f46d19e193 | ||
|
|
d8571dc196 |
@@ -267,6 +267,7 @@ In addition to the sequential process, you can use the hierarchical process, whi
|
||||
- **Role-Based Agent Design**: Customize agents with specific roles, goals, and tools.
|
||||
- **Autonomous Inter-Agent Delegation**: Agents can autonomously delegate tasks and inquire amongst themselves, enhancing problem-solving efficiency.
|
||||
- **Flexible Task Management**: Define tasks with customizable tools and assign them to agents dynamically.
|
||||
- **Tool Instructions**: Attach specific usage instructions to tools for better control over when and how agents use them.
|
||||
- **Processes Driven**: Currently only supports `sequential` task execution and `hierarchical` processes, but more complex processes like consensual and autonomous are being worked on.
|
||||
- **Save output as file**: Save the output of individual tasks as a file, so you can use it later.
|
||||
- **Parse output as Pydantic or Json**: Parse the output of individual tasks as a Pydantic model or as a Json if you want to.
|
||||
|
||||
153
docs/how-to/ToolInstructions.md
Normal file
153
docs/how-to/ToolInstructions.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Tool Instructions
|
||||
|
||||
CrewAI allows you to provide specific instructions for when and how to use tools. This is useful when you want to guide agents on proper tool usage without cluttering their backstory.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import ScrapeWebsiteTool
|
||||
from crewai.tools import ToolWithInstruction
|
||||
|
||||
# Create a tool with instructions
|
||||
scrape_tool = ScrapeWebsiteTool()
|
||||
scrape_with_instructions = ToolWithInstruction(
|
||||
tool=scrape_tool,
|
||||
instructions="""
|
||||
ALWAYS use this tool when making a joke.
|
||||
NEVER use this tool when making joke about someone's mom.
|
||||
"""
|
||||
)
|
||||
|
||||
# Use the tool with an agent
|
||||
agent = Agent(
|
||||
role="Comedian",
|
||||
goal="Create hilarious and engaging jokes",
|
||||
backstory="""
|
||||
You are a professional stand-up comedian with years of experience in crafting jokes.
|
||||
You have a great sense of humor and can create jokes about any topic
|
||||
while keeping them appropriate and entertaining.
|
||||
""",
|
||||
tools=[scrape_with_instructions],
|
||||
)
|
||||
```
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
### Example 1: Research Assistant with Web Search Tool
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import SearchTool
|
||||
from crewai.tools import ToolWithInstruction
|
||||
|
||||
search_tool = SearchTool()
|
||||
search_with_instructions = ToolWithInstruction(
|
||||
tool=search_tool,
|
||||
instructions="""
|
||||
Use this tool ONLY for factual information that requires up-to-date data.
|
||||
ALWAYS verify information by searching multiple sources.
|
||||
DO NOT use this tool for speculative questions or opinions.
|
||||
"""
|
||||
)
|
||||
|
||||
research_agent = Agent(
|
||||
role="Research Analyst",
|
||||
goal="Provide accurate and well-sourced information",
|
||||
backstory="You are a meticulous research analyst with attention to detail and fact-checking.",
|
||||
tools=[search_with_instructions],
|
||||
)
|
||||
```
|
||||
|
||||
### Example 2: Data Scientist with Multiple Analysis Tools
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from crewai_tools import PythonTool, DataVisualizationTool
|
||||
from crewai.tools import ToolWithInstruction
|
||||
|
||||
# Python tool for data processing
|
||||
python_tool = PythonTool()
|
||||
python_with_instructions = ToolWithInstruction(
|
||||
tool=python_tool,
|
||||
instructions="""
|
||||
Use this tool for data cleaning, transformation, and statistical analysis.
|
||||
ALWAYS include comments in your code.
|
||||
DO NOT use this tool for creating visualizations.
|
||||
"""
|
||||
)
|
||||
|
||||
# Visualization tool
|
||||
viz_tool = DataVisualizationTool()
|
||||
viz_with_instructions = ToolWithInstruction(
|
||||
tool=viz_tool,
|
||||
instructions="""
|
||||
Use this tool ONLY for creating data visualizations.
|
||||
ALWAYS label axes and include titles in your charts.
|
||||
PREFER simple visualizations that clearly communicate the main insight.
|
||||
"""
|
||||
)
|
||||
|
||||
data_scientist = Agent(
|
||||
role="Data Scientist",
|
||||
goal="Analyze data and create insightful visualizations",
|
||||
backstory="You are an experienced data scientist who excels at finding patterns in data.",
|
||||
tools=[python_with_instructions, viz_with_instructions],
|
||||
)
|
||||
```
|
||||
|
||||
## How Instructions Are Presented to Agents
|
||||
|
||||
When an agent considers using a tool, the instructions are included in the tool's description. For example, a tool with instructions might appear to the agent like this:
|
||||
|
||||
```
|
||||
Tool: search_web
|
||||
Description: Search the web for information on a given topic.
|
||||
Instructions: Use this tool ONLY for factual information that requires up-to-date data.
|
||||
ALWAYS verify information by searching multiple sources.
|
||||
DO NOT use this tool for speculative questions or opinions.
|
||||
```
|
||||
|
||||
This clear presentation helps the agent understand when and how to use the tool appropriately.
|
||||
|
||||
## Dynamically Updating Instructions
|
||||
|
||||
You can update tool instructions dynamically during execution:
|
||||
|
||||
```python
|
||||
# Create a tool with initial instructions
|
||||
search_with_instructions = ToolWithInstruction(
|
||||
tool=search_tool,
|
||||
instructions="Initial instructions for tool usage"
|
||||
)
|
||||
|
||||
# Later, update the instructions based on new requirements
|
||||
search_with_instructions.update_instructions("Updated instructions for tool usage")
|
||||
```
|
||||
|
||||
## Error Handling and Best Practices
|
||||
|
||||
### Validation
|
||||
|
||||
The `ToolWithInstruction` class includes validation to ensure instructions are not empty and don't exceed a maximum length. If you provide invalid instructions, a `ValueError` will be raised.
|
||||
|
||||
### Best Practices for Writing Instructions
|
||||
|
||||
1. **Be specific and clear** about when to use and when not to use the tool
|
||||
2. **Use imperative language** like "ALWAYS", "NEVER", "USE", "DO NOT USE"
|
||||
3. **Keep instructions concise** but comprehensive
|
||||
4. **Include examples** of good and bad usage scenarios when possible
|
||||
5. **Format instructions** with line breaks for readability
|
||||
|
||||
## When to Use Tool Instructions
|
||||
|
||||
Tool instructions are useful when:
|
||||
|
||||
1. You want to specify precise conditions for tool usage
|
||||
2. You have multiple similar tools that should be used in different situations
|
||||
3. You want to keep the agent's backstory focused on its role and personality,
|
||||
not technical details about tools
|
||||
4. You need to provide technical guidance on how to format inputs or interpret outputs
|
||||
5. You want to enforce consistent tool usage across multiple agents
|
||||
|
||||
Tool instructions are semantically more correct than putting tool usage guidelines in the agent's backstory.
|
||||
@@ -113,10 +113,6 @@ class Crew(BaseModel):
|
||||
default=False,
|
||||
description="Whether the crew should use memory to store memories of it's execution",
|
||||
)
|
||||
memory_verbose: bool = Field(
|
||||
default=False,
|
||||
description="Whether to show verbose logs about memory operations",
|
||||
)
|
||||
memory_config: Optional[Dict[str, Any]] = Field(
|
||||
default=None,
|
||||
description="Configuration for the memory to be used for the crew.",
|
||||
@@ -261,7 +257,7 @@ class Crew(BaseModel):
|
||||
"""Set private attributes."""
|
||||
if self.memory:
|
||||
self._long_term_memory = (
|
||||
self.long_term_memory if self.long_term_memory else LongTermMemory(memory_verbose=self.memory_verbose)
|
||||
self.long_term_memory if self.long_term_memory else LongTermMemory()
|
||||
)
|
||||
self._short_term_memory = (
|
||||
self.short_term_memory
|
||||
@@ -269,17 +265,16 @@ class Crew(BaseModel):
|
||||
else ShortTermMemory(
|
||||
crew=self,
|
||||
embedder_config=self.embedder,
|
||||
memory_verbose=self.memory_verbose,
|
||||
)
|
||||
)
|
||||
self._entity_memory = (
|
||||
self.entity_memory
|
||||
if self.entity_memory
|
||||
else EntityMemory(crew=self, embedder_config=self.embedder, memory_verbose=self.memory_verbose)
|
||||
else EntityMemory(crew=self, embedder_config=self.embedder)
|
||||
)
|
||||
if hasattr(self, "memory_config") and self.memory_config is not None:
|
||||
self._user_memory = (
|
||||
self.user_memory if self.user_memory else UserMemory(crew=self, memory_verbose=self.memory_verbose)
|
||||
self.user_memory if self.user_memory else UserMemory(crew=self)
|
||||
)
|
||||
else:
|
||||
self._user_memory = None
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from crewai.memory.entity.entity_memory_item import EntityMemoryItem
|
||||
from crewai.memory.memory import Memory, MemoryOperationError
|
||||
from crewai.memory.memory import Memory
|
||||
from crewai.memory.storage.rag_storage import RAGStorage
|
||||
|
||||
|
||||
@@ -10,24 +8,9 @@ class EntityMemory(Memory):
|
||||
EntityMemory class for managing structured information about entities
|
||||
and their relationships using SQLite storage.
|
||||
Inherits from the Memory class.
|
||||
|
||||
Attributes:
|
||||
memory_provider: The memory provider to use, if any.
|
||||
storage: The storage backend for the memory.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
|
||||
def __init__(self, crew=None, embedder_config=None, storage=None, path=None, memory_verbose=False):
|
||||
"""
|
||||
Initialize an EntityMemory instance.
|
||||
|
||||
Args:
|
||||
crew: The crew to associate with this memory.
|
||||
embedder_config: Configuration for the embedder.
|
||||
storage: The storage backend for the memory.
|
||||
path: Path to the storage file, if any.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
def __init__(self, crew=None, embedder_config=None, storage=None, path=None):
|
||||
if hasattr(crew, "memory_config") and crew.memory_config is not None:
|
||||
self.memory_provider = crew.memory_config.get("provider")
|
||||
else:
|
||||
@@ -53,48 +36,23 @@ class EntityMemory(Memory):
|
||||
path=path,
|
||||
)
|
||||
)
|
||||
super().__init__(storage, memory_verbose=memory_verbose)
|
||||
super().__init__(storage)
|
||||
|
||||
def save(self, item: EntityMemoryItem) -> None: # type: ignore # BUG?: Signature of "save" incompatible with supertype "Memory"
|
||||
"""
|
||||
Saves an entity item into storage.
|
||||
|
||||
Args:
|
||||
item: The entity memory item to save.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error saving the entity to memory.
|
||||
"""
|
||||
try:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Saving entity", f"{item.name} ({item.type})")
|
||||
self._log_operation("Description", item.description)
|
||||
|
||||
if self.memory_provider == "mem0":
|
||||
data = f"""
|
||||
Remember details about the following entity:
|
||||
Name: {item.name}
|
||||
Type: {item.type}
|
||||
Entity Description: {item.description}
|
||||
"""
|
||||
else:
|
||||
data = f"{item.name}({item.type}): {item.description}"
|
||||
super().save(data, item.metadata)
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error saving entity", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "save entity", self.__class__.__name__)
|
||||
"""Saves an entity item into the SQLite storage."""
|
||||
if self.memory_provider == "mem0":
|
||||
data = f"""
|
||||
Remember details about the following entity:
|
||||
Name: {item.name}
|
||||
Type: {item.type}
|
||||
Entity Description: {item.description}
|
||||
"""
|
||||
else:
|
||||
data = f"{item.name}({item.type}): {item.description}"
|
||||
super().save(data, item.metadata)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
Reset the entity memory.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error resetting the memory.
|
||||
"""
|
||||
try:
|
||||
self.storage.reset()
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error resetting", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "reset", self.__class__.__name__)
|
||||
raise Exception(f"An error occurred while resetting the entity memory: {e}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from crewai.memory.long_term.long_term_memory_item import LongTermMemoryItem
|
||||
from crewai.memory.memory import Memory, MemoryOperationError
|
||||
from crewai.memory.memory import Memory
|
||||
from crewai.memory.storage.ltm_sqlite_storage import LTMSQLiteStorage
|
||||
|
||||
|
||||
@@ -12,90 +12,25 @@ class LongTermMemory(Memory):
|
||||
Inherits from the Memory class and utilizes an instance of a class that
|
||||
adheres to the Storage for data storage, specifically working with
|
||||
LongTermMemoryItem instances.
|
||||
|
||||
Attributes:
|
||||
storage: The storage backend for the memory.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
|
||||
def __init__(self, storage=None, path=None, memory_verbose=False):
|
||||
"""
|
||||
Initialize a LongTermMemory instance.
|
||||
|
||||
Args:
|
||||
storage: The storage backend for the memory.
|
||||
path: Path to the storage file, if any.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
def __init__(self, storage=None, path=None):
|
||||
if not storage:
|
||||
storage = LTMSQLiteStorage(db_path=path) if path else LTMSQLiteStorage()
|
||||
super().__init__(storage, memory_verbose=memory_verbose)
|
||||
super().__init__(storage)
|
||||
|
||||
def save(self, item: LongTermMemoryItem) -> None: # type: ignore # BUG?: Signature of "save" incompatible with supertype "Memory"
|
||||
"""
|
||||
Save a long-term memory item to storage.
|
||||
|
||||
Args:
|
||||
item: The long-term memory item to save.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error saving the item to memory.
|
||||
"""
|
||||
try:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Saving task", item.task)
|
||||
self._log_operation("Agent", item.agent)
|
||||
self._log_operation("Quality", str(item.metadata.get('quality')))
|
||||
|
||||
metadata = item.metadata
|
||||
metadata.update({"agent": item.agent, "expected_output": item.expected_output})
|
||||
self.storage.save( # type: ignore # BUG?: Unexpected keyword argument "task_description","score","datetime" for "save" of "Storage"
|
||||
task_description=item.task,
|
||||
score=metadata["quality"],
|
||||
metadata=metadata,
|
||||
datetime=item.datetime,
|
||||
)
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error saving task", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "save task", self.__class__.__name__)
|
||||
metadata = item.metadata
|
||||
metadata.update({"agent": item.agent, "expected_output": item.expected_output})
|
||||
self.storage.save( # type: ignore # BUG?: Unexpected keyword argument "task_description","score","datetime" for "save" of "Storage"
|
||||
task_description=item.task,
|
||||
score=metadata["quality"],
|
||||
metadata=metadata,
|
||||
datetime=item.datetime,
|
||||
)
|
||||
|
||||
def search(self, task: str, latest_n: int = 3) -> List[Dict[str, Any]]: # type: ignore # signature of "search" incompatible with supertype "Memory"
|
||||
"""
|
||||
Search for long-term memories related to a task.
|
||||
|
||||
Args:
|
||||
task: The task description to search for.
|
||||
latest_n: Maximum number of results to return.
|
||||
|
||||
Returns:
|
||||
A list of matching long-term memories.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error searching memory.
|
||||
"""
|
||||
try:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Searching for task", task)
|
||||
results = self.storage.load(task, latest_n) # type: ignore # BUG?: "Storage" has no attribute "load"
|
||||
if self.memory_verbose and results:
|
||||
self._log_operation("Found", f"{len(results)} results")
|
||||
return results
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error searching", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "search", self.__class__.__name__)
|
||||
return self.storage.load(task, latest_n) # type: ignore # BUG?: "Storage" has no attribute "load"
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
Reset the long-term memory.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error resetting the memory.
|
||||
"""
|
||||
try:
|
||||
self.storage.reset()
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error resetting", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "reset", self.__class__.__name__)
|
||||
self.storage.reset()
|
||||
|
||||
@@ -1,67 +1,15 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from crewai.memory.storage.rag_storage import RAGStorage
|
||||
from crewai.utilities.logger import Logger
|
||||
|
||||
|
||||
class MemoryOperationError(Exception):
|
||||
"""
|
||||
Exception raised for errors in memory operations.
|
||||
|
||||
Attributes:
|
||||
message: Explanation of the error
|
||||
operation: The operation that failed (e.g., "save", "search")
|
||||
memory_type: The type of memory where the error occurred
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, operation: str, memory_type: str):
|
||||
self.operation = operation
|
||||
self.memory_type = memory_type
|
||||
super().__init__(f"{memory_type} {operation} error: {message}")
|
||||
|
||||
|
||||
class Memory:
|
||||
"""
|
||||
Base class for memory, now supporting agent tags and generic metadata.
|
||||
|
||||
Attributes:
|
||||
storage: The storage backend for the memory.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
|
||||
def __init__(self, storage: RAGStorage, memory_verbose: bool = False):
|
||||
"""
|
||||
Initialize a Memory instance.
|
||||
|
||||
Args:
|
||||
storage: The storage backend for the memory.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
def __init__(self, storage: RAGStorage):
|
||||
self.storage = storage
|
||||
self.memory_verbose = memory_verbose
|
||||
self._logger = Logger(verbose=memory_verbose)
|
||||
|
||||
def _log_operation(self, operation: str, details: str, agent: Optional[str] = None, level: str = "info", color: str = "cyan") -> None:
|
||||
"""
|
||||
Log a memory operation if memory_verbose is enabled.
|
||||
|
||||
Args:
|
||||
operation: The type of operation (e.g., "Saving", "Searching").
|
||||
details: Details about the operation.
|
||||
agent: The agent performing the operation, if any.
|
||||
level: The log level.
|
||||
color: The color to use for the log message.
|
||||
"""
|
||||
if not self.memory_verbose:
|
||||
return
|
||||
|
||||
sanitized_details = str(details)
|
||||
if len(sanitized_details) > 100:
|
||||
sanitized_details = f"{sanitized_details[:100]}..."
|
||||
|
||||
memory_type = self.__class__.__name__
|
||||
agent_info = f" from agent '{agent}'" if agent else ""
|
||||
self._logger.log(level, f"{memory_type}: {operation}{agent_info}: {sanitized_details}", color=color)
|
||||
|
||||
def save(
|
||||
self,
|
||||
@@ -69,30 +17,11 @@ class Memory:
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
agent: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Save a value to memory.
|
||||
|
||||
Args:
|
||||
value: The value to save.
|
||||
metadata: Additional metadata to store with the value.
|
||||
agent: The agent saving the value, if any.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error saving the value to memory.
|
||||
"""
|
||||
metadata = metadata or {}
|
||||
if agent:
|
||||
metadata["agent"] = agent
|
||||
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Saving", str(value), agent)
|
||||
|
||||
try:
|
||||
self.storage.save(value, metadata)
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error saving", str(e), agent, level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "save", self.__class__.__name__)
|
||||
self.storage.save(value, metadata)
|
||||
|
||||
def search(
|
||||
self,
|
||||
@@ -100,33 +29,6 @@ class Memory:
|
||||
limit: int = 3,
|
||||
score_threshold: float = 0.35,
|
||||
) -> List[Any]:
|
||||
"""
|
||||
Search for values in memory.
|
||||
|
||||
Args:
|
||||
query: The search query.
|
||||
limit: Maximum number of results to return.
|
||||
score_threshold: Minimum similarity score for results.
|
||||
|
||||
Returns:
|
||||
A list of matching values.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error searching memory.
|
||||
"""
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Searching for", query)
|
||||
|
||||
try:
|
||||
results = self.storage.search(
|
||||
query=query, limit=limit, score_threshold=score_threshold
|
||||
)
|
||||
|
||||
if self.memory_verbose and results:
|
||||
self._log_operation("Found", f"{len(results)} results")
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error searching", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "search", self.__class__.__name__)
|
||||
return self.storage.search(
|
||||
query=query, limit=limit, score_threshold=score_threshold
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from crewai.memory.memory import Memory, MemoryOperationError
|
||||
from crewai.memory.memory import Memory
|
||||
from crewai.memory.short_term.short_term_memory_item import ShortTermMemoryItem
|
||||
from crewai.memory.storage.rag_storage import RAGStorage
|
||||
|
||||
@@ -12,24 +12,9 @@ class ShortTermMemory(Memory):
|
||||
Inherits from the Memory class and utilizes an instance of a class that
|
||||
adheres to the Storage for data storage, specifically working with
|
||||
MemoryItem instances.
|
||||
|
||||
Attributes:
|
||||
memory_provider: The memory provider to use, if any.
|
||||
storage: The storage backend for the memory.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
|
||||
def __init__(self, crew=None, embedder_config=None, storage=None, path=None, memory_verbose=False):
|
||||
"""
|
||||
Initialize a ShortTermMemory instance.
|
||||
|
||||
Args:
|
||||
crew: The crew to associate with this memory.
|
||||
embedder_config: Configuration for the embedder.
|
||||
storage: The storage backend for the memory.
|
||||
path: Path to the storage file, if any.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
def __init__(self, crew=None, embedder_config=None, storage=None, path=None):
|
||||
if hasattr(crew, "memory_config") and crew.memory_config is not None:
|
||||
self.memory_provider = crew.memory_config.get("provider")
|
||||
else:
|
||||
@@ -54,7 +39,7 @@ class ShortTermMemory(Memory):
|
||||
path=path,
|
||||
)
|
||||
)
|
||||
super().__init__(storage, memory_verbose=memory_verbose)
|
||||
super().__init__(storage)
|
||||
|
||||
def save(
|
||||
self,
|
||||
@@ -62,68 +47,26 @@ class ShortTermMemory(Memory):
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
agent: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Save a value to short-term memory.
|
||||
|
||||
Args:
|
||||
value: The value to save.
|
||||
metadata: Additional metadata to store with the value.
|
||||
agent: The agent saving the value, if any.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error saving to memory.
|
||||
"""
|
||||
try:
|
||||
item = ShortTermMemoryItem(data=value, metadata=metadata, agent=agent)
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Saving item", str(item.data), agent)
|
||||
|
||||
if self.memory_provider == "mem0":
|
||||
item.data = f"Remember the following insights from Agent run: {item.data}"
|
||||
item = ShortTermMemoryItem(data=value, metadata=metadata, agent=agent)
|
||||
if self.memory_provider == "mem0":
|
||||
item.data = f"Remember the following insights from Agent run: {item.data}"
|
||||
|
||||
super().save(value=item.data, metadata=item.metadata, agent=item.agent)
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error saving item", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "save", self.__class__.__name__)
|
||||
super().save(value=item.data, metadata=item.metadata, agent=item.agent)
|
||||
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 3,
|
||||
score_threshold: float = 0.35,
|
||||
) -> List[Any]:
|
||||
"""
|
||||
Search for values in short-term memory.
|
||||
|
||||
Args:
|
||||
query: The search query.
|
||||
limit: Maximum number of results to return.
|
||||
score_threshold: Minimum similarity score for results.
|
||||
|
||||
Returns:
|
||||
A list of matching values.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error searching memory.
|
||||
"""
|
||||
try:
|
||||
return super().search(query=query, limit=limit, score_threshold=score_threshold)
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error searching", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "search", self.__class__.__name__)
|
||||
):
|
||||
return self.storage.search(
|
||||
query=query, limit=limit, score_threshold=score_threshold
|
||||
) # type: ignore # BUG? The reference is to the parent class, but the parent class does not have this parameters
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
Reset the short-term memory.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error resetting the memory.
|
||||
"""
|
||||
try:
|
||||
self.storage.reset()
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error resetting", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "reset", self.__class__.__name__)
|
||||
raise Exception(
|
||||
f"An error occurred while resetting the short-term memory: {e}"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from crewai.memory.memory import Memory, MemoryOperationError
|
||||
from crewai.memory.memory import Memory
|
||||
|
||||
|
||||
class UserMemory(Memory):
|
||||
@@ -9,23 +9,9 @@ class UserMemory(Memory):
|
||||
Inherits from the Memory class and utilizes an instance of a class that
|
||||
adheres to the Storage for data storage, specifically working with
|
||||
MemoryItem instances.
|
||||
|
||||
Attributes:
|
||||
storage: The storage backend for the memory.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
"""
|
||||
|
||||
def __init__(self, crew=None, memory_verbose=False):
|
||||
"""
|
||||
Initialize a UserMemory instance.
|
||||
|
||||
Args:
|
||||
crew: The crew to associate with this memory.
|
||||
memory_verbose: Whether to log memory operations.
|
||||
|
||||
Raises:
|
||||
ImportError: If Mem0 is not installed.
|
||||
"""
|
||||
def __init__(self, crew=None):
|
||||
try:
|
||||
from crewai.memory.storage.mem0_storage import Mem0Storage
|
||||
except ImportError:
|
||||
@@ -33,72 +19,27 @@ class UserMemory(Memory):
|
||||
"Mem0 is not installed. Please install it with `pip install mem0ai`."
|
||||
)
|
||||
storage = Mem0Storage(type="user", crew=crew)
|
||||
super().__init__(storage, memory_verbose=memory_verbose)
|
||||
super().__init__(storage)
|
||||
|
||||
def save(
|
||||
self,
|
||||
value: Any,
|
||||
value,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
agent: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Save user memory.
|
||||
|
||||
Args:
|
||||
value: The value to save.
|
||||
metadata: Additional metadata to store with the value.
|
||||
agent: The agent saving the value, if any.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error saving to memory.
|
||||
"""
|
||||
try:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Saving user memory", str(value))
|
||||
|
||||
# TODO: Change this function since we want to take care of the case where we save memories for the usr
|
||||
data = f"Remember the details about the user: {value}"
|
||||
super().save(data, metadata)
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error saving user memory", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "save", self.__class__.__name__)
|
||||
# TODO: Change this function since we want to take care of the case where we save memories for the usr
|
||||
data = f"Remember the details about the user: {value}"
|
||||
super().save(data, metadata)
|
||||
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 3,
|
||||
score_threshold: float = 0.35,
|
||||
) -> List[Any]:
|
||||
"""
|
||||
Search for user memories.
|
||||
|
||||
Args:
|
||||
query: The search query.
|
||||
limit: Maximum number of results to return.
|
||||
score_threshold: Minimum similarity score for results.
|
||||
|
||||
Returns:
|
||||
A list of matching user memories.
|
||||
|
||||
Raises:
|
||||
MemoryOperationError: If there's an error searching memory.
|
||||
"""
|
||||
try:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Searching user memory", query)
|
||||
|
||||
results = self.storage.search(
|
||||
query=query,
|
||||
limit=limit,
|
||||
score_threshold=score_threshold,
|
||||
)
|
||||
|
||||
if self.memory_verbose and results:
|
||||
self._log_operation("Found", f"{len(results)} results")
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
if self.memory_verbose:
|
||||
self._log_operation("Error searching user memory", str(e), level="error", color="red")
|
||||
raise MemoryOperationError(str(e), "search", self.__class__.__name__)
|
||||
):
|
||||
results = self.storage.search(
|
||||
query=query,
|
||||
limit=limit,
|
||||
score_threshold=score_threshold,
|
||||
)
|
||||
return results
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from .base_tool import BaseTool, tool
|
||||
from .tool_with_instruction import ToolWithInstruction
|
||||
|
||||
110
src/crewai/tools/tool_with_instruction.py
Normal file
110
src/crewai/tools/tool_with_instruction.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from typing import Any, List, Optional, Dict, Callable, Union, ClassVar
|
||||
|
||||
from pydantic import Field, model_validator, field_validator, ConfigDict
|
||||
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
|
||||
|
||||
class ToolWithInstruction(BaseTool):
|
||||
"""A wrapper for tools that adds specific usage instructions.
|
||||
|
||||
This allows users to provide specific instructions on when and how to use a tool,
|
||||
without having to include these instructions in the agent's backstory.
|
||||
|
||||
Attributes:
|
||||
tool: The tool to wrap
|
||||
instructions: Specific instructions about when and how to use this tool
|
||||
name: Name of the tool (inherited from the wrapped tool)
|
||||
description: Description of the tool (inherited from the wrapped tool with instructions)
|
||||
"""
|
||||
|
||||
MAX_INSTRUCTION_LENGTH: ClassVar[int] = 2000
|
||||
|
||||
name: str = Field(default="", description="Name of the tool")
|
||||
description: str = Field(default="", description="Description of the tool")
|
||||
tool: BaseTool = Field(description="The tool to wrap")
|
||||
instructions: str = Field(description="Instructions about when and how to use this tool")
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
@field_validator("instructions")
|
||||
@classmethod
|
||||
def validate_instructions(cls, value: str) -> str:
|
||||
"""Validate that instructions are not empty and not too long.
|
||||
|
||||
Args:
|
||||
value: The instructions string to validate
|
||||
|
||||
Returns:
|
||||
str: The validated and sanitized instructions
|
||||
|
||||
Raises:
|
||||
ValueError: If instructions are empty or exceed maximum length
|
||||
"""
|
||||
if not value or not value.strip():
|
||||
raise ValueError("Instructions cannot be empty")
|
||||
|
||||
if len(value) > cls.MAX_INSTRUCTION_LENGTH:
|
||||
raise ValueError(
|
||||
f"Instructions exceed maximum length of {cls.MAX_INSTRUCTION_LENGTH} characters"
|
||||
)
|
||||
|
||||
return value.strip()
|
||||
|
||||
@model_validator(mode="after")
|
||||
def set_tool_attributes(self) -> "ToolWithInstruction":
|
||||
"""Sets name, description, and args_schema from the wrapped tool.
|
||||
|
||||
Returns:
|
||||
ToolWithInstruction: The validated instance with updated attributes.
|
||||
"""
|
||||
self.name = self.tool.name
|
||||
self.description = f"{self.tool.description}\nInstructions: {self.instructions}"
|
||||
self.args_schema = self.tool.args_schema
|
||||
return self
|
||||
|
||||
def update_instructions(self, new_instructions: str) -> None:
|
||||
"""Updates the tool's usage instructions.
|
||||
|
||||
Args:
|
||||
new_instructions (str): New instructions for tool usage.
|
||||
|
||||
Raises:
|
||||
ValueError: If new instructions are empty or exceed maximum length
|
||||
"""
|
||||
if not new_instructions or not new_instructions.strip():
|
||||
raise ValueError("Instructions cannot be empty")
|
||||
|
||||
if len(new_instructions) > self.MAX_INSTRUCTION_LENGTH:
|
||||
raise ValueError(
|
||||
f"Instructions exceed maximum length of {self.MAX_INSTRUCTION_LENGTH} characters"
|
||||
)
|
||||
|
||||
self.instructions = new_instructions.strip()
|
||||
|
||||
self.description = f"{self.tool.description}\nInstructions: {self.instructions}"
|
||||
|
||||
def _run(self, *args: Any, **kwargs: Any) -> Any:
|
||||
"""Run the wrapped tool.
|
||||
|
||||
Args:
|
||||
*args: Positional arguments to pass to the wrapped tool
|
||||
**kwargs: Keyword arguments to pass to the wrapped tool
|
||||
|
||||
Returns:
|
||||
Any: The result from the wrapped tool's _run method
|
||||
"""
|
||||
return self.tool._run(*args, **kwargs)
|
||||
|
||||
def to_structured_tool(self) -> CrewStructuredTool:
|
||||
"""Convert this tool to a CrewStructuredTool instance.
|
||||
|
||||
Returns:
|
||||
CrewStructuredTool: A structured tool with instructions included in the description
|
||||
"""
|
||||
structured_tool = self.tool.to_structured_tool()
|
||||
|
||||
structured_tool.description = f"{structured_tool.description}\nInstructions: {self.instructions}"
|
||||
|
||||
return structured_tool
|
||||
@@ -1,147 +0,0 @@
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from crewai.agent import Agent
|
||||
from crewai.crew import Crew
|
||||
from crewai.memory.memory import Memory, MemoryOperationError
|
||||
from crewai.memory.short_term.short_term_memory import ShortTermMemory
|
||||
from crewai.memory.short_term.short_term_memory_item import ShortTermMemoryItem
|
||||
from crewai.task import Task
|
||||
from crewai.utilities.logger import Logger
|
||||
|
||||
|
||||
def test_memory_verbose_flag_in_crew():
|
||||
"""Test that memory_verbose flag is correctly set in Crew"""
|
||||
agent = Agent(
|
||||
role="Researcher",
|
||||
goal="Research goal",
|
||||
backstory="Researcher backstory",
|
||||
)
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
agent=agent,
|
||||
)
|
||||
crew = Crew(agents=[agent], tasks=[task], memory=True, memory_verbose=True)
|
||||
assert crew.memory_verbose is True
|
||||
|
||||
|
||||
def test_memory_verbose_logging_in_memory():
|
||||
"""Test that memory operations are logged when memory_verbose is enabled"""
|
||||
storage = MagicMock()
|
||||
|
||||
mock_logger = MagicMock(spec=Logger)
|
||||
|
||||
memory = Memory(storage=storage, memory_verbose=True)
|
||||
|
||||
memory._logger = mock_logger
|
||||
|
||||
memory.save("test value", {"test": "metadata"}, "test_agent")
|
||||
mock_logger.log.assert_called_once()
|
||||
args = mock_logger.log.call_args[0]
|
||||
assert args[0] == "info"
|
||||
assert "Saving" in args[1]
|
||||
|
||||
mock_logger.log.reset_mock()
|
||||
memory.search("test query")
|
||||
assert mock_logger.log.call_count == 2
|
||||
first_call_args = mock_logger.log.call_args_list[0][0]
|
||||
assert first_call_args[0] == "info"
|
||||
assert "Searching" in first_call_args[1]
|
||||
second_call_args = mock_logger.log.call_args_list[1][0]
|
||||
assert "Found" in second_call_args[1]
|
||||
|
||||
|
||||
def test_no_logging_when_memory_verbose_disabled():
|
||||
"""Test that no logging occurs when memory_verbose is disabled"""
|
||||
storage = MagicMock()
|
||||
|
||||
mock_logger = MagicMock(spec=Logger)
|
||||
|
||||
memory = Memory(storage=storage, memory_verbose=False)
|
||||
|
||||
memory._logger = mock_logger
|
||||
|
||||
memory.save("test value", {"test": "metadata"}, "test_agent")
|
||||
mock_logger.log.assert_not_called()
|
||||
|
||||
memory.search("test query")
|
||||
mock_logger.log.assert_not_called()
|
||||
|
||||
|
||||
def test_memory_verbose_in_short_term_memory():
|
||||
"""Test that memory_verbose flag is correctly passed to ShortTermMemory"""
|
||||
with patch('crewai.memory.short_term.short_term_memory.RAGStorage') as mock_storage_class:
|
||||
mock_storage = MagicMock()
|
||||
mock_storage_class.return_value = mock_storage
|
||||
|
||||
memory = ShortTermMemory(memory_verbose=True)
|
||||
assert memory.memory_verbose is True
|
||||
|
||||
mock_logger = MagicMock()
|
||||
memory._logger = mock_logger
|
||||
|
||||
memory.save("test value", {"test": "metadata"}, "test_agent")
|
||||
assert mock_logger.log.call_count >= 1
|
||||
|
||||
|
||||
def test_memory_verbose_passed_from_crew_to_memory():
|
||||
"""Test that memory_verbose flag is correctly passed from Crew to memory instances"""
|
||||
with patch('crewai.crew.LongTermMemory') as mock_ltm, \
|
||||
patch('crewai.crew.ShortTermMemory') as mock_stm, \
|
||||
patch('crewai.crew.EntityMemory') as mock_em, \
|
||||
patch('crewai.crew.UserMemory') as mock_um:
|
||||
|
||||
mock_ltm_instance = MagicMock()
|
||||
mock_stm_instance = MagicMock()
|
||||
mock_em_instance = MagicMock()
|
||||
mock_um_instance = MagicMock()
|
||||
|
||||
mock_ltm.return_value = mock_ltm_instance
|
||||
mock_stm.return_value = mock_stm_instance
|
||||
mock_em.return_value = mock_em_instance
|
||||
mock_um.return_value = mock_um_instance
|
||||
|
||||
agent = Agent(
|
||||
role="Researcher",
|
||||
goal="Research goal",
|
||||
backstory="Researcher backstory",
|
||||
)
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
agent=agent,
|
||||
)
|
||||
|
||||
crew = Crew(agents=[agent], tasks=[task], memory=True, memory_verbose=True, memory_config={})
|
||||
|
||||
mock_ltm.assert_called_once_with(memory_verbose=True)
|
||||
mock_stm.assert_called_with(crew=crew, embedder_config=None, memory_verbose=True)
|
||||
mock_em.assert_called_with(crew=crew, embedder_config=None, memory_verbose=True)
|
||||
mock_um.assert_called_with(crew=crew, memory_verbose=True)
|
||||
|
||||
|
||||
def test_memory_verbose_error_handling():
|
||||
"""Test that memory operations errors are properly handled when memory_verbose is enabled"""
|
||||
storage = MagicMock()
|
||||
storage.save.side_effect = Exception("Test error")
|
||||
storage.search.side_effect = Exception("Test error")
|
||||
|
||||
mock_logger = MagicMock()
|
||||
|
||||
with patch('crewai.memory.memory.Logger', return_value=mock_logger):
|
||||
memory = Memory(storage=storage, memory_verbose=True)
|
||||
|
||||
with pytest.raises(MemoryOperationError) as exc_info:
|
||||
memory.save("test value", {"test": "metadata"}, "test_agent")
|
||||
|
||||
assert "save" in str(exc_info.value)
|
||||
assert "Test error" in str(exc_info.value)
|
||||
assert "Memory" in str(exc_info.value)
|
||||
|
||||
with pytest.raises(MemoryOperationError) as exc_info:
|
||||
memory.search("test query")
|
||||
|
||||
assert "search" in str(exc_info.value)
|
||||
assert "Test error" in str(exc_info.value)
|
||||
110
tests/tools/test_tool_with_instruction.py
Normal file
110
tests/tools/test_tool_with_instruction.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from crewai.tools.base_tool import BaseTool, Tool
|
||||
from crewai.tools.tool_with_instruction import ToolWithInstruction
|
||||
|
||||
|
||||
class MockTool(BaseTool):
|
||||
"""Mock tool for testing."""
|
||||
name: str = "mock_tool"
|
||||
description: str = "A mock tool for testing"
|
||||
|
||||
def _run(self, *args: Any, **kwargs: Any) -> str:
|
||||
return "mock result"
|
||||
|
||||
|
||||
class TestToolWithInstruction:
|
||||
"""Test suite for ToolWithInstruction."""
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test tool initialization with instructions."""
|
||||
tool = MockTool()
|
||||
instructions = "Only use this tool for XYZ"
|
||||
|
||||
wrapped_tool = ToolWithInstruction(tool=tool, instructions=instructions)
|
||||
|
||||
assert wrapped_tool.name == tool.name
|
||||
assert "Instructions: Only use this tool for XYZ" in wrapped_tool.description
|
||||
assert wrapped_tool.args_schema == tool.args_schema
|
||||
|
||||
def test_run_method(self):
|
||||
"""Test that the run method delegates to the original tool."""
|
||||
tool = MockTool()
|
||||
instructions = "Only use this tool for XYZ"
|
||||
|
||||
wrapped_tool = ToolWithInstruction(tool=tool, instructions=instructions)
|
||||
result = wrapped_tool.run()
|
||||
|
||||
assert result == "mock result"
|
||||
|
||||
def test_to_structured_tool(self):
|
||||
"""Test that to_structured_tool includes instructions."""
|
||||
tool = MockTool()
|
||||
instructions = "Only use this tool for XYZ"
|
||||
|
||||
wrapped_tool = ToolWithInstruction(tool=tool, instructions=instructions)
|
||||
structured_tool = wrapped_tool.to_structured_tool()
|
||||
|
||||
assert "Instructions: Only use this tool for XYZ" in structured_tool.description
|
||||
|
||||
def test_with_function_tool(self):
|
||||
"""Test tool wrapping with a function tool."""
|
||||
def sample_func():
|
||||
return "sample result"
|
||||
|
||||
tool = Tool(
|
||||
name="sample_tool",
|
||||
description="A sample tool",
|
||||
func=sample_func
|
||||
)
|
||||
|
||||
instructions = "Only use this tool for XYZ"
|
||||
wrapped_tool = ToolWithInstruction(tool=tool, instructions=instructions)
|
||||
|
||||
assert wrapped_tool.name == tool.name
|
||||
assert "Instructions: Only use this tool for XYZ" in wrapped_tool.description
|
||||
|
||||
def test_empty_instructions(self):
|
||||
"""Test that empty instructions raise ValueError."""
|
||||
tool = MockTool()
|
||||
|
||||
with pytest.raises(ValueError, match="Instructions cannot be empty"):
|
||||
ToolWithInstruction(tool=tool, instructions="")
|
||||
|
||||
with pytest.raises(ValueError, match="Instructions cannot be empty"):
|
||||
ToolWithInstruction(tool=tool, instructions=" ")
|
||||
|
||||
def test_too_long_instructions(self):
|
||||
"""Test that instructions exceeding maximum length raise ValueError."""
|
||||
tool = MockTool()
|
||||
long_instructions = "x" * (ToolWithInstruction.MAX_INSTRUCTION_LENGTH + 1)
|
||||
|
||||
with pytest.raises(ValueError, match="Instructions exceed maximum length"):
|
||||
ToolWithInstruction(tool=tool, instructions=long_instructions)
|
||||
|
||||
def test_update_instructions(self):
|
||||
"""Test updating instructions dynamically."""
|
||||
tool = MockTool()
|
||||
initial_instructions = "Initial instructions"
|
||||
new_instructions = "Updated instructions"
|
||||
|
||||
wrapped_tool = ToolWithInstruction(tool=tool, instructions=initial_instructions)
|
||||
assert "Instructions: Initial instructions" in wrapped_tool.description
|
||||
|
||||
wrapped_tool.update_instructions(new_instructions)
|
||||
assert "Instructions: Updated instructions" in wrapped_tool.description
|
||||
assert wrapped_tool.instructions == new_instructions
|
||||
|
||||
def test_update_instructions_validation(self):
|
||||
"""Test validation when updating instructions."""
|
||||
tool = MockTool()
|
||||
wrapped_tool = ToolWithInstruction(tool=tool, instructions="Valid instructions")
|
||||
|
||||
with pytest.raises(ValueError, match="Instructions cannot be empty"):
|
||||
wrapped_tool.update_instructions("")
|
||||
|
||||
long_instructions = "x" * (ToolWithInstruction.MAX_INSTRUCTION_LENGTH + 1)
|
||||
with pytest.raises(ValueError, match="Instructions exceed maximum length"):
|
||||
wrapped_tool.update_instructions(long_instructions)
|
||||
Reference in New Issue
Block a user