mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-24 16:28:29 +00:00
Compare commits
1 Commits
gl/chore/a
...
lg-memory-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df4754301a |
286
.cursorrules
286
.cursorrules
@@ -260,7 +260,7 @@ def handle_success(self):
|
||||
# Handle success case
|
||||
pass
|
||||
|
||||
@listen("failure_path")
|
||||
@listen("failure_path")
|
||||
def handle_failure(self):
|
||||
# Handle failure case
|
||||
pass
|
||||
@@ -288,7 +288,7 @@ class SelectiveFlow(Flow):
|
||||
def critical_step(self):
|
||||
# Only this method's state is persisted
|
||||
self.state["important_data"] = "value"
|
||||
|
||||
|
||||
@start()
|
||||
def temporary_step(self):
|
||||
# This method's state is not persisted
|
||||
@@ -322,20 +322,20 @@ flow.plot("workflow_diagram") # Generates HTML visualization
|
||||
class CyclicFlow(Flow):
|
||||
max_iterations = 5
|
||||
current_iteration = 0
|
||||
|
||||
|
||||
@start("loop")
|
||||
def process_iteration(self):
|
||||
if self.current_iteration >= self.max_iterations:
|
||||
return
|
||||
# Process current iteration
|
||||
self.current_iteration += 1
|
||||
|
||||
|
||||
@router(process_iteration)
|
||||
def check_continue(self):
|
||||
if self.current_iteration < self.max_iterations:
|
||||
return "loop" # Continue cycling
|
||||
return "complete"
|
||||
|
||||
|
||||
@listen("complete")
|
||||
def finalize(self):
|
||||
# Final processing
|
||||
@@ -369,7 +369,7 @@ def risky_operation(self):
|
||||
self.state["success"] = False
|
||||
return None
|
||||
|
||||
@listen(risky_operation)
|
||||
@listen(risky_operation)
|
||||
def handle_result(self, result):
|
||||
if self.state.get("success", False):
|
||||
# Handle success case
|
||||
@@ -390,7 +390,7 @@ class CrewOrchestrationFlow(Flow[WorkflowState]):
|
||||
result = research_crew.crew().kickoff(inputs={"topic": self.state.research_topic})
|
||||
self.state.research_results = result.raw
|
||||
return result
|
||||
|
||||
|
||||
@listen(research_phase)
|
||||
def analysis_phase(self, research_results):
|
||||
analysis_crew = AnalysisCrew()
|
||||
@@ -400,13 +400,13 @@ class CrewOrchestrationFlow(Flow[WorkflowState]):
|
||||
})
|
||||
self.state.analysis_results = result.raw
|
||||
return result
|
||||
|
||||
|
||||
@router(analysis_phase)
|
||||
def decide_next_action(self):
|
||||
if self.state.analysis_results.confidence > 0.7:
|
||||
return "generate_report"
|
||||
return "additional_research"
|
||||
|
||||
|
||||
@listen("generate_report")
|
||||
def final_report(self):
|
||||
reporting_crew = ReportingCrew()
|
||||
@@ -439,7 +439,7 @@ class CrewOrchestrationFlow(Flow[WorkflowState]):
|
||||
## CrewAI Version Compatibility:
|
||||
- Stay updated with CrewAI releases for new features and bug fixes
|
||||
- Test crew functionality when upgrading CrewAI versions
|
||||
- Use version constraints in pyproject.toml (e.g., "crewai[tools]>=0.140.0,<1.0.0")
|
||||
- Use version constraints in pyproject.toml (e.g., "crewai[tools]>=0.134.0,<1.0.0")
|
||||
- Monitor deprecation warnings for future compatibility
|
||||
|
||||
## Code Examples and Implementation Patterns
|
||||
@@ -464,22 +464,22 @@ class ResearchOutput(BaseModel):
|
||||
@CrewBase
|
||||
class ResearchCrew():
|
||||
"""Advanced research crew with structured outputs and validation"""
|
||||
|
||||
|
||||
agents: List[BaseAgent]
|
||||
tasks: List[Task]
|
||||
|
||||
|
||||
@before_kickoff
|
||||
def setup_environment(self):
|
||||
"""Initialize environment before crew execution"""
|
||||
print("🚀 Setting up research environment...")
|
||||
# Validate API keys, create directories, etc.
|
||||
|
||||
|
||||
@after_kickoff
|
||||
def cleanup_and_report(self, output):
|
||||
"""Handle post-execution tasks"""
|
||||
print(f"✅ Research completed. Generated {len(output.tasks_output)} task outputs")
|
||||
print(f"📊 Token usage: {output.token_usage}")
|
||||
|
||||
|
||||
@agent
|
||||
def researcher(self) -> Agent:
|
||||
return Agent(
|
||||
@@ -490,7 +490,7 @@ class ResearchCrew():
|
||||
max_iter=15,
|
||||
max_execution_time=1800
|
||||
)
|
||||
|
||||
|
||||
@agent
|
||||
def analyst(self) -> Agent:
|
||||
return Agent(
|
||||
@@ -499,7 +499,7 @@ class ResearchCrew():
|
||||
verbose=True,
|
||||
memory=True
|
||||
)
|
||||
|
||||
|
||||
@task
|
||||
def research_task(self) -> Task:
|
||||
return Task(
|
||||
@@ -507,7 +507,7 @@ class ResearchCrew():
|
||||
agent=self.researcher(),
|
||||
output_pydantic=ResearchOutput
|
||||
)
|
||||
|
||||
|
||||
@task
|
||||
def validation_task(self) -> Task:
|
||||
return Task(
|
||||
@@ -517,7 +517,7 @@ class ResearchCrew():
|
||||
guardrail=self.validate_research_quality,
|
||||
max_retries=3
|
||||
)
|
||||
|
||||
|
||||
def validate_research_quality(self, output) -> tuple[bool, str]:
|
||||
"""Custom guardrail to ensure research quality"""
|
||||
content = output.raw
|
||||
@@ -526,7 +526,7 @@ class ResearchCrew():
|
||||
if not any(keyword in content.lower() for keyword in ['conclusion', 'finding', 'result']):
|
||||
return False, "Missing key analytical elements."
|
||||
return True, content
|
||||
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
return Crew(
|
||||
@@ -557,13 +557,13 @@ class RobustSearchTool(BaseTool):
|
||||
name: str = "robust_search"
|
||||
description: str = "Perform web search with retry logic and error handling"
|
||||
args_schema: Type[BaseModel] = SearchInput
|
||||
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.api_key = api_key or os.getenv("SEARCH_API_KEY")
|
||||
self.rate_limit_delay = 1.0
|
||||
self.last_request_time = 0
|
||||
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(3),
|
||||
wait=wait_exponential(multiplier=1, min=4, max=10)
|
||||
@@ -575,43 +575,43 @@ class RobustSearchTool(BaseTool):
|
||||
time_since_last = time.time() - self.last_request_time
|
||||
if time_since_last < self.rate_limit_delay:
|
||||
time.sleep(self.rate_limit_delay - time_since_last)
|
||||
|
||||
|
||||
# Input validation
|
||||
if not query or len(query.strip()) == 0:
|
||||
return "Error: Empty search query provided"
|
||||
|
||||
|
||||
if len(query) > 500:
|
||||
return "Error: Search query too long (max 500 characters)"
|
||||
|
||||
|
||||
# Perform search
|
||||
results = self._perform_search(query, max_results, timeout)
|
||||
self.last_request_time = time.time()
|
||||
|
||||
|
||||
return self._format_results(results)
|
||||
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return f"Search timed out after {timeout} seconds"
|
||||
except requests.exceptions.RequestException as e:
|
||||
return f"Search failed due to network error: {str(e)}"
|
||||
except Exception as e:
|
||||
return f"Unexpected error during search: {str(e)}"
|
||||
|
||||
|
||||
def _perform_search(self, query: str, max_results: int, timeout: int) -> List[dict]:
|
||||
"""Implement actual search logic here"""
|
||||
# Your search API implementation
|
||||
pass
|
||||
|
||||
|
||||
def _format_results(self, results: List[dict]) -> str:
|
||||
"""Format search results for LLM consumption"""
|
||||
if not results:
|
||||
return "No results found for the given query."
|
||||
|
||||
|
||||
formatted = "Search Results:\n\n"
|
||||
for i, result in enumerate(results[:10], 1):
|
||||
formatted += f"{i}. {result.get('title', 'No title')}\n"
|
||||
formatted += f" URL: {result.get('url', 'No URL')}\n"
|
||||
formatted += f" Summary: {result.get('snippet', 'No summary')}\n\n"
|
||||
|
||||
|
||||
return formatted
|
||||
```
|
||||
|
||||
@@ -623,20 +623,20 @@ from crewai.memory.storage.mem0_storage import Mem0Storage
|
||||
|
||||
class AdvancedMemoryManager:
|
||||
"""Enhanced memory management for CrewAI applications"""
|
||||
|
||||
|
||||
def __init__(self, crew, config: dict = None):
|
||||
self.crew = crew
|
||||
self.config = config or {}
|
||||
self.setup_memory_systems()
|
||||
|
||||
|
||||
def setup_memory_systems(self):
|
||||
"""Configure multiple memory systems"""
|
||||
# Short-term memory for current session
|
||||
self.short_term = ShortTermMemory()
|
||||
|
||||
|
||||
# Long-term memory for cross-session persistence
|
||||
self.long_term = LongTermMemory()
|
||||
|
||||
|
||||
# External memory with Mem0 (if configured)
|
||||
if self.config.get('use_external_memory'):
|
||||
self.external = ExternalMemory.create_storage(
|
||||
@@ -649,8 +649,8 @@ class AdvancedMemoryManager:
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def save_with_context(self, content: str, memory_type: str = "short_term",
|
||||
|
||||
def save_with_context(self, content: str, memory_type: str = "short_term",
|
||||
metadata: dict = None, agent: str = None):
|
||||
"""Save content with enhanced metadata"""
|
||||
enhanced_metadata = {
|
||||
@@ -659,14 +659,14 @@ class AdvancedMemoryManager:
|
||||
"crew_type": self.crew.__class__.__name__,
|
||||
**(metadata or {})
|
||||
}
|
||||
|
||||
|
||||
if memory_type == "short_term":
|
||||
self.short_term.save(content, enhanced_metadata, agent)
|
||||
elif memory_type == "long_term":
|
||||
self.long_term.save(content, enhanced_metadata, agent)
|
||||
elif memory_type == "external" and hasattr(self, 'external'):
|
||||
self.external.save(content, enhanced_metadata, agent)
|
||||
|
||||
|
||||
def search_across_memories(self, query: str, limit: int = 5) -> dict:
|
||||
"""Search across all memory systems"""
|
||||
results = {
|
||||
@@ -674,23 +674,23 @@ class AdvancedMemoryManager:
|
||||
"long_term": [],
|
||||
"external": []
|
||||
}
|
||||
|
||||
|
||||
# Search short-term memory
|
||||
results["short_term"] = self.short_term.search(query, limit=limit)
|
||||
|
||||
|
||||
# Search long-term memory
|
||||
results["long_term"] = self.long_term.search(query, limit=limit)
|
||||
|
||||
|
||||
# Search external memory (if available)
|
||||
if hasattr(self, 'external'):
|
||||
results["external"] = self.external.search(query, limit=limit)
|
||||
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def cleanup_old_memories(self, days_threshold: int = 30):
|
||||
"""Clean up old memories based on age"""
|
||||
cutoff_time = time.time() - (days_threshold * 24 * 60 * 60)
|
||||
|
||||
|
||||
# Implement cleanup logic based on timestamps in metadata
|
||||
# This would vary based on your specific storage implementation
|
||||
pass
|
||||
@@ -719,12 +719,12 @@ class TaskMetrics:
|
||||
|
||||
class CrewMonitor:
|
||||
"""Comprehensive monitoring for CrewAI applications"""
|
||||
|
||||
|
||||
def __init__(self, crew_name: str, log_level: str = "INFO"):
|
||||
self.crew_name = crew_name
|
||||
self.metrics: List[TaskMetrics] = []
|
||||
self.session_start = time.time()
|
||||
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, log_level),
|
||||
@@ -735,7 +735,7 @@ class CrewMonitor:
|
||||
]
|
||||
)
|
||||
self.logger = logging.getLogger(f"CrewAI.{crew_name}")
|
||||
|
||||
|
||||
def start_task_monitoring(self, task_name: str, agent_name: str) -> dict:
|
||||
"""Start monitoring a task execution"""
|
||||
context = {
|
||||
@@ -743,16 +743,16 @@ class CrewMonitor:
|
||||
"agent_name": agent_name,
|
||||
"start_time": time.time()
|
||||
}
|
||||
|
||||
|
||||
self.logger.info(f"Task started: {task_name} by {agent_name}")
|
||||
return context
|
||||
|
||||
def end_task_monitoring(self, context: dict, success: bool = True,
|
||||
|
||||
def end_task_monitoring(self, context: dict, success: bool = True,
|
||||
tokens_used: int = 0, error: str = None):
|
||||
"""End monitoring and record metrics"""
|
||||
end_time = time.time()
|
||||
duration = end_time - context["start_time"]
|
||||
|
||||
|
||||
# Get memory usage (if psutil is available)
|
||||
memory_usage = None
|
||||
try:
|
||||
@@ -761,7 +761,7 @@ class CrewMonitor:
|
||||
memory_usage = process.memory_info().rss / 1024 / 1024 # MB
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
metrics = TaskMetrics(
|
||||
task_name=context["task_name"],
|
||||
agent_name=context["agent_name"],
|
||||
@@ -773,29 +773,29 @@ class CrewMonitor:
|
||||
error_message=error,
|
||||
memory_usage_mb=memory_usage
|
||||
)
|
||||
|
||||
|
||||
self.metrics.append(metrics)
|
||||
|
||||
|
||||
# Log the completion
|
||||
status = "SUCCESS" if success else "FAILED"
|
||||
self.logger.info(f"Task {status}: {context['task_name']} "
|
||||
f"(Duration: {duration:.2f}s, Tokens: {tokens_used})")
|
||||
|
||||
|
||||
if error:
|
||||
self.logger.error(f"Task error: {error}")
|
||||
|
||||
|
||||
def get_performance_summary(self) -> Dict[str, Any]:
|
||||
"""Generate comprehensive performance summary"""
|
||||
if not self.metrics:
|
||||
return {"message": "No metrics recorded yet"}
|
||||
|
||||
|
||||
successful_tasks = [m for m in self.metrics if m.success]
|
||||
failed_tasks = [m for m in self.metrics if not m.success]
|
||||
|
||||
|
||||
total_duration = sum(m.duration for m in self.metrics)
|
||||
total_tokens = sum(m.tokens_used for m in self.metrics)
|
||||
avg_duration = total_duration / len(self.metrics)
|
||||
|
||||
|
||||
return {
|
||||
"crew_name": self.crew_name,
|
||||
"session_duration": time.time() - self.session_start,
|
||||
@@ -811,7 +811,7 @@ class CrewMonitor:
|
||||
"most_token_intensive": max(self.metrics, key=lambda x: x.tokens_used).task_name if self.metrics else None,
|
||||
"common_errors": self._get_common_errors()
|
||||
}
|
||||
|
||||
|
||||
def _get_common_errors(self) -> Dict[str, int]:
|
||||
"""Get frequency of common errors"""
|
||||
error_counts = {}
|
||||
@@ -819,20 +819,20 @@ class CrewMonitor:
|
||||
if metric.error_message:
|
||||
error_counts[metric.error_message] = error_counts.get(metric.error_message, 0) + 1
|
||||
return dict(sorted(error_counts.items(), key=lambda x: x[1], reverse=True))
|
||||
|
||||
|
||||
def export_metrics(self, filename: str = None) -> str:
|
||||
"""Export metrics to JSON file"""
|
||||
if not filename:
|
||||
filename = f"crew_metrics_{self.crew_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||
|
||||
|
||||
export_data = {
|
||||
"summary": self.get_performance_summary(),
|
||||
"detailed_metrics": [asdict(m) for m in self.metrics]
|
||||
}
|
||||
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(export_data, f, indent=2, default=str)
|
||||
|
||||
|
||||
self.logger.info(f"Metrics exported to {filename}")
|
||||
return filename
|
||||
|
||||
@@ -847,10 +847,10 @@ def monitored_research_task(self) -> Task:
|
||||
if context:
|
||||
tokens = getattr(task_output, 'token_usage', {}).get('total', 0)
|
||||
monitor.end_task_monitoring(context, success=True, tokens_used=tokens)
|
||||
|
||||
|
||||
# Start monitoring would be called before task execution
|
||||
# This is a simplified example - in practice you'd integrate this into the task execution flow
|
||||
|
||||
|
||||
return Task(
|
||||
config=self.tasks_config['research_task'],
|
||||
agent=self.researcher(),
|
||||
@@ -872,7 +872,7 @@ class ErrorSeverity(Enum):
|
||||
|
||||
class CrewError(Exception):
|
||||
"""Base exception for CrewAI applications"""
|
||||
def __init__(self, message: str, severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
||||
def __init__(self, message: str, severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
||||
context: dict = None):
|
||||
super().__init__(message)
|
||||
self.severity = severity
|
||||
@@ -893,19 +893,19 @@ class ConfigurationError(CrewError):
|
||||
|
||||
class ErrorHandler:
|
||||
"""Centralized error handling for CrewAI applications"""
|
||||
|
||||
|
||||
def __init__(self, crew_name: str):
|
||||
self.crew_name = crew_name
|
||||
self.error_log: List[CrewError] = []
|
||||
self.recovery_strategies: Dict[type, Callable] = {}
|
||||
|
||||
|
||||
def register_recovery_strategy(self, error_type: type, strategy: Callable):
|
||||
"""Register a recovery strategy for specific error types"""
|
||||
self.recovery_strategies[error_type] = strategy
|
||||
|
||||
|
||||
def handle_error(self, error: Exception, context: dict = None) -> Any:
|
||||
"""Handle errors with appropriate recovery strategies"""
|
||||
|
||||
|
||||
# Convert to CrewError if needed
|
||||
if not isinstance(error, CrewError):
|
||||
crew_error = CrewError(
|
||||
@@ -915,11 +915,11 @@ class ErrorHandler:
|
||||
)
|
||||
else:
|
||||
crew_error = error
|
||||
|
||||
|
||||
# Log the error
|
||||
self.error_log.append(crew_error)
|
||||
self._log_error(crew_error)
|
||||
|
||||
|
||||
# Apply recovery strategy if available
|
||||
error_type = type(error)
|
||||
if error_type in self.recovery_strategies:
|
||||
@@ -931,21 +931,21 @@ class ErrorHandler:
|
||||
ErrorSeverity.HIGH,
|
||||
{"original_error": str(error), "recovery_error": str(recovery_error)}
|
||||
))
|
||||
|
||||
|
||||
# If critical, re-raise
|
||||
if crew_error.severity == ErrorSeverity.CRITICAL:
|
||||
raise crew_error
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _log_error(self, error: CrewError):
|
||||
"""Log error with appropriate level based on severity"""
|
||||
logger = logging.getLogger(f"CrewAI.{self.crew_name}.ErrorHandler")
|
||||
|
||||
|
||||
error_msg = f"[{error.severity.value.upper()}] {error}"
|
||||
if error.context:
|
||||
error_msg += f" | Context: {error.context}"
|
||||
|
||||
|
||||
if error.severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL]:
|
||||
logger.error(error_msg)
|
||||
logger.error(f"Stack trace: {traceback.format_exc()}")
|
||||
@@ -953,16 +953,16 @@ class ErrorHandler:
|
||||
logger.warning(error_msg)
|
||||
else:
|
||||
logger.info(error_msg)
|
||||
|
||||
|
||||
def get_error_summary(self) -> Dict[str, Any]:
|
||||
"""Get summary of errors encountered"""
|
||||
if not self.error_log:
|
||||
return {"total_errors": 0}
|
||||
|
||||
|
||||
severity_counts = {}
|
||||
for error in self.error_log:
|
||||
severity_counts[error.severity.value] = severity_counts.get(error.severity.value, 0) + 1
|
||||
|
||||
|
||||
return {
|
||||
"total_errors": len(self.error_log),
|
||||
"severity_breakdown": severity_counts,
|
||||
@@ -1004,7 +1004,7 @@ def robust_task(self) -> Task:
|
||||
# Use fallback response
|
||||
return "Task failed, using fallback response"
|
||||
return wrapper
|
||||
|
||||
|
||||
return Task(
|
||||
config=self.tasks_config['research_task'],
|
||||
agent=self.researcher()
|
||||
@@ -1020,60 +1020,60 @@ from pydantic import BaseSettings, Field, validator
|
||||
|
||||
class Environment(str, Enum):
|
||||
DEVELOPMENT = "development"
|
||||
TESTING = "testing"
|
||||
TESTING = "testing"
|
||||
STAGING = "staging"
|
||||
PRODUCTION = "production"
|
||||
|
||||
class CrewAISettings(BaseSettings):
|
||||
"""Comprehensive settings management for CrewAI applications"""
|
||||
|
||||
|
||||
# Environment
|
||||
environment: Environment = Field(default=Environment.DEVELOPMENT)
|
||||
debug: bool = Field(default=True)
|
||||
|
||||
|
||||
# API Keys (loaded from environment)
|
||||
openai_api_key: Optional[str] = Field(default=None, env="OPENAI_API_KEY")
|
||||
anthropic_api_key: Optional[str] = Field(default=None, env="ANTHROPIC_API_KEY")
|
||||
serper_api_key: Optional[str] = Field(default=None, env="SERPER_API_KEY")
|
||||
mem0_api_key: Optional[str] = Field(default=None, env="MEM0_API_KEY")
|
||||
|
||||
|
||||
# CrewAI Configuration
|
||||
crew_max_rpm: int = Field(default=100)
|
||||
crew_max_execution_time: int = Field(default=3600) # 1 hour
|
||||
default_llm_model: str = Field(default="gpt-4")
|
||||
fallback_llm_model: str = Field(default="gpt-3.5-turbo")
|
||||
|
||||
|
||||
# Memory and Storage
|
||||
crewai_storage_dir: str = Field(default="./storage", env="CREWAI_STORAGE_DIR")
|
||||
memory_enabled: bool = Field(default=True)
|
||||
memory_cleanup_interval: int = Field(default=86400) # 24 hours in seconds
|
||||
|
||||
|
||||
# Performance
|
||||
enable_caching: bool = Field(default=True)
|
||||
max_retries: int = Field(default=3)
|
||||
retry_delay: float = Field(default=1.0)
|
||||
|
||||
|
||||
# Monitoring
|
||||
enable_monitoring: bool = Field(default=True)
|
||||
log_level: str = Field(default="INFO")
|
||||
metrics_export_interval: int = Field(default=3600) # 1 hour
|
||||
|
||||
|
||||
# Security
|
||||
input_sanitization: bool = Field(default=True)
|
||||
max_input_length: int = Field(default=10000)
|
||||
allowed_file_types: list = Field(default=["txt", "md", "pdf", "docx"])
|
||||
|
||||
|
||||
@validator('environment', pre=True)
|
||||
def set_debug_based_on_env(cls, v):
|
||||
return v
|
||||
|
||||
|
||||
@validator('debug')
|
||||
def set_debug_from_env(cls, v, values):
|
||||
env = values.get('environment')
|
||||
if env == Environment.PRODUCTION:
|
||||
return False
|
||||
return v
|
||||
|
||||
|
||||
@validator('openai_api_key')
|
||||
def validate_openai_key(cls, v):
|
||||
if not v:
|
||||
@@ -1081,15 +1081,15 @@ class CrewAISettings(BaseSettings):
|
||||
if not v.startswith('sk-'):
|
||||
raise ValueError("Invalid OpenAI API key format")
|
||||
return v
|
||||
|
||||
|
||||
@property
|
||||
def is_production(self) -> bool:
|
||||
return self.environment == Environment.PRODUCTION
|
||||
|
||||
|
||||
@property
|
||||
def is_development(self) -> bool:
|
||||
return self.environment == Environment.DEVELOPMENT
|
||||
|
||||
|
||||
def get_llm_config(self) -> Dict[str, Any]:
|
||||
"""Get LLM configuration based on environment"""
|
||||
config = {
|
||||
@@ -1098,12 +1098,12 @@ class CrewAISettings(BaseSettings):
|
||||
"max_tokens": 4000 if self.is_production else 2000,
|
||||
"timeout": 60
|
||||
}
|
||||
|
||||
|
||||
if self.is_development:
|
||||
config["model"] = self.fallback_llm_model
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_memory_config(self) -> Dict[str, Any]:
|
||||
"""Get memory configuration"""
|
||||
return {
|
||||
@@ -1112,7 +1112,7 @@ class CrewAISettings(BaseSettings):
|
||||
"cleanup_interval": self.memory_cleanup_interval,
|
||||
"provider": "mem0" if self.mem0_api_key and self.is_production else "local"
|
||||
}
|
||||
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = 'utf-8'
|
||||
@@ -1125,25 +1125,25 @@ settings = CrewAISettings()
|
||||
@CrewBase
|
||||
class ConfigurableCrew():
|
||||
"""Crew that uses centralized configuration"""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.settings = settings
|
||||
self.validate_configuration()
|
||||
|
||||
|
||||
def validate_configuration(self):
|
||||
"""Validate configuration before crew execution"""
|
||||
required_keys = [self.settings.openai_api_key]
|
||||
if not all(required_keys):
|
||||
raise ConfigurationError("Missing required API keys")
|
||||
|
||||
|
||||
if not os.path.exists(self.settings.crewai_storage_dir):
|
||||
os.makedirs(self.settings.crewai_storage_dir, exist_ok=True)
|
||||
|
||||
|
||||
@agent
|
||||
def adaptive_agent(self) -> Agent:
|
||||
"""Agent that adapts to configuration"""
|
||||
llm_config = self.settings.get_llm_config()
|
||||
|
||||
|
||||
return Agent(
|
||||
config=self.agents_config['researcher'],
|
||||
llm=llm_config["model"],
|
||||
@@ -1163,7 +1163,7 @@ from crewai.tasks.task_output import TaskOutput
|
||||
|
||||
class CrewAITestFramework:
|
||||
"""Comprehensive testing framework for CrewAI applications"""
|
||||
|
||||
|
||||
@staticmethod
|
||||
def create_mock_agent(role: str = "test_agent", tools: list = None) -> Mock:
|
||||
"""Create a mock agent for testing"""
|
||||
@@ -1175,9 +1175,9 @@ class CrewAITestFramework:
|
||||
mock_agent.llm = "gpt-3.5-turbo"
|
||||
mock_agent.verbose = False
|
||||
return mock_agent
|
||||
|
||||
|
||||
@staticmethod
|
||||
def create_mock_task_output(content: str, success: bool = True,
|
||||
def create_mock_task_output(content: str, success: bool = True,
|
||||
tokens: int = 100) -> TaskOutput:
|
||||
"""Create a mock task output for testing"""
|
||||
return TaskOutput(
|
||||
@@ -1187,13 +1187,13 @@ class CrewAITestFramework:
|
||||
pydantic=None,
|
||||
json_dict=None
|
||||
)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def create_test_crew(agents: list = None, tasks: list = None) -> Crew:
|
||||
"""Create a test crew with mock components"""
|
||||
test_agents = agents or [CrewAITestFramework.create_mock_agent()]
|
||||
test_tasks = tasks or []
|
||||
|
||||
|
||||
return Crew(
|
||||
agents=test_agents,
|
||||
tasks=test_tasks,
|
||||
@@ -1203,53 +1203,53 @@ class CrewAITestFramework:
|
||||
# Example test cases
|
||||
class TestResearchCrew:
|
||||
"""Test cases for research crew functionality"""
|
||||
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test environment"""
|
||||
self.framework = CrewAITestFramework()
|
||||
self.mock_serper = Mock()
|
||||
|
||||
|
||||
@patch('crewai_tools.SerperDevTool')
|
||||
def test_agent_creation(self, mock_serper_tool):
|
||||
"""Test agent creation with proper configuration"""
|
||||
mock_serper_tool.return_value = self.mock_serper
|
||||
|
||||
|
||||
crew = ResearchCrew()
|
||||
researcher = crew.researcher()
|
||||
|
||||
|
||||
assert researcher.role == "Senior Research Analyst"
|
||||
assert len(researcher.tools) > 0
|
||||
assert researcher.verbose is True
|
||||
|
||||
|
||||
def test_task_validation(self):
|
||||
"""Test task validation logic"""
|
||||
crew = ResearchCrew()
|
||||
|
||||
|
||||
# Test valid output
|
||||
valid_output = self.framework.create_mock_task_output(
|
||||
"This is a comprehensive research summary with conclusions and findings."
|
||||
)
|
||||
is_valid, message = crew.validate_research_quality(valid_output)
|
||||
assert is_valid is True
|
||||
|
||||
|
||||
# Test invalid output (too short)
|
||||
invalid_output = self.framework.create_mock_task_output("Too short")
|
||||
is_valid, message = crew.validate_research_quality(invalid_output)
|
||||
assert is_valid is False
|
||||
assert "brief" in message.lower()
|
||||
|
||||
|
||||
@patch('requests.get')
|
||||
def test_tool_error_handling(self, mock_requests):
|
||||
"""Test tool error handling and recovery"""
|
||||
# Simulate network error
|
||||
mock_requests.side_effect = requests.exceptions.RequestException("Network error")
|
||||
|
||||
|
||||
tool = RobustSearchTool()
|
||||
result = tool._run("test query")
|
||||
|
||||
|
||||
assert "network error" in result.lower()
|
||||
assert "failed" in result.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crew_execution_flow(self):
|
||||
"""Test complete crew execution with mocked dependencies"""
|
||||
@@ -1257,18 +1257,18 @@ class TestResearchCrew:
|
||||
mock_execute.return_value = self.framework.create_mock_task_output(
|
||||
"Research completed successfully with findings and recommendations."
|
||||
)
|
||||
|
||||
|
||||
crew = ResearchCrew()
|
||||
result = crew.crew().kickoff(inputs={"topic": "AI testing"})
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert "successfully" in result.raw.lower()
|
||||
|
||||
|
||||
def test_memory_integration(self):
|
||||
"""Test memory system integration"""
|
||||
crew = ResearchCrew()
|
||||
memory_manager = AdvancedMemoryManager(crew)
|
||||
|
||||
|
||||
# Test saving to memory
|
||||
test_content = "Important research finding about AI"
|
||||
memory_manager.save_with_context(
|
||||
@@ -1277,34 +1277,34 @@ class TestResearchCrew:
|
||||
metadata={"importance": "high"},
|
||||
agent="researcher"
|
||||
)
|
||||
|
||||
|
||||
# Test searching memory
|
||||
results = memory_manager.search_across_memories("AI research")
|
||||
assert "short_term" in results
|
||||
|
||||
|
||||
def test_error_handling_workflow(self):
|
||||
"""Test error handling and recovery mechanisms"""
|
||||
error_handler = ErrorHandler("test_crew")
|
||||
|
||||
|
||||
# Test error registration and handling
|
||||
test_error = TaskExecutionError("Test task failed", ErrorSeverity.MEDIUM)
|
||||
result = error_handler.handle_error(test_error)
|
||||
|
||||
|
||||
assert len(error_handler.error_log) == 1
|
||||
assert error_handler.error_log[0].severity == ErrorSeverity.MEDIUM
|
||||
|
||||
|
||||
def test_configuration_validation(self):
|
||||
"""Test configuration validation"""
|
||||
# Test with missing API key
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
with pytest.raises(ValueError):
|
||||
settings = CrewAISettings()
|
||||
|
||||
|
||||
# Test with valid configuration
|
||||
with patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test-key"}):
|
||||
settings = CrewAISettings()
|
||||
assert settings.openai_api_key == "sk-test-key"
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_end_to_end_workflow(self):
|
||||
"""Integration test for complete workflow"""
|
||||
@@ -1315,41 +1315,41 @@ class TestResearchCrew:
|
||||
# Performance testing
|
||||
class TestCrewPerformance:
|
||||
"""Performance tests for CrewAI applications"""
|
||||
|
||||
|
||||
def test_memory_usage(self):
|
||||
"""Test memory usage during crew execution"""
|
||||
import psutil
|
||||
import gc
|
||||
|
||||
|
||||
process = psutil.Process()
|
||||
initial_memory = process.memory_info().rss
|
||||
|
||||
|
||||
# Create and run crew multiple times
|
||||
for i in range(10):
|
||||
crew = ResearchCrew()
|
||||
# Simulate crew execution
|
||||
del crew
|
||||
gc.collect()
|
||||
|
||||
|
||||
final_memory = process.memory_info().rss
|
||||
memory_increase = final_memory - initial_memory
|
||||
|
||||
|
||||
# Assert memory increase is reasonable (less than 100MB)
|
||||
assert memory_increase < 100 * 1024 * 1024
|
||||
|
||||
|
||||
def test_concurrent_execution(self):
|
||||
"""Test concurrent crew execution"""
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
def run_crew(crew_id):
|
||||
crew = ResearchCrew()
|
||||
# Simulate execution
|
||||
return f"crew_{crew_id}_completed"
|
||||
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
|
||||
futures = [executor.submit(run_crew, i) for i in range(5)]
|
||||
results = [future.result() for future in futures]
|
||||
|
||||
|
||||
assert len(results) == 5
|
||||
assert all("completed" in result for result in results)
|
||||
|
||||
@@ -1400,7 +1400,7 @@ class TestCrewPerformance:
|
||||
|
||||
### Development:
|
||||
1. Always use .env files for sensitive configuration
|
||||
2. Implement comprehensive error handling and logging
|
||||
2. Implement comprehensive error handling and logging
|
||||
3. Use structured outputs with Pydantic for reliability
|
||||
4. Test crew functionality with different input scenarios
|
||||
5. Follow CrewAI patterns and conventions consistently
|
||||
@@ -1426,4 +1426,4 @@ class TestCrewPerformance:
|
||||
5. Use async patterns for I/O-bound operations
|
||||
6. Implement proper connection pooling and resource management
|
||||
7. Profile and optimize critical paths
|
||||
8. Plan for horizontal scaling when needed
|
||||
8. Plan for horizontal scaling when needed
|
||||
20
.github/workflows/tests.yml
vendored
20
.github/workflows/tests.yml
vendored
@@ -7,18 +7,14 @@ permissions:
|
||||
|
||||
env:
|
||||
OPENAI_API_KEY: fake-api-key
|
||||
PYTHONUNBUFFERED: 1
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: tests (${{ matrix.python-version }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
python-version: ['3.10', '3.11', '3.12', '3.13']
|
||||
group: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -27,9 +23,6 @@ jobs:
|
||||
uses: astral-sh/setup-uv@v3
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: |
|
||||
**/pyproject.toml
|
||||
**/uv.lock
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
run: uv python install ${{ matrix.python-version }}
|
||||
@@ -37,14 +30,5 @@ jobs:
|
||||
- name: Install the project
|
||||
run: uv sync --dev --all-extras
|
||||
|
||||
- name: Run tests (group ${{ matrix.group }} of 8)
|
||||
run: |
|
||||
uv run pytest \
|
||||
--block-network \
|
||||
--timeout=30 \
|
||||
-vv \
|
||||
--splits 8 \
|
||||
--group ${{ matrix.group }} \
|
||||
--durations=10 \
|
||||
-n auto \
|
||||
--maxfail=3
|
||||
- name: Run tests
|
||||
run: uv run pytest --block-network --timeout=60 -vv
|
||||
|
||||
@@ -5,7 +5,3 @@ repos:
|
||||
- id: ruff
|
||||
args: ["--fix"]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/commitizen-tools/commitizen
|
||||
rev: v3.13.0
|
||||
hooks:
|
||||
- id: commitizen
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
"pages": [
|
||||
"en/guides/advanced/customizing-prompts",
|
||||
"en/guides/advanced/fingerprinting"
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -296,8 +296,7 @@
|
||||
"en/enterprise/features/webhook-streaming",
|
||||
"en/enterprise/features/traces",
|
||||
"en/enterprise/features/hallucination-guardrail",
|
||||
"en/enterprise/features/integrations",
|
||||
"en/enterprise/features/agent-repositories"
|
||||
"en/enterprise/features/integrations"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -374,7 +373,7 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -731,7 +730,7 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -775,7 +774,7 @@
|
||||
"destination": "/en/introduction"
|
||||
},
|
||||
{
|
||||
"source": "/installation",
|
||||
"source": "/installation",
|
||||
"destination": "/en/installation"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -526,103 +526,6 @@ agent = Agent(
|
||||
The context window management feature works automatically in the background. You don't need to call any special functions - just set `respect_context_window` to your preferred behavior and CrewAI handles the rest!
|
||||
</Note>
|
||||
|
||||
## Direct Agent Interaction with `kickoff()`
|
||||
|
||||
Agents can be used directly without going through a task or crew workflow using the `kickoff()` method. This provides a simpler way to interact with an agent when you don't need the full crew orchestration capabilities.
|
||||
|
||||
### How `kickoff()` Works
|
||||
|
||||
The `kickoff()` method allows you to send messages directly to an agent and get a response, similar to how you would interact with an LLM but with all the agent's capabilities (tools, reasoning, etc.).
|
||||
|
||||
```python Code
|
||||
from crewai import Agent
|
||||
from crewai_tools import SerperDevTool
|
||||
|
||||
# Create an agent
|
||||
researcher = Agent(
|
||||
role="AI Technology Researcher",
|
||||
goal="Research the latest AI developments",
|
||||
tools=[SerperDevTool()],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Use kickoff() to interact directly with the agent
|
||||
result = researcher.kickoff("What are the latest developments in language models?")
|
||||
|
||||
# Access the raw response
|
||||
print(result.raw)
|
||||
```
|
||||
|
||||
### Parameters and Return Values
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :---------------- | :---------------------------------- | :------------------------------------------------------------------------ |
|
||||
| `messages` | `Union[str, List[Dict[str, str]]]` | Either a string query or a list of message dictionaries with role/content |
|
||||
| `response_format` | `Optional[Type[Any]]` | Optional Pydantic model for structured output |
|
||||
|
||||
The method returns a `LiteAgentOutput` object with the following properties:
|
||||
|
||||
- `raw`: String containing the raw output text
|
||||
- `pydantic`: Parsed Pydantic model (if a `response_format` was provided)
|
||||
- `agent_role`: Role of the agent that produced the output
|
||||
- `usage_metrics`: Token usage metrics for the execution
|
||||
|
||||
### Structured Output
|
||||
|
||||
You can get structured output by providing a Pydantic model as the `response_format`:
|
||||
|
||||
```python Code
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
class ResearchFindings(BaseModel):
|
||||
main_points: List[str]
|
||||
key_technologies: List[str]
|
||||
future_predictions: str
|
||||
|
||||
# Get structured output
|
||||
result = researcher.kickoff(
|
||||
"Summarize the latest developments in AI for 2025",
|
||||
response_format=ResearchFindings
|
||||
)
|
||||
|
||||
# Access structured data
|
||||
print(result.pydantic.main_points)
|
||||
print(result.pydantic.future_predictions)
|
||||
```
|
||||
|
||||
### Multiple Messages
|
||||
|
||||
You can also provide a conversation history as a list of message dictionaries:
|
||||
|
||||
```python Code
|
||||
messages = [
|
||||
{"role": "user", "content": "I need information about large language models"},
|
||||
{"role": "assistant", "content": "I'd be happy to help with that! What specifically would you like to know?"},
|
||||
{"role": "user", "content": "What are the latest developments in 2025?"}
|
||||
]
|
||||
|
||||
result = researcher.kickoff(messages)
|
||||
```
|
||||
|
||||
### Async Support
|
||||
|
||||
An asynchronous version is available via `kickoff_async()` with the same parameters:
|
||||
|
||||
```python Code
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
result = await researcher.kickoff_async("What are the latest developments in AI?")
|
||||
print(result.raw)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `kickoff()` method uses a `LiteAgent` internally, which provides a simpler execution flow while preserving all of the agent's configuration (role, goal, backstory, tools, etc.).
|
||||
</Note>
|
||||
|
||||
## Important Considerations and Best Practices
|
||||
|
||||
### Security and Code Execution
|
||||
|
||||
@@ -4,8 +4,6 @@ description: Learn how to use the CrewAI CLI to interact with CrewAI.
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
<Warning>Since release 0.140.0, CrewAI Enterprise started a process of migrating their login provider. As such, the authentication flow via CLI was updated. Users that use Google to login, or that created their account after July 3rd, 2025 will be unable to log in with older versions of the `crewai` library.</Warning>
|
||||
|
||||
## Overview
|
||||
|
||||
The CrewAI CLI provides a set of commands to interact with CrewAI, allowing you to create, train, run, and manage crews & flows.
|
||||
@@ -188,7 +186,10 @@ def crew(self) -> Crew:
|
||||
Deploy the crew or flow to [CrewAI Enterprise](https://app.crewai.com).
|
||||
|
||||
- **Authentication**: You need to be authenticated to deploy to CrewAI Enterprise.
|
||||
You can login or create an account with:
|
||||
```shell Terminal
|
||||
crewai signup
|
||||
```
|
||||
If you already have an account, you can login with:
|
||||
```shell Terminal
|
||||
crewai login
|
||||
```
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
---
|
||||
title: 'Agent Repositories'
|
||||
description: 'Learn how to use Agent Repositories to share and reuse your agents across teams and projects'
|
||||
icon: 'database'
|
||||
---
|
||||
|
||||
Agent Repositories allow enterprise users to store, share, and reuse agent definitions across teams and projects. This feature enables organizations to maintain a centralized library of standardized agents, promoting consistency and reducing duplication of effort.
|
||||
|
||||
## Benefits of Agent Repositories
|
||||
|
||||
- **Standardization**: Maintain consistent agent definitions across your organization
|
||||
- **Reusability**: Create an agent once and use it in multiple crews and projects
|
||||
- **Governance**: Implement organization-wide policies for agent configurations
|
||||
- **Collaboration**: Enable teams to share and build upon each other's work
|
||||
|
||||
## Using Agent Repositories
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. You must have an account at CrewAI, try the [free plan](https://app.crewai.com).
|
||||
2. You need to be authenticated using the CrewAI CLI.
|
||||
3. If you have more than one organization, make sure you are switched to the correct organization using the CLI command:
|
||||
|
||||
```bash
|
||||
crewai org switch <org_id>
|
||||
```
|
||||
|
||||
### Creating and Managing Agents in Repositories
|
||||
|
||||
To create and manage agents in repositories,Enterprise Dashboard.
|
||||
|
||||
### Loading Agents from Repositories
|
||||
|
||||
You can load agents from repositories in your code using the `from_repository` parameter:
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
|
||||
# Create an agent by loading it from a repository
|
||||
# The agent is loaded with all its predefined configurations
|
||||
researcher = Agent(
|
||||
from_repository="market-research-agent"
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
### Overriding Repository Settings
|
||||
|
||||
You can override specific settings from the repository by providing them in the configuration:
|
||||
|
||||
```python
|
||||
researcher = Agent(
|
||||
from_repository="market-research-agent",
|
||||
goal="Research the latest trends in AI development", # Override the repository goal
|
||||
verbose=True # Add a setting not in the repository
|
||||
)
|
||||
```
|
||||
|
||||
### Example: Creating a Crew with Repository Agents
|
||||
|
||||
```python
|
||||
from crewai import Crew, Agent, Task
|
||||
|
||||
# Load agents from repositories
|
||||
researcher = Agent(
|
||||
from_repository="market-research-agent"
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
from_repository="content-writer-agent"
|
||||
)
|
||||
|
||||
# Create tasks
|
||||
research_task = Task(
|
||||
description="Research the latest trends in AI",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
writing_task = Task(
|
||||
description="Write a comprehensive report based on the research",
|
||||
agent=writer
|
||||
)
|
||||
|
||||
# Create the crew
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
tasks=[research_task, writing_task],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Run the crew
|
||||
result = crew.kickoff()
|
||||
```
|
||||
|
||||
### Example: Using `kickoff()` with Repository Agents
|
||||
|
||||
You can also use repository agents directly with the `kickoff()` method for simpler interactions:
|
||||
|
||||
```python
|
||||
from crewai import Agent
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
# Define a structured output format
|
||||
class MarketAnalysis(BaseModel):
|
||||
key_trends: List[str]
|
||||
opportunities: List[str]
|
||||
recommendation: str
|
||||
|
||||
# Load an agent from repository
|
||||
analyst = Agent(
|
||||
from_repository="market-analyst-agent",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Get a free-form response
|
||||
result = analyst.kickoff("Analyze the AI market in 2025")
|
||||
print(result.raw) # Access the raw response
|
||||
|
||||
# Get structured output
|
||||
structured_result = analyst.kickoff(
|
||||
"Provide a structured analysis of the AI market in 2025",
|
||||
response_format=MarketAnalysis
|
||||
)
|
||||
|
||||
# Access structured data
|
||||
print(f"Key Trends: {structured_result.pydantic.key_trends}")
|
||||
print(f"Recommendation: {structured_result.pydantic.recommendation}")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Naming Convention**: Use clear, descriptive names for your repository agents
|
||||
2. **Documentation**: Include comprehensive descriptions for each agent
|
||||
3. **Tool Management**: Ensure that tools referenced by repository agents are available in your environment
|
||||
4. **Access Control**: Manage permissions to ensure only authorized team members can modify repository agents
|
||||
|
||||
## Organization Management
|
||||
|
||||
To switch between organizations or see your current organization, use the CrewAI CLI:
|
||||
|
||||
```bash
|
||||
# View current organization
|
||||
crewai org current
|
||||
|
||||
# Switch to a different organization
|
||||
crewai org switch <org_id>
|
||||
|
||||
# List all available organizations
|
||||
crewai org list
|
||||
```
|
||||
|
||||
<Note>
|
||||
When loading agents from repositories, you must be authenticated and switched to the correct organization. If you receive errors, check your authentication status and organization settings using the CLI commands above.
|
||||
</Note>
|
||||
@@ -41,8 +41,11 @@ The CLI provides the fastest way to deploy locally developed crews to the Enterp
|
||||
First, you need to authenticate your CLI with the CrewAI Enterprise platform:
|
||||
|
||||
```bash
|
||||
# If you already have a CrewAI Enterprise account, or want to create one:
|
||||
# If you already have a CrewAI Enterprise account
|
||||
crewai login
|
||||
|
||||
# If you're creating a new account
|
||||
crewai signup
|
||||
```
|
||||
|
||||
When you run either command, the CLI will:
|
||||
|
||||
@@ -149,33 +149,34 @@ from crewai_tools import SerperDevTool
|
||||
|
||||
# Crie um agente com todos os parâmetros disponíveis
|
||||
agent = Agent(
|
||||
role="Cientista de Dados Sênior",
|
||||
goal="Analisar e interpretar conjuntos de dados complexos para fornecer insights acionáveis",
|
||||
backstory="Com mais de 10 anos de experiência em ciência de dados e aprendizado de máquina, você é especialista em encontrar padrões em grandes volumes de dados.",
|
||||
llm="gpt-4", # Padrão: OPENAI_MODEL_NAME ou "gpt-4"
|
||||
function_calling_llm=None, # Opcional: LLM separado para chamadas de ferramentas
|
||||
verbose=False, # Padrão: False
|
||||
allow_delegation=False, # Padrão: False
|
||||
max_iter=20, # Padrão: 20 iterações
|
||||
max_rpm=None, # Opcional: Limite de requisições por minuto
|
||||
max_execution_time=None, # Opcional: Tempo máximo de execução em segundos
|
||||
max_retry_limit=2, # Padrão: 2 tentativas em caso de erro
|
||||
allow_code_execution=False, # Padrão: False
|
||||
code_execution_mode="safe", # Padrão: "safe" (opções: "safe", "unsafe")
|
||||
respect_context_window=True, # Padrão: True
|
||||
use_system_prompt=True, # Padrão: True
|
||||
multimodal=False, # Padrão: False
|
||||
inject_date=False, # Padrão: False
|
||||
date_format="%Y-%m-%d", # Padrão: formato ISO
|
||||
reasoning=False, # Padrão: False
|
||||
max_reasoning_attempts=None, # Padrão: None
|
||||
tools=[SerperDevTool()], # Opcional: Lista de ferramentas
|
||||
knowledge_sources=None, # Opcional: Lista de fontes de conhecimento
|
||||
embedder=None, # Opcional: Configuração de embedder customizado
|
||||
system_template=None, # Opcional: Template de prompt de sistema
|
||||
prompt_template=None, # Opcional: Template de prompt customizado
|
||||
response_template=None, # Opcional: Template de resposta customizado
|
||||
step_callback=None, # Opcional: Função de callback para monitoramento
|
||||
role="Senior Data Scientist",
|
||||
goal="Analyze and interpret complex datasets to provide actionable insights",
|
||||
backstory="With over 10 years of experience in data science and machine learning, "
|
||||
"you excel at finding patterns in complex datasets.",
|
||||
llm="gpt-4", # Default: OPENAI_MODEL_NAME or "gpt-4"
|
||||
function_calling_llm=None, # Optional: Separate LLM for tool calling
|
||||
verbose=False, # Default: False
|
||||
allow_delegation=False, # Default: False
|
||||
max_iter=20, # Default: 20 iterations
|
||||
max_rpm=None, # Optional: Rate limit for API calls
|
||||
max_execution_time=None, # Optional: Maximum execution time in seconds
|
||||
max_retry_limit=2, # Default: 2 retries on error
|
||||
allow_code_execution=False, # Default: False
|
||||
code_execution_mode="safe", # Default: "safe" (options: "safe", "unsafe")
|
||||
respect_context_window=True, # Default: True
|
||||
use_system_prompt=True, # Default: True
|
||||
multimodal=False, # Default: False
|
||||
inject_date=False, # Default: False
|
||||
date_format="%Y-%m-%d", # Default: ISO format
|
||||
reasoning=False, # Default: False
|
||||
max_reasoning_attempts=None, # Default: None
|
||||
tools=[SerperDevTool()], # Optional: List of tools
|
||||
knowledge_sources=None, # Optional: List of knowledge sources
|
||||
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
|
||||
step_callback=None, # Optional: Callback function for monitoring
|
||||
)
|
||||
```
|
||||
|
||||
@@ -184,62 +185,65 @@ Vamos detalhar algumas combinações de parâmetros-chave para casos de uso comu
|
||||
#### Agente de Pesquisa Básico
|
||||
```python Code
|
||||
research_agent = Agent(
|
||||
role="Analista de Pesquisa",
|
||||
goal="Encontrar e resumir informações sobre tópicos específicos",
|
||||
backstory="Você é um pesquisador experiente com atenção aos detalhes",
|
||||
role="Research Analyst",
|
||||
goal="Find and summarize information about specific topics",
|
||||
backstory="You are an experienced researcher with attention to detail",
|
||||
tools=[SerperDevTool()],
|
||||
verbose=True # Ativa logs para depuração
|
||||
verbose=True # Enable logging for debugging
|
||||
)
|
||||
```
|
||||
|
||||
#### Agente de Desenvolvimento de Código
|
||||
```python Code
|
||||
dev_agent = Agent(
|
||||
role="Desenvolvedor Python Sênior",
|
||||
goal="Escrever e depurar códigos Python",
|
||||
backstory="Desenvolvedor Python especialista com 10 anos de experiência",
|
||||
role="Senior Python Developer",
|
||||
goal="Write and debug Python code",
|
||||
backstory="Expert Python developer with 10 years of experience",
|
||||
allow_code_execution=True,
|
||||
code_execution_mode="safe", # Usa Docker para segurança
|
||||
max_execution_time=300, # Limite de 5 minutos
|
||||
max_retry_limit=3 # Mais tentativas para tarefas complexas
|
||||
code_execution_mode="safe", # Uses Docker for safety
|
||||
max_execution_time=300, # 5-minute timeout
|
||||
max_retry_limit=3 # More retries for complex code tasks
|
||||
)
|
||||
```
|
||||
|
||||
#### Agente de Análise de Longa Duração
|
||||
```python Code
|
||||
analysis_agent = Agent(
|
||||
role="Analista de Dados",
|
||||
goal="Realizar análise aprofundada de grandes conjuntos de dados",
|
||||
backstory="Especialista em análise de big data e reconhecimento de padrões",
|
||||
role="Data Analyst",
|
||||
goal="Perform deep analysis of large datasets",
|
||||
backstory="Specialized in big data analysis and pattern recognition",
|
||||
memory=True,
|
||||
respect_context_window=True,
|
||||
max_rpm=10, # Limite de requisições por minuto
|
||||
function_calling_llm="gpt-4o-mini" # Modelo mais econômico para chamadas de ferramentas
|
||||
max_rpm=10, # Limit API calls
|
||||
function_calling_llm="gpt-4o-mini" # Cheaper model for tool calls
|
||||
)
|
||||
```
|
||||
|
||||
#### Agente com Template Personalizado
|
||||
```python Code
|
||||
custom_agent = Agent(
|
||||
role="Atendente de Suporte ao Cliente",
|
||||
goal="Auxiliar clientes com suas dúvidas e solicitações",
|
||||
backstory="Experiente em atendimento ao cliente com foco em satisfação",
|
||||
system_template="""<|start_header_id|>system<|end_header_id|>\n {{ .System }}<|eot_id|>""",
|
||||
prompt_template="""<|start_header_id|>user<|end_header_id|>\n {{ .Prompt }}<|eot_id|>""",
|
||||
response_template="""<|start_header_id|>assistant<|end_header_id|>\n {{ .Response }}<|eot_id|>""",
|
||||
role="Customer Service Representative",
|
||||
goal="Assist customers with their inquiries",
|
||||
backstory="Experienced in customer support with a focus on satisfaction",
|
||||
system_template="""<|start_header_id|>system<|end_header_id|>
|
||||
{{ .System }}<|eot_id|>""",
|
||||
prompt_template="""<|start_header_id|>user<|end_header_id|>
|
||||
{{ .Prompt }}<|eot_id|>""",
|
||||
response_template="""<|start_header_id|>assistant<|end_header_id|>
|
||||
{{ .Response }}<|eot_id|>""",
|
||||
)
|
||||
```
|
||||
|
||||
#### Agente Ciente de Data, com Raciocínio
|
||||
```python Code
|
||||
strategic_agent = Agent(
|
||||
role="Analista de Mercado",
|
||||
goal="Acompanhar movimentos do mercado com referências de datas precisas e planejamento estratégico",
|
||||
backstory="Especialista em análise financeira sensível ao tempo e relatórios estratégicos",
|
||||
inject_date=True, # Injeta automaticamente a data atual nas tarefas
|
||||
date_format="%d de %B de %Y", # Exemplo: "21 de maio de 2025"
|
||||
reasoning=True, # Ativa planejamento estratégico
|
||||
max_reasoning_attempts=2, # Limite de iterações de planejamento
|
||||
role="Market Analyst",
|
||||
goal="Track market movements with precise date references and strategic planning",
|
||||
backstory="Expert in time-sensitive financial analysis and strategic reporting",
|
||||
inject_date=True, # Automatically inject current date into tasks
|
||||
date_format="%B %d, %Y", # Format as "May 21, 2025"
|
||||
reasoning=True, # Enable strategic planning
|
||||
max_reasoning_attempts=2, # Limit planning iterations
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
@@ -247,12 +251,12 @@ strategic_agent = Agent(
|
||||
#### Agente de Raciocínio
|
||||
```python Code
|
||||
reasoning_agent = Agent(
|
||||
role="Planejador Estratégico",
|
||||
goal="Analisar problemas complexos e criar planos de execução detalhados",
|
||||
backstory="Especialista em planejamento estratégico que desmembra desafios complexos metodicamente",
|
||||
reasoning=True, # Ativa raciocínio e planejamento
|
||||
max_reasoning_attempts=3, # Limite de tentativas de raciocínio
|
||||
max_iter=30, # Permite mais iterações para planejamento complexo
|
||||
role="Strategic Planner",
|
||||
goal="Analyze complex problems and create detailed execution plans",
|
||||
backstory="Expert strategic planner who methodically breaks down complex challenges",
|
||||
reasoning=True, # Enable reasoning and planning
|
||||
max_reasoning_attempts=3, # Limit reasoning attempts
|
||||
max_iter=30, # Allow more iterations for complex planning
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
@@ -260,10 +264,10 @@ reasoning_agent = Agent(
|
||||
#### Agente Multimodal
|
||||
```python Code
|
||||
multimodal_agent = Agent(
|
||||
role="Analista de Conteúdo Visual",
|
||||
goal="Analisar e processar tanto conteúdo textual quanto visual",
|
||||
backstory="Especialista em análise multimodal combinando compreensão de texto e imagem",
|
||||
multimodal=True, # Ativa capacidades multimodais
|
||||
role="Visual Content Analyst",
|
||||
goal="Analyze and process both text and visual content",
|
||||
backstory="Specialized in multimodal analysis combining text and image understanding",
|
||||
multimodal=True, # Enable multimodal capabilities
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
@@ -332,8 +336,8 @@ wiki_tool = WikipediaTools()
|
||||
|
||||
# Adicionar ferramentas ao agente
|
||||
researcher = Agent(
|
||||
role="Pesquisador de Tecnologia em IA",
|
||||
goal="Pesquisar os últimos avanços em IA",
|
||||
role="AI Technology Researcher",
|
||||
goal="Research the latest AI developments",
|
||||
tools=[search_tool, wiki_tool],
|
||||
verbose=True
|
||||
)
|
||||
@@ -347,9 +351,9 @@ Agentes podem manter a memória de suas interações e usar contexto de tarefas
|
||||
from crewai import Agent
|
||||
|
||||
analyst = Agent(
|
||||
role="Analista de Dados",
|
||||
goal="Analisar e memorizar padrões complexos de dados",
|
||||
memory=True, # Ativa memória
|
||||
role="Data Analyst",
|
||||
goal="Analyze and remember complex data patterns",
|
||||
memory=True, # Enable memory
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
@@ -376,10 +380,10 @@ Esta é a **configuração padrão e recomendada** para a maioria dos casos. Qua
|
||||
```python Code
|
||||
# Agente com gerenciamento automático de contexto (padrão)
|
||||
smart_agent = Agent(
|
||||
role="Analista de Pesquisa",
|
||||
goal="Analisar grandes documentos e conjuntos de dados",
|
||||
backstory="Especialista em processar informações extensas",
|
||||
respect_context_window=True, # 🔑 Padrão: gerencia limites de contexto automaticamente
|
||||
role="Research Analyst",
|
||||
goal="Analyze large documents and datasets",
|
||||
backstory="Expert at processing extensive information",
|
||||
respect_context_window=True, # 🔑 Default: auto-handle context limits
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
@@ -3,7 +3,6 @@ title: CLI
|
||||
description: Aprenda a usar o CLI do CrewAI para interagir com o CrewAI.
|
||||
icon: terminal
|
||||
---
|
||||
<Warning>A partir da versão 0.140.0, a plataforma CrewAI Enterprise iniciou um processo de migração de seu provedor de login. Como resultado, o fluxo de autenticação via CLI foi atualizado. Usuários que utlizam o Google para fazer login, ou que criaram conta após 3 de julho de 2025 não poderão fazer login com versões anteriores da biblioteca `crewai`.</Warning>
|
||||
|
||||
## Visão Geral
|
||||
|
||||
@@ -76,20 +75,6 @@ Exemplo:
|
||||
crewai train -n 10 -f my_training_data.pkl
|
||||
```
|
||||
|
||||
# Exemplo de uso programático do comando train
|
||||
n_iterations = 2
|
||||
inputs = {"topic": "Treinamento CrewAI"}
|
||||
filename = "seu_modelo.pkl"
|
||||
|
||||
try:
|
||||
SuaCrew().crew().train(
|
||||
n_iterations=n_iterations,
|
||||
inputs=inputs,
|
||||
filename=filename
|
||||
)
|
||||
except Exception as e:
|
||||
raise Exception(f"Ocorreu um erro ao treinar a crew: {e}")
|
||||
|
||||
### 4. Replay
|
||||
|
||||
Reexecute a execução do crew a partir de uma tarefa específica.
|
||||
|
||||
@@ -15,18 +15,18 @@ from crewai import Agent, Crew, Task
|
||||
|
||||
# Enable collaboration for agents
|
||||
researcher = Agent(
|
||||
role="Especialista em Pesquisa",
|
||||
goal="Realizar pesquisas aprofundadas sobre qualquer tema",
|
||||
backstory="Pesquisador especialista com acesso a diversas fontes",
|
||||
allow_delegation=True, # 🔑 Configuração chave para colaboração
|
||||
role="Research Specialist",
|
||||
goal="Conduct thorough research on any topic",
|
||||
backstory="Expert researcher with access to various sources",
|
||||
allow_delegation=True, # 🔑 Key setting for collaboration
|
||||
verbose=True
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
role="Redator de Conteúdo",
|
||||
goal="Criar conteúdo envolvente com base em pesquisas",
|
||||
backstory="Redator habilidoso que transforma pesquisas em conteúdo atraente",
|
||||
allow_delegation=True, # 🔑 Permite fazer perguntas a outros agentes
|
||||
role="Content Writer",
|
||||
goal="Create engaging content based on research",
|
||||
backstory="Skilled writer who transforms research into compelling content",
|
||||
allow_delegation=True, # 🔑 Enables asking questions to other agents
|
||||
verbose=True
|
||||
)
|
||||
|
||||
@@ -67,17 +67,19 @@ from crewai import Agent, Crew, Task, Process
|
||||
|
||||
# Create collaborative agents
|
||||
researcher = Agent(
|
||||
role="Especialista em Pesquisa",
|
||||
goal="Realizar pesquisas aprofundadas sobre qualquer tema",
|
||||
backstory="Pesquisador especialista com acesso a diversas fontes",
|
||||
role="Research Specialist",
|
||||
goal="Find accurate, up-to-date information on any topic",
|
||||
backstory="""You're a meticulous researcher with expertise in finding
|
||||
reliable sources and fact-checking information across various domains.""",
|
||||
allow_delegation=True,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
role="Redator de Conteúdo",
|
||||
goal="Criar conteúdo envolvente com base em pesquisas",
|
||||
backstory="Redator habilidoso que transforma pesquisas em conteúdo atraente",
|
||||
role="Content Writer",
|
||||
goal="Create engaging, well-structured content",
|
||||
backstory="""You're a skilled content writer who excels at transforming
|
||||
research into compelling, readable content for different audiences.""",
|
||||
allow_delegation=True,
|
||||
verbose=True
|
||||
)
|
||||
@@ -93,17 +95,17 @@ editor = Agent(
|
||||
|
||||
# Create a task that encourages collaboration
|
||||
article_task = Task(
|
||||
description="""Escreva um artigo abrangente de 1000 palavras sobre 'O Futuro da IA na Saúde'.
|
||||
|
||||
O artigo deve incluir:
|
||||
- Aplicações atuais de IA na saúde
|
||||
- Tendências e tecnologias emergentes
|
||||
- Desafios potenciais e considerações éticas
|
||||
- Previsões de especialistas para os próximos 5 anos
|
||||
|
||||
Colabore com seus colegas para garantir precisão e qualidade.""",
|
||||
expected_output="Um artigo bem pesquisado, envolvente, com 1000 palavras, estrutura adequada e citações",
|
||||
agent=writer # O redator lidera, mas pode delegar pesquisa ao pesquisador
|
||||
description="""Write a comprehensive 1000-word article about 'The Future of AI in Healthcare'.
|
||||
|
||||
The article should include:
|
||||
- Current AI applications in healthcare
|
||||
- Emerging trends and technologies
|
||||
- Potential challenges and ethical considerations
|
||||
- Expert predictions for the next 5 years
|
||||
|
||||
Collaborate with your teammates to ensure accuracy and quality.""",
|
||||
expected_output="A well-researched, engaging 1000-word article with proper structure and citations",
|
||||
agent=writer # Writer leads, but can delegate research to researcher
|
||||
)
|
||||
|
||||
# Create collaborative crew
|
||||
@@ -122,37 +124,37 @@ result = crew.kickoff()
|
||||
### Padrão 1: Pesquisa → Redação → Edição
|
||||
```python
|
||||
research_task = Task(
|
||||
description="Pesquise os últimos avanços em computação quântica",
|
||||
expected_output="Resumo abrangente da pesquisa com principais descobertas e fontes",
|
||||
description="Research the latest developments in quantum computing",
|
||||
expected_output="Comprehensive research summary with key findings and sources",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
writing_task = Task(
|
||||
description="Escreva um artigo com base nos achados da pesquisa",
|
||||
expected_output="Artigo envolvente de 800 palavras sobre computação quântica",
|
||||
description="Write an article based on the research findings",
|
||||
expected_output="Engaging 800-word article about quantum computing",
|
||||
agent=writer,
|
||||
context=[research_task] # Recebe a saída da pesquisa como contexto
|
||||
context=[research_task] # Gets research output as context
|
||||
)
|
||||
|
||||
editing_task = Task(
|
||||
description="Edite e revise o artigo para publicação",
|
||||
expected_output="Artigo pronto para publicação, com clareza e fluidez aprimoradas",
|
||||
description="Edit and polish the article for publication",
|
||||
expected_output="Publication-ready article with improved clarity and flow",
|
||||
agent=editor,
|
||||
context=[writing_task] # Recebe o rascunho do artigo como contexto
|
||||
context=[writing_task] # Gets article draft as context
|
||||
)
|
||||
```
|
||||
|
||||
### Padrão 2: Tarefa Única Colaborativa
|
||||
```python
|
||||
collaborative_task = Task(
|
||||
description="""Crie uma estratégia de marketing para um novo produto de IA.
|
||||
|
||||
Redator: Foque em mensagens e estratégia de conteúdo
|
||||
Pesquisador: Forneça análise de mercado e insights de concorrentes
|
||||
|
||||
Trabalhem juntos para criar uma estratégia abrangente.""",
|
||||
expected_output="Estratégia de marketing completa com embasamento em pesquisa",
|
||||
agent=writer # Agente líder, mas pode delegar ao pesquisador
|
||||
description="""Create a marketing strategy for a new AI product.
|
||||
|
||||
Writer: Focus on messaging and content strategy
|
||||
Researcher: Provide market analysis and competitor insights
|
||||
|
||||
Work together to create a comprehensive strategy.""",
|
||||
expected_output="Complete marketing strategy with research backing",
|
||||
agent=writer # Lead agent, but can delegate to researcher
|
||||
)
|
||||
```
|
||||
|
||||
@@ -165,35 +167,35 @@ from crewai import Agent, Crew, Task, Process
|
||||
|
||||
# Manager agent coordinates the team
|
||||
manager = Agent(
|
||||
role="Gerente de Projetos",
|
||||
goal="Coordenar esforços da equipe e garantir o sucesso do projeto",
|
||||
backstory="Gerente de projetos experiente, habilidoso em delegação e controle de qualidade",
|
||||
role="Project Manager",
|
||||
goal="Coordinate team efforts and ensure project success",
|
||||
backstory="Experienced project manager skilled at delegation and quality control",
|
||||
allow_delegation=True,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Specialist agents
|
||||
researcher = Agent(
|
||||
role="Pesquisador",
|
||||
goal="Fornecer pesquisa e análise precisas",
|
||||
backstory="Pesquisador especialista com habilidades analíticas profundas",
|
||||
allow_delegation=False, # Especialistas focam em sua expertise
|
||||
role="Researcher",
|
||||
goal="Provide accurate research and analysis",
|
||||
backstory="Expert researcher with deep analytical skills",
|
||||
allow_delegation=False, # Specialists focus on their expertise
|
||||
verbose=True
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
role="Redator",
|
||||
goal="Criar conteúdo envolvente",
|
||||
backstory="Redator habilidoso que cria conteúdo atraente",
|
||||
role="Writer",
|
||||
goal="Create compelling content",
|
||||
backstory="Skilled writer who creates engaging content",
|
||||
allow_delegation=False,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Manager-led task
|
||||
project_task = Task(
|
||||
description="Crie um relatório de análise de mercado completo com recomendações",
|
||||
expected_output="Resumo executivo, análise detalhada e recomendações estratégicas",
|
||||
agent=manager # O gerente delega para especialistas
|
||||
description="Create a comprehensive market analysis report with recommendations",
|
||||
expected_output="Executive summary, detailed analysis, and strategic recommendations",
|
||||
agent=manager # Manager will delegate to specialists
|
||||
)
|
||||
|
||||
# Hierarchical crew
|
||||
|
||||
@@ -153,32 +153,32 @@ from crewai_tools import YourCustomTool
|
||||
class YourCrewName:
|
||||
def agent_one(self) -> Agent:
|
||||
return Agent(
|
||||
role="Analista de Dados",
|
||||
goal="Analisar tendências de dados no mercado brasileiro",
|
||||
backstory="Analista experiente com formação em economia",
|
||||
role="Data Analyst",
|
||||
goal="Analyze data trends in the market",
|
||||
backstory="An experienced data analyst with a background in economics",
|
||||
verbose=True,
|
||||
tools=[YourCustomTool()]
|
||||
)
|
||||
|
||||
def agent_two(self) -> Agent:
|
||||
return Agent(
|
||||
role="Pesquisador de Mercado",
|
||||
goal="Coletar informações sobre a dinâmica do mercado nacional",
|
||||
backstory="Pesquisador dedicado com olhar atento aos detalhes",
|
||||
role="Market Researcher",
|
||||
goal="Gather information on market dynamics",
|
||||
backstory="A diligent researcher with a keen eye for detail",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
def task_one(self) -> Task:
|
||||
return Task(
|
||||
description="Coletar dados recentes do mercado brasileiro e identificar tendências.",
|
||||
expected_output="Um relatório resumido com as principais tendências do mercado.",
|
||||
description="Collect recent market data and identify trends.",
|
||||
expected_output="A report summarizing key trends in the market.",
|
||||
agent=self.agent_one()
|
||||
)
|
||||
|
||||
def task_two(self) -> Task:
|
||||
return Task(
|
||||
description="Pesquisar fatores que afetam a dinâmica do mercado nacional.",
|
||||
expected_output="Uma análise dos fatores que influenciam o mercado.",
|
||||
description="Research factors affecting market dynamics.",
|
||||
expected_output="An analysis of factors influencing the market.",
|
||||
agent=self.agent_two()
|
||||
)
|
||||
|
||||
|
||||
@@ -51,24 +51,24 @@ from crewai.utilities.events import (
|
||||
)
|
||||
from crewai.utilities.events.base_event_listener import BaseEventListener
|
||||
|
||||
class MeuListenerPersonalizado(BaseEventListener):
|
||||
class MyCustomListener(BaseEventListener):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def setup_listeners(self, crewai_event_bus):
|
||||
@crewai_event_bus.on(CrewKickoffStartedEvent)
|
||||
def ao_iniciar_crew(source, event):
|
||||
print(f"Crew '{event.crew_name}' iniciou a execução!")
|
||||
def on_crew_started(source, event):
|
||||
print(f"Crew '{event.crew_name}' has started execution!")
|
||||
|
||||
@crewai_event_bus.on(CrewKickoffCompletedEvent)
|
||||
def ao_finalizar_crew(source, event):
|
||||
print(f"Crew '{event.crew_name}' finalizou a execução!")
|
||||
print(f"Saída: {event.output}")
|
||||
def on_crew_completed(source, event):
|
||||
print(f"Crew '{event.crew_name}' has completed execution!")
|
||||
print(f"Output: {event.output}")
|
||||
|
||||
@crewai_event_bus.on(AgentExecutionCompletedEvent)
|
||||
def ao_finalizar_execucao_agente(source, event):
|
||||
print(f"Agente '{event.agent.role}' concluiu a tarefa")
|
||||
print(f"Saída: {event.output}")
|
||||
def on_agent_execution_completed(source, event):
|
||||
print(f"Agent '{event.agent.role}' completed task")
|
||||
print(f"Output: {event.output}")
|
||||
```
|
||||
|
||||
## Registrando Corretamente Seu Listener
|
||||
|
||||
@@ -486,9 +486,8 @@ Existem duas formas de executar um flow:
|
||||
Você pode executar um flow programaticamente criando uma instância da sua classe de flow e chamando o método `kickoff()`:
|
||||
|
||||
```python
|
||||
# Exemplo de execução de flow em português
|
||||
flow = ExemploFlow()
|
||||
resultado = flow.kickoff()
|
||||
flow = ExampleFlow()
|
||||
result = flow.kickoff()
|
||||
```
|
||||
|
||||
### Usando a CLI
|
||||
|
||||
@@ -39,17 +39,17 @@ llm = LLM(model="gpt-4o-mini", temperature=0)
|
||||
|
||||
# Create an agent with the knowledge store
|
||||
agent = Agent(
|
||||
role="Sobre o Usuário",
|
||||
goal="Você sabe tudo sobre o usuário.",
|
||||
backstory="Você é mestre em entender pessoas e suas preferências.",
|
||||
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=llm,
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Responda às seguintes perguntas sobre o usuário: {question}",
|
||||
expected_output="Uma resposta para a pergunta.",
|
||||
description="Answer the following questions about the user: {question}",
|
||||
expected_output="An answer to the question.",
|
||||
agent=agent,
|
||||
)
|
||||
|
||||
@@ -87,17 +87,17 @@ llm = LLM(model="gpt-4o-mini", temperature=0)
|
||||
|
||||
# Create an agent with the knowledge store
|
||||
agent = Agent(
|
||||
role="Sobre artigos",
|
||||
goal="Você sabe tudo sobre os artigos.",
|
||||
backstory="Você é mestre em entender artigos e seus conteúdos.",
|
||||
role="About papers",
|
||||
goal="You know everything about the papers.",
|
||||
backstory="You are a master at understanding papers and their content.",
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
llm=llm,
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Responda às seguintes perguntas sobre os artigos: {question}",
|
||||
expected_output="Uma resposta para a pergunta.",
|
||||
description="Answer the following questions about the papers: {question}",
|
||||
expected_output="An answer to the question.",
|
||||
agent=agent,
|
||||
)
|
||||
|
||||
@@ -201,16 +201,16 @@ specialist_knowledge = StringKnowledgeSource(
|
||||
)
|
||||
|
||||
specialist_agent = Agent(
|
||||
role="Especialista Técnico",
|
||||
goal="Fornecer expertise técnica",
|
||||
backstory="Especialista em domínios técnicos especializados",
|
||||
knowledge_sources=[specialist_knowledge] # Conhecimento específico do agente
|
||||
role="Technical Specialist",
|
||||
goal="Provide technical expertise",
|
||||
backstory="Expert in specialized technical domains",
|
||||
knowledge_sources=[specialist_knowledge] # Agent-specific knowledge
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Responda perguntas técnicas",
|
||||
description="Answer technical questions",
|
||||
agent=specialist_agent,
|
||||
expected_output="Resposta técnica"
|
||||
expected_output="Technical answer"
|
||||
)
|
||||
|
||||
# No crew-level knowledge required
|
||||
@@ -240,7 +240,7 @@ Cada nível de knowledge usa coleções de armazenamento independentes:
|
||||
|
||||
```python
|
||||
# Agent knowledge storage
|
||||
agent_collection_name = agent.role # e.g., "Especialista Técnico"
|
||||
agent_collection_name = agent.role # e.g., "Technical Specialist"
|
||||
|
||||
# Crew knowledge storage
|
||||
crew_collection_name = "crew"
|
||||
@@ -248,7 +248,7 @@ crew_collection_name = "crew"
|
||||
# Both stored in same ChromaDB instance but different collections
|
||||
# Path: ~/.local/share/CrewAI/{project}/knowledge/
|
||||
# ├── crew/ # Crew knowledge collection
|
||||
# ├── Especialista Técnico/ # Agent knowledge collection
|
||||
# ├── Technical Specialist/ # Agent knowledge collection
|
||||
# └── Another Agent Role/ # Another agent's collection
|
||||
```
|
||||
|
||||
@@ -265,7 +265,7 @@ agent_knowledge = StringKnowledgeSource(
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
role="Especialista",
|
||||
role="Specialist",
|
||||
goal="Use specialized knowledge",
|
||||
backstory="Expert with specific knowledge",
|
||||
knowledge_sources=[agent_knowledge],
|
||||
@@ -299,10 +299,10 @@ specialist_knowledge = StringKnowledgeSource(
|
||||
)
|
||||
|
||||
specialist = Agent(
|
||||
role="Especialista Técnico",
|
||||
goal="Fornecer expertise técnica",
|
||||
backstory="Especialista em domínios técnicos especializados",
|
||||
knowledge_sources=[specialist_knowledge] # Conhecimento específico do agente
|
||||
role="Technical Specialist",
|
||||
goal="Provide technical expertise",
|
||||
backstory="Technical expert",
|
||||
knowledge_sources=[specialist_knowledge] # Agent-specific
|
||||
)
|
||||
|
||||
generalist = Agent(
|
||||
|
||||
@@ -78,15 +78,15 @@ Existem diferentes locais no código do CrewAI onde você pode especificar o mod
|
||||
|
||||
# Configuração avançada com parâmetros detalhados
|
||||
llm = LLM(
|
||||
model="openai/gpt-4",
|
||||
temperature=0.8,
|
||||
max_tokens=150,
|
||||
top_p=0.9,
|
||||
frequency_penalty=0.1,
|
||||
presence_penalty=0.1,
|
||||
response_format={"type":"json"},
|
||||
stop=["FIM"],
|
||||
seed=42
|
||||
model="model-id-here", # gpt-4o, gemini-2.0-flash, anthropic/claude...
|
||||
temperature=0.7, # Mais alto para saídas criativas
|
||||
timeout=120, # Segundos para aguardar resposta
|
||||
max_tokens=4000, # Comprimento máximo da resposta
|
||||
top_p=0.9, # Parâmetro de amostragem nucleus
|
||||
frequency_penalty=0.1 , # Reduz repetição
|
||||
presence_penalty=0.1, # Incentiva diversidade de tópicos
|
||||
response_format={"type": "json"}, # Para respostas estruturadas
|
||||
seed=42 # Para resultados reproduzíveis
|
||||
)
|
||||
```
|
||||
|
||||
@@ -127,13 +127,13 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(
|
||||
model="openai/gpt-4",
|
||||
model="openai/gpt-4", # chamar modelo por provider/model_name
|
||||
temperature=0.8,
|
||||
max_tokens=150,
|
||||
top_p=0.9,
|
||||
frequency_penalty=0.1,
|
||||
presence_penalty=0.1,
|
||||
stop=["FIM"],
|
||||
stop=["END"],
|
||||
seed=42
|
||||
)
|
||||
```
|
||||
@@ -169,7 +169,7 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
llm = LLM(
|
||||
model="meta_llama/Llama-4-Scout-17B-16E-Instruct-FP8",
|
||||
temperature=0.8,
|
||||
stop=["FIM"],
|
||||
stop=["END"],
|
||||
seed=42
|
||||
)
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ Começar a usar o recurso de planejamento é muito simples, o único passo neces
|
||||
from crewai import Crew, Agent, Task, Process
|
||||
|
||||
# Monte sua crew com capacidades de planejamento
|
||||
minha_crew = Crew(
|
||||
my_crew = Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
|
||||
@@ -28,23 +28,23 @@ from crewai import Crew, Process
|
||||
|
||||
# Exemplo: Criando uma crew com processo sequencial
|
||||
crew = Crew(
|
||||
agents=meus_agentes,
|
||||
tasks=minhas_tarefas,
|
||||
agents=my_agents,
|
||||
tasks=my_tasks,
|
||||
process=Process.sequential
|
||||
)
|
||||
|
||||
# Exemplo: Criando uma crew com processo hierárquico
|
||||
# Certifique-se de fornecer um manager_llm ou manager_agent
|
||||
crew = Crew(
|
||||
agents=meus_agentes,
|
||||
tasks=minhas_tarefas,
|
||||
agents=my_agents,
|
||||
tasks=my_tasks,
|
||||
process=Process.hierarchical,
|
||||
manager_llm="gpt-4o"
|
||||
# ou
|
||||
# manager_agent=meu_agente_gerente
|
||||
# manager_agent=my_manager_agent
|
||||
)
|
||||
```
|
||||
**Nota:** Certifique-se de que `meus_agentes` e `minhas_tarefas` estejam definidos antes de criar o objeto `Crew`, e para o processo hierárquico, é necessário também fornecer o `manager_llm` ou `manager_agent`.
|
||||
**Nota:** Certifique-se de que `my_agents` e `my_tasks` estejam definidos antes de criar o objeto `Crew`, e para o processo hierárquico, é necessário também fornecer o `manager_llm` ou `manager_agent`.
|
||||
|
||||
## Processo Sequencial
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ Para habilitar o reasoning para um agente, basta definir `reasoning=True` ao cri
|
||||
```python
|
||||
from crewai import Agent
|
||||
|
||||
analista = Agent(
|
||||
role="Analista de Dados",
|
||||
goal="Analisar dados e fornecer insights",
|
||||
backstory="Você é um analista de dados especialista.",
|
||||
reasoning=True,
|
||||
max_reasoning_attempts=3 # Opcional: Defina um limite de tentativas de reasoning
|
||||
agent = Agent(
|
||||
role="Data Analyst",
|
||||
goal="Analyze complex datasets and provide insights",
|
||||
backstory="You are an experienced data analyst with expertise in finding patterns in complex data.",
|
||||
reasoning=True, # Enable reasoning
|
||||
max_reasoning_attempts=3 # Optional: Set a maximum number of reasoning attempts
|
||||
)
|
||||
```
|
||||
|
||||
@@ -53,23 +53,23 @@ Aqui está um exemplo completo:
|
||||
from crewai import Agent, Task, Crew
|
||||
|
||||
# Create an agent with reasoning enabled
|
||||
analista = Agent(
|
||||
role="Analista de Dados",
|
||||
goal="Analisar dados e fornecer insights",
|
||||
backstory="Você é um analista de dados especialista.",
|
||||
analyst = Agent(
|
||||
role="Data Analyst",
|
||||
goal="Analyze data and provide insights",
|
||||
backstory="You are an expert data analyst.",
|
||||
reasoning=True,
|
||||
max_reasoning_attempts=3 # Opcional: Defina um limite de tentativas de reasoning
|
||||
max_reasoning_attempts=3 # Optional: Set a limit on reasoning attempts
|
||||
)
|
||||
|
||||
# Create a task
|
||||
analysis_task = Task(
|
||||
description="Analise os dados de vendas fornecidos e identifique as principais tendências.",
|
||||
expected_output="Um relatório destacando as 3 principais tendências de vendas.",
|
||||
agent=analista
|
||||
description="Analyze the provided sales data and identify key trends.",
|
||||
expected_output="A report highlighting the top 3 sales trends.",
|
||||
agent=analyst
|
||||
)
|
||||
|
||||
# Create a crew and run the task
|
||||
crew = Crew(agents=[analista], tasks=[analysis_task])
|
||||
crew = Crew(agents=[analyst], tasks=[analysis_task])
|
||||
result = crew.kickoff()
|
||||
|
||||
print(result)
|
||||
@@ -90,16 +90,16 @@ logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Create an agent with reasoning enabled
|
||||
agent = Agent(
|
||||
role="Analista de Dados",
|
||||
goal="Analisar dados e fornecer insights",
|
||||
role="Data Analyst",
|
||||
goal="Analyze data and provide insights",
|
||||
reasoning=True,
|
||||
max_reasoning_attempts=3
|
||||
)
|
||||
|
||||
# Create a task
|
||||
task = Task(
|
||||
description="Analise os dados de vendas fornecidos e identifique as principais tendências.",
|
||||
expected_output="Um relatório destacando as 3 principais tendências de vendas.",
|
||||
description="Analyze the provided sales data and identify key trends.",
|
||||
expected_output="A report highlighting the top 3 sales trends.",
|
||||
agent=agent
|
||||
)
|
||||
|
||||
@@ -113,7 +113,7 @@ result = agent.execute_task(task)
|
||||
Veja um exemplo de como pode ser um plano de reasoning para uma tarefa de análise de dados:
|
||||
|
||||
```
|
||||
Task: Analise os dados de vendas fornecidos e identifique as principais tendências.
|
||||
Task: Analyze the provided sales data and identify key trends.
|
||||
|
||||
Reasoning Plan:
|
||||
I'll analyze the sales data to identify the top 3 trends.
|
||||
|
||||
@@ -386,7 +386,7 @@ def validate_with_context(result: TaskOutput) -> Tuple[bool, Any]:
|
||||
validated_data = perform_validation(result)
|
||||
return (True, validated_data)
|
||||
except ValidationError as e:
|
||||
return (False, f"ERRO_DE_VALIDACAO: {str(e)}")
|
||||
return (False, f"VALIDATION_ERROR: {str(e)}")
|
||||
except Exception as e:
|
||||
return (False, str(e))
|
||||
```
|
||||
|
||||
@@ -67,17 +67,17 @@ web_rag_tool = WebsiteSearchTool()
|
||||
|
||||
# Criar agentes
|
||||
researcher = Agent(
|
||||
role='Analista de Mercado',
|
||||
goal='Fornecer análise de mercado atualizada da indústria de IA',
|
||||
backstory='Analista especialista com olhar atento para tendências de mercado.',
|
||||
role='Market Research Analyst',
|
||||
goal='Provide up-to-date market analysis of the AI industry',
|
||||
backstory='An expert analyst with a keen eye for market trends.',
|
||||
tools=[search_tool, web_rag_tool],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
role='Redator de Conteúdo',
|
||||
goal='Criar posts de blog envolventes sobre a indústria de IA',
|
||||
backstory='Redator habilidoso com paixão por tecnologia.',
|
||||
role='Content Writer',
|
||||
goal='Craft engaging blog posts about the AI industry',
|
||||
backstory='A skilled writer with a passion for technology.',
|
||||
tools=[docs_tool, file_tool],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
@@ -36,18 +36,19 @@ Para treinar sua crew de forma programática, siga estes passos:
|
||||
3. Execute o comando de treinamento dentro de um bloco try-except para tratar possíveis erros.
|
||||
|
||||
```python Code
|
||||
n_iteracoes = 2
|
||||
entradas = {"topic": "Treinamento CrewAI"}
|
||||
nome_arquivo = "seu_modelo.pkl"
|
||||
n_iterations = 2
|
||||
inputs = {"topic": "CrewAI Training"}
|
||||
filename = "your_model.pkl"
|
||||
|
||||
try:
|
||||
SuaCrew().crew().train(
|
||||
n_iterations=n_iteracoes,
|
||||
inputs=entradas,
|
||||
filename=nome_arquivo
|
||||
YourCrewName_Crew().crew().train(
|
||||
n_iterations=n_iterations,
|
||||
inputs=inputs,
|
||||
filename=filename
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Ocorreu um erro ao treinar a crew: {e}")
|
||||
raise Exception(f"An error occurred while training the crew: {e}")
|
||||
```
|
||||
|
||||
### Pontos Importantes
|
||||
|
||||
@@ -26,13 +26,13 @@ from crewai.tasks.hallucination_guardrail import HallucinationGuardrail
|
||||
from crewai import LLM
|
||||
|
||||
# Uso básico - utiliza o expected_output da tarefa como contexto
|
||||
protecao = HallucinationGuardrail(
|
||||
guardrail = HallucinationGuardrail(
|
||||
llm=LLM(model="gpt-4o-mini")
|
||||
)
|
||||
|
||||
# Com contexto de referência explícito
|
||||
protecao_com_contexto = HallucinationGuardrail(
|
||||
context="IA ajuda em várias tarefas, incluindo análise e geração.",
|
||||
context_guardrail = HallucinationGuardrail(
|
||||
context="AI helps with various tasks including analysis and generation.",
|
||||
llm=LLM(model="gpt-4o-mini")
|
||||
)
|
||||
```
|
||||
@@ -43,11 +43,11 @@ protecao_com_contexto = HallucinationGuardrail(
|
||||
from crewai import Task
|
||||
|
||||
# Crie sua tarefa com a proteção
|
||||
minha_tarefa = Task(
|
||||
description="Escreva um resumo sobre as capacidades da IA",
|
||||
expected_output="Um resumo factual baseado no contexto fornecido",
|
||||
agent=meu_agente,
|
||||
guardrail=protecao # Adiciona a proteção para validar a saída
|
||||
task = Task(
|
||||
description="Write a summary about AI capabilities",
|
||||
expected_output="A factual summary based on the provided context",
|
||||
agent=my_agent,
|
||||
guardrail=guardrail # Adiciona a proteção para validar a saída
|
||||
)
|
||||
```
|
||||
|
||||
@@ -59,8 +59,8 @@ Para validação mais rigorosa, é possível definir um limiar de fidelidade per
|
||||
|
||||
```python
|
||||
# Proteção rigorosa exigindo alta pontuação de fidelidade
|
||||
protecao_rigorosa = HallucinationGuardrail(
|
||||
context="Computação quântica utiliza qubits que existem em estados de superposição.",
|
||||
strict_guardrail = HallucinationGuardrail(
|
||||
context="Quantum computing uses qubits that exist in superposition states.",
|
||||
llm=LLM(model="gpt-4o-mini"),
|
||||
threshold=8.0 # Requer pontuação >= 8 para validar
|
||||
)
|
||||
@@ -72,10 +72,10 @@ Se sua tarefa utiliza ferramentas, você pode incluir as respostas das ferrament
|
||||
|
||||
```python
|
||||
# Proteção com contexto de resposta da ferramenta
|
||||
protecao_clima = HallucinationGuardrail(
|
||||
context="Informações meteorológicas atuais para o local solicitado",
|
||||
weather_guardrail = HallucinationGuardrail(
|
||||
context="Current weather information for the requested location",
|
||||
llm=LLM(model="gpt-4o-mini"),
|
||||
tool_response="API do Clima retornou: Temperatura 22°C, Umidade 65%, Céu limpo"
|
||||
tool_response="Weather API returned: Temperature 22°C, Humidity 65%, Clear skies"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -123,15 +123,15 @@ Quando uma proteção é adicionada à tarefa, ela valida automaticamente a saí
|
||||
|
||||
```python
|
||||
# Fluxo de validação de saída da tarefa
|
||||
task_output = meu_agente.execute_task(minha_tarefa)
|
||||
resultado_validacao = protecao(task_output)
|
||||
task_output = agent.execute_task(task)
|
||||
validation_result = guardrail(task_output)
|
||||
|
||||
if resultado_validacao.valid:
|
||||
if validation_result.valid:
|
||||
# Tarefa concluída com sucesso
|
||||
return task_output
|
||||
else:
|
||||
# Tarefa falha com feedback de validação
|
||||
raise ValidationError(resultado_validacao.feedback)
|
||||
raise ValidationError(validation_result.feedback)
|
||||
```
|
||||
|
||||
### Rastreamento de Eventos
|
||||
@@ -151,10 +151,10 @@ A proteção se integra ao sistema de eventos do CrewAI para fornecer observabil
|
||||
Inclua todas as informações factuais relevantes nas quais a IA deve basear sua saída:
|
||||
|
||||
```python
|
||||
contexto = """
|
||||
Empresa XYZ foi fundada em 2020 e é especializada em soluções de energia renovável.
|
||||
Possui 150 funcionários e faturou R$ 50 milhões em 2023.
|
||||
Seus principais produtos incluem painéis solares e turbinas eólicas.
|
||||
context = """
|
||||
Company XYZ was founded in 2020 and specializes in renewable energy solutions.
|
||||
They have 150 employees and generated $50M revenue in 2023.
|
||||
Their main products include solar panels and wind turbines.
|
||||
"""
|
||||
```
|
||||
</Step>
|
||||
@@ -164,10 +164,10 @@ A proteção se integra ao sistema de eventos do CrewAI para fornecer observabil
|
||||
|
||||
```python
|
||||
# Bom: Contexto focado
|
||||
contexto = "O clima atual em Nova York é 18°C com chuva leve."
|
||||
context = "The current weather in New York is 18°C with light rain."
|
||||
|
||||
# Evite: Informações irrelevantes
|
||||
contexto = "The weather is 18°C. The city has 8 million people. Traffic is heavy."
|
||||
context = "The weather is 18°C. The city has 8 million people. Traffic is heavy."
|
||||
```
|
||||
</Step>
|
||||
|
||||
|
||||
@@ -84,31 +84,31 @@ from crewai import Agent, Task, Crew
|
||||
from crewai_tools import CrewaiEnterpriseTools
|
||||
|
||||
# Obtenha ferramentas enterprise (a ferramenta Gmail será incluída)
|
||||
ferramentas_enterprise = CrewaiEnterpriseTools(
|
||||
enterprise_token="seu_token_enterprise"
|
||||
enterprise_tools = CrewaiEnterpriseTools(
|
||||
enterprise_token="your_enterprise_token"
|
||||
)
|
||||
# imprima as ferramentas
|
||||
printf(ferramentas_enterprise)
|
||||
print(enterprise_tools)
|
||||
|
||||
# Crie um agente com capacidades do Gmail
|
||||
agente_email = Agent(
|
||||
role="Gerente de E-mails",
|
||||
goal="Gerenciar e organizar comunicações por e-mail",
|
||||
backstory="Um assistente de IA especializado em gestão de e-mails e comunicação.",
|
||||
tools=ferramentas_enterprise
|
||||
email_agent = Agent(
|
||||
role="Email Manager",
|
||||
goal="Manage and organize email communications",
|
||||
backstory="An AI assistant specialized in email management and communication.",
|
||||
tools=enterprise_tools
|
||||
)
|
||||
|
||||
# Tarefa para enviar um e-mail
|
||||
tarefa_email = Task(
|
||||
description="Redigir e enviar um e-mail de acompanhamento para john@example.com sobre a atualização do projeto",
|
||||
agent=agente_email,
|
||||
expected_output="Confirmação de que o e-mail foi enviado com sucesso"
|
||||
email_task = Task(
|
||||
description="Draft and send a follow-up email to john@example.com about the project update",
|
||||
agent=email_agent,
|
||||
expected_output="Confirmation that email was sent successfully"
|
||||
)
|
||||
|
||||
# Execute a tarefa
|
||||
crew = Crew(
|
||||
agents=[agente_email],
|
||||
tasks=[tarefa_email]
|
||||
agents=[email_agent],
|
||||
tasks=[email_task]
|
||||
)
|
||||
|
||||
# Execute o crew
|
||||
@@ -125,23 +125,23 @@ enterprise_tools = CrewaiEnterpriseTools(
|
||||
)
|
||||
gmail_tool = enterprise_tools["gmail_find_email"]
|
||||
|
||||
agente_gmail = Agent(
|
||||
role="Gerente do Gmail",
|
||||
goal="Gerenciar comunicações e notificações do gmail",
|
||||
backstory="Um assistente de IA que ajuda a coordenar comunicações no gmail.",
|
||||
gmail_agent = Agent(
|
||||
role="Gmail Manager",
|
||||
goal="Manage gmail communications and notifications",
|
||||
backstory="An AI assistant that helps coordinate gmail communications.",
|
||||
tools=[gmail_tool]
|
||||
)
|
||||
|
||||
tarefa_notificacao = Task(
|
||||
description="Encontrar o e-mail de john@example.com",
|
||||
agent=agente_gmail,
|
||||
expected_output="E-mail encontrado de john@example.com"
|
||||
notification_task = Task(
|
||||
description="Find the email from john@example.com",
|
||||
agent=gmail_agent,
|
||||
expected_output="Email found from john@example.com"
|
||||
)
|
||||
|
||||
# Execute a tarefa
|
||||
crew = Crew(
|
||||
agents=[agente_gmail],
|
||||
tasks=[tarefa_notificacao]
|
||||
agents=[slack_agent],
|
||||
tasks=[notification_task]
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Antes de usar o Repositório de Ferramentas, certifique-se de que você possui:
|
||||
Para instalar uma ferramenta:
|
||||
|
||||
```bash
|
||||
crewai tool install <nome-da-ferramenta>
|
||||
crewai tool install <tool-name>
|
||||
```
|
||||
|
||||
Isso instala a ferramenta e a adiciona ao `pyproject.toml`.
|
||||
@@ -40,7 +40,7 @@ Isso instala a ferramenta e a adiciona ao `pyproject.toml`.
|
||||
Para criar um novo projeto de ferramenta:
|
||||
|
||||
```bash
|
||||
crewai tool create <nome-da-ferramenta>
|
||||
crewai tool create <tool-name>
|
||||
```
|
||||
|
||||
Isso gera um projeto de ferramenta estruturado localmente.
|
||||
@@ -76,7 +76,7 @@ Para atualizar uma ferramenta publicada:
|
||||
3. Faça o commit das alterações e publique
|
||||
|
||||
```bash
|
||||
git commit -m "Atualizar versão para 0.1.1"
|
||||
git commit -m "Update version to 0.1.1"
|
||||
crewai tool publish
|
||||
```
|
||||
|
||||
|
||||
@@ -12,17 +12,16 @@ O Enterprise Event Streaming permite que você receba atualizações em tempo re
|
||||
|
||||
Ao utilizar a API Kickoff, inclua um objeto `webhooks` em sua requisição, por exemplo:
|
||||
|
||||
# Exemplo de uso da API Kickoff com webhooks
|
||||
```json
|
||||
{
|
||||
"inputs": {"foo": "bar"},
|
||||
"webhooks": {
|
||||
"events": ["crew_kickoff_started", "llm_call_started"],
|
||||
"url": "https://seu.endpoint/webhook",
|
||||
"url": "https://your.endpoint/webhook",
|
||||
"realtime": false,
|
||||
"authentication": {
|
||||
"strategy": "bearer",
|
||||
"token": "meu-token-secreto"
|
||||
"token": "my-secret-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,20 +33,19 @@ Se `realtime` estiver definido como `true`, cada evento será entregue individua
|
||||
|
||||
Cada webhook envia uma lista de eventos:
|
||||
|
||||
# Exemplo de evento enviado pelo webhook
|
||||
```json
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"id": "id-do-evento",
|
||||
"execution_id": "id-da-execucao-do-crew",
|
||||
"id": "event-id",
|
||||
"execution_id": "crew-run-id",
|
||||
"timestamp": "2025-02-16T10:58:44.965Z",
|
||||
"type": "llm_call_started",
|
||||
"data": {
|
||||
"model": "gpt-4",
|
||||
"messages": [
|
||||
{"role": "system", "content": "Você é um assistente."},
|
||||
{"role": "user", "content": "Resuma este artigo."}
|
||||
{"role": "system", "content": "You are an assistant."},
|
||||
{"role": "user", "content": "Summarize this article."}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,11 @@ A CLI fornece a maneira mais rápida de implantar crews desenvolvidos localmente
|
||||
Primeiro, você precisa autenticar sua CLI com a plataforma CrewAI Enterprise:
|
||||
|
||||
```bash
|
||||
# Se já possui uma conta CrewAI Enterprise, ou deseja criar uma:
|
||||
# Se já possui uma conta CrewAI Enterprise
|
||||
crewai login
|
||||
|
||||
# Se vai criar uma nova conta
|
||||
crewai signup
|
||||
```
|
||||
|
||||
Ao executar qualquer um dos comandos, a CLI irá:
|
||||
|
||||
@@ -16,17 +16,17 @@ from crewai import CrewBase
|
||||
from crewai.project import before_kickoff
|
||||
|
||||
@CrewBase
|
||||
class MinhaEquipe:
|
||||
class MyCrew:
|
||||
@before_kickoff
|
||||
def preparar_dados(self, entradas):
|
||||
# Pré-processa ou modifica as entradas
|
||||
entradas['processado'] = True
|
||||
return entradas
|
||||
def prepare_data(self, inputs):
|
||||
# Preprocess or modify inputs
|
||||
inputs['processed'] = True
|
||||
return inputs
|
||||
|
||||
#...
|
||||
```
|
||||
|
||||
Neste exemplo, a função preparar_dados modifica as entradas adicionando um novo par chave-valor indicando que as entradas foram processadas.
|
||||
Neste exemplo, a função prepare_data modifica as entradas adicionando um novo par chave-valor indicando que as entradas foram processadas.
|
||||
|
||||
## Hook Depois do Kickoff
|
||||
|
||||
@@ -39,17 +39,17 @@ from crewai import CrewBase
|
||||
from crewai.project import after_kickoff
|
||||
|
||||
@CrewBase
|
||||
class MinhaEquipe:
|
||||
class MyCrew:
|
||||
@after_kickoff
|
||||
def registrar_resultados(self, resultado):
|
||||
# Registra ou modifica os resultados
|
||||
print("Execução da equipe concluída com resultado:", resultado)
|
||||
return resultado
|
||||
def log_results(self, result):
|
||||
# Log or modify the results
|
||||
print("Crew execution completed with result:", result)
|
||||
return result
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Na função `registrar_resultados`, os resultados da execução da crew são simplesmente impressos. Você pode estender isso para realizar operações mais complexas, como enviar notificações ou integrar com outros serviços.
|
||||
Na função `log_results`, os resultados da execução da crew são simplesmente impressos. Você pode estender isso para realizar operações mais complexas, como enviar notificações ou integrar com outros serviços.
|
||||
|
||||
## Utilizando Ambos os Hooks
|
||||
|
||||
|
||||
@@ -77,9 +77,9 @@ search_tool = SerperDevTool()
|
||||
|
||||
# Inicialize o agente com opções avançadas
|
||||
agent = Agent(
|
||||
role='Analista de Pesquisa',
|
||||
goal='Fornecer análises de mercado atualizadas',
|
||||
backstory='Um analista especialista com olhar atento para tendências de mercado.',
|
||||
role='Research Analyst',
|
||||
goal='Provide up-to-date market analysis',
|
||||
backstory='An expert analyst with a keen eye for market trends.',
|
||||
tools=[search_tool],
|
||||
memory=True, # Ativa memória
|
||||
verbose=True,
|
||||
@@ -98,9 +98,14 @@ eficiência dentro do ecossistema CrewAI. Se necessário, a delegação pode ser
|
||||
|
||||
```python Code
|
||||
agent = Agent(
|
||||
role='Redator de Conteúdo',
|
||||
goal='Escrever conteúdo envolvente sobre tendências de mercado',
|
||||
backstory='Um redator experiente com expertise em análise de mercado.',
|
||||
role='Content Writer',
|
||||
goal='Write engaging content on market trends',
|
||||
backstory='A seasoned writer with expertise in market analysis.',
|
||||
allow_delegation=True # Habilitando delegação
|
||||
)
|
||||
```
|
||||
```
|
||||
|
||||
## Conclusão
|
||||
|
||||
Personalizar agentes no CrewAI definindo seus papéis, objetivos, histórias e ferramentas, juntamente com opções avançadas como personalização de modelo de linguagem, memória, ajustes de performance e preferências de delegação,
|
||||
proporciona uma equipe de IA sofisticada e preparada para enfrentar desafios complexos.
|
||||
@@ -45,17 +45,17 @@ from crewai import Crew, Agent, Task
|
||||
|
||||
# Create an agent with code execution enabled
|
||||
coding_agent = Agent(
|
||||
role="Analista de Dados Python",
|
||||
goal="Analisar dados e fornecer insights usando Python",
|
||||
backstory="Você é um analista de dados experiente com fortes habilidades em Python.",
|
||||
role="Python Data Analyst",
|
||||
goal="Analyze data and provide insights using Python",
|
||||
backstory="You are an experienced data analyst with strong Python skills.",
|
||||
allow_code_execution=True
|
||||
)
|
||||
|
||||
# Create a task that requires code execution
|
||||
data_analysis_task = Task(
|
||||
description="Analise o conjunto de dados fornecido e calcule a idade média dos participantes. Idades: {ages}",
|
||||
description="Analyze the given dataset and calculate the average age of participants. Ages: {ages}",
|
||||
agent=coding_agent,
|
||||
expected_output="A idade média dos participantes."
|
||||
expected_output="The average age of the participants."
|
||||
)
|
||||
|
||||
# Create a crew and add the task
|
||||
@@ -83,23 +83,23 @@ from crewai import Crew, Agent, Task
|
||||
|
||||
# Create an agent with code execution enabled
|
||||
coding_agent = Agent(
|
||||
role="Analista de Dados Python",
|
||||
goal="Analisar dados e fornecer insights usando Python",
|
||||
backstory="Você é um analista de dados experiente com fortes habilidades em Python.",
|
||||
role="Python Data Analyst",
|
||||
goal="Analyze data and provide insights using Python",
|
||||
backstory="You are an experienced data analyst with strong Python skills.",
|
||||
allow_code_execution=True
|
||||
)
|
||||
|
||||
# Create tasks that require code execution
|
||||
task_1 = Task(
|
||||
description="Analise o primeiro conjunto de dados e calcule a idade média dos participantes. Idades: {ages}",
|
||||
description="Analyze the first dataset and calculate the average age of participants. Ages: {ages}",
|
||||
agent=coding_agent,
|
||||
expected_output="A idade média dos participantes."
|
||||
expected_output="The average age of the participants."
|
||||
)
|
||||
|
||||
task_2 = Task(
|
||||
description="Analise o segundo conjunto de dados e calcule a idade média dos participantes. Idades: {ages}",
|
||||
description="Analyze the second dataset and calculate the average age of participants. Ages: {ages}",
|
||||
agent=coding_agent,
|
||||
expected_output="A idade média dos participantes."
|
||||
expected_output="The average age of the participants."
|
||||
)
|
||||
|
||||
# Create two crews and add tasks
|
||||
|
||||
@@ -43,11 +43,11 @@ try:
|
||||
with MCPServerAdapter(server_params_list) as aggregated_tools:
|
||||
print(f"Available aggregated tools: {[tool.name for tool in aggregated_tools]}")
|
||||
|
||||
agente_multiservidor = Agent(
|
||||
role="Assistente Versátil",
|
||||
goal="Utilizar ferramentas de servidores MCP locais Stdio, remotos SSE e remotos HTTP.",
|
||||
backstory="Um agente de IA capaz de aproveitar um conjunto diversificado de ferramentas de múltiplas fontes.",
|
||||
tools=aggregated_tools, # Todas as ferramentas estão disponíveis aqui
|
||||
multi_server_agent = Agent(
|
||||
role="Versatile Assistant",
|
||||
goal="Utilize tools from local Stdio, remote SSE, and remote HTTP MCP servers.",
|
||||
backstory="An AI agent capable of leveraging a diverse set of tools from multiple sources.",
|
||||
tools=aggregated_tools, # All tools are available here
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -73,10 +73,10 @@ server_params = {
|
||||
with MCPServerAdapter(server_params) as mcp_tools:
|
||||
print(f"Available tools: {[tool.name for tool in mcp_tools]}")
|
||||
|
||||
meu_agente = Agent(
|
||||
role="Usuário de Ferramentas MCP",
|
||||
goal="Utilizar ferramentas de um servidor MCP.",
|
||||
backstory="Posso conectar a servidores MCP e usar suas ferramentas.",
|
||||
my_agent = Agent(
|
||||
role="MCP Tool User",
|
||||
goal="Utilize tools from an MCP server.",
|
||||
backstory="I can connect to MCP servers and use their tools.",
|
||||
tools=mcp_tools, # Passe as ferramentas carregadas para o seu agente
|
||||
reasoning=True,
|
||||
verbose=True
|
||||
@@ -91,10 +91,10 @@ Este padrão geral mostra como integrar ferramentas. Para exemplos específicos
|
||||
with MCPServerAdapter(server_params) as mcp_tools:
|
||||
print(f"Available tools: {[tool.name for tool in mcp_tools]}")
|
||||
|
||||
meu_agente = Agent(
|
||||
role="Usuário de Ferramentas MCP",
|
||||
goal="Utilizar ferramentas de um servidor MCP.",
|
||||
backstory="Posso conectar a servidores MCP e usar suas ferramentas.",
|
||||
my_agent = Agent(
|
||||
role="MCP Tool User",
|
||||
goal="Utilize tools from an MCP server.",
|
||||
backstory="I can connect to MCP servers and use their tools.",
|
||||
tools=mcp_tools["tool_name"], # Passe as ferramentas filtradas para o seu agente
|
||||
reasoning=True,
|
||||
verbose=True
|
||||
|
||||
@@ -37,24 +37,24 @@ try:
|
||||
print(f"Available tools from SSE MCP server: {[tool.name for tool in tools]}")
|
||||
|
||||
# Example: Using a tool from the SSE MCP server
|
||||
agente_sse = Agent(
|
||||
role="Usuário de Serviço Remoto",
|
||||
goal="Utilizar uma ferramenta fornecida por um servidor MCP remoto via SSE.",
|
||||
backstory="Um agente de IA que conecta a serviços externos via SSE.",
|
||||
sse_agent = Agent(
|
||||
role="Remote Service User",
|
||||
goal="Utilize a tool provided by a remote SSE MCP server.",
|
||||
backstory="An AI agent that connects to external services via SSE.",
|
||||
tools=tools,
|
||||
reasoning=True,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
sse_task = Task(
|
||||
description="Buscar atualizações em tempo real das ações 'AAPL' usando uma ferramenta SSE.",
|
||||
expected_output="O preço mais recente da ação AAPL.",
|
||||
agent=agente_sse,
|
||||
description="Fetch real-time stock updates for 'AAPL' using an SSE tool.",
|
||||
expected_output="The latest stock price for AAPL.",
|
||||
agent=sse_agent,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
sse_crew = Crew(
|
||||
agents=[agente_sse],
|
||||
agents=[sse_agent],
|
||||
tasks=[sse_task],
|
||||
verbose=True,
|
||||
process=Process.sequential
|
||||
@@ -101,16 +101,16 @@ try:
|
||||
print(f"Available tools (manual SSE): {[tool.name for tool in tools]}")
|
||||
|
||||
manual_sse_agent = Agent(
|
||||
role="Analista Remoto de Dados",
|
||||
goal="Analisar dados obtidos de um servidor MCP remoto SSE usando gerenciamento manual de conexão.",
|
||||
backstory="Um agente de IA especializado em gerenciar conexões SSE explicitamente.",
|
||||
role="Remote Data Analyst",
|
||||
goal="Analyze data fetched from a remote SSE MCP server using manual connection management.",
|
||||
backstory="An AI skilled in handling SSE connections explicitly.",
|
||||
tools=tools,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
analysis_task = Task(
|
||||
description="Buscar e analisar as tendências mais recentes de atividade de usuários do servidor SSE.",
|
||||
expected_output="Um relatório resumido das tendências de atividade dos usuários.",
|
||||
description="Fetch and analyze the latest user activity trends from the SSE server.",
|
||||
expected_output="A summary report of user activity trends.",
|
||||
agent=manual_sse_agent
|
||||
)
|
||||
|
||||
|
||||
@@ -38,24 +38,24 @@ with MCPServerAdapter(server_params) as tools:
|
||||
print(f"Available tools from Stdio MCP server: {[tool.name for tool in tools]}")
|
||||
|
||||
# Exemplo: Usando as ferramentas do servidor MCP Stdio em um Agente CrewAI
|
||||
pesquisador_local = Agent(
|
||||
role="Processador Local de Dados",
|
||||
goal="Processar dados usando uma ferramenta local baseada em Stdio.",
|
||||
backstory="Uma IA que utiliza scripts locais via MCP para tarefas especializadas.",
|
||||
research_agent = Agent(
|
||||
role="Local Data Processor",
|
||||
goal="Process data using a local Stdio-based tool.",
|
||||
backstory="An AI that leverages local scripts via MCP for specialized tasks.",
|
||||
tools=tools,
|
||||
reasoning=True,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
processing_task = Task(
|
||||
description="Processar o arquivo de dados de entrada 'data.txt' e resumir seu conteúdo.",
|
||||
expected_output="Um resumo dos dados processados.",
|
||||
agent=pesquisador_local,
|
||||
description="Process the input data file 'data.txt' and summarize its contents.",
|
||||
expected_output="A summary of the processed data.",
|
||||
agent=research_agent,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
data_crew = Crew(
|
||||
agents=[pesquisador_local],
|
||||
agents=[research_agent],
|
||||
tasks=[processing_task],
|
||||
verbose=True,
|
||||
process=Process.sequential
|
||||
@@ -95,16 +95,16 @@ try:
|
||||
|
||||
# Exemplo: Usando as ferramentas com sua configuração de Agent, Task, Crew
|
||||
manual_agent = Agent(
|
||||
role="Executor Local de Tarefas",
|
||||
goal="Executar uma tarefa local específica usando uma ferramenta Stdio gerenciada manualmente.",
|
||||
backstory="Uma IA proficiente em controlar processos locais via MCP.",
|
||||
role="Local Task Executor",
|
||||
goal="Execute a specific local task using a manually managed Stdio tool.",
|
||||
backstory="An AI proficient in controlling local processes via MCP.",
|
||||
tools=tools,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
manual_task = Task(
|
||||
description="Executar o comando 'perform_analysis' via ferramenta Stdio.",
|
||||
expected_output="Resultados da análise.",
|
||||
description="Execute the 'perform_analysis' command via the Stdio tool.",
|
||||
expected_output="Results of the analysis.",
|
||||
agent=manual_agent
|
||||
)
|
||||
|
||||
|
||||
@@ -35,22 +35,22 @@ try:
|
||||
with MCPServerAdapter(server_params) as tools:
|
||||
print(f"Available tools from Streamable HTTP MCP server: {[tool.name for tool in tools]}")
|
||||
|
||||
agente_http = Agent(
|
||||
role="Integrador de Serviços HTTP",
|
||||
goal="Utilizar ferramentas de um servidor MCP remoto via Streamable HTTP.",
|
||||
backstory="Um agente de IA especializado em interagir com serviços web complexos.",
|
||||
http_agent = Agent(
|
||||
role="HTTP Service Integrator",
|
||||
goal="Utilize tools from a remote MCP server via Streamable HTTP.",
|
||||
backstory="An AI agent adept at interacting with complex web services.",
|
||||
tools=tools,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
http_task = Task(
|
||||
description="Realizar uma consulta de dados complexa usando uma ferramenta do servidor Streamable HTTP.",
|
||||
expected_output="O resultado da consulta de dados complexa.",
|
||||
agent=agente_http,
|
||||
description="Perform a complex data query using a tool from the Streamable HTTP server.",
|
||||
expected_output="The result of the complex data query.",
|
||||
agent=http_agent,
|
||||
)
|
||||
|
||||
http_crew = Crew(
|
||||
agents=[agente_http],
|
||||
agents=[http_agent],
|
||||
tasks=[http_task],
|
||||
verbose=True,
|
||||
process=Process.sequential
|
||||
@@ -91,16 +91,16 @@ try:
|
||||
print(f"Available tools (manual Streamable HTTP): {[tool.name for tool in tools]}")
|
||||
|
||||
manual_http_agent = Agent(
|
||||
role="Usuário Avançado de Serviços Web",
|
||||
goal="Interagir com um servidor MCP usando conexões HTTP Streamable gerenciadas manualmente.",
|
||||
backstory="Um especialista em IA em ajustar integrações baseadas em HTTP.",
|
||||
role="Advanced Web Service User",
|
||||
goal="Interact with an MCP server using manually managed Streamable HTTP connections.",
|
||||
backstory="An AI specialist in fine-tuning HTTP-based service integrations.",
|
||||
tools=tools,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
data_processing_task = Task(
|
||||
description="Enviar dados para processamento e recuperar resultados via Streamable HTTP.",
|
||||
expected_output="Dados processados ou confirmação.",
|
||||
description="Submit data for processing and retrieve results via Streamable HTTP.",
|
||||
expected_output="Processed data or confirmation.",
|
||||
agent=manual_http_agent
|
||||
)
|
||||
|
||||
|
||||
@@ -78,40 +78,47 @@ CrewAIInstrumentor().instrument(skip_dep_check=True, tracer_provider=tracer_prov
|
||||
search_tool = SerperDevTool()
|
||||
|
||||
# Defina seus agentes com papéis e objetivos
|
||||
pesquisador = Agent(
|
||||
role="Analista Sênior de Pesquisa",
|
||||
goal="Descobrir os avanços mais recentes em IA e ciência de dados",
|
||||
backstory="""
|
||||
Você trabalha em um importante think tank de tecnologia. Sua especialidade é identificar tendências emergentes. Você tem habilidade para dissecar dados complexos e apresentar insights acionáveis.
|
||||
""",
|
||||
researcher = Agent(
|
||||
role="Senior Research Analyst",
|
||||
goal="Uncover cutting-edge developments in AI and data science",
|
||||
backstory="""You work at a leading tech think tank.
|
||||
Your expertise lies in identifying emerging trends.
|
||||
You have a knack for dissecting complex data and presenting actionable insights.""",
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
# You can pass an optional llm attribute specifying what model you wanna use.
|
||||
# llm=ChatOpenAI(model_name="gpt-3.5", temperature=0.7),
|
||||
tools=[search_tool],
|
||||
)
|
||||
writer = Agent(
|
||||
role="Estrategista de Conteúdo Técnico",
|
||||
goal="Criar conteúdo envolvente sobre avanços tecnológicos",
|
||||
backstory="Você é um Estrategista de Conteúdo renomado, conhecido por seus artigos perspicazes e envolventes. Você transforma conceitos complexos em narrativas atraentes.",
|
||||
role="Tech Content Strategist",
|
||||
goal="Craft compelling content on tech advancements",
|
||||
backstory="""You are a renowned Content Strategist, known for your insightful and engaging articles.
|
||||
You transform complex concepts into compelling narratives.""",
|
||||
verbose=True,
|
||||
allow_delegation=True,
|
||||
)
|
||||
|
||||
# Crie tarefas para seus agentes
|
||||
task1 = Task(
|
||||
description="Realize uma análise abrangente dos avanços mais recentes em IA em 2024. Identifique tendências-chave, tecnologias inovadoras e impactos potenciais na indústria.",
|
||||
expected_output="Relatório analítico completo em tópicos",
|
||||
agent=pesquisador,
|
||||
description="""Conduct a comprehensive analysis of the latest advancements in AI in 2024.
|
||||
Identify key trends, breakthrough technologies, and potential industry impacts.""",
|
||||
expected_output="Full analysis report in bullet points",
|
||||
agent=researcher,
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
description="Utilizando os insights fornecidos, desenvolva um blog envolvente destacando os avanços mais significativos em IA. O post deve ser informativo e acessível, voltado para um público técnico. Dê um tom interessante, evite palavras complexas para não soar como IA.",
|
||||
expected_output="Post de blog completo com pelo menos 4 parágrafos",
|
||||
description="""Using the insights provided, develop an engaging blog
|
||||
post that highlights the most significant AI advancements.
|
||||
Your post should be informative yet accessible, catering to a tech-savvy audience.
|
||||
Make it sound cool, avoid complex words so it doesn't sound like AI.""",
|
||||
expected_output="Full blog post of at least 4 paragraphs",
|
||||
agent=writer,
|
||||
)
|
||||
|
||||
# Instancie seu crew com um processo sequencial
|
||||
crew = Crew(
|
||||
agents=[pesquisador, writer], tasks=[task1, task2], verbose=1, process=Process.sequential
|
||||
agents=[researcher, writer], tasks=[task1, task2], verbose=1, process=Process.sequential
|
||||
)
|
||||
|
||||
# Coloque seu crew para trabalhar!
|
||||
|
||||
@@ -76,20 +76,20 @@ from crewai_tools import (
|
||||
|
||||
web_rag_tool = WebsiteSearchTool()
|
||||
|
||||
escritor = Agent(
|
||||
role="Escritor",
|
||||
goal="Você torna a matemática envolvente e compreensível para crianças pequenas através de poesias",
|
||||
backstory="Você é especialista em escrever haicais mas não sabe nada de matemática.",
|
||||
tools=[web_rag_tool],
|
||||
)
|
||||
writer = Agent(
|
||||
role="Writer",
|
||||
goal="Você torna a matemática envolvente e compreensível para crianças pequenas através de poesias",
|
||||
backstory="Você é especialista em escrever haicais mas não sabe nada de matemática.",
|
||||
tools=[web_rag_tool],
|
||||
)
|
||||
|
||||
tarefa = Task(description=("O que é {multiplicação}?"),
|
||||
expected_output=("Componha um haicai que inclua a resposta."),
|
||||
agent=escritor)
|
||||
task = Task(description=("O que é {multiplicação}?"),
|
||||
expected_output=("Componha um haicai que inclua a resposta."),
|
||||
agent=writer)
|
||||
|
||||
equipe = Crew(
|
||||
agents=[escritor],
|
||||
tasks=[tarefa],
|
||||
crew = Crew(
|
||||
agents=[writer],
|
||||
tasks=[task],
|
||||
share_crew=False
|
||||
)
|
||||
```
|
||||
|
||||
@@ -35,7 +35,7 @@ Essa integração permite o registro de hiperparâmetros, o monitoramento de reg
|
||||
|
||||
```python
|
||||
from langtrace_python_sdk import langtrace
|
||||
langtrace.init(api_key='<SUA_CHAVE_LANGTRACE>')
|
||||
langtrace.init(api_key='<LANGTRACE_API_KEY>')
|
||||
|
||||
# Agora importe os módulos do CrewAI
|
||||
from crewai import Agent, Task, Crew
|
||||
|
||||
@@ -73,24 +73,26 @@ instrument_crewai(logger)
|
||||
### 4. Crie e execute sua aplicação CrewAI normalmente
|
||||
|
||||
```python
|
||||
pesquisador = Agent(
|
||||
role='Pesquisador Sênior',
|
||||
goal='Descobrir os avanços mais recentes em IA',
|
||||
backstory="Você é um pesquisador especialista em um think tank de tecnologia...",
|
||||
|
||||
# Crie seu agente
|
||||
researcher = Agent(
|
||||
role='Senior Research Analyst',
|
||||
goal='Uncover cutting-edge developments in AI',
|
||||
backstory="You are an expert researcher at a tech think tank...",
|
||||
verbose=True,
|
||||
llm=llm
|
||||
)
|
||||
|
||||
# Defina a tarefa
|
||||
research_task = Task(
|
||||
description="Pesquise os avanços mais recentes em IA...",
|
||||
description="Research the latest AI advancements...",
|
||||
expected_output="",
|
||||
agent=pesquisador
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# Configure e execute a crew
|
||||
crew = Crew(
|
||||
agents=[pesquisador],
|
||||
agents=[researcher],
|
||||
tasks=[research_task],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
@@ -70,19 +70,22 @@ O tracing fornece uma forma de registrar os inputs, outputs e metadados associad
|
||||
|
||||
class TripAgents:
|
||||
def city_selection_agent(self):
|
||||
especialista_cidades = Agent(
|
||||
role="Especialista em Seleção de Cidades",
|
||||
goal="Selecionar a melhor cidade com base no clima, estação e preços",
|
||||
backstory="Especialista em analisar dados de viagem para escolher destinos ideais",
|
||||
tools=[search_tool],
|
||||
return Agent(
|
||||
role="City Selection Expert",
|
||||
goal="Select the best city based on weather, season, and prices",
|
||||
backstory="An expert in analyzing travel data to pick ideal destinations",
|
||||
tools=[
|
||||
search_tool,
|
||||
],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
def local_expert(self):
|
||||
especialista_local = Agent(
|
||||
role="Especialista Local nesta cidade",
|
||||
goal="Fornecer as MELHORES informações sobre a cidade selecionada",
|
||||
backstory="Um guia local experiente com amplo conhecimento sobre a cidade, suas atrações e costumes",
|
||||
return Agent(
|
||||
role="Local Expert at this city",
|
||||
goal="Provide the BEST insights about the selected city",
|
||||
backstory="""A knowledgeable local guide with extensive information
|
||||
about the city, it's attractions and customs""",
|
||||
tools=[search_tool],
|
||||
verbose=True,
|
||||
)
|
||||
@@ -93,36 +96,53 @@ O tracing fornece uma forma de registrar os inputs, outputs e metadados associad
|
||||
return Task(
|
||||
description=dedent(
|
||||
f"""
|
||||
Analise e selecione a melhor cidade para a viagem com base em critérios específicos como padrões climáticos, eventos sazonais e custos de viagem. Esta tarefa envolve comparar várias cidades, considerando fatores como condições climáticas atuais, eventos culturais ou sazonais e despesas gerais de viagem.
|
||||
Sua resposta final deve ser um relatório detalhado sobre a cidade escolhida e tudo o que você descobriu sobre ela, incluindo custos reais de voo, previsão do tempo e atrações.
|
||||
Analyze and select the best city for the trip based
|
||||
on specific criteria such as weather patterns, seasonal
|
||||
events, and travel costs. This task involves comparing
|
||||
multiple cities, considering factors like current weather
|
||||
conditions, upcoming cultural or seasonal events, and
|
||||
overall travel expenses.
|
||||
Your final answer must be a detailed
|
||||
report on the chosen city, and everything you found out
|
||||
about it, including the actual flight costs, weather
|
||||
forecast and attractions.
|
||||
|
||||
Saindo de: {origin}
|
||||
Opções de cidades: {cities}
|
||||
Data da viagem: {range}
|
||||
Interesses do viajante: {interests}
|
||||
Traveling from: {origin}
|
||||
City Options: {cities}
|
||||
Trip Date: {range}
|
||||
Traveler Interests: {interests}
|
||||
"""
|
||||
),
|
||||
agent=agent,
|
||||
expected_output="Relatório detalhado sobre a cidade escolhida incluindo custos de voo, previsão do tempo e atrações",
|
||||
expected_output="Detailed report on the chosen city including flight costs, weather forecast, and attractions",
|
||||
)
|
||||
|
||||
def gather_task(self, agent, origin, interests, range):
|
||||
return Task(
|
||||
description=dedent(
|
||||
f"""
|
||||
Como especialista local nesta cidade, você deve compilar um guia aprofundado para alguém que está viajando para lá e quer ter a MELHOR viagem possível!
|
||||
Reúna informações sobre principais atrações, costumes locais, eventos especiais e recomendações de atividades diárias.
|
||||
Encontre os melhores lugares para ir, aqueles que só um local conhece.
|
||||
Este guia deve fornecer uma visão abrangente do que a cidade tem a oferecer, incluindo joias escondidas, pontos culturais, marcos imperdíveis, previsão do tempo e custos gerais.
|
||||
A resposta final deve ser um guia completo da cidade, rico em insights culturais e dicas práticas, adaptado para aprimorar a experiência de viagem.
|
||||
As a local expert on this city you must compile an
|
||||
in-depth guide for someone traveling there and wanting
|
||||
to have THE BEST trip ever!
|
||||
Gather information about key attractions, local customs,
|
||||
special events, and daily activity recommendations.
|
||||
Find the best spots to go to, the kind of place only a
|
||||
local would know.
|
||||
This guide should provide a thorough overview of what
|
||||
the city has to offer, including hidden gems, cultural
|
||||
hotspots, must-visit landmarks, weather forecasts, and
|
||||
high level costs.
|
||||
The final answer must be a comprehensive city guide,
|
||||
rich in cultural insights and practical tips,
|
||||
tailored to enhance the travel experience.
|
||||
|
||||
Data da viagem: {range}
|
||||
Saindo de: {origin}
|
||||
Interesses do viajante: {interests}
|
||||
Trip Date: {range}
|
||||
Traveling from: {origin}
|
||||
Traveler Interests: {interests}
|
||||
"""
|
||||
),
|
||||
agent=agent,
|
||||
expected_output="Guia completo da cidade incluindo joias escondidas, pontos culturais e dicas práticas",
|
||||
expected_output="Comprehensive city guide including hidden gems, cultural hotspots, and practical travel tips",
|
||||
)
|
||||
|
||||
|
||||
@@ -169,7 +189,7 @@ O tracing fornece uma forma de registrar os inputs, outputs e metadados associad
|
||||
trip_crew = TripCrew("California", "Tokyo", "Dec 12 - Dec 20", "sports")
|
||||
result = trip_crew.run()
|
||||
|
||||
print("Resultado da equipe:", result)
|
||||
print(result)
|
||||
```
|
||||
Consulte a [Documentação de Tracing do MLflow](https://mlflow.org/docs/latest/llms/tracing/index.html) para mais configurações e casos de uso.
|
||||
</Step>
|
||||
|
||||
@@ -69,10 +69,10 @@ Essa configuração permite acompanhar hiperparâmetros e monitorar problemas de
|
||||
|
||||
openlit.init(disable_metrics=True)
|
||||
# Definir seus agentes
|
||||
pesquisador = Agent(
|
||||
role="Pesquisador",
|
||||
goal="Realizar pesquisas e análises aprofundadas sobre IA e agentes de IA",
|
||||
backstory="Você é um pesquisador especialista em tecnologia, engenharia de software, IA e startups. Trabalha como freelancer e está atualmente pesquisando para um novo cliente.",
|
||||
researcher = Agent(
|
||||
role="Researcher",
|
||||
goal="Conduct thorough research and analysis on AI and AI agents",
|
||||
backstory="You're an expert researcher, specialized in technology, software engineering, AI, and startups. You work as a freelancer and are currently researching for a new client.",
|
||||
allow_delegation=False,
|
||||
llm='command-r'
|
||||
)
|
||||
@@ -80,24 +80,24 @@ Essa configuração permite acompanhar hiperparâmetros e monitorar problemas de
|
||||
|
||||
# Definir sua task
|
||||
task = Task(
|
||||
description="Gere uma lista com 5 ideias interessantes para um artigo e escreva um parágrafo cativante para cada ideia, mostrando o potencial de um artigo completo sobre o tema. Retorne a lista de ideias com seus parágrafos e suas anotações.",
|
||||
expected_output="5 tópicos, cada um com um parágrafo e notas complementares.",
|
||||
description="Generate a list of 5 interesting ideas for an article, then write one captivating paragraph for each idea that showcases the potential of a full article on this topic. Return the list of ideas with their paragraphs and your notes.",
|
||||
expected_output="5 bullet points, each with a paragraph and accompanying notes.",
|
||||
)
|
||||
|
||||
# Definir o agente gerente
|
||||
gerente = Agent(
|
||||
role="Gerente de Projeto",
|
||||
goal="Gerenciar eficientemente a equipe e garantir a conclusão de tarefas de alta qualidade",
|
||||
backstory="Você é um gerente de projetos experiente, habilidoso em supervisionar projetos complexos e guiar equipes para o sucesso. Sua função é coordenar os esforços dos membros da equipe, garantindo que cada tarefa seja concluída no prazo e com o mais alto padrão.",
|
||||
manager = Agent(
|
||||
role="Project Manager",
|
||||
goal="Efficiently manage the crew and ensure high-quality task completion",
|
||||
backstory="You're an experienced project manager, skilled in overseeing complex projects and guiding teams to success. Your role is to coordinate the efforts of the crew members, ensuring that each task is completed on time and to the highest standard.",
|
||||
allow_delegation=True,
|
||||
llm='command-r'
|
||||
)
|
||||
|
||||
# Instanciar sua crew com um manager personalizado
|
||||
crew = Crew(
|
||||
agents=[pesquisador],
|
||||
agents=[researcher],
|
||||
tasks=[task],
|
||||
manager_agent=gerente,
|
||||
manager_agent=manager,
|
||||
process=Process.hierarchical,
|
||||
)
|
||||
|
||||
@@ -132,18 +132,18 @@ Essa configuração permite acompanhar hiperparâmetros e monitorar problemas de
|
||||
|
||||
# Criar um agente com execução de código habilitada
|
||||
coding_agent = Agent(
|
||||
role="Analista de Dados Python",
|
||||
goal="Analisar dados e fornecer insights usando Python",
|
||||
backstory="Você é um analista de dados experiente com fortes habilidades em Python.",
|
||||
role="Python Data Analyst",
|
||||
goal="Analyze data and provide insights using Python",
|
||||
backstory="You are an experienced data analyst with strong Python skills.",
|
||||
allow_code_execution=True,
|
||||
llm="command-r"
|
||||
)
|
||||
|
||||
# Criar uma task que exige execução de código
|
||||
data_analysis_task = Task(
|
||||
description="Analise o conjunto de dados fornecido e calcule a idade média dos participantes. Idades: {ages}",
|
||||
description="Analyze the given dataset and calculate the average age of participants. Ages: {ages}",
|
||||
agent=coding_agent,
|
||||
expected_output="5 tópicos, cada um com um parágrafo e notas complementares.",
|
||||
expected_output="5 bullet points, each with a paragraph and accompanying notes.",
|
||||
)
|
||||
|
||||
# Criar uma crew e adicionar a task
|
||||
|
||||
@@ -58,43 +58,43 @@ Neste guia, utilizaremos o exemplo de início rápido da CrewAI.
|
||||
from crewai import Agent, Crew, Task, Process
|
||||
|
||||
|
||||
class NomeDaEquipe:
|
||||
def agente_um(self) -> Agent:
|
||||
class YourCrewName:
|
||||
def agent_one(self) -> Agent:
|
||||
return Agent(
|
||||
role="Analista de Dados",
|
||||
goal="Analisar tendências de dados no mercado",
|
||||
backstory="Analista de dados experiente com formação em economia",
|
||||
role="Data Analyst",
|
||||
goal="Analyze data trends in the market",
|
||||
backstory="An experienced data analyst with a background in economics",
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
def agente_dois(self) -> Agent:
|
||||
def agent_two(self) -> Agent:
|
||||
return Agent(
|
||||
role="Pesquisador de Mercado",
|
||||
goal="Coletar informações sobre a dinâmica do mercado",
|
||||
backstory="Pesquisador dedicado com olhar atento para detalhes",
|
||||
role="Market Researcher",
|
||||
goal="Gather information on market dynamics",
|
||||
backstory="A diligent researcher with a keen eye for detail",
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
def tarefa_um(self) -> Task:
|
||||
def task_one(self) -> Task:
|
||||
return Task(
|
||||
name="Tarefa de Coleta de Dados",
|
||||
description="Coletar dados recentes do mercado e identificar tendências.",
|
||||
expected_output="Um relatório resumindo as principais tendências do mercado.",
|
||||
agent=self.agente_um(),
|
||||
name="Collect Data Task",
|
||||
description="Collect recent market data and identify trends.",
|
||||
expected_output="A report summarizing key trends in the market.",
|
||||
agent=self.agent_one(),
|
||||
)
|
||||
|
||||
def tarefa_dois(self) -> Task:
|
||||
def task_two(self) -> Task:
|
||||
return Task(
|
||||
name="Tarefa de Pesquisa de Mercado",
|
||||
description="Pesquisar fatores que afetam a dinâmica do mercado.",
|
||||
expected_output="Uma análise dos fatores que influenciam o mercado.",
|
||||
agent=self.agente_dois(),
|
||||
name="Market Research Task",
|
||||
description="Research factors affecting market dynamics.",
|
||||
expected_output="An analysis of factors influencing the market.",
|
||||
agent=self.agent_two(),
|
||||
)
|
||||
|
||||
def equipe(self) -> Crew:
|
||||
def crew(self) -> Crew:
|
||||
return Crew(
|
||||
agents=[self.agente_um(), self.agente_dois()],
|
||||
tasks=[self.tarefa_um(), self.tarefa_dois()],
|
||||
agents=[self.agent_one(), self.agent_two()],
|
||||
tasks=[self.task_one(), self.task_two()],
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
)
|
||||
@@ -108,7 +108,7 @@ Neste guia, utilizaremos o exemplo de início rápido da CrewAI.
|
||||
|
||||
track_crewai(project_name="crewai-integration-demo")
|
||||
|
||||
my_crew = NomeDaEquipe().equipe()
|
||||
my_crew = YourCrewName().crew()
|
||||
result = my_crew.kickoff()
|
||||
|
||||
print(result)
|
||||
|
||||
@@ -64,17 +64,17 @@ patronus_eval_tool = PatronusEvalTool()
|
||||
|
||||
# Define an agent that uses the tool
|
||||
coding_agent = Agent(
|
||||
role="Agente de Programação",
|
||||
goal="Gerar código de alta qualidade e verificar se a saída é código",
|
||||
backstory="Um programador experiente que pode gerar código Python de alta qualidade.",
|
||||
role="Coding Agent",
|
||||
goal="Generate high quality code and verify that the output is code",
|
||||
backstory="An experienced coder who can generate high quality python code.",
|
||||
tools=[patronus_eval_tool],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
# Example task to generate and evaluate code
|
||||
generate_code_task = Task(
|
||||
description="Crie um programa simples para gerar os N primeiros números da sequência de Fibonacci. Selecione o avaliador e os critérios mais apropriados para avaliar sua saída.",
|
||||
expected_output="Programa que gera os N primeiros números da sequência de Fibonacci.",
|
||||
description="Create a simple program to generate the first N numbers in the Fibonacci sequence. Select the most appropriate evaluator and criteria for evaluating your output.",
|
||||
expected_output="Program that generates the first N numbers in the Fibonacci sequence.",
|
||||
agent=coding_agent,
|
||||
)
|
||||
|
||||
@@ -98,17 +98,17 @@ patronus_eval_tool = PatronusPredefinedCriteriaEvalTool(
|
||||
|
||||
# Define an agent that uses the tool
|
||||
coding_agent = Agent(
|
||||
role="Agente de Programação",
|
||||
goal="Gerar código de alta qualidade",
|
||||
backstory="Um programador experiente que pode gerar código Python de alta qualidade.",
|
||||
role="Coding Agent",
|
||||
goal="Generate high quality code",
|
||||
backstory="An experienced coder who can generate high quality python code.",
|
||||
tools=[patronus_eval_tool],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
# Example task to generate code
|
||||
generate_code_task = Task(
|
||||
description="Crie um programa simples para gerar os N primeiros números da sequência de Fibonacci.",
|
||||
expected_output="Programa que gera os N primeiros números da sequência de Fibonacci.",
|
||||
description="Create a simple program to generate the first N numbers in the Fibonacci sequence.",
|
||||
expected_output="Program that generates the first N numbers in the Fibonacci sequence.",
|
||||
agent=coding_agent,
|
||||
)
|
||||
|
||||
@@ -149,17 +149,17 @@ patronus_eval_tool = PatronusLocalEvaluatorTool(
|
||||
|
||||
# Define an agent that uses the tool
|
||||
coding_agent = Agent(
|
||||
role="Agente de Programação",
|
||||
goal="Gerar código de alta qualidade",
|
||||
backstory="Um programador experiente que pode gerar código Python de alta qualidade.",
|
||||
role="Coding Agent",
|
||||
goal="Generate high quality code",
|
||||
backstory="An experienced coder who can generate high quality python code.",
|
||||
tools=[patronus_eval_tool],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
# Example task to generate code
|
||||
generate_code_task = Task(
|
||||
description="Crie um programa simples para gerar os N primeiros números da sequência de Fibonacci.",
|
||||
expected_output="Programa que gera os N primeiros números da sequência de Fibonacci.",
|
||||
description="Create a simple program to generate the first N numbers in the Fibonacci sequence.",
|
||||
expected_output="Program that generates the first N numbers in the Fibonacci sequence.",
|
||||
agent=coding_agent,
|
||||
)
|
||||
|
||||
|
||||
@@ -50,48 +50,48 @@ O Weave captura automaticamente rastreamentos (traces) de suas aplicações Crew
|
||||
llm = LLM(model="gpt-4o", temperature=0)
|
||||
|
||||
# Crie os agentes
|
||||
pesquisador = Agent(
|
||||
role='Analista de Pesquisa',
|
||||
goal='Encontrar e analisar as melhores oportunidades de investimento',
|
||||
backstory='Especialista em análise financeira e pesquisa de mercado',
|
||||
researcher = Agent(
|
||||
role='Research Analyst',
|
||||
goal='Find and analyze the best investment opportunities',
|
||||
backstory='Expert in financial analysis and market research',
|
||||
llm=llm,
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
)
|
||||
|
||||
redator = Agent(
|
||||
role='Redator de Relatórios',
|
||||
goal='Escrever relatórios de investimento claros e concisos',
|
||||
backstory='Experiente na criação de relatórios financeiros detalhados',
|
||||
writer = Agent(
|
||||
role='Report Writer',
|
||||
goal='Write clear and concise investment reports',
|
||||
backstory='Experienced in creating detailed financial reports',
|
||||
llm=llm,
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
)
|
||||
|
||||
# Crie as tarefas
|
||||
pesquisa = Task(
|
||||
description='Pesquisa aprofundada sobre o {tema}',
|
||||
expected_output='Dados de mercado abrangentes incluindo principais players, tamanho de mercado e tendências de crescimento.',
|
||||
agent=pesquisador
|
||||
research_task = Task(
|
||||
description='Deep research on the {topic}',
|
||||
expected_output='Comprehensive market data including key players, market size, and growth trends.',
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
redacao = Task(
|
||||
description='Escreva um relatório detalhado com base na pesquisa',
|
||||
expected_output='O relatório deve ser fácil de ler e entender. Use tópicos quando aplicável.',
|
||||
agent=redator
|
||||
writing_task = Task(
|
||||
description='Write a detailed report based on the research',
|
||||
expected_output='The report should be easy to read and understand. Use bullet points where applicable.',
|
||||
agent=writer
|
||||
)
|
||||
|
||||
# Crie o crew
|
||||
equipe = Crew(
|
||||
agents=[pesquisador, redator],
|
||||
tasks=[pesquisa, redacao],
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
tasks=[research_task, writing_task],
|
||||
verbose=True,
|
||||
process=Process.sequential,
|
||||
)
|
||||
|
||||
# Execute o crew
|
||||
resultado = equipe.kickoff(inputs={"tema": "IA em ciência dos materiais"})
|
||||
print(resultado)
|
||||
result = crew.kickoff(inputs={"topic": "AI in material science"})
|
||||
print(result)
|
||||
```
|
||||
</Step>
|
||||
<Step title="Visualize rastreamentos no Weave">
|
||||
|
||||
@@ -39,19 +39,23 @@ Siga os passos abaixo para começar a tripular! 🚣♂️
|
||||
# src/latest_ai_development/config/agents.yaml
|
||||
researcher:
|
||||
role: >
|
||||
Pesquisador Sênior de Dados em {topic}
|
||||
{topic} Senior Data Researcher
|
||||
goal: >
|
||||
Descobrir os avanços mais recentes em {topic}
|
||||
Uncover cutting-edge developments in {topic}
|
||||
backstory: >
|
||||
Você é um pesquisador experiente com talento para descobrir os últimos avanços em {topic}. Conhecido por sua habilidade em encontrar as informações mais relevantes e apresentá-las de forma clara e concisa.
|
||||
You're a seasoned researcher with a knack for uncovering the latest
|
||||
developments in {topic}. Known for your ability to find the most relevant
|
||||
information and present it in a clear and concise manner.
|
||||
|
||||
reporting_analyst:
|
||||
role: >
|
||||
Analista de Relatórios em {topic}
|
||||
{topic} Reporting Analyst
|
||||
goal: >
|
||||
Criar relatórios detalhados com base na análise de dados e descobertas de pesquisa em {topic}
|
||||
Create detailed reports based on {topic} data analysis and research findings
|
||||
backstory: >
|
||||
Você é um analista meticuloso com um olhar atento aos detalhes. É conhecido por sua capacidade de transformar dados complexos em relatórios claros e concisos, facilitando o entendimento e a tomada de decisão por parte dos outros.
|
||||
You're a meticulous analyst with a keen eye for detail. You're known for
|
||||
your ability to turn complex data into clear and concise reports, making
|
||||
it easy for others to understand and act on the information you provide.
|
||||
```
|
||||
</Step>
|
||||
<Step title="Modifique seu arquivo `tasks.yaml`">
|
||||
@@ -59,19 +63,20 @@ Siga os passos abaixo para começar a tripular! 🚣♂️
|
||||
# src/latest_ai_development/config/tasks.yaml
|
||||
research_task:
|
||||
description: >
|
||||
Realize uma pesquisa aprofundada sobre {topic}.
|
||||
Certifique-se de encontrar informações interessantes e relevantes considerando que o ano atual é 2025.
|
||||
Conduct a thorough research about {topic}
|
||||
Make sure you find any interesting and relevant information given
|
||||
the current year is 2025.
|
||||
expected_output: >
|
||||
Uma lista com 10 tópicos dos dados mais relevantes sobre {topic}
|
||||
A list with 10 bullet points of the most relevant information about {topic}
|
||||
agent: researcher
|
||||
|
||||
reporting_task:
|
||||
description: >
|
||||
Revise o contexto obtido e expanda cada tópico em uma seção completa para um relatório.
|
||||
Certifique-se de que o relatório seja detalhado e contenha todas as informações relevantes.
|
||||
Review the context you got and expand each topic into a full section for a report.
|
||||
Make sure the report is detailed and contains any and all relevant information.
|
||||
expected_output: >
|
||||
Um relatório completo com os principais tópicos, cada um com uma seção detalhada de informações.
|
||||
Formate como markdown sem usar '```'
|
||||
A fully fledge reports with the mains topics, each with a full section of information.
|
||||
Formatted as markdown without '```'
|
||||
agent: reporting_analyst
|
||||
output_file: report.md
|
||||
```
|
||||
@@ -117,15 +122,15 @@ Siga os passos abaixo para começar a tripular! 🚣♂️
|
||||
def reporting_task(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config['reporting_task'], # type: ignore[index]
|
||||
output_file='output/report.md' # Este é o arquivo que conterá o relatório final.
|
||||
output_file='output/report.md' # This is the file that will be contain the final report.
|
||||
)
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
"""Creates the LatestAiDevelopment crew"""
|
||||
return Crew(
|
||||
agents=self.agents, # Criado automaticamente pelo decorador @agent
|
||||
tasks=self.tasks, # Criado automaticamente pelo decorador @task
|
||||
agents=self.agents, # Automatically created by the @agent decorator
|
||||
tasks=self.tasks, # Automatically created by the @task decorator
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
)
|
||||
@@ -224,7 +229,7 @@ Siga os passos abaixo para começar a tripular! 🚣♂️
|
||||
|
||||
<CodeGroup>
|
||||
```markdown output/report.md
|
||||
# Relatório Abrangente sobre a Ascensão e o Impacto dos Agentes de IA em 2025
|
||||
# Comprehensive Report on the Rise and Impact of AI Agents in 2025
|
||||
|
||||
## 1. Introduction to AI Agents
|
||||
In 2025, Artificial Intelligence (AI) agents are at the forefront of innovation across various industries. As intelligent systems that can perform tasks typically requiring human cognition, AI agents are paving the way for significant advancements in operational efficiency, decision-making, and overall productivity within sectors like Human Resources (HR) and Finance. This report aims to detail the rise of AI agents, their frameworks, applications, and potential implications on the workforce.
|
||||
|
||||
@@ -35,18 +35,78 @@ from crewai_tools import LinkupSearchTool
|
||||
from crewai import Agent
|
||||
import os
|
||||
|
||||
# Inicialize a ferramenta com sua chave de API
|
||||
linkup_ferramenta = LinkupSearchTool(api_key=os.getenv("LINKUP_API_KEY"))
|
||||
# Initialize the tool with your API key
|
||||
linkup_tool = LinkupSearchTool(api_key=os.getenv("LINKUP_API_KEY"))
|
||||
|
||||
# Defina um agente que usa a ferramenta
|
||||
# Define an agent that uses the tool
|
||||
@agent
|
||||
def pesquisador(self) -> Agent:
|
||||
def researcher(self) -> Agent:
|
||||
'''
|
||||
Este agente usa o LinkupSearchTool para recuperar informações contextuais
|
||||
da API do Linkup.
|
||||
This agent uses the LinkupSearchTool to retrieve contextual information
|
||||
from the Linkup API.
|
||||
'''
|
||||
return Agent(
|
||||
config=self.agentes_config["pesquisador"],
|
||||
tools=[linkup_ferramenta]
|
||||
config=self.agents_config["researcher"],
|
||||
tools=[linkup_tool]
|
||||
)
|
||||
```
|
||||
```
|
||||
|
||||
## Parâmetros
|
||||
|
||||
O `LinkupSearchTool` aceita os seguintes parâmetros:
|
||||
|
||||
### Parâmetros do Construtor
|
||||
- **api_key**: Obrigatório. Sua chave de API do Linkup.
|
||||
|
||||
### Parâmetros de Execução
|
||||
- **query**: Obrigatório. O termo ou frase de busca.
|
||||
- **depth**: Opcional. A profundidade da busca. O padrão é "standard".
|
||||
- **output_type**: Opcional. O tipo de saída. O padrão é "searchResults".
|
||||
|
||||
## Uso Avançado
|
||||
|
||||
Você pode personalizar os parâmetros de busca para resultados mais específicos:
|
||||
|
||||
```python Code
|
||||
# Perform a search with custom parameters
|
||||
results = linkup_tool.run(
|
||||
query="Women Nobel Prize Physics",
|
||||
depth="deep",
|
||||
output_type="searchResults"
|
||||
)
|
||||
```
|
||||
|
||||
## Formato de Retorno
|
||||
|
||||
A ferramenta retorna resultados no seguinte formato:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": [
|
||||
{
|
||||
"name": "Result Title",
|
||||
"url": "https://example.com/result",
|
||||
"content": "Content of the result..."
|
||||
},
|
||||
// Additional results...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Se ocorrer um erro, a resposta será:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Error message"
|
||||
}
|
||||
```
|
||||
|
||||
## Tratamento de Erros
|
||||
|
||||
A ferramenta lida com erros de API de forma amigável e fornece feedback estruturado. Se a requisição à API falhar, a ferramenta retornará um dicionário com `success: false` e uma mensagem de erro.
|
||||
|
||||
## Conclusão
|
||||
|
||||
O `LinkupSearchTool` oferece uma forma integrada de incorporar as capacidades de busca de informações contextuais do Linkup aos seus agentes CrewAI. Ao utilizar esta ferramenta, os agentes podem acessar informações relevantes e atualizadas para aprimorar sua tomada de decisão e execução de tarefas.
|
||||
@@ -27,13 +27,13 @@ dependencies = [
|
||||
"openpyxl>=3.1.5",
|
||||
"pyvis>=0.3.2",
|
||||
# Authentication and Security
|
||||
"auth0-python>=4.7.1",
|
||||
"python-dotenv>=1.0.0",
|
||||
"pyjwt>=2.9.0",
|
||||
# Configuration and Utils
|
||||
"click>=8.1.7",
|
||||
"appdirs>=1.4.4",
|
||||
"jsonref>=1.1.0",
|
||||
"json-repair==0.25.2",
|
||||
"json-repair>=0.25.2",
|
||||
"uv>=0.4.25",
|
||||
"tomli-w>=1.1.0",
|
||||
"tomli>=2.0.2",
|
||||
@@ -47,11 +47,11 @@ Documentation = "https://docs.crewai.com"
|
||||
Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = ["crewai-tools~=0.49.0"]
|
||||
tools = ["crewai-tools~=0.48.0"]
|
||||
embeddings = [
|
||||
"tiktoken~=0.8.0"
|
||||
]
|
||||
agentops = ["agentops==0.3.18"]
|
||||
agentops = ["agentops>=0.3.0"]
|
||||
pdfplumber = [
|
||||
"pdfplumber>=0.11.4",
|
||||
]
|
||||
@@ -83,8 +83,6 @@ dev-dependencies = [
|
||||
"pytest-recording>=0.13.2",
|
||||
"pytest-randomly>=3.16.0",
|
||||
"pytest-timeout>=2.3.1",
|
||||
"pytest-xdist>=3.6.1",
|
||||
"pytest-split>=0.9.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
@@ -125,15 +123,3 @@ path = "src/crewai/__init__.py"
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
exclude = [
|
||||
"docs/**",
|
||||
"docs/",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.sdist]
|
||||
exclude = [
|
||||
"docs/**",
|
||||
"docs/",
|
||||
]
|
||||
|
||||
@@ -28,19 +28,19 @@ _telemetry_submitted = False
|
||||
def _track_install():
|
||||
"""Track package installation/first-use via Scarf analytics."""
|
||||
global _telemetry_submitted
|
||||
|
||||
|
||||
if _telemetry_submitted or Telemetry._is_telemetry_disabled():
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
pixel_url = "https://api.scarf.sh/v2/packages/CrewAI/crewai/docs/00f2dad1-8334-4a39-934e-003b2e1146db"
|
||||
|
||||
|
||||
req = urllib.request.Request(pixel_url)
|
||||
req.add_header('User-Agent', f'CrewAI-Python/{__version__}')
|
||||
|
||||
|
||||
with urllib.request.urlopen(req, timeout=2): # nosec B310
|
||||
_telemetry_submitted = True
|
||||
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -54,7 +54,7 @@ def _track_install_async():
|
||||
|
||||
_track_install_async()
|
||||
|
||||
__version__ = "0.140.0"
|
||||
__version__ = "0.134.0"
|
||||
__all__ = [
|
||||
"Agent",
|
||||
"Crew",
|
||||
|
||||
@@ -2,7 +2,3 @@ ALGORITHMS = ["RS256"]
|
||||
AUTH0_DOMAIN = "crewai.us.auth0.com"
|
||||
AUTH0_CLIENT_ID = "DEVC5Fw6NlRoSzmDCcOhVq85EfLBjKa8"
|
||||
AUTH0_AUDIENCE = "https://crewai.us.auth0.com/api/v2/"
|
||||
|
||||
WORKOS_DOMAIN = "login.crewai.com"
|
||||
WORKOS_CLI_CONNECT_APP_ID = "client_01JYT06R59SP0NXYGD994NFXXX"
|
||||
WORKOS_ENVIRONMENT_ID = "client_01JNJQWB4HG8T5980R5VHP057C"
|
||||
|
||||
@@ -5,72 +5,37 @@ from typing import Any, Dict
|
||||
import requests
|
||||
from rich.console import Console
|
||||
|
||||
from .constants import (
|
||||
AUTH0_AUDIENCE,
|
||||
AUTH0_CLIENT_ID,
|
||||
AUTH0_DOMAIN,
|
||||
WORKOS_DOMAIN,
|
||||
WORKOS_CLI_CONNECT_APP_ID,
|
||||
WORKOS_ENVIRONMENT_ID,
|
||||
)
|
||||
|
||||
from .utils import TokenManager, validate_jwt_token
|
||||
from urllib.parse import quote
|
||||
from crewai.cli.plus_api import PlusAPI
|
||||
from crewai.cli.config import Settings
|
||||
from .constants import AUTH0_AUDIENCE, AUTH0_CLIENT_ID, AUTH0_DOMAIN
|
||||
from .utils import TokenManager, validate_token
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
class AuthenticationCommand:
|
||||
AUTH0_DEVICE_CODE_URL = f"https://{AUTH0_DOMAIN}/oauth/device/code"
|
||||
AUTH0_TOKEN_URL = f"https://{AUTH0_DOMAIN}/oauth/token"
|
||||
|
||||
WORKOS_DEVICE_CODE_URL = f"https://{WORKOS_DOMAIN}/oauth2/device_authorization"
|
||||
WORKOS_TOKEN_URL = f"https://{WORKOS_DOMAIN}/oauth2/token"
|
||||
DEVICE_CODE_URL = f"https://{AUTH0_DOMAIN}/oauth/device/code"
|
||||
TOKEN_URL = f"https://{AUTH0_DOMAIN}/oauth/token"
|
||||
|
||||
def __init__(self):
|
||||
self.token_manager = TokenManager()
|
||||
# TODO: WORKOS - This variable is temporary until migration to WorkOS is complete.
|
||||
self.user_provider = "workos"
|
||||
|
||||
def login(self) -> None:
|
||||
def signup(self) -> None:
|
||||
"""Sign up to CrewAI+"""
|
||||
|
||||
device_code_url = self.WORKOS_DEVICE_CODE_URL
|
||||
token_url = self.WORKOS_TOKEN_URL
|
||||
client_id = WORKOS_CLI_CONNECT_APP_ID
|
||||
audience = None
|
||||
|
||||
console.print("Signing in to CrewAI Enterprise...\n", style="bold blue")
|
||||
|
||||
# TODO: WORKOS - Next line and conditional are temporary until migration to WorkOS is complete.
|
||||
user_provider = self._determine_user_provider()
|
||||
if user_provider == "auth0":
|
||||
device_code_url = self.AUTH0_DEVICE_CODE_URL
|
||||
token_url = self.AUTH0_TOKEN_URL
|
||||
client_id = AUTH0_CLIENT_ID
|
||||
audience = AUTH0_AUDIENCE
|
||||
self.user_provider = "auth0"
|
||||
# End of temporary code.
|
||||
|
||||
device_code_data = self._get_device_code(client_id, device_code_url, audience)
|
||||
console.print("Signing Up to CrewAI+ \n", style="bold blue")
|
||||
device_code_data = self._get_device_code()
|
||||
self._display_auth_instructions(device_code_data)
|
||||
|
||||
return self._poll_for_token(device_code_data, client_id, token_url)
|
||||
return self._poll_for_token(device_code_data)
|
||||
|
||||
def _get_device_code(
|
||||
self, client_id: str, device_code_url: str, audience: str | None = None
|
||||
) -> Dict[str, Any]:
|
||||
def _get_device_code(self) -> Dict[str, Any]:
|
||||
"""Get the device code to authenticate the user."""
|
||||
|
||||
device_code_payload = {
|
||||
"client_id": client_id,
|
||||
"client_id": AUTH0_CLIENT_ID,
|
||||
"scope": "openid",
|
||||
"audience": audience,
|
||||
"audience": AUTH0_AUDIENCE,
|
||||
}
|
||||
response = requests.post(
|
||||
url=device_code_url, data=device_code_payload, timeout=20
|
||||
url=self.DEVICE_CODE_URL, data=device_code_payload, timeout=20
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
@@ -81,33 +46,38 @@ class AuthenticationCommand:
|
||||
console.print("2. Enter the following code: ", device_code_data["user_code"])
|
||||
webbrowser.open(device_code_data["verification_uri_complete"])
|
||||
|
||||
def _poll_for_token(
|
||||
self, device_code_data: Dict[str, Any], client_id: str, token_poll_url: str
|
||||
) -> None:
|
||||
"""Polls the server for the token until it is received, or max attempts are reached."""
|
||||
|
||||
def _poll_for_token(self, device_code_data: Dict[str, Any]) -> None:
|
||||
"""Poll the server for the token."""
|
||||
token_payload = {
|
||||
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
||||
"device_code": device_code_data["device_code"],
|
||||
"client_id": client_id,
|
||||
"client_id": AUTH0_CLIENT_ID,
|
||||
}
|
||||
|
||||
console.print("\nWaiting for authentication... ", style="bold blue", end="")
|
||||
|
||||
attempts = 0
|
||||
while True and attempts < 10:
|
||||
response = requests.post(token_poll_url, data=token_payload, timeout=30)
|
||||
while True and attempts < 5:
|
||||
response = requests.post(self.TOKEN_URL, data=token_payload, timeout=30)
|
||||
token_data = response.json()
|
||||
|
||||
if response.status_code == 200:
|
||||
self._validate_and_save_token(token_data)
|
||||
validate_token(token_data["id_token"])
|
||||
expires_in = 360000 # Token expiration time in seconds
|
||||
self.token_manager.save_tokens(token_data["access_token"], expires_in)
|
||||
|
||||
console.print(
|
||||
"Success!",
|
||||
style="bold green",
|
||||
)
|
||||
|
||||
self._login_to_tool_repository()
|
||||
try:
|
||||
from crewai.cli.tools.main import ToolCommand
|
||||
ToolCommand().login()
|
||||
except Exception:
|
||||
console.print(
|
||||
"\n[bold yellow]Warning:[/bold yellow] Authentication with the Tool Repository failed.",
|
||||
style="yellow",
|
||||
)
|
||||
console.print(
|
||||
"Other features will work normally, but you may experience limitations "
|
||||
"with downloading and publishing tools."
|
||||
"\nRun [bold]crewai login[/bold] to try logging in again.\n",
|
||||
style="yellow",
|
||||
)
|
||||
|
||||
console.print(
|
||||
"\n[bold green]Welcome to CrewAI Enterprise![/bold green]\n"
|
||||
@@ -123,88 +93,3 @@ class AuthenticationCommand:
|
||||
console.print(
|
||||
"Timeout: Failed to get the token. Please try again.", style="bold red"
|
||||
)
|
||||
|
||||
def _validate_and_save_token(self, token_data: Dict[str, Any]) -> None:
|
||||
"""Validates the JWT token and saves the token to the token manager."""
|
||||
|
||||
jwt_token = token_data["access_token"]
|
||||
jwt_token_data = {
|
||||
"jwt_token": jwt_token,
|
||||
"jwks_url": f"https://{WORKOS_DOMAIN}/oauth2/jwks",
|
||||
"issuer": f"https://{WORKOS_DOMAIN}",
|
||||
"audience": WORKOS_ENVIRONMENT_ID,
|
||||
}
|
||||
|
||||
# TODO: WORKOS - The following conditional is temporary until migration to WorkOS is complete.
|
||||
if self.user_provider == "auth0":
|
||||
jwt_token_data["jwks_url"] = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
|
||||
jwt_token_data["issuer"] = f"https://{AUTH0_DOMAIN}/"
|
||||
jwt_token_data["audience"] = AUTH0_AUDIENCE
|
||||
|
||||
decoded_token = validate_jwt_token(**jwt_token_data)
|
||||
|
||||
expires_at = decoded_token.get("exp", 0)
|
||||
self.token_manager.save_tokens(jwt_token, expires_at)
|
||||
|
||||
def _login_to_tool_repository(self) -> None:
|
||||
"""Login to the tool repository."""
|
||||
|
||||
from crewai.cli.tools.main import ToolCommand
|
||||
|
||||
try:
|
||||
console.print(
|
||||
"Now logging you in to the Tool Repository... ",
|
||||
style="bold blue",
|
||||
end="",
|
||||
)
|
||||
|
||||
ToolCommand().login()
|
||||
|
||||
console.print(
|
||||
"Success!\n",
|
||||
style="bold green",
|
||||
)
|
||||
|
||||
settings = Settings()
|
||||
console.print(
|
||||
f"You are authenticated to the tool repository as [bold cyan]'{settings.org_name}'[/bold cyan] ({settings.org_uuid})",
|
||||
style="green",
|
||||
)
|
||||
except Exception:
|
||||
console.print(
|
||||
"\n[bold yellow]Warning:[/bold yellow] Authentication with the Tool Repository failed.",
|
||||
style="yellow",
|
||||
)
|
||||
console.print(
|
||||
"Other features will work normally, but you may experience limitations "
|
||||
"with downloading and publishing tools."
|
||||
"\nRun [bold]crewai login[/bold] to try logging in again.\n",
|
||||
style="yellow",
|
||||
)
|
||||
|
||||
# TODO: WORKOS - This method is temporary until migration to WorkOS is complete.
|
||||
def _determine_user_provider(self) -> str:
|
||||
"""Determine which provider to use for authentication."""
|
||||
|
||||
console.print(
|
||||
"Enter your CrewAI Enterprise account email: ", style="bold blue", end=""
|
||||
)
|
||||
email = input()
|
||||
email_encoded = quote(email)
|
||||
|
||||
# It's not correct to call this method directly, but it's temporary until migration is complete.
|
||||
response = PlusAPI("")._make_request(
|
||||
"GET", f"/crewai_plus/api/v1/me/provider?email={email_encoded}"
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.json().get("provider") == "auth0":
|
||||
return "auth0"
|
||||
else:
|
||||
return "workos"
|
||||
else:
|
||||
console.print(
|
||||
"Error: Failed to authenticate with crewai enterprise. Ensure that you are using the latest crewai version and please try again. If the problem persists, contact support@crewai.com.",
|
||||
style="red",
|
||||
)
|
||||
raise SystemExit
|
||||
|
||||
@@ -1,63 +1,32 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import jwt
|
||||
from jwt import PyJWKClient
|
||||
|
||||
from auth0.authentication.token_verifier import (
|
||||
AsymmetricSignatureVerifier,
|
||||
TokenVerifier,
|
||||
)
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
from .constants import AUTH0_CLIENT_ID, AUTH0_DOMAIN
|
||||
|
||||
def validate_jwt_token(
|
||||
jwt_token: str, jwks_url: str, issuer: str, audience: str
|
||||
) -> dict:
|
||||
|
||||
def validate_token(id_token: str) -> None:
|
||||
"""
|
||||
Verify the token's signature and claims using PyJWT.
|
||||
:param jwt_token: The JWT (JWS) string to validate.
|
||||
:param jwks_url: The URL of the JWKS endpoint.
|
||||
:param issuer: The expected issuer of the token.
|
||||
:param audience: The expected audience of the token.
|
||||
:return: The decoded token.
|
||||
:raises Exception: If the token is invalid for any reason (e.g., signature mismatch,
|
||||
expired, incorrect issuer/audience, JWKS fetching error,
|
||||
missing required claims).
|
||||
Verify the token and its precedence
|
||||
|
||||
:param id_token:
|
||||
"""
|
||||
|
||||
decoded_token = None
|
||||
|
||||
try:
|
||||
jwk_client = PyJWKClient(jwks_url)
|
||||
signing_key = jwk_client.get_signing_key_from_jwt(jwt_token)
|
||||
|
||||
decoded_token = jwt.decode(
|
||||
jwt_token,
|
||||
signing_key.key,
|
||||
algorithms=["RS256"],
|
||||
audience=audience,
|
||||
issuer=issuer,
|
||||
options={
|
||||
"verify_signature": True,
|
||||
"verify_exp": True,
|
||||
"verify_nbf": True,
|
||||
"verify_iat": True,
|
||||
"require": ["exp", "iat", "iss", "aud", "sub"],
|
||||
},
|
||||
)
|
||||
return decoded_token
|
||||
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise Exception("Token has expired.")
|
||||
except jwt.InvalidAudienceError:
|
||||
raise Exception(f"Invalid token audience. Expected: '{audience}'")
|
||||
except jwt.InvalidIssuerError:
|
||||
raise Exception(f"Invalid token issuer. Expected: '{issuer}'")
|
||||
except jwt.MissingRequiredClaimError as e:
|
||||
raise Exception(f"Token is missing required claims: {str(e)}")
|
||||
except jwt.exceptions.PyJWKClientError as e:
|
||||
raise Exception(f"JWKS or key processing error: {str(e)}")
|
||||
except jwt.InvalidTokenError as e:
|
||||
raise Exception(f"Invalid token: {str(e)}")
|
||||
jwks_url = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
|
||||
issuer = f"https://{AUTH0_DOMAIN}/"
|
||||
signature_verifier = AsymmetricSignatureVerifier(jwks_url)
|
||||
token_verifier = TokenVerifier(
|
||||
signature_verifier=signature_verifier, issuer=issuer, audience=AUTH0_CLIENT_ID
|
||||
)
|
||||
token_verifier.verify(id_token)
|
||||
|
||||
|
||||
class TokenManager:
|
||||
@@ -87,14 +56,14 @@ class TokenManager:
|
||||
self.save_secure_file(key_filename, new_key)
|
||||
return new_key
|
||||
|
||||
def save_tokens(self, access_token: str, expires_at: int) -> None:
|
||||
def save_tokens(self, access_token: str, expires_in: int) -> None:
|
||||
"""
|
||||
Save the access token and its expiration time.
|
||||
|
||||
:param access_token: The access token to save.
|
||||
:param expires_at: The UNIX timestamp of the expiration time.
|
||||
:param expires_in: The expiration time of the access token in seconds.
|
||||
"""
|
||||
expiration_time = datetime.fromtimestamp(expires_at)
|
||||
expiration_time = datetime.now() + timedelta(seconds=expires_in)
|
||||
data = {
|
||||
"access_token": access_token,
|
||||
"expiration": expiration_time.isoformat(),
|
||||
|
||||
@@ -2,7 +2,7 @@ from importlib.metadata import version as get_version
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
from crewai.cli.config import Settings
|
||||
|
||||
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
|
||||
@@ -138,12 +138,8 @@ def log_tasks_outputs() -> None:
|
||||
@click.option("-s", "--short", is_flag=True, help="Reset SHORT TERM memory")
|
||||
@click.option("-e", "--entities", is_flag=True, help="Reset ENTITIES memory")
|
||||
@click.option("-kn", "--knowledge", is_flag=True, help="Reset KNOWLEDGE storage")
|
||||
@click.option(
|
||||
"-akn", "--agent-knowledge", is_flag=True, help="Reset AGENT KNOWLEDGE storage"
|
||||
)
|
||||
@click.option(
|
||||
"-k", "--kickoff-outputs", is_flag=True, help="Reset LATEST KICKOFF TASK OUTPUTS"
|
||||
)
|
||||
@click.option("-akn", "--agent-knowledge", is_flag=True, help="Reset AGENT KNOWLEDGE storage")
|
||||
@click.option("-k","--kickoff-outputs",is_flag=True,help="Reset LATEST KICKOFF TASK OUTPUTS")
|
||||
@click.option("-a", "--all", is_flag=True, help="Reset ALL memories")
|
||||
def reset_memories(
|
||||
long: bool,
|
||||
@@ -158,23 +154,13 @@ def reset_memories(
|
||||
Reset the crew memories (long, short, entity, latest_crew_kickoff_ouputs, knowledge, agent_knowledge). This will delete all the data saved.
|
||||
"""
|
||||
try:
|
||||
memory_types = [
|
||||
long,
|
||||
short,
|
||||
entities,
|
||||
knowledge,
|
||||
agent_knowledge,
|
||||
kickoff_outputs,
|
||||
all,
|
||||
]
|
||||
memory_types = [long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all]
|
||||
if not any(memory_types):
|
||||
click.echo(
|
||||
"Please specify at least one memory type to reset using the appropriate flags."
|
||||
)
|
||||
return
|
||||
reset_memories_command(
|
||||
long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all
|
||||
)
|
||||
reset_memories_command(long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all)
|
||||
except Exception as e:
|
||||
click.echo(f"An error occurred while resetting memories: {e}", err=True)
|
||||
|
||||
@@ -224,11 +210,16 @@ def update():
|
||||
update_crew()
|
||||
|
||||
|
||||
@crewai.command()
|
||||
def signup():
|
||||
"""Sign Up/Login to CrewAI+."""
|
||||
AuthenticationCommand().signup()
|
||||
|
||||
|
||||
@crewai.command()
|
||||
def login():
|
||||
"""Sign Up/Login to CrewAI Enterprise."""
|
||||
Settings().clear()
|
||||
AuthenticationCommand().login()
|
||||
"""Sign Up/Login to CrewAI+."""
|
||||
AuthenticationCommand().signup()
|
||||
|
||||
|
||||
# DEPLOY CREWAI+ COMMANDS
|
||||
|
||||
@@ -37,10 +37,6 @@ class Settings(BaseModel):
|
||||
merged_data = {**file_data, **data}
|
||||
super().__init__(config_path=config_path, **merged_data)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all settings"""
|
||||
self.config_path.unlink(missing_ok=True)
|
||||
|
||||
def dump(self) -> None:
|
||||
"""Save current settings to settings.json"""
|
||||
if self.config_path.is_file():
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.140.0,<1.0.0"
|
||||
"crewai[tools]>=0.134.0,<1.0.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.140.0,<1.0.0",
|
||||
"crewai[tools]>=0.134.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.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.140.0"
|
||||
"crewai[tools]>=0.134.0"
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
|
||||
@@ -156,7 +156,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
|
||||
console.print(f"Successfully installed {handle}", style="bold green")
|
||||
|
||||
def login(self) -> None:
|
||||
def login(self):
|
||||
login_response = self.plus_api_client.login_to_tool_repository()
|
||||
|
||||
if login_response.status_code != 200:
|
||||
@@ -175,10 +175,18 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
settings.tool_repository_password = login_response_json["credential"][
|
||||
"password"
|
||||
]
|
||||
settings.org_uuid = login_response_json["current_organization"]["uuid"]
|
||||
settings.org_name = login_response_json["current_organization"]["name"]
|
||||
settings.org_uuid = login_response_json["current_organization"][
|
||||
"uuid"
|
||||
]
|
||||
settings.org_name = login_response_json["current_organization"][
|
||||
"name"
|
||||
]
|
||||
settings.dump()
|
||||
|
||||
console.print(
|
||||
f"Successfully authenticated to the tool repository as {settings.org_name} ({settings.org_uuid}).", style="bold green"
|
||||
)
|
||||
|
||||
def _add_package(self, tool_details: dict[str, Any]):
|
||||
is_from_pypi = tool_details.get("source", None) == "pypi"
|
||||
tool_handle = tool_details["handle"]
|
||||
@@ -235,15 +243,9 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
|
||||
return env
|
||||
|
||||
def _print_current_organization(self) -> None:
|
||||
def _print_current_organization(self):
|
||||
settings = Settings()
|
||||
if settings.org_uuid:
|
||||
console.print(
|
||||
f"Current organization: {settings.org_name} ({settings.org_uuid})",
|
||||
style="bold blue",
|
||||
)
|
||||
console.print(f"Current organization: {settings.org_name} ({settings.org_uuid})", style="bold blue")
|
||||
else:
|
||||
console.print(
|
||||
"No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.",
|
||||
style="yellow",
|
||||
)
|
||||
console.print("No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.", style="yellow")
|
||||
|
||||
@@ -18,11 +18,6 @@ from typing import (
|
||||
cast,
|
||||
)
|
||||
|
||||
from opentelemetry import baggage
|
||||
from opentelemetry.context import attach, detach
|
||||
|
||||
from crewai.utilities.crew.models import CrewContext
|
||||
|
||||
from pydantic import (
|
||||
UUID4,
|
||||
BaseModel,
|
||||
@@ -621,11 +616,6 @@ class Crew(FlowTrackable, BaseModel):
|
||||
self,
|
||||
inputs: Optional[Dict[str, Any]] = None,
|
||||
) -> CrewOutput:
|
||||
ctx = baggage.set_baggage(
|
||||
"crew_context", CrewContext(id=str(self.id), key=self.key)
|
||||
)
|
||||
token = attach(ctx)
|
||||
|
||||
try:
|
||||
for before_callback in self.before_kickoff_callbacks:
|
||||
if inputs is None:
|
||||
@@ -686,8 +676,6 @@ class Crew(FlowTrackable, BaseModel):
|
||||
CrewKickoffFailedEvent(error=str(e), crew_name=self.name or "crew"),
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
detach(token)
|
||||
|
||||
def kickoff_for_each(self, inputs: List[Dict[str, Any]]) -> List[CrewOutput]:
|
||||
"""Executes the Crew's workflow for each input in the list and aggregates results."""
|
||||
|
||||
@@ -97,7 +97,7 @@ class Task(BaseModel):
|
||||
)
|
||||
context: Union[List["Task"], None, _NotSpecified] = Field(
|
||||
description="Other tasks that will have their output used as context for this task.",
|
||||
default=NOT_SPECIFIED,
|
||||
default=NOT_SPECIFIED
|
||||
)
|
||||
async_execution: Optional[bool] = Field(
|
||||
description="Whether the task should be executed asynchronously or not.",
|
||||
@@ -158,7 +158,9 @@ class Task(BaseModel):
|
||||
end_time: Optional[datetime.datetime] = Field(
|
||||
default=None, description="End time of the task execution"
|
||||
)
|
||||
model_config = {"arbitrary_types_allowed": True}
|
||||
model_config = {
|
||||
"arbitrary_types_allowed": True
|
||||
}
|
||||
|
||||
@field_validator("guardrail")
|
||||
@classmethod
|
||||
@@ -202,6 +204,7 @@ class Task(BaseModel):
|
||||
# Check return annotation if present, but don't require it
|
||||
return_annotation = sig.return_annotation
|
||||
if return_annotation != inspect.Signature.empty:
|
||||
|
||||
return_annotation_args = get_args(return_annotation)
|
||||
if not (
|
||||
get_origin(return_annotation) is tuple
|
||||
@@ -434,7 +437,7 @@ class Task(BaseModel):
|
||||
guardrail_result = process_guardrail(
|
||||
output=task_output,
|
||||
guardrail=self._guardrail,
|
||||
retry_count=self.retry_count,
|
||||
retry_count=self.retry_count
|
||||
)
|
||||
if not guardrail_result.success:
|
||||
if self.retry_count >= self.max_retries:
|
||||
@@ -507,6 +510,8 @@ class Task(BaseModel):
|
||||
)
|
||||
from crewai.utilities.events.crewai_event_bus import crewai_event_bus
|
||||
|
||||
result = self._guardrail(task_output)
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
LLMGuardrailStartedEvent(
|
||||
@@ -514,13 +519,7 @@ class Task(BaseModel):
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
result = self._guardrail(task_output)
|
||||
guardrail_result = GuardrailResult.from_tuple(result)
|
||||
except Exception as e:
|
||||
guardrail_result = GuardrailResult(
|
||||
success=False, result=None, error=f"Guardrail execution error: {str(e)}"
|
||||
)
|
||||
guardrail_result = GuardrailResult.from_tuple(result)
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""Crew-specific utilities."""
|
||||
@@ -1,16 +0,0 @@
|
||||
"""Context management utilities for tracking crew and task execution context using OpenTelemetry baggage."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from opentelemetry import baggage
|
||||
|
||||
from crewai.utilities.crew.models import CrewContext
|
||||
|
||||
|
||||
def get_crew_context() -> Optional[CrewContext]:
|
||||
"""Get the current crew context from OpenTelemetry baggage.
|
||||
|
||||
Returns:
|
||||
CrewContext instance containing crew context information, or None if no context is set
|
||||
"""
|
||||
return baggage.get_baggage("crew_context")
|
||||
@@ -1,16 +0,0 @@
|
||||
"""Models for crew-related data structures."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CrewContext(BaseModel):
|
||||
"""Model representing crew context information."""
|
||||
|
||||
id: Optional[str] = Field(
|
||||
default=None, description="Unique identifier for the crew"
|
||||
)
|
||||
key: Optional[str] = Field(
|
||||
default=None, description="Optional crew key/name for identification"
|
||||
)
|
||||
@@ -22,10 +22,6 @@ from crewai.utilities.events.llm_events import (
|
||||
LLMCallStartedEvent,
|
||||
LLMStreamChunkEvent,
|
||||
)
|
||||
from crewai.utilities.events.llm_guardrail_events import (
|
||||
LLMGuardrailStartedEvent,
|
||||
LLMGuardrailCompletedEvent,
|
||||
)
|
||||
from crewai.utilities.events.utils.console_formatter import ConsoleFormatter
|
||||
|
||||
from .agent_events import (
|
||||
@@ -374,23 +370,6 @@ class EventListener(BaseEventListener):
|
||||
print(content, end="", flush=True)
|
||||
self.next_chunk = self.text_stream.tell()
|
||||
|
||||
# ----------- LLM GUARDRAIL EVENTS -----------
|
||||
|
||||
@crewai_event_bus.on(LLMGuardrailStartedEvent)
|
||||
def on_llm_guardrail_started(source, event: LLMGuardrailStartedEvent):
|
||||
guardrail_str = str(event.guardrail)
|
||||
guardrail_name = (
|
||||
guardrail_str[:50] + "..." if len(guardrail_str) > 50 else guardrail_str
|
||||
)
|
||||
|
||||
self.formatter.handle_guardrail_started(guardrail_name, event.retry_count)
|
||||
|
||||
@crewai_event_bus.on(LLMGuardrailCompletedEvent)
|
||||
def on_llm_guardrail_completed(source, event: LLMGuardrailCompletedEvent):
|
||||
self.formatter.handle_guardrail_completed(
|
||||
event.success, event.error, event.retry_count
|
||||
)
|
||||
|
||||
@crewai_event_bus.on(CrewTestStartedEvent)
|
||||
def on_crew_test_started(source, event: CrewTestStartedEvent):
|
||||
cloned_crew = source.copy()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from inspect import getsource
|
||||
from typing import Any, Callable, Optional, Union
|
||||
|
||||
from crewai.utilities.events.base_events import BaseEvent
|
||||
@@ -17,26 +16,23 @@ class LLMGuardrailStartedEvent(BaseEvent):
|
||||
retry_count: int
|
||||
|
||||
def __init__(self, **data):
|
||||
from inspect import getsource
|
||||
|
||||
from crewai.tasks.llm_guardrail import LLMGuardrail
|
||||
from crewai.tasks.hallucination_guardrail import HallucinationGuardrail
|
||||
|
||||
super().__init__(**data)
|
||||
|
||||
if isinstance(self.guardrail, (LLMGuardrail, HallucinationGuardrail)):
|
||||
if isinstance(self.guardrail, LLMGuardrail) or isinstance(
|
||||
self.guardrail, HallucinationGuardrail
|
||||
):
|
||||
self.guardrail = self.guardrail.description.strip()
|
||||
elif isinstance(self.guardrail, Callable):
|
||||
self.guardrail = getsource(self.guardrail).strip()
|
||||
|
||||
|
||||
class LLMGuardrailCompletedEvent(BaseEvent):
|
||||
"""Event emitted when a guardrail task completes
|
||||
|
||||
Attributes:
|
||||
success: Whether the guardrail validation passed
|
||||
result: The validation result
|
||||
error: Error message if validation failed
|
||||
retry_count: The number of times the guardrail has been retried
|
||||
"""
|
||||
"""Event emitted when a guardrail task completes"""
|
||||
|
||||
type: str = "llm_guardrail_completed"
|
||||
success: bool
|
||||
|
||||
@@ -1473,7 +1473,9 @@ class ConsoleFormatter:
|
||||
return None
|
||||
|
||||
memory_branch = branch_to_use.add("")
|
||||
self.update_tree_label(memory_branch, "🧠", "Memory Retrieval Started", "blue")
|
||||
self.update_tree_label(
|
||||
memory_branch, "🧠", "Memory Retrieval Started", "blue"
|
||||
)
|
||||
|
||||
self.print(tree_to_use)
|
||||
self.print()
|
||||
@@ -1547,6 +1549,7 @@ class ConsoleFormatter:
|
||||
if memory_content:
|
||||
add_panel()
|
||||
|
||||
|
||||
def handle_memory_query_completed(
|
||||
self,
|
||||
agent_branch: Optional[Tree],
|
||||
@@ -1613,8 +1616,11 @@ class ConsoleFormatter:
|
||||
sources_branch.add(f"❌ {memory_type} - Error: {error}")
|
||||
break
|
||||
|
||||
|
||||
def handle_memory_save_started(
|
||||
self, agent_branch: Optional[Tree], crew_tree: Optional[Tree]
|
||||
self,
|
||||
agent_branch: Optional[Tree],
|
||||
crew_tree: Optional[Tree]
|
||||
) -> None:
|
||||
if not self.verbose:
|
||||
return None
|
||||
@@ -1627,7 +1633,7 @@ class ConsoleFormatter:
|
||||
|
||||
for child in tree_to_use.children:
|
||||
if "Memory Update" in str(child.label):
|
||||
break
|
||||
break
|
||||
else:
|
||||
memory_branch = tree_to_use.add("")
|
||||
self.update_tree_label(
|
||||
@@ -1694,62 +1700,4 @@ class ConsoleFormatter:
|
||||
memory_branch.add(content)
|
||||
|
||||
self.print(tree_to_use)
|
||||
self.print()
|
||||
|
||||
def handle_guardrail_started(
|
||||
self,
|
||||
guardrail_name: str,
|
||||
retry_count: int,
|
||||
) -> None:
|
||||
"""Display guardrail evaluation started status.
|
||||
|
||||
Args:
|
||||
guardrail_name: Name/description of the guardrail being evaluated.
|
||||
retry_count: Zero-based retry count (0 = first attempt).
|
||||
"""
|
||||
if not self.verbose:
|
||||
return
|
||||
|
||||
content = self.create_status_content(
|
||||
"Guardrail Evaluation Started",
|
||||
guardrail_name,
|
||||
"yellow",
|
||||
Status="🔄 Evaluating",
|
||||
Attempt=f"{retry_count + 1}",
|
||||
)
|
||||
self.print_panel(content, "🛡️ Guardrail Check", "yellow")
|
||||
|
||||
def handle_guardrail_completed(
|
||||
self,
|
||||
success: bool,
|
||||
error: Optional[str],
|
||||
retry_count: int,
|
||||
) -> None:
|
||||
"""Display guardrail evaluation result.
|
||||
|
||||
Args:
|
||||
success: Whether validation passed.
|
||||
error: Error message if validation failed.
|
||||
retry_count: Zero-based retry count.
|
||||
"""
|
||||
if not self.verbose:
|
||||
return
|
||||
|
||||
if success:
|
||||
content = self.create_status_content(
|
||||
"Guardrail Passed",
|
||||
"Validation Successful",
|
||||
"green",
|
||||
Status="✅ Validated",
|
||||
Attempts=f"{retry_count + 1}",
|
||||
)
|
||||
self.print_panel(content, "🛡️ Guardrail Success", "green")
|
||||
else:
|
||||
content = self.create_status_content(
|
||||
"Guardrail Failed",
|
||||
"Validation Error",
|
||||
"red",
|
||||
Error=str(error) if error else "Unknown error",
|
||||
Attempts=f"{retry_count + 1}",
|
||||
)
|
||||
self.print_panel(content, "🛡️ Guardrail Failed", "red")
|
||||
self.print()
|
||||
File diff suppressed because one or more lines are too long
@@ -1,419 +1,103 @@
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import requests
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
|
||||
from crewai.cli.authentication.main import AuthenticationCommand
|
||||
from crewai.cli.authentication.constants import (
|
||||
AUTH0_AUDIENCE,
|
||||
AUTH0_CLIENT_ID,
|
||||
AUTH0_DOMAIN,
|
||||
WORKOS_DOMAIN,
|
||||
WORKOS_CLI_CONNECT_APP_ID,
|
||||
WORKOS_ENVIRONMENT_ID,
|
||||
)
|
||||
|
||||
|
||||
class TestAuthenticationCommand:
|
||||
def setup_method(self):
|
||||
class TestAuthenticationCommand(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.auth_command = AuthenticationCommand()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"user_provider,expected_urls",
|
||||
[
|
||||
(
|
||||
"auth0",
|
||||
{
|
||||
"device_code_url": f"https://{AUTH0_DOMAIN}/oauth/device/code",
|
||||
"token_url": f"https://{AUTH0_DOMAIN}/oauth/token",
|
||||
"client_id": AUTH0_CLIENT_ID,
|
||||
"audience": AUTH0_AUDIENCE,
|
||||
},
|
||||
),
|
||||
(
|
||||
"workos",
|
||||
{
|
||||
"device_code_url": f"https://{WORKOS_DOMAIN}/oauth2/device_authorization",
|
||||
"token_url": f"https://{WORKOS_DOMAIN}/oauth2/token",
|
||||
"client_id": WORKOS_CLI_CONNECT_APP_ID,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@patch(
|
||||
"crewai.cli.authentication.main.AuthenticationCommand._determine_user_provider"
|
||||
)
|
||||
@patch("crewai.cli.authentication.main.AuthenticationCommand._get_device_code")
|
||||
@patch(
|
||||
"crewai.cli.authentication.main.AuthenticationCommand._display_auth_instructions"
|
||||
)
|
||||
@patch("crewai.cli.authentication.main.AuthenticationCommand._poll_for_token")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
def test_login(
|
||||
self,
|
||||
mock_console_print,
|
||||
mock_poll,
|
||||
mock_display,
|
||||
mock_get_device,
|
||||
mock_determine_provider,
|
||||
user_provider,
|
||||
expected_urls,
|
||||
):
|
||||
mock_determine_provider.return_value = user_provider
|
||||
mock_get_device.return_value = {
|
||||
"device_code": "test_code",
|
||||
"user_code": "123456",
|
||||
@patch("crewai.cli.authentication.main.requests.post")
|
||||
def test_get_device_code(self, mock_post):
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {
|
||||
"device_code": "123456",
|
||||
"user_code": "ABCDEF",
|
||||
"verification_uri_complete": "https://example.com",
|
||||
"interval": 5,
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
self.auth_command.login()
|
||||
device_code_data = self.auth_command._get_device_code()
|
||||
|
||||
mock_console_print.assert_called_once_with(
|
||||
"Signing in to CrewAI Enterprise...\n", style="bold blue"
|
||||
)
|
||||
mock_determine_provider.assert_called_once()
|
||||
mock_get_device.assert_called_once_with(
|
||||
expected_urls["client_id"],
|
||||
expected_urls["device_code_url"],
|
||||
expected_urls.get("audience", None),
|
||||
)
|
||||
mock_display.assert_called_once_with(
|
||||
{"device_code": "test_code", "user_code": "123456"}
|
||||
)
|
||||
mock_poll.assert_called_once_with(
|
||||
{"device_code": "test_code", "user_code": "123456"},
|
||||
expected_urls["client_id"],
|
||||
expected_urls["token_url"],
|
||||
self.assertEqual(device_code_data["device_code"], "123456")
|
||||
self.assertEqual(device_code_data["user_code"], "ABCDEF")
|
||||
self.assertEqual(
|
||||
device_code_data["verification_uri_complete"], "https://example.com"
|
||||
)
|
||||
self.assertEqual(device_code_data["interval"], 5)
|
||||
|
||||
@patch("crewai.cli.authentication.main.webbrowser")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
def test_display_auth_instructions(self, mock_console_print, mock_webbrowser):
|
||||
@patch("crewai.cli.authentication.main.webbrowser.open")
|
||||
def test_display_auth_instructions(self, mock_open, mock_print):
|
||||
device_code_data = {
|
||||
"verification_uri_complete": "https://example.com/auth",
|
||||
"user_code": "123456",
|
||||
"verification_uri_complete": "https://example.com",
|
||||
"user_code": "ABCDEF",
|
||||
}
|
||||
|
||||
self.auth_command._display_auth_instructions(device_code_data)
|
||||
|
||||
expected_calls = [
|
||||
call("1. Navigate to: ", "https://example.com/auth"),
|
||||
call("2. Enter the following code: ", "123456"),
|
||||
]
|
||||
mock_console_print.assert_has_calls(expected_calls)
|
||||
mock_webbrowser.open.assert_called_once_with("https://example.com/auth")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"user_provider,jwt_config",
|
||||
[
|
||||
(
|
||||
"auth0",
|
||||
{
|
||||
"jwks_url": f"https://{AUTH0_DOMAIN}/.well-known/jwks.json",
|
||||
"issuer": f"https://{AUTH0_DOMAIN}/",
|
||||
"audience": AUTH0_AUDIENCE,
|
||||
},
|
||||
),
|
||||
(
|
||||
"workos",
|
||||
{
|
||||
"jwks_url": f"https://{WORKOS_DOMAIN}/oauth2/jwks",
|
||||
"issuer": f"https://{WORKOS_DOMAIN}",
|
||||
"audience": WORKOS_ENVIRONMENT_ID,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("has_expiration", [True, False])
|
||||
@patch("crewai.cli.authentication.main.validate_jwt_token")
|
||||
@patch("crewai.cli.authentication.main.TokenManager.save_tokens")
|
||||
def test_validate_and_save_token(
|
||||
self,
|
||||
mock_save_tokens,
|
||||
mock_validate_jwt,
|
||||
user_provider,
|
||||
jwt_config,
|
||||
has_expiration,
|
||||
):
|
||||
self.auth_command.user_provider = user_provider
|
||||
token_data = {"access_token": "test_access_token", "id_token": "test_id_token"}
|
||||
|
||||
if has_expiration:
|
||||
future_timestamp = int((datetime.now() + timedelta(days=100)).timestamp())
|
||||
decoded_token = {"exp": future_timestamp}
|
||||
else:
|
||||
decoded_token = {}
|
||||
|
||||
mock_validate_jwt.return_value = decoded_token
|
||||
|
||||
self.auth_command._validate_and_save_token(token_data)
|
||||
|
||||
mock_validate_jwt.assert_called_once_with(
|
||||
jwt_token="test_access_token",
|
||||
jwks_url=jwt_config["jwks_url"],
|
||||
issuer=jwt_config["issuer"],
|
||||
audience=jwt_config["audience"],
|
||||
)
|
||||
|
||||
if has_expiration:
|
||||
mock_save_tokens.assert_called_once_with(
|
||||
"test_access_token", future_timestamp
|
||||
)
|
||||
else:
|
||||
mock_save_tokens.assert_called_once_with("test_access_token", 0)
|
||||
mock_print.assert_any_call("1. Navigate to: ", "https://example.com")
|
||||
mock_print.assert_any_call("2. Enter the following code: ", "ABCDEF")
|
||||
mock_open.assert_called_once_with("https://example.com")
|
||||
|
||||
@patch("crewai.cli.tools.main.ToolCommand")
|
||||
@patch("crewai.cli.authentication.main.Settings")
|
||||
@patch("crewai.cli.authentication.main.requests.post")
|
||||
@patch("crewai.cli.authentication.main.validate_token")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
def test_login_to_tool_repository_success(
|
||||
self, mock_console_print, mock_settings, mock_tool_command
|
||||
def test_poll_for_token_success(
|
||||
self, mock_print, mock_validate_token, mock_post, mock_tool
|
||||
):
|
||||
mock_tool_instance = MagicMock()
|
||||
mock_tool_command.return_value = mock_tool_instance
|
||||
|
||||
mock_settings_instance = MagicMock()
|
||||
mock_settings_instance.org_name = "Test Org"
|
||||
mock_settings_instance.org_uuid = "test-uuid-123"
|
||||
mock_settings.return_value = mock_settings_instance
|
||||
|
||||
self.auth_command._login_to_tool_repository()
|
||||
|
||||
mock_tool_command.assert_called_once()
|
||||
mock_tool_instance.login.assert_called_once()
|
||||
|
||||
expected_calls = [
|
||||
call(
|
||||
"Now logging you in to the Tool Repository... ",
|
||||
style="bold blue",
|
||||
end="",
|
||||
),
|
||||
call("Success!\n", style="bold green"),
|
||||
call(
|
||||
"You are authenticated to the tool repository as [bold cyan]'Test Org'[/bold cyan] (test-uuid-123)",
|
||||
style="green",
|
||||
),
|
||||
]
|
||||
mock_console_print.assert_has_calls(expected_calls)
|
||||
|
||||
@patch("crewai.cli.tools.main.ToolCommand")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
def test_login_to_tool_repository_error(
|
||||
self, mock_console_print, mock_tool_command
|
||||
):
|
||||
mock_tool_instance = MagicMock()
|
||||
mock_tool_instance.login.side_effect = Exception("Tool repository error")
|
||||
mock_tool_command.return_value = mock_tool_instance
|
||||
|
||||
self.auth_command._login_to_tool_repository()
|
||||
|
||||
mock_tool_command.assert_called_once()
|
||||
mock_tool_instance.login.assert_called_once()
|
||||
|
||||
expected_calls = [
|
||||
call(
|
||||
"Now logging you in to the Tool Repository... ",
|
||||
style="bold blue",
|
||||
end="",
|
||||
),
|
||||
call(
|
||||
"\n[bold yellow]Warning:[/bold yellow] Authentication with the Tool Repository failed.",
|
||||
style="yellow",
|
||||
),
|
||||
call(
|
||||
"Other features will work normally, but you may experience limitations with downloading and publishing tools.\nRun [bold]crewai login[/bold] to try logging in again.\n",
|
||||
style="yellow",
|
||||
),
|
||||
]
|
||||
mock_console_print.assert_has_calls(expected_calls)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"api_response,expected_provider",
|
||||
[
|
||||
({"provider": "auth0"}, "auth0"),
|
||||
({"provider": "workos"}, "workos"),
|
||||
({"provider": "none"}, "workos"), # Default to workos for any other value
|
||||
(
|
||||
{},
|
||||
"workos",
|
||||
), # Default to workos if no provider key is sent in the response
|
||||
],
|
||||
)
|
||||
@patch("crewai.cli.authentication.main.PlusAPI")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
@patch("builtins.input", return_value="test@example.com")
|
||||
def test_determine_user_provider_success(
|
||||
self,
|
||||
mock_input,
|
||||
mock_console_print,
|
||||
mock_plus_api,
|
||||
api_response,
|
||||
expected_provider,
|
||||
):
|
||||
mock_api_instance = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = api_response
|
||||
mock_api_instance._make_request.return_value = mock_response
|
||||
mock_plus_api.return_value = mock_api_instance
|
||||
|
||||
result = self.auth_command._determine_user_provider()
|
||||
|
||||
mock_input.assert_called_once()
|
||||
|
||||
mock_plus_api.assert_called_once_with("")
|
||||
mock_api_instance._make_request.assert_called_once_with(
|
||||
"GET", "/crewai_plus/api/v1/me/provider?email=test%40example.com"
|
||||
)
|
||||
|
||||
assert result == expected_provider
|
||||
|
||||
@patch("crewai.cli.authentication.main.PlusAPI")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
@patch("builtins.input", return_value="test@example.com")
|
||||
def test_determine_user_provider_error(
|
||||
self, mock_input, mock_console_print, mock_plus_api
|
||||
):
|
||||
mock_api_instance = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 500
|
||||
mock_api_instance._make_request.return_value = mock_response
|
||||
mock_plus_api.return_value = mock_api_instance
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
self.auth_command._determine_user_provider()
|
||||
|
||||
mock_input.assert_called_once()
|
||||
|
||||
mock_plus_api.assert_called_once_with("")
|
||||
mock_api_instance._make_request.assert_called_once_with(
|
||||
"GET", "/crewai_plus/api/v1/me/provider?email=test%40example.com"
|
||||
)
|
||||
|
||||
mock_console_print.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
"Enter your CrewAI Enterprise account email: ",
|
||||
style="bold blue",
|
||||
end="",
|
||||
),
|
||||
call(
|
||||
"Error: Failed to authenticate with crewai enterprise. Ensure that you are using the latest crewai version and please try again. If the problem persists, contact support@crewai.com.",
|
||||
style="red",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@patch("requests.post")
|
||||
def test_get_device_code(self, mock_post):
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {
|
||||
"device_code": "test_device_code",
|
||||
"user_code": "123456",
|
||||
"verification_uri_complete": "https://example.com/auth",
|
||||
"id_token": "TOKEN",
|
||||
"access_token": "ACCESS_TOKEN",
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = self.auth_command._get_device_code(
|
||||
client_id="test_client",
|
||||
device_code_url="https://example.com/device",
|
||||
audience="test_audience",
|
||||
mock_instance = mock_tool.return_value
|
||||
mock_instance.login.return_value = None
|
||||
|
||||
self.auth_command._poll_for_token({"device_code": "123456"})
|
||||
|
||||
mock_validate_token.assert_called_once_with("TOKEN")
|
||||
mock_print.assert_called_once_with(
|
||||
"\n[bold green]Welcome to CrewAI Enterprise![/bold green]\n"
|
||||
)
|
||||
|
||||
mock_post.assert_called_once_with(
|
||||
url="https://example.com/device",
|
||||
data={
|
||||
"client_id": "test_client",
|
||||
"scope": "openid",
|
||||
"audience": "test_audience",
|
||||
},
|
||||
timeout=20,
|
||||
)
|
||||
|
||||
assert result == {
|
||||
"device_code": "test_device_code",
|
||||
"user_code": "123456",
|
||||
"verification_uri_complete": "https://example.com/auth",
|
||||
}
|
||||
|
||||
@patch("requests.post")
|
||||
@patch("crewai.cli.authentication.main.requests.post")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
def test_poll_for_token_success(self, mock_console_print, mock_post):
|
||||
mock_response_success = MagicMock()
|
||||
mock_response_success.status_code = 200
|
||||
mock_response_success.json.return_value = {
|
||||
"access_token": "test_access_token",
|
||||
"id_token": "test_id_token",
|
||||
def test_poll_for_token_error(self, mock_print, mock_post):
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 400
|
||||
mock_response.json.return_value = {
|
||||
"error": "invalid_request",
|
||||
"error_description": "Invalid request",
|
||||
}
|
||||
mock_post.return_value = mock_response_success
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
device_code_data = {"device_code": "test_device_code", "interval": 1}
|
||||
with self.assertRaises(requests.HTTPError):
|
||||
self.auth_command._poll_for_token({"device_code": "123456"})
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
self.auth_command, "_validate_and_save_token"
|
||||
) as mock_validate,
|
||||
patch.object(
|
||||
self.auth_command, "_login_to_tool_repository"
|
||||
) as mock_tool_login,
|
||||
):
|
||||
self.auth_command._poll_for_token(
|
||||
device_code_data, "test_client", "https://example.com/token"
|
||||
)
|
||||
mock_print.assert_not_called()
|
||||
|
||||
mock_post.assert_called_once_with(
|
||||
"https://example.com/token",
|
||||
data={
|
||||
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
||||
"device_code": "test_device_code",
|
||||
"client_id": "test_client",
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
mock_validate.assert_called_once()
|
||||
mock_tool_login.assert_called_once()
|
||||
|
||||
expected_calls = [
|
||||
call("\nWaiting for authentication... ", style="bold blue", end=""),
|
||||
call("Success!", style="bold green"),
|
||||
call("\n[bold green]Welcome to CrewAI Enterprise![/bold green]\n"),
|
||||
]
|
||||
mock_console_print.assert_has_calls(expected_calls)
|
||||
|
||||
@patch("requests.post")
|
||||
@patch("crewai.cli.authentication.main.requests.post")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
def test_poll_for_token_timeout(self, mock_console_print, mock_post):
|
||||
mock_response_pending = MagicMock()
|
||||
mock_response_pending.status_code = 400
|
||||
mock_response_pending.json.return_value = {"error": "authorization_pending"}
|
||||
mock_post.return_value = mock_response_pending
|
||||
|
||||
device_code_data = {
|
||||
"device_code": "test_device_code",
|
||||
"interval": 0.1, # Short interval for testing
|
||||
def test_poll_for_token_timeout(self, mock_print, mock_post):
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 400
|
||||
mock_response.json.return_value = {
|
||||
"error": "authorization_pending",
|
||||
"error_description": "Authorization pending",
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
self.auth_command._poll_for_token(
|
||||
device_code_data, "test_client", "https://example.com/token"
|
||||
)
|
||||
self.auth_command._poll_for_token({"device_code": "123456", "interval": 0.01})
|
||||
|
||||
mock_console_print.assert_any_call(
|
||||
mock_print.assert_called_once_with(
|
||||
"Timeout: Failed to get the token. Please try again.", style="bold red"
|
||||
)
|
||||
|
||||
@patch("requests.post")
|
||||
def test_poll_for_token_error(self, mock_post):
|
||||
"""Test the method to poll for token (error path)."""
|
||||
# Setup mock to return error
|
||||
mock_response_error = MagicMock()
|
||||
mock_response_error.status_code = 400
|
||||
mock_response_error.json.return_value = {
|
||||
"error": "access_denied",
|
||||
"error_description": "User denied access",
|
||||
}
|
||||
mock_post.return_value = mock_response_error
|
||||
|
||||
device_code_data = {"device_code": "test_device_code", "interval": 1}
|
||||
|
||||
with pytest.raises(requests.HTTPError):
|
||||
self.auth_command._poll_for_token(
|
||||
device_code_data, "test_client", "https://example.com/token"
|
||||
)
|
||||
|
||||
@@ -1,110 +1,31 @@
|
||||
import json
|
||||
import jwt
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
from crewai.cli.authentication.utils import TokenManager, validate_jwt_token
|
||||
from crewai.cli.authentication.utils import TokenManager, validate_token
|
||||
|
||||
|
||||
@patch("crewai.cli.authentication.utils.PyJWKClient", return_value=MagicMock())
|
||||
@patch("crewai.cli.authentication.utils.jwt")
|
||||
class TestValidateToken(unittest.TestCase):
|
||||
def test_validate_jwt_token(self, mock_jwt, mock_pyjwkclient):
|
||||
mock_jwt.decode.return_value = {"exp": 1719859200}
|
||||
@patch("crewai.cli.authentication.utils.AsymmetricSignatureVerifier")
|
||||
@patch("crewai.cli.authentication.utils.TokenVerifier")
|
||||
def test_validate_token(self, mock_token_verifier, mock_asymmetric_verifier):
|
||||
mock_verifier_instance = mock_token_verifier.return_value
|
||||
mock_id_token = "mock_id_token"
|
||||
|
||||
# Create signing key object mock with a .key attribute
|
||||
mock_pyjwkclient.return_value.get_signing_key_from_jwt.return_value = MagicMock(
|
||||
key="mock_signing_key"
|
||||
validate_token(mock_id_token)
|
||||
|
||||
mock_asymmetric_verifier.assert_called_once_with(
|
||||
"https://crewai.us.auth0.com/.well-known/jwks.json"
|
||||
)
|
||||
|
||||
decoded_token = validate_jwt_token(
|
||||
jwt_token="aaaaa.bbbbbb.cccccc",
|
||||
jwks_url="https://mock_jwks_url",
|
||||
issuer="https://mock_issuer",
|
||||
audience="app_id_xxxx",
|
||||
mock_token_verifier.assert_called_once_with(
|
||||
signature_verifier=mock_asymmetric_verifier.return_value,
|
||||
issuer="https://crewai.us.auth0.com/",
|
||||
audience="DEVC5Fw6NlRoSzmDCcOhVq85EfLBjKa8",
|
||||
)
|
||||
|
||||
mock_jwt.decode.assert_called_once_with(
|
||||
"aaaaa.bbbbbb.cccccc",
|
||||
"mock_signing_key",
|
||||
algorithms=["RS256"],
|
||||
audience="app_id_xxxx",
|
||||
issuer="https://mock_issuer",
|
||||
options={
|
||||
"verify_signature": True,
|
||||
"verify_exp": True,
|
||||
"verify_nbf": True,
|
||||
"verify_iat": True,
|
||||
"require": ["exp", "iat", "iss", "aud", "sub"],
|
||||
},
|
||||
)
|
||||
mock_pyjwkclient.assert_called_once_with("https://mock_jwks_url")
|
||||
self.assertEqual(decoded_token, {"exp": 1719859200})
|
||||
|
||||
def test_validate_jwt_token_expired(self, mock_jwt, mock_pyjwkclient):
|
||||
mock_jwt.decode.side_effect = jwt.ExpiredSignatureError
|
||||
with self.assertRaises(Exception):
|
||||
validate_jwt_token(
|
||||
jwt_token="aaaaa.bbbbbb.cccccc",
|
||||
jwks_url="https://mock_jwks_url",
|
||||
issuer="https://mock_issuer",
|
||||
audience="app_id_xxxx",
|
||||
)
|
||||
|
||||
def test_validate_jwt_token_invalid_audience(self, mock_jwt, mock_pyjwkclient):
|
||||
mock_jwt.decode.side_effect = jwt.InvalidAudienceError
|
||||
with self.assertRaises(Exception):
|
||||
validate_jwt_token(
|
||||
jwt_token="aaaaa.bbbbbb.cccccc",
|
||||
jwks_url="https://mock_jwks_url",
|
||||
issuer="https://mock_issuer",
|
||||
audience="app_id_xxxx",
|
||||
)
|
||||
|
||||
def test_validate_jwt_token_invalid_issuer(self, mock_jwt, mock_pyjwkclient):
|
||||
mock_jwt.decode.side_effect = jwt.InvalidIssuerError
|
||||
with self.assertRaises(Exception):
|
||||
validate_jwt_token(
|
||||
jwt_token="aaaaa.bbbbbb.cccccc",
|
||||
jwks_url="https://mock_jwks_url",
|
||||
issuer="https://mock_issuer",
|
||||
audience="app_id_xxxx",
|
||||
)
|
||||
|
||||
def test_validate_jwt_token_missing_required_claims(
|
||||
self, mock_jwt, mock_pyjwkclient
|
||||
):
|
||||
mock_jwt.decode.side_effect = jwt.MissingRequiredClaimError
|
||||
with self.assertRaises(Exception):
|
||||
validate_jwt_token(
|
||||
jwt_token="aaaaa.bbbbbb.cccccc",
|
||||
jwks_url="https://mock_jwks_url",
|
||||
issuer="https://mock_issuer",
|
||||
audience="app_id_xxxx",
|
||||
)
|
||||
|
||||
def test_validate_jwt_token_jwks_error(self, mock_jwt, mock_pyjwkclient):
|
||||
mock_jwt.decode.side_effect = jwt.exceptions.PyJWKClientError
|
||||
with self.assertRaises(Exception):
|
||||
validate_jwt_token(
|
||||
jwt_token="aaaaa.bbbbbb.cccccc",
|
||||
jwks_url="https://mock_jwks_url",
|
||||
issuer="https://mock_issuer",
|
||||
audience="app_id_xxxx",
|
||||
)
|
||||
|
||||
def test_validate_jwt_token_invalid_token(self, mock_jwt, mock_pyjwkclient):
|
||||
mock_jwt.decode.side_effect = jwt.InvalidTokenError
|
||||
with self.assertRaises(Exception):
|
||||
validate_jwt_token(
|
||||
jwt_token="aaaaa.bbbbbb.cccccc",
|
||||
jwks_url="https://mock_jwks_url",
|
||||
issuer="https://mock_issuer",
|
||||
audience="app_id_xxxx",
|
||||
)
|
||||
mock_verifier_instance.verify.assert_called_once_with(mock_id_token)
|
||||
|
||||
|
||||
class TestTokenManager(unittest.TestCase):
|
||||
@@ -141,9 +62,9 @@ class TestTokenManager(unittest.TestCase):
|
||||
@patch("crewai.cli.authentication.utils.TokenManager.save_secure_file")
|
||||
def test_save_tokens(self, mock_save):
|
||||
access_token = "test_token"
|
||||
expires_at = int((datetime.now() + timedelta(seconds=3600)).timestamp())
|
||||
expires_in = 3600
|
||||
|
||||
self.token_manager.save_tokens(access_token, expires_at)
|
||||
self.token_manager.save_tokens(access_token, expires_in)
|
||||
|
||||
mock_save.assert_called_once()
|
||||
args = mock_save.call_args[0]
|
||||
@@ -152,7 +73,11 @@ class TestTokenManager(unittest.TestCase):
|
||||
data = json.loads(decrypted_data)
|
||||
self.assertEqual(data["access_token"], access_token)
|
||||
expiration = datetime.fromisoformat(data["expiration"])
|
||||
self.assertEqual(expiration, datetime.fromtimestamp(expires_at))
|
||||
self.assertAlmostEqual(
|
||||
expiration,
|
||||
datetime.now() + timedelta(seconds=expires_in),
|
||||
delta=timedelta(seconds=1),
|
||||
)
|
||||
|
||||
@patch("crewai.cli.authentication.utils.TokenManager.read_secure_file")
|
||||
def test_get_token_valid(self, mock_read):
|
||||
|
||||
@@ -13,7 +13,7 @@ from crewai.cli.cli import (
|
||||
deply_status,
|
||||
flow_add_crew,
|
||||
reset_memories,
|
||||
login,
|
||||
signup,
|
||||
test,
|
||||
train,
|
||||
version,
|
||||
@@ -261,12 +261,12 @@ def test_test_invalid_string_iterations(evaluate_crew, runner):
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.cli.AuthenticationCommand")
|
||||
def test_login(command, runner):
|
||||
def test_signup(command, runner):
|
||||
mock_auth = command.return_value
|
||||
result = runner.invoke(login)
|
||||
result = runner.invoke(signup)
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_auth.login.assert_called_once()
|
||||
mock_auth.signup.assert_called_once()
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.cli.DeployCommand")
|
||||
|
||||
@@ -2,7 +2,6 @@ import os
|
||||
import tempfile
|
||||
import unittest
|
||||
import unittest.mock
|
||||
from datetime import datetime, timedelta
|
||||
from contextlib import contextmanager
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, patch
|
||||
@@ -27,9 +26,7 @@ def in_temp_dir():
|
||||
|
||||
@pytest.fixture
|
||||
def tool_command():
|
||||
TokenManager().save_tokens(
|
||||
"test-token", (datetime.now() + timedelta(seconds=36000)).timestamp()
|
||||
)
|
||||
TokenManager().save_tokens("test-token", 36000)
|
||||
tool_command = ToolCommand()
|
||||
with patch.object(tool_command, "login"):
|
||||
yield tool_command
|
||||
@@ -60,9 +57,7 @@ def test_create_success(mock_subprocess, capsys, tool_command):
|
||||
@patch("crewai.cli.tools.main.subprocess.run")
|
||||
@patch("crewai.cli.plus_api.PlusAPI.get_tool")
|
||||
@patch("crewai.cli.tools.main.ToolCommand._print_current_organization")
|
||||
def test_install_success(
|
||||
mock_print_org, mock_get, mock_subprocess_run, capsys, tool_command
|
||||
):
|
||||
def test_install_success(mock_print_org, mock_get, mock_subprocess_run, capsys, tool_command):
|
||||
mock_get_response = MagicMock()
|
||||
mock_get_response.status_code = 200
|
||||
mock_get_response.json.return_value = {
|
||||
@@ -94,7 +89,6 @@ def test_install_success(
|
||||
# Verify _print_current_organization was called
|
||||
mock_print_org.assert_called_once()
|
||||
|
||||
|
||||
@patch("crewai.cli.tools.main.subprocess.run")
|
||||
@patch("crewai.cli.plus_api.PlusAPI.get_tool")
|
||||
def test_install_success_from_pypi(mock_get, mock_subprocess_run, capsys, tool_command):
|
||||
@@ -175,10 +169,7 @@ def test_publish_when_not_in_sync(mock_is_synced, capsys, tool_command):
|
||||
)
|
||||
@patch("crewai.cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=False)
|
||||
@patch(
|
||||
"crewai.cli.tools.main.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch("crewai.cli.tools.main.extract_available_exports", return_value=[{"name": "SampleTool"}])
|
||||
@patch("crewai.cli.tools.main.ToolCommand._print_current_organization")
|
||||
def test_publish_when_not_in_sync_and_force(
|
||||
mock_print_org,
|
||||
@@ -232,10 +223,7 @@ def test_publish_when_not_in_sync_and_force(
|
||||
)
|
||||
@patch("crewai.cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=True)
|
||||
@patch(
|
||||
"crewai.cli.tools.main.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch("crewai.cli.tools.main.extract_available_exports", return_value=[{"name": "SampleTool"}])
|
||||
def test_publish_success(
|
||||
mock_available_exports,
|
||||
mock_is_synced,
|
||||
@@ -285,10 +273,7 @@ def test_publish_success(
|
||||
read_data=b"sample tarball content",
|
||||
)
|
||||
@patch("crewai.cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch(
|
||||
"crewai.cli.tools.main.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch("crewai.cli.tools.main.extract_available_exports", return_value=[{"name": "SampleTool"}])
|
||||
def test_publish_failure(
|
||||
mock_available_exports,
|
||||
mock_publish,
|
||||
@@ -326,10 +311,7 @@ def test_publish_failure(
|
||||
read_data=b"sample tarball content",
|
||||
)
|
||||
@patch("crewai.cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch(
|
||||
"crewai.cli.tools.main.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch("crewai.cli.tools.main.extract_available_exports", return_value=[{"name": "SampleTool"}])
|
||||
def test_publish_api_error(
|
||||
mock_available_exports,
|
||||
mock_publish,
|
||||
@@ -356,6 +338,7 @@ def test_publish_api_error(
|
||||
mock_publish.assert_called_once()
|
||||
|
||||
|
||||
|
||||
@patch("crewai.cli.tools.main.Settings")
|
||||
def test_print_current_organization_with_org(mock_settings, capsys, tool_command):
|
||||
mock_settings_instance = MagicMock()
|
||||
|
||||
@@ -52,7 +52,6 @@ from crewai.utilities.events.memory_events import (
|
||||
MemoryRetrievalCompletedEvent,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ceo():
|
||||
return Agent(
|
||||
@@ -936,27 +935,12 @@ def test_cache_hitting_between_agents(researcher, writer, ceo):
|
||||
read.return_value = "12"
|
||||
crew.kickoff()
|
||||
assert read.call_count == 2, "read was not called exactly twice"
|
||||
|
||||
# Filter the mock calls to only include the ones with 'tool' and 'input' keywords
|
||||
cache_calls = [
|
||||
call
|
||||
for call in read.call_args_list
|
||||
if len(call.kwargs) == 2
|
||||
and "tool" in call.kwargs
|
||||
and "input" in call.kwargs
|
||||
# Check if read was called with the expected arguments
|
||||
expected_calls = [
|
||||
call(tool="multiplier", input={"first_number": 2, "second_number": 6}),
|
||||
call(tool="multiplier", input={"first_number": 2, "second_number": 6}),
|
||||
]
|
||||
|
||||
# Check if we have the expected number of cache calls
|
||||
assert len(cache_calls) == 2, f"Expected 2 cache calls, got {len(cache_calls)}"
|
||||
|
||||
# Check if both calls were made with the expected arguments
|
||||
expected_call = call(
|
||||
tool="multiplier", input={"first_number": 2, "second_number": 6}
|
||||
)
|
||||
assert cache_calls[0] == expected_call, f"First call mismatch: {cache_calls[0]}"
|
||||
assert (
|
||||
cache_calls[1] == expected_call
|
||||
), f"Second call mismatch: {cache_calls[1]}"
|
||||
read.assert_has_calls(expected_calls, any_order=False)
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
@@ -1813,7 +1797,7 @@ def test_hierarchical_kickoff_usage_metrics_include_manager(researcher):
|
||||
agent=researcher, # *regular* agent
|
||||
)
|
||||
|
||||
# ── 2. Stub out each agent's _token_process.get_summary() ───────────────────
|
||||
# ── 2. Stub out each agent’s _token_process.get_summary() ───────────────────
|
||||
researcher_metrics = UsageMetrics(
|
||||
total_tokens=120, prompt_tokens=80, completion_tokens=40, successful_requests=2
|
||||
)
|
||||
@@ -1837,7 +1821,7 @@ def test_hierarchical_kickoff_usage_metrics_include_manager(researcher):
|
||||
process=Process.hierarchical,
|
||||
)
|
||||
|
||||
# We don't care about LLM output here; patch execute_sync to avoid network
|
||||
# We don’t care about LLM output here; patch execute_sync to avoid network
|
||||
with patch.object(
|
||||
Task,
|
||||
"execute_sync",
|
||||
@@ -2505,19 +2489,17 @@ def test_using_contextual_memory():
|
||||
memory=True,
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
ContextualMemory, "build_context_for_task", return_value=""
|
||||
) as contextual_mem:
|
||||
with patch.object(ContextualMemory, "build_context_for_task", return_value="") as contextual_mem:
|
||||
crew.kickoff()
|
||||
contextual_mem.assert_called_once()
|
||||
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_memory_events_are_emitted():
|
||||
events = defaultdict(list)
|
||||
|
||||
with crewai_event_bus.scoped_handlers():
|
||||
|
||||
@crewai_event_bus.on(MemorySaveStartedEvent)
|
||||
def handle_memory_save_started(source, event):
|
||||
events["MemorySaveStartedEvent"].append(event)
|
||||
@@ -2580,7 +2562,6 @@ def test_memory_events_are_emitted():
|
||||
assert len(events["MemoryRetrievalStartedEvent"]) == 1
|
||||
assert len(events["MemoryRetrievalCompletedEvent"]) == 1
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_using_contextual_memory_with_long_term_memory():
|
||||
from unittest.mock import patch
|
||||
@@ -2604,9 +2585,7 @@ def test_using_contextual_memory_with_long_term_memory():
|
||||
long_term_memory=LongTermMemory(),
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
ContextualMemory, "build_context_for_task", return_value=""
|
||||
) as contextual_mem:
|
||||
with patch.object(ContextualMemory, "build_context_for_task", return_value="") as contextual_mem:
|
||||
crew.kickoff()
|
||||
contextual_mem.assert_called_once()
|
||||
assert crew.memory is False
|
||||
@@ -2707,9 +2686,7 @@ def test_using_contextual_memory_with_short_term_memory():
|
||||
short_term_memory=ShortTermMemory(),
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
ContextualMemory, "build_context_for_task", return_value=""
|
||||
) as contextual_mem:
|
||||
with patch.object(ContextualMemory, "build_context_for_task", return_value="") as contextual_mem:
|
||||
crew.kickoff()
|
||||
contextual_mem.assert_called_once()
|
||||
assert crew.memory is False
|
||||
@@ -2738,9 +2715,7 @@ def test_disabled_memory_using_contextual_memory():
|
||||
memory=False,
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
ContextualMemory, "build_context_for_task", return_value=""
|
||||
) as contextual_mem:
|
||||
with patch.object(ContextualMemory, "build_context_for_task", return_value="") as contextual_mem:
|
||||
crew.kickoff()
|
||||
contextual_mem.assert_not_called()
|
||||
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
import asyncio
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Dict, Any, Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from crewai import Agent, Crew, Task
|
||||
from crewai.utilities.crew.crew_context import get_crew_context
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def simple_agent_factory():
|
||||
def create_agent(name: str) -> Agent:
|
||||
return Agent(
|
||||
role=f"{name} Agent",
|
||||
goal=f"Complete {name} task",
|
||||
backstory=f"I am agent for {name}",
|
||||
)
|
||||
|
||||
return create_agent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def simple_task_factory():
|
||||
def create_task(name: str, callback: Callable = None) -> Task:
|
||||
return Task(
|
||||
description=f"Task for {name}", expected_output="Done", callback=callback
|
||||
)
|
||||
|
||||
return create_task
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def crew_factory(simple_agent_factory, simple_task_factory):
|
||||
def create_crew(name: str, task_callback: Callable = None) -> Crew:
|
||||
agent = simple_agent_factory(name)
|
||||
task = simple_task_factory(name, callback=task_callback)
|
||||
task.agent = agent
|
||||
|
||||
return Crew(agents=[agent], tasks=[task], verbose=False)
|
||||
|
||||
return create_crew
|
||||
|
||||
|
||||
class TestCrewThreadSafety:
|
||||
@patch("crewai.Agent.execute_task")
|
||||
def test_parallel_crews_thread_safety(self, mock_execute_task, crew_factory):
|
||||
mock_execute_task.return_value = "Task completed"
|
||||
num_crews = 5
|
||||
|
||||
def run_crew_with_context_check(crew_id: str) -> Dict[str, Any]:
|
||||
results = {"crew_id": crew_id, "contexts": []}
|
||||
|
||||
def check_context_task(output):
|
||||
context = get_crew_context()
|
||||
results["contexts"].append(
|
||||
{
|
||||
"stage": "task_callback",
|
||||
"crew_id": context.id if context else None,
|
||||
"crew_key": context.key if context else None,
|
||||
"thread": threading.current_thread().name,
|
||||
}
|
||||
)
|
||||
return output
|
||||
|
||||
context_before = get_crew_context()
|
||||
results["contexts"].append(
|
||||
{
|
||||
"stage": "before_kickoff",
|
||||
"crew_id": context_before.id if context_before else None,
|
||||
"thread": threading.current_thread().name,
|
||||
}
|
||||
)
|
||||
|
||||
crew = crew_factory(crew_id, task_callback=check_context_task)
|
||||
output = crew.kickoff()
|
||||
|
||||
context_after = get_crew_context()
|
||||
results["contexts"].append(
|
||||
{
|
||||
"stage": "after_kickoff",
|
||||
"crew_id": context_after.id if context_after else None,
|
||||
"thread": threading.current_thread().name,
|
||||
}
|
||||
)
|
||||
|
||||
results["crew_uuid"] = str(crew.id)
|
||||
results["output"] = output.raw
|
||||
|
||||
return results
|
||||
|
||||
with ThreadPoolExecutor(max_workers=num_crews) as executor:
|
||||
futures = []
|
||||
for i in range(num_crews):
|
||||
future = executor.submit(run_crew_with_context_check, f"crew_{i}")
|
||||
futures.append(future)
|
||||
|
||||
results = [f.result() for f in futures]
|
||||
|
||||
for result in results:
|
||||
crew_uuid = result["crew_uuid"]
|
||||
|
||||
before_ctx = next(
|
||||
ctx for ctx in result["contexts"] if ctx["stage"] == "before_kickoff"
|
||||
)
|
||||
assert (
|
||||
before_ctx["crew_id"] is None
|
||||
), f"Context should be None before kickoff for {result['crew_id']}"
|
||||
|
||||
task_ctx = next(
|
||||
ctx for ctx in result["contexts"] if ctx["stage"] == "task_callback"
|
||||
)
|
||||
assert (
|
||||
task_ctx["crew_id"] == crew_uuid
|
||||
), f"Context mismatch during task for {result['crew_id']}"
|
||||
|
||||
after_ctx = next(
|
||||
ctx for ctx in result["contexts"] if ctx["stage"] == "after_kickoff"
|
||||
)
|
||||
assert (
|
||||
after_ctx["crew_id"] is None
|
||||
), f"Context should be None after kickoff for {result['crew_id']}"
|
||||
|
||||
thread_name = before_ctx["thread"]
|
||||
assert (
|
||||
"ThreadPoolExecutor" in thread_name
|
||||
), f"Should run in thread pool for {result['crew_id']}"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("crewai.Agent.execute_task")
|
||||
async def test_async_crews_thread_safety(self, mock_execute_task, crew_factory):
|
||||
mock_execute_task.return_value = "Task completed"
|
||||
num_crews = 5
|
||||
|
||||
async def run_crew_async(crew_id: str) -> Dict[str, Any]:
|
||||
task_context = {"crew_id": crew_id, "context": None}
|
||||
|
||||
def capture_context(output):
|
||||
ctx = get_crew_context()
|
||||
task_context["context"] = {
|
||||
"crew_id": ctx.id if ctx else None,
|
||||
"crew_key": ctx.key if ctx else None,
|
||||
}
|
||||
return output
|
||||
|
||||
crew = crew_factory(crew_id, task_callback=capture_context)
|
||||
output = await crew.kickoff_async()
|
||||
|
||||
return {
|
||||
"crew_id": crew_id,
|
||||
"crew_uuid": str(crew.id),
|
||||
"output": output.raw,
|
||||
"task_context": task_context,
|
||||
}
|
||||
|
||||
tasks = [run_crew_async(f"async_crew_{i}") for i in range(num_crews)]
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
for result in results:
|
||||
crew_uuid = result["crew_uuid"]
|
||||
task_ctx = result["task_context"]["context"]
|
||||
|
||||
assert (
|
||||
task_ctx is not None
|
||||
), f"Context should exist during task for {result['crew_id']}"
|
||||
assert (
|
||||
task_ctx["crew_id"] == crew_uuid
|
||||
), f"Context mismatch for {result['crew_id']}"
|
||||
|
||||
@patch("crewai.Agent.execute_task")
|
||||
def test_concurrent_kickoff_for_each(self, mock_execute_task, crew_factory):
|
||||
mock_execute_task.return_value = "Task completed"
|
||||
contexts_captured = []
|
||||
|
||||
def capture_context(output):
|
||||
ctx = get_crew_context()
|
||||
contexts_captured.append(
|
||||
{
|
||||
"context_id": ctx.id if ctx else None,
|
||||
"thread": threading.current_thread().name,
|
||||
}
|
||||
)
|
||||
return output
|
||||
|
||||
crew = crew_factory("for_each_test", task_callback=capture_context)
|
||||
inputs = [{"item": f"input_{i}"} for i in range(3)]
|
||||
|
||||
results = crew.kickoff_for_each(inputs=inputs)
|
||||
|
||||
assert len(results) == len(inputs)
|
||||
assert len(contexts_captured) == len(inputs)
|
||||
|
||||
context_ids = [ctx["context_id"] for ctx in contexts_captured]
|
||||
assert len(set(context_ids)) == len(
|
||||
inputs
|
||||
), "Each execution should have unique context"
|
||||
|
||||
@patch("crewai.Agent.execute_task")
|
||||
def test_no_context_leakage_between_crews(self, mock_execute_task, crew_factory):
|
||||
mock_execute_task.return_value = "Task completed"
|
||||
contexts = []
|
||||
|
||||
def check_context(output):
|
||||
ctx = get_crew_context()
|
||||
contexts.append(
|
||||
{
|
||||
"context_id": ctx.id if ctx else None,
|
||||
"context_key": ctx.key if ctx else None,
|
||||
}
|
||||
)
|
||||
return output
|
||||
|
||||
def run_crew(name: str):
|
||||
crew = crew_factory(name, task_callback=check_context)
|
||||
crew.kickoff()
|
||||
return str(crew.id)
|
||||
|
||||
crew1_id = run_crew("First")
|
||||
crew2_id = run_crew("Second")
|
||||
|
||||
assert len(contexts) == 2
|
||||
assert contexts[0]["context_id"] == crew1_id
|
||||
assert contexts[1]["context_id"] == crew2_id
|
||||
assert contexts[0]["context_id"] != contexts[1]["context_id"]
|
||||
@@ -1,88 +0,0 @@
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
from opentelemetry import baggage
|
||||
from opentelemetry.context import attach, detach
|
||||
|
||||
from crewai.utilities.crew.crew_context import get_crew_context
|
||||
from crewai.utilities.crew.models import CrewContext
|
||||
|
||||
|
||||
def test_crew_context_creation():
|
||||
crew_id = str(uuid.uuid4())
|
||||
context = CrewContext(id=crew_id, key="test-crew")
|
||||
assert context.id == crew_id
|
||||
assert context.key == "test-crew"
|
||||
|
||||
|
||||
def test_get_crew_context_with_baggage():
|
||||
crew_id = str(uuid.uuid4())
|
||||
assert get_crew_context() is None
|
||||
|
||||
crew_ctx = CrewContext(id=crew_id, key="test-key")
|
||||
ctx = baggage.set_baggage("crew_context", crew_ctx)
|
||||
token = attach(ctx)
|
||||
|
||||
try:
|
||||
context = get_crew_context()
|
||||
assert context is not None
|
||||
assert context.id == crew_id
|
||||
assert context.key == "test-key"
|
||||
finally:
|
||||
detach(token)
|
||||
|
||||
assert get_crew_context() is None
|
||||
|
||||
|
||||
def test_get_crew_context_empty():
|
||||
assert get_crew_context() is None
|
||||
|
||||
|
||||
def test_baggage_nested_contexts():
|
||||
crew_id1 = str(uuid.uuid4())
|
||||
crew_id2 = str(uuid.uuid4())
|
||||
|
||||
crew_ctx1 = CrewContext(id=crew_id1, key="outer")
|
||||
ctx1 = baggage.set_baggage("crew_context", crew_ctx1)
|
||||
token1 = attach(ctx1)
|
||||
|
||||
try:
|
||||
outer_context = get_crew_context()
|
||||
assert outer_context.id == crew_id1
|
||||
assert outer_context.key == "outer"
|
||||
|
||||
crew_ctx2 = CrewContext(id=crew_id2, key="inner")
|
||||
ctx2 = baggage.set_baggage("crew_context", crew_ctx2)
|
||||
token2 = attach(ctx2)
|
||||
|
||||
try:
|
||||
inner_context = get_crew_context()
|
||||
assert inner_context.id == crew_id2
|
||||
assert inner_context.key == "inner"
|
||||
finally:
|
||||
detach(token2)
|
||||
|
||||
restored_context = get_crew_context()
|
||||
assert restored_context.id == crew_id1
|
||||
assert restored_context.key == "outer"
|
||||
finally:
|
||||
detach(token1)
|
||||
|
||||
assert get_crew_context() is None
|
||||
|
||||
|
||||
def test_baggage_exception_handling():
|
||||
crew_id = str(uuid.uuid4())
|
||||
|
||||
crew_ctx = CrewContext(id=crew_id, key="test")
|
||||
ctx = baggage.set_baggage("crew_context", crew_ctx)
|
||||
token = attach(ctx)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
try:
|
||||
assert get_crew_context() is not None
|
||||
raise ValueError("Test exception")
|
||||
finally:
|
||||
detach(token)
|
||||
|
||||
assert get_crew_context() is None
|
||||
Reference in New Issue
Block a user