Skip to content

Commit 1697ec8

Browse files
committed
agent run limits, fallback on malformed responses, overview instructions, ship updater
- Package: add codewiki.src.be.updater to [tool.setuptools] packages; a non-editable install had no updater, so every --update failed on import. New test checks every package directory is listed. (FSoft-AI4Code#119) - Agent limits: pass UsageLimits(request_limit=...) to every agent run and retries=... to every Agent. pydantic-ai defaults are 50 requests per run and 1 retry per failing tool call, which complex modules and generate_sub_module_documentation hit. New settings request_limit (default 100) and agent_retries (default 3), in `codewiki config set`, `config show` and as per-run overrides on `codewiki generate`. (FSoft-AI4Code#115, FSoft-AI4Code#118) - Fallback: FallbackModel now also falls back on UnexpectedModelBehavior (a 200 response whose body does not parse), keeping ModelAPIError. (FSoft-AI4Code#117) - Overview pages: MODULE_OVERVIEW_PROMPT / REPO_OVERVIEW_PROMPT had no slot for the user's instructions. complete() takes an optional system_prompt (OpenAI- compatible, litellm, Azure, and caw via CawAgent(system_prompt=...)), and parent/repo overviews send the instructions as a system message. (FSoft-AI4Code#116) - Sub-module agents built their system prompt with a raw .format(), so with no instructions the prompt ended in the literal text "None"; they now use format_system_prompt / format_leaf_system_prompt like the top-level agents. Reported in FSoft-AI4Code#115, FSoft-AI4Code#116, FSoft-AI4Code#117, FSoft-AI4Code#118, FSoft-AI4Code#119.
1 parent a966b4c commit 1697ec8

19 files changed

Lines changed: 293 additions & 25 deletions

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ tests/*
4747
!tests/test_branding.py
4848
!tests/test_artifact_analyzer.py
4949
!tests/test_scala_analyzer.py
50+
!tests/test_agent_limits.py
5051

5152
# Jupyter
5253
*.ipynb

‎codewiki/cli/adapters/doc_generator.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ def generate(self) -> DocumentationJob:
145145
max_token_per_leaf_module=self.config.get("max_token_per_leaf_module", 16000),
146146
max_leaf_nodes_per_cluster=self.config.get("max_leaf_nodes_per_cluster", 600),
147147
max_depth=self.config.get("max_depth", 2),
148+
request_limit=self.config.get("request_limit", 100),
149+
agent_retries=self.config.get("agent_retries", 3),
148150
agent_instructions=self.config.get("agent_instructions"),
149151
use_gitignore=self.config.get("use_gitignore", True),
150152
prompt_caching=self.config.get("prompt_caching", True),

‎codewiki/cli/commands/config.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ def config_group():
5151
@click.option(
5252
"--max-depth", type=int, help="Maximum depth for hierarchical decomposition (default: 2)"
5353
)
54+
@click.option(
55+
"--request-limit", type=int, help="Maximum model requests per agent run (default: 100)"
56+
)
57+
@click.option(
58+
"--agent-retries", type=int, help="Retries for a failing agent tool call (default: 3)"
59+
)
5460
@click.option(
5561
"--provider",
5662
type=click.Choice(
@@ -97,6 +103,8 @@ def config_set(
97103
max_token_per_module: Optional[int],
98104
max_token_per_leaf_module: Optional[int],
99105
max_depth: Optional[int],
106+
request_limit: Optional[int] = None,
107+
agent_retries: Optional[int] = None,
100108
provider: Optional[str] = None,
101109
aws_region: Optional[str] = None,
102110
api_version: Optional[str] = None,
@@ -166,6 +174,8 @@ def config_set(
166174
max_token_per_module,
167175
max_token_per_leaf_module,
168176
max_depth,
177+
request_limit is not None,
178+
agent_retries is not None,
169179
provider,
170180
aws_region,
171181
api_version,
@@ -226,6 +236,16 @@ def config_set(
226236
raise ConfigurationError("max_depth must be a positive integer")
227237
validated_data["max_depth"] = max_depth
228238

239+
if request_limit is not None:
240+
if request_limit < 1:
241+
raise ConfigurationError("request_limit must be a positive integer")
242+
validated_data["request_limit"] = request_limit
243+
244+
if agent_retries is not None:
245+
if agent_retries < 0:
246+
raise ConfigurationError("agent_retries must be zero or a positive integer")
247+
validated_data["agent_retries"] = agent_retries
248+
229249
if provider is not None:
230250
validated_data["provider"] = provider
231251

@@ -258,6 +278,8 @@ def config_set(
258278
max_token_per_module=validated_data.get("max_token_per_module"),
259279
max_token_per_leaf_module=validated_data.get("max_token_per_leaf_module"),
260280
max_depth=validated_data.get("max_depth"),
281+
request_limit=validated_data.get("request_limit"),
282+
agent_retries=validated_data.get("agent_retries"),
261283
provider=validated_data.get("provider"),
262284
aws_region=validated_data.get("aws_region"),
263285
api_version=validated_data.get("api_version"),
@@ -311,6 +333,12 @@ def config_set(
311333
if max_depth:
312334
click.secho(f"✓ Max depth: {max_depth}", fg="green")
313335

336+
if request_limit:
337+
click.secho(f"✓ Request limit: {request_limit}", fg="green")
338+
339+
if agent_retries is not None:
340+
click.secho(f"✓ Agent retries: {agent_retries}", fg="green")
341+
314342
if provider:
315343
click.secho(f"✓ Provider: {provider}", fg="green")
316344

@@ -384,6 +412,8 @@ def config_show(output_json: bool):
384412
"max_token_per_module": config.max_token_per_module if config else 36369,
385413
"max_token_per_leaf_module": config.max_token_per_leaf_module if config else 16000,
386414
"max_depth": config.max_depth if config else 2,
415+
"request_limit": config.request_limit if config else 100,
416+
"agent_retries": config.agent_retries if config else 3,
387417
"use_gitignore": config.use_gitignore if config else True,
388418
"prompt_caching": config.prompt_caching if config else True,
389419
"agent_instructions": config.agent_instructions.to_dict()
@@ -450,6 +480,8 @@ def config_show(output_json: bool):
450480
click.secho("Decomposition Settings", fg="cyan", bold=True)
451481
if config:
452482
click.echo(f" Max Depth: {config.max_depth}")
483+
click.echo(f" Request Limit: {config.request_limit}")
484+
click.echo(f" Agent Retries: {config.agent_retries}")
453485
click.echo(f" Use Gitignore: {config.use_gitignore}")
454486

455487
click.echo()

‎codewiki/cli/commands/generate.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,18 @@ def _find_affected(tree, parent_names=None):
296296
default=None,
297297
help="Maximum depth for hierarchical decomposition (overrides config)",
298298
)
299+
@click.option(
300+
"--request-limit",
301+
type=click.IntRange(min=1),
302+
default=None,
303+
help="Maximum model requests per agent run (overrides config)",
304+
)
305+
@click.option(
306+
"--agent-retries",
307+
type=click.IntRange(min=0),
308+
default=None,
309+
help="Retries for a failing agent tool call (overrides config)",
310+
)
299311
@click.option(
300312
"--prompt-caching/--no-prompt-caching",
301313
default=None,
@@ -398,6 +410,8 @@ def generate_command(
398410
max_token_per_module: int | None,
399411
max_token_per_leaf_module: int | None,
400412
max_depth: int | None,
413+
request_limit: int | None,
414+
agent_retries: int | None,
401415
prompt_caching: bool | None,
402416
artifacts: bool = True,
403417
artifact_token_budget: int = 200_000,
@@ -717,6 +731,13 @@ def generate_command(
717731
else config.max_token_per_leaf_module,
718732
# Max depth setting (runtime override takes precedence)
719733
"max_depth": max_depth if max_depth is not None else config.max_depth,
734+
# Agent run limits (runtime override takes precedence)
735+
"request_limit": request_limit
736+
if request_limit is not None
737+
else config.request_limit,
738+
"agent_retries": agent_retries
739+
if agent_retries is not None
740+
else config.agent_retries,
720741
# Gitignore setting (runtime override takes precedence)
721742
"use_gitignore": use_gitignore
722743
if use_gitignore is not None

‎codewiki/cli/config_manager.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ def save(
136136
max_token_per_module: Optional[int] = None,
137137
max_token_per_leaf_module: Optional[int] = None,
138138
max_depth: Optional[int] = None,
139+
request_limit: Optional[int] = None,
140+
agent_retries: Optional[int] = None,
139141
provider: Optional[str] = None,
140142
aws_region: Optional[str] = None,
141143
api_version: Optional[str] = None,
@@ -157,6 +159,8 @@ def save(
157159
max_token_per_module: Maximum tokens per module for clustering
158160
max_token_per_leaf_module: Maximum tokens per leaf module
159161
max_depth: Maximum depth for hierarchical decomposition
162+
request_limit: Maximum model requests per agent run
163+
agent_retries: Retries for a failing tool call
160164
provider: LLM provider type (openai-compatible, anthropic, bedrock, azure-openai)
161165
aws_region: AWS region for Bedrock provider
162166
api_version: Azure OpenAI API version
@@ -205,6 +209,10 @@ def save(
205209
self._config.max_token_per_leaf_module = max_token_per_leaf_module
206210
if max_depth is not None:
207211
self._config.max_depth = max_depth
212+
if request_limit is not None:
213+
self._config.request_limit = request_limit
214+
if agent_retries is not None:
215+
self._config.agent_retries = agent_retries
208216
if provider is not None:
209217
self._config.provider = provider
210218
if aws_region is not None:

‎codewiki/cli/models/config.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ class Configuration:
130130
max_token_per_module: Maximum tokens per module for clustering (default: 36369)
131131
max_token_per_leaf_module: Maximum tokens per leaf module (default: 16000)
132132
max_depth: Maximum depth for hierarchical decomposition (default: 2)
133+
request_limit: Maximum model requests per agent run (default: 100)
134+
agent_retries: Retries for a failing tool call (default: 3)
133135
use_gitignore: Apply Git ignore rules during repository analysis
134136
prompt_caching: Add prompt-cache breakpoints to agentic LLM calls (default: True)
135137
agent_instructions: Custom agent instructions for documentation generation
@@ -148,6 +150,8 @@ class Configuration:
148150
max_token_per_module: int = 36369
149151
max_token_per_leaf_module: int = 16000
150152
max_depth: int = 2
153+
request_limit: int = 100
154+
agent_retries: int = 3
151155
use_gitignore: bool = True
152156
prompt_caching: bool = True
153157
agent_instructions: AgentInstructions = field(default_factory=AgentInstructions)
@@ -187,6 +191,8 @@ def to_dict(self) -> dict:
187191
"max_token_per_module": self.max_token_per_module,
188192
"max_token_per_leaf_module": self.max_token_per_leaf_module,
189193
"max_depth": self.max_depth,
194+
"request_limit": self.request_limit,
195+
"agent_retries": self.agent_retries,
190196
"use_gitignore": self.use_gitignore,
191197
"prompt_caching": self.prompt_caching,
192198
"fallback_model": self.fallback_model,
@@ -224,6 +230,8 @@ def from_dict(cls, data: dict) -> "Configuration":
224230
max_token_per_module=data.get("max_token_per_module", 36369),
225231
max_token_per_leaf_module=data.get("max_token_per_leaf_module", 16000),
226232
max_depth=data.get("max_depth", 2),
233+
request_limit=data.get("request_limit", 100),
234+
agent_retries=data.get("agent_retries", 3),
227235
use_gitignore=data.get("use_gitignore", True),
228236
prompt_caching=data.get("prompt_caching", True),
229237
agent_instructions=agent_instructions,
@@ -300,6 +308,8 @@ def to_backend_config(
300308
max_token_per_module=self.max_token_per_module,
301309
max_token_per_leaf_module=self.max_token_per_leaf_module,
302310
max_depth=self.max_depth,
311+
request_limit=self.request_limit,
312+
agent_retries=self.agent_retries,
303313
agent_instructions=final_instructions.to_dict() if final_instructions else None,
304314
use_gitignore=self.use_gitignore,
305315
prompt_caching=self.prompt_caching,

‎codewiki/src/be/agent_tools/generate_sub_module_documentations.py‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import os
22

33
from pydantic_ai import RunContext, Tool, Agent
4+
from pydantic_ai.usage import UsageLimits
45

56
from codewiki.src.be.agent_tools.deps import CodeWikiDeps
67
from codewiki.src.be.module_naming import plan_sub_module_specs
78
from codewiki.src.be.agent_tools.read_code_components import read_code_components_tool
89
from codewiki.src.be.agent_tools.str_replace_editor import str_replace_editor_tool
910
from codewiki.src.be.llm_services import create_fallback_models
10-
from codewiki.src.be.prompt_template import SYSTEM_PROMPT, LEAF_SYSTEM_PROMPT, format_user_prompt
11+
from codewiki.src.be.prompt_template import (
12+
format_leaf_system_prompt,
13+
format_system_prompt,
14+
format_user_prompt,
15+
)
1116
from codewiki.src.be.utils import is_complex_module, count_tokens
1217
from codewiki.src.be.cluster_modules import format_potential_core_components
1318

@@ -83,9 +88,8 @@ async def generate_sub_module_documentation(
8388
model=fallback_models,
8489
name=sub_module_name,
8590
deps_type=CodeWikiDeps,
86-
system_prompt=SYSTEM_PROMPT.format(
87-
module_name=sub_module_name, custom_instructions=ctx.deps.custom_instructions
88-
),
91+
system_prompt=format_system_prompt(sub_module_name, ctx.deps.custom_instructions),
92+
retries=ctx.deps.config.agent_retries,
8993
tools=[
9094
read_code_components_tool,
9195
str_replace_editor_tool,
@@ -97,9 +101,10 @@ async def generate_sub_module_documentation(
97101
model=fallback_models,
98102
name=sub_module_name,
99103
deps_type=CodeWikiDeps,
100-
system_prompt=LEAF_SYSTEM_PROMPT.format(
101-
module_name=sub_module_name, custom_instructions=ctx.deps.custom_instructions
104+
system_prompt=format_leaf_system_prompt(
105+
sub_module_name, ctx.deps.custom_instructions
102106
),
107+
retries=ctx.deps.config.agent_retries,
103108
tools=[read_code_components_tool, str_replace_editor_tool],
104109
)
105110

@@ -117,6 +122,7 @@ async def generate_sub_module_documentation(
117122
module_tree=ctx.deps.module_tree,
118123
),
119124
deps=ctx.deps,
125+
usage_limits=UsageLimits(request_limit=ctx.deps.config.request_limit),
120126
)
121127

122128
# remove the sub-module name from the path to current module and the module tree

‎codewiki/src/be/backend.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,9 @@ def complete(
8989
prompt: str,
9090
*,
9191
model: str | None = None,
92+
system_prompt: str | None = None,
9293
) -> str:
93-
"""Single-shot text completion."""
94+
"""Single-shot text completion, with an optional system message."""
9495

9596
@abc.abstractmethod
9697
async def run_module_agent(

‎codewiki/src/be/caw_backend.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ def complete(
246246
prompt: str,
247247
*,
248248
model: str | None = None,
249+
system_prompt: str | None = None,
249250
) -> str:
250251
# Blocks the calling thread for the lifetime of the claude/codex
251252
# subprocess. Callers running this from an async context (e.g. the
@@ -256,6 +257,7 @@ def complete(
256257
provider=self._caw_provider,
257258
model=effective_model,
258259
tools=ToolGroup.READER,
260+
system_prompt=system_prompt,
259261
)
260262
traj = agent.completion(prompt)
261263
self.last_usage = usage_to_dict(getattr(traj, "total_usage", None))

‎codewiki/src/be/documentation_generator.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
MODULE_OVERVIEW_PROMPT,
2525
REPO_OVERVIEW_ARTIFACT_ADDENDUM,
2626
REPO_OVERVIEW_PROMPT,
27+
format_overview_system_prompt,
2728
)
2829
from codewiki.src.config import (
2930
FIRST_MODULE_TREE_FILENAME,
@@ -354,7 +355,10 @@ async def generate_parent_module_docs(
354355
logger.debug(f"Overview prompt for {module_name}: {len(prompt)} chars")
355356

356357
try:
357-
parent_docs = self.backend.complete(prompt)
358+
parent_docs = self.backend.complete(
359+
prompt,
360+
system_prompt=format_overview_system_prompt(self.config.get_prompt_addition()),
361+
)
358362
if not parent_docs:
359363
raise RuntimeError(
360364
f"LLM returned empty content for {module_name} overview "

0 commit comments

Comments
 (0)