Merge branch 'lorenze/feat/grep-tool' of github.com:crewAIInc/crewAI into lorenze/feat/file-discovery-tools

This commit is contained in:
lorenzejay
2026-02-04 11:58:33 -08:00
6 changed files with 171 additions and 27 deletions

View File

@@ -77,6 +77,7 @@ from crewai_tools.tools.generate_crewai_automation_tool.generate_crewai_automati
GenerateCrewaiAutomationTool,
)
from crewai_tools.tools.github_search_tool.github_search_tool import GithubSearchTool
from crewai_tools.tools.grep_tool.grep_tool import GrepTool
from crewai_tools.tools.hyperbrowser_load_tool.hyperbrowser_load_tool import (
HyperbrowserLoadTool,
)
@@ -230,6 +231,7 @@ __all__ = [
"FirecrawlSearchTool",
"GenerateCrewaiAutomationTool",
"GithubSearchTool",
"GrepTool",
"HyperbrowserLoadTool",
"InvokeCrewAIAutomationTool",
"JSONSearchTool",

View File

@@ -66,6 +66,7 @@ from crewai_tools.tools.generate_crewai_automation_tool.generate_crewai_automati
GenerateCrewaiAutomationTool,
)
from crewai_tools.tools.github_search_tool.github_search_tool import GithubSearchTool
from crewai_tools.tools.grep_tool.grep_tool import GrepTool
from crewai_tools.tools.hyperbrowser_load_tool.hyperbrowser_load_tool import (
HyperbrowserLoadTool,
)
@@ -214,6 +215,7 @@ __all__ = [
"FirecrawlSearchTool",
"GenerateCrewaiAutomationTool",
"GithubSearchTool",
"GrepTool",
"HyperbrowserLoadTool",
"InvokeCrewAIAutomationTool",
"JSONSearchTool",

View File

@@ -0,0 +1,3 @@
from crewai_tools.tools.grep_tool.grep_tool import GrepTool
__all__ = ["GrepTool"]

View File

@@ -2,33 +2,33 @@
from __future__ import annotations
from dataclasses import dataclass, field
import os
from pathlib import Path
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Literal
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
from crewai.tools.base_tool import BaseTool
MAX_OUTPUT_CHARS = 50_000
MAX_FILES = 10_000
MAX_MATCHES_PER_FILE = 200
MAX_LINE_LENGTH = 500
BINARY_CHECK_SIZE = 8192
SKIP_DIRS = frozenset({
".git",
"__pycache__",
"node_modules",
".venv",
"venv",
".tox",
".mypy_cache",
".pytest_cache",
})
SKIP_DIRS = frozenset(
{
".git",
"__pycache__",
"node_modules",
".venv",
"venv",
".tox",
".mypy_cache",
".pytest_cache",
}
)
@dataclass
@@ -52,7 +52,9 @@ class FileSearchResult:
class GrepToolSchema(BaseModel):
"""Schema for grep tool arguments."""
pattern: str = Field(..., description="Regex pattern to search for in file contents")
pattern: str = Field(
..., description="Regex pattern to search for in file contents"
)
path: str | None = Field(
default=None,
description="File or directory to search in. Defaults to current working directory.",
@@ -84,13 +86,23 @@ class GrepTool(BaseTool):
Recursively searches files in a directory for lines matching a regex pattern.
Supports glob filtering, context lines, and multiple output modes.
Example:
>>> tool = GrepTool()
>>> result = tool.run(pattern="def.*main", path="/path/to/project")
>>> result = tool.run(
... pattern="TODO",
... path="/path/to/project",
... glob_pattern="*.py",
... context_lines=2,
... )
"""
name: str = "grep"
name: str = "Search file contents"
description: str = (
"Search file contents on disk using regex patterns. "
"A tool that searches file contents on disk using regex patterns. "
"Recursively searches files in a directory for matching lines. "
"Returns matching content, file paths, or match counts."
"Returns matching content with line numbers, file paths only, or match counts."
)
args_schema: type[BaseModel] = GrepToolSchema
@@ -154,7 +166,10 @@ class GrepTool(BaseTool):
# Truncate if needed
if len(output) > MAX_OUTPUT_CHARS:
output = output[:MAX_OUTPUT_CHARS] + "\n\n... Output truncated. Try a narrower search pattern or glob filter."
output = (
output[:MAX_OUTPUT_CHARS]
+ "\n\n... Output truncated. Try a narrower search pattern or glob filter."
)
return output
@@ -255,11 +270,13 @@ class GrepTool(BaseTool):
text = lines[i].rstrip("\n\r")
if len(text) > MAX_LINE_LENGTH:
text = text[:MAX_LINE_LENGTH] + "..."
current_group.append(MatchLine(
line_number=i + 1, # 1-indexed
text=text,
is_match=(i in match_line_nums),
))
current_group.append(
MatchLine(
line_number=i + 1, # 1-indexed
text=text,
is_match=(i in match_line_nums),
)
)
prev_end = end

View File

@@ -4,7 +4,7 @@ from pathlib import Path
import pytest
from crewai.tools.agent_tools.grep_tool import GrepTool
from crewai_tools import GrepTool
@pytest.fixture
@@ -69,7 +69,7 @@ class TestGrepTool:
def test_tool_metadata(self) -> None:
"""Test tool has correct name and description."""
assert self.tool.name == "grep"
assert self.tool.name == "Search file contents"
assert "search" in self.tool.description.lower() or "Search" in self.tool.description
def test_args_schema(self) -> None:

View File

@@ -8360,6 +8360,126 @@
"type": "object"
}
},
{
"description": "A tool that searches file contents on disk using regex patterns. Recursively searches files in a directory for matching lines. Returns matching content with line numbers, file paths only, or match counts.",
"env_vars": [],
"humanized_name": "Search file contents",
"init_params_schema": {
"$defs": {
"EnvVar": {
"properties": {
"default": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Default"
},
"description": {
"title": "Description",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"required": {
"default": true,
"title": "Required",
"type": "boolean"
}
},
"required": [
"name",
"description"
],
"title": "EnvVar",
"type": "object"
}
},
"description": "Tool for searching file contents on disk using regex patterns.\n\nRecursively searches files in a directory for lines matching a regex pattern.\nSupports glob filtering, context lines, and multiple output modes.\n\nExample:\n >>> tool = GrepTool()\n >>> result = tool.run(pattern=\"def.*main\", path=\"/path/to/project\")\n >>> result = tool.run(\n ... pattern=\"TODO\",\n ... path=\"/path/to/project\",\n ... glob_pattern=\"*.py\",\n ... context_lines=2,\n ... )",
"properties": {},
"title": "GrepTool",
"type": "object"
},
"name": "GrepTool",
"package_dependencies": [],
"run_params_schema": {
"description": "Schema for grep tool arguments.",
"properties": {
"case_insensitive": {
"default": false,
"description": "Whether to perform case-insensitive matching",
"title": "Case Insensitive",
"type": "boolean"
},
"context_lines": {
"default": 0,
"description": "Number of lines to show before and after each match",
"title": "Context Lines",
"type": "integer"
},
"glob_pattern": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Glob pattern to filter files (e.g. '*.py', '*.{ts,tsx}')",
"title": "Glob Pattern"
},
"include_line_numbers": {
"default": true,
"description": "Whether to prefix matching lines with line numbers",
"title": "Include Line Numbers",
"type": "boolean"
},
"output_mode": {
"default": "content",
"description": "Output mode: 'content' shows matching lines, 'files_with_matches' shows only file paths, 'count' shows match counts per file",
"enum": [
"content",
"files_with_matches",
"count"
],
"title": "Output Mode",
"type": "string"
},
"path": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "File or directory to search in. Defaults to current working directory.",
"title": "Path"
},
"pattern": {
"description": "Regex pattern to search for in file contents",
"title": "Pattern",
"type": "string"
}
},
"required": [
"pattern"
],
"title": "GrepToolSchema",
"type": "object"
}
},
{
"description": "Scrape or crawl a website using Hyperbrowser and return the contents in properly formatted markdown or html",
"env_vars": [