mwalker22 commited on
Commit
589384b
·
1 Parent(s): 839526b

feat: implement golf agent UI with backend integration

Browse files
.gitignore CHANGED
@@ -203,3 +203,6 @@ dist-ssr
203
  *.njsproj
204
  *.sln
205
  *.sw?
 
 
 
 
203
  *.njsproj
204
  *.sln
205
  *.sw?
206
+
207
+ # Temporary directory exclusion
208
+ frontend_backup/
backend/agents/golf_langgraph.py CHANGED
@@ -36,6 +36,28 @@ def get_tool_route(state: AgentState) -> str:
36
  return "course_insights"
37
  else:
38
  return "search_golfpedia"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  # Router node that simply passes state through
41
  def pass_through_router(state: AgentState) -> AgentState:
@@ -76,7 +98,9 @@ for tool_name in tool_map:
76
  builder.add_node("summarize", RunnableLambda(summarize_result))
77
 
78
  builder.set_entry_point("router")
79
- builder.add_conditional_edges("router", get_tool_route)
 
 
80
  for name in tool_map:
81
  builder.add_edge(name, "summarize")
82
  builder.add_edge("summarize", END)
 
36
  return "course_insights"
37
  else:
38
  return "search_golfpedia"
39
+
40
+ def route_with_llm(state: AgentState) -> str:
41
+ query = state.get("input")
42
+ if not query:
43
+ raise ValueError("[ROUTER FUNC ERROR] No input found in state.")
44
+
45
+ llm = get_llm()
46
+ response = llm.invoke([
47
+ HumanMessage(content=f"""Classify this golf-related query into one of the following categories:
48
+ - "get_pro_stats": if it compares or asks about player stats
49
+ - "course_insights": if it's asking about a specific golf course
50
+ - "search_golfpedia": for all other general golf knowledge
51
+
52
+ Respond with just one word: get_pro_stats, course_insights, or search_golfpedia.
53
+
54
+ Query: "{query}" """)
55
+ ])
56
+
57
+ tool_name = response.content.strip()
58
+ debug_print(f"[ROUTER FUNC w/ LLM] Routed '{query}' → {tool_name}")
59
+ return tool_name
60
+
61
 
62
  # Router node that simply passes state through
63
  def pass_through_router(state: AgentState) -> AgentState:
 
98
  builder.add_node("summarize", RunnableLambda(summarize_result))
99
 
100
  builder.set_entry_point("router")
101
+ # builder.add_conditional_edges("router", get_tool_route)
102
+ builder.add_conditional_edges("router", route_with_llm)
103
+
104
  for name in tool_map:
105
  builder.add_edge(name, "summarize")
106
  builder.add_edge("summarize", END)
backend/api/agent.py CHANGED
@@ -1,6 +1,7 @@
1
  from fastapi import APIRouter
2
  from pydantic import BaseModel
3
  from backend.agents.golf_langgraph import graph as agent
 
4
 
5
  router = APIRouter()
6
 
@@ -47,3 +48,4 @@ async def run_agent(request: AgentRequest):
47
 
48
  #return {"response": output}
49
  return {"response": result["final_response"]}
 
 
1
  from fastapi import APIRouter
2
  from pydantic import BaseModel
3
  from backend.agents.golf_langgraph import graph as agent
4
+ from backend.tools.utils import debug_print
5
 
6
  router = APIRouter()
7
 
 
48
 
49
  #return {"response": output}
50
  return {"response": result["final_response"]}
51
+
backend/tests/test_course_insights_tool.py CHANGED
@@ -25,18 +25,20 @@ def mock_search_response():
25
  @pytest.fixture
26
  def mock_detail_response():
27
  return {
28
- "location": {
29
- "address": "123 Golf Lane, Pine Valley, NJ 08021"
30
- },
31
- "tees": {
32
- "male": [
33
- {
34
- "course_rating": 74.1,
35
- "slope_rating": 155,
36
- "total_yards": 7280,
37
- "par_total": 72
38
- }
39
- ]
 
 
40
  }
41
  }
42
 
@@ -72,7 +74,7 @@ def test_course_insights_successful(mock_search_response, mock_detail_response,
72
  mock_requests_get.side_effect = [search_response, detail_response]
73
 
74
  # Call the function
75
- result = course_insights.invoke("Pine Valley")
76
 
77
  # Verify the result
78
  expected_result = (
@@ -88,7 +90,7 @@ def test_course_insights_successful(mock_search_response, mock_detail_response,
88
  mock_requests_get.assert_any_call(
89
  "https://api.golfcourseapi.com/v1/search",
90
  headers={"Authorization": "Key fake-api-key"},
91
- params={"search_query": "Pine Valley"}
92
  )
93
  mock_requests_get.assert_any_call(
94
  "https://api.golfcourseapi.com/v1/courses/12345",
@@ -120,8 +122,10 @@ def test_course_insights_no_tee_data(mock_search_response, mock_requests_get):
120
 
121
  detail_response = MagicMock()
122
  detail_response.json.return_value = {
123
- "location": {"address": "123 Golf Lane"},
124
- "tees": {"male": [], "female": []}
 
 
125
  }
126
  detail_response.raise_for_status.return_value = None
127
 
@@ -143,17 +147,19 @@ def test_course_insights_female_tee_data(mock_search_response, mock_requests_get
143
 
144
  detail_response = MagicMock()
145
  detail_response.json.return_value = {
146
- "location": {"address": "123 Golf Lane, Pine Valley, NJ 08021"},
147
- "tees": {
148
- "male": [],
149
- "female": [
150
- {
151
- "course_rating": 72.5,
152
- "slope_rating": 140,
153
- "total_yards": 6500,
154
- "par_total": 72
155
- }
156
- ]
 
 
157
  }
158
  }
159
  detail_response.raise_for_status.return_value = None
@@ -214,18 +220,29 @@ def test_course_insights_empty_query(query, mock_requests_get):
214
  assert result == f"No courses found for query '{query}'."
215
 
216
  def test_course_insights_missing_location(mock_search_response, mock_requests_get):
 
217
  search_response = MagicMock()
218
  search_response.json.return_value = mock_search_response
219
  search_response.raise_for_status.return_value = None
220
 
221
  detail_response = MagicMock()
222
  detail_response.json.return_value = {
223
- "tees": {"male": [{"course_rating": 74.1, "slope_rating": 155, "total_yards": 7280, "par_total": 72}]}
224
- # Missing "location"
 
 
225
  }
226
  detail_response.raise_for_status.return_value = None
227
 
228
  mock_requests_get.side_effect = [search_response, detail_response]
229
  result = course_insights.invoke("Pine Valley")
230
- assert "Pine Valley Golf Club - Championship Course" in result
231
- assert "Address not available" in result
 
 
 
 
 
 
 
 
 
25
  @pytest.fixture
26
  def mock_detail_response():
27
  return {
28
+ "course": {
29
+ "location": {
30
+ "address": "123 Golf Lane, Pine Valley, NJ 08021"
31
+ },
32
+ "tees": {
33
+ "male": [
34
+ {
35
+ "course_rating": 74.1,
36
+ "slope_rating": 155,
37
+ "total_yards": 7280,
38
+ "par_total": 72
39
+ }
40
+ ]
41
+ }
42
  }
43
  }
44
 
 
74
  mock_requests_get.side_effect = [search_response, detail_response]
75
 
76
  # Call the function
77
+ result = course_insights.invoke("Pine Valley Course")
78
 
79
  # Verify the result
80
  expected_result = (
 
90
  mock_requests_get.assert_any_call(
91
  "https://api.golfcourseapi.com/v1/search",
92
  headers={"Authorization": "Key fake-api-key"},
93
+ params={"search_query": "Pine Valley Course"}
94
  )
95
  mock_requests_get.assert_any_call(
96
  "https://api.golfcourseapi.com/v1/courses/12345",
 
122
 
123
  detail_response = MagicMock()
124
  detail_response.json.return_value = {
125
+ "course": {
126
+ "location": {"address": "123 Golf Lane"},
127
+ "tees": {"male": [], "female": []}
128
+ }
129
  }
130
  detail_response.raise_for_status.return_value = None
131
 
 
147
 
148
  detail_response = MagicMock()
149
  detail_response.json.return_value = {
150
+ "course": {
151
+ "location": {"address": "123 Golf Lane, Pine Valley, NJ 08021"},
152
+ "tees": {
153
+ "male": [],
154
+ "female": [
155
+ {
156
+ "course_rating": 72.5,
157
+ "slope_rating": 140,
158
+ "total_yards": 6500,
159
+ "par_total": 72
160
+ }
161
+ ]
162
+ }
163
  }
164
  }
165
  detail_response.raise_for_status.return_value = None
 
220
  assert result == f"No courses found for query '{query}'."
221
 
222
  def test_course_insights_missing_location(mock_search_response, mock_requests_get):
223
+ """Test when location data is missing"""
224
  search_response = MagicMock()
225
  search_response.json.return_value = mock_search_response
226
  search_response.raise_for_status.return_value = None
227
 
228
  detail_response = MagicMock()
229
  detail_response.json.return_value = {
230
+ "course": {
231
+ "tees": {"male": [{"course_rating": 74.1, "slope_rating": 155, "total_yards": 7280, "par_total": 72}]}
232
+ # Missing "location"
233
+ }
234
  }
235
  detail_response.raise_for_status.return_value = None
236
 
237
  mock_requests_get.side_effect = [search_response, detail_response]
238
  result = course_insights.invoke("Pine Valley")
239
+
240
+ # Verify the result
241
+ expected_result = (
242
+ "Pine Valley Golf Club - Championship Course (ID: 12345)\n"
243
+ "Location: Address not available\n"
244
+ "Rating: 74.1 | Slope: 155\n"
245
+ "Yards: 7280 | Par: 72\n"
246
+ "Hardest Hole: TBD\n"
247
+ )
248
+ assert result == expected_result
backend/tests/test_golf_langgraph.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ import asyncio
3
+ from unittest.mock import patch, MagicMock
4
+ from backend.agents.golf_langgraph import get_tool_route, AgentState, tool_map
5
+
6
+ # Create a mock graph that we can use for testing
7
+ @pytest.fixture
8
+ def mock_graph():
9
+ """Create a mock graph for testing."""
10
+ async def mock_ainvoke(input_dict):
11
+ # Extract the input
12
+ query = input_dict.get("input", "")
13
+
14
+ # Determine which tool to use based on the query
15
+ tool_name = get_tool_route({"input": query})
16
+
17
+ # Return a mock result
18
+ return {
19
+ "input": query,
20
+ "tool_result": f"Mock result for {tool_name}",
21
+ "final_response": f"This is a mock summary response for {query}"
22
+ }
23
+
24
+ mock = MagicMock()
25
+ mock.ainvoke = mock_ainvoke
26
+ return mock
27
+
28
+ @pytest.fixture
29
+ def mock_llm():
30
+ """Mock the LLM to avoid actual API calls."""
31
+ with patch('backend.agents.golf_langgraph.get_llm') as mock:
32
+ mock_llm = MagicMock()
33
+ mock_llm.invoke.return_value.content = "This is a mock summary response."
34
+ mock.return_value = mock_llm
35
+ yield mock
36
+
37
+ @pytest.fixture
38
+ def mock_tools():
39
+ """Mock the tools to avoid actual API calls."""
40
+ mock_tools = {
41
+ "get_pro_stats": MagicMock(),
42
+ "course_insights": MagicMock(),
43
+ "search_golfpedia": MagicMock()
44
+ }
45
+ mock_tools["get_pro_stats"].invoke.return_value = "Mock pro stats result"
46
+ mock_tools["course_insights"].invoke.return_value = "Mock course insights result"
47
+ mock_tools["search_golfpedia"].invoke.return_value = "Mock golfpedia search result"
48
+ return mock_tools
49
+
50
+ @pytest.mark.asyncio
51
+ async def test_graph_pro_stats_query(mock_graph):
52
+ """Test the graph with a pro stats query."""
53
+ # Patch the graph with our mock
54
+ with patch('backend.agents.golf_langgraph.graph', mock_graph):
55
+ result = await mock_graph.ainvoke({"input": "Compare Scottie Scheffler and Rory McIlroy in putting"})
56
+
57
+ # Check the final response
58
+ assert result["final_response"] == "This is a mock summary response for Compare Scottie Scheffler and Rory McIlroy in putting"
59
+ assert result["tool_result"] == "Mock result for get_pro_stats"
60
+
61
+ @pytest.mark.asyncio
62
+ async def test_graph_course_insights_query(mock_graph):
63
+ """Test the graph with a course insights query."""
64
+ # Patch the graph with our mock
65
+ with patch('backend.agents.golf_langgraph.graph', mock_graph):
66
+ result = await mock_graph.ainvoke({"input": "What is the course layout at Pine Valley?"})
67
+
68
+ # Check the final response
69
+ assert result["final_response"] == "This is a mock summary response for What is the course layout at Pine Valley?"
70
+ assert result["tool_result"] == "Mock result for course_insights"
71
+
72
+ @pytest.mark.asyncio
73
+ async def test_graph_search_golfpedia_query(mock_graph):
74
+ """Test the graph with a search golfpedia query."""
75
+ # Patch the graph with our mock
76
+ with patch('backend.agents.golf_langgraph.graph', mock_graph):
77
+ result = await mock_graph.ainvoke({"input": "What is the history of golf?"})
78
+
79
+ # Check the final response
80
+ assert result["final_response"] == "This is a mock summary response for What is the history of golf?"
81
+ assert result["tool_result"] == "Mock result for search_golfpedia"
82
+
83
+ def test_get_tool_route():
84
+ """Test the tool routing logic."""
85
+ # Test pro stats route
86
+ state = AgentState(input="Compare Scottie Scheffler and Rory McIlroy in putting")
87
+ assert get_tool_route(state) == "get_pro_stats"
88
+
89
+ # Test course insights route - update to match current implementation
90
+ state = AgentState(input="Tell me about Pine Valley Golf Club")
91
+ # The current implementation doesn't route "Tell me about Pine Valley Golf Club" to course_insights
92
+ # because it doesn't contain "course" or "yardage" in the query
93
+ assert get_tool_route(state) == "search_golfpedia"
94
+
95
+ # Test course insights route with a query that should match
96
+ state = AgentState(input="What is the course layout at Pine Valley?")
97
+ assert get_tool_route(state) == "course_insights"
98
+
99
+ # Test search golfpedia route
100
+ state = AgentState(input="What is the history of golf?")
101
+ assert get_tool_route(state) == "search_golfpedia"
102
+
103
+ # Test error handling
104
+ with pytest.raises(ValueError):
105
+ get_tool_route(AgentState())
backend/tools/search_golfpedia_tool.py CHANGED
@@ -16,4 +16,19 @@ def search_golfpedia(query: str) -> str:
16
  tavily = get_tavily_client()
17
  result = tavily.search(query=query, search_depth="basic")
18
  debug_print(f"[TOOL RESULT] search_golfpedia: {json.dumps(result, indent=2)}")
19
- return result["answer"] if "answer" in result else NO_SUMMARY_AVAILABLE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  tavily = get_tavily_client()
17
  result = tavily.search(query=query, search_depth="basic")
18
  debug_print(f"[TOOL RESULT] search_golfpedia: {json.dumps(result, indent=2)}")
19
+
20
+ # If there's a direct answer, return it
21
+ if result.get("answer"):
22
+ return result["answer"]
23
+
24
+ # If there are search results, format them into a readable string
25
+ if result.get("results") and len(result["results"]) > 0:
26
+ formatted_results = "Here are some relevant search results:\n\n"
27
+ for i, item in enumerate(result["results"][:5], 1): # Limit to top 5 results
28
+ formatted_results += f"{i}. {item.get('title', 'No title')}\n"
29
+ formatted_results += f" {item.get('content', 'No content')}\n"
30
+ formatted_results += f" Source: {item.get('url', 'No URL')}\n\n"
31
+ return formatted_results
32
+
33
+ # If no answer and no results, return the default message
34
+ return NO_SUMMARY_AVAILABLE
frontend/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>TMD - Pythonic RAG Chat</title>
7
  </head>
8
  <body>
9
  <div id="root"></div>
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>TMD - Golf Caddie AI</title>
7
  </head>
8
  <body>
9
  <div id="root"></div>
frontend/package-lock.json CHANGED
@@ -2586,13 +2586,13 @@
2586
  }
2587
  },
2588
  "node_modules/tinyglobby": {
2589
- "version": "0.2.12",
2590
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
2591
- "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
2592
  "dev": true,
2593
  "license": "MIT",
2594
  "dependencies": {
2595
- "fdir": "^6.4.3",
2596
  "picomatch": "^4.0.2"
2597
  },
2598
  "engines": {
@@ -2657,18 +2657,18 @@
2657
  }
2658
  },
2659
  "node_modules/vite": {
2660
- "version": "6.3.2",
2661
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz",
2662
- "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==",
2663
  "dev": true,
2664
  "license": "MIT",
2665
  "dependencies": {
2666
  "esbuild": "^0.25.0",
2667
- "fdir": "^6.4.3",
2668
  "picomatch": "^4.0.2",
2669
  "postcss": "^8.5.3",
2670
  "rollup": "^4.34.9",
2671
- "tinyglobby": "^0.2.12"
2672
  },
2673
  "bin": {
2674
  "vite": "bin/vite.js"
 
2586
  }
2587
  },
2588
  "node_modules/tinyglobby": {
2589
+ "version": "0.2.13",
2590
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
2591
+ "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
2592
  "dev": true,
2593
  "license": "MIT",
2594
  "dependencies": {
2595
+ "fdir": "^6.4.4",
2596
  "picomatch": "^4.0.2"
2597
  },
2598
  "engines": {
 
2657
  }
2658
  },
2659
  "node_modules/vite": {
2660
+ "version": "6.3.5",
2661
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
2662
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
2663
  "dev": true,
2664
  "license": "MIT",
2665
  "dependencies": {
2666
  "esbuild": "^0.25.0",
2667
+ "fdir": "^6.4.4",
2668
  "picomatch": "^4.0.2",
2669
  "postcss": "^8.5.3",
2670
  "rollup": "^4.34.9",
2671
+ "tinyglobby": "^0.2.13"
2672
  },
2673
  "bin": {
2674
  "vite": "bin/vite.js"
frontend/src/App.css CHANGED
@@ -123,3 +123,34 @@
123
  .question-input-row button:hover:not(:disabled) {
124
  background-color: var(--primary-dark);
125
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  .question-input-row button:hover:not(:disabled) {
124
  background-color: var(--primary-dark);
125
  }
126
+
127
+ .response-panel {
128
+ flex: 1;
129
+ overflow-y: auto;
130
+ padding: 1rem;
131
+ background: #f5f5f5;
132
+ border-radius: 8px;
133
+ margin-bottom: 1rem;
134
+ min-height: 200px;
135
+ max-height: 60vh;
136
+ }
137
+
138
+ .message {
139
+ margin-bottom: 1rem;
140
+ padding: 0.75rem;
141
+ border-radius: 8px;
142
+ max-width: 80%;
143
+ word-wrap: break-word;
144
+ }
145
+
146
+ .user-message {
147
+ background-color: #007bff;
148
+ color: white;
149
+ margin-left: auto;
150
+ }
151
+
152
+ .ai-message {
153
+ background-color: #e9ecef;
154
+ color: #212529;
155
+ margin-right: auto;
156
+ }
frontend/src/App.jsx CHANGED
@@ -1,33 +1,21 @@
1
- import FileUploader from './components/FileUploader'
2
  import ChatBox from './components/ChatBox'
3
- import { useState } from 'react'
4
  import './App.css' // Layout styles
 
5
 
6
  function App() {
7
- const [fileUploaded, setFileUploaded] = useState(false)
8
-
9
  return (
10
- <div className={`app-root ${fileUploaded ? 'chat-mode' : 'initial-mode'}`}>
11
  <div className="branding">
12
- {/* TODO: Replace branding logo and app title with your own */}
13
  <img
14
- src="/logo_light_transparent.png"
15
- alt="Your Logo"
16
  className="logo"
17
  />
18
- <h1>AI Agent Chat Template</h1>
19
  </div>
20
 
21
- {fileUploaded && (
22
- <div className="chat-section">
23
- {/* TODO: Customize ChatBox behavior, styling, or replace it entirely */}
24
- <ChatBox />
25
- </div>
26
- )}
27
-
28
- <div className="footer-uploader">
29
- {/* TODO: Replace FileUploader or bypass if not needed */}
30
- <FileUploader onUploadSuccess={() => setFileUploaded(true)} />
31
  </div>
32
  </div>
33
  )
 
 
1
  import ChatBox from './components/ChatBox'
 
2
  import './App.css' // Layout styles
3
+ import logo from './assets/logo_light_transparent.png'
4
 
5
  function App() {
 
 
6
  return (
7
+ <div className="app-root chat-mode">
8
  <div className="branding">
 
9
  <img
10
+ src={logo}
11
+ alt="Golf Caddie Logo"
12
  className="logo"
13
  />
14
+ <h1>Golf Caddie AI</h1>
15
  </div>
16
 
17
+ <div className="chat-section">
18
+ <ChatBox />
 
 
 
 
 
 
 
 
19
  </div>
20
  </div>
21
  )
frontend/src/assets/logo_light_transparent.png ADDED
frontend/src/components/ChatBox.jsx CHANGED
@@ -1,39 +1,49 @@
1
- import { useState } from 'react'
2
  import { getApiUrl } from '../utils/env'
3
 
4
  export default function ChatBox() {
5
  const [question, setQuestion] = useState('')
6
- const [response, setResponse] = useState('')
7
  const [isStreaming, setIsStreaming] = useState(false)
8
- const [endpoint, setEndpoint] = useState('/ask') // or '/agent'
 
 
 
 
 
 
 
 
9
 
10
  const askQuestion = async (e) => {
11
  e.preventDefault()
12
  if (!question.trim()) return
13
 
 
 
 
 
14
  setIsStreaming(true)
15
- setResponse('')
16
 
17
  try {
18
- const formData = new FormData()
19
- formData.append(endpoint === '/ask' ? 'question' : 'query', question) // flexible
20
-
21
- const res = await fetch(`${getApiUrl()}${endpoint}`, {
22
  method: 'POST',
23
- body: formData,
 
 
 
24
  })
25
 
26
  if (!res.ok) {
27
- // Try to parse the error response
28
  try {
29
  const errorData = await res.json()
30
  throw new Error(errorData.error || `HTTP error! status: ${res.status}`)
31
  } catch (e) {
32
- // If parsing fails, use the status text
33
  throw new Error(`HTTP error! status: ${res.status}`)
34
  }
35
  }
36
 
 
37
  if (res.body) {
38
  const reader = res.body.getReader()
39
  const decoder = new TextDecoder('utf-8')
@@ -42,29 +52,67 @@ export default function ChatBox() {
42
  if (done) break
43
  const text = decoder.decode(value)
44
  try {
45
- const parsed = JSON.parse(text);
46
- setResponse(prev => prev + (parsed.response || text));
 
 
 
 
 
 
 
 
 
 
47
  } catch {
48
- setResponse(prev => prev + text);
 
 
 
 
 
 
 
 
 
49
  }
50
  }
51
  } else {
52
  const data = await res.json()
53
- setResponse(data.response || 'No response received')
 
54
  }
55
  } catch (error) {
56
  console.error('Error:', error)
57
- setResponse('Error: ' + error.message)
58
  } finally {
59
  setIsStreaming(false)
60
  }
61
  }
62
 
 
 
 
 
 
 
 
63
  return (
64
  <>
65
- {/* TODO: Customize this UI based on your agent or query-based backend */}
66
  <div className="response-panel" data-testid="response-panel">
67
- {response || 'Response will appear here...'}
 
 
 
 
 
 
 
 
 
 
 
 
68
  </div>
69
 
70
  <div className="question-input-row">
@@ -73,18 +121,10 @@ export default function ChatBox() {
73
  rows={3}
74
  value={question}
75
  onChange={(e) => setQuestion(e.target.value)}
76
- placeholder="Ask a question..."
 
77
  />
78
 
79
- <select
80
- value={endpoint}
81
- onChange={(e) => setEndpoint(e.target.value)}
82
- disabled={isStreaming}
83
- >
84
- <option value="/ask">RAG (/ask)</option>
85
- <option value="/agent">Agent (/agent)</option>
86
- </select>
87
-
88
  <button onClick={askQuestion} disabled={isStreaming}>
89
  {isStreaming ? 'Thinking…' : 'Ask'}
90
  </button>
 
1
+ import { useState, useRef, useEffect } from 'react'
2
  import { getApiUrl } from '../utils/env'
3
 
4
  export default function ChatBox() {
5
  const [question, setQuestion] = useState('')
6
+ const [messages, setMessages] = useState([])
7
  const [isStreaming, setIsStreaming] = useState(false)
8
+ const chatEndRef = useRef(null)
9
+
10
+ const scrollToBottom = () => {
11
+ chatEndRef.current?.scrollIntoView({ behavior: "smooth" })
12
+ }
13
+
14
+ useEffect(() => {
15
+ scrollToBottom()
16
+ }, [messages])
17
 
18
  const askQuestion = async (e) => {
19
  e.preventDefault()
20
  if (!question.trim()) return
21
 
22
+ // Add user message to chat
23
+ const userMessage = { type: 'user', content: question }
24
+ setMessages(prev => [...prev, userMessage])
25
+ setQuestion('') // Clear the input
26
  setIsStreaming(true)
 
27
 
28
  try {
29
+ const res = await fetch(`${getApiUrl()}/agent`, {
 
 
 
30
  method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ },
34
+ body: JSON.stringify({ query: question }),
35
  })
36
 
37
  if (!res.ok) {
 
38
  try {
39
  const errorData = await res.json()
40
  throw new Error(errorData.error || `HTTP error! status: ${res.status}`)
41
  } catch (e) {
 
42
  throw new Error(`HTTP error! status: ${res.status}`)
43
  }
44
  }
45
 
46
+ let aiResponse = ''
47
  if (res.body) {
48
  const reader = res.body.getReader()
49
  const decoder = new TextDecoder('utf-8')
 
52
  if (done) break
53
  const text = decoder.decode(value)
54
  try {
55
+ const parsed = JSON.parse(text)
56
+ const responseText = parsed.response || parsed.final_response || text
57
+ aiResponse += responseText
58
+ setMessages(prev => {
59
+ const newMessages = [...prev]
60
+ if (newMessages[newMessages.length - 1]?.type === 'ai') {
61
+ newMessages[newMessages.length - 1].content = aiResponse
62
+ } else {
63
+ newMessages.push({ type: 'ai', content: aiResponse })
64
+ }
65
+ return newMessages
66
+ })
67
  } catch {
68
+ aiResponse += text
69
+ setMessages(prev => {
70
+ const newMessages = [...prev]
71
+ if (newMessages[newMessages.length - 1]?.type === 'ai') {
72
+ newMessages[newMessages.length - 1].content = aiResponse
73
+ } else {
74
+ newMessages.push({ type: 'ai', content: aiResponse })
75
+ }
76
+ return newMessages
77
+ })
78
  }
79
  }
80
  } else {
81
  const data = await res.json()
82
+ const responseText = data.response || data.final_response || 'No response received'
83
+ setMessages(prev => [...prev, { type: 'ai', content: responseText }])
84
  }
85
  } catch (error) {
86
  console.error('Error:', error)
87
+ setMessages(prev => [...prev, { type: 'ai', content: 'Error: ' + error.message }])
88
  } finally {
89
  setIsStreaming(false)
90
  }
91
  }
92
 
93
+ const handleKeyDown = (e) => {
94
+ if (e.key === 'Enter' && !e.shiftKey) {
95
+ e.preventDefault()
96
+ askQuestion(e)
97
+ }
98
+ }
99
+
100
  return (
101
  <>
 
102
  <div className="response-panel" data-testid="response-panel">
103
+ {messages.length === 0 ? (
104
+ 'Ask me anything about golf...'
105
+ ) : (
106
+ messages.map((message, index) => (
107
+ <div
108
+ key={index}
109
+ className={`message ${message.type === 'user' ? 'user-message' : 'ai-message'}`}
110
+ >
111
+ {message.content}
112
+ </div>
113
+ ))
114
+ )}
115
+ <div ref={chatEndRef} />
116
  </div>
117
 
118
  <div className="question-input-row">
 
121
  rows={3}
122
  value={question}
123
  onChange={(e) => setQuestion(e.target.value)}
124
+ onKeyDown={handleKeyDown}
125
+ placeholder="Ask a question about golf... (Press Enter to submit, Shift+Enter for new line)"
126
  />
127
 
 
 
 
 
 
 
 
 
 
128
  <button onClick={askQuestion} disabled={isStreaming}>
129
  {isStreaming ? 'Thinking…' : 'Ask'}
130
  </button>
frontend/src/components/FileUploader.jsx DELETED
@@ -1,75 +0,0 @@
1
- import { useState } from 'react'
2
- import { getApiUrl } from '../utils/env'
3
-
4
- function FileUploader({ onUploadSuccess }) {
5
- const [file, setFile] = useState(null)
6
- const [uploading, setUploading] = useState(false)
7
- const [message, setMessage] = useState('')
8
-
9
- const handleFileChange = (e) => {
10
- setFile(e.target.files[0])
11
- setMessage('')
12
- }
13
-
14
- const uploadFile = async (e) => {
15
- e.preventDefault()
16
- if (!file) {
17
- setMessage('Please select a file first')
18
- return
19
- }
20
-
21
- setUploading(true)
22
- setMessage('')
23
-
24
- const formData = new FormData()
25
- formData.append('file', file)
26
-
27
- try {
28
- // TODO: This endpoint expects a backend route POST /upload
29
- const response = await fetch(`${getApiUrl()}/upload`, {
30
- method: 'POST',
31
- body: formData,
32
- })
33
-
34
- if (!response.ok) {
35
- throw new Error(`Upload failed with status: ${response.status}`)
36
- }
37
-
38
- const data = await response.json()
39
- setMessage(data.message || 'Upload successful!')
40
- setFile(null)
41
- if (onUploadSuccess) onUploadSuccess()
42
- } catch (error) {
43
- console.error('Upload error:', error)
44
- setMessage('Upload error: ' + error.message)
45
- } finally {
46
- setUploading(false)
47
- }
48
- }
49
-
50
- return (
51
- <div className="file-uploader">
52
- {/* TODO: Replace or style this component for your use case */}
53
- <form onSubmit={uploadFile} role="form">
54
- <label htmlFor="file-input">File:</label>
55
- <input
56
- id="file-input"
57
- type="file"
58
- onChange={handleFileChange}
59
- disabled={uploading}
60
- />
61
- <button type="submit" disabled={!file || uploading}>
62
- {uploading ? 'Uploading...' : 'Upload'}
63
- </button>
64
- </form>
65
-
66
- {message && (
67
- <p className="message" data-testid="upload-message">
68
- {message}
69
- </p>
70
- )}
71
- </div>
72
- )
73
- }
74
-
75
- export default FileUploader
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -16,6 +16,7 @@ dependencies = [
16
  "langchain-community>=0.0.22",
17
  "langchain-openai>=0.0.8",
18
  "pytest>=7.0.0", # Added pytest dependency
 
19
  "tavily-python>=0.3.0", # Added tavily dependency
20
  "langgraph>=0.0.15", # Added langgraph dependency
21
  ]
 
16
  "langchain-community>=0.0.22",
17
  "langchain-openai>=0.0.8",
18
  "pytest>=7.0.0", # Added pytest dependency
19
+ "pytest-asyncio>=0.23.0", # Added pytest-asyncio dependency
20
  "tavily-python>=0.3.0", # Added tavily dependency
21
  "langgraph>=0.0.15", # Added langgraph dependency
22
  ]
uv.lock CHANGED
@@ -845,6 +845,61 @@ wheels = [
845
  { url = "https://files.pythonhosted.org/packages/8b/a3/3696ff2444658053c01b6b7443e761f28bb71217d82bb89137a978c5f66f/langchain_text_splitters-0.3.8-py3-none-any.whl", hash = "sha256:e75cc0f4ae58dcf07d9f18776400cf8ade27fadd4ff6d264df6278bb302f6f02", size = 32440 },
846
  ]
847
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848
  [[package]]
849
  name = "langsmith"
850
  version = "0.3.32"
@@ -872,14 +927,17 @@ dependencies = [
872
  { name = "langchain" },
873
  { name = "langchain-community" },
874
  { name = "langchain-openai" },
 
875
  { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
876
  { name = "numpy", version = "2.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
877
  { name = "openai" },
878
  { name = "pypdf" },
879
  { name = "pypdf2" },
880
  { name = "pytest" },
 
881
  { name = "python-dotenv" },
882
  { name = "python-multipart" },
 
883
  { name = "uvicorn", extra = ["standard"] },
884
  ]
885
 
@@ -889,13 +947,16 @@ requires-dist = [
889
  { name = "langchain", specifier = ">=0.1.0" },
890
  { name = "langchain-community", specifier = ">=0.0.22" },
891
  { name = "langchain-openai", specifier = ">=0.0.8" },
 
892
  { name = "numpy", specifier = ">=1.24.0" },
893
  { name = "openai", specifier = ">=1.0.0" },
894
  { name = "pypdf" },
895
  { name = "pypdf2", specifier = ">=3.0.0" },
896
  { name = "pytest", specifier = ">=7.0.0" },
 
897
  { name = "python-dotenv" },
898
  { name = "python-multipart" },
 
899
  { name = "uvicorn", extras = ["standard"] },
900
  ]
901
 
@@ -1249,6 +1310,54 @@ wheels = [
1249
  { url = "https://files.pythonhosted.org/packages/d7/78/8db408b16d0cf53a3e9d195bd2866759a7dcd5a89a28e3c9d3c8b8f85649/orjson-3.10.16-cp39-cp39-win_amd64.whl", hash = "sha256:c338dc2296d1ed0d5c5c27dfb22d00b330555cb706c2e0be1e1c3940a0895905", size = 133598 },
1250
  ]
1251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1252
  [[package]]
1253
  name = "packaging"
1254
  version = "24.2"
@@ -1560,6 +1669,19 @@ wheels = [
1560
  { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
1561
  ]
1562
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1563
  [[package]]
1564
  name = "python-dotenv"
1565
  version = "1.1.0"
@@ -1818,6 +1940,20 @@ wheels = [
1818
  { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 },
1819
  ]
1820
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1821
  [[package]]
1822
  name = "tenacity"
1823
  version = "9.1.2"
@@ -2183,6 +2319,99 @@ wheels = [
2183
  { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 },
2184
  ]
2185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2186
  [[package]]
2187
  name = "yarl"
2188
  version = "1.20.0"
 
845
  { url = "https://files.pythonhosted.org/packages/8b/a3/3696ff2444658053c01b6b7443e761f28bb71217d82bb89137a978c5f66f/langchain_text_splitters-0.3.8-py3-none-any.whl", hash = "sha256:e75cc0f4ae58dcf07d9f18776400cf8ade27fadd4ff6d264df6278bb302f6f02", size = 32440 },
846
  ]
847
 
848
+ [[package]]
849
+ name = "langgraph"
850
+ version = "0.3.31"
851
+ source = { registry = "https://pypi.org/simple" }
852
+ dependencies = [
853
+ { name = "langchain-core" },
854
+ { name = "langgraph-checkpoint" },
855
+ { name = "langgraph-prebuilt" },
856
+ { name = "langgraph-sdk" },
857
+ { name = "xxhash" },
858
+ ]
859
+ sdist = { url = "https://files.pythonhosted.org/packages/b5/49/f6814bc1b016fd8f403856e986a4a0b35fd9cc34671f2abf649753643194/langgraph-0.3.31.tar.gz", hash = "sha256:c76bbc8f26604929d6c2520d937b5602d5bb1dde2c0580398d015f7f6841f14c", size = 120379 }
860
+ wheels = [
861
+ { url = "https://files.pythonhosted.org/packages/53/0f/c4802c26da60b84af1ee4a1d3013378da2354220aa2d0e766d07c5db1de2/langgraph-0.3.31-py3-none-any.whl", hash = "sha256:f42a6850d03696f2a54d3c833db39aa201cfda78217846a5b8936ad95fe41e6c", size = 145197 },
862
+ ]
863
+
864
+ [[package]]
865
+ name = "langgraph-checkpoint"
866
+ version = "2.0.24"
867
+ source = { registry = "https://pypi.org/simple" }
868
+ dependencies = [
869
+ { name = "langchain-core" },
870
+ { name = "ormsgpack" },
871
+ ]
872
+ sdist = { url = "https://files.pythonhosted.org/packages/0d/df/bacef68562ba4c391ded751eecda8e579ec78a581506064cf625e0ebd93a/langgraph_checkpoint-2.0.24.tar.gz", hash = "sha256:9596dad332344e7e871257be464df8a07c2e9bac66143081b11b9422b0167e5b", size = 37328 }
873
+ wheels = [
874
+ { url = "https://files.pythonhosted.org/packages/bc/60/30397e8fd2b7dead3754aa79d708caff9dbb371f30b4cd21802c60f6b921/langgraph_checkpoint-2.0.24-py3-none-any.whl", hash = "sha256:3836e2909ef2387d1fa8d04ee3e2a353f980d519fd6c649af352676dc73d66b8", size = 42028 },
875
+ ]
876
+
877
+ [[package]]
878
+ name = "langgraph-prebuilt"
879
+ version = "0.1.8"
880
+ source = { registry = "https://pypi.org/simple" }
881
+ dependencies = [
882
+ { name = "langchain-core" },
883
+ { name = "langgraph-checkpoint" },
884
+ ]
885
+ sdist = { url = "https://files.pythonhosted.org/packages/57/30/f31f0e076c37d097b53e4cff5d479a3686e1991f6c86a1a4727d5d1f5489/langgraph_prebuilt-0.1.8.tar.gz", hash = "sha256:4de7659151829b2b955b6798df6800e580e617782c15c2c5b29b139697491831", size = 24543 }
886
+ wheels = [
887
+ { url = "https://files.pythonhosted.org/packages/36/72/9e092665502f8f52f2708065ed14fbbba3f95d1a1b65d62049b0c5fcdf00/langgraph_prebuilt-0.1.8-py3-none-any.whl", hash = "sha256:ae97b828ae00be2cefec503423aa782e1bff165e9b94592e224da132f2526968", size = 25903 },
888
+ ]
889
+
890
+ [[package]]
891
+ name = "langgraph-sdk"
892
+ version = "0.1.63"
893
+ source = { registry = "https://pypi.org/simple" }
894
+ dependencies = [
895
+ { name = "httpx" },
896
+ { name = "orjson" },
897
+ ]
898
+ sdist = { url = "https://files.pythonhosted.org/packages/38/bc/23571ccda8bd442b7090e5f5263fbae439dcfbe7c06c2d51b55d464dfb69/langgraph_sdk-0.1.63.tar.gz", hash = "sha256:62bf2cc31e5aa6c5b9011ee1702bcf1e36e67e142a60bd97af2611162fb58e18", size = 43837 }
899
+ wheels = [
900
+ { url = "https://files.pythonhosted.org/packages/5a/1f/f1462fef67ddca45b2e4dd1cfbb452aa4b6532edaf77b99f9588406229b0/langgraph_sdk-0.1.63-py3-none-any.whl", hash = "sha256:6fb78a7fc6a30eea43bd0d6401dbc9e3263d0d4c03f63c04035980da7e586b05", size = 47343 },
901
+ ]
902
+
903
  [[package]]
904
  name = "langsmith"
905
  version = "0.3.32"
 
927
  { name = "langchain" },
928
  { name = "langchain-community" },
929
  { name = "langchain-openai" },
930
+ { name = "langgraph" },
931
  { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
932
  { name = "numpy", version = "2.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
933
  { name = "openai" },
934
  { name = "pypdf" },
935
  { name = "pypdf2" },
936
  { name = "pytest" },
937
+ { name = "pytest-asyncio" },
938
  { name = "python-dotenv" },
939
  { name = "python-multipart" },
940
+ { name = "tavily-python" },
941
  { name = "uvicorn", extra = ["standard"] },
942
  ]
943
 
 
947
  { name = "langchain", specifier = ">=0.1.0" },
948
  { name = "langchain-community", specifier = ">=0.0.22" },
949
  { name = "langchain-openai", specifier = ">=0.0.8" },
950
+ { name = "langgraph", specifier = ">=0.0.15" },
951
  { name = "numpy", specifier = ">=1.24.0" },
952
  { name = "openai", specifier = ">=1.0.0" },
953
  { name = "pypdf" },
954
  { name = "pypdf2", specifier = ">=3.0.0" },
955
  { name = "pytest", specifier = ">=7.0.0" },
956
+ { name = "pytest-asyncio", specifier = ">=0.23.0" },
957
  { name = "python-dotenv" },
958
  { name = "python-multipart" },
959
+ { name = "tavily-python", specifier = ">=0.3.0" },
960
  { name = "uvicorn", extras = ["standard"] },
961
  ]
962
 
 
1310
  { url = "https://files.pythonhosted.org/packages/d7/78/8db408b16d0cf53a3e9d195bd2866759a7dcd5a89a28e3c9d3c8b8f85649/orjson-3.10.16-cp39-cp39-win_amd64.whl", hash = "sha256:c338dc2296d1ed0d5c5c27dfb22d00b330555cb706c2e0be1e1c3940a0895905", size = 133598 },
1311
  ]
1312
 
1313
+ [[package]]
1314
+ name = "ormsgpack"
1315
+ version = "1.9.1"
1316
+ source = { registry = "https://pypi.org/simple" }
1317
+ sdist = { url = "https://files.pythonhosted.org/packages/25/a7/462cf8ff5e29241868b82d3a5ec124d690eb6a6a5c6fa5bb1367b839e027/ormsgpack-1.9.1.tar.gz", hash = "sha256:3da6e63d82565e590b98178545e64f0f8506137b92bd31a2d04fd7c82baf5794", size = 56887 }
1318
+ wheels = [
1319
+ { url = "https://files.pythonhosted.org/packages/7f/32/5f504c0695ff96aaaf0452bee522d79b5a3ee809f22fd77fdb0dd5756d86/ormsgpack-1.9.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f1f804fd9c0fd84213a6022c34172f82323b34afa7052a4af18797582cf56365", size = 382793 },
1320
+ { url = "https://files.pythonhosted.org/packages/1e/c6/64fe1270271b61495611f1d3068baedb57d76e0f93ce7156f3763fb79b32/ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eab5cec99c46276b37071d570aab98603f3d0309b3818da3247eb64bb95e5cfc", size = 213974 },
1321
+ { url = "https://files.pythonhosted.org/packages/13/56/6666d6a9b82c7d2021fce6823ff823bc373a4e7280979c1b453317678fbc/ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c12c6bb30e6df6fc0213b77f0a5e143f371d618be2e8eb4d555340ce01c6900", size = 217200 },
1322
+ { url = "https://files.pythonhosted.org/packages/5f/fb/b844ed1e69d8615163525a8403d7abd3548b3fbfa0f3a973808f36145a0f/ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994d4bbb7ee333264a3e55e30ccee063df6635d785f21a08bf52f67821454a51", size = 223648 },
1323
+ { url = "https://files.pythonhosted.org/packages/f2/26/c40c3e300f9c61a5ed7a6921656dd0d2907a8174936e1e643677585e497c/ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a668a584cf4bb6e1a6ef5a35f3f0d0fdae80cfb7237344ad19a50cce8c79317b", size = 394197 },
1324
+ { url = "https://files.pythonhosted.org/packages/2d/4c/7a4ae187f18e7abf7ab0662b473264a60a5aa4e9bff266f541a8855df163/ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:aaf77699203822638014c604d100f132583844d4fd01eb639a2266970c02cfdf", size = 480550 },
1325
+ { url = "https://files.pythonhosted.org/packages/b1/33/5c465dfd5571f816835bb9e371987bf081b529c64ef28a72d18b0b59902d/ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:003d7e1992b447898caf25a820b3037ec68a57864b3e2f34b64693b7d60a9984", size = 396955 },
1326
+ { url = "https://files.pythonhosted.org/packages/8f/fd/8f64f477b5c6d66e9c6343d7d3f32d7063ba20ab151dd36884e6504899ab/ormsgpack-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:67fefc77e4ba9469f79426769eb4c78acf21f22bef3ab1239a72dd728036ffc2", size = 125102 },
1327
+ { url = "https://files.pythonhosted.org/packages/d8/3b/388e7915a28db6ab3daedfd4937bd7b063c50dd1543068daa31c0a3b70ed/ormsgpack-1.9.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:16eaf32c33ab4249e242181d59e2509b8e0330d6f65c1d8bf08c3dea38fd7c02", size = 382794 },
1328
+ { url = "https://files.pythonhosted.org/packages/0f/b4/3f4afba058822bf69b274e0defe507056be0340e65363c3ebcd312b01b84/ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c70f2e5b2f9975536e8f7936a9721601dc54febe363d2d82f74c9b31d4fe1c65", size = 213974 },
1329
+ { url = "https://files.pythonhosted.org/packages/bf/be/f0e21366d51b6e28fc3a55425be6a125545370d3479bf25be081e83ee236/ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:17c9e18b07d69e3db2e0f8af4731040175e11bdfde78ad8e28126e9e66ec5167", size = 217200 },
1330
+ { url = "https://files.pythonhosted.org/packages/cc/90/67a23c1c880a6e5552acb45f9555b642528f89c8bcf75283a2ea64ef7175/ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73538d749096bb6470328601a2be8f7bdec28849ec6fd19595c232a5848d7124", size = 223649 },
1331
+ { url = "https://files.pythonhosted.org/packages/80/ad/116c1f970b5b4453e4faa52645517a2e5eaf1ab385ba09a5c54253d07d0e/ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:827ff71de228cfd6d07b9d6b47911aa61b1e8dc995dec3caf8fdcdf4f874bcd0", size = 394200 },
1332
+ { url = "https://files.pythonhosted.org/packages/c5/a2/b224a5ef193628a15205e473179276b87e8290d321693e4934a05cbd6ccf/ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7307f808b3df282c8e8ed92c6ebceeb3eea3d8eeec808438f3f212226b25e217", size = 480551 },
1333
+ { url = "https://files.pythonhosted.org/packages/b5/f4/a0f528196af6ab46e6c3f3051cf7403016bdc7b7d3e673ea5b04b145be98/ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f30aad7fb083bed1c540a3c163c6a9f63a94e3c538860bf8f13386c29b560ad5", size = 396959 },
1334
+ { url = "https://files.pythonhosted.org/packages/bc/6b/60c6f4787e3e93f5eb34fccb163753a8771465983a579e3405152f2422fd/ormsgpack-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:829a1b4c5bc3c38ece0c55cf91ebc09c3b987fceb24d3f680c2bcd03fd3789a4", size = 125100 },
1335
+ { url = "https://files.pythonhosted.org/packages/dd/f1/155a598cc8030526ccaaf91ba4d61530f87900645559487edba58b0a90a2/ormsgpack-1.9.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1ede445fc3fdba219bb0e0d1f289df26a9c7602016b7daac6fafe8fe4e91548f", size = 383225 },
1336
+ { url = "https://files.pythonhosted.org/packages/23/1c/ef3097ba550fad55c79525f461febdd4e0d9cc18d065248044536f09488e/ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db50b9f918e25b289114312ed775794d0978b469831b992bdc65bfe20b91fe30", size = 214056 },
1337
+ { url = "https://files.pythonhosted.org/packages/27/77/64d0da25896b2cbb99505ca518c109d7dd1964d7fde14c10943731738b60/ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c7d8fc58e4333308f58ec720b1ee6b12b2b3fe2d2d8f0766ab751cb351e8757", size = 217339 },
1338
+ { url = "https://files.pythonhosted.org/packages/6c/10/c3a7fd0a0068b0bb52cccbfeb5656db895d69e895a3abbc210c4b3f98ff8/ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeee6d08c040db265cb8563444aba343ecb32cbdbe2414a489dcead9f70c6765", size = 223816 },
1339
+ { url = "https://files.pythonhosted.org/packages/43/e7/aee1238dba652f2116c2523d36fd1c5f9775436032be5c233108fd2a1415/ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2fbb8181c198bdc413a4e889e5200f010724eea4b6d5a9a7eee2df039ac04aca", size = 394287 },
1340
+ { url = "https://files.pythonhosted.org/packages/c7/09/1b452a92376f29d7a2da7c18fb01cf09978197a8eccbb8b204e72fd5a970/ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:16488f094ac0e2250cceea6caf72962614aa432ee11dd57ef45e1ad25ece3eff", size = 480709 },
1341
+ { url = "https://files.pythonhosted.org/packages/de/13/7fa9fee5a73af8a73a42bf8c2e69489605714f65f5a41454400a05e84a3b/ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:422d960bfd6ad88be20794f50ec7953d8f7a0f2df60e19d0e8feb994e2ed64ee", size = 397247 },
1342
+ { url = "https://files.pythonhosted.org/packages/a1/2d/2e87cb28110db0d3bb750edd4d8719b5068852a2eef5e96b0bf376bb8a81/ormsgpack-1.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:e6e2f9eab527cf43fb4a4293e493370276b1c8716cf305689202d646c6a782ef", size = 125368 },
1343
+ { url = "https://files.pythonhosted.org/packages/b8/54/0390d5d092831e4df29dbafe32402891fc14b3e6ffe5a644b16cbbc9d9bc/ormsgpack-1.9.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ac61c18d9dd085e8519b949f7e655f7fb07909fd09c53b4338dd33309012e289", size = 383226 },
1344
+ { url = "https://files.pythonhosted.org/packages/47/64/8b15d262d1caefead8fb22ec144f5ff7d9505fc31c22bc34598053d46fbe/ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134840b8c6615da2c24ce77bd12a46098015c808197a9995c7a2d991e1904eec", size = 214057 },
1345
+ { url = "https://files.pythonhosted.org/packages/57/00/65823609266bad4d5ed29ea753d24a3bdb01c7edaf923da80967fc31f9c5/ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38fd42618f626394b2c7713c5d4bcbc917254e9753d5d4cde460658b51b11a74", size = 217340 },
1346
+ { url = "https://files.pythonhosted.org/packages/a0/51/e535c50f7f87b49110233647f55300d7975139ef5e51f1adb4c55f58c124/ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d36397333ad07b9eba4c2e271fa78951bd81afc059c85a6e9f6c0eb2de07cda", size = 223815 },
1347
+ { url = "https://files.pythonhosted.org/packages/0c/ee/393e4a6de2a62124bf589602648f295a9fb3907a0e2fe80061b88899d072/ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:603063089597917d04e4c1b1d53988a34f7dc2ff1a03adcfd1cf4ae966d5fba6", size = 394287 },
1348
+ { url = "https://files.pythonhosted.org/packages/c6/d8/e56d7c3cb73a0e533e3e2a21ae5838b2aa36a9dac1ca9c861af6bae5a369/ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:94bbf2b185e0cb721ceaba20e64b7158e6caf0cecd140ca29b9f05a8d5e91e2f", size = 480707 },
1349
+ { url = "https://files.pythonhosted.org/packages/e6/e0/6a3c6a6dc98583a721c54b02f5195bde8f801aebdeda9b601fa2ab30ad39/ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38f380b1e8c96a712eb302b9349347385161a8e29046868ae2bfdfcb23e2692", size = 397246 },
1350
+ { url = "https://files.pythonhosted.org/packages/b0/60/0ee5d790f13507e1f75ac21fc82dc1ef29afe1f520bd0f249d65b2f4839b/ormsgpack-1.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:a4bc63fb30db94075611cedbbc3d261dd17cf2aa8ff75a0fd684cd45ca29cb1b", size = 125371 },
1351
+ { url = "https://files.pythonhosted.org/packages/85/02/ac4a2263c9aad0d455f240e1bdd41b443e5452257cf13bc188177b0dfd1f/ormsgpack-1.9.1-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e95909248bece8e88a310a913838f17ff5a39190aa4e61de909c3cd27f59744b", size = 382789 },
1352
+ { url = "https://files.pythonhosted.org/packages/81/6f/e50d070ae3a6aa7cb50849d0796ac6d72c0f8f01d5a42438c9567ab352e3/ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3939188810c5c641d6b207f29994142ae2b1c70534f7839bbd972d857ac2072", size = 213967 },
1353
+ { url = "https://files.pythonhosted.org/packages/55/1d/379734bca4f2d71ce11c7096d85276280cf13d1bb7243bf809b171c25cda/ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b6476344a585aea00a2acc9fd07355bf2daac04062cfdd480fa83ec3e2403b", size = 217183 },
1354
+ { url = "https://files.pythonhosted.org/packages/9a/f6/036a44ada8659b1729db5f20ba50dc1945a84a50cd4fa6b3a74d0f16fab9/ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d8b9d53da82b31662ce5a3834b65479cf794a34befb9fc50baa51518383250", size = 223647 },
1355
+ { url = "https://files.pythonhosted.org/packages/b9/61/5c8671ab3b7cac21076169972bad9f4faa1fdda1f70e0ae78b365894b164/ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3933d4b0c0d404ee234dbc372836d6f2d2f4b6330c2a2fb9709ba4eaebfae7ba", size = 394232 },
1356
+ { url = "https://files.pythonhosted.org/packages/7b/70/6e9ba8c8c405dee5dffd86edf1188ef1a116421598e18df89dac0c499aae/ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f824e94a7969f0aee9a6847ec232cf731a03b8734951c2a774dd4762308ea2d2", size = 480582 },
1357
+ { url = "https://files.pythonhosted.org/packages/a6/e1/2113022dd9236cea13f0ba850a5f8a640c0c9e3b07bfbbaf01409190068b/ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c1f3f2295374020f9650e4aa7af6403ff016a0d92778b4a48bb3901fd801232d", size = 396952 },
1358
+ { url = "https://files.pythonhosted.org/packages/01/d4/58ca5de3124ac975dae1a96a475c3cb9ed70c70dfba39fd4ceca53838ee6/ormsgpack-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:92eb1b4f7b168da47f547329b4b58d16d8f19508a97ce5266567385d42d81968", size = 125107 },
1359
+ ]
1360
+
1361
  [[package]]
1362
  name = "packaging"
1363
  version = "24.2"
 
1669
  { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
1670
  ]
1671
 
1672
+ [[package]]
1673
+ name = "pytest-asyncio"
1674
+ version = "0.26.0"
1675
+ source = { registry = "https://pypi.org/simple" }
1676
+ dependencies = [
1677
+ { name = "pytest" },
1678
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
1679
+ ]
1680
+ sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156 }
1681
+ wheels = [
1682
+ { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694 },
1683
+ ]
1684
+
1685
  [[package]]
1686
  name = "python-dotenv"
1687
  version = "1.1.0"
 
1940
  { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 },
1941
  ]
1942
 
1943
+ [[package]]
1944
+ name = "tavily-python"
1945
+ version = "0.5.4"
1946
+ source = { registry = "https://pypi.org/simple" }
1947
+ dependencies = [
1948
+ { name = "httpx" },
1949
+ { name = "requests" },
1950
+ { name = "tiktoken" },
1951
+ ]
1952
+ sdist = { url = "https://files.pythonhosted.org/packages/2c/72/f36a49c2fd344b45e6f1ddab59a7f809bff4f46dcc78af7ac9758461a5bf/tavily_python-0.5.4.tar.gz", hash = "sha256:fdad5303f9f6603a06fddcc7e21b128bebc1adf7694e553a664caf87eb2d2d9d", size = 109050 }
1953
+ wheels = [
1954
+ { url = "https://files.pythonhosted.org/packages/2f/dd/63d1d2fddcaaee040745ea58d13b54c0b1483260ddae52eb59abf691f757/tavily_python-0.5.4-py3-none-any.whl", hash = "sha256:47f8c0b41283d44849fe9531596cd26d3de42a59618ef66f9e1244d8fedba404", size = 44419 },
1955
+ ]
1956
+
1957
  [[package]]
1958
  name = "tenacity"
1959
  version = "9.1.2"
 
2319
  { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 },
2320
  ]
2321
 
2322
+ [[package]]
2323
+ name = "xxhash"
2324
+ version = "3.5.0"
2325
+ source = { registry = "https://pypi.org/simple" }
2326
+ sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 }
2327
+ wheels = [
2328
+ { url = "https://files.pythonhosted.org/packages/bb/8a/0e9feca390d512d293afd844d31670e25608c4a901e10202aa98785eab09/xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212", size = 31970 },
2329
+ { url = "https://files.pythonhosted.org/packages/16/e6/be5aa49580cd064a18200ab78e29b88b1127e1a8c7955eb8ecf81f2626eb/xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520", size = 30801 },
2330
+ { url = "https://files.pythonhosted.org/packages/20/ee/b8a99ebbc6d1113b3a3f09e747fa318c3cde5b04bd9c197688fadf0eeae8/xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680", size = 220927 },
2331
+ { url = "https://files.pythonhosted.org/packages/58/62/15d10582ef159283a5c2b47f6d799fc3303fe3911d5bb0bcc820e1ef7ff4/xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da", size = 200360 },
2332
+ { url = "https://files.pythonhosted.org/packages/23/41/61202663ea9b1bd8e53673b8ec9e2619989353dba8cfb68e59a9cbd9ffe3/xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23", size = 428528 },
2333
+ { url = "https://files.pythonhosted.org/packages/f2/07/d9a3059f702dec5b3b703737afb6dda32f304f6e9da181a229dafd052c29/xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196", size = 194149 },
2334
+ { url = "https://files.pythonhosted.org/packages/eb/58/27caadf78226ecf1d62dbd0c01d152ed381c14c1ee4ad01f0d460fc40eac/xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c", size = 207703 },
2335
+ { url = "https://files.pythonhosted.org/packages/b1/08/32d558ce23e1e068453c39aed7b3c1cdc690c177873ec0ca3a90d5808765/xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482", size = 216255 },
2336
+ { url = "https://files.pythonhosted.org/packages/3f/d4/2b971e2d2b0a61045f842b622ef11e94096cf1f12cd448b6fd426e80e0e2/xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296", size = 202744 },
2337
+ { url = "https://files.pythonhosted.org/packages/19/ae/6a6438864a8c4c39915d7b65effd85392ebe22710412902487e51769146d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415", size = 210115 },
2338
+ { url = "https://files.pythonhosted.org/packages/48/7d/b3c27c27d1fc868094d02fe4498ccce8cec9fcc591825c01d6bcb0b4fc49/xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198", size = 414247 },
2339
+ { url = "https://files.pythonhosted.org/packages/a1/05/918f9e7d2fbbd334b829997045d341d6239b563c44e683b9a7ef8fe50f5d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442", size = 191419 },
2340
+ { url = "https://files.pythonhosted.org/packages/08/29/dfe393805b2f86bfc47c290b275f0b7c189dc2f4e136fd4754f32eb18a8d/xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da", size = 30114 },
2341
+ { url = "https://files.pythonhosted.org/packages/7b/d7/aa0b22c4ebb7c3ccb993d4c565132abc641cd11164f8952d89eb6a501909/xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9", size = 30003 },
2342
+ { url = "https://files.pythonhosted.org/packages/69/12/f969b81541ee91b55f1ce469d7ab55079593c80d04fd01691b550e535000/xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6", size = 26773 },
2343
+ { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969 },
2344
+ { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800 },
2345
+ { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566 },
2346
+ { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214 },
2347
+ { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433 },
2348
+ { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822 },
2349
+ { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538 },
2350
+ { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953 },
2351
+ { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594 },
2352
+ { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971 },
2353
+ { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050 },
2354
+ { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216 },
2355
+ { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120 },
2356
+ { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003 },
2357
+ { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777 },
2358
+ { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 },
2359
+ { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 },
2360
+ { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 },
2361
+ { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006 },
2362
+ { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326 },
2363
+ { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380 },
2364
+ { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934 },
2365
+ { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301 },
2366
+ { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351 },
2367
+ { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294 },
2368
+ { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674 },
2369
+ { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022 },
2370
+ { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170 },
2371
+ { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040 },
2372
+ { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796 },
2373
+ { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795 },
2374
+ { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792 },
2375
+ { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950 },
2376
+ { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980 },
2377
+ { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324 },
2378
+ { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370 },
2379
+ { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911 },
2380
+ { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352 },
2381
+ { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410 },
2382
+ { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322 },
2383
+ { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725 },
2384
+ { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070 },
2385
+ { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172 },
2386
+ { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041 },
2387
+ { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 },
2388
+ { url = "https://files.pythonhosted.org/packages/d4/f6/531dd6858adf8877675270b9d6989b6dacfd1c2d7135b17584fc29866df3/xxhash-3.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc8cdd7f33d57f0468b0614ae634cc38ab9202c6957a60e31d285a71ebe0301", size = 31971 },
2389
+ { url = "https://files.pythonhosted.org/packages/7c/a8/b2a42b6c9ae46e233f474f3d307c2e7bca8d9817650babeca048d2ad01d6/xxhash-3.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c48b6300cd0b0106bf49169c3e0536408dfbeb1ccb53180068a18b03c662ab", size = 30801 },
2390
+ { url = "https://files.pythonhosted.org/packages/b4/92/9ac297e3487818f429bcf369c1c6a097edf5b56ed6fc1feff4c1882e87ef/xxhash-3.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe1a92cfbaa0a1253e339ccec42dbe6db262615e52df591b68726ab10338003f", size = 220644 },
2391
+ { url = "https://files.pythonhosted.org/packages/86/48/c1426dd3c86fc4a52f983301867463472f6a9013fb32d15991e60c9919b6/xxhash-3.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33513d6cc3ed3b559134fb307aae9bdd94d7e7c02907b37896a6c45ff9ce51bd", size = 200021 },
2392
+ { url = "https://files.pythonhosted.org/packages/f3/de/0ab8c79993765c94fc0d0c1a22b454483c58a0161e1b562f58b654f47660/xxhash-3.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eefc37f6138f522e771ac6db71a6d4838ec7933939676f3753eafd7d3f4c40bc", size = 428217 },
2393
+ { url = "https://files.pythonhosted.org/packages/b4/b4/332647451ed7d2c021294b7c1e9c144dbb5586b1fb214ad4f5a404642835/xxhash-3.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a606c8070ada8aa2a88e181773fa1ef17ba65ce5dd168b9d08038e2a61b33754", size = 193868 },
2394
+ { url = "https://files.pythonhosted.org/packages/f4/1c/a42c0a6cac752f84f7b44a90d1a9fa9047cf70bdba5198a304fde7cc471f/xxhash-3.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42eca420c8fa072cc1dd62597635d140e78e384a79bb4944f825fbef8bfeeef6", size = 207403 },
2395
+ { url = "https://files.pythonhosted.org/packages/c4/d7/04e1b0daae9dc9b02c73c1664cc8aa527498c3f66ccbc586eeb25bbe9f14/xxhash-3.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:604253b2143e13218ff1ef0b59ce67f18b8bd1c4205d2ffda22b09b426386898", size = 215978 },
2396
+ { url = "https://files.pythonhosted.org/packages/c4/f4/05e15e67505228fc19ee98a79e427b3a0b9695f5567cd66ced5d66389883/xxhash-3.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6e93a5ad22f434d7876665444a97e713a8f60b5b1a3521e8df11b98309bff833", size = 202416 },
2397
+ { url = "https://files.pythonhosted.org/packages/94/fb/e9028d3645bba5412a09de13ee36df276a567e60bdb31d499dafa46d76ae/xxhash-3.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7a46e1d6d2817ba8024de44c4fd79913a90e5f7265434cef97026215b7d30df6", size = 209853 },
2398
+ { url = "https://files.pythonhosted.org/packages/02/2c/18c6a622429368274739372d2f86c8125413ec169025c7d8ffb051784bba/xxhash-3.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:30eb2efe6503c379b7ab99c81ba4a779748e3830241f032ab46bd182bf5873af", size = 413926 },
2399
+ { url = "https://files.pythonhosted.org/packages/72/bb/5b55c391084a0321c3809632a018b9b657e59d5966289664f85a645942ac/xxhash-3.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c8aa771ff2c13dd9cda8166d685d7333d389fae30a4d2bb39d63ab5775de8606", size = 191156 },
2400
+ { url = "https://files.pythonhosted.org/packages/86/2b/915049db13401792fec159f57e4f4a5ca7a9768e83ef71d6645b9d0cd749/xxhash-3.5.0-cp39-cp39-win32.whl", hash = "sha256:5ed9ebc46f24cf91034544b26b131241b699edbfc99ec5e7f8f3d02d6eb7fba4", size = 30122 },
2401
+ { url = "https://files.pythonhosted.org/packages/d5/87/382ef7b24917d7cf4c540ee30f29b283bc87ac5893d2f89b23ea3cdf7d77/xxhash-3.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:220f3f896c6b8d0316f63f16c077d52c412619e475f9372333474ee15133a558", size = 30021 },
2402
+ { url = "https://files.pythonhosted.org/packages/e2/47/d06b24e2d9c3dcabccfd734d11b5bbebfdf59ceac2c61509d8205dd20ac6/xxhash-3.5.0-cp39-cp39-win_arm64.whl", hash = "sha256:a7b1d8315d9b5e9f89eb2933b73afae6ec9597a258d52190944437158b49d38e", size = 26780 },
2403
+ { url = "https://files.pythonhosted.org/packages/ab/9a/233606bada5bd6f50b2b72c45de3d9868ad551e83893d2ac86dc7bb8553a/xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c", size = 29732 },
2404
+ { url = "https://files.pythonhosted.org/packages/0c/67/f75276ca39e2c6604e3bee6c84e9db8a56a4973fde9bf35989787cf6e8aa/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986", size = 36214 },
2405
+ { url = "https://files.pythonhosted.org/packages/0f/f8/f6c61fd794229cc3848d144f73754a0c107854372d7261419dcbbd286299/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6", size = 32020 },
2406
+ { url = "https://files.pythonhosted.org/packages/79/d3/c029c99801526f859e6b38d34ab87c08993bf3dcea34b11275775001638a/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b", size = 40515 },
2407
+ { url = "https://files.pythonhosted.org/packages/62/e3/bef7b82c1997579c94de9ac5ea7626d01ae5858aa22bf4fcb38bf220cb3e/xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da", size = 30064 },
2408
+ { url = "https://files.pythonhosted.org/packages/c2/56/30d3df421814947f9d782b20c9b7e5e957f3791cbd89874578011daafcbd/xxhash-3.5.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:531af8845aaadcadf951b7e0c1345c6b9c68a990eeb74ff9acd8501a0ad6a1c9", size = 29734 },
2409
+ { url = "https://files.pythonhosted.org/packages/82/dd/3c42a1f022ad0d82c852d3cb65493ebac03dcfa8c994465a5fb052b00e3c/xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce379bcaa9fcc00f19affa7773084dd09f5b59947b3fb47a1ceb0179f91aaa1", size = 36216 },
2410
+ { url = "https://files.pythonhosted.org/packages/b2/40/8f902ab3bebda228a9b4de69eba988280285a7f7f167b942bc20bb562df9/xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1b2281d01723f076df3c8188f43f2472248a6b63118b036e641243656b1b0f", size = 32042 },
2411
+ { url = "https://files.pythonhosted.org/packages/db/87/bd06beb8ccaa0e9e577c9b909a49cfa5c5cd2ca46034342d72dd9ce5bc56/xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c770750cc80e8694492244bca7251385188bc5597b6a39d98a9f30e8da984e0", size = 40516 },
2412
+ { url = "https://files.pythonhosted.org/packages/bb/f8/505385e2fbd753ddcaafd5550eabe86f6232cbebabad3b2508d411b19153/xxhash-3.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b150b8467852e1bd844387459aa6fbe11d7f38b56e901f9f3b3e6aba0d660240", size = 30108 },
2413
+ ]
2414
+
2415
  [[package]]
2416
  name = "yarl"
2417
  version = "1.20.0"