agentlisp 2.0.0__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.
host/gateway.py ADDED
@@ -0,0 +1,128 @@
1
+ """FastAPI HTTP + SSE gateway skeleton.
2
+
3
+ Imports even without optional web deps installed (raises FeatureNotInstalledError only
4
+ when constructing the app; import-level smoke stays green).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from collections.abc import AsyncIterable
11
+ from typing import Any
12
+
13
+ from runtime.base_harness import BaseHarness, ExecutionTrace
14
+ from runtime.errors import FeatureNotInstalledError
15
+
16
+
17
+ class Gateway:
18
+ """Lazy FastAPI container. Builds the app only when `app` is accessed; imports fail
19
+ only if optional dep group `web` is missing and user actually instantiates it."""
20
+
21
+ def __init__(
22
+ self,
23
+ harness_registry: dict[str, BaseHarness] | None = None,
24
+ ) -> None:
25
+ self.registry: dict[str, BaseHarness] = harness_registry or {}
26
+ self.traces: dict[str, ExecutionTrace] = {}
27
+ self._app: Any | None = None
28
+
29
+ def register(self, name: str, harness: BaseHarness) -> None:
30
+ self.registry[name] = harness
31
+
32
+ # --------------------------------------------------------- FastAPI lazy build
33
+ @property
34
+ def app(self) -> Any:
35
+ if self._app is None:
36
+ try:
37
+ from fastapi import FastAPI, HTTPException
38
+ from fastapi.responses import StreamingResponse
39
+ from pydantic import BaseModel as _BM # noqa: N814
40
+ except ImportError as exc: # pragma: no cover
41
+ raise FeatureNotInstalledError("FastAPI gateway", "web") from exc
42
+
43
+ app = FastAPI(
44
+ title="AgentLisp Gateway",
45
+ version="2.0.0",
46
+ description="HTTP/SSE gateway for AgentLisp v2 compiled harnesses",
47
+ )
48
+
49
+ class RunRequest(_BM):
50
+ inputs: dict[str, Any] = {}
51
+ workflow: str | None = None
52
+ stream: bool = False
53
+
54
+ @app.get("/health", tags=["meta"])
55
+ async def health() -> dict[str, Any]:
56
+ return {
57
+ "status": "ok",
58
+ "agents": sorted(self.registry.keys()),
59
+ }
60
+
61
+ @app.post("/v1/agents/{name}/run", tags=["agents"])
62
+ async def run_agent(name: str, req: RunRequest):
63
+ if name not in self.registry:
64
+ raise HTTPException(status_code=404, detail=f"agent not found: {name}")
65
+ if req.stream:
66
+ trace_ref: dict[str, Any] = {}
67
+
68
+ async def _events() -> AsyncIterable[str]:
69
+ harness = self.registry[name]
70
+ async for evt in harness.stream_async(inputs=req.inputs):
71
+ yield (
72
+ "data: " + json.dumps(evt, default=str, ensure_ascii=False) + "\n\n"
73
+ )
74
+ final = trace_ref.get("trace")
75
+ if final is not None:
76
+ yield (
77
+ "data: "
78
+ + json.dumps(
79
+ {"event": "trace", "value": final.model_dump(mode="json")},
80
+ ensure_ascii=False,
81
+ default=str,
82
+ )
83
+ + "\n\n"
84
+ )
85
+ yield "data: [DONE]\n\n"
86
+
87
+ return StreamingResponse(_events(), media_type="text/event-stream")
88
+
89
+ trace = await self.registry[name].run_async(
90
+ inputs=req.inputs, workflow=req.workflow
91
+ )
92
+ self.traces[trace.run_id] = trace
93
+ return trace.model_dump(mode="json")
94
+
95
+ @app.get("/v1/runs/{run_id}", tags=["runs"])
96
+ async def get_run(run_id: str):
97
+ t = self.traces.get(run_id)
98
+ if t is None:
99
+ raise HTTPException(status_code=404, detail="run not found")
100
+ return t.model_dump(mode="json")
101
+
102
+ self._app = app
103
+ return self._app
104
+
105
+
106
+ # ---------------------------------------------------------------- quick launcher
107
+ def serve(
108
+ gateway: Gateway,
109
+ *,
110
+ host: str = "0.0.0.0",
111
+ port: int = 8000,
112
+ uvicorn_kwargs: dict[str, Any] | None = None,
113
+ ) -> None: # pragma: no cover
114
+ """Convenience launcher. Requires 'web' optional group."""
115
+ try:
116
+ import uvicorn # type: ignore[import-not-found]
117
+ except ImportError as exc:
118
+ raise FeatureNotInstalledError("uvicorn launcher", "web") from exc
119
+ kwargs = uvicorn_kwargs or {}
120
+ uvicorn.run(gateway.app, host=host, port=port, **kwargs)
121
+
122
+
123
+ if __name__ == "__main__": # pragma: no cover
124
+ from runtime.base_harness import BaseHarness
125
+
126
+ gw = Gateway()
127
+ gw.register("demo", BaseHarness(agent_cfg={"name": "demo", "purpose": "smoke"}))
128
+ serve(gw)