chore: align V2 into final four-project layout
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Yuqei Python SDK
|
||||
|
||||
Python client for V2 service-to-service calls.
|
||||
|
||||
Initial scope:
|
||||
|
||||
- AI Platform legal QA client.
|
||||
- Request context headers.
|
||||
- Typed request and response models.
|
||||
@@ -0,0 +1,13 @@
|
||||
[project]
|
||||
name = "yuqei-python-sdk"
|
||||
version = "0.1.0"
|
||||
description = "Python SDK for Yuqei V2 services"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"httpx>=0.28",
|
||||
"pydantic>=2.10",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
@@ -0,0 +1,10 @@
|
||||
from yuqei_sdk.ai_platform import AiPlatformClient, LegalCitation, LegalQaRequest, LegalQaResponse
|
||||
from yuqei_sdk.context import RequestContext
|
||||
|
||||
__all__ = [
|
||||
"AiPlatformClient",
|
||||
"LegalCitation",
|
||||
"LegalQaRequest",
|
||||
"LegalQaResponse",
|
||||
"RequestContext",
|
||||
]
|
||||
@@ -0,0 +1,67 @@
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from yuqei_sdk.context import RequestContext
|
||||
|
||||
|
||||
class LegalQaRequest(BaseModel):
|
||||
question: str = Field(min_length=1)
|
||||
matter_context: str | None = None
|
||||
knowledge_base_ids: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class LegalCitation(BaseModel):
|
||||
source_type: str
|
||||
title: str
|
||||
reference: str
|
||||
quote: str
|
||||
url: str | None = None
|
||||
|
||||
|
||||
class LegalQaResponse(BaseModel):
|
||||
answer: str
|
||||
citations: list[LegalCitation]
|
||||
confidence: float | None = None
|
||||
|
||||
|
||||
class AiPlatformClient:
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
*,
|
||||
api_prefix: str = "/api/v1",
|
||||
timeout: float = 30.0,
|
||||
transport: httpx.BaseTransport | None = None,
|
||||
) -> None:
|
||||
self._api_prefix = api_prefix.rstrip("/")
|
||||
self._client = httpx.Client(
|
||||
base_url=base_url.rstrip("/"),
|
||||
timeout=timeout,
|
||||
transport=transport,
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
self._client.close()
|
||||
|
||||
def __enter__(self) -> "AiPlatformClient":
|
||||
return self
|
||||
|
||||
def __exit__(self, *_exc_info: object) -> None:
|
||||
self.close()
|
||||
|
||||
def run_legal_qa(
|
||||
self,
|
||||
request: LegalQaRequest,
|
||||
*,
|
||||
context: RequestContext | None = None,
|
||||
) -> LegalQaResponse:
|
||||
response = self._client.post(
|
||||
f"{self._api_prefix}/legal/qa",
|
||||
json=request.model_dump(exclude_none=True),
|
||||
headers=(context or RequestContext()).to_headers(),
|
||||
)
|
||||
response.raise_for_status()
|
||||
payload: dict[str, Any] = response.json()
|
||||
return LegalQaResponse.model_validate(payload)
|
||||
@@ -0,0 +1,17 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RequestContext(BaseModel):
|
||||
trace_id: str | None = None
|
||||
tenant_id: str | None = None
|
||||
user_id: str | None = None
|
||||
|
||||
def to_headers(self) -> dict[str, str]:
|
||||
headers: dict[str, str] = {}
|
||||
if self.trace_id:
|
||||
headers["X-Trace-Id"] = self.trace_id
|
||||
if self.tenant_id:
|
||||
headers["X-Tenant-Id"] = self.tenant_id
|
||||
if self.user_id:
|
||||
headers["X-User-Id"] = self.user_id
|
||||
return headers
|
||||
@@ -0,0 +1 @@
|
||||
"""Yuqei SDK test package."""
|
||||
@@ -0,0 +1,43 @@
|
||||
import httpx
|
||||
|
||||
from yuqei_sdk import AiPlatformClient, LegalQaRequest, RequestContext
|
||||
|
||||
|
||||
def test_ai_platform_client_posts_legal_qa_with_context_headers() -> None:
|
||||
captured_request: httpx.Request | None = None
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
nonlocal captured_request
|
||||
captured_request = request
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"answer": "依据劳动合同法第十九条,需要核对合同期限。",
|
||||
"citations": [
|
||||
{
|
||||
"source_type": "law",
|
||||
"title": "中华人民共和国劳动合同法",
|
||||
"reference": "第十九条",
|
||||
"quote": "劳动合同期限三个月以上不满一年的,试用期不得超过一个月。",
|
||||
}
|
||||
],
|
||||
"confidence": 0.72,
|
||||
},
|
||||
)
|
||||
|
||||
client = AiPlatformClient(
|
||||
"http://ai-platform.test",
|
||||
transport=httpx.MockTransport(handler),
|
||||
)
|
||||
|
||||
response = client.run_legal_qa(
|
||||
LegalQaRequest(question="试用期如何约定?"),
|
||||
context=RequestContext(trace_id="trace-1", tenant_id="tenant-1", user_id="user-1"),
|
||||
)
|
||||
|
||||
assert response.citations[0].reference == "第十九条"
|
||||
assert captured_request is not None
|
||||
assert captured_request.url.path == "/api/v1/legal/qa"
|
||||
assert captured_request.headers["X-Trace-Id"] == "trace-1"
|
||||
assert captured_request.headers["X-Tenant-Id"] == "tenant-1"
|
||||
assert captured_request.headers["X-User-Id"] == "user-1"
|
||||
Reference in New Issue
Block a user