Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions codewiki/cli/adapters/doc_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def generate(self) -> DocumentationJob:
max_token_per_leaf_module=self.config.get("max_token_per_leaf_module", 16000),
max_leaf_nodes_per_cluster=self.config.get("max_leaf_nodes_per_cluster", 600),
max_depth=self.config.get("max_depth", 2),
request_limit=self.config.get("request_limit", 100),
agent_instructions=self.config.get("agent_instructions"),
use_gitignore=self.config.get("use_gitignore", True),
prompt_caching=self.config.get("prompt_caching", True),
Expand Down
22 changes: 22 additions & 0 deletions codewiki/cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ def config_group():
@click.option(
"--max-depth", type=int, help="Maximum depth for hierarchical decomposition (default: 2)"
)
@click.option(
"--request-limit",
type=int,
help="Max model requests per agent run, raise for large/complex modules (default: 100)",
)
@click.option(
"--provider",
type=click.Choice(
Expand Down Expand Up @@ -97,6 +102,7 @@ def config_set(
max_token_per_module: Optional[int],
max_token_per_leaf_module: Optional[int],
max_depth: Optional[int],
request_limit: Optional[int] = None,
provider: Optional[str] = None,
aws_region: Optional[str] = None,
api_version: Optional[str] = None,
Expand Down Expand Up @@ -149,6 +155,10 @@ def config_set(
# Set max depth for hierarchical decomposition
$ codewiki config set --max-depth 3

\b
# Raise the per-agent-run request limit for large/complex repos
$ codewiki config set --request-limit 200

\b
# Persistently disable Git ignore filtering
$ codewiki config set --no-gitignore
Expand All @@ -166,6 +176,7 @@ def config_set(
max_token_per_module,
max_token_per_leaf_module,
max_depth,
request_limit,
provider,
aws_region,
api_version,
Expand Down Expand Up @@ -226,6 +237,11 @@ def config_set(
raise ConfigurationError("max_depth must be a positive integer")
validated_data["max_depth"] = max_depth

if request_limit is not None:
if request_limit < 1:
raise ConfigurationError("request_limit must be a positive integer")
validated_data["request_limit"] = request_limit

if provider is not None:
validated_data["provider"] = provider

Expand Down Expand Up @@ -258,6 +274,7 @@ def config_set(
max_token_per_module=validated_data.get("max_token_per_module"),
max_token_per_leaf_module=validated_data.get("max_token_per_leaf_module"),
max_depth=validated_data.get("max_depth"),
request_limit=validated_data.get("request_limit"),
provider=validated_data.get("provider"),
aws_region=validated_data.get("aws_region"),
api_version=validated_data.get("api_version"),
Expand Down Expand Up @@ -311,6 +328,9 @@ def config_set(
if max_depth:
click.secho(f"✓ Max depth: {max_depth}", fg="green")

if request_limit:
click.secho(f"✓ Request limit: {request_limit}", fg="green")

if provider:
click.secho(f"✓ Provider: {provider}", fg="green")

Expand Down Expand Up @@ -384,6 +404,7 @@ def config_show(output_json: bool):
"max_token_per_module": config.max_token_per_module if config else 36369,
"max_token_per_leaf_module": config.max_token_per_leaf_module if config else 16000,
"max_depth": config.max_depth if config else 2,
"request_limit": config.request_limit if config else 100,
"use_gitignore": config.use_gitignore if config else True,
"prompt_caching": config.prompt_caching if config else True,
"agent_instructions": config.agent_instructions.to_dict()
Expand Down Expand Up @@ -444,6 +465,7 @@ def config_show(output_json: bool):
click.echo(f" Max Tokens: {config.max_tokens}")
click.echo(f" Max Token/Module: {config.max_token_per_module}")
click.echo(f" Max Token/Leaf Module: {config.max_token_per_leaf_module}")
click.echo(f" Request Limit: {config.request_limit}")
click.echo(f" Prompt Caching: {config.prompt_caching}")

click.echo()
Expand Down
19 changes: 19 additions & 0 deletions codewiki/cli/commands/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,12 @@ def _find_affected(tree, parent_names=None):
default=None,
help="Maximum depth for hierarchical decomposition (overrides config)",
)
@click.option(
"--request-limit",
type=int,
default=None,
help="Max model requests per agent run, raise for large/complex modules (overrides config)",
)
@click.option(
"--prompt-caching/--no-prompt-caching",
default=None,
Expand Down Expand Up @@ -398,6 +404,7 @@ def generate_command(
max_token_per_module: int | None,
max_token_per_leaf_module: int | None,
max_depth: int | None,
request_limit: int | None,
prompt_caching: bool | None,
artifacts: bool = True,
artifact_token_budget: int = 200_000,
Expand Down Expand Up @@ -461,6 +468,10 @@ def generate_command(
\b
# Override max depth for hierarchical decomposition
$ codewiki generate --max-depth 3

\b
# Raise the per-agent-run request limit for large/complex repos
$ codewiki generate --request-limit 200
"""
print_banner()
logger = create_logger(verbose=verbose)
Expand Down Expand Up @@ -638,6 +649,9 @@ def generate_command(
else config.max_token_per_leaf_module
)
effective_max_depth = max_depth if max_depth is not None else config.max_depth
effective_request_limit = (
request_limit if request_limit is not None else config.request_limit
)
effective_use_gitignore = (
use_gitignore if use_gitignore is not None else config.use_gitignore
)
Expand All @@ -648,6 +662,7 @@ def generate_command(
logger.debug(f"Max token/module: {effective_max_token_per_module}")
logger.debug(f"Max token/leaf module: {effective_max_token_per_leaf}")
logger.debug(f"Max depth: {effective_max_depth}")
logger.debug(f"Request limit: {effective_request_limit}")
logger.debug(f"Use gitignore: {effective_use_gitignore}")
logger.debug(f"Prompt caching: {effective_prompt_caching}")
logger.debug(
Expand Down Expand Up @@ -717,6 +732,10 @@ def generate_command(
else config.max_token_per_leaf_module,
# Max depth setting (runtime override takes precedence)
"max_depth": max_depth if max_depth is not None else config.max_depth,
# Request limit setting (runtime override takes precedence)
"request_limit": request_limit
if request_limit is not None
else config.request_limit,
# Gitignore setting (runtime override takes precedence)
"use_gitignore": use_gitignore
if use_gitignore is not None
Expand Down
4 changes: 4 additions & 0 deletions codewiki/cli/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def save(
max_token_per_module: Optional[int] = None,
max_token_per_leaf_module: Optional[int] = None,
max_depth: Optional[int] = None,
request_limit: Optional[int] = None,
provider: Optional[str] = None,
aws_region: Optional[str] = None,
api_version: Optional[str] = None,
Expand All @@ -157,6 +158,7 @@ def save(
max_token_per_module: Maximum tokens per module for clustering
max_token_per_leaf_module: Maximum tokens per leaf module
max_depth: Maximum depth for hierarchical decomposition
request_limit: Max model requests per agent run
provider: LLM provider type (openai-compatible, anthropic, bedrock, azure-openai)
aws_region: AWS region for Bedrock provider
api_version: Azure OpenAI API version
Expand Down Expand Up @@ -205,6 +207,8 @@ def save(
self._config.max_token_per_leaf_module = max_token_per_leaf_module
if max_depth is not None:
self._config.max_depth = max_depth
if request_limit is not None:
self._config.request_limit = request_limit
if provider is not None:
self._config.provider = provider
if aws_region is not None:
Expand Down
5 changes: 5 additions & 0 deletions codewiki/cli/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class Configuration:
max_token_per_module: Maximum tokens per module for clustering (default: 36369)
max_token_per_leaf_module: Maximum tokens per leaf module (default: 16000)
max_depth: Maximum depth for hierarchical decomposition (default: 2)
request_limit: Max model requests per agent run (default: 100)
use_gitignore: Apply Git ignore rules during repository analysis
prompt_caching: Add prompt-cache breakpoints to agentic LLM calls (default: True)
agent_instructions: Custom agent instructions for documentation generation
Expand All @@ -148,6 +149,7 @@ class Configuration:
max_token_per_module: int = 36369
max_token_per_leaf_module: int = 16000
max_depth: int = 2
request_limit: int = 100
use_gitignore: bool = True
prompt_caching: bool = True
agent_instructions: AgentInstructions = field(default_factory=AgentInstructions)
Expand Down Expand Up @@ -187,6 +189,7 @@ def to_dict(self) -> dict:
"max_token_per_module": self.max_token_per_module,
"max_token_per_leaf_module": self.max_token_per_leaf_module,
"max_depth": self.max_depth,
"request_limit": self.request_limit,
"use_gitignore": self.use_gitignore,
"prompt_caching": self.prompt_caching,
"fallback_model": self.fallback_model,
Expand Down Expand Up @@ -224,6 +227,7 @@ def from_dict(cls, data: dict) -> "Configuration":
max_token_per_module=data.get("max_token_per_module", 36369),
max_token_per_leaf_module=data.get("max_token_per_leaf_module", 16000),
max_depth=data.get("max_depth", 2),
request_limit=data.get("request_limit", 100),
use_gitignore=data.get("use_gitignore", True),
prompt_caching=data.get("prompt_caching", True),
agent_instructions=agent_instructions,
Expand Down Expand Up @@ -300,6 +304,7 @@ def to_backend_config(
max_token_per_module=self.max_token_per_module,
max_token_per_leaf_module=self.max_token_per_leaf_module,
max_depth=self.max_depth,
request_limit=self.request_limit,
agent_instructions=final_instructions.to_dict() if final_instructions else None,
use_gitignore=self.use_gitignore,
prompt_caching=self.prompt_caching,
Expand Down
12 changes: 11 additions & 1 deletion codewiki/src/be/pydantic_ai_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import Any

from pydantic_ai import Agent
from pydantic_ai.usage import UsageLimits

from codewiki.src.be.agent_tools.deps import CodeWikiDeps
from codewiki.src.be.agent_tools.generate_sub_module_documentations import (
Expand All @@ -36,6 +37,13 @@

logger = logging.getLogger(__name__)

# pydantic-ai's own default (`UsageLimits(request_limit=50)`) is too low for complex
# modules whose agent loop explores several components and/or spins off sub-module
# docs via `generate_sub_module_documentation_tool`: on a real-world run (5,381-file
# monorepo), 4 modules hit `UsageLimitExceeded` and were skipped outright with no
# retry, no fallback. `Config.request_limit` (default 100, see `codewiki/src/config.py`)
# makes the limit adjustable from the CLI/config file instead of hardcoding it here.


def _run_usage(result: Any) -> dict[str, Any] | None:
"""Token usage of a pydantic-ai run; ``usage`` is a property in pydantic-ai
Expand All @@ -56,6 +64,7 @@ def __init__(self, config: Config) -> None:
self._config = config
self._fallback_models = create_fallback_models(config)
self._custom_instructions = config.get_prompt_addition()
self._agent_usage_limits = UsageLimits(request_limit=config.request_limit)
self.last_usage: dict[str, Any] | None = None

def complete(
Expand Down Expand Up @@ -83,7 +92,7 @@ async def run_update_agent(
system_prompt=system_prompt,
)
started = time.time()
result = await agent.run(user_prompt, deps=deps)
result = await agent.run(user_prompt, deps=deps, usage_limits=self._agent_usage_limits)
seconds = time.time() - started
usage = _run_usage(result)
self.last_usage = usage
Expand Down Expand Up @@ -160,6 +169,7 @@ async def run_module_agent(
module_tree=deps.module_tree,
),
deps=deps,
usage_limits=self._agent_usage_limits,
)
self.last_usage = _run_usage(result)
file_manager.save_json(deps.module_tree, module_tree_path)
Expand Down
11 changes: 11 additions & 0 deletions codewiki/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
# Artifact-aware generation: total token budget for build/CI/container/
# manifest/config file contents added to the dependency graph.
DEFAULT_ARTIFACT_TOKEN_BUDGET = 200_000
# Max number of model requests per agent run (pydantic-ai's `UsageLimits.
# request_limit`). pydantic-ai's own default of 50 is too low for complex
# modules whose agent loop explores several components and/or spins off
# sub-module docs via `generate_sub_module_documentation_tool`.
DEFAULT_REQUEST_LIMIT = 100
# Legacy constants (for backward compatibility)
MAX_TOKEN_PER_MODULE = DEFAULT_MAX_TOKEN_PER_MODULE
MAX_TOKEN_PER_LEAF_MODULE = DEFAULT_MAX_TOKEN_PER_LEAF_MODULE
Expand Down Expand Up @@ -91,6 +96,8 @@ class Config:
max_token_per_leaf_module: int = DEFAULT_MAX_TOKEN_PER_LEAF_MODULE
min_modules_for_super_grouping: int = DEFAULT_MIN_MODULES_FOR_SUPER_GROUPING
max_leaf_nodes_per_cluster: int = DEFAULT_MAX_LEAF_NODES_PER_CLUSTER
# Max model requests per agent run (see DEFAULT_REQUEST_LIMIT above)
request_limit: int = DEFAULT_REQUEST_LIMIT
# Prompt caching for agentic/multi-turn calls (auto-disables per model if
# the provider rejects cache_control markers)
prompt_caching: bool = True
Expand Down Expand Up @@ -216,6 +223,7 @@ def from_cli(
max_token_per_leaf_module: int = DEFAULT_MAX_TOKEN_PER_LEAF_MODULE,
min_modules_for_super_grouping: int = DEFAULT_MIN_MODULES_FOR_SUPER_GROUPING,
max_leaf_nodes_per_cluster: int = DEFAULT_MAX_LEAF_NODES_PER_CLUSTER,
request_limit: int = DEFAULT_REQUEST_LIMIT,
max_depth: int = MAX_DEPTH,
agent_instructions: dict[str, Any] | None = None,
use_gitignore: bool = True,
Expand Down Expand Up @@ -247,6 +255,8 @@ def from_cli(
(0 or negative disables the pass)
max_leaf_nodes_per_cluster: Partition clustering inputs into
structure-based batches of at most this many leaf nodes
request_limit: Max number of model requests per agent run
(pydantic-ai's `UsageLimits.request_limit`)
max_depth: Maximum depth for hierarchical decomposition
agent_instructions: Custom agent instructions dict
use_gitignore: Whether to apply Git ignore rules
Expand Down Expand Up @@ -281,6 +291,7 @@ def from_cli(
max_token_per_leaf_module=max_token_per_leaf_module,
min_modules_for_super_grouping=min_modules_for_super_grouping,
max_leaf_nodes_per_cluster=max_leaf_nodes_per_cluster,
request_limit=request_limit,
agent_instructions=agent_instructions,
use_gitignore=use_gitignore,
prompt_caching=prompt_caching,
Expand Down
Loading