Nigou Julien commited on
Commit
b4bc906
·
1 Parent(s): ec4907d

Set up LangGraph GAIA agent skeleton

Browse files
.env.example ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copy this file to .env for local development.
2
+ # Do not commit real secrets.
3
+
4
+ # LLM provider
5
+ OPENAI_API_KEY=
6
+ OPENAI_MODEL=gpt-4o-mini
7
+
8
+ # Langfuse tracing
9
+ LANGFUSE_PUBLIC_KEY=
10
+ LANGFUSE_SECRET_KEY=
11
+ LANGFUSE_HOST=https://cloud.langfuse.com
12
+
13
+ # Optional assignment/runtime settings
14
+ SPACE_ID=
15
+ GAIA_API_URL=https://agents-course-unit4-scoring.hf.space
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ .env
2
+ .venv/
3
+ __pycache__/
4
+ *.py[cod]
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ *.egg-info/
app.py CHANGED
@@ -1,27 +1,17 @@
1
  import os
2
  import gradio as gr
3
  import requests
4
- import inspect
5
  import pandas as pd
6
 
7
- # (Keep Constants as is)
 
 
8
  # --- Constants ---
9
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
-
11
- # --- Basic Agent Definition ---
12
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
- class BasicAgent:
14
- def __init__(self):
15
- print("BasicAgent initialized.")
16
- def __call__(self, question: str) -> str:
17
- print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "Julien test"
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
21
 
22
  def run_and_submit_all( profile: gr.OAuthProfile | None):
23
  """
24
- Fetches all questions, runs the BasicAgent on them, submits all answers,
25
  and displays the results.
26
  """
27
  # --- Determine HF Space Runtime URL and Repo URL ---
@@ -40,7 +30,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
40
 
41
  # 1. Instantiate Agent ( modify this part to create your agent)
42
  try:
43
- agent = BasicAgent()
44
  except Exception as e:
45
  print(f"Error instantiating agent: {e}")
46
  return f"Error initializing agent: {e}", None
 
1
  import os
2
  import gradio as gr
3
  import requests
 
4
  import pandas as pd
5
 
6
+ from gaia_agent import GaiaAgent
7
+ from gaia_agent.config import settings
8
+
9
  # --- Constants ---
10
+ DEFAULT_API_URL = settings.gaia_api_url
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  def run_and_submit_all( profile: gr.OAuthProfile | None):
13
  """
14
+ Fetches all questions, runs the GaiaAgent on them, submits all answers,
15
  and displays the results.
16
  """
17
  # --- Determine HF Space Runtime URL and Repo URL ---
 
30
 
31
  # 1. Instantiate Agent ( modify this part to create your agent)
32
  try:
33
+ agent = GaiaAgent()
34
  except Exception as e:
35
  print(f"Error instantiating agent: {e}")
36
  return f"Error initializing agent: {e}", None
gaia_agent/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from gaia_agent.agent import GaiaAgent
2
+
3
+ __all__ = ["GaiaAgent"]
gaia_agent/agent.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gaia_agent.graph import build_graph
2
+ from gaia_agent.observability import trace_agent_run
3
+
4
+
5
+ class GaiaAgent:
6
+ def __init__(self):
7
+ print("GaiaAgent initialized.")
8
+
9
+ def __call__(self, question: str) -> str:
10
+ print(f"Agent received question (first 80 chars): {question[:80]}...")
11
+ with trace_agent_run(question) as trace:
12
+ graph = build_graph(trace=trace)
13
+ result = graph.invoke({"question": question})
14
+ final_answer = result["final_answer"]
15
+ print(f"Agent returning answer: {final_answer}")
16
+ return final_answer
gaia_agent/answer.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ def normalize_answer(answer: str) -> str:
2
+ """Apply minimal GAIA answer cleanup without changing meaning."""
3
+ return answer.strip()
gaia_agent/config.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ import os
3
+
4
+ from dotenv import load_dotenv
5
+
6
+
7
+ load_dotenv()
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class Settings:
12
+ openai_api_key: str | None = os.getenv("OPENAI_API_KEY")
13
+ openai_model: str = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
14
+ langfuse_public_key: str | None = os.getenv("LANGFUSE_PUBLIC_KEY")
15
+ langfuse_secret_key: str | None = os.getenv("LANGFUSE_SECRET_KEY")
16
+ langfuse_host: str = os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com")
17
+ gaia_api_url: str = os.getenv(
18
+ "GAIA_API_URL",
19
+ "https://agents-course-unit4-scoring.hf.space",
20
+ )
21
+
22
+
23
+ settings = Settings()
gaia_agent/graph.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import END, StateGraph
2
+
3
+ from gaia_agent.answer import normalize_answer
4
+ from gaia_agent.observability import traced_step
5
+ from gaia_agent.state import GaiaState
6
+
7
+
8
+ PLACEHOLDER_ANSWER = "Julien test"
9
+
10
+
11
+ def build_graph(trace=None):
12
+ graph = StateGraph(GaiaState)
13
+
14
+ def draft_answer(state: GaiaState) -> GaiaState:
15
+ def run() -> dict[str, str]:
16
+ return {"draft_answer": PLACEHOLDER_ANSWER}
17
+
18
+ return traced_step(trace, "draft_answer", run)
19
+
20
+ def normalize_final_answer(state: GaiaState) -> GaiaState:
21
+ def run() -> dict[str, str]:
22
+ return {"final_answer": normalize_answer(state["draft_answer"])}
23
+
24
+ return traced_step(trace, "normalize_final_answer", run)
25
+
26
+ graph.add_node("draft_answer", draft_answer)
27
+ graph.add_node("normalize_final_answer", normalize_final_answer)
28
+ graph.set_entry_point("draft_answer")
29
+ graph.add_edge("draft_answer", "normalize_final_answer")
30
+ graph.add_edge("normalize_final_answer", END)
31
+
32
+ return graph.compile()
gaia_agent/observability.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Callable
2
+ from contextlib import contextmanager
3
+ from typing import Any
4
+
5
+ from gaia_agent.config import settings
6
+
7
+
8
+ def langfuse_enabled() -> bool:
9
+ return bool(settings.langfuse_public_key and settings.langfuse_secret_key)
10
+
11
+
12
+ @contextmanager
13
+ def trace_agent_run(question: str):
14
+ """Create a Langfuse trace when credentials are configured.
15
+
16
+ This keeps local development and HF Space startup working before secrets are set.
17
+ """
18
+ if not langfuse_enabled():
19
+ yield None
20
+ return
21
+
22
+ from langfuse import Langfuse
23
+
24
+ client = Langfuse(
25
+ public_key=settings.langfuse_public_key,
26
+ secret_key=settings.langfuse_secret_key,
27
+ host=settings.langfuse_host,
28
+ )
29
+ with client.start_as_current_observation(
30
+ name="gaia-agent-run",
31
+ as_type="agent",
32
+ input={"question": question},
33
+ metadata={"component": "GaiaAgent"},
34
+ ) as observation:
35
+ try:
36
+ yield observation
37
+ finally:
38
+ client.flush()
39
+
40
+
41
+ def traced_step(trace: Any, name: str, fn: Callable[[], dict[str, Any]]) -> dict[str, Any]:
42
+ if trace is None:
43
+ return fn()
44
+
45
+ span = trace.start_observation(name=name, as_type="span")
46
+ try:
47
+ output = fn()
48
+ span.end(output=output)
49
+ return output
50
+ except Exception as exc:
51
+ span.end(level="ERROR", status_message=str(exc))
52
+ raise
gaia_agent/prompts.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ GAIA_FINAL_ANSWER_INSTRUCTIONS = """
2
+ Return only the final answer.
3
+ The answer should be a number, as few words as possible, or a comma-separated
4
+ list of numbers and/or strings.
5
+ """.strip()
gaia_agent/state.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from typing import TypedDict
2
+
3
+
4
+ class GaiaState(TypedDict, total=False):
5
+ question: str
6
+ draft_answer: str
7
+ final_answer: str
gaia_agent/tools/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Tool modules will be added incrementally as the GAIA agent grows."""
gaia_agent/tools/files.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """File-reading tools will live here."""
gaia_agent/tools/python_repl.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Python execution tools will live here."""
gaia_agent/tools/search.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Search tools will live here."""
gaia_agent/tools/web.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Web browsing tools will live here."""
pyproject.toml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "gaia-final-assignment-agent"
3
+ version = "0.1.0"
4
+ description = "LangGraph agent for the Hugging Face GAIA final assignment."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "gradio[oauth]==5.25.2",
9
+ "requests>=2.32.0",
10
+ "pandas>=2.2.0",
11
+ "python-dotenv>=1.0.1",
12
+ "langchain>=0.3.0",
13
+ "langchain-openai>=0.3.0",
14
+ "langgraph>=0.2.60",
15
+ "langfuse>=2.57.0",
16
+ ]
17
+
18
+ [build-system]
19
+ requires = ["setuptools>=80.0"]
20
+ build-backend = "setuptools.build_meta"
21
+
22
+ [tool.setuptools.packages.find]
23
+ include = ["gaia_agent*"]
24
+
25
+ [dependency-groups]
26
+ dev = [
27
+ "pytest>=8.3.0",
28
+ ]
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
requirements.txt CHANGED
@@ -1,2 +1,8 @@
1
- gradio
2
- requests
 
 
 
 
 
 
 
1
+ gradio[oauth]==5.25.2
2
+ requests>=2.32.0
3
+ pandas>=2.2.0
4
+ python-dotenv>=1.0.1
5
+ langchain>=0.3.0
6
+ langchain-openai>=0.3.0
7
+ langgraph>=0.2.60
8
+ langfuse>=2.57.0
scripts/run_one.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gaia_agent import GaiaAgent
2
+
3
+
4
+ def main() -> None:
5
+ question = "What is the capital of France?"
6
+ answer = GaiaAgent()(question)
7
+ print(answer)
8
+
9
+
10
+ if __name__ == "__main__":
11
+ main()
scripts/run_smoke.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gaia_agent import GaiaAgent
2
+
3
+
4
+ QUESTIONS = [
5
+ "What is the capital of France?",
6
+ "Return the word test.",
7
+ ]
8
+
9
+
10
+ def main() -> None:
11
+ agent = GaiaAgent()
12
+ for question in QUESTIONS:
13
+ print({"question": question, "answer": agent(question)})
14
+
15
+
16
+ if __name__ == "__main__":
17
+ main()
tests/test_answer.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from gaia_agent.answer import normalize_answer
2
+
3
+
4
+ def test_normalize_answer_strips_whitespace():
5
+ assert normalize_answer(" Paris \n") == "Paris"
tests/test_graph_smoke.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from gaia_agent.agent import GaiaAgent
2
+
3
+
4
+ def test_agent_returns_placeholder_answer():
5
+ agent = GaiaAgent()
6
+
7
+ assert agent("What is the answer?") == "Julien test"
uv.lock ADDED
The diff for this file is too large to render. See raw diff