remove volengine package (#464)

This commit is contained in:
殷逸维
2025-07-23 06:06:57 +08:00
committed by GitHub
parent 32d8e514e1
commit 660395485c
4 changed files with 455 additions and 412 deletions

View File

@@ -33,7 +33,6 @@ dependencies = [
"mcp>=1.6.0", "mcp>=1.6.0",
"langchain-mcp-adapters>=0.0.9", "langchain-mcp-adapters>=0.0.9",
"langchain-deepseek>=0.1.3", "langchain-deepseek>=0.1.3",
"volcengine>=1.0.191",
] ]
[project.optional-dependencies] [project.optional-dependencies]

View File

@@ -4,11 +4,12 @@
import os import os
import requests import requests
import json import json
import hashlib
import hmac
import urllib.parse
from datetime import datetime
from src.rag.retriever import Chunk, Document, Resource, Retriever from src.rag.retriever import Chunk, Document, Resource, Retriever
from urllib.parse import urlparse from urllib.parse import urlparse
from volcengine.auth.SignerV4 import SignerV4
from volcengine.base.Request import Request
from volcengine.Credentials import Credentials
class VikingDBKnowledgeBaseProvider(Retriever): class VikingDBKnowledgeBaseProvider(Retriever):
@@ -20,6 +21,8 @@ class VikingDBKnowledgeBaseProvider(Retriever):
api_ak: str api_ak: str
api_sk: str api_sk: str
retrieval_size: int = 10 retrieval_size: int = 10
region: str = "cn-north-1"
service: str = "air"
def __init__(self): def __init__(self):
api_url = os.getenv("VIKINGDB_KNOWLEDGE_BASE_API_URL") api_url = os.getenv("VIKINGDB_KNOWLEDGE_BASE_API_URL")
@@ -41,41 +44,137 @@ class VikingDBKnowledgeBaseProvider(Retriever):
if retrieval_size: if retrieval_size:
self.retrieval_size = int(retrieval_size) self.retrieval_size = int(retrieval_size)
def prepare_request(self, method, path, params=None, data=None, doseq=0): # 设置region如果需要可以从环境变量获取
""" region = os.getenv("VIKINGDB_KNOWLEDGE_BASE_REGION", "cn-north-1")
Prepare signed request using volcengine auth self.region = region
"""
if params:
for key in params:
if (
type(params[key]) is int
or type(params[key]) is float
or type(params[key]) is bool
):
params[key] = str(params[key])
elif type(params[key]) is list:
if not doseq:
params[key] = ",".join(params[key])
r = Request() def _hmac_sha256(self, key: bytes, content: str) -> bytes:
r.set_shema("https") return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest()
r.set_method(method)
r.set_connection_timeout(10)
r.set_socket_timeout(10)
mheaders = {
"Accept": "application/json",
"Content-Type": "application/json",
}
r.set_headers(mheaders)
if params:
r.set_query(params)
r.set_path(path)
if data is not None:
r.set_body(json.dumps(data))
credentials = Credentials(self.api_ak, self.api_sk, "air", "cn-north-1") def _hash_sha256(self, data: bytes) -> bytes:
SignerV4.sign(r, credentials) return hashlib.sha256(data).digest()
return r
def _get_signed_key(
self, secret_key: str, date: str, region: str, service: str
) -> bytes:
k_date = self._hmac_sha256(secret_key.encode("utf-8"), date)
k_region = self._hmac_sha256(k_date, region)
k_service = self._hmac_sha256(k_region, service)
k_signing = self._hmac_sha256(k_service, "request")
return k_signing
def _create_canonical_request(
self, method: str, path: str, query_params: dict, headers: dict, payload: bytes
) -> str:
canonical_method = method.upper()
canonical_uri = path if path else "/"
if query_params:
encoded_params = []
for key in sorted(query_params.keys()):
value = query_params[key]
encoded_key = urllib.parse.quote(str(key), safe="")
encoded_value = urllib.parse.quote(str(value), safe="")
encoded_params.append(f"{encoded_key}={encoded_value}")
canonical_query_string = "&".join(encoded_params)
else:
canonical_query_string = ""
canonical_headers_list = []
signed_headers_list = []
for header_name in sorted(headers.keys(), key=str.lower):
header_name_lower = header_name.lower()
header_value = str(headers[header_name]).strip()
canonical_headers_list.append(f"{header_name_lower}:{header_value}")
signed_headers_list.append(header_name_lower)
canonical_headers = "\n".join(canonical_headers_list) + "\n"
signed_headers = ";".join(signed_headers_list)
payload_hash = self._hash_sha256(payload).hex()
canonical_request = "\n".join(
[
canonical_method,
canonical_uri,
canonical_query_string,
canonical_headers,
signed_headers,
payload_hash,
]
)
return canonical_request, signed_headers
def _create_signature(
self, method: str, path: str, query_params: dict, headers: dict, payload: bytes
) -> str:
now = datetime.utcnow()
date_stamp = now.strftime("%Y%m%dT%H%M%SZ")
auth_date = date_stamp[:8]
headers["X-Date"] = date_stamp
headers["Host"] = self.api_url.replace("https://", "").replace("http://", "")
headers["X-Content-Sha256"] = self._hash_sha256(payload).hex()
headers["Content-Type"] = "application/json"
canonical_request, signed_headers = self._create_canonical_request(
method, path, query_params, headers, payload
)
algorithm = "HMAC-SHA256"
credential_scope = f"{auth_date}/{self.region}/{self.service}/request"
canonical_request_hash = self._hash_sha256(
canonical_request.encode("utf-8")
).hex()
string_to_sign = "\n".join(
[algorithm, date_stamp, credential_scope, canonical_request_hash]
)
signing_key = self._get_signed_key(
self.api_sk, auth_date, self.region, self.service
)
signature = hmac.new(
signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
).hexdigest()
authorization = (
f"{algorithm} "
f"Credential={self.api_ak}/{credential_scope}, "
f"SignedHeaders={signed_headers}, "
f"Signature={signature}"
)
headers["Authorization"] = authorization
return headers
def _make_signed_request(
self, method: str, path: str, params: dict = None, data: dict = None
):
if data is None:
payload = b""
else:
payload = json.dumps(data).encode("utf-8")
if params is None:
params = {}
url = f"https://{self.api_url}{path}"
headers = {}
signed_headers = self._create_signature(method, path, params, headers, payload)
try:
response = requests.request(
method=method,
url=url,
headers=signed_headers,
params=params,
data=payload if payload else None,
timeout=30,
)
return response
except Exception as e:
raise ValueError(f"Request failed: {e}")
def query_relevant_documents( def query_relevant_documents(
self, query: str, resources: list[Resource] = [] self, query: str, resources: list[Resource] = []
@@ -111,29 +210,24 @@ class VikingDBKnowledgeBaseProvider(Retriever):
query_param = {"doc_filter": doc_filter} query_param = {"doc_filter": doc_filter}
request_params["query_param"] = query_param request_params["query_param"] = query_param
method = "POST"
path = "/api/knowledge/collection/search_knowledge" path = "/api/knowledge/collection/search_knowledge"
info_req = self.prepare_request(
method=method, path=path, data=request_params # 使用新的签名请求方法
) response = self._make_signed_request(
rsp = requests.request( method="POST", path=path, data=request_params
method=info_req.method,
url="http://{}{}".format(self.api_url, info_req.path),
headers=info_req.headers,
data=info_req.body,
) )
try: try:
response = json.loads(rsp.text) response_data = response.json()
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
raise ValueError(f"Failed to parse JSON response: {e}") raise ValueError(f"Failed to parse JSON response: {e}")
if response["code"] != 0: if response_data["code"] != 0:
raise ValueError( raise ValueError(
f"Failed to query documents from resource: {response['message']}" f"Failed to query documents from resource: {response_data['message']}"
) )
rsp_data = response.get("data", {}) rsp_data = response_data.get("data", {})
if "result_list" not in rsp_data: if "result_list" not in rsp_data:
continue continue
@@ -163,25 +257,20 @@ class VikingDBKnowledgeBaseProvider(Retriever):
""" """
List resources (knowledge bases) from the knowledge base service List resources (knowledge bases) from the knowledge base service
""" """
method = "POST"
path = "/api/knowledge/collection/list" path = "/api/knowledge/collection/list"
info_req = self.prepare_request(method=method, path=path)
rsp = requests.request( response = self._make_signed_request(method="POST", path=path)
method=info_req.method,
url="http://{}{}".format(self.api_url, info_req.path),
headers=info_req.headers,
data=info_req.body,
)
try: try:
response = json.loads(rsp.text) response_data = response.json()
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
raise ValueError(f"Failed to parse JSON response: {e}") raise ValueError(f"Failed to parse JSON response: {e}")
if response["code"] != 0: if response_data["code"] != 0:
raise Exception(f"Failed to list resources: {response["message"]}") raise Exception(f"Failed to list resources: {response_data['message']}")
resources = [] resources = []
rsp_data = response.get("data", {}) rsp_data = response_data.get("data", {})
collection_list = rsp_data.get("collection_list", []) collection_list = rsp_data.get("collection_list", [])
for item in collection_list: for item in collection_list:
collection_name = item.get("collection_name", "") collection_name = item.get("collection_name", "")

View File

@@ -4,7 +4,10 @@
import os import os
import pytest import pytest
import json import json
import hashlib
import hmac
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from datetime import datetime
from src.rag.vikingdb_knowledge_base import VikingDBKnowledgeBaseProvider, parse_uri from src.rag.vikingdb_knowledge_base import VikingDBKnowledgeBaseProvider, parse_uri
@@ -50,6 +53,7 @@ def env_vars():
"VIKINGDB_KNOWLEDGE_BASE_API_AK": "test_ak", "VIKINGDB_KNOWLEDGE_BASE_API_AK": "test_ak",
"VIKINGDB_KNOWLEDGE_BASE_API_SK": "test_sk", "VIKINGDB_KNOWLEDGE_BASE_API_SK": "test_sk",
"VIKINGDB_KNOWLEDGE_BASE_RETRIEVAL_SIZE": "10", "VIKINGDB_KNOWLEDGE_BASE_RETRIEVAL_SIZE": "10",
"VIKINGDB_KNOWLEDGE_BASE_REGION": "cn-north-1",
}, },
): ):
yield yield
@@ -89,6 +93,8 @@ class TestVikingDBKnowledgeBaseProviderInit:
assert provider.api_ak == "test_ak" assert provider.api_ak == "test_ak"
assert provider.api_sk == "test_sk" assert provider.api_sk == "test_sk"
assert provider.retrieval_size == 10 assert provider.retrieval_size == 10
assert provider.region == "cn-north-1"
assert provider.service == "air"
def test_init_success_without_retrieval_size(self): def test_init_success_without_retrieval_size(self):
"""Test initialization without VIKINGDB_KNOWLEDGE_BASE_RETRIEVAL_SIZE (should use default)""" """Test initialization without VIKINGDB_KNOWLEDGE_BASE_RETRIEVAL_SIZE (should use default)"""
@@ -118,6 +124,20 @@ class TestVikingDBKnowledgeBaseProviderInit:
provider = VikingDBKnowledgeBaseProvider() provider = VikingDBKnowledgeBaseProvider()
assert provider.retrieval_size == 5 assert provider.retrieval_size == 5
def test_init_custom_region(self):
"""Test initialization with custom region"""
with patch.dict(
os.environ,
{
"VIKINGDB_KNOWLEDGE_BASE_API_URL": "api-test.example.com",
"VIKINGDB_KNOWLEDGE_BASE_API_AK": "test_ak",
"VIKINGDB_KNOWLEDGE_BASE_API_SK": "test_sk",
"VIKINGDB_KNOWLEDGE_BASE_REGION": "us-east-1",
},
):
provider = VikingDBKnowledgeBaseProvider()
assert provider.region == "us-east-1"
def test_init_missing_api_url(self): def test_init_missing_api_url(self):
"""Test initialization fails when API URL is missing""" """Test initialization fails when API URL is missing"""
with patch.dict( with patch.dict(
@@ -164,61 +184,107 @@ class TestVikingDBKnowledgeBaseProviderInit:
VikingDBKnowledgeBaseProvider() VikingDBKnowledgeBaseProvider()
class TestVikingDBKnowledgeBaseProviderPrepareRequest: class TestVikingDBKnowledgeBaseProviderSignature:
@pytest.fixture @pytest.fixture
def provider(self, env_vars): def provider(self, env_vars):
return VikingDBKnowledgeBaseProvider() return VikingDBKnowledgeBaseProvider()
def test_prepare_request_basic(self, provider): def test_hmac_sha256(self, provider):
"""Test basic request preparation""" """Test HMAC SHA256 calculation"""
with ( key = b"test_key"
patch("src.rag.vikingdb_knowledge_base.Request") as mock_request, content = "test_content"
patch("src.rag.vikingdb_knowledge_base.Credentials") as _mock_credentials, result = provider._hmac_sha256(key, content)
patch("src.rag.vikingdb_knowledge_base.SignerV4.sign") as _mock_sign, expected = hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest()
): assert result == expected
mock_req_instance = MagicMock() def test_hash_sha256(self, provider):
mock_request.return_value = mock_req_instance """Test SHA256 hash calculation"""
data = b"test_data"
result = provider._hash_sha256(data)
expected = hashlib.sha256(data).digest()
assert result == expected
result = provider.prepare_request("POST", "/test/path") def test_get_signed_key(self, provider):
"""Test signed key generation"""
secret_key = "test_secret"
date = "20250722"
region = "cn-north-1"
service = "air"
assert result == mock_req_instance result = provider._get_signed_key(secret_key, date, region, service)
mock_req_instance.set_shema.assert_called_once_with("https") assert isinstance(result, bytes)
mock_req_instance.set_method.assert_called_once_with("POST") assert len(result) == 32 # SHA256 digest is 32 bytes
mock_req_instance.set_path.assert_called_once_with("/test/path")
def test_prepare_request_with_params(self, provider): def test_create_canonical_request(self, provider):
"""Test request preparation with parameters""" """Test canonical request creation"""
with ( method = "POST"
patch("src.rag.vikingdb_knowledge_base.Request") as mock_request, path = "/api/test"
patch("src.rag.vikingdb_knowledge_base.Credentials"), query_params = {"param1": "value1", "param2": "value2"}
patch("src.rag.vikingdb_knowledge_base.SignerV4.sign"), headers = {"Content-Type": "application/json", "Host": "example.com"}
): payload = b'{"test": "data"}'
mock_req_instance = MagicMock() canonical_request, signed_headers = provider._create_canonical_request(
mock_request.return_value = mock_req_instance method, path, query_params, headers, payload
)
params = {"key": "value", "number": 123, "boolean": True} assert "POST" in canonical_request
provider.prepare_request("GET", "/test", params=params) assert "/api/test" in canonical_request
assert "param1=value1&param2=value2" in canonical_request
assert "content-type:application/json" in canonical_request
assert "host:example.com" in canonical_request
assert signed_headers == "content-type;host"
expected_params = {"key": "value", "number": "123", "boolean": "True"} @patch("src.rag.vikingdb_knowledge_base.datetime")
mock_req_instance.set_query.assert_called_once_with(expected_params) def test_create_signature(self, mock_datetime, provider):
"""Test signature creation"""
# Mock datetime
mock_now = datetime(2025, 7, 22, 10, 30, 45)
mock_datetime.utcnow.return_value = mock_now
def test_prepare_request_with_data(self, provider): method = "POST"
"""Test request preparation with data""" path = "/api/test"
with ( query_params = {}
patch("src.rag.vikingdb_knowledge_base.Request") as mock_request, headers = {}
patch("src.rag.vikingdb_knowledge_base.Credentials"), payload = b'{"test": "data"}'
patch("src.rag.vikingdb_knowledge_base.SignerV4.sign"),
):
mock_req_instance = MagicMock() result = provider._create_signature(
mock_request.return_value = mock_req_instance method, path, query_params, headers, payload
)
data = {"test": "data"} assert "X-Date" in result
provider.prepare_request("POST", "/test", data=data) assert "Host" in result
assert "X-Content-Sha256" in result
assert "Content-Type" in result
assert "Authorization" in result
assert "HMAC-SHA256" in result["Authorization"]
mock_req_instance.set_body.assert_called_once_with(json.dumps(data)) @patch("src.rag.vikingdb_knowledge_base.requests.request")
def test_make_signed_request_success(self, mock_request, provider):
"""Test successful signed request"""
mock_response = MagicMock()
mock_response.json.return_value = {"code": 0, "data": {}}
mock_request.return_value = mock_response
result = provider._make_signed_request(
"POST", "/api/test", data={"test": "data"}
)
assert result == mock_response
mock_request.assert_called_once()
# Verify the call arguments
call_args = mock_request.call_args
assert call_args[1]["method"] == "POST"
assert call_args[1]["url"] == f"https://{provider.api_url}/api/test"
assert call_args[1]["timeout"] == 30
@patch("src.rag.vikingdb_knowledge_base.requests.request")
def test_make_signed_request_with_exception(self, mock_request, provider):
"""Test signed request with exception"""
mock_request.side_effect = Exception("Network error")
with pytest.raises(ValueError, match="Request failed: Network error"):
provider._make_signed_request("GET", "/api/test")
class TestVikingDBKnowledgeBaseProviderQueryRelevantDocuments: class TestVikingDBKnowledgeBaseProviderQueryRelevantDocuments:
@@ -231,166 +297,147 @@ class TestVikingDBKnowledgeBaseProviderQueryRelevantDocuments:
result = provider.query_relevant_documents("test query", []) result = provider.query_relevant_documents("test query", [])
assert result == [] assert result == []
@patch("src.rag.vikingdb_knowledge_base.requests.request") @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_query_relevant_documents_success(self, mock_request, provider): def test_query_relevant_documents_success(self, mock_request, provider):
"""Test successful document query""" """Test successful document query"""
# Mock response # Mock response
mock_response = MagicMock() mock_response = MagicMock()
mock_response.text = json.dumps( mock_response.json.return_value = {
"code": 0,
"data": {
"result_list": [
{
"doc_info": {
"doc_id": "doc123",
"doc_name": "Test Document",
},
"content": "Test content",
"score": 0.95,
}
]
},
}
mock_request.return_value = mock_response
resources = [MockResource("rag://dataset/123")]
result = provider.query_relevant_documents("test query", resources)
assert len(result) == 1
assert result[0].id == "doc123"
assert result[0].title == "Test Document"
assert len(result[0].chunks) == 1
assert result[0].chunks[0].content == "Test content"
assert result[0].chunks[0].similarity == 0.95
@patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_query_relevant_documents_with_document_filter(
self, mock_request, provider
):
"""Test document query with document ID filter"""
mock_response = MagicMock()
mock_response.json.return_value = {"code": 0, "data": {"result_list": []}}
mock_request.return_value = mock_response
resources = [MockResource("rag://dataset/123#doc456")]
provider.query_relevant_documents("test query", resources)
# Verify that query_param with doc_filter was included in the request
call_args = mock_request.call_args
request_data = call_args[1]["data"]
assert "query_param" in request_data
assert "doc_filter" in request_data["query_param"]
doc_filter = request_data["query_param"]["doc_filter"]
assert doc_filter["op"] == "must"
assert doc_filter["field"] == "doc_id"
assert doc_filter["conds"] == ["doc456"]
@patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_query_relevant_documents_api_error(self, mock_request, provider):
"""Test handling of API error response"""
mock_response = MagicMock()
mock_response.json.return_value = {"code": 1, "message": "API Error"}
mock_request.return_value = mock_response
resources = [MockResource("rag://dataset/123")]
with pytest.raises(
ValueError, match="Failed to query documents from resource: API Error"
):
provider.query_relevant_documents("test query", resources)
@patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_query_relevant_documents_json_decode_error(self, mock_request, provider):
"""Test handling of JSON decode error"""
mock_response = MagicMock()
mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0)
mock_request.return_value = mock_response
resources = [MockResource("rag://dataset/123")]
with pytest.raises(ValueError, match="Failed to parse JSON response"):
provider.query_relevant_documents("test query", resources)
@patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_query_relevant_documents_multiple_resources(self, mock_request, provider):
"""Test querying multiple resources and merging results"""
# Mock responses for different resources
responses = [
{ {
"code": 0, "code": 0,
"data": { "data": {
"result_list": [ "result_list": [
{ {
"doc_info": { "doc_info": {
"doc_id": "doc123", "doc_id": "doc1",
"doc_name": "Test Document", "doc_name": "Document 1",
}, },
"content": "Test content", "content": "Content 1",
"score": 0.95, "score": 0.9,
} }
] ]
}, },
} },
) {
mock_request.return_value = mock_response "code": 0,
"data": {
# Mock prepare_request "result_list": [
with patch.object(provider, "prepare_request") as mock_prepare: {
mock_req = MagicMock() "doc_info": {
mock_req.method = "POST" "doc_id": "doc1",
mock_req.path = "/api/knowledge/collection/search_knowledge" "doc_name": "Document 1",
mock_req.headers = {}
mock_req.body = "{}"
mock_prepare.return_value = mock_req
resources = [MockResource("rag://dataset/123")]
result = provider.query_relevant_documents("test query", resources)
assert len(result) == 1
assert result[0].id == "doc123"
assert result[0].title == "Test Document"
assert len(result[0].chunks) == 1
assert result[0].chunks[0].content == "Test content"
assert result[0].chunks[0].similarity == 0.95
@patch("src.rag.vikingdb_knowledge_base.requests.request")
def test_query_relevant_documents_with_document_filter(
self, mock_request, provider
):
"""Test document query with document ID filter"""
mock_response = MagicMock()
mock_response.text = json.dumps({"code": 0, "data": {"result_list": []}})
mock_request.return_value = mock_response
with patch.object(provider, "prepare_request") as mock_prepare:
mock_req = MagicMock()
mock_prepare.return_value = mock_req
resources = [MockResource("rag://dataset/123#doc456")]
provider.query_relevant_documents("test query", resources)
# Verify that query_param with doc_filter was included in the request
call_args = mock_prepare.call_args
request_data = call_args[1]["data"]
assert "query_param" in request_data
assert "doc_filter" in request_data["query_param"]
doc_filter = request_data["query_param"]["doc_filter"]
assert doc_filter["op"] == "must"
assert doc_filter["field"] == "doc_id"
assert doc_filter["conds"] == ["doc456"]
@patch("src.rag.vikingdb_knowledge_base.requests.request")
def test_query_relevant_documents_api_error(self, mock_request, provider):
"""Test handling of API error response"""
mock_response = MagicMock()
mock_response.text = json.dumps({"code": 1, "message": "API Error"})
mock_request.return_value = mock_response
with patch.object(provider, "prepare_request"):
resources = [MockResource("rag://dataset/123")]
with pytest.raises(
ValueError, match="Failed to query documents from resource: API Error"
):
provider.query_relevant_documents("test query", resources)
@patch("src.rag.vikingdb_knowledge_base.requests.request")
def test_query_relevant_documents_json_decode_error(self, mock_request, provider):
"""Test handling of JSON decode error"""
mock_response = MagicMock()
mock_response.text = "invalid json"
mock_request.return_value = mock_response
with patch.object(provider, "prepare_request"):
resources = [MockResource("rag://dataset/123")]
with pytest.raises(ValueError, match="Failed to parse JSON response"):
provider.query_relevant_documents("test query", resources)
@patch("src.rag.vikingdb_knowledge_base.requests.request")
def test_query_relevant_documents_multiple_resources(self, mock_request, provider):
"""Test querying multiple resources and merging results"""
# Mock responses for different resources
responses = [
json.dumps(
{
"code": 0,
"data": {
"result_list": [
{
"doc_info": {
"doc_id": "doc1",
"doc_name": "Document 1",
},
"content": "Content 1",
"score": 0.9,
}
]
},
}
),
json.dumps(
{
"code": 0,
"data": {
"result_list": [
{
"doc_info": {
"doc_id": "doc1",
"doc_name": "Document 1",
},
"content": "Content 2",
"score": 0.8,
}, },
{ "content": "Content 2",
"doc_info": { "score": 0.8,
"doc_id": "doc2", },
"doc_name": "Document 2", {
}, "doc_info": {
"content": "Content 3", "doc_id": "doc2",
"score": 0.7, "doc_name": "Document 2",
}, },
] "content": "Content 3",
}, "score": 0.7,
} },
), ]
},
},
] ]
mock_request.side_effect = [MagicMock(text=resp) for resp in responses] mock_responses = [MagicMock() for _ in responses]
for i, resp in enumerate(responses):
mock_responses[i].json.return_value = resp
mock_request.side_effect = mock_responses
with patch.object(provider, "prepare_request"): resources = [
resources = [ MockResource("rag://dataset/123"),
MockResource("rag://dataset/123"), MockResource("rag://dataset/456"),
MockResource("rag://dataset/456"), ]
] result = provider.query_relevant_documents("test query", resources)
result = provider.query_relevant_documents("test query", resources)
# Should have 2 documents: doc1 (with 2 chunks) and doc2 (with 1 chunk) # Should have 2 documents: doc1 (with 2 chunks) and doc2 (with 1 chunk)
assert len(result) == 2 assert len(result) == 2
doc1 = next(doc for doc in result if doc.id == "doc1") doc1 = next(doc for doc in result if doc.id == "doc1")
doc2 = next(doc for doc in result if doc.id == "doc2") doc2 = next(doc for doc in result if doc.id == "doc2")
assert len(doc1.chunks) == 2 assert len(doc1.chunks) == 2
assert len(doc2.chunks) == 1 assert len(doc2.chunks) == 1
class TestVikingDBKnowledgeBaseProviderListResources: class TestVikingDBKnowledgeBaseProviderListResources:
@@ -398,106 +445,94 @@ class TestVikingDBKnowledgeBaseProviderListResources:
def provider(self, env_vars): def provider(self, env_vars):
return VikingDBKnowledgeBaseProvider() return VikingDBKnowledgeBaseProvider()
@patch("src.rag.vikingdb_knowledge_base.requests.request") @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_list_resources_success(self, mock_request, provider): def test_list_resources_success(self, mock_request, provider):
"""Test successful resource listing""" """Test successful resource listing"""
mock_response = MagicMock() mock_response = MagicMock()
mock_response.text = json.dumps( mock_response.json.return_value = {
{ "code": 0,
"code": 0, "data": {
"data": { "collection_list": [
"collection_list": [ {
{ "resource_id": "123",
"resource_id": "123", "collection_name": "Dataset 1",
"collection_name": "Dataset 1", "description": "Description 1",
"description": "Description 1", },
}, {
{ "resource_id": "456",
"resource_id": "456", "collection_name": "Dataset 2",
"collection_name": "Dataset 2", "description": "Description 2",
"description": "Description 2", },
}, ]
] },
}, }
}
)
mock_request.return_value = mock_response mock_request.return_value = mock_response
with patch.object(provider, "prepare_request") as mock_prepare: result = provider.list_resources()
mock_req = MagicMock()
mock_prepare.return_value = mock_req
result = provider.list_resources() assert len(result) == 2
assert result[0].uri == "rag://dataset/123"
assert result[0].title == "Dataset 1"
assert result[0].description == "Description 1"
assert result[1].uri == "rag://dataset/456"
assert result[1].title == "Dataset 2"
assert result[1].description == "Description 2"
assert len(result) == 2 @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
assert result[0].uri == "rag://dataset/123"
assert result[0].title == "Dataset 1"
assert result[0].description == "Description 1"
assert result[1].uri == "rag://dataset/456"
assert result[1].title == "Dataset 2"
assert result[1].description == "Description 2"
@patch("src.rag.vikingdb_knowledge_base.requests.request")
def test_list_resources_with_query_filter(self, mock_request, provider): def test_list_resources_with_query_filter(self, mock_request, provider):
"""Test resource listing with query filter""" """Test resource listing with query filter"""
mock_response = MagicMock() mock_response = MagicMock()
mock_response.text = json.dumps( mock_response.json.return_value = {
{ "code": 0,
"code": 0, "data": {
"data": { "collection_list": [
"collection_list": [ {
{ "resource_id": "123",
"resource_id": "123", "collection_name": "Test Dataset",
"collection_name": "Test Dataset", "description": "Description",
"description": "Description", },
}, {
{ "resource_id": "456",
"resource_id": "456", "collection_name": "Other Dataset",
"collection_name": "Other Dataset", "description": "Description",
"description": "Description", },
}, ]
] },
}, }
}
)
mock_request.return_value = mock_response mock_request.return_value = mock_response
with patch.object(provider, "prepare_request"): result = provider.list_resources("test")
result = provider.list_resources("test")
# Should only return the dataset with "test" in the name # Should only return the dataset with "test" in the name
assert len(result) == 1 assert len(result) == 1
assert result[0].title == "Test Dataset" assert result[0].title == "Test Dataset"
@patch("src.rag.vikingdb_knowledge_base.requests.request") @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_list_resources_api_error(self, mock_request, provider): def test_list_resources_api_error(self, mock_request, provider):
"""Test handling of API error in list_resources""" """Test handling of API error in list_resources"""
mock_response = MagicMock() mock_response = MagicMock()
mock_response.text = json.dumps({"code": 1, "message": "API Error"}) mock_response.json.return_value = {"code": 1, "message": "API Error"}
mock_request.return_value = mock_response mock_request.return_value = mock_response
with patch.object(provider, "prepare_request"): with pytest.raises(Exception, match="Failed to list resources: API Error"):
with pytest.raises(Exception, match="Failed to list resources: API Error"): provider.list_resources()
provider.list_resources()
@patch("src.rag.vikingdb_knowledge_base.requests.request") @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_list_resources_json_decode_error(self, mock_request, provider): def test_list_resources_json_decode_error(self, mock_request, provider):
"""Test handling of JSON decode error in list_resources""" """Test handling of JSON decode error in list_resources"""
mock_response = MagicMock() mock_response = MagicMock()
mock_response.text = "invalid json" mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0)
mock_request.return_value = mock_response mock_request.return_value = mock_response
with patch.object(provider, "prepare_request"): with pytest.raises(ValueError, match="Failed to parse JSON response"):
with pytest.raises(ValueError, match="Failed to parse JSON response"): provider.list_resources()
provider.list_resources()
@patch("src.rag.vikingdb_knowledge_base.requests.request") @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
def test_list_resources_empty_response(self, mock_request, provider): def test_list_resources_empty_response(self, mock_request, provider):
"""Test handling of empty response""" """Test handling of empty response"""
mock_response = MagicMock() mock_response = MagicMock()
mock_response.text = json.dumps({"code": 0, "data": {"collection_list": []}}) mock_response.json.return_value = {"code": 0, "data": {"collection_list": []}}
mock_request.return_value = mock_response mock_request.return_value = mock_response
with patch.object(provider, "prepare_request"): result = provider.list_resources()
result = provider.list_resources() assert result == []
assert result == []

116
uv.lock generated
View File

@@ -365,15 +365,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" },
] ]
[[package]]
name = "decorator"
version = "5.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" },
]
[[package]] [[package]]
name = "deer-flow" name = "deer-flow"
version = "0.1.0" version = "0.1.0"
@@ -402,7 +393,6 @@ dependencies = [
{ name = "socksio" }, { name = "socksio" },
{ name = "sse-starlette" }, { name = "sse-starlette" },
{ name = "uvicorn" }, { name = "uvicorn" },
{ name = "volcengine" },
{ name = "yfinance" }, { name = "yfinance" },
] ]
@@ -449,7 +439,6 @@ requires-dist = [
{ name = "socksio", specifier = ">=1.0.0" }, { name = "socksio", specifier = ">=1.0.0" },
{ name = "sse-starlette", specifier = ">=1.6.5" }, { name = "sse-starlette", specifier = ">=1.6.5" },
{ name = "uvicorn", specifier = ">=0.27.1" }, { name = "uvicorn", specifier = ">=0.27.1" },
{ name = "volcengine", specifier = ">=1.0.191" },
{ name = "yfinance", specifier = ">=0.2.54" }, { name = "yfinance", specifier = ">=0.2.54" },
] ]
provides-extras = ["dev", "test"] provides-extras = ["dev", "test"]
@@ -577,18 +566,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615, upload-time = "2025-03-07T21:47:54.809Z" }, { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615, upload-time = "2025-03-07T21:47:54.809Z" },
] ]
[[package]]
name = "google"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4" },
]
sdist = { url = "https://files.pythonhosted.org/packages/89/97/b49c69893cddea912c7a660a4b6102c6b02cd268f8c7162dd70b7c16f753/google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe", size = 44978, upload-time = "2020-07-11T14:50:45.678Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ac/35/17c9141c4ae21e9a29a43acdfd848e3e468a810517f862cad07977bf8fe9/google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935", size = 45258, upload-time = "2020-07-11T14:49:58.287Z" },
]
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "3.1.1" version = "3.1.1"
@@ -1620,29 +1597,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/35/6c4c6fc8774a9e3629cd750dc24a7a4fb090a25ccd5c3246d127b70f9e22/propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043", size = 12101, upload-time = "2025-02-20T19:03:27.202Z" }, { url = "https://files.pythonhosted.org/packages/b5/35/6c4c6fc8774a9e3629cd750dc24a7a4fb090a25ccd5c3246d127b70f9e22/propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043", size = 12101, upload-time = "2025-02-20T19:03:27.202Z" },
] ]
[[package]]
name = "protobuf"
version = "6.31.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797, upload-time = "2025-05-28T19:25:54.947Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603, upload-time = "2025-05-28T19:25:41.198Z" },
{ url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283, upload-time = "2025-05-28T19:25:44.275Z" },
{ url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604, upload-time = "2025-05-28T19:25:45.702Z" },
{ url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115, upload-time = "2025-05-28T19:25:47.128Z" },
{ url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070, upload-time = "2025-05-28T19:25:50.036Z" },
{ url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" },
]
[[package]]
name = "py"
version = "1.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796, upload-time = "2021-11-04T17:17:01.377Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload-time = "2021-11-04T17:17:00.152Z" },
]
[[package]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.22" version = "2.22"
@@ -1652,12 +1606,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" },
] ]
[[package]]
name = "pycryptodome"
version = "3.9.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c4/3a/5bca2cb1648b171afd6b7d29a11c6bca8b305bb75b7e2d78a0f5c61ff95e/pycryptodome-3.9.9.tar.gz", hash = "sha256:910e202a557e1131b1c1b3f17a63914d57aac55cf9fb9b51644962841c3995c4", size = 15488528, upload-time = "2020-11-03T13:15:26.723Z" }
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.10.6" version = "2.10.6"
@@ -1923,19 +1871,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
] ]
[[package]]
name = "retry"
version = "0.9.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "decorator" },
{ name = "py" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/72/75d0b85443fbc8d9f38d08d2b1b67cc184ce35280e4a3813cda2f445f3a4/retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4", size = 6448, upload-time = "2016-05-11T13:58:51.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/0d/53aea75710af4528a25ed6837d71d117602b01946b307a3912cb3cfcbcba/retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", size = 7986, upload-time = "2016-05-11T13:58:39.925Z" },
]
[[package]] [[package]]
name = "rpds-py" name = "rpds-py"
version = "0.23.1" version = "0.23.1"
@@ -1987,25 +1922,25 @@ wheels = [
name = "ruff" name = "ruff"
version = "0.11.10" version = "0.11.10"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632 } sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632, upload-time = "2025-05-15T14:08:56.76Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243 }, { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243, upload-time = "2025-05-15T14:08:12.884Z" },
{ url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636 }, { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636, upload-time = "2025-05-15T14:08:16.551Z" },
{ url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624 }, { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624, upload-time = "2025-05-15T14:08:19.032Z" },
{ url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358 }, { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358, upload-time = "2025-05-15T14:08:21.542Z" },
{ url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850 }, { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850, upload-time = "2025-05-15T14:08:23.682Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787 }, { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787, upload-time = "2025-05-15T14:08:25.733Z" },
{ url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479 }, { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479, upload-time = "2025-05-15T14:08:28.013Z" },
{ url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760 }, { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760, upload-time = "2025-05-15T14:08:30.956Z" },
{ url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747 }, { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747, upload-time = "2025-05-15T14:08:33.297Z" },
{ url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657 }, { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657, upload-time = "2025-05-15T14:08:35.639Z" },
{ url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671 }, { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671, upload-time = "2025-05-15T14:08:38.437Z" },
{ url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135 }, { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135, upload-time = "2025-05-15T14:08:41.247Z" },
{ url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179 }, { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179, upload-time = "2025-05-15T14:08:43.762Z" },
{ url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021 }, { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021, upload-time = "2025-05-15T14:08:46.451Z" },
{ url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958 }, { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958, upload-time = "2025-05-15T14:08:49.601Z" },
{ url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285 }, { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285, upload-time = "2025-05-15T14:08:52.392Z" },
{ url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278 }, { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278, upload-time = "2025-05-15T14:08:54.56Z" },
] ]
[[package]] [[package]]
@@ -2246,21 +2181,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315, upload-time = "2024-12-15T13:33:27.467Z" }, { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315, upload-time = "2024-12-15T13:33:27.467Z" },
] ]
[[package]]
name = "volcengine"
version = "1.0.191"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "google" },
{ name = "protobuf" },
{ name = "pycryptodome" },
{ name = "pytz" },
{ name = "requests" },
{ name = "retry" },
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f4/d8/0ea9b18f216808af709306084d10369f712b98cb5381381d44115dfa6536/volcengine-1.0.191.tar.gz", hash = "sha256:cf3c2dc118c92a7a47f1ab8a48f4789d47e84d17778a1717e1afe9cbce90c986", size = 356076, upload-time = "2025-06-26T12:25:16.353Z" }
[[package]] [[package]]
name = "watchfiles" name = "watchfiles"
version = "1.0.5" version = "1.0.5"