Shubhamm-02 commited on
Commit
37629cf
·
1 Parent(s): 381e858

Cleanup: single source of truth for backend, fresh agent per review

Browse files
Files changed (2) hide show
  1. app.py +6 -13
  2. pr_review_agent/agent.py +39 -17
app.py CHANGED
@@ -9,7 +9,6 @@ Deployed demo uses `groq` (free tier); local dev uses `ollama`.
9
 
10
  from __future__ import annotations
11
 
12
- import os
13
  import time
14
 
15
  import gradio as gr
@@ -19,7 +18,7 @@ load_dotenv() # load .env locally; a no-op in hosted envs that use real env var
19
 
20
  from pr_review_agent.agent import (
21
  assemble_context,
22
- build_agent,
23
  reason_over,
24
  )
25
  from pr_review_agent.tools import (
@@ -42,17 +41,9 @@ try: # pragma: no cover - only runs on Hugging Face Spaces
42
  except Exception:
43
  pass
44
 
45
- # Build the agent once at startup and reuse it across requests.
46
- _agent = build_agent()
47
-
48
  # Which model is answering — shown in the header as a small technical detail.
49
- _BACKEND = os.getenv("MODEL_BACKEND", "groq").lower()
50
- _MODEL_LABEL = {
51
- "groq": f"{os.getenv('GROQ_MODEL', 'openai/gpt-oss-120b')} · Groq",
52
- "gemini": f"{os.getenv('GEMINI_MODEL', 'gemini-2.0-flash')} · Gemini",
53
- "bedrock": "Claude · Bedrock",
54
- "ollama": f"{os.getenv('OLLAMA_MODEL', 'qwen2.5:7b')} · local",
55
- }.get(_BACKEND, _BACKEND)
56
 
57
  CSS = """
58
  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
@@ -220,7 +211,9 @@ def review(pr_ref: str):
220
  yield _loader(2)
221
  conventions = get_repo_conventions(pr_ref)
222
  yield _loader(3)
223
- report = reason_over(assemble_context(pr_ref, metadata, diff, conventions), agent=_agent)
 
 
224
  # The model call does reasoning + writing in one shot; briefly show the final
225
  # "Writing the briefing" step so the loader visibly completes before the report.
226
  yield _loader(4)
 
9
 
10
  from __future__ import annotations
11
 
 
12
  import time
13
 
14
  import gradio as gr
 
18
 
19
  from pr_review_agent.agent import (
20
  assemble_context,
21
+ current_model_label,
22
  reason_over,
23
  )
24
  from pr_review_agent.tools import (
 
41
  except Exception:
42
  pass
43
 
 
 
 
44
  # Which model is answering — shown in the header as a small technical detail.
45
+ # Resolved from the agent module so the label can't drift from what actually runs.
46
+ _MODEL_LABEL = current_model_label()
 
 
 
 
 
47
 
48
  CSS = """
49
  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
 
211
  yield _loader(2)
212
  conventions = get_repo_conventions(pr_ref)
213
  yield _loader(3)
214
+ # No shared agent: reason_over builds a fresh one per review, so concurrent
215
+ # reviews never share mutable conversation state.
216
+ report = reason_over(assemble_context(pr_ref, metadata, diff, conventions))
217
  # The model call does reasoning + writing in one shot; briefly show the final
218
  # "Writing the briefing" step so the loader visibly completes before the report.
219
  yield _loader(4)
pr_review_agent/agent.py CHANGED
@@ -54,15 +54,41 @@ Rules:
54
  """
55
 
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  def _build_model():
58
- backend = os.getenv("MODEL_BACKEND", "bedrock").lower()
59
 
60
  if backend == "ollama":
61
  from strands.models.ollama import OllamaModel
62
 
63
  return OllamaModel(
64
  host=os.getenv("OLLAMA_HOST", "http://localhost:11434"),
65
- model_id=os.getenv("OLLAMA_MODEL", "llama3.1"),
66
  temperature=0.1,
67
  )
68
 
@@ -77,7 +103,7 @@ def _build_model():
77
  "api_key": os.getenv("GROQ_API_KEY"),
78
  "base_url": "https://api.groq.com/openai/v1",
79
  },
80
- model_id=os.getenv("GROQ_MODEL", "openai/gpt-oss-120b"),
81
  params={"temperature": 0.2},
82
  )
83
 
@@ -90,7 +116,7 @@ def _build_model():
90
  client_args={
91
  "api_key": os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
92
  },
93
- model_id=os.getenv("GEMINI_MODEL", "gemini-2.0-flash"),
94
  params={"temperature": 0.2},
95
  )
96
 
@@ -99,10 +125,7 @@ def _build_model():
99
  from strands.models import BedrockModel
100
 
101
  return BedrockModel(
102
- model_id=os.getenv(
103
- "BEDROCK_MODEL_ID",
104
- "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
105
- ),
106
  region_name=os.getenv("AWS_REGION", "us-west-2"),
107
  temperature=0.2,
108
  )
@@ -155,15 +178,15 @@ def gather_context(pr_ref: str) -> str:
155
 
156
 
157
  def reason_over(context: str, agent: Agent | None = None) -> str:
158
- """Run the agent over an assembled context blob and return the cleaned report."""
 
 
 
 
 
 
 
159
  agent = agent or build_agent()
160
- # Each review is an independent single-turn call. Clear any prior history so we
161
- # never replay a previous assistant turn — gpt-oss reasoning content is rejected
162
- # by Groq in multi-turn conversations, which would break the 2nd+ review.
163
- try:
164
- agent.messages.clear()
165
- except Exception:
166
- agent.messages = []
167
  instruction = (
168
  f"{context}\n"
169
  "Using ONLY the context above, write the review report now, with all five "
@@ -189,5 +212,4 @@ def prepare_review(pr_ref: str, agent: Agent | None = None) -> str:
189
  Returns:
190
  The five-section markdown review report.
191
  """
192
- agent = agent or build_agent()
193
  return reason_over(gather_context(pr_ref), agent)
 
54
  """
55
 
56
 
57
+ DEFAULT_BACKEND = "groq"
58
+
59
+ # Single source of truth for each backend: env var holding the model id, its default,
60
+ # and a display name. Used both to build the model and to label it in the UI, so the two
61
+ # can never drift apart.
62
+ _BACKENDS = {
63
+ "groq": ("GROQ_MODEL", "openai/gpt-oss-120b", "Groq"),
64
+ "gemini": ("GEMINI_MODEL", "gemini-2.0-flash", "Gemini"),
65
+ "bedrock": ("BEDROCK_MODEL_ID", "us.anthropic.claude-sonnet-4-5-20250929-v1:0", "Bedrock"),
66
+ "ollama": ("OLLAMA_MODEL", "qwen2.5:7b", "local"),
67
+ }
68
+
69
+
70
+ def current_backend() -> str:
71
+ """The active model backend, from MODEL_BACKEND (defaulting to DEFAULT_BACKEND)."""
72
+ return os.getenv("MODEL_BACKEND", DEFAULT_BACKEND).lower()
73
+
74
+
75
+ def current_model_label() -> str:
76
+ """A 'model-id · Backend' label for the active backend, for the UI header."""
77
+ backend = current_backend()
78
+ env_var, default_id, nice = _BACKENDS.get(backend, (None, "", backend))
79
+ model_id = os.getenv(env_var, default_id) if env_var else backend
80
+ return f"{model_id} · {nice}"
81
+
82
+
83
  def _build_model():
84
+ backend = current_backend()
85
 
86
  if backend == "ollama":
87
  from strands.models.ollama import OllamaModel
88
 
89
  return OllamaModel(
90
  host=os.getenv("OLLAMA_HOST", "http://localhost:11434"),
91
+ model_id=os.getenv("OLLAMA_MODEL", _BACKENDS["ollama"][1]),
92
  temperature=0.1,
93
  )
94
 
 
103
  "api_key": os.getenv("GROQ_API_KEY"),
104
  "base_url": "https://api.groq.com/openai/v1",
105
  },
106
+ model_id=os.getenv("GROQ_MODEL", _BACKENDS["groq"][1]),
107
  params={"temperature": 0.2},
108
  )
109
 
 
116
  client_args={
117
  "api_key": os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
118
  },
119
+ model_id=os.getenv("GEMINI_MODEL", _BACKENDS["gemini"][1]),
120
  params={"temperature": 0.2},
121
  )
122
 
 
125
  from strands.models import BedrockModel
126
 
127
  return BedrockModel(
128
+ model_id=os.getenv("BEDROCK_MODEL_ID", _BACKENDS["bedrock"][1]),
 
 
 
129
  region_name=os.getenv("AWS_REGION", "us-west-2"),
130
  temperature=0.2,
131
  )
 
178
 
179
 
180
  def reason_over(context: str, agent: Agent | None = None) -> str:
181
+ """Run the agent over an assembled context blob and return the cleaned report.
182
+
183
+ Each review is an independent single-turn call. When no agent is passed, a fresh
184
+ one is built — so there is no prior turn to replay (a reused agent would send its
185
+ previous assistant turn back, and gpt-oss reasoning content is rejected by Groq in
186
+ multi-turn conversations). Callers that serve concurrent reviews should NOT share
187
+ one agent; pass None and let each review build its own.
188
+ """
189
  agent = agent or build_agent()
 
 
 
 
 
 
 
190
  instruction = (
191
  f"{context}\n"
192
  "Using ONLY the context above, write the review report now, with all five "
 
212
  Returns:
213
  The five-section markdown review report.
214
  """
 
215
  return reason_over(gather_context(pr_ref), agent)