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.

@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any