Skip to content

Commit 36dddf2

Browse files
committed
feat: Phase 7 - Multi-Model Agent System (v0.7.0)
- Model Registry: 5 Gemini models (3.1 Pro, 3 Pro, 3 Flash, 3.1 Flash Lite, 2.5 Flash) with capability scores, costs, context windows - Smart Task Router: 3 strategies (performance/balanced/economy), complexity-aware routing, fallback chains - Multi-Agent: 5 specialized agents (Analyzer, CodeGen, Reviewer, DocsWriter, Planner) with AgentCoordinator pipeline - Env var fallback: GitHub token from GITHUB_TOKEN/gh CLI, LLM keys from GEMINI/OPENAI/ANTHROPIC_API_KEY - Vertex AI: Google Cloud support via vertex_project + vertex_location in config - CLI: contribai models command to list models and task assignments - MultiModelConfig with strategy and per-task model overrides - Tests: 35 new (total: 286), fixed StrEnum lint
1 parent 6de1988 commit 36dddf2

13 files changed

Lines changed: 1154 additions & 15 deletions

File tree

contribai/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"""ContribAI - AI Agent for Open Source Contributions."""
22

3-
__version__ = "0.6.0"
3+
__version__ = "0.7.0"
44
__app_name__ = "contribai"

contribai/cli/main.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def run(ctx, language, stars, max_prs, dry_run):
8888
console.print("Set it in config.yaml or run: contribai config set github.token <token>")
8989
sys.exit(1)
9090

91-
if not config.llm.api_key:
91+
if not config.llm.api_key and not config.llm.use_vertex:
9292
console.print("[red]❌ LLM API key not configured![/red]")
9393
sys.exit(1)
9494

@@ -126,7 +126,7 @@ def target(ctx, url, types, dry_run):
126126
console.print("[red]❌ GitHub token not configured![/red]")
127127
sys.exit(1)
128128

129-
if not config.llm.api_key:
129+
if not config.llm.api_key and not config.llm.use_vertex:
130130
console.print("[red]❌ LLM API key not configured![/red]")
131131
sys.exit(1)
132132

@@ -154,7 +154,7 @@ def analyze(ctx, url):
154154
console.print("[red]❌ GitHub token not configured![/red]")
155155
sys.exit(1)
156156

157-
if not config.llm.api_key:
157+
if not config.llm.api_key and not config.llm.use_vertex:
158158
console.print("[red]❌ LLM API key not configured![/red]")
159159
sys.exit(1)
160160

@@ -222,7 +222,7 @@ def solve(ctx, url, max_issues, dry_run):
222222
console.print("[red]❌ GitHub token not configured![/red]")
223223
sys.exit(1)
224224

225-
if not config.llm.api_key:
225+
if not config.llm.api_key and not config.llm.use_vertex:
226226
console.print("[red]❌ LLM API key not configured![/red]")
227227
sys.exit(1)
228228

@@ -574,6 +574,70 @@ def _print_result(result, dry_run: bool):
574574
console.print(f" • {e}")
575575

576576

577+
@cli.command("models")
578+
@click.option("--task", default=None, help="Filter by task type")
579+
@click.pass_context
580+
def show_models(ctx, task):
581+
"""List available models and their capabilities."""
582+
from contribai.llm.models import (
583+
ALL_MODELS,
584+
TaskType,
585+
get_models_for_task,
586+
)
587+
from contribai.llm.router import TaskRouter
588+
589+
if task:
590+
try:
591+
tt = TaskType(task)
592+
except ValueError:
593+
console.print(
594+
f"[red]Unknown task type: {task}[/red]\n"
595+
f"Valid: {', '.join(t.value for t in TaskType)}"
596+
)
597+
return
598+
models = get_models_for_task(tt)
599+
console.print(f"\n[bold]Best models for [cyan]{task}[/cyan]:[/bold]\n")
600+
else:
601+
models = ALL_MODELS
602+
console.print("\n[bold]Available Models:[/bold]\n")
603+
604+
table = Table()
605+
table.add_column("Model", style="cyan")
606+
table.add_column("Tier")
607+
table.add_column("Code", justify="right")
608+
table.add_column("Analysis", justify="right")
609+
table.add_column("Speed", justify="right")
610+
table.add_column("Cost (in/out)")
611+
table.add_column("Best For")
612+
613+
for m in models:
614+
tier_color = {
615+
"pro": "red",
616+
"flash": "yellow",
617+
"lite": "green",
618+
}.get(m.tier.value, "white")
619+
620+
table.add_row(
621+
m.display_name,
622+
f"[{tier_color}]{m.tier.value.upper()}[/{tier_color}]",
623+
str(m.coding),
624+
str(m.analysis),
625+
str(m.speed),
626+
f"${m.input_cost:.2f}/${m.output_cost:.2f}",
627+
", ".join(t.value for t in m.best_for[:3]),
628+
)
629+
630+
console.print(table)
631+
632+
# Show default assignments
633+
router = TaskRouter()
634+
defaults = router.get_default_assignments()
635+
console.print("\n[bold]Default Task Assignments:[/bold]")
636+
for task_type, model_name in defaults.items():
637+
console.print(f" {task_type}: [cyan]{model_name}[/cyan]")
638+
console.print()
639+
640+
577641
@cli.command("interactive")
578642
@click.pass_context
579643
def interactive(ctx):

contribai/core/config.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class LLMConfig(BaseModel):
5252
base_url: str | None = None # for ollama or custom endpoints
5353
# Vertex AI (Google Cloud)
5454
vertex_project: str = ""
55-
vertex_location: str = "us-central1"
55+
vertex_location: str = "global"
5656

5757
@model_validator(mode="after")
5858
def resolve_api_key_and_defaults(self):
@@ -185,8 +185,17 @@ class NotificationConfig(BaseModel):
185185
on_run_complete: bool = True
186186

187187

188+
class MultiModelConfig(BaseModel):
189+
"""Multi-model routing configuration."""
190+
191+
enabled: bool = False
192+
strategy: str = "balanced" # performance | balanced | economy
193+
# Per-task model overrides (task_type → model_name)
194+
model_overrides: dict[str, str] = Field(default_factory=dict)
195+
196+
188197
class ContribAIConfig(BaseModel):
189-
"""Root configuration for ContribAI."""
198+
"""Root configuration for ContribAIConfig."""
190199

191200
github: GitHubConfig = Field(default_factory=GitHubConfig)
192201
llm: LLMConfig = Field(default_factory=LLMConfig)
@@ -199,6 +208,7 @@ class ContribAIConfig(BaseModel):
199208
pipeline: PipelineConfig = Field(default_factory=PipelineConfig)
200209
quota: QuotaConfig = Field(default_factory=QuotaConfig)
201210
notifications: NotificationConfig = Field(default_factory=NotificationConfig)
211+
multi_model: MultiModelConfig = Field(default_factory=MultiModelConfig)
202212

203213

204214
def load_config(path: str | Path | None = None) -> ContribAIConfig:

0 commit comments

Comments
 (0)