From ec1a9d59fb44e46571eaf33880459237567c6154 Mon Sep 17 00:00:00 2001 From: Fabricio Ceolin Date: Fri, 25 Sep 2026 08:35:34 -0300 Subject: [PATCH 1/2] fix: raise pydantic-ai request_limit from 50 to 100 for complex modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pydantic-ai's own default (UsageLimits(request_limit=50)) is too low for "complex" modules whose agent loop reads several code components and/or spins off sub-module docs via generate_sub_module_documentation_tool. On a real-world run (voll-intelligence, a 5,381-file TypeScript monorepo, 16,720 functions), 4 modules hit UsageLimitExceeded mid-run and were skipped outright — no retry, no fallback to the configured fallback-model, and no CLI flag to raise the limit. All 4 succeeded on the same input once the limit was raised to 100, with no other change. --- codewiki/src/be/pydantic_ai_backend.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/codewiki/src/be/pydantic_ai_backend.py b/codewiki/src/be/pydantic_ai_backend.py index 23ad08e9..ad733137 100644 --- a/codewiki/src/be/pydantic_ai_backend.py +++ b/codewiki/src/be/pydantic_ai_backend.py @@ -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 ( @@ -36,6 +37,14 @@ 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, and no way to raise the limit from the CLI. +_REQUEST_LIMIT = 100 +_AGENT_USAGE_LIMITS = UsageLimits(request_limit=_REQUEST_LIMIT) + def _run_usage(result: Any) -> dict[str, Any] | None: """Token usage of a pydantic-ai run; ``usage`` is a property in pydantic-ai @@ -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=_AGENT_USAGE_LIMITS) seconds = time.time() - started usage = _run_usage(result) self.last_usage = usage @@ -160,6 +169,7 @@ async def run_module_agent( module_tree=deps.module_tree, ), deps=deps, + usage_limits=_AGENT_USAGE_LIMITS, ) self.last_usage = _run_usage(result) file_manager.save_json(deps.module_tree, module_tree_path) From c9ee53e0cd7a7660019fdec566b2ae8e5277728f Mon Sep 17 00:00:00 2001 From: Fabricio Ceolin Date: Fri, 25 Sep 2026 09:06:26 -0300 Subject: [PATCH 2/2] feat: make request_limit configurable instead of hardcoding it at 100 The previous commit fixed UsageLimitExceeded on complex modules by raising pydantic-ai's request_limit from 50 (its own default) to 100, but hardcoded the new value with no way to tune it. Some repos/modules need more than 100 requests per agent run, so expose it the same way as the other generation knobs (max_tokens, max_token_per_module, ...): - Config.request_limit (backend), default DEFAULT_REQUEST_LIMIT = 100 - --request-limit on `codewiki generate` and `codewiki config set` - persisted in ~/.codewiki/config.json, shown in `codewiki config show` --- codewiki/cli/adapters/doc_generator.py | 1 + codewiki/cli/commands/config.py | 22 ++++++++++++++++++++++ codewiki/cli/commands/generate.py | 19 +++++++++++++++++++ codewiki/cli/config_manager.py | 4 ++++ codewiki/cli/models/config.py | 5 +++++ codewiki/src/be/pydantic_ai_backend.py | 10 +++++----- codewiki/src/config.py | 11 +++++++++++ 7 files changed, 67 insertions(+), 5 deletions(-) diff --git a/codewiki/cli/adapters/doc_generator.py b/codewiki/cli/adapters/doc_generator.py index 92c066dd..8332047f 100644 --- a/codewiki/cli/adapters/doc_generator.py +++ b/codewiki/cli/adapters/doc_generator.py @@ -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), diff --git a/codewiki/cli/commands/config.py b/codewiki/cli/commands/config.py index 17bed6cd..5415d3ed 100644 --- a/codewiki/cli/commands/config.py +++ b/codewiki/cli/commands/config.py @@ -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( @@ -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, @@ -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 @@ -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, @@ -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 @@ -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"), @@ -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") @@ -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() @@ -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() diff --git a/codewiki/cli/commands/generate.py b/codewiki/cli/commands/generate.py index 2e476c86..e686ff94 100644 --- a/codewiki/cli/commands/generate.py +++ b/codewiki/cli/commands/generate.py @@ -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, @@ -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, @@ -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) @@ -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 ) @@ -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( @@ -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 diff --git a/codewiki/cli/config_manager.py b/codewiki/cli/config_manager.py index 3cbadcff..e9f218ed 100644 --- a/codewiki/cli/config_manager.py +++ b/codewiki/cli/config_manager.py @@ -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, @@ -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 @@ -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: diff --git a/codewiki/cli/models/config.py b/codewiki/cli/models/config.py index c1cabe07..d5f60275 100644 --- a/codewiki/cli/models/config.py +++ b/codewiki/cli/models/config.py @@ -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 @@ -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) @@ -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, @@ -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, @@ -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, diff --git a/codewiki/src/be/pydantic_ai_backend.py b/codewiki/src/be/pydantic_ai_backend.py index ad733137..74c9b99c 100644 --- a/codewiki/src/be/pydantic_ai_backend.py +++ b/codewiki/src/be/pydantic_ai_backend.py @@ -41,9 +41,8 @@ # 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, and no way to raise the limit from the CLI. -_REQUEST_LIMIT = 100 -_AGENT_USAGE_LIMITS = UsageLimits(request_limit=_REQUEST_LIMIT) +# 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: @@ -65,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( @@ -92,7 +92,7 @@ async def run_update_agent( system_prompt=system_prompt, ) started = time.time() - result = await agent.run(user_prompt, deps=deps, usage_limits=_AGENT_USAGE_LIMITS) + 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 @@ -169,7 +169,7 @@ async def run_module_agent( module_tree=deps.module_tree, ), deps=deps, - usage_limits=_AGENT_USAGE_LIMITS, + usage_limits=self._agent_usage_limits, ) self.last_usage = _run_usage(result) file_manager.save_json(deps.module_tree, module_tree_path) diff --git a/codewiki/src/config.py b/codewiki/src/config.py index b98208ed..d3d5fdca 100644 --- a/codewiki/src/config.py +++ b/codewiki/src/config.py @@ -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 @@ -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 @@ -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, @@ -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 @@ -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,