actualbrain commited on
Commit
3c0a133
·
1 Parent(s): 81917a3

score-45, gpt-4.1

Browse files
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ .env
2
+ .lock
3
+
4
+ __pycache__/
5
+ .venv/
6
+
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
app.py CHANGED
@@ -1,34 +1,48 @@
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.")
@@ -38,13 +52,13 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
38
  questions_url = f"{api_url}/questions"
39
  submit_url = f"{api_url}/submit"
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
47
- # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
48
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
49
  print(agent_code)
50
 
@@ -55,16 +69,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
@@ -80,22 +94,41 @@ def run_and_submit_all( profile: gr.OAuthProfile | 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
-
99
  # 5. Submit
100
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
101
  try:
@@ -162,20 +195,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 +215,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
  import gradio as gr
3
  import requests
 
4
  import pandas as pd
5
+ from dotenv import load_dotenv
6
+ from researchgraph.graph import researchgraph
7
 
8
  # (Keep Constants as is)
9
  # --- Constants ---
10
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
 
12
+ ENV_FILE = ".env"
13
+ if os.path.exists(ENV_FILE):
14
+ load_dotenv(ENV_FILE)
15
+
16
+
17
  # --- Basic Agent Definition ---
18
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
19
  class BasicAgent:
20
  def __init__(self):
21
  print("BasicAgent initialized.")
22
+
23
+ async def __call__(self, question: str, task_id: str) -> str:
24
  print(f"Agent received question (first 50 chars): {question[:50]}...")
 
 
 
25
 
26
+ research_result = await researchgraph.ainvoke(
27
+ {"question": question, "task_id": task_id}
28
+ )
29
+ result = research_result.get("info", {})
30
+ answer = result.get("result", "No answer found.")
31
+
32
+ print(f"Agent returning answer: {answer}")
33
+ return answer
34
+
35
+
36
+ async def run_and_submit_all(profile: gr.OAuthProfile | None):
37
  """
38
  Fetches all questions, runs the BasicAgent on them, submits all answers,
39
  and displays the results.
40
  """
41
  # --- Determine HF Space Runtime URL and Repo URL ---
42
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
43
 
44
  if profile:
45
+ username = f"{profile.username}"
46
  print(f"User logged in: {username}")
47
  else:
48
  print("User not logged in.")
 
52
  questions_url = f"{api_url}/questions"
53
  submit_url = f"{api_url}/submit"
54
 
55
+ # 1. Instantiate Agent
56
  try:
57
  agent = BasicAgent()
58
  except Exception as e:
59
  print(f"Error instantiating agent: {e}")
60
  return f"Error initializing agent: {e}", None
61
+
62
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
63
  print(agent_code)
64
 
 
69
  response.raise_for_status()
70
  questions_data = response.json()
71
  if not questions_data:
72
+ print("Fetched questions list is empty.")
73
+ return "Fetched questions list is empty or invalid format.", None
74
  print(f"Fetched {len(questions_data)} questions.")
75
  except requests.exceptions.RequestException as e:
76
  print(f"Error fetching questions: {e}")
77
  return f"Error fetching questions: {e}", None
78
  except requests.exceptions.JSONDecodeError as e:
79
+ print(f"Error decoding JSON response from questions endpoint: {e}")
80
+ print(f"Response text: {response.text[:500]}")
81
+ return f"Error decoding server response for questions: {e}", None
82
  except Exception as e:
83
  print(f"An unexpected error occurred fetching questions: {e}")
84
  return f"An unexpected error occurred fetching questions: {e}", None
 
94
  print(f"Skipping item with missing task_id or question: {item}")
95
  continue
96
  try:
97
+ submitted_answer = await agent(question_text, task_id)
98
+ answers_payload.append(
99
+ {"task_id": task_id, "submitted_answer": submitted_answer}
100
+ )
101
+ results_log.append(
102
+ {
103
+ "Task ID": task_id,
104
+ "Question": question_text,
105
+ "Submitted Answer": submitted_answer,
106
+ }
107
+ )
108
  except Exception as e:
109
+ print(f"Error running agent on task {task_id}: {e}")
110
+ results_log.append(
111
+ {
112
+ "Task ID": task_id,
113
+ "Question": question_text,
114
+ "Submitted Answer": f"AGENT ERROR: {e}",
115
+ }
116
+ )
117
+
118
+ # ... rest of function remains the same ...
119
+ # 4. Prepare Submission
120
+ submission_data = {
121
+ "username": username.strip(),
122
+ "agent_code": agent_code,
123
+ "answers": answers_payload,
124
+ }
125
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
126
+ print(status_update)
127
 
128
  if not answers_payload:
129
  print("Agent did not produce any answers to submit.")
130
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
131
 
 
 
 
 
 
132
  # 5. Submit
133
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
134
  try:
 
195
 
196
  run_button = gr.Button("Run Evaluation & Submit All Answers")
197
 
198
+ status_output = gr.Textbox(
199
+ label="Run Status / Submission Result", lines=5, interactive=False
200
+ )
201
  # Removed max_rows=10 from DataFrame constructor
202
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
203
 
204
+ run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
 
 
 
205
 
206
  if __name__ == "__main__":
207
+ print("\n" + "-" * 30 + " App Starting " + "-" * 30)
208
  # Check for SPACE_HOST and SPACE_ID at startup for information
209
  space_host_startup = os.getenv("SPACE_HOST")
210
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
211
 
212
  if space_host_startup:
213
  print(f"✅ SPACE_HOST found: {space_host_startup}")
 
215
  else:
216
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
217
 
218
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
219
  print(f"✅ SPACE_ID found: {space_id_startup}")
220
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
221
+ print(
222
+ f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main"
223
+ )
224
  else:
225
+ print(
226
+ "ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined."
227
+ )
228
 
229
+ print("-" * (60 + len(" App Starting ")) + "\n")
230
 
231
  print("Launching Gradio Interface for Basic Agent Evaluation...")
232
+ demo.queue().launch(debug=True, share=False) # Added queue() for async support
pyproject.toml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "agent-course-final-assesment"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "aiohttp>=3.11.18",
9
+ "gradio[oauth]>=5.26.0",
10
+ "langchain>=0.3.24",
11
+ "langchain-community>=0.3.22",
12
+ "langchain-core>=0.3.55",
13
+ "langchain-openai>=0.3.14",
14
+ "langgraph>=0.3.34",
15
+ ]
requirements.txt CHANGED
@@ -1,2 +1,305 @@
1
- gradio
2
- requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv pip compile pyproject.toml --output-file requirements.txt
3
+ aiofiles==24.1.0
4
+ # via gradio
5
+ aiohappyeyeballs==2.6.1
6
+ # via aiohttp
7
+ aiohttp==3.11.18
8
+ # via
9
+ # agent-course-final-assesment (pyproject.toml)
10
+ # langchain-community
11
+ aiosignal==1.3.2
12
+ # via aiohttp
13
+ annotated-types==0.7.0
14
+ # via pydantic
15
+ anyio==4.9.0
16
+ # via
17
+ # gradio
18
+ # httpx
19
+ # openai
20
+ # starlette
21
+ attrs==25.3.0
22
+ # via aiohttp
23
+ authlib==1.5.2
24
+ # via gradio
25
+ certifi==2025.1.31
26
+ # via
27
+ # httpcore
28
+ # httpx
29
+ # requests
30
+ cffi==1.17.1
31
+ # via cryptography
32
+ charset-normalizer==3.4.1
33
+ # via requests
34
+ click==8.1.8
35
+ # via
36
+ # typer
37
+ # uvicorn
38
+ colorama==0.4.6
39
+ # via
40
+ # click
41
+ # tqdm
42
+ cryptography==44.0.2
43
+ # via authlib
44
+ dataclasses-json==0.6.7
45
+ # via langchain-community
46
+ distro==1.9.0
47
+ # via openai
48
+ fastapi==0.115.12
49
+ # via gradio
50
+ ffmpy==0.5.0
51
+ # via gradio
52
+ filelock==3.18.0
53
+ # via huggingface-hub
54
+ frozenlist==1.6.0
55
+ # via
56
+ # aiohttp
57
+ # aiosignal
58
+ fsspec==2025.3.2
59
+ # via
60
+ # gradio-client
61
+ # huggingface-hub
62
+ gradio==5.26.0
63
+ # via agent-course-final-assesment (pyproject.toml)
64
+ gradio-client==1.9.0
65
+ # via gradio
66
+ greenlet==3.2.1
67
+ # via sqlalchemy
68
+ groovy==0.1.2
69
+ # via gradio
70
+ h11==0.14.0
71
+ # via
72
+ # httpcore
73
+ # uvicorn
74
+ httpcore==1.0.8
75
+ # via httpx
76
+ httpx==0.28.1
77
+ # via
78
+ # gradio
79
+ # gradio-client
80
+ # langgraph-sdk
81
+ # langsmith
82
+ # openai
83
+ # safehttpx
84
+ httpx-sse==0.4.0
85
+ # via langchain-community
86
+ huggingface-hub==0.30.2
87
+ # via
88
+ # gradio
89
+ # gradio-client
90
+ idna==3.10
91
+ # via
92
+ # anyio
93
+ # httpx
94
+ # requests
95
+ # yarl
96
+ itsdangerous==2.2.0
97
+ # via gradio
98
+ jinja2==3.1.6
99
+ # via gradio
100
+ jiter==0.9.0
101
+ # via openai
102
+ jsonpatch==1.33
103
+ # via langchain-core
104
+ jsonpointer==3.0.0
105
+ # via jsonpatch
106
+ langchain==0.3.24
107
+ # via
108
+ # agent-course-final-assesment (pyproject.toml)
109
+ # langchain-community
110
+ langchain-community==0.3.22
111
+ # via agent-course-final-assesment (pyproject.toml)
112
+ langchain-core==0.3.56
113
+ # via
114
+ # agent-course-final-assesment (pyproject.toml)
115
+ # langchain
116
+ # langchain-community
117
+ # langchain-openai
118
+ # langchain-text-splitters
119
+ # langgraph
120
+ # langgraph-checkpoint
121
+ # langgraph-prebuilt
122
+ langchain-openai==0.3.14
123
+ # via agent-course-final-assesment (pyproject.toml)
124
+ langchain-text-splitters==0.3.8
125
+ # via langchain
126
+ langgraph==0.3.34
127
+ # via agent-course-final-assesment (pyproject.toml)
128
+ langgraph-checkpoint==2.0.24
129
+ # via
130
+ # langgraph
131
+ # langgraph-prebuilt
132
+ langgraph-prebuilt==0.1.8
133
+ # via langgraph
134
+ langgraph-sdk==0.1.63
135
+ # via langgraph
136
+ langsmith==0.3.33
137
+ # via
138
+ # langchain
139
+ # langchain-community
140
+ # langchain-core
141
+ markdown-it-py==3.0.0
142
+ # via rich
143
+ markupsafe==3.0.2
144
+ # via
145
+ # gradio
146
+ # jinja2
147
+ marshmallow==3.26.1
148
+ # via dataclasses-json
149
+ mdurl==0.1.2
150
+ # via markdown-it-py
151
+ multidict==6.4.3
152
+ # via
153
+ # aiohttp
154
+ # yarl
155
+ mypy-extensions==1.1.0
156
+ # via typing-inspect
157
+ numpy==2.2.5
158
+ # via
159
+ # gradio
160
+ # langchain-community
161
+ # pandas
162
+ openai==1.76.0
163
+ # via langchain-openai
164
+ orjson==3.10.16
165
+ # via
166
+ # gradio
167
+ # langgraph-sdk
168
+ # langsmith
169
+ ormsgpack==1.9.1
170
+ # via langgraph-checkpoint
171
+ packaging==24.2
172
+ # via
173
+ # gradio
174
+ # gradio-client
175
+ # huggingface-hub
176
+ # langchain-core
177
+ # langsmith
178
+ # marshmallow
179
+ pandas==2.2.3
180
+ # via gradio
181
+ pillow==11.2.1
182
+ # via gradio
183
+ propcache==0.3.1
184
+ # via
185
+ # aiohttp
186
+ # yarl
187
+ pycparser==2.22
188
+ # via cffi
189
+ pydantic==2.11.3
190
+ # via
191
+ # fastapi
192
+ # gradio
193
+ # langchain
194
+ # langchain-core
195
+ # langsmith
196
+ # openai
197
+ # pydantic-settings
198
+ pydantic-core==2.33.1
199
+ # via pydantic
200
+ pydantic-settings==2.9.1
201
+ # via langchain-community
202
+ pydub==0.25.1
203
+ # via gradio
204
+ pygments==2.19.1
205
+ # via rich
206
+ python-dateutil==2.9.0.post0
207
+ # via pandas
208
+ python-dotenv==1.1.0
209
+ # via pydantic-settings
210
+ python-multipart==0.0.20
211
+ # via gradio
212
+ pytz==2025.2
213
+ # via pandas
214
+ pyyaml==6.0.2
215
+ # via
216
+ # gradio
217
+ # huggingface-hub
218
+ # langchain
219
+ # langchain-community
220
+ # langchain-core
221
+ regex==2024.11.6
222
+ # via tiktoken
223
+ requests==2.32.3
224
+ # via
225
+ # huggingface-hub
226
+ # langchain
227
+ # langchain-community
228
+ # langsmith
229
+ # requests-toolbelt
230
+ # tiktoken
231
+ requests-toolbelt==1.0.0
232
+ # via langsmith
233
+ rich==14.0.0
234
+ # via typer
235
+ ruff==0.11.7
236
+ # via gradio
237
+ safehttpx==0.1.6
238
+ # via gradio
239
+ semantic-version==2.10.0
240
+ # via gradio
241
+ shellingham==1.5.4
242
+ # via typer
243
+ six==1.17.0
244
+ # via python-dateutil
245
+ sniffio==1.3.1
246
+ # via
247
+ # anyio
248
+ # openai
249
+ sqlalchemy==2.0.40
250
+ # via
251
+ # langchain
252
+ # langchain-community
253
+ starlette==0.46.2
254
+ # via
255
+ # fastapi
256
+ # gradio
257
+ tenacity==9.1.2
258
+ # via
259
+ # langchain-community
260
+ # langchain-core
261
+ tiktoken==0.9.0
262
+ # via langchain-openai
263
+ tomlkit==0.13.2
264
+ # via gradio
265
+ tqdm==4.67.1
266
+ # via
267
+ # huggingface-hub
268
+ # openai
269
+ typer==0.15.2
270
+ # via gradio
271
+ typing-extensions==4.13.2
272
+ # via
273
+ # anyio
274
+ # fastapi
275
+ # gradio
276
+ # gradio-client
277
+ # huggingface-hub
278
+ # langchain-core
279
+ # openai
280
+ # pydantic
281
+ # pydantic-core
282
+ # sqlalchemy
283
+ # typer
284
+ # typing-inspect
285
+ # typing-inspection
286
+ typing-inspect==0.9.0
287
+ # via dataclasses-json
288
+ typing-inspection==0.4.0
289
+ # via
290
+ # pydantic
291
+ # pydantic-settings
292
+ tzdata==2025.2
293
+ # via pandas
294
+ urllib3==2.4.0
295
+ # via requests
296
+ uvicorn==0.34.2
297
+ # via gradio
298
+ websockets==15.0.1
299
+ # via gradio-client
300
+ xxhash==3.5.0
301
+ # via langgraph
302
+ yarl==1.20.0
303
+ # via aiohttp
304
+ zstandard==0.23.0
305
+ # via langsmith
researchgraph/configuration.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Define the configurable parameters for the agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field, fields
6
+ from typing import Annotated, Optional
7
+
8
+ from langchain_core.runnables import RunnableConfig, ensure_config
9
+
10
+ from researchgraph import prompts
11
+ from researchgraph import schema
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class Configuration:
16
+ """The configuration for the agent."""
17
+
18
+ model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
19
+ default="openai/gpt-4.1",
20
+ metadata={
21
+ "description": "The name of the language model to use for the agent. "
22
+ "Should be in the form: provider/model-name."
23
+ },
24
+ )
25
+
26
+ prompt: str = field(
27
+ default=prompts.MAIN_PROMPT,
28
+ metadata={
29
+ "description": "The main prompt template to use for the agent's interactions. "
30
+ "Expects two f-string arguments: {info} and {question}."
31
+ },
32
+ )
33
+
34
+ extraction_schema: dict = field(
35
+ default_factory=lambda: schema.extraction_schema,
36
+ metadata={
37
+ "description": "The schema to use for extracting information from the agent's responses. "
38
+ "Should be a valid JSON schema."
39
+ },
40
+ )
41
+
42
+ max_search_results: int = field(
43
+ default=25,
44
+ metadata={
45
+ "description": "The maximum number of search results to return for each search query."
46
+ },
47
+ )
48
+
49
+ max_info_tool_calls: int = field(
50
+ default=25,
51
+ metadata={
52
+ "description": "The maximum number of times the Info tool can be called during a single interaction."
53
+ },
54
+ )
55
+
56
+ max_loops: int = field(
57
+ default=25,
58
+ metadata={
59
+ "description": "The maximum number of interaction loops allowed before the agent terminates."
60
+ },
61
+ )
62
+
63
+ @classmethod
64
+ def from_runnable_config(
65
+ cls, config: Optional[RunnableConfig] = None
66
+ ) -> Configuration:
67
+ """Load configuration w/ defaults for the given invocation."""
68
+ config = ensure_config(config)
69
+ configurable = config.get("configurable") or {}
70
+ _fields = {f.name for f in fields(cls) if f.init}
71
+ return cls(**{k: v for k, v in configurable.items() if k in _fields})
researchgraph/graph.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Define a data enrichment agent.
2
+
3
+ Works with a chat model with tool calling support.
4
+ """
5
+
6
+ import json
7
+ from typing import Any, Dict, List, Literal, Optional, cast
8
+
9
+ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage
10
+ from langchain_core.runnables import RunnableConfig
11
+ from langgraph.graph import StateGraph
12
+ from langgraph.prebuilt import ToolNode
13
+ from pydantic import BaseModel, Field
14
+
15
+ from researchgraph import prompts
16
+ from researchgraph.configuration import Configuration
17
+ from researchgraph.state import InputState, OutputState, State
18
+ from researchgraph.tools import scrape_website, search, get_file_content
19
+ from researchgraph.utils import init_model
20
+
21
+
22
+ async def call_agent_model(
23
+ state: State, *, config: Optional[RunnableConfig] = None
24
+ ) -> Dict[str, Any]:
25
+ """Call the primary Language Model (LLM) to decide on the next research action.
26
+
27
+ This asynchronous function performs the following steps:
28
+ 1. Initializes configuration and sets up the 'Info' tool, which is the user-defined extraction schema.
29
+ 2. Prepares the prompt and message history for the LLM.
30
+ 3. Initializes and configures the LLM with available tools.
31
+ 4. Invokes the LLM and processes its response.
32
+ 5. Handles the LLM's decision to either continue research or submit final info.
33
+ """
34
+ # Load configuration from the provided RunnableConfig
35
+ configuration = Configuration.from_runnable_config(config)
36
+
37
+ # Define the 'Info' tool, which is the user-defined extraction schema
38
+ info_tool = {
39
+ "name": "Info",
40
+ "description": "Call this when you have gathered all the relevant info",
41
+ "parameters": configuration.extraction_schema,
42
+ }
43
+
44
+ # Define the GetFile tool
45
+ get_file_tool = {
46
+ "name": "GetFile",
47
+ "description": "Fetch content from the scoring system for a given task ID",
48
+ "parameters": {
49
+ "type": "object",
50
+ "properties": {
51
+ "task_id": {
52
+ "type": "string",
53
+ "description": "The ID of the task/file to fetch",
54
+ }
55
+ },
56
+ "required": ["task_id"],
57
+ },
58
+ }
59
+
60
+ # Format the prompt defined in prompts.py with the extraction schema, question and task_id
61
+ p = configuration.prompt.format(
62
+ info=json.dumps(configuration.extraction_schema, indent=2),
63
+ question=state.question,
64
+ task_id=state.task_id,
65
+ )
66
+
67
+ # Create the messages list with the formatted prompt and the previous messages
68
+ messages = [HumanMessage(content=p)] + state.messages
69
+
70
+ # Initialize the raw model with the provided configuration and bind the tools
71
+ raw_model = init_model(config)
72
+ model = raw_model.bind_tools(
73
+ [scrape_website, search, get_file_content, info_tool, get_file_tool],
74
+ tool_choice="any",
75
+ )
76
+ response = cast(AIMessage, await model.ainvoke(messages))
77
+
78
+ # Initialize info to None
79
+ info = None
80
+
81
+ # Check if the response has tool calls
82
+ if response.tool_calls:
83
+ for tool_call in response.tool_calls:
84
+ if tool_call["name"] == "Info":
85
+ info = tool_call["args"]
86
+ break
87
+ if info is not None:
88
+ # The agent is submitting their answer;
89
+ # ensure it isn't erroneously attempting to simultaneously perform research
90
+ response.tool_calls = [
91
+ next(tc for tc in response.tool_calls if tc["name"] == "Info")
92
+ ]
93
+ response_messages: List[BaseMessage] = [response]
94
+ if not response.tool_calls: # If LLM didn't respect the tool_choice
95
+ response_messages.append(
96
+ HumanMessage(content="Please respond by calling one of the provided tools.")
97
+ )
98
+ return {
99
+ "messages": response_messages,
100
+ "info": info,
101
+ # Add 1 to the step count
102
+ "loop_step": 1,
103
+ }
104
+
105
+
106
+ class InfoIsSatisfactory(BaseModel):
107
+ """Validate whether the current extracted info is satisfactory and complete."""
108
+
109
+ reason: List[str] = Field(
110
+ description="First, provide reasoning for why this is either good or bad as a final result. Must include at least 3 reasons."
111
+ )
112
+ is_satisfactory: bool = Field(
113
+ description="After providing your reasoning, provide a value indicating whether the result is satisfactory. If not, you will continue researching."
114
+ )
115
+ improvement_instructions: Optional[str] = Field(
116
+ description="If the result is not satisfactory, provide clear and specific instructions on what needs to be improved or added to make the information satisfactory."
117
+ " This should include details on missing information, areas that need more depth, or specific aspects to focus on in further research.",
118
+ default=None,
119
+ )
120
+
121
+
122
+ async def reflect(
123
+ state: State, *, config: Optional[RunnableConfig] = None
124
+ ) -> Dict[str, Any]:
125
+ """Validate the quality of the data enrichment agent's output.
126
+
127
+ This asynchronous function performs the following steps:
128
+ 1. Prepares the initial prompt using the main prompt template.
129
+ 2. Constructs a message history for the model.
130
+ 3. Prepares a checker prompt to evaluate the presumed info.
131
+ 4. Initializes and configures a language model with structured output.
132
+ 5. Invokes the model to assess the quality of the gathered information.
133
+ 6. Processes the model's response and determines if the info is satisfactory.
134
+ """
135
+
136
+ configuration = Configuration.from_runnable_config(config)
137
+
138
+ p = prompts.MAIN_PROMPT.format(
139
+ info=json.dumps(configuration.extraction_schema, indent=2),
140
+ question=state.question,
141
+ task_id=state.task_id,
142
+ )
143
+ last_message = state.messages[-1]
144
+ if not isinstance(last_message, AIMessage):
145
+ raise ValueError(
146
+ f"{reflect.__name__} expects the last message in the state to be an AI message with tool calls."
147
+ f" Got: {type(last_message)}"
148
+ )
149
+ messages = [HumanMessage(content=p)] + state.messages[:-1]
150
+ presumed_info = state.info
151
+ checker_prompt = """I am thinking of calling the info tool with the info below. \
152
+ Is this good? Give your reasoning as well. \
153
+ You can encourage the Assistant to look at specific URLs if that seems relevant, or do more searches.
154
+ If you don't think it is good, you should be very specific about what could be improved.
155
+
156
+ {presumed_info}"""
157
+ p1 = checker_prompt.format(presumed_info=json.dumps(presumed_info or {}, indent=2))
158
+ messages.append(HumanMessage(content=p1))
159
+ raw_model = init_model(config)
160
+ bound_model = raw_model.with_structured_output(InfoIsSatisfactory)
161
+ response = cast(InfoIsSatisfactory, await bound_model.ainvoke(messages))
162
+ if response.is_satisfactory and presumed_info:
163
+ return {
164
+ "info": presumed_info,
165
+ "messages": [
166
+ ToolMessage(
167
+ tool_call_id=last_message.tool_calls[0]["id"],
168
+ content="\n".join(response.reason),
169
+ name="Info",
170
+ additional_kwargs={"artifact": response.model_dump()},
171
+ status="success",
172
+ )
173
+ ],
174
+ }
175
+ else:
176
+ return {
177
+ "messages": [
178
+ ToolMessage(
179
+ tool_call_id=last_message.tool_calls[0]["id"],
180
+ content=f"Unsatisfactory response:\n{response.improvement_instructions}",
181
+ name="Info",
182
+ additional_kwargs={"artifact": response.model_dump()},
183
+ status="error",
184
+ )
185
+ ]
186
+ }
187
+
188
+
189
+ def route_after_agent(
190
+ state: State,
191
+ ) -> Literal["reflect", "tools", "call_agent_model", "__end__"]:
192
+ """Schedule the next node after the agent's action.
193
+
194
+ This function determines the next step in the research process based on the
195
+ last message in the state. It handles three main scenarios:
196
+
197
+ 1. Error recovery: If the last message is unexpectedly not an AIMessage.
198
+ 2. Info submission: If the agent has called the "Info" tool to submit findings.
199
+ 3. Continued research: If the agent has called any other tool.
200
+ """
201
+ last_message = state.messages[-1]
202
+
203
+ # "If for some reason the last message is not an AIMessage (due to a bug or unexpected behavior elsewhere in the code),
204
+ # it ensures the system doesn't crash but instead tries to recover by calling the agent model again.
205
+ if not isinstance(last_message, AIMessage):
206
+ return "call_agent_model"
207
+ # If the "Info" tool was called, then the model provided its extraction output. Reflect on the result
208
+ if last_message.tool_calls and last_message.tool_calls[0]["name"] == "Info":
209
+ return "reflect"
210
+ # The last message is a tool call that is not "Info" (extraction output)
211
+ else:
212
+ return "tools"
213
+
214
+
215
+ def route_after_checker(
216
+ state: State, config: RunnableConfig
217
+ ) -> Literal["__end__", "call_agent_model"]:
218
+ """Schedule the next node after the checker's evaluation.
219
+
220
+ This function determines whether to continue the research process or end it
221
+ based on the checker's evaluation and the current state of the research.
222
+ """
223
+ configurable = Configuration.from_runnable_config(config)
224
+ last_message = state.messages[-1]
225
+
226
+ if state.loop_step < configurable.max_loops:
227
+ if not state.info:
228
+ return "call_agent_model"
229
+ if not isinstance(last_message, ToolMessage):
230
+ raise ValueError(
231
+ f"{route_after_checker.__name__} expected a tool messages. Received: {type(last_message)}."
232
+ )
233
+ if last_message.status == "error":
234
+ # Research deemed unsatisfactory
235
+ return "call_agent_model"
236
+ # It's great!
237
+ return "__end__"
238
+ else:
239
+ return "__end__"
240
+
241
+
242
+ # Create the researcher graph
243
+ researcher_workflow = StateGraph(
244
+ State, input=InputState, output=OutputState, config_schema=Configuration
245
+ )
246
+ researcher_workflow.add_node(call_agent_model)
247
+ researcher_workflow.add_node(reflect)
248
+ researcher_workflow.add_node(
249
+ "tools", ToolNode([search, scrape_website, get_file_content])
250
+ )
251
+ researcher_workflow.add_edge("__start__", "call_agent_model")
252
+ researcher_workflow.add_conditional_edges("call_agent_model", route_after_agent)
253
+ researcher_workflow.add_edge("tools", "call_agent_model")
254
+ researcher_workflow.add_conditional_edges("reflect", route_after_checker)
255
+
256
+ researchgraph = researcher_workflow.compile()
257
+ researchgraph.name = "Agent"
researchgraph/prompts.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Default prompts used in this project."""
2
+
3
+ MAIN_PROMPT = """You are a general AI assistant. I will ask you a question.
4
+ Report your thoughts, and finish your answer with the following template:
5
+ FINAL ANSWER: [YOUR FINAL ANSWER].
6
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
7
+ If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.
8
+ If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.
9
+ If you are 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.
10
+
11
+ <info>
12
+ {info}
13
+ </info>
14
+
15
+ You have access to the following tools:
16
+
17
+ - `Search`: call this tool to find relevant web sources.
18
+ - `ScrapeWebsite`: use this to extract detailed insights from specific web pages. This will update your notes.
19
+ - `GetFile`: use this to fetch specific task file content. You can access the file using the task ID: {task_id}
20
+ - `Info`: call this when you have collected and structured all the necessary information.
21
+
22
+ Here is the question you need to uncover:
23
+
24
+ question: {question}
25
+
26
+ Be thorough, organize your findings according to the above structure, and validate for accuracy and completeness.
27
+ """
researchgraph/schema.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ extraction_schema = {
2
+ "type": "object",
3
+ "properties": {
4
+ "result": {
5
+ "type": "string",
6
+ "description": "The answer to the question"
7
+ }
8
+ },
9
+ "required": ["result"]
10
+ }
researchgraph/state.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """State definitions.
2
+
3
+ State is the interface between the graph and end user as well as the
4
+ data model used internally by the graph.
5
+ """
6
+
7
+ import operator
8
+ from dataclasses import dataclass, field
9
+ from typing import Annotated, Any, List, Optional
10
+
11
+ from langchain_core.messages import BaseMessage
12
+ from langgraph.graph import add_messages
13
+
14
+
15
+ @dataclass(kw_only=True)
16
+ class InputState:
17
+ """Input state defines the interface between the graph and the user (external API)."""
18
+
19
+ question: str
20
+ "The question for which the agent is tasked to gather information."
21
+
22
+ task_id: str
23
+ "The ID of the task being processed"
24
+
25
+ info: Optional[dict[str, Any]] = field(default=None)
26
+ "The info state tracks the current extracted data for the given question, conforming to the provided schema. This is primarily populated by the agent."
27
+
28
+
29
+ @dataclass(kw_only=True)
30
+ class State(InputState):
31
+ """A graph's State defines three main things.
32
+
33
+ 1. The structure of the data to be passed between nodes (which "channels" to read from/write to and their types)
34
+ 2. Default values for each field
35
+ 3. Reducers for the state's fields. Reducers are functions that determine how to apply updates to the state.
36
+ See [Reducers](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) for more information.
37
+ """
38
+
39
+ messages: Annotated[List[BaseMessage], add_messages] = field(default_factory=list)
40
+ """
41
+ Messages track the primary execution state of the agent.
42
+
43
+ Typically accumulates a pattern of:
44
+
45
+ 1. HumanMessage - user input
46
+ 2. AIMessage with .tool_calls - agent picking tool(s) to use to collect
47
+ information
48
+ 3. ToolMessage(s) - the responses (or errors) from the executed tools
49
+
50
+ (... repeat steps 2 and 3 as needed ...)
51
+ 4. AIMessage without .tool_calls - agent responding in unstructured
52
+ format to the user.
53
+
54
+ 5. HumanMessage - user responds with the next conversational turn.
55
+
56
+ (... repeat steps 2-5 as needed ... )
57
+
58
+ Merges two lists of messages, updating existing messages by ID.
59
+
60
+ By default, this ensures the state is "append-only", unless the
61
+ new message has the same ID as an existing message.
62
+
63
+ Returns:
64
+ A new list of messages with the messages from `right` merged into `left`.
65
+ If a message in `right` has the same ID as a message in `left`, the
66
+ message from `right` will replace the message from `left`.
67
+ """
68
+
69
+ loop_step: Annotated[int, operator.add] = field(default=0)
70
+
71
+ # Feel free to add additional attributes to your state as needed.
72
+ # Common examples include retrieved documents, extracted entities, API connections, etc.
73
+
74
+
75
+ @dataclass(kw_only=True)
76
+ class OutputState:
77
+ """The response object for the end user.
78
+
79
+ This class defines the structure of the output that will be provided
80
+ to the user after the graph's execution is complete.
81
+ """
82
+
83
+ info: dict[str, Any]
84
+ """
85
+ A dictionary containing the extracted and processed information
86
+ based on the user's query and the graph's execution.
87
+ This is the primary output of the enrichment process.
88
+ """
researchgraph/tools.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tools for data enrichment.
2
+
3
+ This module contains functions that are directly exposed to the LLM as tools.
4
+ These tools can be used for tasks such as web searching and scraping.
5
+ Users can edit and extend these tools as needed.
6
+ """
7
+
8
+ import json
9
+ from typing import Any, Optional, cast
10
+
11
+ import aiohttp
12
+ from langchain_community.tools.tavily_search import TavilySearchResults
13
+ from langchain_core.runnables import RunnableConfig
14
+ from langchain_core.tools import InjectedToolArg
15
+ from langgraph.prebuilt import InjectedState
16
+ from typing_extensions import Annotated
17
+
18
+ from researchgraph.configuration import Configuration
19
+ from researchgraph.state import State
20
+ from researchgraph.utils import init_model
21
+
22
+
23
+ async def get_file_content(
24
+ task_id: str, *, config: Annotated[RunnableConfig, InjectedToolArg]
25
+ ) -> Optional[str]:
26
+ """Fetch and process a file from the scoring system.
27
+
28
+ Args:
29
+ task_id: The ID of the task/file to fetch.
30
+ config: Runtime configuration.
31
+
32
+ Returns:
33
+ Optional[str]: The content of the file if successful, None otherwise.
34
+ """
35
+ url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
36
+ async with aiohttp.ClientSession() as session:
37
+ async with session.get(url) as response:
38
+ if response.status == 200:
39
+ return await response.text()
40
+ return None
41
+
42
+
43
+ async def search(
44
+ query: str, *, config: Annotated[RunnableConfig, InjectedToolArg]
45
+ ) -> Optional[list[dict[str, Any]]]:
46
+ """Query a search engine.
47
+
48
+ This function queries the web to fetch comprehensive, accurate, and trusted results. It's particularly useful
49
+ for answering questions about current events. Provide as much context in the query as needed to ensure high recall.
50
+ """
51
+ configuration = Configuration.from_runnable_config(config)
52
+ wrapped = TavilySearchResults(max_results=configuration.max_search_results)
53
+ result = await wrapped.ainvoke({"query": query})
54
+ return cast(list[dict[str, Any]], result)
55
+
56
+
57
+ _INFO_PROMPT = """You are doing web research on behalf of a user. You are trying to find out this information:
58
+
59
+ <info>
60
+ {info}
61
+ </info>
62
+
63
+ You just scraped the following website: {url}
64
+
65
+ Based on the website content below, jot down some notes about the website.
66
+
67
+ <Website content>
68
+ {content}
69
+ </Website content>"""
70
+
71
+
72
+ async def scrape_website(
73
+ url: str,
74
+ *,
75
+ state: Annotated[State, InjectedState],
76
+ config: Annotated[RunnableConfig, InjectedToolArg],
77
+ ) -> str:
78
+ """Scrape and summarize content from a given URL.
79
+
80
+ Returns:
81
+ str: A summary of the scraped content, tailored to the extraction schema.
82
+ """
83
+ async with aiohttp.ClientSession() as session:
84
+ async with session.get(url) as response:
85
+ content = await response.text()
86
+ configuration = Configuration.from_runnable_config(config)
87
+ p = _INFO_PROMPT.format(
88
+ info=json.dumps(configuration.extraction_schema, indent=2),
89
+ url=url,
90
+ content=content[:40_000],
91
+ )
92
+ raw_model = init_model(config)
93
+ result = await raw_model.ainvoke(p)
94
+ return str(result.content)
researchgraph/utils.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Utility functions used in our graph."""
2
+
3
+ from typing import Optional
4
+
5
+ from langchain.chat_models import init_chat_model
6
+ from langchain_core.language_models import BaseChatModel
7
+ from langchain_core.messages import AnyMessage
8
+ from langchain_core.runnables import RunnableConfig
9
+
10
+ from researchgraph.configuration import Configuration
11
+
12
+
13
+ def get_message_text(msg: AnyMessage) -> str:
14
+ """Get the text content of a message."""
15
+ content = msg.content
16
+ if isinstance(content, str):
17
+ return content
18
+ elif isinstance(content, dict):
19
+ return content.get("text", "")
20
+ else:
21
+ txts = [c if isinstance(c, str) else (c.get("text") or "") for c in content]
22
+ return "".join(txts).strip()
23
+
24
+
25
+ def init_model(config: Optional[RunnableConfig] = None) -> BaseChatModel:
26
+ """Initialize the configured chat model."""
27
+ configuration = Configuration.from_runnable_config(config)
28
+ fully_specified_name = configuration.model
29
+ if "/" in fully_specified_name:
30
+ provider, model = fully_specified_name.split("/", maxsplit=1)
31
+ else:
32
+ provider = None
33
+ model = fully_specified_name
34
+ return init_chat_model(model, model_provider=provider)
uv.lock ADDED
The diff for this file is too large to render. See raw diff