Commit
·
c9ab476
1
Parent(s):
f1cb55d
+ files fetching for the agent
Browse files- __pycache__/langraph_agent.cpython-313.pyc +0 -0
- langraph_agent.py +60 -10
- quick_specific_agent_test.py +91 -0
__pycache__/langraph_agent.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/langraph_agent.cpython-313.pyc and b/__pycache__/langraph_agent.cpython-313.pyc differ
|
|
|
langraph_agent.py
CHANGED
|
@@ -15,6 +15,7 @@ from langchain_core.messages import SystemMessage, HumanMessage
|
|
| 15 |
from langchain_core.tools import tool
|
| 16 |
from langchain.tools.retriever import create_retriever_tool
|
| 17 |
from supabase.client import Client, create_client
|
|
|
|
| 18 |
|
| 19 |
from langfuse.langchain import CallbackHandler
|
| 20 |
|
|
@@ -32,6 +33,9 @@ load_dotenv("env.local") # Try env.local as backup
|
|
| 32 |
print(f"SUPABASE_URL loaded: {bool(os.environ.get('SUPABASE_URL'))}")
|
| 33 |
print(f"GROQ_API_KEY loaded: {bool(os.environ.get('GROQ_API_KEY'))}")
|
| 34 |
|
|
|
|
|
|
|
|
|
|
| 35 |
@tool
|
| 36 |
def multiply(a: int, b: int) -> int:
|
| 37 |
"""Multiply two numbers.
|
|
@@ -240,21 +244,67 @@ def build_graph(provider: str = "groq"):
|
|
| 240 |
if not state["messages"]:
|
| 241 |
print("Retriever node: No messages in state")
|
| 242 |
return {"messages": [sys_msg]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
if not vector_store:
|
|
|
|
|
|
|
|
|
|
| 244 |
print("Retriever node: Vector store not available, skipping retrieval")
|
| 245 |
-
return {"messages":
|
| 246 |
-
|
| 247 |
-
|
|
|
|
| 248 |
similar_question = vector_store.similarity_search(query_content)
|
| 249 |
print(f"Retriever node: Found {len(similar_question)} similar questions")
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
print("Retriever node: No similar questions found, proceeding without example")
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
|
|
|
| 258 |
except Exception as e:
|
| 259 |
print(f"Error in retriever node: {e}")
|
| 260 |
return {"messages": [sys_msg] + state["messages"]}
|
|
|
|
| 15 |
from langchain_core.tools import tool
|
| 16 |
from langchain.tools.retriever import create_retriever_tool
|
| 17 |
from supabase.client import Client, create_client
|
| 18 |
+
import requests # NEW: for HTTP requests to scoring API
|
| 19 |
|
| 20 |
from langfuse.langchain import CallbackHandler
|
| 21 |
|
|
|
|
| 33 |
print(f"SUPABASE_URL loaded: {bool(os.environ.get('SUPABASE_URL'))}")
|
| 34 |
print(f"GROQ_API_KEY loaded: {bool(os.environ.get('GROQ_API_KEY'))}")
|
| 35 |
|
| 36 |
+
# Base URL of the scoring API (duplicated here to avoid circular import with basic_agent)
|
| 37 |
+
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 38 |
+
|
| 39 |
@tool
|
| 40 |
def multiply(a: int, b: int) -> int:
|
| 41 |
"""Multiply two numbers.
|
|
|
|
| 244 |
if not state["messages"]:
|
| 245 |
print("Retriever node: No messages in state")
|
| 246 |
return {"messages": [sys_msg]}
|
| 247 |
+
|
| 248 |
+
# Extract the user query content early for downstream steps
|
| 249 |
+
query_content = state["messages"][0].content
|
| 250 |
+
|
| 251 |
+
# ------------------- NEW: fetch attachment if available -------------------
|
| 252 |
+
attachment_msg = None
|
| 253 |
+
try:
|
| 254 |
+
resp = requests.get(f"{DEFAULT_API_URL}/questions", timeout=30)
|
| 255 |
+
resp.raise_for_status()
|
| 256 |
+
questions = resp.json()
|
| 257 |
+
matched_task_id = None
|
| 258 |
+
for q in questions:
|
| 259 |
+
if str(q.get("question")).strip() == str(query_content).strip():
|
| 260 |
+
matched_task_id = str(q.get("task_id"))
|
| 261 |
+
break
|
| 262 |
+
if matched_task_id:
|
| 263 |
+
print(f"Retriever node: Found task_id {matched_task_id} for current question, attempting to download attachment…")
|
| 264 |
+
file_resp = requests.get(f"{DEFAULT_API_URL}/files/{matched_task_id}", timeout=60)
|
| 265 |
+
if file_resp.status_code == 200 and file_resp.content:
|
| 266 |
+
try:
|
| 267 |
+
file_text = file_resp.content.decode("utf-8", errors="replace")
|
| 268 |
+
except Exception:
|
| 269 |
+
file_text = "(binary or non-UTF8 file omitted)"
|
| 270 |
+
MAX_CHARS = 8000
|
| 271 |
+
if len(file_text) > MAX_CHARS:
|
| 272 |
+
print(f"Retriever node: Attachment length {len(file_text)} > {MAX_CHARS}, truncating…")
|
| 273 |
+
file_text = file_text[:MAX_CHARS] + "\n… (truncated)"
|
| 274 |
+
attachment_msg = HumanMessage(content=f"Attached file content for task {matched_task_id}:\n```python\n{file_text}\n```")
|
| 275 |
+
print("Retriever node: Prepared attachment message")
|
| 276 |
+
else:
|
| 277 |
+
print(f"Retriever node: No attachment found for task {matched_task_id} (status {file_resp.status_code})")
|
| 278 |
+
except Exception as api_e:
|
| 279 |
+
print(f"Retriever node: Error while fetching attachment – {api_e}")
|
| 280 |
+
# -------------------------------------------------------------------------
|
| 281 |
+
|
| 282 |
+
# If vector store unavailable, simply return sys_msg + user message (+ attachment if any)
|
| 283 |
if not vector_store:
|
| 284 |
+
msgs = [sys_msg] + state["messages"]
|
| 285 |
+
if attachment_msg:
|
| 286 |
+
msgs.append(attachment_msg)
|
| 287 |
print("Retriever node: Vector store not available, skipping retrieval")
|
| 288 |
+
return {"messages": msgs}
|
| 289 |
+
|
| 290 |
+
# Perform similarity search when vector store is available
|
| 291 |
+
print(f"Retriever node: Searching for similar questions with query: {query_content[:100]}…")
|
| 292 |
similar_question = vector_store.similarity_search(query_content)
|
| 293 |
print(f"Retriever node: Found {len(similar_question)} similar questions")
|
| 294 |
+
msgs = [sys_msg] + state["messages"]
|
| 295 |
+
if similar_question:
|
| 296 |
+
example_msg = HumanMessage(content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}")
|
| 297 |
+
msgs.append(example_msg)
|
| 298 |
+
print("Retriever node: Added example message from similar question")
|
| 299 |
+
else:
|
| 300 |
print("Retriever node: No similar questions found, proceeding without example")
|
| 301 |
+
|
| 302 |
+
# Attach the file content if we have it
|
| 303 |
+
if attachment_msg:
|
| 304 |
+
msgs.append(attachment_msg)
|
| 305 |
+
print("Retriever node: Added attachment content to messages")
|
| 306 |
+
|
| 307 |
+
return {"messages": msgs}
|
| 308 |
except Exception as e:
|
| 309 |
print(f"Error in retriever node: {e}")
|
| 310 |
return {"messages": [sys_msg] + state["messages"]}
|
quick_specific_agent_test.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import tempfile
|
| 4 |
+
import requests
|
| 5 |
+
from basic_agent import BasicAgent, DEFAULT_API_URL
|
| 6 |
+
from langchain_core.messages import HumanMessage
|
| 7 |
+
from langfuse.langchain import CallbackHandler
|
| 8 |
+
|
| 9 |
+
# Initialize Langfuse CallbackHandler for LangGraph/Langchain (tracing)
|
| 10 |
+
try:
|
| 11 |
+
langfuse_handler = CallbackHandler()
|
| 12 |
+
except Exception as e:
|
| 13 |
+
print(f"Warning: Could not initialize Langfuse handler: {e}")
|
| 14 |
+
langfuse_handler = None
|
| 15 |
+
|
| 16 |
+
# Default Task ID (replace with your desired one or pass via CLI)
|
| 17 |
+
DEFAULT_TASK_ID = "f918266a-b3e0-4914-865d-4faa564f1aef"
|
| 18 |
+
|
| 19 |
+
def fetch_question_by_id(task_id: str, api_base: str = DEFAULT_API_URL):
|
| 20 |
+
"""Return JSON of a question for a given task_id.
|
| 21 |
+
|
| 22 |
+
The scoring API does not (yet) expose an explicit /question/{id} endpoint,
|
| 23 |
+
so we fetch the full /questions list and filter locally. This works fine
|
| 24 |
+
because the list is small (<100 items).
|
| 25 |
+
"""
|
| 26 |
+
try:
|
| 27 |
+
resp = requests.get(f"{api_base}/questions", timeout=30)
|
| 28 |
+
resp.raise_for_status()
|
| 29 |
+
questions = resp.json()
|
| 30 |
+
except Exception as e:
|
| 31 |
+
raise RuntimeError(f"Failed to fetch questions list: {e}") from e
|
| 32 |
+
|
| 33 |
+
for q in questions:
|
| 34 |
+
if str(q.get("task_id")) == str(task_id):
|
| 35 |
+
return q
|
| 36 |
+
|
| 37 |
+
raise ValueError(f"Task ID {task_id} not found in /questions list.")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def maybe_download_file(task_id: str, api_base: str = DEFAULT_API_URL) -> str | None:
|
| 41 |
+
"""Try to download the file associated with a given task id. Returns local path or None."""
|
| 42 |
+
url = f"{api_base}/files/{task_id}"
|
| 43 |
+
try:
|
| 44 |
+
resp = requests.get(url, timeout=60)
|
| 45 |
+
if resp.status_code != 200:
|
| 46 |
+
print(f"No file associated with task {task_id} (status {resp.status_code}).")
|
| 47 |
+
return None
|
| 48 |
+
# Create temp file with same name from headers if available
|
| 49 |
+
filename = resp.headers.get("content-disposition", "").split("filename=")[-1].strip("\"") or f"{task_id}_attachment"
|
| 50 |
+
tmp_path = os.path.join(tempfile.gettempdir(), filename)
|
| 51 |
+
with open(tmp_path, "wb") as f:
|
| 52 |
+
f.write(resp.content)
|
| 53 |
+
print(f"Downloaded attachment to {tmp_path}")
|
| 54 |
+
return tmp_path
|
| 55 |
+
except requests.HTTPError as e:
|
| 56 |
+
print(f"Could not download file for task {task_id}: {e}")
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"Error downloading file: {e}")
|
| 59 |
+
return None
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def main():
|
| 63 |
+
# Determine the task ID (CLI arg > env var > default)
|
| 64 |
+
task_id = (
|
| 65 |
+
sys.argv[1] if len(sys.argv) > 1 else os.environ.get("TASK_ID", DEFAULT_TASK_ID)
|
| 66 |
+
)
|
| 67 |
+
print(f"Using task ID: {task_id}")
|
| 68 |
+
|
| 69 |
+
q = fetch_question_by_id(task_id)
|
| 70 |
+
question_text = q["question"]
|
| 71 |
+
|
| 72 |
+
print("\n=== Specific Question ===")
|
| 73 |
+
print(f"Task ID : {task_id}")
|
| 74 |
+
print(f"Question: {question_text}")
|
| 75 |
+
|
| 76 |
+
# Attempt to get attachment if any
|
| 77 |
+
maybe_download_file(task_id)
|
| 78 |
+
|
| 79 |
+
# Run the agent
|
| 80 |
+
agent = BasicAgent()
|
| 81 |
+
result = agent.agent.invoke({"messages": [HumanMessage(content=question_text)]}, config={"callbacks": [langfuse_handler]})
|
| 82 |
+
if isinstance(result, dict) and "messages" in result and result["messages"]:
|
| 83 |
+
answer = result["messages"][-1].content.strip()
|
| 84 |
+
else:
|
| 85 |
+
answer = str(result)
|
| 86 |
+
print("\n=== Agent Answer ===")
|
| 87 |
+
print(answer)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
if __name__ == "__main__":
|
| 91 |
+
main()
|