Lucifer-cyber007 commited on
Commit
07d4313
Β·
1 Parent(s): aa0c12c

Update API fallback logic for Groq and Gemini

Browse files
Files changed (3) hide show
  1. baseline.py +64 -22
  2. free_review.py +76 -42
  3. inference.py +82 -29
baseline.py CHANGED
@@ -17,6 +17,8 @@ import sys
17
  import json
18
  import argparse
19
  from typing import Dict, Any
 
 
20
 
21
  from openai import OpenAI
22
  from environment import CodeReviewEnv
@@ -103,28 +105,70 @@ def parse_llm_response(content: str) -> Action:
103
  )
104
 
105
 
106
- def run_task(client: OpenAI, task_id: str, model: str, verbose: bool = True) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  env = CodeReviewEnv(task_id=task_id)
108
  obs = env.reset(task_id=task_id)
109
 
110
  if verbose:
111
  print(f"\n{'='*60}\n Task: {task_id.upper()} β€” {obs.file_name}\n{'='*60}")
112
 
113
- try:
114
- response = client.chat.completions.create(
115
- model=model,
116
- messages=[
117
- {"role": "system", "content": SYSTEM_PROMPT},
118
- {"role": "user", "content": build_user_prompt(obs.model_dump())},
119
- ],
120
- temperature=0.0,
121
- max_tokens=2000,
122
- )
123
- action = parse_llm_response(response.choices[0].message.content)
124
- except Exception as e:
125
  if verbose:
126
- print(f" [ERROR] {e}")
127
- action = Action(comments=[], verdict="comment", summary=f"Error: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
  _, reward, _, info = env.step(action)
130
  episode_history = [{
@@ -161,21 +205,19 @@ def main():
161
  parser.add_argument("--output-json", action="store_true")
162
  args = parser.parse_args()
163
 
164
- if not API_KEY:
165
- print("ERROR: No API_KEY or GEMINI_API_KEY env variable found.", file=sys.stderr)
166
- sys.exit(1)
167
 
168
- client = OpenAI(api_key=API_KEY, base_url=API_BASE_URL)
169
  task_ids = [args.task] if args.task else ["easy", "medium", "hard"]
170
- results = [run_task(client, t, args.model, not args.output_json) for t in task_ids]
171
 
172
  if args.output_json:
 
173
  print(json.dumps({
174
  "scores": [{"task_id": r["task_id"], "task_name": r["task_name"],
175
  "difficulty": r["difficulty"], "score": r["score"],
176
  "feedback": r["feedback"]} for r in results],
177
- "model_used": args.model,
178
- "note": "Temperature=0. Provider: Google Gemini free tier.",
179
  }))
180
  else:
181
  print(f"\n{'='*60}\n BASELINE SCORES\n{'='*60}")
 
17
  import json
18
  import argparse
19
  from typing import Dict, Any
20
+ from dotenv import load_dotenv
21
+ load_dotenv()
22
 
23
  from openai import OpenAI
24
  from environment import CodeReviewEnv
 
105
  )
106
 
107
 
108
+ def get_providers(model_arg):
109
+ providers = []
110
+
111
+ # 1. Groq
112
+ if os.environ.get("GROQ_API_KEY"):
113
+ providers.append({
114
+ "name": "Groq",
115
+ "api_key": os.environ.get("GROQ_API_KEY"),
116
+ "base_url": "https://api.groq.com/openai/v1",
117
+ "model": "llama-3.3-70b-versatile"
118
+ })
119
+
120
+ # 2. Gemini
121
+ if os.environ.get("GEMINI_API_KEY"):
122
+ providers.append({
123
+ "name": "Gemini",
124
+ "api_key": os.environ.get("GEMINI_API_KEY"),
125
+ "base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
126
+ "model": "gemini-2.0-flash"
127
+ })
128
+
129
+ # 3. Default
130
+ if not providers:
131
+ providers.append({
132
+ "name": "Default",
133
+ "api_key": API_KEY,
134
+ "base_url": API_BASE_URL,
135
+ "model": model_arg
136
+ })
137
+
138
+ return providers
139
+
140
+
141
+ def run_task(task_id: str, providers: list, verbose: bool = True) -> Dict[str, Any]:
142
  env = CodeReviewEnv(task_id=task_id)
143
  obs = env.reset(task_id=task_id)
144
 
145
  if verbose:
146
  print(f"\n{'='*60}\n Task: {task_id.upper()} β€” {obs.file_name}\n{'='*60}")
147
 
148
+ action = None
149
+
150
+ for provider in providers:
151
+ client = OpenAI(api_key=provider["api_key"], base_url=provider["base_url"])
 
 
 
 
 
 
 
 
152
  if verbose:
153
+ print(f" [INFO] Attempting inference with {provider['name']} ({provider['model']})", flush=True)
154
+
155
+ try:
156
+ response = client.chat.completions.create(
157
+ model=provider["model"],
158
+ messages=[
159
+ {"role": "system", "content": SYSTEM_PROMPT},
160
+ {"role": "user", "content": build_user_prompt(obs.model_dump())},
161
+ ],
162
+ temperature=0.0,
163
+ max_tokens=2000,
164
+ )
165
+ action = parse_llm_response(response.choices[0].message.content)
166
+ break # Success
167
+ except Exception as e:
168
+ if verbose:
169
+ print(f" [ERROR] {provider['name']} failed: {e}")
170
+ action = Action(comments=[], verdict="comment", summary=f"Error: {e}")
171
+ continue # Try next provider
172
 
173
  _, reward, _, info = env.step(action)
174
  episode_history = [{
 
205
  parser.add_argument("--output-json", action="store_true")
206
  args = parser.parse_args()
207
 
208
+ providers = get_providers(args.model)
 
 
209
 
 
210
  task_ids = [args.task] if args.task else ["easy", "medium", "hard"]
211
+ results = [run_task(t, providers, not args.output_json) for t in task_ids]
212
 
213
  if args.output_json:
214
+ used_model = providers[0]['model'] if providers else args.model
215
  print(json.dumps({
216
  "scores": [{"task_id": r["task_id"], "task_name": r["task_name"],
217
  "difficulty": r["difficulty"], "score": r["score"],
218
  "feedback": r["feedback"]} for r in results],
219
+ "model_used": used_model,
220
+ "note": "Temperature=0. Provider: Groq -> Gemini fallback.",
221
  }))
222
  else:
223
  print(f"\n{'='*60}\n BASELINE SCORES\n{'='*60}")
free_review.py CHANGED
@@ -1,13 +1,12 @@
1
  from openai import OpenAI
2
  import os
3
  import json
 
 
 
4
 
5
  import inference
6
 
7
- GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
8
- GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
9
- DEFAULT_MODEL = os.environ.get("FREE_REVIEW_MODEL", "gemini-2.0-flash")
10
-
11
  SYSTEM_PROMPT = """You are an expert code reviewer with 15+ years
12
  of experience. Review the provided code and identify ALL issues.
13
 
@@ -38,23 +37,43 @@ Respond ONLY with valid JSON, no markdown:
38
  "summary": "<summary>",
39
  "positive_aspects": ["<aspect1>", "<aspect2>"]
40
  }"""
41
-
42
  def review_free_code(code: str, language: str = "python",
43
  context: str = "") -> dict:
44
  """
45
- Review any arbitrary code using Gemini.
46
  Returns structured findings without a grader score.
47
  """
48
- api_key_to_use = GEMINI_API_KEY if GEMINI_API_KEY else inference._api_key
49
- base_url_to_use = GEMINI_BASE_URL if GEMINI_API_KEY else inference.API_BASE_URL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- if not api_key_to_use:
52
- return {"error": "GEMINI_API_KEY not set and inference proxy key unavailable"}
53
-
54
- client = OpenAI(
55
- api_key=api_key_to_use,
56
- base_url=base_url_to_use
57
- )
58
 
59
  user_prompt = f"""Language: {language}
60
  Context: {context if context else "General code review"}
@@ -66,30 +85,45 @@ Code to review:
66
 
67
  Review this code thoroughly and return JSON only."""
68
 
69
- try:
70
- response = client.chat.completions.create(
71
- model=DEFAULT_MODEL,
72
- messages=[
73
- {"role": "system", "content": SYSTEM_PROMPT},
74
- {"role": "user", "content": user_prompt}
75
- ],
76
- temperature=0.1,
77
- max_tokens=3000,
78
- )
79
- content = response.choices[0].message.content.strip()
80
- if content.startswith("```"):
81
- lines = content.split("\n")
82
- content = "\n".join(lines[1:])
83
- if content.strip().endswith("```"):
84
- content = content.strip()[:-3].strip()
85
- return json.loads(content)
86
- except json.JSONDecodeError:
87
- return {"error": "Failed to parse AI response",
88
- "raw": content[:500]}
89
- except Exception as e:
90
- err_str = str(e)
91
- if "429" in err_str or "quota" in err_str.lower() or "RESOURCE_EXHAUSTED" in err_str:
92
- return {"error": "API rate limit reached (429). Your Gemini free-tier quota is exhausted. "
93
- "Wait a minute and try again, or get a new API key at "
94
- "https://aistudio.google.com/app/apikey"}
95
- return {"error": err_str}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from openai import OpenAI
2
  import os
3
  import json
4
+ import time
5
+ from dotenv import load_dotenv
6
+ load_dotenv()
7
 
8
  import inference
9
 
 
 
 
 
10
  SYSTEM_PROMPT = """You are an expert code reviewer with 15+ years
11
  of experience. Review the provided code and identify ALL issues.
12
 
 
37
  "summary": "<summary>",
38
  "positive_aspects": ["<aspect1>", "<aspect2>"]
39
  }"""
 
40
  def review_free_code(code: str, language: str = "python",
41
  context: str = "") -> dict:
42
  """
43
+ Review any arbitrary code using Groq or Gemini.
44
  Returns structured findings without a grader score.
45
  """
46
+ providers = []
47
+
48
+ # 1. Primary: Groq
49
+ if os.environ.get("GROQ_API_KEY"):
50
+ providers.append({
51
+ "name": "Groq",
52
+ "api_key": os.environ.get("GROQ_API_KEY"),
53
+ "base_url": "https://api.groq.com/openai/v1",
54
+ "model": "llama-3.3-70b-versatile"
55
+ })
56
+
57
+ # 2. Fallback: Gemini
58
+ if os.environ.get("GEMINI_API_KEY"):
59
+ providers.append({
60
+ "name": "Gemini",
61
+ "api_key": os.environ.get("GEMINI_API_KEY"),
62
+ "base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
63
+ "model": "gemini-2.0-flash"
64
+ })
65
+
66
+ # 3. Last Resort: Inference Proxy / Hackathon Default
67
+ if not providers and hasattr(inference, '_api_key') and inference._api_key:
68
+ providers.append({
69
+ "name": "Fallback Proxy",
70
+ "api_key": inference._api_key,
71
+ "base_url": inference.API_BASE_URL,
72
+ "model": inference.MODEL_NAME
73
+ })
74
 
75
+ if not providers:
76
+ return {"error": "API keys not set. Please add GROQ_API_KEY or GEMINI_API_KEY to your .env file."}
 
 
 
 
 
77
 
78
  user_prompt = f"""Language: {language}
79
  Context: {context if context else "General code review"}
 
85
 
86
  Review this code thoroughly and return JSON only."""
87
 
88
+ last_error = ""
89
+
90
+ for provider in providers:
91
+ client = OpenAI(api_key=provider["api_key"], base_url=provider["base_url"])
92
+ print(f"Attempting review with {provider['name']} ({provider['model']})...")
93
+
94
+ provider_attempts = 3
95
+ for attempt in range(provider_attempts):
96
+ try:
97
+ response = client.chat.completions.create(
98
+ model=provider["model"],
99
+ messages=[
100
+ {"role": "system", "content": SYSTEM_PROMPT},
101
+ {"role": "user", "content": user_prompt}
102
+ ],
103
+ temperature=0.1,
104
+ max_tokens=3000,
105
+ )
106
+ content = response.choices[0].message.content.strip()
107
+ if content.startswith("```"):
108
+ lines = content.split("\n")
109
+ content = "\n".join(lines[1:])
110
+ if content.strip().endswith("```"):
111
+ content = content.strip()[:-3].strip()
112
+ return json.loads(content)
113
+ except json.JSONDecodeError:
114
+ return {"error": f"Failed to parse JSON from {provider['name']} model",
115
+ "raw": content[:500]}
116
+ except Exception as e:
117
+ err_str = str(e)
118
+ last_error = err_str
119
+ # For Groq or Gemini 429 errors
120
+ if "429" in err_str or "quota" in err_str.lower() or "RESOURCE_EXHAUSTED" in err_str:
121
+ if attempt < provider_attempts - 1:
122
+ wait_time = 10 * (attempt + 1)
123
+ print(f"[{provider['name']}] Rate limit hit. Waiting {wait_time}s before retry ({attempt+1}/{provider_attempts})...")
124
+ time.sleep(wait_time)
125
+ continue
126
+ print(f"[{provider['name']}] Request failed. Falling back to next provider if available.")
127
+ break # Exit retry loop and move to next provider
128
+
129
+ return {"error": f"All fallback APIs failed. Last error: {last_error}"}
inference.py CHANGED
@@ -15,6 +15,8 @@ import sys
15
  import json
16
  import argparse
17
  from typing import Dict, Any
 
 
18
 
19
  from openai import OpenAI
20
  from environment import CodeReviewEnv
@@ -23,14 +25,13 @@ from models import Action, CodeComment, GraderInput
23
 
24
 
25
  # ── Exactly as required by the Pre-Submission Checklist ──────────────────
26
- API_BASE_URL = os.getenv("API_BASE_URL", "https://generativelanguage.googleapis.com/v1beta/openai/")
27
- MODEL_NAME = os.getenv("MODEL_NAME", "gemini-2.0-flash")
28
  HF_TOKEN = os.getenv("HF_TOKEN")
29
  LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME")
30
 
31
  # Use HF_TOKEN if provided by validator, else fall back to GEMINI_API_KEY
32
- _api_key = HF_TOKEN or os.getenv("GEMINI_API_KEY", "AIzaSyA92Np6UCnjIDhyKr-xFWcPDgWwcZ9Q63M")
33
-
34
 
35
  SYSTEM_PROMPT = """You are an expert code reviewer. You will be given a code diff from a pull request.
36
  Your job is to identify ALL bugs, security vulnerabilities, performance issues, and logic errors.
@@ -100,7 +101,49 @@ def parse_llm_response(content: str) -> Action:
100
  )
101
 
102
 
103
- def run_task(client: OpenAI, task_id: str, model: str, verbose: bool = True) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  env = CodeReviewEnv(task_id=task_id)
105
  obs = env.reset(task_id=task_id)
106
 
@@ -109,23 +152,35 @@ def run_task(client: OpenAI, task_id: str, model: str, verbose: bool = True) ->
109
 
110
  # REQUIRED: [START] block
111
  print(f"[START] task={task_id}", flush=True)
112
-
113
- try:
114
- # All LLM calls use OpenAI client configured via checklist variables
115
- response = client.chat.completions.create(
116
- model=model,
117
- messages=[
118
- {"role": "system", "content": SYSTEM_PROMPT},
119
- {"role": "user", "content": build_user_prompt(obs.model_dump())},
120
- ],
121
- temperature=0.0,
122
- max_tokens=2000,
123
- )
124
- action = parse_llm_response(response.choices[0].message.content)
125
- except Exception as e:
126
  if verbose:
127
- print(f" [ERROR] {e}", flush=True)
128
- action = Action(comments=[], verdict="comment", summary=f"Error: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  _, reward, _, info = env.step(action)
131
 
@@ -169,22 +224,20 @@ def main():
169
  parser.add_argument("--output-json", action="store_true")
170
  args = parser.parse_args()
171
 
172
- # All LLM calls use OpenAI client configured via checklist variables
173
- client = OpenAI(
174
- api_key=_api_key,
175
- base_url=API_BASE_URL,
176
- )
177
 
178
  task_ids = [args.task] if args.task else ["easy", "medium", "hard"]
179
- results = [run_task(client, t, args.model, not args.output_json) for t in task_ids]
180
 
181
  if args.output_json:
 
 
182
  print(json.dumps({
183
  "scores": [{"task_id": r["task_id"], "task_name": r["task_name"],
184
  "difficulty": r["difficulty"], "score": r["score"],
185
  "feedback": r["feedback"]} for r in results],
186
- "model_used": args.model,
187
- "note": "Temperature=0. Uses API_BASE_URL + HF_TOKEN from environment.",
188
  }), flush=True)
189
  else:
190
  print(f"\n{'='*60}\n BASELINE SCORES\n{'='*60}", flush=True)
 
15
  import json
16
  import argparse
17
  from typing import Dict, Any
18
+ from dotenv import load_dotenv
19
+ load_dotenv()
20
 
21
  from openai import OpenAI
22
  from environment import CodeReviewEnv
 
25
 
26
 
27
  # ── Exactly as required by the Pre-Submission Checklist ──────────────────
28
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://api.groq.com/openai/v1")
29
+ MODEL_NAME = os.getenv("MODEL_NAME", "llama-3.3-70b-versatile")
30
  HF_TOKEN = os.getenv("HF_TOKEN")
31
  LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME")
32
 
33
  # Use HF_TOKEN if provided by validator, else fall back to GEMINI_API_KEY
34
+ _api_key = HF_TOKEN or os.getenv("GROQ_API_KEY") or os.getenv("GEMINI_API_KEY", "")
 
35
 
36
  SYSTEM_PROMPT = """You are an expert code reviewer. You will be given a code diff from a pull request.
37
  Your job is to identify ALL bugs, security vulnerabilities, performance issues, and logic errors.
 
101
  )
102
 
103
 
104
+ def get_providers(model_arg):
105
+ providers = []
106
+
107
+ # 1. Hackathon Proxy environment (if injected by validator)
108
+ if os.getenv("HF_TOKEN") and os.getenv("API_BASE_URL") and "generative" not in os.getenv("API_BASE_URL", "") and "groq" not in os.getenv("API_BASE_URL", ""):
109
+ providers.append({
110
+ "name": "Hackathon Proxy",
111
+ "api_key": os.getenv("HF_TOKEN"),
112
+ "base_url": os.getenv("API_BASE_URL"),
113
+ "model": os.getenv("MODEL_NAME", model_arg)
114
+ })
115
+
116
+ # 2. Main Provider: Groq
117
+ if os.environ.get("GROQ_API_KEY"):
118
+ providers.append({
119
+ "name": "Groq",
120
+ "api_key": os.environ.get("GROQ_API_KEY"),
121
+ "base_url": "https://api.groq.com/openai/v1",
122
+ "model": "llama-3.3-70b-versatile"
123
+ })
124
+
125
+ # 3. Fallback: Gemini
126
+ if os.environ.get("GEMINI_API_KEY"):
127
+ providers.append({
128
+ "name": "Gemini",
129
+ "api_key": os.environ.get("GEMINI_API_KEY"),
130
+ "base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
131
+ "model": "gemini-2.0-flash"
132
+ })
133
+
134
+ # Fallback to defaults
135
+ if not providers:
136
+ providers.append({
137
+ "name": "Default",
138
+ "api_key": _api_key,
139
+ "base_url": API_BASE_URL,
140
+ "model": model_arg
141
+ })
142
+
143
+ return providers
144
+
145
+
146
+ def run_task(task_id: str, providers: list, verbose: bool = True) -> Dict[str, Any]:
147
  env = CodeReviewEnv(task_id=task_id)
148
  obs = env.reset(task_id=task_id)
149
 
 
152
 
153
  # REQUIRED: [START] block
154
  print(f"[START] task={task_id}", flush=True)
155
+
156
+ action = None
157
+
158
+ for provider in providers:
159
+ client = OpenAI(api_key=provider["api_key"], base_url=provider["base_url"])
 
 
 
 
 
 
 
 
 
160
  if verbose:
161
+ print(f" [INFO] Attempting inference with {provider['name']} ({provider['model']})", flush=True)
162
+
163
+ try:
164
+ response = client.chat.completions.create(
165
+ model=provider["model"],
166
+ messages=[
167
+ {"role": "system", "content": SYSTEM_PROMPT},
168
+ {"role": "user", "content": build_user_prompt(obs.model_dump())},
169
+ ],
170
+ temperature=0.0,
171
+ max_tokens=2000,
172
+ )
173
+ action = parse_llm_response(response.choices[0].message.content)
174
+ break # Success, exit provider loop
175
+ except Exception as e:
176
+ err_str = str(e)
177
+ if verbose:
178
+ print(f" [ERROR] {provider['name']} failed: {err_str}", flush=True)
179
+ if "429" in err_str or "quota" in err_str.lower() or "RESOURCE_EXHAUSTED" in err_str:
180
+ if verbose:
181
+ print(f" [INFO] Rate limit reached on {provider['name']}, switching to fallback...", flush=True)
182
+ action = Action(comments=[], verdict="comment", summary=f"Error: {e}")
183
+ continue # Try next provider
184
 
185
  _, reward, _, info = env.step(action)
186
 
 
224
  parser.add_argument("--output-json", action="store_true")
225
  args = parser.parse_args()
226
 
227
+ providers = get_providers(args.model)
 
 
 
 
228
 
229
  task_ids = [args.task] if args.task else ["easy", "medium", "hard"]
230
+ results = [run_task(t, providers, not args.output_json) for t in task_ids]
231
 
232
  if args.output_json:
233
+ # Just grab the first provider's model as a proxy for what was used across tasks
234
+ used_model = providers[0]['model'] if providers else args.model
235
  print(json.dumps({
236
  "scores": [{"task_id": r["task_id"], "task_name": r["task_name"],
237
  "difficulty": r["difficulty"], "score": r["score"],
238
  "feedback": r["feedback"]} for r in results],
239
+ "model_used": used_model,
240
+ "note": "Temperature=0. Uses environment variables with Groq->Gemini fallback.",
241
  }), flush=True)
242
  else:
243
  print(f"\n{'='*60}\n BASELINE SCORES\n{'='*60}", flush=True)