feat: add web_search and fetch_url research tools (#2)

- Add ResearchTools class in gitea/tools/research_tools.py
  - web_search: DuckDuckGo HTML search, no API key required
  - fetch_url: httpx-based URL fetcher with HTML stripping + truncation
- Wire both tools into planning_tools and coding_tools_list in dispatcher.py
- Add 19 unit tests covering all methods, error paths, and edge cases

---------

Co-authored-by: Michael <michael@example.com>
Reviewed-on: #2
This commit is contained in:
2026-06-28 19:01:55 +02:00
parent 0461c6c7ab
commit c0c9278d75
5 changed files with 1596 additions and 4 deletions
+18 -4
View File
@@ -6,6 +6,7 @@ from typing import Any
from core.coding_agent import CodingAgent
from core.queue import WorkItem
from gitea.tools.coding_tools import CodingTools
from gitea.tools.research_tools import ResearchTools
from gitea.tools.gitea_tools import GiteaTools
from gitea.client import GiteaClient
from core.coding_prompt import CODING_AGENT_SYSTEM_PROMPT
@@ -98,6 +99,7 @@ class AgentDispatcher:
workspace = WorkspaceManager()
repo_path = workspace.get_repo_path(repo)
coding_tools = CodingTools(str(repo_path))
research_tools = ResearchTools()
planning_tools: list[Any] = [
self._tools.get_issue,
@@ -114,6 +116,8 @@ class AgentDispatcher:
coding_tools.grep_search,
coding_tools.get_working_directory,
coding_tools.run_command,
research_tools.web_search,
research_tools.fetch_url,
]
coding_tools_list: list[Any] = [
@@ -147,6 +151,8 @@ class AgentDispatcher:
coding_tools.run_command,
coding_tools.grep_search,
coding_tools.get_working_directory,
research_tools.web_search,
research_tools.fetch_url,
]
results: list[str] = []
@@ -207,12 +213,17 @@ class AgentDispatcher:
logger.info(f"Starting Planning Phase for {item.task_type} #{item.task_number} (attempt {attempt})")
planning_mission = (
f"PHASE 1: PLANNING PHASE\n\n"
f"Your task is to analyze the repository structure and create a detailed implementation plan.\n"
f"Your task is to research the problem, analyse the repository structure, and produce a detailed implementation plan.\n"
f"Original Mission details:\n{base_mission}\n\n"
f"CRITICAL RULES:\n"
f"1. You are ONLY generating an implementation plan. DO NOT write files, DO NOT edit files, DO NOT commit, DO NOT push, and DO NOT create branches or PRs.\n"
f"2. Explore the codebase using read_file, list_files, grep_search, or run_command (for read-only queries like find/grep).\n"
f"3. Output your final plan clearly, describing the exact changes to be made and which files to modify.\n"
f"2. RESEARCH FIRST (mandatory before writing the plan):\n"
f" - Use web_search to find relevant documentation, known solutions, library APIs, error explanations, and best practices.\n"
f" - Use fetch_url to read specific documentation pages, changelogs, or Stack Overflow answers in full.\n"
f" - Use web_search with time_range='month' or time_range='year' for recency-sensitive queries.\n"
f" - Use web_search with categories='it' for technical/programming topics.\n"
f"3. EXPLORE the codebase using read_file, list_files, grep_search, or run_command (read-only queries like find/grep).\n"
f"4. Output your final plan clearly, describing the exact changes to be made and which files to modify.\n"
)
planning_agent = CodingAgent(self._model_name)
plan = await planning_agent.run_with_tools(planning_mission, planning_tools)
@@ -225,7 +236,10 @@ class AgentDispatcher:
f"You must now implement the changes based on the following plan generated in Phase 1:\n"
f"--- PLAN ---\n{plan}\n--- PLAN END ---\n\n"
f"Original Mission details:\n{base_mission}\n\n"
f"Follow the repository workflow to make the changes, verify them, commit, push, and create a PR.\n"
f"AVAILABLE RESEARCH TOOLS (use whenever uncertain about an API, syntax, or behaviour):\n"
f" - web_search(query, time_range, categories) — search the web via SearXNG/DuckDuckGo\n"
f" - fetch_url(url) — read any documentation page, README, or Stack Overflow answer in full\n\n"
f"Follow the repository workflow: implement changes, run verification, commit, push, and create a PR.\n"
)
coding_agent = CodingAgent(self._model_name)
response = await coding_agent.run_with_tools(coding_mission, coding_tools_list)