mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-05 15:10:20 +08:00
remove volengine package (#464)
This commit is contained in:
@@ -33,7 +33,6 @@ dependencies = [
|
||||
"mcp>=1.6.0",
|
||||
"langchain-mcp-adapters>=0.0.9",
|
||||
"langchain-deepseek>=0.1.3",
|
||||
"volcengine>=1.0.191",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
import hashlib
|
||||
import hmac
|
||||
import urllib.parse
|
||||
from datetime import datetime
|
||||
from src.rag.retriever import Chunk, Document, Resource, Retriever
|
||||
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):
|
||||
@@ -20,6 +21,8 @@ class VikingDBKnowledgeBaseProvider(Retriever):
|
||||
api_ak: str
|
||||
api_sk: str
|
||||
retrieval_size: int = 10
|
||||
region: str = "cn-north-1"
|
||||
service: str = "air"
|
||||
|
||||
def __init__(self):
|
||||
api_url = os.getenv("VIKINGDB_KNOWLEDGE_BASE_API_URL")
|
||||
@@ -41,41 +44,137 @@ class VikingDBKnowledgeBaseProvider(Retriever):
|
||||
if retrieval_size:
|
||||
self.retrieval_size = int(retrieval_size)
|
||||
|
||||
def prepare_request(self, method, path, params=None, data=None, doseq=0):
|
||||
"""
|
||||
Prepare signed request using volcengine auth
|
||||
"""
|
||||
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])
|
||||
# 设置region,如果需要可以从环境变量获取
|
||||
region = os.getenv("VIKINGDB_KNOWLEDGE_BASE_REGION", "cn-north-1")
|
||||
self.region = region
|
||||
|
||||
r = Request()
|
||||
r.set_shema("https")
|
||||
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))
|
||||
def _hmac_sha256(self, key: bytes, content: str) -> bytes:
|
||||
return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest()
|
||||
|
||||
credentials = Credentials(self.api_ak, self.api_sk, "air", "cn-north-1")
|
||||
SignerV4.sign(r, credentials)
|
||||
return r
|
||||
def _hash_sha256(self, data: bytes) -> bytes:
|
||||
return hashlib.sha256(data).digest()
|
||||
|
||||
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(
|
||||
self, query: str, resources: list[Resource] = []
|
||||
@@ -111,29 +210,24 @@ class VikingDBKnowledgeBaseProvider(Retriever):
|
||||
query_param = {"doc_filter": doc_filter}
|
||||
request_params["query_param"] = query_param
|
||||
|
||||
method = "POST"
|
||||
path = "/api/knowledge/collection/search_knowledge"
|
||||
info_req = self.prepare_request(
|
||||
method=method, path=path, data=request_params
|
||||
)
|
||||
rsp = requests.request(
|
||||
method=info_req.method,
|
||||
url="http://{}{}".format(self.api_url, info_req.path),
|
||||
headers=info_req.headers,
|
||||
data=info_req.body,
|
||||
|
||||
# 使用新的签名请求方法
|
||||
response = self._make_signed_request(
|
||||
method="POST", path=path, data=request_params
|
||||
)
|
||||
|
||||
try:
|
||||
response = json.loads(rsp.text)
|
||||
response_data = response.json()
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Failed to parse JSON response: {e}")
|
||||
|
||||
if response["code"] != 0:
|
||||
if response_data["code"] != 0:
|
||||
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:
|
||||
continue
|
||||
@@ -163,25 +257,20 @@ class VikingDBKnowledgeBaseProvider(Retriever):
|
||||
"""
|
||||
List resources (knowledge bases) from the knowledge base service
|
||||
"""
|
||||
method = "POST"
|
||||
path = "/api/knowledge/collection/list"
|
||||
info_req = self.prepare_request(method=method, path=path)
|
||||
rsp = requests.request(
|
||||
method=info_req.method,
|
||||
url="http://{}{}".format(self.api_url, info_req.path),
|
||||
headers=info_req.headers,
|
||||
data=info_req.body,
|
||||
)
|
||||
|
||||
response = self._make_signed_request(method="POST", path=path)
|
||||
|
||||
try:
|
||||
response = json.loads(rsp.text)
|
||||
response_data = response.json()
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Failed to parse JSON response: {e}")
|
||||
|
||||
if response["code"] != 0:
|
||||
raise Exception(f"Failed to list resources: {response["message"]}")
|
||||
if response_data["code"] != 0:
|
||||
raise Exception(f"Failed to list resources: {response_data['message']}")
|
||||
|
||||
resources = []
|
||||
rsp_data = response.get("data", {})
|
||||
rsp_data = response_data.get("data", {})
|
||||
collection_list = rsp_data.get("collection_list", [])
|
||||
for item in collection_list:
|
||||
collection_name = item.get("collection_name", "")
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
import os
|
||||
import pytest
|
||||
import json
|
||||
import hashlib
|
||||
import hmac
|
||||
from unittest.mock import patch, MagicMock
|
||||
from datetime import datetime
|
||||
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_SK": "test_sk",
|
||||
"VIKINGDB_KNOWLEDGE_BASE_RETRIEVAL_SIZE": "10",
|
||||
"VIKINGDB_KNOWLEDGE_BASE_REGION": "cn-north-1",
|
||||
},
|
||||
):
|
||||
yield
|
||||
@@ -89,6 +93,8 @@ class TestVikingDBKnowledgeBaseProviderInit:
|
||||
assert provider.api_ak == "test_ak"
|
||||
assert provider.api_sk == "test_sk"
|
||||
assert provider.retrieval_size == 10
|
||||
assert provider.region == "cn-north-1"
|
||||
assert provider.service == "air"
|
||||
|
||||
def test_init_success_without_retrieval_size(self):
|
||||
"""Test initialization without VIKINGDB_KNOWLEDGE_BASE_RETRIEVAL_SIZE (should use default)"""
|
||||
@@ -118,6 +124,20 @@ class TestVikingDBKnowledgeBaseProviderInit:
|
||||
provider = VikingDBKnowledgeBaseProvider()
|
||||
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):
|
||||
"""Test initialization fails when API URL is missing"""
|
||||
with patch.dict(
|
||||
@@ -164,61 +184,107 @@ class TestVikingDBKnowledgeBaseProviderInit:
|
||||
VikingDBKnowledgeBaseProvider()
|
||||
|
||||
|
||||
class TestVikingDBKnowledgeBaseProviderPrepareRequest:
|
||||
class TestVikingDBKnowledgeBaseProviderSignature:
|
||||
@pytest.fixture
|
||||
def provider(self, env_vars):
|
||||
return VikingDBKnowledgeBaseProvider()
|
||||
|
||||
def test_prepare_request_basic(self, provider):
|
||||
"""Test basic request preparation"""
|
||||
with (
|
||||
patch("src.rag.vikingdb_knowledge_base.Request") as mock_request,
|
||||
patch("src.rag.vikingdb_knowledge_base.Credentials") as _mock_credentials,
|
||||
patch("src.rag.vikingdb_knowledge_base.SignerV4.sign") as _mock_sign,
|
||||
):
|
||||
def test_hmac_sha256(self, provider):
|
||||
"""Test HMAC SHA256 calculation"""
|
||||
key = b"test_key"
|
||||
content = "test_content"
|
||||
result = provider._hmac_sha256(key, content)
|
||||
expected = hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest()
|
||||
assert result == expected
|
||||
|
||||
mock_req_instance = MagicMock()
|
||||
mock_request.return_value = mock_req_instance
|
||||
def test_hash_sha256(self, provider):
|
||||
"""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
|
||||
mock_req_instance.set_shema.assert_called_once_with("https")
|
||||
mock_req_instance.set_method.assert_called_once_with("POST")
|
||||
mock_req_instance.set_path.assert_called_once_with("/test/path")
|
||||
result = provider._get_signed_key(secret_key, date, region, service)
|
||||
assert isinstance(result, bytes)
|
||||
assert len(result) == 32 # SHA256 digest is 32 bytes
|
||||
|
||||
def test_prepare_request_with_params(self, provider):
|
||||
"""Test request preparation with parameters"""
|
||||
with (
|
||||
patch("src.rag.vikingdb_knowledge_base.Request") as mock_request,
|
||||
patch("src.rag.vikingdb_knowledge_base.Credentials"),
|
||||
patch("src.rag.vikingdb_knowledge_base.SignerV4.sign"),
|
||||
):
|
||||
def test_create_canonical_request(self, provider):
|
||||
"""Test canonical request creation"""
|
||||
method = "POST"
|
||||
path = "/api/test"
|
||||
query_params = {"param1": "value1", "param2": "value2"}
|
||||
headers = {"Content-Type": "application/json", "Host": "example.com"}
|
||||
payload = b'{"test": "data"}'
|
||||
|
||||
mock_req_instance = MagicMock()
|
||||
mock_request.return_value = mock_req_instance
|
||||
canonical_request, signed_headers = provider._create_canonical_request(
|
||||
method, path, query_params, headers, payload
|
||||
)
|
||||
|
||||
params = {"key": "value", "number": 123, "boolean": True}
|
||||
provider.prepare_request("GET", "/test", params=params)
|
||||
assert "POST" in canonical_request
|
||||
assert "/api/test" in canonical_request
|
||||
assert "param1=value1¶m2=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"}
|
||||
mock_req_instance.set_query.assert_called_once_with(expected_params)
|
||||
@patch("src.rag.vikingdb_knowledge_base.datetime")
|
||||
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):
|
||||
"""Test request preparation with data"""
|
||||
with (
|
||||
patch("src.rag.vikingdb_knowledge_base.Request") as mock_request,
|
||||
patch("src.rag.vikingdb_knowledge_base.Credentials"),
|
||||
patch("src.rag.vikingdb_knowledge_base.SignerV4.sign"),
|
||||
):
|
||||
method = "POST"
|
||||
path = "/api/test"
|
||||
query_params = {}
|
||||
headers = {}
|
||||
payload = b'{"test": "data"}'
|
||||
|
||||
mock_req_instance = MagicMock()
|
||||
mock_request.return_value = mock_req_instance
|
||||
result = provider._create_signature(
|
||||
method, path, query_params, headers, payload
|
||||
)
|
||||
|
||||
data = {"test": "data"}
|
||||
provider.prepare_request("POST", "/test", data=data)
|
||||
assert "X-Date" in result
|
||||
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:
|
||||
@@ -231,166 +297,147 @@ class TestVikingDBKnowledgeBaseProviderQueryRelevantDocuments:
|
||||
result = provider.query_relevant_documents("test query", [])
|
||||
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):
|
||||
"""Test successful document query"""
|
||||
# Mock response
|
||||
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,
|
||||
"data": {
|
||||
"result_list": [
|
||||
{
|
||||
"doc_info": {
|
||||
"doc_id": "doc123",
|
||||
"doc_name": "Test Document",
|
||||
"doc_id": "doc1",
|
||||
"doc_name": "Document 1",
|
||||
},
|
||||
"content": "Test content",
|
||||
"score": 0.95,
|
||||
"content": "Content 1",
|
||||
"score": 0.9,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
# Mock prepare_request
|
||||
with patch.object(provider, "prepare_request") as mock_prepare:
|
||||
mock_req = MagicMock()
|
||||
mock_req.method = "POST"
|
||||
mock_req.path = "/api/knowledge/collection/search_knowledge"
|
||||
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,
|
||||
},
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"result_list": [
|
||||
{
|
||||
"doc_info": {
|
||||
"doc_id": "doc1",
|
||||
"doc_name": "Document 1",
|
||||
},
|
||||
{
|
||||
"doc_info": {
|
||||
"doc_id": "doc2",
|
||||
"doc_name": "Document 2",
|
||||
},
|
||||
"content": "Content 3",
|
||||
"score": 0.7,
|
||||
"content": "Content 2",
|
||||
"score": 0.8,
|
||||
},
|
||||
{
|
||||
"doc_info": {
|
||||
"doc_id": "doc2",
|
||||
"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 = [
|
||||
MockResource("rag://dataset/123"),
|
||||
MockResource("rag://dataset/456"),
|
||||
]
|
||||
result = provider.query_relevant_documents("test query", resources)
|
||||
resources = [
|
||||
MockResource("rag://dataset/123"),
|
||||
MockResource("rag://dataset/456"),
|
||||
]
|
||||
result = provider.query_relevant_documents("test query", resources)
|
||||
|
||||
# Should have 2 documents: doc1 (with 2 chunks) and doc2 (with 1 chunk)
|
||||
assert len(result) == 2
|
||||
doc1 = next(doc for doc in result if doc.id == "doc1")
|
||||
doc2 = next(doc for doc in result if doc.id == "doc2")
|
||||
assert len(doc1.chunks) == 2
|
||||
assert len(doc2.chunks) == 1
|
||||
# Should have 2 documents: doc1 (with 2 chunks) and doc2 (with 1 chunk)
|
||||
assert len(result) == 2
|
||||
doc1 = next(doc for doc in result if doc.id == "doc1")
|
||||
doc2 = next(doc for doc in result if doc.id == "doc2")
|
||||
assert len(doc1.chunks) == 2
|
||||
assert len(doc2.chunks) == 1
|
||||
|
||||
|
||||
class TestVikingDBKnowledgeBaseProviderListResources:
|
||||
@@ -398,106 +445,94 @@ class TestVikingDBKnowledgeBaseProviderListResources:
|
||||
def provider(self, env_vars):
|
||||
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):
|
||||
"""Test successful resource listing"""
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = json.dumps(
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"collection_list": [
|
||||
{
|
||||
"resource_id": "123",
|
||||
"collection_name": "Dataset 1",
|
||||
"description": "Description 1",
|
||||
},
|
||||
{
|
||||
"resource_id": "456",
|
||||
"collection_name": "Dataset 2",
|
||||
"description": "Description 2",
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
mock_response.json.return_value = {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"collection_list": [
|
||||
{
|
||||
"resource_id": "123",
|
||||
"collection_name": "Dataset 1",
|
||||
"description": "Description 1",
|
||||
},
|
||||
{
|
||||
"resource_id": "456",
|
||||
"collection_name": "Dataset 2",
|
||||
"description": "Description 2",
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
with patch.object(provider, "prepare_request") as mock_prepare:
|
||||
mock_req = MagicMock()
|
||||
mock_prepare.return_value = mock_req
|
||||
result = provider.list_resources()
|
||||
|
||||
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
|
||||
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")
|
||||
@patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request")
|
||||
def test_list_resources_with_query_filter(self, mock_request, provider):
|
||||
"""Test resource listing with query filter"""
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = json.dumps(
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"collection_list": [
|
||||
{
|
||||
"resource_id": "123",
|
||||
"collection_name": "Test Dataset",
|
||||
"description": "Description",
|
||||
},
|
||||
{
|
||||
"resource_id": "456",
|
||||
"collection_name": "Other Dataset",
|
||||
"description": "Description",
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
mock_response.json.return_value = {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"collection_list": [
|
||||
{
|
||||
"resource_id": "123",
|
||||
"collection_name": "Test Dataset",
|
||||
"description": "Description",
|
||||
},
|
||||
{
|
||||
"resource_id": "456",
|
||||
"collection_name": "Other Dataset",
|
||||
"description": "Description",
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
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
|
||||
assert len(result) == 1
|
||||
assert result[0].title == "Test Dataset"
|
||||
# Should only return the dataset with "test" in the name
|
||||
assert len(result) == 1
|
||||
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):
|
||||
"""Test handling of API error in list_resources"""
|
||||
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
|
||||
|
||||
with patch.object(provider, "prepare_request"):
|
||||
with pytest.raises(Exception, match="Failed to list resources: API Error"):
|
||||
provider.list_resources()
|
||||
with pytest.raises(Exception, match="Failed to list resources: API Error"):
|
||||
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):
|
||||
"""Test handling of JSON decode error in list_resources"""
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = "invalid json"
|
||||
mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0)
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
with patch.object(provider, "prepare_request"):
|
||||
with pytest.raises(ValueError, match="Failed to parse JSON response"):
|
||||
provider.list_resources()
|
||||
with pytest.raises(ValueError, match="Failed to parse JSON response"):
|
||||
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):
|
||||
"""Test handling of empty response"""
|
||||
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
|
||||
|
||||
with patch.object(provider, "prepare_request"):
|
||||
result = provider.list_resources()
|
||||
assert result == []
|
||||
result = provider.list_resources()
|
||||
assert result == []
|
||||
|
||||
116
uv.lock
generated
116
uv.lock
generated
@@ -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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "deer-flow"
|
||||
version = "0.1.0"
|
||||
@@ -402,7 +393,6 @@ dependencies = [
|
||||
{ name = "socksio" },
|
||||
{ name = "sse-starlette" },
|
||||
{ name = "uvicorn" },
|
||||
{ name = "volcengine" },
|
||||
{ name = "yfinance" },
|
||||
]
|
||||
|
||||
@@ -449,7 +439,6 @@ requires-dist = [
|
||||
{ name = "socksio", specifier = ">=1.0.0" },
|
||||
{ name = "sse-starlette", specifier = ">=1.6.5" },
|
||||
{ name = "uvicorn", specifier = ">=0.27.1" },
|
||||
{ name = "volcengine", specifier = ">=1.0.191" },
|
||||
{ name = "yfinance", specifier = ">=0.2.54" },
|
||||
]
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "greenlet"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pycparser"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pydantic"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "rpds-py"
|
||||
version = "0.23.1"
|
||||
@@ -1987,25 +1922,25 @@ wheels = [
|
||||
name = "ruff"
|
||||
version = "0.11.10"
|
||||
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 = [
|
||||
{ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958 },
|
||||
{ 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/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278 },
|
||||
{ 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, upload-time = "2025-05-15T14:08:54.56Z" },
|
||||
]
|
||||
|
||||
[[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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "watchfiles"
|
||||
version = "1.0.5"
|
||||
|
||||
Reference in New Issue
Block a user