83 lines
3.3 KiB
Python
83 lines
3.3 KiB
Python
"""Async client for the Langflow authoring server (visual designer).
|
|
|
|
Only used for the authoring pipeline (list/export flows). Execution always
|
|
goes through LFX (see lfx_client.py).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import httpx
|
|
|
|
from .base_client import BaseApiClient, transport_retry
|
|
|
|
|
|
class LangflowError(Exception):
|
|
pass
|
|
|
|
|
|
class LangflowNotFound(LangflowError):
|
|
pass
|
|
|
|
|
|
class LangflowUnavailable(LangflowError):
|
|
pass
|
|
|
|
|
|
class LangflowClient(BaseApiClient):
|
|
"""Thin async wrapper around the Langflow `/api/v1/flows` endpoints."""
|
|
|
|
def __init__(self, base_url: str, api_key: str = "", timeout: float = 60.0) -> None:
|
|
super().__init__(base_url, timeout=timeout, api_key=api_key)
|
|
|
|
@transport_retry
|
|
async def list_flows(self) -> list[dict]:
|
|
"""List flows (header mode keeps payloads small)."""
|
|
try:
|
|
async with self._client() as client:
|
|
response = await client.get("/api/v1/flows/", params={"header_flows": "true"})
|
|
if response.status_code == 404:
|
|
raise LangflowNotFound("flow list not found")
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
return data if isinstance(data, list) else data.get("items", [])
|
|
except httpx.HTTPStatusError as exc:
|
|
raise LangflowError(f"Langflow error {exc.response.status_code}") from exc
|
|
except httpx.HTTPError as exc:
|
|
raise LangflowUnavailable(f"Langflow unreachable: {exc}") from exc
|
|
|
|
@transport_retry
|
|
async def export_flow(self, langflow_flow_id: str) -> dict:
|
|
"""Export one flow as Langflow JSON (FlowRead: id/name/description/data)."""
|
|
try:
|
|
async with self._client() as client:
|
|
response = await client.get(f"/api/v1/flows/{langflow_flow_id}")
|
|
if response.status_code == 404:
|
|
raise LangflowNotFound(f"flow {langflow_flow_id} not found")
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except httpx.HTTPStatusError as exc:
|
|
raise LangflowError(f"Langflow error {exc.response.status_code}") from exc
|
|
except httpx.HTTPError as exc:
|
|
raise LangflowUnavailable(f"Langflow unreachable: {exc}") from exc
|
|
|
|
@transport_retry
|
|
async def upsert_flow(self, doc: dict) -> dict:
|
|
"""Create or update a flow in Langflow by name (authoring UI refresh).
|
|
|
|
Used after flow/component tightening so the visual editor shows the
|
|
latest canonical JSON without a container restart.
|
|
"""
|
|
try:
|
|
async with self._client() as client:
|
|
existing = await self.list_flows()
|
|
match = next((f for f in existing if f.get("name") == doc.get("name")), None)
|
|
if match and match.get("id"):
|
|
response = await client.patch(f"/api/v1/flows/{match['id']}", json=doc)
|
|
else:
|
|
response = await client.post("/api/v1/flows/", json=doc)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except httpx.HTTPStatusError as exc:
|
|
raise LangflowError(f"Langflow error {exc.response.status_code}") from exc
|
|
except httpx.HTTPError as exc:
|
|
raise LangflowUnavailable(f"Langflow unreachable: {exc}") from exc
|