fmarky commited on
Commit
3bf4af2
·
1 Parent(s): 81917a3

feat: create agent to pass gaia tests with 30p level 1 + integration langfuse

Browse files
.env.template ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================
2
+ # 1. Model choices/config
3
+ # ============================================
4
+
5
+ ### MAIN MODEL AGENT ###
6
+ PROVIDER=google
7
+ # Available models: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/model-versions
8
+ MODEL=gemini-2.5-flash
9
+ TEMPERATURE=0
10
+
11
+ ### VISION MODEL ###
12
+ # Available models: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/model-versions
13
+ GOOGLE_VISION_MODEL=gemini-2.5-flash
14
+
15
+ # ============================================
16
+ # 2. LLM Provider API Keys
17
+ # ============================================
18
+
19
+ OPENAI_API_KEY=<your_openai_token>
20
+ GOOGLE_API_KEY=<your_google_genai_api_key>
21
+ GROQ_API_KEY=<your_groq_api_key>
22
+ HUGGINGFACEHUB_API_TOKEN=<your_huggingface_token>
23
+ HF_TOKEN=<your_huggingface_token> # some clients use this instead of HUGGINGFACEHUB_API_TOKEN
24
+ OLLAMA_HOST=http://localhost:11434 # default, change if custom host/port
25
+
26
+ # ============================================
27
+ # 3. Observability (OTEL, costs, tokens...)
28
+ # ============================================
29
+
30
+ # LangFuse (if you want to use it)
31
+ # Note: You need to run at least Python 3.11 (GitHub Issue).
32
+
33
+ LANGFUSE_PUBLIC_KEY=<your-langfuse-public-key>
34
+ LANGFUSE_SECRET_KEY=<your-langfuse-secret-key>
35
+ # 🇪🇺 EU region
36
+ LANGFUSE_HOST=https://cloud.langfuse.com
37
+
38
+ # LangChain / LangSmith (or if you prefer)
39
+
40
+ # LANGSMITH_TRACING=true
41
+ # LANGSMITH_ENDPOINT=https://api.smith.langchain.com
42
+ # LANGCHAIN_API_KEY=<your_langsmith_key>
43
+ # LANGCHAIN_PROJECT=<your_project_name>
44
+
45
+ # ============================================
46
+ # 4. Audio transcoding (mp3 local or distant)
47
+ # ============================================
48
+
49
+ ASSEMBLYAI_API_KEY=<your_assemblyai_api_key>
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.png filter=lfs diff=lfs merge=lfs -text
37
+ *.mp3 filter=lfs diff=lfs merge=lfs -text
38
+ *.xls filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__
2
+ .env
3
+ myanswers.logs
4
+ .venv
5
+ downloads
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.10
.vscode/launch.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python Debugger: Current File",
9
+ "type": "debugpy",
10
+ "request": "launch",
11
+ "program": "app.py",
12
+ "console": "integratedTerminal"
13
+ }
14
+ ]
15
+ }
.vscode/tasks.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "uv: run active file",
6
+ "type": "shell",
7
+ "command": "uv",
8
+ "args": ["run", "${fileBasename}"],
9
+ "options": {
10
+ "cwd": "${fileDirname}"
11
+ },
12
+ "problemMatcher": [],
13
+ "group": {
14
+ "kind": "build",
15
+ "isDefault": true
16
+ }
17
+ }
18
+ ]
19
+ }
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Template Final Assignment
3
  emoji: 🕵🏻‍♂️
4
  colorFrom: indigo
5
  colorTo: indigo
@@ -8,8 +8,24 @@ sdk_version: 5.25.2
8
  app_file: app.py
9
  pinned: false
10
  hf_oauth: true
11
- # optional, default duration is 8 hours/480 minutes. Max duration is 30 days/43200 minutes.
12
  hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Final Assignment Agents Course LangGraph
3
  emoji: 🕵🏻‍♂️
4
  colorFrom: indigo
5
  colorTo: indigo
 
8
  app_file: app.py
9
  pinned: false
10
  hf_oauth: true
 
11
  hf_oauth_expiration_minutes: 480
12
  ---
13
 
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
+
16
+ # 1. Getting started wit uv
17
+
18
+ ## 1.1. Download Python into uv’s local cache (~/.uv/)
19
+ uv python install 3.10
20
+
21
+ ## 1.2. Pin this project to use Python 3.10 (writes a config file in the repo)
22
+ uv python pin 3.10
23
+
24
+ ## 1.3. Sync all dependencies from pyproject.toml (installs into .venv by default)
25
+ uv sync
26
+
27
+ ## 1.4. Run your app with the pinned interpreter + venv
28
+ uv run python app.py --reload
29
+
30
+ ## 1.5. (Optional) Generate requirements.txt for Hugging Face Spaces
31
+ uv export --format=requirements-txt > requirements.txt
agent.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from typing import Literal, TypedDict, Annotated, Optional, Any
4
+ from dotenv import load_dotenv
5
+ from langchain_core.messages import HumanMessage, SystemMessage, AnyMessage
6
+ from langchain_ollama import ChatOllama
7
+ from langgraph.graph.message import add_messages
8
+ from langgraph.graph import START, END, StateGraph
9
+ from langgraph.prebuilt import ToolNode, tools_condition
10
+ from langchain_google_genai import ChatGoogleGenerativeAI
11
+ from langchain_openai import ChatOpenAI
12
+ from langchain_groq import ChatGroq
13
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
14
+ from tools import build_tools
15
+ from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeStyles
16
+ from langfuse.langchain import CallbackHandler
17
+
18
+ load_dotenv()
19
+ # Initialize Langfuse CallbackHandler for Langchain (tracing)
20
+ langfuse_handler = CallbackHandler()
21
+
22
+
23
+ class AwesomeAgent:
24
+ def __init__(self):
25
+ self.all_tools = build_tools()
26
+ self.react_graph = build_langgraph(
27
+ os.getenv("PROVIDER"),
28
+ model=os.getenv("MODEL"),
29
+ temperature=float(os.getenv("TEMPERATURE")),
30
+ all_tools=self.all_tools,
31
+ )
32
+
33
+ # Sauvegarder dans un fichier PNG
34
+ self.react_graph.get_graph().draw_mermaid_png(
35
+ output_file_path="graph.png",
36
+ curve_style=CurveStyle.LINEAR,
37
+ node_colors=NodeStyles(first="#ffdfba", last="#baffc9", default="#fad7de"),
38
+ wrap_label_n_words=9,
39
+ draw_method=MermaidDrawMethod.API,
40
+ background_color="white",
41
+ padding=10,
42
+ )
43
+
44
+ print("Graph saved to graph.png")
45
+
46
+ print("AwesomeAgent initialized.")
47
+
48
+ def __call__(self, question: str, input_file: str) -> str:
49
+ print(f"Agent received question: {question}")
50
+ if input_file:
51
+ print(f"A file is associated with it: {input_file}")
52
+
53
+ messages = [HumanMessage(content=question)]
54
+
55
+ fixed_answer = "Default answer"
56
+ try:
57
+ result = self.react_graph.invoke(
58
+ {"messages": messages, "input_file": input_file}
59
+ )
60
+ fixed_answer = result["messages"][-1].content
61
+ except Exception as e:
62
+ print(f"Error running agent on question: {e}")
63
+
64
+ print(f"Agent returning fixed answer: {fixed_answer}")
65
+ return fixed_answer
66
+
67
+
68
+ class AgentState(TypedDict):
69
+ input_file: Optional[str] # Contains file path (PDF/PNG/...)
70
+ # We want to remember about the history so we use add_messages operator
71
+ messages: Annotated[list[AnyMessage], add_messages]
72
+
73
+
74
+ ### How tools are integrated here
75
+ # User → Assistant (LLM+tools) → ToolNode (executes) → Assistant → (loop if more tools) → Answer
76
+ # llm.bind_tools(all_tools) lets the LLM know which tools exist so it can output structured tool_calls (function calling).
77
+ # ToolNode(all_tools) actually executes those tool calls and returns their results as messages.
78
+ # tools_condition routes between the assistant and the tools, closing the loop between “planning” and “acting.”
79
+
80
+
81
+ def build_langgraph(
82
+ provider: str,
83
+ model: Optional[str] = None,
84
+ temperature: float = 0.1,
85
+ all_tools: Optional[list[Any]] = None,
86
+ ) -> StateGraph:
87
+ """Builds and returns the LangGraph agent with the given provider."""
88
+
89
+ if all_tools is None:
90
+ all_tools = []
91
+
92
+ # Select model and provider for the chat
93
+ if provider == "google":
94
+ llm = ChatGoogleGenerativeAI(
95
+ model=model or "gemini-2.5-flash", temperature=temperature
96
+ )
97
+ elif provider == "openai":
98
+ llm = ChatOpenAI(model=model or "gpt-4o", temperature=temperature)
99
+ elif provider == "groq":
100
+ llm = ChatGroq(model=model or "qwen/qwen3-32b", temperature=temperature)
101
+ elif provider == "huggingface":
102
+ llm = ChatHuggingFace(
103
+ llm=HuggingFaceEndpoint(
104
+ repo_id=model or "meta-llama/Llama-3.1-8B-Instruct",
105
+ temperature=temperature,
106
+ )
107
+ )
108
+ elif provider == "ollama":
109
+ llm = ChatOllama(
110
+ model=model or "gpt-oss:20b",
111
+ base_url="http://localhost:11434",
112
+ temperature=temperature,
113
+ )
114
+ else:
115
+ raise ValueError(
116
+ "Unsupported provider. Choose from 'openai', 'google', 'groq', 'huggingface', or 'ollama'."
117
+ )
118
+
119
+ llm_with_tools = llm.bind_tools(all_tools)
120
+ llm_without_tools = llm
121
+
122
+ def assistant(state: AgentState):
123
+ tools_description = """
124
+ WEB & SEARCH:
125
+ - duckduckgo_search: Search the web
126
+ - wikipedia_tool: Search Wikipedia for knowledge
127
+ - visit_webpage: Visit a webpage and extract readable markdown content
128
+ - arxiv_tool: Search arXiv for research papers
129
+
130
+ CALCULATIONS:
131
+ - calculator: Basic arithmetic (+, -, *, /)
132
+ - advanced_math: Advanced math functions (sqrt, log, trig)
133
+ - python_repl: Execute Python code for complex computations (use carefully)
134
+
135
+ TEXT PROCESSING:
136
+ - reverse_text: Reverse text character by character
137
+ - reverse_words: Reverse word order in text
138
+
139
+ IMAGE PROCESSING:
140
+ - ask_question_on_image_content: Ask any question on the image content
141
+
142
+ MUSIC PROCESSING:
143
+ - transcribe_mp3: Transcribe an MP3 (local path or URL) and return the result
144
+
145
+ DATA ANALYSIS:
146
+ - read_excel_file: Read Excel file content for content analysis
147
+
148
+ SYSTEM:
149
+ - shell_tool: Execute shell commands (use carefully)
150
+ """
151
+
152
+ file = state["input_file"]
153
+ sys_msg = SystemMessage(
154
+ content=f"""
155
+ You are a LLM agent. You will be given a question.
156
+ Your task it to answer the question using the tools you have access to.
157
+ take time to analyse the steps to answer the question.
158
+
159
+ # Input File
160
+ Loaded file: {file}
161
+
162
+ # Available Tools
163
+ {tools_description}
164
+ """
165
+ )
166
+
167
+ return {
168
+ "messages": [llm_with_tools.invoke([sys_msg] + state["messages"])],
169
+ "input_file": state["input_file"],
170
+ }
171
+
172
+ def format(state: AgentState):
173
+ sys_msg = SystemMessage(
174
+ content=f"""
175
+ You are an Answer Summarizer AI assistant.
176
+
177
+ Here is the question: {state.get(state["messages"][0].content)}
178
+
179
+ Summarize the answer with the following rules:
180
+ - YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
181
+ - If the question asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. Write numbers using digits.
182
+ - If the question asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.
183
+ - If the question asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.
184
+
185
+ Here is the content to format:
186
+ """
187
+ )
188
+
189
+ response = llm_without_tools.invoke(
190
+ [
191
+ sys_msg,
192
+ HumanMessage(
193
+ content=state["messages"][-1].content,
194
+ config={"callbacks": [langfuse_handler]},
195
+ ),
196
+ ]
197
+ )
198
+
199
+ return {
200
+ # "messages": [llm_with_tools.invoke([sys_msg, state["messages"][-1]])],
201
+ "messages": [response],
202
+ "input_file": state["input_file"],
203
+ }
204
+
205
+ def to_format_or_tools(state) -> Literal["tools", "format"]:
206
+ out = tools_condition(state)
207
+ return "format" if out == "__end__" else out
208
+
209
+ # Build the graph
210
+ builder = StateGraph(AgentState)
211
+ builder.add_node("assistant", assistant)
212
+ builder.add_node("tools", ToolNode(all_tools))
213
+ builder.add_node("format", format)
214
+ builder.add_edge(START, "assistant")
215
+ builder.add_conditional_edges("assistant", to_format_or_tools)
216
+ builder.add_edge("tools", "assistant")
217
+ builder.add_edge("format", END)
218
+ return builder.compile()
219
+
220
+
221
+ if __name__ == "__main__":
222
+ all_tools = build_tools()
223
+ react_graph = build_langgraph("google", all_tools=all_tools)
224
+
225
+ messages = [
226
+ HumanMessage(content="Calculate the square root of 169, then multiply by 15")
227
+ ]
228
+ result = react_graph.invoke({"messages": messages, "input_file": None})
229
+
230
+ for m in result["messages"]:
231
+ m.pretty_print()
232
+
233
+ # Example: Knowledge retrieval
234
+ print("📚 Testing Wikipedia search...")
235
+ messages = [
236
+ HumanMessage(
237
+ content="Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?"
238
+ )
239
+ ]
240
+ result = react_graph.invoke({"messages": messages, "input_file": None})
241
+
242
+ for m in result["messages"]:
243
+ m.pretty_print()
244
+
245
+ print("\n" + "=" * 60 + "\n")
app.py CHANGED
@@ -1,34 +1,63 @@
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 = "This is a default answer."
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 ---
28
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
29
 
30
  if profile:
31
- username= f"{profile.username}"
32
  print(f"User logged in: {username}")
33
  else:
34
  print("User not logged in.")
@@ -40,7 +69,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
@@ -55,16 +84,16 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
55
  response.raise_for_status()
56
  questions_data = response.json()
57
  if not questions_data:
58
- print("Fetched questions list is empty.")
59
- return "Fetched questions list is empty or invalid format.", None
60
  print(f"Fetched {len(questions_data)} questions.")
61
  except requests.exceptions.RequestException as e:
62
  print(f"Error fetching questions: {e}")
63
  return f"Error fetching questions: {e}", None
64
  except requests.exceptions.JSONDecodeError as e:
65
- print(f"Error decoding JSON response from questions endpoint: {e}")
66
- print(f"Response text: {response.text[:500]}")
67
- return f"Error decoding server response for questions: {e}", None
68
  except Exception as e:
69
  print(f"An unexpected error occurred fetching questions: {e}")
70
  return f"An unexpected error occurred fetching questions: {e}", None
@@ -73,26 +102,66 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
73
  results_log = []
74
  answers_payload = []
75
  print(f"Running agent on {len(questions_data)} questions...")
 
 
 
 
 
 
 
 
 
76
  for item in questions_data:
77
  task_id = item.get("task_id")
78
  question_text = item.get("question")
 
 
79
  if not task_id or question_text is None:
80
  print(f"Skipping item with missing task_id or question: {item}")
81
  continue
82
  try:
83
- submitted_answer = agent(question_text)
84
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
85
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
 
 
 
 
 
 
 
 
 
86
  except Exception as e:
87
- print(f"Error running agent on task {task_id}: {e}")
88
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
 
 
 
 
 
 
89
 
90
  if not answers_payload:
91
  print("Agent did not produce any answers to submit.")
92
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
93
 
94
- # 4. Prepare Submission
95
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
97
  print(status_update)
98
 
@@ -142,19 +211,16 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
142
 
143
  # --- Build Gradio Interface using Blocks ---
144
  with gr.Blocks() as demo:
145
- gr.Markdown("# Basic Agent Evaluation Runner")
146
  gr.Markdown(
147
  """
148
  **Instructions:**
149
 
150
- 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
151
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
152
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
153
 
154
- ---
155
  **Disclaimers:**
156
- Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
157
- This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
158
  """
159
  )
160
 
@@ -162,20 +228,19 @@ with gr.Blocks() as demo:
162
 
163
  run_button = gr.Button("Run Evaluation & Submit All Answers")
164
 
165
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
 
 
166
  # Removed max_rows=10 from DataFrame constructor
167
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
168
 
169
- run_button.click(
170
- fn=run_and_submit_all,
171
- outputs=[status_output, results_table]
172
- )
173
 
174
  if __name__ == "__main__":
175
- print("\n" + "-"*30 + " App Starting " + "-"*30)
176
  # Check for SPACE_HOST and SPACE_ID at startup for information
177
  space_host_startup = os.getenv("SPACE_HOST")
178
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
179
 
180
  if space_host_startup:
181
  print(f"✅ SPACE_HOST found: {space_host_startup}")
@@ -183,14 +248,18 @@ if __name__ == "__main__":
183
  else:
184
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
185
 
186
- if space_id_startup: # Print repo URLs if SPACE_ID is found
187
  print(f"✅ SPACE_ID found: {space_id_startup}")
188
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
189
- print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
 
 
190
  else:
191
- print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
 
 
192
 
193
- print("-"*(60 + len(" App Starting ")) + "\n")
194
 
195
  print("Launching Gradio Interface for Basic Agent Evaluation...")
196
- demo.launch(debug=True, share=False)
 
1
  import os
2
+
3
  import gradio as gr
 
 
4
  import pandas as pd
5
+ import requests
6
+ from dotenv import load_dotenv
7
+ from langfuse import get_client
8
+
9
+ from agent import AwesomeAgent
10
+
11
+ load_dotenv()
12
+
13
+ langfuse = get_client()
14
+
15
+ # Verify connection
16
+ if langfuse.auth_check():
17
+ print("Langfuse client is authenticated and ready!")
18
+ else:
19
+ print("Authentication failed. Please check your credentials and host.")
20
+
21
 
22
  # (Keep Constants as is)
23
  # --- Constants ---
24
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
25
 
26
+
27
+ def get_local_file(
28
+ api_url: str, task_id: str, file_name: str, folder: str = "downloads"
29
+ ) -> str | None:
30
+ if not file_name:
31
+ return None
32
+ os.makedirs(folder, exist_ok=True)
33
+ local_path = os.path.join(folder, file_name)
34
+ if os.path.exists(local_path):
35
+ return local_path
36
+ try:
37
+ url = f"{api_url}/file={file_name}"
38
+ r = requests.get(url, timeout=15)
39
+ r.raise_for_status()
40
+ except Exception:
41
+ url = f"{api_url}/files/{task_id}"
42
+ r = requests.get(url, timeout=15)
43
+ r.raise_for_status()
44
+ with open(local_path, "wb") as f:
45
+ f.write(r.content)
46
+ return local_path
47
+
48
+
49
+ # Questions available here : https://agents-course-unit4-scoring.hf.space/questions
50
+ # File available here : cca530fc-4052-43b2-b130-b30968d8aa44.png
51
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
52
  """
53
  Fetches all questions, runs the BasicAgent on them, submits all answers,
54
  and displays the results.
55
  """
56
  # --- Determine HF Space Runtime URL and Repo URL ---
57
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
58
 
59
  if profile:
60
+ username = f"{profile.username}"
61
  print(f"User logged in: {username}")
62
  else:
63
  print("User not logged in.")
 
69
 
70
  # 1. Instantiate Agent ( modify this part to create your agent)
71
  try:
72
+ agent = AwesomeAgent()
73
  except Exception as e:
74
  print(f"Error instantiating agent: {e}")
75
  return f"Error initializing agent: {e}", None
 
84
  response.raise_for_status()
85
  questions_data = response.json()
86
  if not questions_data:
87
+ print("Fetched questions list is empty.")
88
+ return "Fetched questions list is empty or invalid format.", None
89
  print(f"Fetched {len(questions_data)} questions.")
90
  except requests.exceptions.RequestException as e:
91
  print(f"Error fetching questions: {e}")
92
  return f"Error fetching questions: {e}", None
93
  except requests.exceptions.JSONDecodeError as e:
94
+ print(f"Error decoding JSON response from questions endpoint: {e}")
95
+ print(f"Response text: {response.text[:500]}")
96
+ return f"Error decoding server response for questions: {e}", None
97
  except Exception as e:
98
  print(f"An unexpected error occurred fetching questions: {e}")
99
  return f"An unexpected error occurred fetching questions: {e}", None
 
102
  results_log = []
103
  answers_payload = []
104
  print(f"Running agent on {len(questions_data)} questions...")
105
+
106
+ ############################################################################################################
107
+ # TODO: REMOVE THIS PART - AVOID ASKING 50 QUESTIONS
108
+ # questions_data = questions_data[:5] # the top 5
109
+ # questions_data = [questions_data[18]] # the 19th question
110
+ keep = {2, 3, 7, 11, 12, 16}
111
+ questions_data[:] = [x for i, x in enumerate(questions_data, start=1) if i in keep]
112
+ ############################################################################################################
113
+
114
  for item in questions_data:
115
  task_id = item.get("task_id")
116
  question_text = item.get("question")
117
+ file_name = item.get("file_name")
118
+
119
  if not task_id or question_text is None:
120
  print(f"Skipping item with missing task_id or question: {item}")
121
  continue
122
  try:
123
+ input_file = get_local_file(api_url, task_id, file_name)
124
+ submitted_answer = agent(question_text, input_file)
125
+ answers_payload.append(
126
+ {"task_id": task_id, "submitted_answer": submitted_answer}
127
+ )
128
+ results_log.append(
129
+ {
130
+ "Task ID": task_id,
131
+ "Question": question_text,
132
+ "Submitted Answer": submitted_answer,
133
+ }
134
+ )
135
  except Exception as e:
136
+ print(f"Error running agent on task {task_id}: {e}")
137
+ results_log.append(
138
+ {
139
+ "Task ID": task_id,
140
+ "Question": question_text,
141
+ "Submitted Answer": f"AGENT ERROR: {e}",
142
+ }
143
+ )
144
 
145
  if not answers_payload:
146
  print("Agent did not produce any answers to submit.")
147
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
148
 
149
+ # ############################################################################################################
150
+ # # TODO: REMOVE THIS PART - AVOID SUBMISSION
151
+ # results_df = pd.DataFrame(results_log)
152
+ # status = (
153
+ # f"✅ Dry run complete: processed {len(answers_payload)} question(s). "
154
+ # f"Submission skipped."
155
+ # )
156
+ # return status, results_df
157
+ # ############################################################################################################
158
+
159
+ # 4. Prepare Submission
160
+ submission_data = {
161
+ "username": username.strip(),
162
+ "agent_code": agent_code,
163
+ "answers": answers_payload,
164
+ }
165
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
166
  print(status_update)
167
 
 
211
 
212
  # --- Build Gradio Interface using Blocks ---
213
  with gr.Blocks() as demo:
214
+ gr.Markdown("# Awesome Agent Evaluation Runner")
215
  gr.Markdown(
216
  """
217
  **Instructions:**
218
 
219
+ 1. Log in to your Hugging Face account using the button below.
220
+ 2. Click 'Run Evaluation & Submit All Answers' to fetch questions, run the agent, submit answers, and see the score.
 
221
 
 
222
  **Disclaimers:**
223
+ Once clicking on the "submit button, it can take quite some time (this is the time for the agent to go through all the questions).
 
224
  """
225
  )
226
 
 
228
 
229
  run_button = gr.Button("Run Evaluation & Submit All Answers")
230
 
231
+ status_output = gr.Textbox(
232
+ label="Run Status / Submission Result", lines=5, interactive=False
233
+ )
234
  # Removed max_rows=10 from DataFrame constructor
235
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
236
 
237
+ run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
 
 
 
238
 
239
  if __name__ == "__main__":
240
+ print("\n" + "-" * 30 + " App Starting " + "-" * 30)
241
  # Check for SPACE_HOST and SPACE_ID at startup for information
242
  space_host_startup = os.getenv("SPACE_HOST")
243
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
244
 
245
  if space_host_startup:
246
  print(f"✅ SPACE_HOST found: {space_host_startup}")
 
248
  else:
249
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
250
 
251
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
252
  print(f"✅ SPACE_ID found: {space_id_startup}")
253
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
254
+ print(
255
+ f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main"
256
+ )
257
  else:
258
+ print(
259
+ "ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined."
260
+ )
261
 
262
+ print("-" * (60 + len(" App Starting ")) + "\n")
263
 
264
  print("Launching Gradio Interface for Basic Agent Evaluation...")
265
+ demo.launch(debug=True, share=False)
examples/apples.png ADDED

Git LFS Details

  • SHA256: 66264a2c69af31f9e04d8ae72d4378fa31dca910abe706d23bcf6302b4a95eec
  • Pointer size: 131 Bytes
  • Size of remote file: 229 kB
examples/file_example_XLS_10.xls ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:753e8d18b04ab7c476bdf43a237603ade93d8a985c39f0366cd5f889d15d2dc7
3
+ size 8704
examples/receipt.png ADDED

Git LFS Details

  • SHA256: cb71171d58bf2b8d91c94e53017169e47ed743af54528c2bdbc5ab0fac7d0089
  • Pointer size: 131 Bytes
  • Size of remote file: 632 kB
examples/sample.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:200f767e732b49efef5c05d128903ee4d2c34e66fdce7f5593ac123b2e637673
3
+ size 280868
graph.png ADDED

Git LFS Details

  • SHA256: 1ce5e63db1a058f562b62046ebb94c278c3fd624a9290c4cbbe14a3078bcc16d
  • Pointer size: 130 Bytes
  • Size of remote file: 11.9 kB
pyproject.toml ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "final-assignment-agents-course"
3
+ version = "0.1.0"
4
+ description = "Final assignment Hugging Face agents course"
5
+ readme = "README.md"
6
+ requires-python = "==3.10.*"
7
+ dependencies = [
8
+ "gradio",
9
+ "gradio[oauth]",
10
+ "requests",
11
+ "langgraph",
12
+ "langgraph-checkpoint",
13
+ "langgraph-prebuilt",
14
+ "langgraph-sdk",
15
+ "langsmith",
16
+ "python-dotenv>=1.1.1",
17
+ "wikipedia",
18
+ "langchain[openai]",
19
+ "langchain",
20
+ "langchain-community",
21
+ "langchain-core",
22
+ "langchain-experimental",
23
+ "langchain-google-genai",
24
+ "langchain-groq",
25
+ "langchain-huggingface",
26
+ "langchain-ollama",
27
+ "langchain-openai",
28
+ "langchain-text-splitters",
29
+ "pandas",
30
+ "markdownify",
31
+ "ddgs",
32
+ "duckduckgo-search",
33
+ "arxiv",
34
+ "youtube_transcript_api",
35
+ "beautifulsoup4",
36
+ "openpyxl",
37
+ "xlrd>=2.0.1",
38
+ "assemblyai",
39
+ "langfuse",
40
+ ]
requirements.txt CHANGED
@@ -1,2 +1,1234 @@
1
- gradio
2
- requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv export --format=requirements-txt
3
+ aiofiles==24.1.0 \
4
+ --hash=sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c \
5
+ --hash=sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5
6
+ # via gradio
7
+ aiohappyeyeballs==2.6.1 \
8
+ --hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \
9
+ --hash=sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8
10
+ # via aiohttp
11
+ aiohttp==3.12.15 \
12
+ --hash=sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af \
13
+ --hash=sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6 \
14
+ --hash=sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1 \
15
+ --hash=sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a \
16
+ --hash=sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79 \
17
+ --hash=sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77 \
18
+ --hash=sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421 \
19
+ --hash=sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c \
20
+ --hash=sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2 \
21
+ --hash=sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb \
22
+ --hash=sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830 \
23
+ --hash=sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2 \
24
+ --hash=sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc \
25
+ --hash=sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5 \
26
+ --hash=sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4 \
27
+ --hash=sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b \
28
+ --hash=sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065 \
29
+ --hash=sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d
30
+ # via langchain-community
31
+ aiosignal==1.4.0 \
32
+ --hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e \
33
+ --hash=sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7
34
+ # via aiohttp
35
+ annotated-types==0.7.0 \
36
+ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
37
+ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
38
+ # via pydantic
39
+ anyio==4.10.0 \
40
+ --hash=sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6 \
41
+ --hash=sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1
42
+ # via
43
+ # gradio
44
+ # groq
45
+ # httpx
46
+ # openai
47
+ # starlette
48
+ arxiv==2.2.0 \
49
+ --hash=sha256:545b8af5ab301efff7697cd112b5189e631b80521ccbc33fbc1e1f9cff63ca4d \
50
+ --hash=sha256:6072a2211e95697092ef32acde0144d7de2cfa71208e2751724316c9df322cc0
51
+ # via final-assignment-agents-course
52
+ assemblyai==0.43.1 \
53
+ --hash=sha256:4e6e11eb07130c5c87aea84cc63d80933a6588e9b3aeee80026c2419756a3513 \
54
+ --hash=sha256:c39e8ddb4434af316ade3259e51f86be8134c24be821d8cd84502850ea551951
55
+ # via final-assignment-agents-course
56
+ async-timeout==4.0.3 \
57
+ --hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
58
+ --hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
59
+ # via
60
+ # aiohttp
61
+ # langchain
62
+ attrs==25.3.0 \
63
+ --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \
64
+ --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b
65
+ # via aiohttp
66
+ authlib==1.6.3 \
67
+ --hash=sha256:7ea0f082edd95a03b7b72edac65ec7f8f68d703017d7e37573aee4fc603f2a48 \
68
+ --hash=sha256:9f7a982cc395de719e4c2215c5707e7ea690ecf84f1ab126f28c053f4219e610
69
+ # via gradio
70
+ backoff==2.2.1 \
71
+ --hash=sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba \
72
+ --hash=sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8
73
+ # via langfuse
74
+ beautifulsoup4==4.13.5 \
75
+ --hash=sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695 \
76
+ --hash=sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a
77
+ # via
78
+ # final-assignment-agents-course
79
+ # markdownify
80
+ # wikipedia
81
+ brotli==1.1.0 \
82
+ --hash=sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128 \
83
+ --hash=sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3 \
84
+ --hash=sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2 \
85
+ --hash=sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da \
86
+ --hash=sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d \
87
+ --hash=sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec \
88
+ --hash=sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c \
89
+ --hash=sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e \
90
+ --hash=sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724 \
91
+ --hash=sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d \
92
+ --hash=sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2 \
93
+ --hash=sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9 \
94
+ --hash=sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752 \
95
+ --hash=sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1 \
96
+ --hash=sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80 \
97
+ --hash=sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0 \
98
+ --hash=sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e
99
+ # via gradio
100
+ cachetools==5.5.2 \
101
+ --hash=sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4 \
102
+ --hash=sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a
103
+ # via google-auth
104
+ certifi==2025.8.3 \
105
+ --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \
106
+ --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5
107
+ # via
108
+ # httpcore
109
+ # httpx
110
+ # requests
111
+ cffi==1.17.1 ; platform_python_implementation != 'PyPy' \
112
+ --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \
113
+ --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \
114
+ --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \
115
+ --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \
116
+ --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \
117
+ --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \
118
+ --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \
119
+ --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \
120
+ --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \
121
+ --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \
122
+ --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \
123
+ --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \
124
+ --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382
125
+ # via cryptography
126
+ charset-normalizer==3.4.3 \
127
+ --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \
128
+ --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \
129
+ --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \
130
+ --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \
131
+ --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \
132
+ --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \
133
+ --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \
134
+ --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \
135
+ --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \
136
+ --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \
137
+ --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \
138
+ --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \
139
+ --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72
140
+ # via requests
141
+ click==8.2.1 \
142
+ --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \
143
+ --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b
144
+ # via
145
+ # ddgs
146
+ # duckduckgo-search
147
+ # typer
148
+ # uvicorn
149
+ colorama==0.4.6 ; sys_platform == 'win32' \
150
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
151
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
152
+ # via
153
+ # click
154
+ # tqdm
155
+ cryptography==45.0.7 \
156
+ --hash=sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513 \
157
+ --hash=sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5 \
158
+ --hash=sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c \
159
+ --hash=sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130 \
160
+ --hash=sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443 \
161
+ --hash=sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59 \
162
+ --hash=sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee \
163
+ --hash=sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf \
164
+ --hash=sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27 \
165
+ --hash=sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971 \
166
+ --hash=sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8 \
167
+ --hash=sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339 \
168
+ --hash=sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6 \
169
+ --hash=sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90 \
170
+ --hash=sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691 \
171
+ --hash=sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3 \
172
+ --hash=sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083 \
173
+ --hash=sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6 \
174
+ --hash=sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1 \
175
+ --hash=sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3 \
176
+ --hash=sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8 \
177
+ --hash=sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2 \
178
+ --hash=sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7 \
179
+ --hash=sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141 \
180
+ --hash=sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3 \
181
+ --hash=sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4 \
182
+ --hash=sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4 \
183
+ --hash=sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b \
184
+ --hash=sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252 \
185
+ --hash=sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17 \
186
+ --hash=sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd
187
+ # via authlib
188
+ dataclasses-json==0.6.7 \
189
+ --hash=sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a \
190
+ --hash=sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0
191
+ # via langchain-community
192
+ ddgs==9.5.5 \
193
+ --hash=sha256:01fc8017a653ad16501bd8ac9fedcd29291f6500b1f8a7e92a916cdad7fc2fa2 \
194
+ --hash=sha256:9a09aa9ba24173d14321137c8c1bc54040ed0bf3bad956cb2f9feeda77968770
195
+ # via final-assignment-agents-course
196
+ defusedxml==0.7.1 \
197
+ --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \
198
+ --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
199
+ # via youtube-transcript-api
200
+ distro==1.9.0 \
201
+ --hash=sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed \
202
+ --hash=sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2
203
+ # via
204
+ # groq
205
+ # openai
206
+ duckduckgo-search==8.1.1 \
207
+ --hash=sha256:9da91c9eb26a17e016ea1da26235d40404b46b0565ea86d75a9f78cc9441f935 \
208
+ --hash=sha256:f48adbb06626ee05918f7e0cef3a45639e9939805c4fc179e68c48a12f1b5062
209
+ # via final-assignment-agents-course
210
+ et-xmlfile==2.0.0 \
211
+ --hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \
212
+ --hash=sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54
213
+ # via openpyxl
214
+ exceptiongroup==1.3.0 \
215
+ --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \
216
+ --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88
217
+ # via anyio
218
+ fastapi==0.116.1 \
219
+ --hash=sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565 \
220
+ --hash=sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143
221
+ # via gradio
222
+ feedparser==6.0.11 \
223
+ --hash=sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45 \
224
+ --hash=sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5
225
+ # via arxiv
226
+ ffmpy==0.6.1 \
227
+ --hash=sha256:69a37e2d7d6feb840e233d5640f3499a8b0a8657336774c86e4c52a3219222d4 \
228
+ --hash=sha256:b5830fd05f72bace05b8fb28724d54a7a63c5119d7f74ca36a75df33f749142d
229
+ # via gradio
230
+ filelock==3.19.1 \
231
+ --hash=sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58 \
232
+ --hash=sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d
233
+ # via huggingface-hub
234
+ filetype==1.2.0 \
235
+ --hash=sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb \
236
+ --hash=sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25
237
+ # via langchain-google-genai
238
+ frozenlist==1.7.0 \
239
+ --hash=sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615 \
240
+ --hash=sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718 \
241
+ --hash=sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50 \
242
+ --hash=sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa \
243
+ --hash=sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f \
244
+ --hash=sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e \
245
+ --hash=sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e \
246
+ --hash=sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e \
247
+ --hash=sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd \
248
+ --hash=sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577 \
249
+ --hash=sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464 \
250
+ --hash=sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61 \
251
+ --hash=sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59 \
252
+ --hash=sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e \
253
+ --hash=sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d \
254
+ --hash=sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c \
255
+ --hash=sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9 \
256
+ --hash=sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a \
257
+ --hash=sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981
258
+ # via
259
+ # aiohttp
260
+ # aiosignal
261
+ fsspec==2025.3.0 \
262
+ --hash=sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972 \
263
+ --hash=sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3
264
+ # via
265
+ # gradio-client
266
+ # huggingface-hub
267
+ google-ai-generativelanguage==0.6.18 \
268
+ --hash=sha256:13d8174fea90b633f520789d32df7b422058fd5883b022989c349f1017db7fcf \
269
+ --hash=sha256:274ba9fcf69466ff64e971d565884434388e523300afd468fc8e3033cd8e606e
270
+ # via langchain-google-genai
271
+ google-api-core==2.25.1 \
272
+ --hash=sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7 \
273
+ --hash=sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8
274
+ # via google-ai-generativelanguage
275
+ google-auth==2.40.3 \
276
+ --hash=sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca \
277
+ --hash=sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77
278
+ # via
279
+ # google-ai-generativelanguage
280
+ # google-api-core
281
+ googleapis-common-protos==1.70.0 \
282
+ --hash=sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257 \
283
+ --hash=sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8
284
+ # via
285
+ # google-api-core
286
+ # grpcio-status
287
+ # opentelemetry-exporter-otlp-proto-http
288
+ gradio==5.44.1 \
289
+ --hash=sha256:8527837aa6de4b0d2398dab11baac8e3eac9da69140ed0da6efc6ac497fa818d \
290
+ --hash=sha256:cb22dd519c3bb2f8c7960cdcc23ca3b869511c85e320f486d7aef6e3627f97b9
291
+ # via final-assignment-agents-course
292
+ gradio-client==1.12.1 \
293
+ --hash=sha256:37c0bcd0e6b3794b2b2e0b5039696d6962d8125bdb96960ad1b79412326b1664 \
294
+ --hash=sha256:64ae7b1d951482194e3a2f8d20cd3fbdaaa13418ee988445d3c9edb28da50ea2
295
+ # via gradio
296
+ greenlet==3.2.4 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' \
297
+ --hash=sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d \
298
+ --hash=sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31 \
299
+ --hash=sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c \
300
+ --hash=sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f \
301
+ --hash=sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c \
302
+ --hash=sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590 \
303
+ --hash=sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5 \
304
+ --hash=sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d \
305
+ --hash=sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b \
306
+ --hash=sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c
307
+ # via sqlalchemy
308
+ groovy==0.1.2 \
309
+ --hash=sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083 \
310
+ --hash=sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64
311
+ # via gradio
312
+ groq==0.31.1 \
313
+ --hash=sha256:4d611e0100cb22732c43b53af37933a1b8a5c5a18fa96132fee14e6c15d737e6 \
314
+ --hash=sha256:536bd5dd6267dea5b3710e41094c0479748da2d155b9e073650e94b7fb2d71e8
315
+ # via langchain-groq
316
+ grpcio==1.74.0 \
317
+ --hash=sha256:136b53c91ac1d02c8c24201bfdeb56f8b3ac3278668cbb8e0ba49c88069e1bdc \
318
+ --hash=sha256:1733969040989f7acc3d94c22f55b4a9501a30f6aaacdbccfaba0a3ffb255ab7 \
319
+ --hash=sha256:4e4181bfc24413d1e3a37a0b7889bea68d973d4b45dd2bc68bb766c140718f82 \
320
+ --hash=sha256:68c8ebcca945efff9d86d8d6d7bfb0841cf0071024417e2d7f45c5e46b5b08eb \
321
+ --hash=sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1 \
322
+ --hash=sha256:85bd5cdf4ed7b2d6438871adf6afff9af7096486fcf51818a81b77ef4dd30907 \
323
+ --hash=sha256:9e912d3c993a29df6c627459af58975b2e5c897d93287939b9d5065f000249b5 \
324
+ --hash=sha256:c3d7bd6e3929fd2ea7fbc3f562e4987229ead70c9ae5f01501a46701e08f1ad9 \
325
+ --hash=sha256:e154d230dc1bbbd78ad2fdc3039fa50ad7ffcf438e4eb2fa30bce223a70c7486 \
326
+ --hash=sha256:e8978003816c7b9eabe217f88c78bc26adc8f9304bf6a594b02e5a49b2ef9c11 \
327
+ --hash=sha256:fe0f540750a13fd8e5da4b3eaba91a785eea8dca5ccd2bc2ffe978caa403090e
328
+ # via
329
+ # google-api-core
330
+ # grpcio-status
331
+ grpcio-status==1.74.0 \
332
+ --hash=sha256:52cdbd759a6760fc8f668098a03f208f493dd5c76bf8e02598bbbaf1f6fc2876 \
333
+ --hash=sha256:c58c1b24aa454e30f1fc6a7e0dbbc194c54a408143971a94b5f4e40bb5831432
334
+ # via google-api-core
335
+ h11==0.16.0 \
336
+ --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
337
+ --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
338
+ # via
339
+ # httpcore
340
+ # uvicorn
341
+ hf-xet==1.1.9 ; platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' \
342
+ --hash=sha256:5aad3933de6b725d61d51034e04174ed1dce7a57c63d530df0014dea15a40127 \
343
+ --hash=sha256:86754c2d6d5afb11b0a435e6e18911a4199262fe77553f8c50d75e21242193ea \
344
+ --hash=sha256:96a6139c9e44dad1c52c52520db0fffe948f6bce487cfb9d69c125f254bb3790 \
345
+ --hash=sha256:9b486de7a64a66f9a172f4b3e0dfe79c9f0a93257c501296a2521a13495a698a \
346
+ --hash=sha256:a3b6215f88638dd7a6ff82cb4e738dcbf3d863bf667997c093a3c990337d1160 \
347
+ --hash=sha256:a4c5a840c2c4e6ec875ed13703a60e3523bc7f48031dfd750923b2a4d1a5fc3c \
348
+ --hash=sha256:ad1022e9a998e784c97b2173965d07fe33ee26e4594770b7785a8cc8f922cd95 \
349
+ --hash=sha256:c99073ce404462e909f1d5839b2d14a3827b8fe75ed8aed551ba6609c026c803
350
+ # via huggingface-hub
351
+ httpcore==1.0.9 \
352
+ --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \
353
+ --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
354
+ # via httpx
355
+ httpx==0.28.1 \
356
+ --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
357
+ --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
358
+ # via
359
+ # assemblyai
360
+ # gradio
361
+ # gradio-client
362
+ # groq
363
+ # langfuse
364
+ # langgraph-sdk
365
+ # langsmith
366
+ # ollama
367
+ # openai
368
+ # safehttpx
369
+ httpx-sse==0.4.1 \
370
+ --hash=sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e \
371
+ --hash=sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37
372
+ # via langchain-community
373
+ huggingface-hub==0.34.4 \
374
+ --hash=sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a \
375
+ --hash=sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c
376
+ # via
377
+ # gradio
378
+ # gradio-client
379
+ # langchain-huggingface
380
+ # tokenizers
381
+ idna==3.10 \
382
+ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
383
+ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
384
+ # via
385
+ # anyio
386
+ # httpx
387
+ # requests
388
+ # yarl
389
+ importlib-metadata==8.7.0 \
390
+ --hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \
391
+ --hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd
392
+ # via opentelemetry-api
393
+ itsdangerous==2.2.0 \
394
+ --hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \
395
+ --hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173
396
+ # via gradio
397
+ jinja2==3.1.6 \
398
+ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
399
+ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
400
+ # via gradio
401
+ jiter==0.10.0 \
402
+ --hash=sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf \
403
+ --hash=sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500 \
404
+ --hash=sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224 \
405
+ --hash=sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e \
406
+ --hash=sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0 \
407
+ --hash=sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7 \
408
+ --hash=sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f \
409
+ --hash=sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6 \
410
+ --hash=sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee \
411
+ --hash=sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90 \
412
+ --hash=sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303 \
413
+ --hash=sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4 \
414
+ --hash=sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5
415
+ # via openai
416
+ jsonpatch==1.33 \
417
+ --hash=sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade \
418
+ --hash=sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c
419
+ # via langchain-core
420
+ jsonpointer==3.0.0 \
421
+ --hash=sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942 \
422
+ --hash=sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef
423
+ # via jsonpatch
424
+ langchain==0.3.27 \
425
+ --hash=sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798 \
426
+ --hash=sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62
427
+ # via
428
+ # final-assignment-agents-course
429
+ # langchain-community
430
+ langchain-community==0.3.29 \
431
+ --hash=sha256:1f3d37973b10458052bb3cc02dce9773a8ffbd02961698c6d395b8c8d7f9e004 \
432
+ --hash=sha256:c876ec7ef40b46353af164197f4e08e157650e8a02c9fb9d49351cdc16c839fe
433
+ # via
434
+ # final-assignment-agents-course
435
+ # langchain-experimental
436
+ langchain-core==0.3.75 \
437
+ --hash=sha256:03ca1fadf955ee3c7d5806a841f4b3a37b816acea5e61a7e6ba1298c05eea7f5 \
438
+ --hash=sha256:ab0eb95a06ed6043f76162e6086b45037690cb70b7f090bd83b5ebb8a05b70ed
439
+ # via
440
+ # final-assignment-agents-course
441
+ # langchain
442
+ # langchain-community
443
+ # langchain-experimental
444
+ # langchain-google-genai
445
+ # langchain-groq
446
+ # langchain-huggingface
447
+ # langchain-ollama
448
+ # langchain-openai
449
+ # langchain-text-splitters
450
+ # langgraph
451
+ # langgraph-checkpoint
452
+ # langgraph-prebuilt
453
+ langchain-experimental==0.3.4 \
454
+ --hash=sha256:2e587306aea36b60fa5e5fc05dc7281bee9f60a806f0bf9d30916e0ee096af80 \
455
+ --hash=sha256:937c4259ee4a639c618d19acf0e2c5c2898ef127050346edc5655259aa281a21
456
+ # via final-assignment-agents-course
457
+ langchain-google-genai==2.1.10 \
458
+ --hash=sha256:247e38908d6b37fb4c4892ca09ae224c7510158b54b0582a9f39a7ddf37a0e2c \
459
+ --hash=sha256:3d091aa61284d7256b2887966e806689adce35e55bc46f705162e5ede8aacda9
460
+ # via final-assignment-agents-course
461
+ langchain-groq==0.3.7 \
462
+ --hash=sha256:2e13870534c032fe1facde3a6ab1fb59af6f1e7a2763125ad549d8e403fc9d00 \
463
+ --hash=sha256:4d799aa565f31e51c632f0f20c588f21c5a0e6c6dd2303efaef43351f9b41bd4
464
+ # via final-assignment-agents-course
465
+ langchain-huggingface==0.3.1 \
466
+ --hash=sha256:0a145534ce65b5a723c8562c456100a92513bbbf212e6d8c93fdbae174b41341 \
467
+ --hash=sha256:de10a692dc812885696fbaab607d28ac86b833b0f305bccd5d82d60336b07b7d
468
+ # via final-assignment-agents-course
469
+ langchain-ollama==0.3.7 \
470
+ --hash=sha256:845192a75a1671bd36bc8c54baae2e929c3b1b0e3c8c618438df20354b0231de \
471
+ --hash=sha256:9deeca09a44fb8c5a46b1e43d670a7301f1d1e73196fa9191a89f9883217903b
472
+ # via final-assignment-agents-course
473
+ langchain-openai==0.3.32 \
474
+ --hash=sha256:3354f76822f7cc76d8069831fe2a77f9bc7ff3b4f13af788bd94e4c6e853b400 \
475
+ --hash=sha256:782ad669bd1bdb964456d8882c5178717adcfceecb482cc20005f770e43d346d
476
+ # via
477
+ # final-assignment-agents-course
478
+ # langchain
479
+ langchain-text-splitters==0.3.11 \
480
+ --hash=sha256:7a50a04ada9a133bbabb80731df7f6ddac51bc9f1b9cab7fa09304d71d38a6cc \
481
+ --hash=sha256:cf079131166a487f1372c8ab5d0bfaa6c0a4291733d9c43a34a16ac9bcd6a393
482
+ # via
483
+ # final-assignment-agents-course
484
+ # langchain
485
+ langfuse==3.3.4 \
486
+ --hash=sha256:15b9d20878cf39a48ca9cfa7e52acdfeb043603d3a9cef8cf451687a4d838c6b \
487
+ --hash=sha256:e5df4e7284298990b522e02a1dc6c3c72ebc4a7a411dc7d39255fb8c2e5a7c3a
488
+ # via final-assignment-agents-course
489
+ langgraph==0.6.6 \
490
+ --hash=sha256:a2283a5236abba6c8307c1a485c04e8a0f0ffa2be770878782a7bf2deb8d7954 \
491
+ --hash=sha256:e7d3cefacf356f8c01721b166b67b3bf581659d5361a3530f59ecd9b8448eca7
492
+ # via final-assignment-agents-course
493
+ langgraph-checkpoint==2.1.1 \
494
+ --hash=sha256:5a779134fd28134a9a83d078be4450bbf0e0c79fdf5e992549658899e6fc5ea7 \
495
+ --hash=sha256:72038c0f9e22260cb9bff1f3ebe5eb06d940b7ee5c1e4765019269d4f21cf92d
496
+ # via
497
+ # final-assignment-agents-course
498
+ # langgraph
499
+ # langgraph-prebuilt
500
+ langgraph-prebuilt==0.6.4 \
501
+ --hash=sha256:819f31d88b84cb2729ff1b79db2d51e9506b8fb7aaacfc0d359d4fe16e717344 \
502
+ --hash=sha256:e9e53b906ee5df46541d1dc5303239e815d3ec551e52bb03dd6463acc79ec28f
503
+ # via
504
+ # final-assignment-agents-course
505
+ # langgraph
506
+ langgraph-sdk==0.2.6 \
507
+ --hash=sha256:477216b573b8177bbd849f4c754782a81279fbbd88bfadfeda44422d14b18b08 \
508
+ --hash=sha256:7db27cd86d1231fa614823ff416fcd2541b5565ad78ae950f31ae96d7af7c519
509
+ # via
510
+ # final-assignment-agents-course
511
+ # langgraph
512
+ langsmith==0.4.25 \
513
+ --hash=sha256:56f0c45810384fba37582ca17fdbcf6ead51934d26d72672e5a810452c0d4ae3 \
514
+ --hash=sha256:adb61784ff58e65f0290ba45770626219fb06a776e69fbcf98aec580478b4686
515
+ # via
516
+ # final-assignment-agents-course
517
+ # langchain
518
+ # langchain-community
519
+ # langchain-core
520
+ lxml==6.0.1 \
521
+ --hash=sha256:07038c62fd0fe2743e2f5326f54d464715373c791035d7dda377b3c9a5d0ad77 \
522
+ --hash=sha256:0abfbaf4ebbd7fd33356217d317b6e4e2ef1648be6a9476a52b57ffc6d8d1780 \
523
+ --hash=sha256:11a052cbd013b7140bbbb38a14e2329b6192478344c99097e378c691b7119551 \
524
+ --hash=sha256:1ebbf2d9775be149235abebdecae88fe3b3dd06b1797cd0f6dffe6948e85309d \
525
+ --hash=sha256:21344d29c82ca8547ea23023bb8e7538fa5d4615a1773b991edf8176a870c1ea \
526
+ --hash=sha256:2b3a882ebf27dd026df3801a87cf49ff791336e0f94b0fad195db77e01240690 \
527
+ --hash=sha256:3b38e20c578149fdbba1fd3f36cb1928a3aaca4b011dfd41ba09d11fb396e1b9 \
528
+ --hash=sha256:4588806a721552692310ebe9f90c17ac6c7c5dac438cd93e3d74dd60531c3211 \
529
+ --hash=sha256:50b5e54f6a9461b1e9c08b4a3420415b538d4773bd9df996b9abcbfe95f4f1fd \
530
+ --hash=sha256:615bb6c73fed7929e3a477a3297a797892846b253d59c84a62c98bdce3849a0a \
531
+ --hash=sha256:6f393e10685b37f15b1daef8aa0d734ec61860bb679ec447afa0001a31e7253f \
532
+ --hash=sha256:7a44a5fb1edd11b3a65c12c23e1049c8ae49d90a24253ff18efbcb6aa042d012 \
533
+ --hash=sha256:8466faa66b0353802fb7c054a400ac17ce2cf416e3ad8516eadeff9cba85b741 \
534
+ --hash=sha256:8f5cf2addfbbe745251132c955ad62d8519bb4b2c28b0aa060eca4541798d86e \
535
+ --hash=sha256:a389e9f11c010bd30531325805bbe97bdf7f728a73d0ec475adef57ffec60547 \
536
+ --hash=sha256:a57d9eb9aadf311c9e8785230eec83c6abb9aef2adac4c0587912caf8f3010b8 \
537
+ --hash=sha256:aa8f130f4b2dc94baa909c17bb7994f0268a2a72b9941c872e8e558fd6709050 \
538
+ --hash=sha256:c43460f4aac016ee0e156bfa14a9de9b3e06249b12c228e27654ac3996a46d5b \
539
+ --hash=sha256:d877874a31590b72d1fa40054b50dc33084021bfc15d01b3a661d85a302af821 \
540
+ --hash=sha256:f1b60a3287bf33a2a54805d76b82055bcc076e445fd539ee9ae1fe85ed373691 \
541
+ --hash=sha256:f7bbfb0751551a8786915fc6b615ee56344dacc1b1033697625b553aefdd9837
542
+ # via
543
+ # ddgs
544
+ # duckduckgo-search
545
+ markdown-it-py==4.0.0 ; sys_platform != 'emscripten' \
546
+ --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \
547
+ --hash=sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3
548
+ # via rich
549
+ markdownify==1.2.0 \
550
+ --hash=sha256:48e150a1c4993d4d50f282f725c0111bd9eb25645d41fa2f543708fd44161351 \
551
+ --hash=sha256:f6c367c54eb24ee953921804dfe6d6575c5e5b42c643955e7242034435de634c
552
+ # via final-assignment-agents-course
553
+ markupsafe==3.0.2 \
554
+ --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \
555
+ --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \
556
+ --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \
557
+ --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \
558
+ --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \
559
+ --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \
560
+ --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \
561
+ --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \
562
+ --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \
563
+ --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \
564
+ --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50
565
+ # via
566
+ # gradio
567
+ # jinja2
568
+ marshmallow==3.26.1 \
569
+ --hash=sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c \
570
+ --hash=sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6
571
+ # via dataclasses-json
572
+ mdurl==0.1.2 ; sys_platform != 'emscripten' \
573
+ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
574
+ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
575
+ # via markdown-it-py
576
+ multidict==6.6.4 \
577
+ --hash=sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729 \
578
+ --hash=sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495 \
579
+ --hash=sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f \
580
+ --hash=sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f \
581
+ --hash=sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c \
582
+ --hash=sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0 \
583
+ --hash=sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db \
584
+ --hash=sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987 \
585
+ --hash=sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796 \
586
+ --hash=sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace \
587
+ --hash=sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f \
588
+ --hash=sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb \
589
+ --hash=sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb \
590
+ --hash=sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877 \
591
+ --hash=sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0 \
592
+ --hash=sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8 \
593
+ --hash=sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd \
594
+ --hash=sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7 \
595
+ --hash=sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb \
596
+ --hash=sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6
597
+ # via
598
+ # aiohttp
599
+ # yarl
600
+ mypy-extensions==1.1.0 \
601
+ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \
602
+ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558
603
+ # via typing-inspect
604
+ numpy==2.2.6 \
605
+ --hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \
606
+ --hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \
607
+ --hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \
608
+ --hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \
609
+ --hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \
610
+ --hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \
611
+ --hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \
612
+ --hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \
613
+ --hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \
614
+ --hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \
615
+ --hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \
616
+ --hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \
617
+ --hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \
618
+ --hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \
619
+ --hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915
620
+ # via
621
+ # gradio
622
+ # langchain-community
623
+ # pandas
624
+ ollama==0.5.3 \
625
+ --hash=sha256:40b6dff729df3b24e56d4042fd9d37e231cee8e528677e0d085413a1d6692394 \
626
+ --hash=sha256:a8303b413d99a9043dbf77ebf11ced672396b59bec27e6d5db67c88f01b279d2
627
+ # via langchain-ollama
628
+ openai==1.106.1 \
629
+ --hash=sha256:5f575967e3a05555825c43829cdcd50be6e49ab6a3e5262f0937a3f791f917f1 \
630
+ --hash=sha256:bfdef37c949f80396c59f2c17e0eda35414979bc07ef3379596a93c9ed044f3a
631
+ # via langchain-openai
632
+ openpyxl==3.1.5 \
633
+ --hash=sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2 \
634
+ --hash=sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050
635
+ # via final-assignment-agents-course
636
+ opentelemetry-api==1.36.0 \
637
+ --hash=sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c \
638
+ --hash=sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0
639
+ # via
640
+ # langfuse
641
+ # opentelemetry-exporter-otlp-proto-http
642
+ # opentelemetry-sdk
643
+ # opentelemetry-semantic-conventions
644
+ opentelemetry-exporter-otlp-proto-common==1.36.0 \
645
+ --hash=sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840 \
646
+ --hash=sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf
647
+ # via opentelemetry-exporter-otlp-proto-http
648
+ opentelemetry-exporter-otlp-proto-http==1.36.0 \
649
+ --hash=sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902 \
650
+ --hash=sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e
651
+ # via langfuse
652
+ opentelemetry-proto==1.36.0 \
653
+ --hash=sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f \
654
+ --hash=sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e
655
+ # via
656
+ # opentelemetry-exporter-otlp-proto-common
657
+ # opentelemetry-exporter-otlp-proto-http
658
+ opentelemetry-sdk==1.36.0 \
659
+ --hash=sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581 \
660
+ --hash=sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb
661
+ # via
662
+ # langfuse
663
+ # opentelemetry-exporter-otlp-proto-http
664
+ opentelemetry-semantic-conventions==0.57b0 \
665
+ --hash=sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32 \
666
+ --hash=sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78
667
+ # via opentelemetry-sdk
668
+ orjson==3.11.3 \
669
+ --hash=sha256:0c212cfdd90512fe722fa9bd620de4d46cda691415be86b2e02243242ae81873 \
670
+ --hash=sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a \
671
+ --hash=sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7 \
672
+ --hash=sha256:58533f9e8266cb0ac298e259ed7b4d42ed3fa0b78ce76860626164de49e0d467 \
673
+ --hash=sha256:5ff835b5d3e67d9207343effb03760c00335f8b5285bfceefd4dc967b0e48f6a \
674
+ --hash=sha256:90368277087d4af32d38bd55f9da2ff466d25325bf6167c8f382d8ee40cb2bbc \
675
+ --hash=sha256:976c6f1975032cc327161c65d4194c549f2589d88b105a5e3499429a54479770 \
676
+ --hash=sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120 \
677
+ --hash=sha256:bb93562146120bb51e6b154962d3dadc678ed0fce96513fa6bc06599bb6f6edc \
678
+ --hash=sha256:d7d18dd34ea2e860553a579df02041845dee0af8985dff7f8661306f95504ddf \
679
+ --hash=sha256:d8b11701bc43be92ea42bd454910437b355dfb63696c06fe953ffb40b5f763b4 \
680
+ --hash=sha256:f5aa4682912a450c2db89cbd92d356fef47e115dffba07992555542f344d301b \
681
+ --hash=sha256:f8d902867b699bcd09c176a280b1acdab57f924489033e53d0afe79817da37e6 \
682
+ --hash=sha256:fd7ff459fb393358d3a155d25b275c60b07a2c83dcd7ea962b1923f5a1134569
683
+ # via
684
+ # gradio
685
+ # langgraph-sdk
686
+ # langsmith
687
+ ormsgpack==1.10.0 \
688
+ --hash=sha256:060f67fe927582f4f63a1260726d019204b72f460cf20930e6c925a1d129f373 \
689
+ --hash=sha256:10f6f3509c1b0e51b15552d314b1d409321718122e90653122ce4b997f01453a \
690
+ --hash=sha256:137aab0d5cdb6df702da950a80405eb2b7038509585e32b4e16289604ac7cb84 \
691
+ --hash=sha256:3e666cb63030538fa5cd74b1e40cb55b6fdb6e2981f024997a288bf138ebad07 \
692
+ --hash=sha256:51c1edafd5c72b863b1f875ec31c529f09c872a5ff6fe473b9dfaf188ccc3227 \
693
+ --hash=sha256:7f7a27efd67ef22d7182ec3b7fa7e9d147c3ad9be2a24656b23c989077e08b16 \
694
+ --hash=sha256:8a52c7ce7659459f3dc8dec9fd6a6c76f855a0a7e2b61f26090982ac10b95216 \
695
+ --hash=sha256:c780b44107a547a9e9327270f802fa4d6b0f6667c9c03c3338c0ce812259a0f7 \
696
+ --hash=sha256:e7058ef6092f995561bf9f71d6c9a4da867b6cc69d2e94cb80184f579a3ceed5
697
+ # via langgraph-checkpoint
698
+ packaging==25.0 \
699
+ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
700
+ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
701
+ # via
702
+ # gradio
703
+ # gradio-client
704
+ # huggingface-hub
705
+ # langchain-core
706
+ # langfuse
707
+ # langsmith
708
+ # marshmallow
709
+ pandas==2.3.2 \
710
+ --hash=sha256:21bb612d148bb5860b7eb2c10faacf1a810799245afd342cf297d7551513fbb6 \
711
+ --hash=sha256:220cc5c35ffaa764dd5bb17cf42df283b5cb7fdf49e10a7b053a06c9cb48ee2b \
712
+ --hash=sha256:42c05e15111221384019897df20c6fe893b2f697d03c811ee67ec9e0bb5a3424 \
713
+ --hash=sha256:52bc29a946304c360561974c6542d1dd628ddafa69134a7131fdfd6a5d7a1a35 \
714
+ --hash=sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb \
715
+ --hash=sha256:b62d586eb25cb8cb70a5746a378fc3194cb7f11ea77170d59f889f5dfe3cec7a \
716
+ --hash=sha256:cc03acc273c5515ab69f898df99d9d4f12c4d70dbfc24c3acc6203751d0804cf \
717
+ --hash=sha256:d25c20a03e8870f6339bcf67281b946bd20b86f1a544ebbebb87e66a8d642cba
718
+ # via
719
+ # final-assignment-agents-course
720
+ # gradio
721
+ pillow==11.3.0 \
722
+ --hash=sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e \
723
+ --hash=sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f \
724
+ --hash=sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860 \
725
+ --hash=sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523 \
726
+ --hash=sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967 \
727
+ --hash=sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae \
728
+ --hash=sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27 \
729
+ --hash=sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad \
730
+ --hash=sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0 \
731
+ --hash=sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9 \
732
+ --hash=sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f \
733
+ --hash=sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6 \
734
+ --hash=sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f \
735
+ --hash=sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe \
736
+ --hash=sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a \
737
+ --hash=sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b \
738
+ --hash=sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25 \
739
+ --hash=sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c \
740
+ --hash=sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50
741
+ # via gradio
742
+ primp==0.15.0 \
743
+ --hash=sha256:1af8ea4b15f57571ff7fc5e282a82c5eb69bc695e19b8ddeeda324397965b30a \
744
+ --hash=sha256:1b281f4ca41a0c6612d4c6e68b96e28acfe786d226a427cd944baa8d7acd644f \
745
+ --hash=sha256:489cbab55cd793ceb8f90bb7423c6ea64ebb53208ffcf7a044138e3c66d77299 \
746
+ --hash=sha256:592f6079646bdf5abbbfc3b0a28dac8de943f8907a250ce09398cda5eaebd260 \
747
+ --hash=sha256:5a728e5a05f37db6189eb413d22c78bd143fa59dd6a8a26dacd43332b3971fe8 \
748
+ --hash=sha256:6b84a6ffa083e34668ff0037221d399c24d939b5629cd38223af860de9e17a83 \
749
+ --hash=sha256:aeb6bd20b06dfc92cfe4436939c18de88a58c640752cf7f30d9e4ae893cdec32 \
750
+ --hash=sha256:c18b45c23f94016215f62d2334552224236217aaeb716871ce0e4dcfa08eb161 \
751
+ --hash=sha256:e985a9cba2e3f96a323722e5440aa9eccaac3178e74b884778e926b5249df080
752
+ # via
753
+ # ddgs
754
+ # duckduckgo-search
755
+ propcache==0.3.2 \
756
+ --hash=sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3 \
757
+ --hash=sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168 \
758
+ --hash=sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b \
759
+ --hash=sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770 \
760
+ --hash=sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3 \
761
+ --hash=sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70 \
762
+ --hash=sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0 \
763
+ --hash=sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220 \
764
+ --hash=sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50 \
765
+ --hash=sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2 \
766
+ --hash=sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb \
767
+ --hash=sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7 \
768
+ --hash=sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9 \
769
+ --hash=sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f \
770
+ --hash=sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e \
771
+ --hash=sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614 \
772
+ --hash=sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339 \
773
+ --hash=sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c
774
+ # via
775
+ # aiohttp
776
+ # yarl
777
+ proto-plus==1.26.1 \
778
+ --hash=sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66 \
779
+ --hash=sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012
780
+ # via
781
+ # google-ai-generativelanguage
782
+ # google-api-core
783
+ protobuf==6.32.0 \
784
+ --hash=sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1 \
785
+ --hash=sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c \
786
+ --hash=sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741 \
787
+ --hash=sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2 \
788
+ --hash=sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e \
789
+ --hash=sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783 \
790
+ --hash=sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0
791
+ # via
792
+ # google-ai-generativelanguage
793
+ # google-api-core
794
+ # googleapis-common-protos
795
+ # grpcio-status
796
+ # opentelemetry-proto
797
+ # proto-plus
798
+ pyasn1==0.6.1 \
799
+ --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
800
+ --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034
801
+ # via
802
+ # pyasn1-modules
803
+ # rsa
804
+ pyasn1-modules==0.4.2 \
805
+ --hash=sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a \
806
+ --hash=sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6
807
+ # via google-auth
808
+ pycparser==2.22 ; platform_python_implementation != 'PyPy' \
809
+ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
810
+ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
811
+ # via cffi
812
+ pydantic==2.11.7 \
813
+ --hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \
814
+ --hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b
815
+ # via
816
+ # assemblyai
817
+ # fastapi
818
+ # gradio
819
+ # groq
820
+ # langchain
821
+ # langchain-core
822
+ # langchain-google-genai
823
+ # langfuse
824
+ # langgraph
825
+ # langsmith
826
+ # ollama
827
+ # openai
828
+ # pydantic-settings
829
+ pydantic-core==2.33.2 \
830
+ --hash=sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d \
831
+ --hash=sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac \
832
+ --hash=sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02 \
833
+ --hash=sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22 \
834
+ --hash=sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec \
835
+ --hash=sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d \
836
+ --hash=sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052 \
837
+ --hash=sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c \
838
+ --hash=sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8 \
839
+ --hash=sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b \
840
+ --hash=sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa \
841
+ --hash=sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2 \
842
+ --hash=sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b \
843
+ --hash=sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc \
844
+ --hash=sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d \
845
+ --hash=sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808 \
846
+ --hash=sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e \
847
+ --hash=sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640 \
848
+ --hash=sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572 \
849
+ --hash=sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29 \
850
+ --hash=sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a \
851
+ --hash=sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a \
852
+ --hash=sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c
853
+ # via pydantic
854
+ pydantic-settings==2.10.1 \
855
+ --hash=sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee \
856
+ --hash=sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796
857
+ # via langchain-community
858
+ pydub==0.25.1 \
859
+ --hash=sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6 \
860
+ --hash=sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f
861
+ # via gradio
862
+ pygments==2.19.2 ; sys_platform != 'emscripten' \
863
+ --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \
864
+ --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b
865
+ # via rich
866
+ python-dateutil==2.9.0.post0 \
867
+ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
868
+ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
869
+ # via pandas
870
+ python-dotenv==1.1.1 \
871
+ --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \
872
+ --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab
873
+ # via
874
+ # final-assignment-agents-course
875
+ # pydantic-settings
876
+ python-multipart==0.0.20 \
877
+ --hash=sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104 \
878
+ --hash=sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13
879
+ # via gradio
880
+ pytz==2025.2 \
881
+ --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \
882
+ --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00
883
+ # via pandas
884
+ pyyaml==6.0.2 \
885
+ --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
886
+ --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
887
+ --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
888
+ --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
889
+ --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
890
+ --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
891
+ --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
892
+ --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
893
+ --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
894
+ --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed
895
+ # via
896
+ # gradio
897
+ # huggingface-hub
898
+ # langchain
899
+ # langchain-community
900
+ # langchain-core
901
+ regex==2025.9.1 \
902
+ --hash=sha256:113d5aa950f428faf46fd77d452df62ebb4cc6531cb619f6cc30a369d326bfbd \
903
+ --hash=sha256:1ec2bd3bdf0f73f7e9f48dca550ba7d973692d5e5e9a90ac42cc5f16c4432d8b \
904
+ --hash=sha256:4bcdff370509164b67a6c8ec23c9fb40797b72a014766fdc159bb809bd74f7d8 \
905
+ --hash=sha256:5c3b96ed0223b32dbdc53a83149b6de7ca3acd5acd9c8e64b42a166228abe29c \
906
+ --hash=sha256:67a74456f410fe5e869239ee7a5423510fe5121549af133809d9591a8075893f \
907
+ --hash=sha256:7383efdf6e8e8c61d85e00cfb2e2e18da1a621b8bfb4b0f1c2747db57b942b8f \
908
+ --hash=sha256:88ac07b38d20b54d79e704e38aa3bd2c0f8027432164226bdee201a1c0c9c9ff \
909
+ --hash=sha256:8c2ff5c01d5e47ad5fc9d31bcd61e78c2fa0068ed00cab86b7320214446da766 \
910
+ --hash=sha256:94533e32dc0065eca43912ee6649c90ea0681d59f56d43c45b5bcda9a740b3dd \
911
+ --hash=sha256:9627e887116c4e9c0986d5c3b4f52bcfe3df09850b704f62ec3cbf177a0ae374 \
912
+ --hash=sha256:a874a61bb580d48642ffd338570ee24ab13fa023779190513fcacad104a6e251 \
913
+ --hash=sha256:c5aa2a6a73bf218515484b36a0d20c6ad9dc63f6339ff6224147b0e2c095ee55 \
914
+ --hash=sha256:d49dc84e796b666181de8a9973284cad6616335f01b52bf099643253094920fc \
915
+ --hash=sha256:d9914fe1040874f83c15fcea86d94ea54091b0666eab330aaab69e30d106aabe \
916
+ --hash=sha256:e71bceb3947362ec5eabd2ca0870bb78eae4edfc60c6c21495133c01b6cd2df4 \
917
+ --hash=sha256:fcdeb38de4f7f3d69d798f4f371189061446792a84e7c92b50054c87aae9c07c
918
+ # via tiktoken
919
+ requests==2.32.5 \
920
+ --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \
921
+ --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf
922
+ # via
923
+ # arxiv
924
+ # final-assignment-agents-course
925
+ # google-api-core
926
+ # huggingface-hub
927
+ # langchain
928
+ # langchain-community
929
+ # langfuse
930
+ # langsmith
931
+ # opentelemetry-exporter-otlp-proto-http
932
+ # requests-toolbelt
933
+ # tiktoken
934
+ # wikipedia
935
+ # youtube-transcript-api
936
+ requests-toolbelt==1.0.0 \
937
+ --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \
938
+ --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06
939
+ # via langsmith
940
+ rich==14.1.0 ; sys_platform != 'emscripten' \
941
+ --hash=sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f \
942
+ --hash=sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8
943
+ # via typer
944
+ rsa==4.9.1 \
945
+ --hash=sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762 \
946
+ --hash=sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75
947
+ # via google-auth
948
+ ruff==0.12.12 ; sys_platform != 'emscripten' \
949
+ --hash=sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92 \
950
+ --hash=sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5 \
951
+ --hash=sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1 \
952
+ --hash=sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee \
953
+ --hash=sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093 \
954
+ --hash=sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45 \
955
+ --hash=sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e \
956
+ --hash=sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8 \
957
+ --hash=sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489 \
958
+ --hash=sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727 \
959
+ --hash=sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577 \
960
+ --hash=sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5 \
961
+ --hash=sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4 \
962
+ --hash=sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb \
963
+ --hash=sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e \
964
+ --hash=sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6 \
965
+ --hash=sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23 \
966
+ --hash=sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc \
967
+ --hash=sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d
968
+ # via gradio
969
+ safehttpx==0.1.6 \
970
+ --hash=sha256:407cff0b410b071623087c63dd2080c3b44dc076888d8c5823c00d1e58cb381c \
971
+ --hash=sha256:b356bfc82cee3a24c395b94a2dbeabbed60aff1aa5fa3b5fe97c4f2456ebce42
972
+ # via gradio
973
+ semantic-version==2.10.0 \
974
+ --hash=sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c \
975
+ --hash=sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177
976
+ # via gradio
977
+ sgmllib3k==1.0.0 \
978
+ --hash=sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9
979
+ # via feedparser
980
+ shellingham==1.5.4 ; sys_platform != 'emscripten' \
981
+ --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \
982
+ --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de
983
+ # via typer
984
+ six==1.17.0 \
985
+ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
986
+ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
987
+ # via
988
+ # markdownify
989
+ # python-dateutil
990
+ sniffio==1.3.1 \
991
+ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
992
+ --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
993
+ # via
994
+ # anyio
995
+ # groq
996
+ # openai
997
+ soupsieve==2.8 \
998
+ --hash=sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c \
999
+ --hash=sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f
1000
+ # via beautifulsoup4
1001
+ sqlalchemy==2.0.43 \
1002
+ --hash=sha256:022e436a1cb39b13756cf93b48ecce7aa95382b9cfacceb80a7d263129dfd019 \
1003
+ --hash=sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc \
1004
+ --hash=sha256:4bf0edb24c128b7be0c61cd17eef432e4bef507013292415f3fb7023f02b7d4b \
1005
+ --hash=sha256:70322986c0c699dca241418fcf18e637a4369e0ec50540a2b907b184c8bca069 \
1006
+ --hash=sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417 \
1007
+ --hash=sha256:87accdbba88f33efa7b592dc2e8b2a9c2cdbca73db2f9d5c510790428c09c154 \
1008
+ --hash=sha256:9c2e02f06c68092b875d5cbe4824238ab93a7fa35d9c38052c033f7ca45daa18 \
1009
+ --hash=sha256:c00e7845d2f692ebfc7d5e4ec1a3fd87698e4337d09e58d6749a16aedfdf8612 \
1010
+ --hash=sha256:c5e73ba0d76eefc82ec0219d2301cb33bfe5205ed7a2602523111e2e56ccbd20 \
1011
+ --hash=sha256:e7a903b5b45b0d9fa03ac6a331e1c1d6b7e0ab41c63b6217b3d10357b83c8b00
1012
+ # via
1013
+ # langchain
1014
+ # langchain-community
1015
+ starlette==0.47.3 \
1016
+ --hash=sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9 \
1017
+ --hash=sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51
1018
+ # via
1019
+ # fastapi
1020
+ # gradio
1021
+ tenacity==9.1.2 \
1022
+ --hash=sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb \
1023
+ --hash=sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138
1024
+ # via
1025
+ # langchain-community
1026
+ # langchain-core
1027
+ tiktoken==0.11.0 \
1028
+ --hash=sha256:10331d08b5ecf7a780b4fe4d0281328b23ab22cdb4ff65e68d56caeda9940ecc \
1029
+ --hash=sha256:195d84bec46169af3b1349a1495c151d37a0ff4cba73fd08282736be7f92cc6c \
1030
+ --hash=sha256:3c518641aee1c52247c2b97e74d8d07d780092af79d5911a6ab5e79359d9b06a \
1031
+ --hash=sha256:8a9b517d6331d7103f8bef29ef93b3cca95fa766e293147fe7bacddf310d5917 \
1032
+ --hash=sha256:b062c82300341dc87e0258c69f79bed725f87e753c21887aea90d272816be882 \
1033
+ --hash=sha256:b4ddb1849e6bf0afa6cc1c5d809fb980ca240a5fffe585a04e119519758788c0 \
1034
+ --hash=sha256:fe91581b0ecdd8783ce8cb6e3178f2260a3912e8724d2f2d49552b98714641a1
1035
+ # via langchain-openai
1036
+ tokenizers==0.22.0 \
1037
+ --hash=sha256:1626cb186e143720c62c6c6b5371e62bbc10af60481388c0da89bc903f37ea0c \
1038
+ --hash=sha256:2e33b98525be8453f355927f3cab312c36cd3e44f4d7e9e97da2fa94d0a49dcb \
1039
+ --hash=sha256:4136e1558a9ef2e2f1de1555dcd573e1cbc4a320c1a06c4107a3d46dc8ac6e4b \
1040
+ --hash=sha256:71784b9ab5bf0ff3075bceeb198149d2c5e068549c0d18fe32d06ba0deb63f79 \
1041
+ --hash=sha256:76cf6757c73a10ef10bf06fa937c0ec7393d90432f543f49adc8cab3fb6f26cb \
1042
+ --hash=sha256:790bad50a1b59d4c21592f9c3cf5e5cf9c3c7ce7e1a23a739f13e01fb1be377a \
1043
+ --hash=sha256:8337ca75d0731fc4860e6204cc24bb36a67d9736142aa06ed320943b50b1e7ed \
1044
+ --hash=sha256:a89264e26f63c449d8cded9061adea7b5de53ba2346fc7e87311f7e4117c1cc8 \
1045
+ --hash=sha256:c78174859eeaee96021f248a56c801e36bfb6bd5b067f2e95aa82445ca324f00 \
1046
+ --hash=sha256:cdf5954de3962a5fd9781dc12048d24a1a6f1f5df038c6e95db328cd22964206 \
1047
+ --hash=sha256:da589a61cbfea18ae267723d6b029b84598dc8ca78db9951d8f5beff72d8507c \
1048
+ --hash=sha256:dbf9d6851bddae3e046fedfb166f47743c1c7bd11c640f0691dd35ef0bcad3be \
1049
+ --hash=sha256:ea8562fa7498850d02a16178105b58803ea825b50dc9094d60549a7ed63654bb \
1050
+ --hash=sha256:eaa9620122a3fb99b943f864af95ed14c8dfc0f47afa3b404ac8c16b3f2bb484 \
1051
+ --hash=sha256:ec5b71f668a8076802b0241a42387d48289f25435b86b769ae1837cad4172a17
1052
+ # via langchain-huggingface
1053
+ tomlkit==0.13.3 \
1054
+ --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \
1055
+ --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0
1056
+ # via gradio
1057
+ tqdm==4.67.1 \
1058
+ --hash=sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2 \
1059
+ --hash=sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2
1060
+ # via
1061
+ # huggingface-hub
1062
+ # openai
1063
+ typer==0.17.3 ; sys_platform != 'emscripten' \
1064
+ --hash=sha256:0c600503d472bcf98d29914d4dcd67f80c24cc245395e2e00ba3603c9332e8ba \
1065
+ --hash=sha256:643919a79182ab7ac7581056d93c6a2b865b026adf2872c4d02c72758e6f095b
1066
+ # via gradio
1067
+ typing-extensions==4.15.0 \
1068
+ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
1069
+ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
1070
+ # via
1071
+ # aiosignal
1072
+ # anyio
1073
+ # assemblyai
1074
+ # beautifulsoup4
1075
+ # exceptiongroup
1076
+ # fastapi
1077
+ # gradio
1078
+ # gradio-client
1079
+ # groq
1080
+ # huggingface-hub
1081
+ # langchain-core
1082
+ # multidict
1083
+ # openai
1084
+ # opentelemetry-api
1085
+ # opentelemetry-exporter-otlp-proto-http
1086
+ # opentelemetry-sdk
1087
+ # opentelemetry-semantic-conventions
1088
+ # pydantic
1089
+ # pydantic-core
1090
+ # sqlalchemy
1091
+ # starlette
1092
+ # typer
1093
+ # typing-inspect
1094
+ # typing-inspection
1095
+ # uvicorn
1096
+ typing-inspect==0.9.0 \
1097
+ --hash=sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f \
1098
+ --hash=sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78
1099
+ # via dataclasses-json
1100
+ typing-inspection==0.4.1 \
1101
+ --hash=sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51 \
1102
+ --hash=sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28
1103
+ # via
1104
+ # pydantic
1105
+ # pydantic-settings
1106
+ tzdata==2025.2 \
1107
+ --hash=sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8 \
1108
+ --hash=sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9
1109
+ # via pandas
1110
+ urllib3==2.5.0 \
1111
+ --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
1112
+ --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
1113
+ # via
1114
+ # gradio
1115
+ # requests
1116
+ uvicorn==0.35.0 ; sys_platform != 'emscripten' \
1117
+ --hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \
1118
+ --hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01
1119
+ # via gradio
1120
+ websockets==15.0.1 \
1121
+ --hash=sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9 \
1122
+ --hash=sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3 \
1123
+ --hash=sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e \
1124
+ --hash=sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1 \
1125
+ --hash=sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256 \
1126
+ --hash=sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41 \
1127
+ --hash=sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c \
1128
+ --hash=sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf \
1129
+ --hash=sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a \
1130
+ --hash=sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d \
1131
+ --hash=sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475 \
1132
+ --hash=sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee \
1133
+ --hash=sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb \
1134
+ --hash=sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205 \
1135
+ --hash=sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04 \
1136
+ --hash=sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122 \
1137
+ --hash=sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b \
1138
+ --hash=sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9 \
1139
+ --hash=sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f
1140
+ # via
1141
+ # assemblyai
1142
+ # gradio-client
1143
+ wikipedia==1.4.0 \
1144
+ --hash=sha256:db0fad1829fdd441b1852306e9856398204dc0786d2996dd2e0c8bb8e26133b2
1145
+ # via final-assignment-agents-course
1146
+ wrapt==1.17.3 \
1147
+ --hash=sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05 \
1148
+ --hash=sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd \
1149
+ --hash=sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22 \
1150
+ --hash=sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04 \
1151
+ --hash=sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390 \
1152
+ --hash=sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18 \
1153
+ --hash=sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6 \
1154
+ --hash=sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2 \
1155
+ --hash=sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418 \
1156
+ --hash=sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0 \
1157
+ --hash=sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775 \
1158
+ --hash=sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c
1159
+ # via langfuse
1160
+ xlrd==2.0.2 \
1161
+ --hash=sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9 \
1162
+ --hash=sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9
1163
+ # via final-assignment-agents-course
1164
+ xxhash==3.5.0 \
1165
+ --hash=sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415 \
1166
+ --hash=sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c \
1167
+ --hash=sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520 \
1168
+ --hash=sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c \
1169
+ --hash=sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482 \
1170
+ --hash=sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6 \
1171
+ --hash=sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da \
1172
+ --hash=sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23 \
1173
+ --hash=sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b \
1174
+ --hash=sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da \
1175
+ --hash=sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680 \
1176
+ --hash=sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da \
1177
+ --hash=sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f \
1178
+ --hash=sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6 \
1179
+ --hash=sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198 \
1180
+ --hash=sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9 \
1181
+ --hash=sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442 \
1182
+ --hash=sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196 \
1183
+ --hash=sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296 \
1184
+ --hash=sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212 \
1185
+ --hash=sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986
1186
+ # via langgraph
1187
+ yarl==1.20.1 \
1188
+ --hash=sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed \
1189
+ --hash=sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23 \
1190
+ --hash=sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e \
1191
+ --hash=sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a \
1192
+ --hash=sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2 \
1193
+ --hash=sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73 \
1194
+ --hash=sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309 \
1195
+ --hash=sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4 \
1196
+ --hash=sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e \
1197
+ --hash=sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8 \
1198
+ --hash=sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13 \
1199
+ --hash=sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30 \
1200
+ --hash=sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77 \
1201
+ --hash=sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16 \
1202
+ --hash=sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb \
1203
+ --hash=sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8 \
1204
+ --hash=sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac \
1205
+ --hash=sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70 \
1206
+ --hash=sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24
1207
+ # via aiohttp
1208
+ youtube-transcript-api==1.2.2 \
1209
+ --hash=sha256:5f67cfaff3621d969778817a3d7b2172c16784855f45fcaed4f0529632e2fef4 \
1210
+ --hash=sha256:feca8c7f7c9d65188ef6377fc0e01cf466e6b68f1b3e648019646ab342f994d2
1211
+ # via final-assignment-agents-course
1212
+ zipp==3.23.0 \
1213
+ --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
1214
+ --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
1215
+ # via importlib-metadata
1216
+ zstandard==0.24.0 \
1217
+ --hash=sha256:0c9c3cba57f5792532a3df3f895980d47d78eda94b0e5b800651b53e96e0b604 \
1218
+ --hash=sha256:0f6d9a146e07458cb41423ca2d783aefe3a3a97fe72838973c13b8f1ecc7343a \
1219
+ --hash=sha256:35f13501a8accf834457d8e40e744568287a215818778bc4d79337af2f3f0d97 \
1220
+ --hash=sha256:388aad2d693707f4a0f6cc687eb457b33303d6b57ecf212c8ff4468c34426892 \
1221
+ --hash=sha256:561123d05681197c0e24eb8ab3cfdaf299e2b59c293d19dad96e1610ccd8fbc6 \
1222
+ --hash=sha256:5e941654cef13a1d53634ec30933722eda11f44f99e1d0bc62bbce3387580d50 \
1223
+ --hash=sha256:7ac6e4d727521d86d20ec291a3f4e64a478e8a73eaee80af8f38ec403e77a409 \
1224
+ --hash=sha256:7de5869e616d426b56809be7dc6dba4d37b95b90411ccd3de47f421a42d4d42c \
1225
+ --hash=sha256:869bf13f66b124b13be37dd6e08e4b728948ff9735308694e0b0479119e08ea7 \
1226
+ --hash=sha256:87ae1684bc3c02d5c35884b3726525eda85307073dbefe68c3c779e104a59036 \
1227
+ --hash=sha256:92be52ca4e6e604f03d5daa079caec9e04ab4cbf6972b995aaebb877d3d24e13 \
1228
+ --hash=sha256:962ea3aecedcc944f8034812e23d7200d52c6e32765b8da396eeb8b8ffca71ce \
1229
+ --hash=sha256:af1394c2c5febc44e0bbf0fc6428263fa928b50d1b1982ce1d870dc793a8e5f4 \
1230
+ --hash=sha256:bf02f915fa7934ea5dfc8d96757729c99a8868b7c340b97704795d6413cf5fe6 \
1231
+ --hash=sha256:d6975f2d903bc354916a17b91a7aaac7299603f9ecdb788145060dde6e573a16 \
1232
+ --hash=sha256:dd91b0134a32dfcd8be504e8e46de44ad0045a569efc25101f2a12ccd41b5759 \
1233
+ --hash=sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f
1234
+ # via langsmith
tools.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import math
3
+ import os
4
+ import re
5
+ from typing import Optional
6
+
7
+ import pandas as pd
8
+ import requests
9
+ from dotenv import load_dotenv
10
+ from langchain_core.messages import HumanMessage
11
+ from langchain_core.tools import tool
12
+ from langchain_google_genai import ChatGoogleGenerativeAI
13
+ from markdownify import markdownify
14
+ from requests.exceptions import RequestException
15
+
16
+ # Built-in LangChain tools
17
+ from langchain_community.tools import (
18
+ WikipediaQueryRun,
19
+ DuckDuckGoSearchRun,
20
+ ArxivQueryRun,
21
+ ShellTool,
22
+ )
23
+ from langchain_community.utilities import (
24
+ WikipediaAPIWrapper,
25
+ DuckDuckGoSearchAPIWrapper,
26
+ ArxivAPIWrapper,
27
+ )
28
+ from langchain_experimental.tools import PythonREPLTool
29
+ from langchain_community.document_loaders import AssemblyAIAudioTranscriptLoader
30
+ from langchain_community.document_loaders.assemblyai import TranscriptFormat
31
+
32
+ # Youtube related tools
33
+ from youtube_transcript import (
34
+ get_youtube_transcript_tool,
35
+ get_youtube_title_description_tool,
36
+ )
37
+
38
+ load_dotenv()
39
+
40
+ # Initialize vision_llm at module level (commented out by default)
41
+ # Uncomment and configure as needed
42
+ # vision_llm = ChatOllama(
43
+ # model="qwen2-vl:7b",
44
+ # base_url="http://localhost:11434"
45
+ # )
46
+ vision_llm = ChatGoogleGenerativeAI(model=os.getenv("GOOGLE_VISION_MODEL"))
47
+
48
+ # ============== CUSTOM TOOLS (not available in LangChain) ==============
49
+
50
+
51
+ @tool
52
+ def reverse_text(text: str) -> str:
53
+ """
54
+ Reverse the given text character by character.
55
+
56
+ Example:
57
+ Input: "hello"
58
+ Output: "olleh"
59
+ """
60
+ return text[::-1]
61
+
62
+
63
+ @tool
64
+ def reverse_words(text: str) -> str:
65
+ """
66
+ Reverse the order of words in the given text.
67
+
68
+ Example:
69
+ Input: "LangChain makes graphs easy"
70
+ Output: "easy graphs makes LangChain"
71
+ """
72
+ return " ".join(text.split()[::-1])
73
+
74
+
75
+ @tool
76
+ def calculator(expression: str) -> str:
77
+ """
78
+ Perform mathematical calculations safely. Supports basic arithmetic operations.
79
+
80
+ Example:
81
+ Input: "10 + 2 * 3 - 4 / 2"
82
+ Output: "14.0"
83
+ """
84
+ try:
85
+ # Safe evaluation - only allow basic math operations
86
+ allowed_chars = set("0123456789+-*/.() ")
87
+ if all(c in allowed_chars for c in expression):
88
+ result = eval(expression)
89
+ return str(result)
90
+ else:
91
+ return "Error: Invalid characters in expression"
92
+ except Exception as e:
93
+ return f"Calculation error: {str(e)}"
94
+
95
+
96
+ @tool
97
+ def advanced_math(operation: str, num1: float, num2: Optional[float] = None) -> str:
98
+ """
99
+ Perform advanced math operations.
100
+ Supported: sqrt, log, sin, cos, tan, power.
101
+
102
+ Examples:
103
+ Input: operation="sqrt", num1=16
104
+ Output: "4.0"
105
+
106
+ Input: operation="power", num1=2, num2=3
107
+ Output: "8.0"
108
+ """
109
+ try:
110
+ if operation == "sqrt":
111
+ return str(math.sqrt(num1))
112
+ elif operation == "log":
113
+ return str(math.log(num1))
114
+ elif operation == "sin":
115
+ return str(math.sin(num1))
116
+ elif operation == "cos":
117
+ return str(math.cos(num1))
118
+ elif operation == "tan":
119
+ return str(math.tan(num1))
120
+ elif operation == "power":
121
+ if num2 is None:
122
+ return "power operation requires two numbers"
123
+ return str(math.pow(num1, num2))
124
+ else:
125
+ return f"Unknown operation: {operation}"
126
+ except Exception as e:
127
+ return f"Math error: {str(e)}"
128
+
129
+
130
+ # @tool
131
+ # def extract_text_from_image(img_path: str) -> str:
132
+ # """Extract text from an image file using a multimodal model."""
133
+ # try:
134
+ # if "vision_llm" not in globals():
135
+ # return "Error: Vision LLM not configured. Please uncomment and configure vision_llm."
136
+
137
+ # with open(img_path, "rb") as image_file:
138
+ # image_bytes = image_file.read()
139
+
140
+ # image_base64 = base64.b64encode(image_bytes).decode("utf-8")
141
+
142
+ # message = [
143
+ # HumanMessage(
144
+ # content=[
145
+ # {
146
+ # "type": "text",
147
+ # "text": "Extract all the text from this image. Return only the extracted text, no explanations.",
148
+ # },
149
+ # {
150
+ # "type": "image_url",
151
+ # "image_url": {"url": f"data:image/png;base64,{image_base64}"},
152
+ # },
153
+ # ]
154
+ # )
155
+ # ]
156
+
157
+ # response_content = vision_llm.invoke(message).content.strip()
158
+ # return response_content
159
+ # except Exception as e:
160
+ # return f"Multimodal text extraction error: {str(e)}"
161
+
162
+
163
+ @tool
164
+ def ask_question_on_image_content(question: str, img_path: str) -> str:
165
+ """Ask specific question on image content."""
166
+ try:
167
+ if "vision_llm" not in globals():
168
+ return "Error: Vision LLM not configured. Please uncomment and configure vision_llm."
169
+
170
+ with open(img_path, "rb") as image_file:
171
+ image_bytes = image_file.read()
172
+
173
+ image_base64 = base64.b64encode(image_bytes).decode("utf-8")
174
+
175
+ message = [
176
+ HumanMessage(
177
+ content=[
178
+ {
179
+ "type": "text",
180
+ "text": question,
181
+ },
182
+ {
183
+ "type": "image_url",
184
+ "image_url": {"url": f"data:image/png;base64,{image_base64}"},
185
+ },
186
+ ]
187
+ )
188
+ ]
189
+
190
+ response_content = vision_llm.invoke(message).content.strip()
191
+ return response_content
192
+ except Exception as e:
193
+ return f"Multimodal text extraction error: {str(e)}"
194
+
195
+
196
+ @tool
197
+ def read_excel_file(file_path: str, sheet_name: Optional[str] = None) -> str:
198
+ """
199
+ Read an Excel file and return its content.
200
+ """
201
+ try:
202
+ if sheet_name:
203
+ json_str = pd.read_excel(file_path, sheet_name=sheet_name).to_json(
204
+ orient="records", date_format="iso", force_ascii=False
205
+ )
206
+ else:
207
+ json_str = pd.read_excel(file_path).to_json(
208
+ orient="records", date_format="iso", force_ascii=False
209
+ )
210
+ return json_str
211
+ except Exception as e:
212
+ return f"Excel reading error: {str(e)}"
213
+
214
+
215
+ @tool
216
+ def visit_webpage(url: str) -> str:
217
+ """
218
+ Visits a webpage at the given URL and returns its content as a markdown string.
219
+ Use this to browse and extract readable content from webpages.
220
+ """
221
+ try:
222
+ response = requests.get(url, timeout=20)
223
+ response.raise_for_status()
224
+ markdown_content = markdownify(response.text).strip()
225
+ markdown_content = re.sub(r"\n{3,}", "\n\n", markdown_content)
226
+ MAX_LEN = 40000
227
+ if len(markdown_content) > MAX_LEN:
228
+ return (
229
+ markdown_content[: MAX_LEN // 2]
230
+ + f"\n\n...[Content truncated to {MAX_LEN} chars]...\n\n"
231
+ + markdown_content[-MAX_LEN // 2 :]
232
+ )
233
+ return markdown_content
234
+ except requests.exceptions.Timeout:
235
+ return "Timeout while trying to access the webpage."
236
+ except RequestException as e:
237
+ return f"Request error: {str(e)}"
238
+ except Exception as e:
239
+ return f"Unexpected error: {str(e)}"
240
+
241
+
242
+ @tool
243
+ def transcribe_mp3(
244
+ file_path: str,
245
+ ) -> str:
246
+ """
247
+ Transcribe an MP3 (local path or URL) using AssemblyAI and return the result.
248
+ """
249
+ try:
250
+ tfmt = getattr(TranscriptFormat, "TEXT", TranscriptFormat.TEXT)
251
+
252
+ loader = AssemblyAIAudioTranscriptLoader(
253
+ file_path=file_path,
254
+ transcript_format=tfmt,
255
+ )
256
+ docs = loader.load()
257
+
258
+ # Concatenate documents
259
+ text = "\n\n".join(d.page_content for d in docs)
260
+ MAX = 40000
261
+ if len(text) > MAX:
262
+ return text[:MAX] + f"\n\n...[truncated {len(text) - MAX} chars]..."
263
+ return text
264
+ except Exception as e:
265
+ return f"Transcription error: {str(e)}"
266
+
267
+
268
+ def build_tools():
269
+ """
270
+ Initialize and return a list of built-in and custom LangChain tools.
271
+ """
272
+ # Initialize built-in LangChain tools
273
+ wikipedia_tool = WikipediaQueryRun(
274
+ api_wrapper=WikipediaAPIWrapper(doc_content_chars_max=2000)
275
+ )
276
+ duckduckgo_search = DuckDuckGoSearchRun(
277
+ api_wrapper=DuckDuckGoSearchAPIWrapper(max_results=15)
278
+ )
279
+ arxiv_tool = ArxivQueryRun(api_wrapper=ArxivAPIWrapper())
280
+ python_repl = PythonREPLTool()
281
+ shell_tool = ShellTool()
282
+
283
+ # Combine built-in tools with custom tools
284
+ all_tools = [
285
+ # Built-in LangChain tools
286
+ wikipedia_tool,
287
+ duckduckgo_search,
288
+ arxiv_tool,
289
+ python_repl,
290
+ shell_tool,
291
+ # Custom tools for specialized tasks
292
+ visit_webpage,
293
+ read_excel_file,
294
+ get_youtube_transcript_tool,
295
+ get_youtube_title_description_tool,
296
+ reverse_text,
297
+ reverse_words,
298
+ calculator,
299
+ advanced_math,
300
+ ask_question_on_image_content,
301
+ transcribe_mp3,
302
+ ]
303
+ return all_tools
304
+
305
+
306
+ if __name__ == "__main__":
307
+ from pprint import pprint
308
+
309
+ print("\n--- reverse_text ---")
310
+ pprint(reverse_text.invoke({"text": "hello"}))
311
+
312
+ print("\n--- reverse_words ---")
313
+ pprint(reverse_words.invoke({"text": "LangChain makes graphs easy"}))
314
+
315
+ print("\n--- calculator ---")
316
+ pprint(calculator.invoke({"expression": "10 + 2 * 3 - 4 / 2"}))
317
+
318
+ print("\n--- advanced_math ---")
319
+ pprint(advanced_math.invoke({"operation": "sqrt", "num1": 16}))
320
+ pprint(advanced_math.invoke({"operation": "power", "num1": 2, "num2": 3}))
321
+
322
+ print("\n--- read_excel_file ---")
323
+ pprint(read_excel_file.invoke({"file_path": "examples/file_example_XLS_10.xls"}))
324
+ pprint(
325
+ read_excel_file.invoke(
326
+ {"file_path": "examples/file_example_XLS_10.xls", "sheet_name": "Sheet2"}
327
+ )
328
+ )
329
+
330
+ print("\n--- visit_webpage ---")
331
+ result = visit_webpage.invoke({"url": "https://example.com"})
332
+ print(result[:200] + "...\n") # tronqué pour affichage
333
+
334
+ # print("\n--- extract_text_from_image ---")
335
+ # pprint(extract_text_from_image.invoke({"img_path": "examples/receipt.png"}))
336
+
337
+ print("\n--- ask_question_on_image_content ---")
338
+ pprint(
339
+ ask_question_on_image_content.invoke(
340
+ {
341
+ "question": "How many apples are in the basket?",
342
+ "img_path": "examples/apples.png",
343
+ }
344
+ )
345
+ )
346
+
347
+ print("\n--- transcribe_mp3 ---")
348
+ pprint(transcribe_mp3.invoke({"file_path": "./examples/sample.mp3"}))
uv.lock ADDED
The diff for this file is too large to render. See raw diff
 
youtube_transcript.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+ from langchain_core.tools import tool
6
+ from markdownify import markdownify as md
7
+ from youtube_transcript_api import YouTubeTranscriptApi
8
+ from youtube_transcript_api._errors import (
9
+ TranscriptsDisabled,
10
+ NoTranscriptFound,
11
+ VideoUnavailable,
12
+ )
13
+
14
+ # ---- Config -----------------------------------------------------------------
15
+
16
+ DEFAULT_TIMEOUT = 30
17
+ DEFAULT_HEADERS = {
18
+ # Helps avoid consent/anti-bot interstitials on some sites
19
+ "User-Agent": (
20
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
21
+ "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
22
+ )
23
+ }
24
+
25
+
26
+ # ---- Small helpers ----------------------------------------------------------
27
+
28
+
29
+ def _fetch_html(url: str, *, timeout: int = DEFAULT_TIMEOUT) -> str:
30
+ """Download raw HTML for a URL or raise on HTTP errors."""
31
+ resp = requests.get(url, headers=DEFAULT_HEADERS, timeout=timeout)
32
+ resp.raise_for_status()
33
+ return resp.text
34
+
35
+
36
+ def _html_to_markdown(html: str) -> str:
37
+ """Convert HTML to clean Markdown (scripts/styles removed)."""
38
+ soup = BeautifulSoup(html, "html.parser")
39
+ for tag in soup(["script", "style"]):
40
+ tag.extract()
41
+ return md(str(soup), strip=["script", "style"], heading_style="ATX").strip()
42
+
43
+
44
+ # ---- Pure functions (safe to call directly in Python) -----------------------
45
+
46
+
47
+ def fetch_webpage(url: str, convert_to_markdown: bool = True) -> str:
48
+ """
49
+ Fetch a web page and return Markdown or raw HTML.
50
+ Keep it simple: HTML -> (optional) Markdown, nothing else.
51
+ """
52
+ html = _fetch_html(url)
53
+ return _html_to_markdown(html) if convert_to_markdown else html
54
+
55
+
56
+ def get_youtube_transcript(video_id: str) -> str:
57
+ """
58
+ Return YouTube transcript text for a given video ID.
59
+ One line per chunk; raises a clear error if transcript is unavailable.
60
+
61
+ Example of video_id:
62
+ For youtube video: https://www.youtube.com/watch?v=1htKBjuUWec
63
+ The video id is: dQw4w9WgXcQ
64
+ """
65
+ try:
66
+ # Initialize the YouTubeTranscriptApi
67
+ ytt_api = YouTubeTranscriptApi()
68
+ fetched_transcript = ytt_api.fetch(video_id)
69
+ raw_data = fetched_transcript.to_raw_data()
70
+ # raw data is in the form of [{ 'text': 'Hey there', 'start': 0.0, 'duration': 1.54 }, { 'text': 'how are you',, 'start': 1.54, 'duration': 4.16 }, ... ] we will return ony the text element as lines
71
+ transcript = "\n".join([item["text"] for item in raw_data])
72
+ return transcript
73
+ except (TranscriptsDisabled, NoTranscriptFound, VideoUnavailable) as e:
74
+ raise RuntimeError(f"Transcript unavailable: {e}") from e
75
+
76
+
77
+ def get_youtube_title_description(video_url: str) -> str:
78
+ """
79
+ get_youtube title and description from youtube url (ex: https://www.youtube.com/watch?v=1htKBjuUWec)
80
+
81
+ Extract YouTube title + description from Open Graph meta tags.
82
+ Falls back to standard <meta name="title"/"description"> if needed.
83
+ """
84
+ html = _fetch_html(video_url)
85
+ soup = BeautifulSoup(html, "html.parser")
86
+
87
+ title_tag = soup.find("meta", property="og:title") or soup.find(
88
+ "meta", attrs={"name": "title"}
89
+ )
90
+ desc_tag = soup.find("meta", property="og:description") or soup.find(
91
+ "meta", attrs={"name": "description"}
92
+ )
93
+
94
+ title = (title_tag.get("content") if title_tag else None) or "No title found"
95
+ description = (
96
+ desc_tag.get("content") if desc_tag else None
97
+ ) or "No description found"
98
+
99
+ return f"Title: {title}\nDescription: {description}"
100
+
101
+
102
+ # ---- LangChain tool wrappers (for agents; call with .invoke) ----------------
103
+
104
+
105
+ @tool("fetch_webpage")
106
+ def fetch_webpage_tool(url: str, convert_to_markdown: bool = True) -> str:
107
+ """Tool: fetch a web page and return Markdown or raw HTML."""
108
+ return fetch_webpage(url, convert_to_markdown)
109
+
110
+
111
+ @tool("get_youtube_transcript")
112
+ def get_youtube_transcript_tool(video_id: str) -> str:
113
+ """Tool: return YouTube transcript text for a video ID."""
114
+ return get_youtube_transcript(video_id)
115
+
116
+
117
+ @tool("get_youtube_title_description")
118
+ def get_youtube_title_description_tool(video_url: str) -> str:
119
+ """Tool: return YouTube title + description for a video URL."""
120
+ return get_youtube_title_description(video_url)
121
+
122
+
123
+ # ---- Minimal demo -----------------------------------------------------------
124
+
125
+ if __name__ == "__main__":
126
+ video_id = "1htKBjuUWec"
127
+ url = f"https://www.youtube.com/watch?v={video_id}"
128
+
129
+ print(get_youtube_title_description(url))
130
+ try:
131
+ print("\n--- Transcript (first 500 chars) ---")
132
+ tx = get_youtube_transcript(video_id)
133
+ print(tx[:500] + ("..." if len(tx) > 500 else ""))
134
+ except Exception as e:
135
+ print(f"Transcript error: {e}")