e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
250 lines
7.9 KiB
Python
250 lines
7.9 KiB
Python
"""Validated request config and intent-to-policy mapping for deep research.
|
|
|
|
Tool composition lives in :mod:`deeptutor.agents._shared.tool_composition`
|
|
— the same shim chat uses. Research has no separate ``sources`` knob:
|
|
whatever tools the user enables in the composer become available to the
|
|
per-block research loop, with ``rag`` auto-mounted when a KB is attached
|
|
(again, identical to chat).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, ValidationError, field_validator
|
|
|
|
ResearchMode = Literal["notes", "report", "comparison", "learning_path"]
|
|
ResearchDepth = Literal["quick", "standard", "deep", "manual"]
|
|
|
|
|
|
class OutlineItem(BaseModel):
|
|
title: str
|
|
overview: str = ""
|
|
|
|
|
|
class DeepResearchRequestConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
mode: ResearchMode
|
|
depth: ResearchDepth
|
|
|
|
manual_subtopics: int | None = None
|
|
manual_max_iterations: int | None = None
|
|
|
|
confirmed_outline: list[OutlineItem] | None = None
|
|
|
|
@field_validator("manual_subtopics")
|
|
@classmethod
|
|
def validate_manual_subtopics(cls, value: int | None) -> int | None:
|
|
if value is not None:
|
|
return max(1, min(value, 10))
|
|
return value
|
|
|
|
@field_validator("manual_max_iterations")
|
|
@classmethod
|
|
def validate_manual_max_iterations(cls, value: int | None) -> int | None:
|
|
if value is not None:
|
|
return max(1, min(value, 10))
|
|
return value
|
|
|
|
|
|
def validate_research_request_config(
|
|
raw_config: dict[str, Any] | None,
|
|
) -> DeepResearchRequestConfig:
|
|
if not isinstance(raw_config, dict):
|
|
raise ValueError("Deep research requires an explicit config object.")
|
|
try:
|
|
return DeepResearchRequestConfig.model_validate(raw_config)
|
|
except ValidationError as exc:
|
|
details = "; ".join(
|
|
f"{'.'.join(str(part) for part in error['loc'])}: {error['msg']}"
|
|
for error in exc.errors()
|
|
)
|
|
raise ValueError(f"Invalid deep research config: {details}") from exc
|
|
|
|
|
|
def build_research_execution_policy(
|
|
*,
|
|
request_config: DeepResearchRequestConfig,
|
|
) -> dict[str, Any]:
|
|
depth_policy = _build_depth_policy(
|
|
request_config.depth,
|
|
manual_max_iterations=request_config.manual_max_iterations,
|
|
manual_subtopics=request_config.manual_subtopics,
|
|
)
|
|
mode_policy = _build_mode_policy(request_config.mode, request_config.depth)
|
|
|
|
if request_config.depth == "manual" and request_config.manual_subtopics is not None:
|
|
n = request_config.manual_subtopics
|
|
if mode_policy.get("decompose_mode") == "auto":
|
|
mode_policy["auto_max_subtopics"] = n
|
|
else:
|
|
mode_policy["initial_subtopics"] = n
|
|
|
|
planning = {
|
|
"rephrase": {
|
|
"enabled": mode_policy["rephrase_enabled"],
|
|
"max_iterations": mode_policy["rephrase_iterations"],
|
|
},
|
|
"decompose": {
|
|
"mode": mode_policy["decompose_mode"],
|
|
"initial_subtopics": mode_policy["initial_subtopics"],
|
|
"auto_max_subtopics": mode_policy["auto_max_subtopics"],
|
|
},
|
|
}
|
|
researching = {
|
|
"max_iterations": depth_policy["max_iterations"],
|
|
"iteration_mode": depth_policy["iteration_mode"],
|
|
"execution_mode": depth_policy["execution_mode"],
|
|
"max_parallel_topics": depth_policy["max_parallel_topics"],
|
|
}
|
|
reporting = {
|
|
"min_section_length": mode_policy["min_section_length"],
|
|
"report_single_pass_threshold": mode_policy["report_single_pass_threshold"],
|
|
"enable_citation_list": mode_policy["enable_citation_list"],
|
|
"enable_inline_citations": mode_policy["enable_inline_citations"],
|
|
"deduplicate_enabled": mode_policy["deduplicate_enabled"],
|
|
"style": mode_policy["style"],
|
|
"mode": request_config.mode,
|
|
"depth": request_config.depth,
|
|
}
|
|
queue = {"max_length": depth_policy["queue_max_length"]}
|
|
|
|
return {
|
|
"planning": planning,
|
|
"researching": researching,
|
|
"reporting": reporting,
|
|
"queue": queue,
|
|
"intent": request_config.model_dump(),
|
|
}
|
|
|
|
|
|
def build_research_runtime_config(
|
|
*,
|
|
base_config: dict[str, Any],
|
|
request_config: DeepResearchRequestConfig,
|
|
kb_name: str | None,
|
|
) -> dict[str, Any]:
|
|
capabilities = (
|
|
base_config.get("capabilities", {})
|
|
if isinstance(base_config.get("capabilities"), dict)
|
|
else {}
|
|
)
|
|
research_root = (
|
|
capabilities.get("research", {}) if isinstance(capabilities.get("research"), dict) else {}
|
|
)
|
|
researching_root = (
|
|
research_root.get("researching", {})
|
|
if isinstance(research_root.get("researching"), dict)
|
|
else {}
|
|
)
|
|
reporting_root = (
|
|
research_root.get("reporting", {})
|
|
if isinstance(research_root.get("reporting"), dict)
|
|
else {}
|
|
)
|
|
rag_root: dict = {}
|
|
policy = build_research_execution_policy(request_config=request_config)
|
|
|
|
runtime_config = dict(base_config)
|
|
runtime_config["planning"] = policy["planning"]
|
|
runtime_config["researching"] = {
|
|
**{
|
|
key: researching_root[key]
|
|
for key in (
|
|
"note_agent_mode",
|
|
"tool_timeout",
|
|
"tool_max_retries",
|
|
"paper_search_years_limit",
|
|
)
|
|
if key in researching_root
|
|
},
|
|
**policy["researching"],
|
|
}
|
|
runtime_config["reporting"] = {
|
|
**{key: reporting_root[key] for key in () if key in reporting_root},
|
|
**policy["reporting"],
|
|
}
|
|
runtime_config["queue"] = policy["queue"]
|
|
runtime_config["rag"] = {
|
|
**rag_root,
|
|
"kb_name": kb_name or rag_root.get("kb_name"),
|
|
}
|
|
runtime_config["intent"] = policy["intent"]
|
|
|
|
system_cfg = dict(runtime_config.get("system", {}) or {})
|
|
paths_cfg = dict(runtime_config.get("paths", {}) or {})
|
|
system_cfg["output_base_dir"] = paths_cfg.get(
|
|
"research_output_dir",
|
|
"./data/user/workspace/chat/deep_research",
|
|
)
|
|
system_cfg["reports_dir"] = paths_cfg.get(
|
|
"research_reports_dir",
|
|
"./data/user/workspace/chat/deep_research/reports",
|
|
)
|
|
runtime_config["system"] = system_cfg
|
|
|
|
return runtime_config
|
|
|
|
|
|
def _build_depth_policy(
|
|
depth: ResearchDepth,
|
|
*,
|
|
manual_max_iterations: int | None = None,
|
|
manual_subtopics: int | None = None,
|
|
) -> dict[str, Any]:
|
|
presets: dict[str, dict[str, Any]] = {
|
|
"quick": {
|
|
"max_iterations": 1,
|
|
"iteration_mode": "fixed",
|
|
"execution_mode": "series",
|
|
"max_parallel_topics": 1,
|
|
"queue_max_length": 2,
|
|
},
|
|
"standard": {
|
|
"max_iterations": 3,
|
|
"iteration_mode": "fixed",
|
|
"execution_mode": "series",
|
|
"max_parallel_topics": 1,
|
|
"queue_max_length": 5,
|
|
},
|
|
"deep": {
|
|
"max_iterations": 5,
|
|
"iteration_mode": "flexible",
|
|
"execution_mode": "parallel",
|
|
"max_parallel_topics": 3,
|
|
"queue_max_length": 8,
|
|
},
|
|
}
|
|
|
|
if depth == "manual":
|
|
iters = manual_max_iterations or 3
|
|
subtopics = manual_subtopics or 3
|
|
return {
|
|
"max_iterations": iters,
|
|
"iteration_mode": "fixed",
|
|
"execution_mode": "series" if subtopics <= 3 else "parallel",
|
|
"max_parallel_topics": min(subtopics, 3),
|
|
"queue_max_length": subtopics + 2,
|
|
}
|
|
|
|
return dict(presets[depth])
|
|
|
|
|
|
def _build_mode_policy(mode: ResearchMode, depth: ResearchDepth) -> dict[str, Any]:
|
|
from deeptutor.agents.research.mode_strategy import get_strategy
|
|
|
|
strategy = get_strategy(mode)
|
|
return dict(strategy.build_policy(depth))
|
|
|
|
|
|
__all__ = [
|
|
"DeepResearchRequestConfig",
|
|
"OutlineItem",
|
|
"ResearchDepth",
|
|
"ResearchMode",
|
|
"build_research_execution_policy",
|
|
"build_research_runtime_config",
|
|
"validate_research_request_config",
|
|
]
|