quick fix for crewai
This commit is contained in:
parent
8a5bd3a826
commit
484520eba5
3 changed files with 101 additions and 66 deletions
|
|
@ -1,31 +1,36 @@
|
||||||
from crewai import Agent, Crew, Process, Task
|
from crewai import Agent, Crew, Process, Task
|
||||||
from crewai.project import CrewBase, agent, crew, task
|
from crewai.project import CrewBase, agent, crew, task, before_kickoff
|
||||||
from .tools import CogneeAdd, CogneeSearch
|
from .tools import CogneeAdd, CogneeSearch
|
||||||
|
|
||||||
from crewai_tools import (
|
from crewai_tools import DirectoryReadTool
|
||||||
DirectoryReadTool
|
|
||||||
)
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
docs_tool = DirectoryReadTool(directory='/Users/vasilije/cognee/examples/python/latest_ai_development/src/latest_ai_development/multimedia')
|
# Determine multimedia input directory (can be overridden via env var)
|
||||||
|
multimedia_dir = os.getenv("MULTIMEDIA_DIR", os.path.join(os.path.dirname(__file__), "multimedia"))
|
||||||
|
docs_tool = DirectoryReadTool(directory=multimedia_dir)
|
||||||
|
|
||||||
|
|
||||||
# Utility function to format paths with file:// prefix
|
# Utility function to format paths with file:// prefix
|
||||||
def format_file_paths(paths):
|
def format_file_paths(paths):
|
||||||
"""
|
"""
|
||||||
Formats a list of file paths with 'file://' prefix
|
Formats a list of file paths with 'file://' prefix
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
paths: A string representing the output of DirectoryReadTool containing file paths
|
paths: A string representing the output of DirectoryReadTool containing file paths
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A formatted string where each path is prefixed with 'file://'
|
A formatted string where each path is prefixed with 'file://'
|
||||||
"""
|
"""
|
||||||
if isinstance(paths, str):
|
if isinstance(paths, str):
|
||||||
# Split the paths by newline if it's a string output
|
# Split the paths by newline if it's a string output
|
||||||
file_list = [line for line in paths.split('\n') if line.strip()]
|
file_list = [line for line in paths.split("\n") if line.strip()]
|
||||||
# Format each path with file:// prefix
|
# Format each path with file:// prefix
|
||||||
formatted_paths = [f"file://{os.path.abspath(path.strip())}" for path in file_list if "File paths:" not in path]
|
formatted_paths = [
|
||||||
return '\n'.join(formatted_paths)
|
f"file://{os.path.abspath(path.strip())}"
|
||||||
|
for path in file_list
|
||||||
|
if "File paths:" not in path
|
||||||
|
]
|
||||||
|
return "\n".join(formatted_paths)
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -50,38 +55,43 @@ class LatestAiDevelopment:
|
||||||
def researcher(self) -> Agent:
|
def researcher(self) -> Agent:
|
||||||
# Initialize the tools with different nodesets
|
# Initialize the tools with different nodesets
|
||||||
cognee_search = CogneeSearch()
|
cognee_search = CogneeSearch()
|
||||||
|
|
||||||
# CogneeAdd for documents with a "documents" nodeset
|
# CogneeAdd for documents with a "documents" nodeset
|
||||||
documents_cognee_add = CogneeAdd()
|
documents_cognee_add = CogneeAdd()
|
||||||
documents_cognee_add.default_nodeset = ["documents"]
|
documents_cognee_add.default_nodeset = ["documents"]
|
||||||
documents_cognee_add.name = "Add Documents to Memory"
|
documents_cognee_add.name = "Add Documents to Memory"
|
||||||
documents_cognee_add.description = "Add document content to Cognee memory with documents nodeset"
|
documents_cognee_add.description = (
|
||||||
|
"Add document content to Cognee memory with documents nodeset"
|
||||||
|
)
|
||||||
|
|
||||||
# CogneeAdd for reasoning/analysis with a "reasoning" nodeset
|
# CogneeAdd for reasoning/analysis with a "reasoning" nodeset
|
||||||
reasoning_cognee_add = CogneeAdd()
|
reasoning_cognee_add = CogneeAdd()
|
||||||
reasoning_cognee_add.default_nodeset = ["reasoning"]
|
reasoning_cognee_add.default_nodeset = ["reasoning"]
|
||||||
reasoning_cognee_add.name = "Add Reasoning to Memory"
|
reasoning_cognee_add.name = "Add Reasoning to Memory"
|
||||||
reasoning_cognee_add.description = "Add reasoning and analysis text to Cognee memory with reasoning nodeset"
|
reasoning_cognee_add.description = (
|
||||||
|
"Add reasoning and analysis text to Cognee memory with reasoning nodeset"
|
||||||
|
)
|
||||||
|
|
||||||
# Create a wrapper for the DirectoryReadTool that formats output
|
# Create a wrapper for the DirectoryReadTool that formats output
|
||||||
class FormattedDirectoryReadTool(DirectoryReadTool):
|
class FormattedDirectoryReadTool(DirectoryReadTool):
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
result = super().__call__(*args, **kwargs)
|
result = super().__call__(*args, **kwargs)
|
||||||
return format_file_paths(result)
|
return format_file_paths(result)
|
||||||
|
|
||||||
formatted_docs_tool = FormattedDirectoryReadTool(directory='/Users/vasilije/cognee/examples/python/latest_ai_development/src/latest_ai_development/multimedia')
|
# Use the project-local multimedia directory
|
||||||
|
formatted_docs_tool = FormattedDirectoryReadTool(directory=multimedia_dir)
|
||||||
|
|
||||||
return Agent(
|
return Agent(
|
||||||
config=self.agents_config["researcher"],
|
config=self.agents_config["researcher"],
|
||||||
tools=[formatted_docs_tool, documents_cognee_add, reasoning_cognee_add, cognee_search],
|
tools=[formatted_docs_tool, documents_cognee_add, reasoning_cognee_add, cognee_search],
|
||||||
verbose=True
|
verbose=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@agent
|
@agent
|
||||||
def reporting_analyst(self) -> Agent:
|
def reporting_analyst(self) -> Agent:
|
||||||
# Initialize the tools with default parameters
|
# Initialize the tools with default parameters
|
||||||
cognee_search = CogneeSearch()
|
cognee_search = CogneeSearch()
|
||||||
|
|
||||||
# Reporting analyst can use a "reports" nodeset
|
# Reporting analyst can use a "reports" nodeset
|
||||||
reports_cognee_add = CogneeAdd()
|
reports_cognee_add = CogneeAdd()
|
||||||
reports_cognee_add.default_nodeset = ["reports"]
|
reports_cognee_add.default_nodeset = ["reports"]
|
||||||
|
|
@ -107,16 +117,23 @@ class LatestAiDevelopment:
|
||||||
def reporting_task(self) -> Task:
|
def reporting_task(self) -> Task:
|
||||||
return Task(config=self.tasks_config["reporting_task"], output_file="report.md")
|
return Task(config=self.tasks_config["reporting_task"], output_file="report.md")
|
||||||
|
|
||||||
|
@before_kickoff
|
||||||
|
def dump_env(self, *args, **kwargs):
|
||||||
|
"""Print environment variables at startup."""
|
||||||
|
print("=== Environment Variables ===")
|
||||||
|
for key in sorted(os.environ):
|
||||||
|
print(f"{key}={os.environ[key]}")
|
||||||
|
|
||||||
@crew
|
@crew
|
||||||
def crew(self) -> Crew:
|
def crew(self) -> Crew:
|
||||||
"""Creates the LatestAiDevelopment crew"""
|
"""Creates the LatestAiDevelopment crew"""
|
||||||
# To learn how to add knowledge sources to your crew, check out the documentation:
|
# To learn how to add knowledge sources to your crew, check out the documentation:
|
||||||
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
|
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
|
||||||
|
print(self.tasks)
|
||||||
return Crew(
|
return Crew(
|
||||||
agents=self.agents, # Automatically created by the @agent decorator
|
agents=self.agents, # Automatically created by the @agent decorator
|
||||||
tasks=self.tasks, # Automatically created by the @task decorator
|
tasks=self.tasks, # Automatically created by the @task decorator
|
||||||
process=Process.sequential,
|
process=Process.sequential,
|
||||||
verbose=True,
|
verbose=True,
|
||||||
# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/
|
# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
This is a dummy text file for testing DirectoryReadTool.
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
from crewai.tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
from typing import Type, Dict, Any, List, Optional, Union
|
from typing import Type, List, Optional
|
||||||
from pydantic import BaseModel, Field, root_validator
|
from pydantic import BaseModel, Field, root_validator
|
||||||
from cognee.api.v1.search import SearchType
|
from cognee.api.v1.search import SearchType
|
||||||
from cognee.modules.engine.models.Entity import Entity
|
|
||||||
from cognee.api.v1.search import SearchType
|
|
||||||
from cognee.modules.users.methods import get_default_user
|
from cognee.modules.users.methods import get_default_user
|
||||||
from cognee.modules.pipelines import run_tasks, Task
|
from cognee.modules.pipelines import run_tasks, Task
|
||||||
from cognee.tasks.experimental_tasks.node_set_edge_association import node_set_edge_association
|
from cognee.tasks.experimental_tasks.node_set_edge_association import node_set_edge_association
|
||||||
|
|
@ -13,27 +11,44 @@ class CogneeAddInput(BaseModel):
|
||||||
"""Input schema for CogneeAdd tool."""
|
"""Input schema for CogneeAdd tool."""
|
||||||
|
|
||||||
context: Optional[str] = Field(None, description="The text content to add to Cognee memory.")
|
context: Optional[str] = Field(None, description="The text content to add to Cognee memory.")
|
||||||
file_paths: Optional[List[str]] = Field(None, description="List of file paths to add to Cognee memory.")
|
file_paths: Optional[List[str]] = Field(
|
||||||
text: Optional[str] = Field(None, description="Alternative field for text content (maps to context).")
|
None, description="List of file paths to add to Cognee memory."
|
||||||
reasoning: Optional[str] = Field(None, description="Alternative field for reasoning text (maps to context).")
|
)
|
||||||
|
files: Optional[List[str]] = Field(
|
||||||
|
None, description="Alias for file_paths; list of file URLs or paths to add to memory."
|
||||||
|
)
|
||||||
|
text: Optional[str] = Field(
|
||||||
|
None, description="Alternative field for text content (maps to context)."
|
||||||
|
)
|
||||||
|
reasoning: Optional[str] = Field(
|
||||||
|
None, description="Alternative field for reasoning text (maps to context)."
|
||||||
|
)
|
||||||
node_set: List[str] = Field(
|
node_set: List[str] = Field(
|
||||||
default=["default"], description="The list of node sets to store the data in."
|
default=["default"], description="The list of node sets to store the data in."
|
||||||
)
|
)
|
||||||
|
|
||||||
@root_validator(pre=True)
|
@root_validator(pre=True)
|
||||||
def normalize_inputs(cls, values):
|
def normalize_inputs(cls, values):
|
||||||
"""Normalize different input formats to standard fields."""
|
"""Normalize different input formats to standard fields."""
|
||||||
|
# Map alias 'files' to 'file_paths' if provided
|
||||||
|
if values.get("files") and not values.get("file_paths"):
|
||||||
|
values["file_paths"] = values.get("files")
|
||||||
# Map text or reasoning to context if provided
|
# Map text or reasoning to context if provided
|
||||||
if values.get('text') and not values.get('context'):
|
if values.get("text") and not values.get("context"):
|
||||||
values['context'] = values.get('text')
|
values["context"] = values.get("text")
|
||||||
|
|
||||||
if values.get('reasoning') and not values.get('context'):
|
if values.get("reasoning") and not values.get("context"):
|
||||||
values['context'] = values.get('reasoning')
|
values["context"] = values.get("reasoning")
|
||||||
|
# Map report_section to context if provided
|
||||||
|
if values.get("report_section") and not values.get("context"):
|
||||||
|
values["context"] = values.get("report_section")
|
||||||
|
|
||||||
# Validate that at least one input field is provided
|
# Validate that at least one input field is provided
|
||||||
if not values.get('context') and not values.get('file_paths'):
|
if not values.get("context") and not values.get("file_paths"):
|
||||||
raise ValueError("Either 'context', 'text', 'reasoning', or 'file_paths' must be provided")
|
raise ValueError(
|
||||||
|
"Either 'context', 'text', 'reasoning', or 'file_paths' must be provided"
|
||||||
|
)
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -51,14 +66,14 @@ class CogneeAdd(BaseTool):
|
||||||
node_set = kwargs.get("node_set", self.default_nodeset)
|
node_set = kwargs.get("node_set", self.default_nodeset)
|
||||||
context = kwargs.get("context")
|
context = kwargs.get("context")
|
||||||
file_paths = kwargs.get("file_paths")
|
file_paths = kwargs.get("file_paths")
|
||||||
|
|
||||||
# Handle alternative input fields
|
# Handle alternative input fields
|
||||||
text = kwargs.get("text")
|
text = kwargs.get("text")
|
||||||
reasoning = kwargs.get("reasoning")
|
reasoning = kwargs.get("reasoning")
|
||||||
|
|
||||||
if text and not context:
|
if text and not context:
|
||||||
context = text
|
context = text
|
||||||
|
|
||||||
if reasoning and not context:
|
if reasoning and not context:
|
||||||
context = reasoning
|
context = reasoning
|
||||||
|
|
||||||
|
|
@ -70,7 +85,7 @@ class CogneeAdd(BaseTool):
|
||||||
elif file_paths:
|
elif file_paths:
|
||||||
# Handle file paths
|
# Handle file paths
|
||||||
await cognee.add(file_paths, node_set=ns)
|
await cognee.add(file_paths, node_set=ns)
|
||||||
|
|
||||||
run = await cognee.cognify()
|
run = await cognee.cognify()
|
||||||
tasks = [Task(node_set_edge_association)]
|
tasks = [Task(node_set_edge_association)]
|
||||||
|
|
||||||
|
|
@ -78,7 +93,9 @@ class CogneeAdd(BaseTool):
|
||||||
pipeline = run_tasks(tasks=tasks, user=user)
|
pipeline = run_tasks(tasks=tasks, user=user)
|
||||||
|
|
||||||
async for pipeline_status in pipeline:
|
async for pipeline_status in pipeline:
|
||||||
print(f"Pipeline run status: {pipeline_status.pipeline_name} - {pipeline_status.status}")
|
print(
|
||||||
|
f"Pipeline run status: {pipeline_status.pipeline_name} - {pipeline_status.status}"
|
||||||
|
)
|
||||||
|
|
||||||
return run
|
return run
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -91,7 +108,6 @@ class CogneeAdd(BaseTool):
|
||||||
# If loop is already running, create a new one
|
# If loop is already running, create a new one
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
result = loop.run_until_complete(main(node_set))
|
result = loop.run_until_complete(main(node_set))
|
||||||
return result.__name__ if hasattr(result, "__name__") else str(result)
|
return result.__name__ if hasattr(result, "__name__") else str(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -113,30 +129,30 @@ class CogneeSearchInput(BaseModel):
|
||||||
node_set: List[str] = Field(
|
node_set: List[str] = Field(
|
||||||
default=["default"], description="The list of node sets to search in."
|
default=["default"], description="The list of node sets to search in."
|
||||||
)
|
)
|
||||||
|
|
||||||
@root_validator(pre=True)
|
@root_validator(pre=True)
|
||||||
def normalize_inputs(cls, values):
|
def normalize_inputs(cls, values):
|
||||||
"""Normalize different input formats to standard fields."""
|
"""Normalize different input formats to standard fields."""
|
||||||
# If the dictionary is empty, use a default query
|
# If the dictionary is empty, use a default query
|
||||||
if not values:
|
if not values:
|
||||||
values['query_text'] = "Latest AI developments"
|
values["query_text"] = "Latest AI developments"
|
||||||
return values
|
return values
|
||||||
|
|
||||||
# Map alternative search fields to query_text
|
# Map alternative search fields to query_text
|
||||||
if values.get('query') and not values.get('query_text'):
|
if values.get("query") and not values.get("query_text"):
|
||||||
values['query_text'] = values.get('query')
|
values["query_text"] = values.get("query")
|
||||||
|
|
||||||
if values.get('search_term') and not values.get('query_text'):
|
if values.get("search_term") and not values.get("query_text"):
|
||||||
values['query_text'] = values.get('search_term')
|
values["query_text"] = values.get("search_term")
|
||||||
|
|
||||||
# If security_context is provided but no query, use a default
|
# If security_context is provided but no query, use a default
|
||||||
if 'security_context' in values and not values.get('query_text'):
|
if "security_context" in values and not values.get("query_text"):
|
||||||
values['query_text'] = "Latest AI developments"
|
values["query_text"] = "Latest AI developments"
|
||||||
|
|
||||||
# Ensure query_text is present
|
# Ensure query_text is present
|
||||||
if not values.get('query_text'):
|
if not values.get("query_text"):
|
||||||
values['query_text'] = "Latest AI developments"
|
values["query_text"] = "Latest AI developments"
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -152,26 +168,27 @@ class CogneeSearch(BaseTool):
|
||||||
|
|
||||||
# Use the provided node_set if given, otherwise use default_nodeset
|
# Use the provided node_set if given, otherwise use default_nodeset
|
||||||
node_set = kwargs.get("node_set", self.default_nodeset)
|
node_set = kwargs.get("node_set", self.default_nodeset)
|
||||||
|
|
||||||
# Get query_text from kwargs or use a default
|
# Get query_text from kwargs or use a default
|
||||||
query_text = kwargs.get("query_text", "Latest AI developments")
|
query_text = kwargs.get("query_text", "Latest AI developments")
|
||||||
|
|
||||||
# Handle alternative input fields
|
# Handle alternative input fields
|
||||||
query = kwargs.get("query")
|
query = kwargs.get("query")
|
||||||
search_term = kwargs.get("search_term")
|
search_term = kwargs.get("search_term")
|
||||||
|
|
||||||
if query and not query_text:
|
if query and not query_text:
|
||||||
query_text = query
|
query_text = query
|
||||||
|
|
||||||
if search_term and not query_text:
|
if search_term and not query_text:
|
||||||
query_text = search_term
|
query_text = search_term
|
||||||
|
|
||||||
async def main(query, ns):
|
async def main(query, ns):
|
||||||
try:
|
try:
|
||||||
|
# Use 'datasets' to specify which node sets (datasets) to search
|
||||||
result = await cognee.search(
|
result = await cognee.search(
|
||||||
query_type=SearchType.GRAPH_COMPLETION,
|
|
||||||
query_text=query + " Only return results from context",
|
query_text=query + " Only return results from context",
|
||||||
node_set=ns # Pass the node_set to the search
|
query_type=SearchType.GRAPH_COMPLETION,
|
||||||
|
datasets=ns,
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -188,4 +205,4 @@ class CogneeSearch(BaseTool):
|
||||||
result = loop.run_until_complete(main(query_text, node_set))
|
result = loop.run_until_complete(main(query_text, node_set))
|
||||||
return str(result)
|
return str(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Tool execution error: {str(e)}"
|
return f"Tool execution error: {str(e)}"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue