agentplane-runtime 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of agentplane-runtime might be problematic. Click here for more details.
- agentplane_runtime/__init__.py +8 -0
- agentplane_runtime/api.py +232 -0
- agentplane_runtime/app.py +85 -0
- agentplane_runtime/auth.py +137 -0
- agentplane_runtime/db.py +129 -0
- agentplane_runtime/definitions.py +280 -0
- agentplane_runtime/engine.py +407 -0
- agentplane_runtime/llm.py +121 -0
- agentplane_runtime/registration.py +145 -0
- agentplane_runtime/resources.py +237 -0
- agentplane_runtime/secrets.py +45 -0
- agentplane_runtime/serving.py +383 -0
- agentplane_runtime/settings.py +31 -0
- agentplane_runtime/tracing.py +52 -0
- agentplane_runtime/validation.py +91 -0
- agentplane_runtime/vector.py +149 -0
- agentplane_runtime-0.0.1.dist-info/METADATA +48 -0
- agentplane_runtime-0.0.1.dist-info/RECORD +19 -0
- agentplane_runtime-0.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Read-only vector DB access (SPEC §3.2): qdrant via REST, pgvector via asyncpg.
|
|
2
|
+
|
|
3
|
+
Vector DBs are consumed **read-only by contract** — there is no upsert path
|
|
4
|
+
anywhere in the runtime.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from importlib.util import find_spec
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from agentplane_core import Document, JsonObject, VectorDBResource
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class VectorDBError(RuntimeError):
|
|
17
|
+
"""Vector DB request failed."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QdrantReader:
|
|
21
|
+
"""Qdrant REST access: collection info + similarity search."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, url: str, api_key: str = "", *, timeout: float = 30.0) -> None:
|
|
24
|
+
self._url = url.rstrip("/")
|
|
25
|
+
self._headers = {"api-key": api_key} if api_key else {}
|
|
26
|
+
self._timeout = timeout
|
|
27
|
+
|
|
28
|
+
async def collection_dimension(self, collection: str) -> int:
|
|
29
|
+
try:
|
|
30
|
+
async with httpx.AsyncClient(timeout=self._timeout) as client:
|
|
31
|
+
response = await client.get(
|
|
32
|
+
f"{self._url}/collections/{collection}", headers=self._headers
|
|
33
|
+
)
|
|
34
|
+
except httpx.HTTPError as exc:
|
|
35
|
+
raise VectorDBError(f"qdrant unreachable: {exc}") from exc
|
|
36
|
+
if response.status_code != httpx.codes.OK:
|
|
37
|
+
raise VectorDBError(
|
|
38
|
+
f"cannot read collection {collection!r}: HTTP {response.status_code}"
|
|
39
|
+
)
|
|
40
|
+
params = response.json().get("result", {}).get("config", {}).get("params", {})
|
|
41
|
+
vectors = params.get("vectors", {})
|
|
42
|
+
size = vectors.get("size") if isinstance(vectors, dict) else None
|
|
43
|
+
if not isinstance(size, int):
|
|
44
|
+
raise VectorDBError(f"collection {collection!r} has no readable vector size")
|
|
45
|
+
return size
|
|
46
|
+
|
|
47
|
+
async def search(
|
|
48
|
+
self,
|
|
49
|
+
collection: str,
|
|
50
|
+
vector: list[float],
|
|
51
|
+
top_k: int,
|
|
52
|
+
filter: JsonObject | None = None,
|
|
53
|
+
) -> list[Document]:
|
|
54
|
+
body: dict[str, object] = {"vector": vector, "limit": top_k, "with_payload": True}
|
|
55
|
+
if filter is not None:
|
|
56
|
+
body["filter"] = filter
|
|
57
|
+
try:
|
|
58
|
+
async with httpx.AsyncClient(timeout=self._timeout) as client:
|
|
59
|
+
response = await client.post(
|
|
60
|
+
f"{self._url}/collections/{collection}/points/search",
|
|
61
|
+
json=body,
|
|
62
|
+
headers=self._headers,
|
|
63
|
+
)
|
|
64
|
+
except httpx.HTTPError as exc:
|
|
65
|
+
raise VectorDBError(f"qdrant unreachable: {exc}") from exc
|
|
66
|
+
if response.status_code != httpx.codes.OK:
|
|
67
|
+
raise VectorDBError(f"search failed: HTTP {response.status_code}")
|
|
68
|
+
documents: list[Document] = []
|
|
69
|
+
for hit in response.json().get("result", []):
|
|
70
|
+
payload = hit.get("payload") or {}
|
|
71
|
+
text = payload.get("text") or payload.get("content") or payload.get("chunk") or ""
|
|
72
|
+
metadata = {k: v for k, v in payload.items() if k not in ("text", "content", "chunk")}
|
|
73
|
+
documents.append(
|
|
74
|
+
Document(text=str(text), score=float(hit.get("score", 0.0)), metadata=metadata)
|
|
75
|
+
)
|
|
76
|
+
return documents
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class PgvectorReader:
|
|
80
|
+
"""pgvector access via asyncpg ([postgres] extra); degrades with a clear error."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, dsn: str, *, table_prefix: str = "") -> None:
|
|
83
|
+
if find_spec("asyncpg") is None:
|
|
84
|
+
raise VectorDBError("pgvector resources need the [postgres] extra (asyncpg) installed")
|
|
85
|
+
self._dsn = dsn
|
|
86
|
+
self._table_prefix = table_prefix
|
|
87
|
+
|
|
88
|
+
async def collection_dimension(self, collection: str) -> int:
|
|
89
|
+
import asyncpg # noqa: PLC0415 - optional [postgres] extra
|
|
90
|
+
|
|
91
|
+
conn = await asyncpg.connect(self._dsn)
|
|
92
|
+
try:
|
|
93
|
+
row = await conn.fetchrow(
|
|
94
|
+
"""
|
|
95
|
+
SELECT atttypmod AS dim FROM pg_attribute
|
|
96
|
+
WHERE attrelid = $1::regclass AND attname = 'embedding'
|
|
97
|
+
""",
|
|
98
|
+
collection,
|
|
99
|
+
)
|
|
100
|
+
finally:
|
|
101
|
+
await conn.close()
|
|
102
|
+
if row is None or not isinstance(row["dim"], int) or row["dim"] <= 0:
|
|
103
|
+
raise VectorDBError(f"table {collection!r} has no readable embedding dimension")
|
|
104
|
+
return int(row["dim"])
|
|
105
|
+
|
|
106
|
+
async def search(
|
|
107
|
+
self,
|
|
108
|
+
collection: str,
|
|
109
|
+
vector: list[float],
|
|
110
|
+
top_k: int,
|
|
111
|
+
filter: JsonObject | None = None,
|
|
112
|
+
) -> list[Document]:
|
|
113
|
+
import asyncpg # noqa: PLC0415 - optional [postgres] extra
|
|
114
|
+
|
|
115
|
+
if not collection.replace("_", "").isalnum():
|
|
116
|
+
raise VectorDBError(f"invalid collection name {collection!r}")
|
|
117
|
+
vector_literal = "[" + ",".join(f"{v:.8f}" for v in vector) + "]"
|
|
118
|
+
conn = await asyncpg.connect(self._dsn)
|
|
119
|
+
try:
|
|
120
|
+
rows = await conn.fetch(
|
|
121
|
+
f"""
|
|
122
|
+
SELECT text, metadata, 1 - (embedding <=> $1::vector) AS score
|
|
123
|
+
FROM {collection} ORDER BY embedding <=> $1::vector LIMIT $2
|
|
124
|
+
""",
|
|
125
|
+
vector_literal,
|
|
126
|
+
top_k,
|
|
127
|
+
)
|
|
128
|
+
finally:
|
|
129
|
+
await conn.close()
|
|
130
|
+
documents: list[Document] = []
|
|
131
|
+
for row in rows:
|
|
132
|
+
metadata = row["metadata"] if isinstance(row["metadata"], dict) else {}
|
|
133
|
+
documents.append(
|
|
134
|
+
Document(text=str(row["text"]), score=float(row["score"]), metadata=metadata)
|
|
135
|
+
)
|
|
136
|
+
return documents
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
Reader = QdrantReader | PgvectorReader
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def reader_for(resource: VectorDBResource, *, api_key: str = "", dsn: str = "") -> Reader:
|
|
143
|
+
"""Build the right reader for a VectorDB resource."""
|
|
144
|
+
if resource.kind == "qdrant":
|
|
145
|
+
return QdrantReader(resource.url, api_key)
|
|
146
|
+
return PgvectorReader(dsn or resource.url)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
__all__ = ["PgvectorReader", "QdrantReader", "Reader", "VectorDBError", "reader_for"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentplane-runtime
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: agentplane runtime: definitions, resources, flow execution, A2A + MCP serving
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: a2a-sdk[http-server]<2,>=1.1
|
|
8
|
+
Requires-Dist: agentplane-core<0.1.0,>=0.0.1
|
|
9
|
+
Requires-Dist: agentplane-sdk<0.1.0,>=0.0.1
|
|
10
|
+
Requires-Dist: aiosqlite>=0.20
|
|
11
|
+
Requires-Dist: cryptography>=43
|
|
12
|
+
Requires-Dist: fastapi>=0.115
|
|
13
|
+
Requires-Dist: fastmcp==3.4.4
|
|
14
|
+
Requires-Dist: httpx>=0.27
|
|
15
|
+
Requires-Dist: langgraph<2,>=1.0
|
|
16
|
+
Requires-Dist: opentelemetry-api>=1.27
|
|
17
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27
|
|
18
|
+
Requires-Dist: opentelemetry-sdk>=1.27
|
|
19
|
+
Requires-Dist: pydantic-settings>=2.4
|
|
20
|
+
Requires-Dist: pyjwt[crypto]>=2.9
|
|
21
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.30
|
|
22
|
+
Requires-Dist: uvicorn>=0.30
|
|
23
|
+
Provides-Extra: postgres
|
|
24
|
+
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
|
|
25
|
+
Requires-Dist: pgvector>=0.3; extra == 'postgres'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# agentplane-runtime
|
|
29
|
+
|
|
30
|
+
Owns flow definitions and resources; executes flows (LangGraph); serves each
|
|
31
|
+
deployed flow as an **A2A agent** (`/a2a/{name}`, a2a-sdk, A2A v1.0) or an
|
|
32
|
+
**MCP server** (`/mcp/{name}`, FastMCP streamable HTTP); self-registers with
|
|
33
|
+
the agentplane registry.
|
|
34
|
+
|
|
35
|
+
Required configuration (env prefix `AGENTPLANE_RUNTIME_`):
|
|
36
|
+
|
|
37
|
+
| Variable | Purpose |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `PUBLIC_BASE_URL` | externally reachable base (a gateway route) |
|
|
40
|
+
| `REGISTRY_URL` | where to self-register (empty disables registration) |
|
|
41
|
+
| `SECRET_KEY` | Fernet key for resource credentials |
|
|
42
|
+
| `LLM_BASE_URL` | gateway's OpenAI-compatible endpoint (resource default) |
|
|
43
|
+
|
|
44
|
+
Run it:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
uvicorn --factory agentplane_runtime.app:create_app --port 8000
|
|
48
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
agentplane_runtime/__init__.py,sha256=q8xssnfMJBzxNFmE1ZngpKpgOBgtq3nCoahw6R4QcH0,300
|
|
2
|
+
agentplane_runtime/api.py,sha256=2b_pOnWtULiyWP2AEOwMsVQD0AZQveyr9s1g5-vIDZw,8190
|
|
3
|
+
agentplane_runtime/app.py,sha256=9YJ36KXLHHEulrxTkJCDws3UYve8uwPQwpwvxHlV59A,3034
|
|
4
|
+
agentplane_runtime/auth.py,sha256=6CygzQV3GgkHnEbZytwEoneK6QAoyDTcCwXT9FTI1o8,4932
|
|
5
|
+
agentplane_runtime/db.py,sha256=MqWNSikYeiPmpxZIAEQIJv8NgIrbQ-tussaNH-4p2P4,4047
|
|
6
|
+
agentplane_runtime/definitions.py,sha256=KwvJWKj4O1gCNpocbcyQxwQnNwvMD3fCaj8q-2hrKNg,10751
|
|
7
|
+
agentplane_runtime/engine.py,sha256=eqC4-LKmPp-TvvZ5BBrw3rGxoh1RctxmNOzDA7Oh9yc,16495
|
|
8
|
+
agentplane_runtime/llm.py,sha256=RYmOrv67s91XeWFIs9qvQJy3nIhZN72jijRiLYP9wgk,4574
|
|
9
|
+
agentplane_runtime/registration.py,sha256=4wwZcdWLoI5b5cyxGx6AoWxgWyKwyjPSbbT763xpza4,5263
|
|
10
|
+
agentplane_runtime/resources.py,sha256=nLP1zyjPUp4g00YapkQKc_K1Iws5NfV6SAM6HGlUEKA,8886
|
|
11
|
+
agentplane_runtime/secrets.py,sha256=vp1Ul_wDs5KlrNwMab_bnloqWEGk8arT7-o3QGsMm0Q,1614
|
|
12
|
+
agentplane_runtime/serving.py,sha256=fq9kyyNm03fivXtq1VXEKVkPL-xafc5m5rGcHeKuv_Y,13694
|
|
13
|
+
agentplane_runtime/settings.py,sha256=9EBcHUXaYAOZwEsczZVXKQzhCICQQWeNyEuzJqt65_8,1116
|
|
14
|
+
agentplane_runtime/tracing.py,sha256=8Wzse1Se3U4m80W7OWuMB84MVaDuk43mci1dDf5OPmA,1949
|
|
15
|
+
agentplane_runtime/validation.py,sha256=_ImfSHqwP2nut1Uv75YSdHz2Dvfd21-nOjFozpS6I6c,3214
|
|
16
|
+
agentplane_runtime/vector.py,sha256=ESwm8P-X-U8ed_kCMgv9RWSx21thmM_m5ZQCZyFcFwA,5732
|
|
17
|
+
agentplane_runtime-0.0.1.dist-info/METADATA,sha256=kIS9F--QfF28jv4DjtH95Ubgsec0x-_Xj-bs2mDpATc,1687
|
|
18
|
+
agentplane_runtime-0.0.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
19
|
+
agentplane_runtime-0.0.1.dist-info/RECORD,,
|