psykoss commited on
Commit
1b025d5
·
1 Parent(s): 691a9fd

Switch LLM to Claude 3.5 Haiku via Anthropic

Browse files
Files changed (5) hide show
  1. .env.example +2 -2
  2. README.md +1 -1
  3. agent.py +57 -14
  4. app.py +6 -6
  5. requirements.txt +1 -3
.env.example CHANGED
@@ -1,4 +1,4 @@
1
  # Copy to .env locally and set Space secrets on Hugging Face (never commit .env)
2
- GOOGLE_API_KEY=your_google_api_key_here
3
- GEMINI_MODEL=gemini-2.0-flash
4
  QUESTION_DELAY_SECONDS=2
 
1
  # Copy to .env locally and set Space secrets on Hugging Face (never commit .env)
2
+ ANTHROPIC_API_KEY=sk-ant-your_key_here
3
+ ANTHROPIC_MODEL=claude-3-5-haiku-20241022
4
  QUESTION_DELAY_SECONDS=2
README.md CHANGED
@@ -15,4 +15,4 @@ hf_oauth_expiration_minutes: 480
15
 
16
  LangGraph agent with web search for the Hugging Face agents course final assignment.
17
 
18
- **Setup:** Add `GOOGLE_API_KEY` in Space secrets. Use **Test agent** to try one question, then **Submit evaluation** after logging in with Hugging Face.
 
15
 
16
  LangGraph agent with web search for the Hugging Face agents course final assignment.
17
 
18
+ **Setup:** Add `ANTHROPIC_API_KEY` in Space secrets (default model: `claude-3-5-haiku-20241022`). Log in with Hugging Face, then run evaluation submit.
agent.py CHANGED
@@ -7,14 +7,34 @@ import re
7
  import json
8
  import time
9
  from dotenv import load_dotenv
10
- from langchain_google_genai import ChatGoogleGenerativeAI
11
- from langchain_google_genai.chat_models import ChatGoogleGenerativeAIError
12
  from typing_extensions import TypedDict
13
  from prompts import PLANNER_PROMPT, SYNTHESIS_ANSWER_PROMPT, ANSWER_PROMPT
14
  from langgraph.graph import StateGraph, START, END
15
  from tools import search_web
16
 
17
- load_dotenv()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  MAX_SEARCH_QUERIES = 3
20
  MIN_SEARCH_QUERIES = 1
@@ -24,9 +44,9 @@ QUESTION_DELAY_SECONDS = float(os.getenv("QUESTION_DELAY_SECONDS", "2"))
24
 
25
  #--- Model --------------------------------------------------------------#
26
 
27
- model = ChatGoogleGenerativeAI(
28
- model=os.getenv("GEMINI_MODEL", "gemini-2.0-flash"),
29
- api_key=os.getenv("GOOGLE_API_KEY"),
30
  temperature=0,
31
  )
32
 
@@ -64,17 +84,40 @@ def _retry_delay_seconds(error: Exception, attempt: int) -> float:
64
 
65
  def _is_rate_limit_error(error: Exception) -> bool:
66
  msg = str(error).lower()
67
- return "429" in msg or "resource_exhausted" in msg or "quota" in msg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
 
70
  def invoke_llm(prompt: str) -> str:
71
- """Call Gemini with retries on rate-limit / quota errors."""
72
  last_error: Exception | None = None
73
  for attempt in range(MAX_LLM_RETRIES):
74
  try:
75
  response = model.invoke(prompt)
76
- return response.content.strip()
77
- except ChatGoogleGenerativeAIError as e:
78
  last_error = e
79
  if _is_rate_limit_error(e) and attempt < MAX_LLM_RETRIES - 1:
80
  delay = _retry_delay_seconds(e, attempt)
@@ -217,7 +260,7 @@ def plan_node(state: AgentState) -> AgentState:
217
  try:
218
  content = invoke_llm(prompt)
219
  data = _parse_planner_response(content, question)
220
- except (json.JSONDecodeError, TypeError, KeyError, ChatGoogleGenerativeAIError):
221
  data = {
222
  "steps": [
223
  "Identify entities and constraints in the question",
@@ -255,12 +298,12 @@ def finalize_node(state: AgentState) -> AgentState:
255
  )
256
  parsed = _parse_synthesis_answer(invoke_llm(prompt))
257
  return parsed
258
- except (json.JSONDecodeError, TypeError, KeyError, ValueError, ChatGoogleGenerativeAIError) as e:
259
  print(f"Finalize LLM failed ({e}); using heuristic synthesis.")
260
  notes = _heuristic_synthesis(search_results)
261
  try:
262
  answer = _answer_from_notes(question, plan, notes)
263
- except ChatGoogleGenerativeAIError:
264
  first_line = notes.split("\n", 1)[0].lstrip("- ").strip()
265
  answer = first_line[:200] if first_line else "Unknown"
266
  return {"synthesis_notes": notes, "answer": answer}
@@ -286,7 +329,7 @@ def run_agent(question: str) -> AgentState:
286
 
287
 
288
  if __name__ == "__main__":
289
- # Free tier: ~20 requests/day per model — use 1 question while testing.
290
  questions = [
291
  "What is the capital of France?",
292
  ]
 
7
  import json
8
  import time
9
  from dotenv import load_dotenv
10
+ from langchain_anthropic import ChatAnthropic
 
11
  from typing_extensions import TypedDict
12
  from prompts import PLANNER_PROMPT, SYNTHESIS_ANSWER_PROMPT, ANSWER_PROMPT
13
  from langgraph.graph import StateGraph, START, END
14
  from tools import search_web
15
 
16
+ load_dotenv(override=True)
17
+
18
+
19
+ def _resolve_anthropic_api_key() -> str:
20
+ """Use ANTHROPIC_API_KEY from .env (override=True), not IDE session tokens."""
21
+ key = (os.getenv("ANTHROPIC_API_KEY") or "").strip().strip('"').strip("'")
22
+ if not key:
23
+ raise ValueError(
24
+ "ANTHROPIC_API_KEY is not set. Add sk-ant-... to .env (see .env.example) "
25
+ "or Space secrets. Create keys at https://console.anthropic.com/settings/keys"
26
+ )
27
+ if key.startswith("sess-") or key.startswith("hf_"):
28
+ raise ValueError(
29
+ "ANTHROPIC_API_KEY must be an Anthropic key (sk-ant-...), not a Cursor/HF token."
30
+ )
31
+ if not key.startswith("sk-ant-"):
32
+ raise ValueError(
33
+ "ANTHROPIC_API_KEY should start with sk-ant-. "
34
+ "Create one at https://console.anthropic.com/settings/keys"
35
+ )
36
+ return key
37
+
38
 
39
  MAX_SEARCH_QUERIES = 3
40
  MIN_SEARCH_QUERIES = 1
 
44
 
45
  #--- Model --------------------------------------------------------------#
46
 
47
+ model = ChatAnthropic(
48
+ model=os.getenv("ANTHROPIC_MODEL", "claude-3-5-haiku-20241022"),
49
+ api_key=_resolve_anthropic_api_key(),
50
  temperature=0,
51
  )
52
 
 
84
 
85
  def _is_rate_limit_error(error: Exception) -> bool:
86
  msg = str(error).lower()
87
+ return (
88
+ "429" in msg
89
+ or "rate_limit" in msg
90
+ or "rate limit" in msg
91
+ or "too many requests" in msg
92
+ or "resource_exhausted" in msg
93
+ or "quota" in msg
94
+ or "overloaded" in msg
95
+ )
96
+
97
+
98
+ def _message_content(response) -> str:
99
+ content = response.content
100
+ if isinstance(content, str):
101
+ return content.strip()
102
+ if isinstance(content, list):
103
+ parts = []
104
+ for block in content:
105
+ if isinstance(block, str):
106
+ parts.append(block)
107
+ elif isinstance(block, dict) and block.get("text"):
108
+ parts.append(str(block["text"]))
109
+ return "\n".join(parts).strip()
110
+ return str(content).strip()
111
 
112
 
113
  def invoke_llm(prompt: str) -> str:
114
+ """Call Claude with retries on rate-limit errors."""
115
  last_error: Exception | None = None
116
  for attempt in range(MAX_LLM_RETRIES):
117
  try:
118
  response = model.invoke(prompt)
119
+ return _message_content(response)
120
+ except Exception as e:
121
  last_error = e
122
  if _is_rate_limit_error(e) and attempt < MAX_LLM_RETRIES - 1:
123
  delay = _retry_delay_seconds(e, attempt)
 
260
  try:
261
  content = invoke_llm(prompt)
262
  data = _parse_planner_response(content, question)
263
+ except Exception:
264
  data = {
265
  "steps": [
266
  "Identify entities and constraints in the question",
 
298
  )
299
  parsed = _parse_synthesis_answer(invoke_llm(prompt))
300
  return parsed
301
+ except Exception as e:
302
  print(f"Finalize LLM failed ({e}); using heuristic synthesis.")
303
  notes = _heuristic_synthesis(search_results)
304
  try:
305
  answer = _answer_from_notes(question, plan, notes)
306
+ except Exception:
307
  first_line = notes.split("\n", 1)[0].lstrip("- ").strip()
308
  answer = first_line[:200] if first_line else "Unknown"
309
  return {"synthesis_notes": notes, "answer": answer}
 
329
 
330
 
331
  if __name__ == "__main__":
332
+ # Use 1 question while testing to save API usage.
333
  questions = [
334
  "What is the capital of France?",
335
  ]
app.py CHANGED
@@ -2,7 +2,7 @@ import os
2
 
3
  from dotenv import load_dotenv
4
 
5
- load_dotenv()
6
 
7
  import gradio as gr
8
  import requests
@@ -185,12 +185,12 @@ if __name__ == "__main__":
185
  # Check for SPACE_HOST and SPACE_ID at startup for information
186
  space_host_startup = os.getenv("SPACE_HOST")
187
  space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
188
- GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
189
-
190
- if GOOGLE_API_KEY:
191
- print(f"✅ GOOGLE_API_KEY found: {GOOGLE_API_KEY}")
192
  else:
193
- print("ℹ️ GOOGLE_API_KEY environment variable not found (running locally?).")
194
 
195
  if space_host_startup:
196
  print(f"✅ SPACE_HOST found: {space_host_startup}")
 
2
 
3
  from dotenv import load_dotenv
4
 
5
+ load_dotenv(override=True)
6
 
7
  import gradio as gr
8
  import requests
 
185
  # Check for SPACE_HOST and SPACE_ID at startup for information
186
  space_host_startup = os.getenv("SPACE_HOST")
187
  space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
188
+ anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
189
+
190
+ if anthropic_api_key:
191
+ print("✅ ANTHROPIC_API_KEY found (set).")
192
  else:
193
+ print("ℹ️ ANTHROPIC_API_KEY environment variable not found (running locally?).")
194
 
195
  if space_host_startup:
196
  print(f"✅ SPACE_HOST found: {space_host_startup}")
requirements.txt CHANGED
@@ -3,9 +3,7 @@ python-dotenv
3
  requests
4
  langgraph
5
  langchain
6
- langchain-google-genai
7
  ddgs
8
- google-generativeai
9
- google-auth
10
  gradio[oauth]
11
  ipython
 
3
  requests
4
  langgraph
5
  langchain
6
+ langchain-anthropic
7
  ddgs
 
 
8
  gradio[oauth]
9
  ipython