fix: support shared AI SDK api prefix
This commit is contained in:
@@ -4,6 +4,7 @@ frontend/.next-dev-log/
|
||||
frontend/node_modules/
|
||||
*.pyc
|
||||
__pycache__/
|
||||
*.egg-info/
|
||||
.DS_Store
|
||||
._*
|
||||
backend/.pytest_cache/
|
||||
|
||||
@@ -15,16 +15,18 @@ class SharedAIPlatformClient:
|
||||
self,
|
||||
*,
|
||||
base_url: str = "http://127.0.0.1:8011",
|
||||
api_key: str,
|
||||
app_id: str,
|
||||
timeout_seconds: float = 30.0,
|
||||
client: httpx.Client | None = None,
|
||||
):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.api_key = api_key
|
||||
self.app_id = app_id
|
||||
self._owns_client = client is None
|
||||
self._client = client or httpx.Client(base_url=self.base_url, timeout=timeout_seconds)
|
||||
api_key: str,
|
||||
app_id: str,
|
||||
timeout_seconds: float = 30.0,
|
||||
api_prefix: str = "/api/v0.2",
|
||||
client: httpx.Client | None = None,
|
||||
):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.api_prefix = self._normalize_api_prefix(api_prefix)
|
||||
self.api_key = api_key
|
||||
self.app_id = app_id
|
||||
self._owns_client = client is None
|
||||
self._client = client or httpx.Client(base_url=self.base_url, timeout=timeout_seconds)
|
||||
|
||||
def close(self):
|
||||
if self._owns_client:
|
||||
@@ -36,8 +38,8 @@ class SharedAIPlatformClient:
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
self.close()
|
||||
|
||||
def list_tasks(self, context: RequestContext) -> list[dict]:
|
||||
return self._request_json("GET", "/api/v0.2/tasks", context=context)["data"]
|
||||
def list_tasks(self, context: RequestContext) -> list[dict]:
|
||||
return self._request_json("GET", self._api_path("/tasks"), context=context)["data"]
|
||||
|
||||
def run_task(
|
||||
self,
|
||||
@@ -48,12 +50,12 @@ class SharedAIPlatformClient:
|
||||
provider: str | None = None,
|
||||
metadata: dict | None = None,
|
||||
) -> dict:
|
||||
return self._request_json(
|
||||
"POST",
|
||||
f"/api/v0.2/tasks/{task_key}/run",
|
||||
context=context,
|
||||
json_body={"inputs": inputs, "provider": provider, "metadata": metadata or {}},
|
||||
)["data"]
|
||||
return self._request_json(
|
||||
"POST",
|
||||
self._api_path(f"/tasks/{task_key}/run"),
|
||||
context=context,
|
||||
json_body={"inputs": inputs, "provider": provider, "metadata": metadata or {}},
|
||||
)["data"]
|
||||
|
||||
def enqueue_task(
|
||||
self,
|
||||
@@ -64,38 +66,38 @@ class SharedAIPlatformClient:
|
||||
provider: str | None = None,
|
||||
metadata: dict | None = None,
|
||||
) -> dict:
|
||||
return self._request_json(
|
||||
"POST",
|
||||
f"/api/v0.2/tasks/{task_key}/enqueue",
|
||||
context=context,
|
||||
json_body={"inputs": inputs, "provider": provider, "metadata": metadata or {}},
|
||||
)["data"]
|
||||
|
||||
def get_task_run(self, context: RequestContext, run_id: str) -> dict:
|
||||
return self._request_json("GET", f"/api/v0.2/task-runs/{run_id}", context=context)["data"]
|
||||
return self._request_json(
|
||||
"POST",
|
||||
self._api_path(f"/tasks/{task_key}/enqueue"),
|
||||
context=context,
|
||||
json_body={"inputs": inputs, "provider": provider, "metadata": metadata or {}},
|
||||
)["data"]
|
||||
|
||||
def get_task_run(self, context: RequestContext, run_id: str) -> dict:
|
||||
return self._request_json("GET", self._api_path(f"/task-runs/{run_id}"), context=context)["data"]
|
||||
|
||||
def list_jobs(self, context: RequestContext, *, status_value: str | None = None, limit: int = 50) -> list[dict]:
|
||||
params = {"limit": limit}
|
||||
if status_value:
|
||||
params["status_value"] = status_value
|
||||
return self._request_json("GET", "/api/v0.2/jobs", context=context, params=params)["data"]
|
||||
return self._request_json("GET", self._api_path("/jobs"), context=context, params=params)["data"]
|
||||
|
||||
def list_conversations(self, context: RequestContext, *, page: int = 1, page_size: int = 20) -> dict:
|
||||
return self._request_json(
|
||||
"GET",
|
||||
"/api/v0.2/conversations",
|
||||
context=context,
|
||||
params={"page": page, "page_size": page_size},
|
||||
)["data"]
|
||||
|
||||
def get_conversation_detail(self, context: RequestContext, conversation_id: str) -> dict:
|
||||
return self._request_json("GET", f"/api/v0.2/conversations/{conversation_id}", context=context)["data"]
|
||||
|
||||
def get_job(self, context: RequestContext, job_id: str) -> dict:
|
||||
return self._request_json("GET", f"/api/v0.2/jobs/{job_id}", context=context)["data"]
|
||||
|
||||
def run_next_job(self, context: RequestContext) -> dict | None:
|
||||
return self._request_json("POST", "/api/v0.2/jobs/run-next", context=context)["data"]["job"]
|
||||
"GET",
|
||||
self._api_path("/conversations"),
|
||||
context=context,
|
||||
params={"page": page, "page_size": page_size},
|
||||
)["data"]
|
||||
|
||||
def get_conversation_detail(self, context: RequestContext, conversation_id: str) -> dict:
|
||||
return self._request_json("GET", self._api_path(f"/conversations/{conversation_id}"), context=context)["data"]
|
||||
|
||||
def get_job(self, context: RequestContext, job_id: str) -> dict:
|
||||
return self._request_json("GET", self._api_path(f"/jobs/{job_id}"), context=context)["data"]
|
||||
|
||||
def run_next_job(self, context: RequestContext) -> dict | None:
|
||||
return self._request_json("POST", self._api_path("/jobs/run-next"), context=context)["data"]["job"]
|
||||
|
||||
def poll_job_until_terminal(self, context: RequestContext, job_id: str, options: PollOptions | None = None) -> dict:
|
||||
opts = options or PollOptions()
|
||||
@@ -108,16 +110,16 @@ class SharedAIPlatformClient:
|
||||
raise TimeoutError(f"job {job_id} did not reach a terminal state within {opts.timeout_seconds} seconds")
|
||||
time.sleep(opts.interval_seconds)
|
||||
|
||||
def list_knowledge_bases(self, context: RequestContext) -> dict:
|
||||
return self._request_json("GET", "/api/v0.2/knowledge-bases", context=context)["data"]
|
||||
def list_knowledge_bases(self, context: RequestContext) -> dict:
|
||||
return self._request_json("GET", self._api_path("/knowledge-bases"), context=context)["data"]
|
||||
|
||||
def list_documents(self, context: RequestContext, knowledge_base_id: str, *, page: int = 1, page_size: int = 20) -> dict:
|
||||
return self._request_json(
|
||||
"GET",
|
||||
f"/api/v0.2/knowledge-bases/{knowledge_base_id}/documents",
|
||||
context=context,
|
||||
params={"page": page, "page_size": page_size},
|
||||
)["data"]
|
||||
"GET",
|
||||
self._api_path(f"/knowledge-bases/{knowledge_base_id}/documents"),
|
||||
context=context,
|
||||
params={"page": page, "page_size": page_size},
|
||||
)["data"]
|
||||
|
||||
def create_knowledge_base(
|
||||
self,
|
||||
@@ -128,11 +130,11 @@ class SharedAIPlatformClient:
|
||||
retrieval_mode: str = "hybrid",
|
||||
) -> dict:
|
||||
return self._request_json(
|
||||
"POST",
|
||||
"/api/v0.2/knowledge-bases",
|
||||
context=context,
|
||||
json_body={"name": name, "description": description, "retrieval_mode": retrieval_mode},
|
||||
)["data"]
|
||||
"POST",
|
||||
self._api_path("/knowledge-bases"),
|
||||
context=context,
|
||||
json_body={"name": name, "description": description, "retrieval_mode": retrieval_mode},
|
||||
)["data"]
|
||||
|
||||
def upload_document(
|
||||
self,
|
||||
@@ -143,11 +145,11 @@ class SharedAIPlatformClient:
|
||||
mime_type: str | None,
|
||||
content: bytes,
|
||||
) -> dict:
|
||||
response = self._client.post(
|
||||
f"/api/v0.2/knowledge-bases/{knowledge_base_id}/documents",
|
||||
headers=self._headers(context),
|
||||
files={"file": (file_name, content, mime_type or "application/octet-stream")},
|
||||
)
|
||||
response = self._client.post(
|
||||
self._api_path(f"/knowledge-bases/{knowledge_base_id}/documents"),
|
||||
headers=self._headers(context),
|
||||
files={"file": (file_name, content, mime_type or "application/octet-stream")},
|
||||
)
|
||||
self._raise_for_status(response)
|
||||
payload = response.json()
|
||||
if payload.get("code") not in (0, None):
|
||||
@@ -174,10 +176,10 @@ class SharedAIPlatformClient:
|
||||
}
|
||||
if content is not None:
|
||||
request_kwargs["files"] = {"file": (file_name or "document.txt", content, mime_type or "application/octet-stream")}
|
||||
response = self._client.post(
|
||||
f"/api/v0.2/knowledge-bases/{knowledge_base_id}/documents/{document_id}/retry",
|
||||
**request_kwargs,
|
||||
)
|
||||
response = self._client.post(
|
||||
self._api_path(f"/knowledge-bases/{knowledge_base_id}/documents/{document_id}/retry"),
|
||||
**request_kwargs,
|
||||
)
|
||||
self._raise_for_status(response)
|
||||
payload = response.json()
|
||||
if payload.get("code") not in (0, None):
|
||||
@@ -205,10 +207,10 @@ class SharedAIPlatformClient:
|
||||
for record in records
|
||||
]
|
||||
return self._request_json(
|
||||
"POST",
|
||||
f"/api/v0.2/knowledge-bases/{knowledge_base_id}/sync-content",
|
||||
context=context,
|
||||
json_body={
|
||||
"POST",
|
||||
self._api_path(f"/knowledge-bases/{knowledge_base_id}/sync-content"),
|
||||
context=context,
|
||||
json_body={
|
||||
"schema_version": schema_version,
|
||||
"sync_mode": sync_mode,
|
||||
"batch_id": batch_id,
|
||||
@@ -219,10 +221,10 @@ class SharedAIPlatformClient:
|
||||
|
||||
def list_sync_jobs(self, context: RequestContext, knowledge_base_id: str) -> dict:
|
||||
return self._request_json(
|
||||
"GET",
|
||||
f"/api/v0.2/knowledge-bases/{knowledge_base_id}/sync-jobs",
|
||||
context=context,
|
||||
)["data"]
|
||||
"GET",
|
||||
self._api_path(f"/knowledge-bases/{knowledge_base_id}/sync-jobs"),
|
||||
context=context,
|
||||
)["data"]
|
||||
|
||||
def run_legal_review(
|
||||
self,
|
||||
@@ -258,30 +260,55 @@ class SharedAIPlatformClient:
|
||||
conversation_id: str | None = None,
|
||||
) -> Iterator[SSEEvent]:
|
||||
headers = self._headers(context)
|
||||
with self._client.stream(
|
||||
"POST",
|
||||
"/api/v0.2/conversations/stream",
|
||||
headers=headers,
|
||||
json={
|
||||
with self._client.stream(
|
||||
"POST",
|
||||
self._api_path("/conversations/stream"),
|
||||
headers=headers,
|
||||
json={
|
||||
"conversation_id": conversation_id,
|
||||
"message": message,
|
||||
"chat_context": chat_context or {},
|
||||
"provider": provider,
|
||||
"template_key": template_key,
|
||||
},
|
||||
) as response:
|
||||
self._raise_for_status(response)
|
||||
for line in response.iter_lines():
|
||||
if not line:
|
||||
continue
|
||||
if isinstance(line, bytes):
|
||||
line = line.decode("utf-8")
|
||||
if not line.startswith("data: "):
|
||||
continue
|
||||
payload = json.loads(line.removeprefix("data: ").strip())
|
||||
yield SSEEvent(event_type=str(payload.get("type") or "unknown"), payload=payload)
|
||||
|
||||
def _request_json(
|
||||
"provider": provider,
|
||||
"template_key": template_key,
|
||||
},
|
||||
) as response:
|
||||
self._raise_for_status(response)
|
||||
data_lines: list[str] = []
|
||||
for line in response.iter_lines():
|
||||
if isinstance(line, bytes):
|
||||
line = line.decode("utf-8")
|
||||
line = line.rstrip("\r")
|
||||
if not line:
|
||||
yield from self._parse_sse_data_lines(data_lines)
|
||||
data_lines = []
|
||||
continue
|
||||
if line.startswith("data:"):
|
||||
data = line.removeprefix("data:")
|
||||
if data.startswith(" "):
|
||||
data = data[1:]
|
||||
data_lines.append(data)
|
||||
continue
|
||||
if data_lines and line.lstrip().startswith(("{", "[")):
|
||||
data_lines.append(line.strip())
|
||||
yield from self._parse_sse_data_lines(data_lines)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_api_prefix(api_prefix: str) -> str:
|
||||
normalized = (api_prefix or "/api/v0.2").strip().strip("/")
|
||||
return f"/{normalized}" if normalized else "/api/v0.2"
|
||||
|
||||
def _api_path(self, path: str) -> str:
|
||||
return f"{self.api_prefix}/{path.lstrip('/')}"
|
||||
|
||||
@staticmethod
|
||||
def _parse_sse_data_lines(data_lines: list[str]) -> Iterator[SSEEvent]:
|
||||
payload_text = "\n".join(line for line in data_lines if line).strip()
|
||||
if not payload_text:
|
||||
return
|
||||
payload = json.loads(payload_text)
|
||||
yield SSEEvent(event_type=str(payload.get("type") or "unknown"), payload=payload)
|
||||
|
||||
def _request_json(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
|
||||
Reference in New Issue
Block a user