alfulanny commited on
Commit
c808e22
·
verified ·
1 Parent(s): 3fd4b07

Update code_agent.py

Browse files
Files changed (1) hide show
  1. code_agent.py +148 -41
code_agent.py CHANGED
@@ -3,9 +3,13 @@ Real `smolagents` CodeAgent integration for the final project.
3
 
4
  This module initializes a `CodeAgent` with standard tools and exposes a
5
  `run_agent(prompt)` function returning the final answer string.
6
- """
7
-
8
 
 
 
 
 
 
 
9
  from typing import List, Any
10
  import logging
11
  import os
@@ -22,12 +26,29 @@ from smolagents import (
22
  )
23
 
24
  # Get HF token from environment (set by huggingface-cli login or HF_TOKEN env var)
25
- HF_TOKEN = os.environ.get("HF_TOKEN")
26
- if not HF_TOKEN:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  logger.warning(
28
- "HF_TOKEN not found in environment. Run 'huggingface-cli login' or set HF_TOKEN env var. "
29
  "CodeAgent initialization will fail without valid credentials."
30
  )
 
 
 
31
 
32
 
33
  def make_code_agent(
@@ -72,14 +93,30 @@ def make_code_agent(
72
  if model_name:
73
  try:
74
  model = InferenceClientModel(model_id=model_name, token=HF_TOKEN)
75
- except Exception:
 
 
76
  try:
77
  model = InferenceClientModel(model_id='allenai/Olmo-3-7B-Instruct', token=HF_TOKEN)
78
- except Exception:
 
 
79
  model = InferenceClientModel(token=HF_TOKEN)
 
80
  else:
81
- model = InferenceClientModel(token=HF_TOKEN)
82
- logger.info("InferenceClientModel initialized successfully with HF_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
83
  except Exception as e:
84
  logger.error(
85
  "InferenceClientModel initialization failed (ensure HF_TOKEN is set and has credits): %s", e
@@ -90,6 +127,55 @@ def make_code_agent(
90
  return agent
91
 
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  _AGENT_SINGLETON = None
94
 
95
 
@@ -97,76 +183,97 @@ def get_agent():
97
  """Get or create the singleton CodeAgent instance."""
98
  global _AGENT_SINGLETON
99
  if _AGENT_SINGLETON is None:
100
- _AGENT_SINGLETON = make_code_agent(model_name="allenai/Olmo-3-7B-Think")
 
 
 
 
 
 
101
  return _AGENT_SINGLETON
102
 
103
 
104
  def run_agent(prompt: str) -> str:
105
- """
106
- Run the CodeAgent and return the final answer string.
107
-
108
  Args:
109
  prompt: the reasoning task/question for the agent.
110
 
111
  Returns:
112
- The agent's final answer as a string.
113
-
114
  Raises:
115
  Exception: if CodeAgent.run fails (e.g., no HF credentials or credits).
116
  """
117
  agent = get_agent()
118
 
119
- # System-level instruction to avoid returning raw code or chained tool
120
- # calls in a single code block. We require a single-line plain-text
121
- # final answer with no surrounding code fences or explanation.
122
  system_instruction = (
123
- "SYSTEM_INSTRUCTION: Return ONLY the concise final answer as plain text on a single line. "
124
- "Do NOT produce code blocks, do NOT chain tool calls inside the same code block, and do NOT include explanations."
 
125
  )
126
 
127
  full_prompt = f"{system_instruction}\n\n{prompt}"
128
 
129
  try:
130
  res = agent.run(full_prompt)
 
131
  except Exception as e:
132
- # Surface the error (likely HF auth/credits); re-raise for the caller to handle
133
- logger.error("CodeAgent.run failed: %s", e)
134
  raise
135
 
136
- # If the agent produced a dict-like response, prefer canonical keys
137
  if isinstance(res, dict):
138
- for key in ("answer", "final_answer", "final"):
139
  if key in res and isinstance(res[key], str):
140
- return res[key].strip()
141
- # fallback: stringify
142
- return str(res).strip()
 
 
143
 
144
- # If model output contains code fences or appears to be code, try a single-pass retry
145
  if isinstance(res, str):
146
  text = res.strip()
147
- # naive detection of code fences or '```' or 'import ' which suggests code
148
- if "```" in text or text.lstrip().startswith("import ") or "def " in text:
149
- logger.warning("Agent returned code-like output; retrying with stricter instruction")
 
 
 
 
 
150
  stricter = (
151
- "STRICT_INSTRUCTION: You must return only the final answer. No code, no markdown, no explanations. "
152
- "If the answer is a number, return only the number."
 
153
  )
154
  retry_prompt = f"{stricter}\n\n{prompt}"
155
  try:
156
  retry_res = agent.run(retry_prompt)
157
  if isinstance(retry_res, str):
158
- return retry_res.strip()
 
 
159
  if isinstance(retry_res, dict):
160
- for key in ("answer", "final_answer", "final"):
161
  if key in retry_res and isinstance(retry_res[key], str):
162
- return retry_res[key].strip()
163
- return str(retry_res).strip()
 
164
  except Exception as e:
165
- logger.error("Retry run failed: %s", e)
166
- # fallthrough to return original text
167
- return text
 
 
 
 
168
 
169
- return str(res)
 
170
 
171
 
172
  if __name__ == "__main__":
 
3
 
4
  This module initializes a `CodeAgent` with standard tools and exposes a
5
  `run_agent(prompt)` function returning the final answer string.
 
 
6
 
7
+ Notes:
8
+ - Requires `smolagents` installed in the environment.
9
+ - For serverless inference via Hugging Face, you must be logged in
10
+ (`huggingface-cli login`) or have `HF_TOKEN` environment variable set,
11
+ and have sufficient provider credits.
12
+ """
13
  from typing import List, Any
14
  import logging
15
  import os
 
26
  )
27
 
28
  # Get HF token from environment (set by huggingface-cli login or HF_TOKEN env var)
29
+ def _get_hf_token() -> str | None:
30
+ """Get HF token from environment or huggingface_hub cache."""
31
+ token = os.environ.get("HF_TOKEN")
32
+ if token:
33
+ return token
34
+
35
+ # Fallback: try huggingface_hub.get_token() if available
36
+ try:
37
+ from huggingface_hub import get_token
38
+ token = get_token()
39
+ if token:
40
+ logger.info("✓ Using HF token from huggingface_hub cache")
41
+ return token
42
+ except ImportError:
43
+ pass
44
+
45
  logger.warning(
46
+ "⚠️ HF_TOKEN not found in environment. Run 'huggingface-cli login' or set HF_TOKEN env var. "
47
  "CodeAgent initialization will fail without valid credentials."
48
  )
49
+ return None
50
+
51
+ HF_TOKEN = _get_hf_token()
52
 
53
 
54
  def make_code_agent(
 
93
  if model_name:
94
  try:
95
  model = InferenceClientModel(model_id=model_name, token=HF_TOKEN)
96
+ logger.info(f"InferenceClientModel initialized with custom model: {model_name}")
97
+ except Exception as e:
98
+ logger.warning(f"Failed to initialize custom model {model_name}: {e}. Falling back to OLMo-Instruct")
99
  try:
100
  model = InferenceClientModel(model_id='allenai/Olmo-3-7B-Instruct', token=HF_TOKEN)
101
+ logger.info("InferenceClientModel initialized with fallback model: allenai/Olmo-3-7B-Instruct")
102
+ except Exception as e2:
103
+ logger.warning(f"Failed to initialize OLMo-Instruct: {e2}. Falling back to default model")
104
  model = InferenceClientModel(token=HF_TOKEN)
105
+ logger.info("InferenceClientModel initialized with default model")
106
  else:
107
+ # Default to OLMo-Think model
108
+ try:
109
+ model = InferenceClientModel(model_id='allenai/Olmo-3-7B-Think', token=HF_TOKEN)
110
+ logger.info("InferenceClientModel initialized with default model: allenai/Olmo-3-7B-Think")
111
+ except Exception as e:
112
+ logger.warning(f"Failed to initialize OLMo-Think: {e}. Falling back to OLMo-Instruct")
113
+ try:
114
+ model = InferenceClientModel(model_id='allenai/Olmo-3-7B-Instruct', token=HF_TOKEN)
115
+ logger.info("InferenceClientModel initialized with fallback model: allenai/Olmo-3-7B-Instruct")
116
+ except Exception as e2:
117
+ logger.warning(f"Failed to initialize OLMo-Instruct: {e2}. Using default model")
118
+ model = InferenceClientModel(token=HF_TOKEN)
119
+ logger.info("InferenceClientModel initialized with default model")
120
  except Exception as e:
121
  logger.error(
122
  "InferenceClientModel initialization failed (ensure HF_TOKEN is set and has credits): %s", e
 
127
  return agent
128
 
129
 
130
+ def _extract_clean_answer(text: str) -> str:
131
+ """Clean agent output by removing markdown, code blocks, and formatting.
132
+
133
+ Handles:
134
+ - Code fences (```python ... ```)
135
+ - Code keywords (import, def, class, from)
136
+ - Markdown formatting (*#-`)
137
+ - Excess whitespace
138
+
139
+ Args:
140
+ text: raw agent output (may contain code, markdown)
141
+
142
+ Returns:
143
+ Clean plain-text answer string.
144
+ """
145
+ lines = text.strip().split('\n')
146
+ cleaned_lines = []
147
+ in_code_block = False
148
+
149
+ for line in lines:
150
+ stripped = line.strip()
151
+
152
+ # Track code blocks
153
+ if '```' in stripped:
154
+ in_code_block = not in_code_block
155
+ continue
156
+
157
+ # Skip code blocks
158
+ if in_code_block:
159
+ continue
160
+
161
+ # Skip lines that look like code
162
+ if stripped.startswith(('import ', 'from ', 'def ', 'class ', '>>>')):
163
+ continue
164
+
165
+ # Skip empty lines at boundaries
166
+ if stripped:
167
+ cleaned_lines.append(stripped)
168
+
169
+ # Join and clean markdown formatting
170
+ answer = ' '.join(cleaned_lines)
171
+
172
+ # Remove markdown bold/italic/strikethrough
173
+ for char in ('*', '**', '_', '~~'):
174
+ answer = answer.replace(char, '')
175
+
176
+ return answer.strip()
177
+
178
+
179
  _AGENT_SINGLETON = None
180
 
181
 
 
183
  """Get or create the singleton CodeAgent instance."""
184
  global _AGENT_SINGLETON
185
  if _AGENT_SINGLETON is None:
186
+ logger.info("⏳ Initializing CodeAgent singleton with model: allenai/Olmo-3-7B-Think")
187
+ try:
188
+ _AGENT_SINGLETON = make_code_agent(model_name="allenai/Olmo-3-7B-Think")
189
+ logger.info("✓ CodeAgent singleton initialized successfully")
190
+ except Exception as e:
191
+ logger.error("❌ Failed to initialize CodeAgent: %s", e)
192
+ raise
193
  return _AGENT_SINGLETON
194
 
195
 
196
  def run_agent(prompt: str) -> str:
197
+ """Run the CodeAgent and return the final answer string.
198
+
 
199
  Args:
200
  prompt: the reasoning task/question for the agent.
201
 
202
  Returns:
203
+ The agent's final answer as a string (plain-text, no code/markdown).
204
+
205
  Raises:
206
  Exception: if CodeAgent.run fails (e.g., no HF credentials or credits).
207
  """
208
  agent = get_agent()
209
 
210
+ # System-level instruction to force plain-text output only
 
 
211
  system_instruction = (
212
+ "IMPORTANT: Your final answer MUST be plain text only. "
213
+ "Do NOT produce code blocks (```), do NOT return Python code, and do NOT include explanations. "
214
+ "Return ONLY the direct answer as a single line or paragraph of plain text."
215
  )
216
 
217
  full_prompt = f"{system_instruction}\n\n{prompt}"
218
 
219
  try:
220
  res = agent.run(full_prompt)
221
+ logger.debug("Agent raw response type: %s", type(res).__name__)
222
  except Exception as e:
223
+ # Surface the error (likely HF auth/credits)
224
+ logger.error("CodeAgent.run failed: %s", e)
225
  raise
226
 
227
+ # Handle dict-like responses
228
  if isinstance(res, dict):
229
+ for key in ("answer", "final_answer", "final", "output"):
230
  if key in res and isinstance(res[key], str):
231
+ answer = _extract_clean_answer(res[key])
232
+ logger.debug("Extracted answer from key '%s': %s", key, answer[:50])
233
+ return answer
234
+ # Fallback: stringify
235
+ return _extract_clean_answer(str(res))
236
 
237
+ # Handle string responses
238
  if isinstance(res, str):
239
  text = res.strip()
240
+
241
+ # Check for code-like output
242
+ has_code_fence = "```" in text
243
+ has_code_keyword = any(text.lstrip().startswith(kw) for kw in ["import ", "def ", "class ", ">>>"])
244
+ looks_like_code = has_code_fence or has_code_keyword
245
+
246
+ if looks_like_code:
247
+ logger.warning("⚠️ Agent returned code-like output; retrying with stricter instruction")
248
  stricter = (
249
+ "FINAL INSTRUCTION: Return ONLY the plain-text answer. No code blocks, no code, no explanations. "
250
+ "If the answer is a number, return only the number. If it's a date, return only the date. "
251
+ "Do not explain your answer."
252
  )
253
  retry_prompt = f"{stricter}\n\n{prompt}"
254
  try:
255
  retry_res = agent.run(retry_prompt)
256
  if isinstance(retry_res, str):
257
+ answer = _extract_clean_answer(retry_res)
258
+ logger.info("✓ Retry successful, extracted clean answer")
259
+ return answer
260
  if isinstance(retry_res, dict):
261
+ for key in ("answer", "final_answer", "final", "output"):
262
  if key in retry_res and isinstance(retry_res[key], str):
263
+ answer = _extract_clean_answer(retry_res[key])
264
+ return answer
265
+ return _extract_clean_answer(str(retry_res))
266
  except Exception as e:
267
+ logger.error("Retry failed: %s", e)
268
+ # Fallthrough to clean original response
269
+
270
+ # Clean and return
271
+ answer = _extract_clean_answer(text)
272
+ logger.debug("✓ Final answer (cleaned): %s", answer[:80])
273
+ return answer
274
 
275
+ # Fallback for other types
276
+ return _extract_clean_answer(str(res))
277
 
278
 
279
  if __name__ == "__main__":