Lesson 1 — an application that is AI-ready¶
What you will build¶
An application that works, and that has a place for a model without depending on one. That is the whole lesson: the model is not on the critical path of a support desk, and an application that cannot start without it is an application whose outage is a model's outage.
xyberos.ai is preview tier (stability guide): the facade's shape is
settled, and its surface may still move in a minor release.
What you need¶
Nothing beyond the core package. The determinism here is not a trick for the lesson — the repository ships a provider built for exactly this, and the AI path never needs a model service to run:
"""Lesson 1 — an application that is AI-ready and needs no model to run.
Run it with:
python -m learn_ai.ai01_ready
"""
from __future__ import annotations
from typing import Annotated
from xyberos import App, Inject, Settings
from xyberos.ai import AIFacade, FakeModelProvider, LoggingPolicy, ModelCapabilities
from xyberos.http import HttpAdapter
from xyberos.http.testing import create_test_client
TICKETS = ["printer is on fire", "badge reader is broken"]
class TicketDesk:
"""The part of the application that has nothing to do with a model."""
def __init__(self) -> None:
self._tickets = list(TICKETS)
def open_tickets(self) -> list[str]:
"""Return the open tickets."""
return list(self._tickets)
def create_app(*, provider: object | None = None) -> App:
"""Compose the application: a service, and an AI facade behind a seam.
``provider`` is what a test or a deployment supplies. Passing nothing still composes a working
application, because this lesson makes no model call at all.
"""
app = App(settings=Settings.load(environ={}, overrides={"app": {"name": "ai_assistant"}}))
app.provide(TicketDesk, instance=TicketDesk())
app.provide(
AIFacade,
instance=AIFacade(
provider
if provider is not None
else FakeModelProvider([], capabilities=ModelCapabilities()),
logging_policy=LoggingPolicy.metadata_only(),
),
eager=True,
)
@app.get("/tickets")
async def list_tickets(desk: Annotated[TicketDesk, Inject()]) -> list[str]:
return desk.open_tickets()
@app.get("/assistant")
async def assistant_status(assistant: Annotated[AIFacade, Inject()]) -> dict[str, object]:
health = await assistant.health()
return {
"provider": assistant.name,
"model": health.model,
"status": health.status.value,
"detail": health.detail,
}
return app
def main() -> None:
"""Show that the application serves its routes with no model call at all."""
provider = FakeModelProvider([], capabilities=ModelCapabilities())
with create_test_client(HttpAdapter(create_app(provider=provider))) as client:
print("tickets: ", client.get("/tickets").json())
print("assistant:", client.get("/assistant").json())
print("model calls:", provider.calls)
if __name__ == "__main__":
main()
Three things in that module are the lesson:
TicketDeskknows nothing about a model. It is a service with a method, exactly like the ones in the traditional path.- The facade is registered behind a contract.
app.provide(AIFacade, instance=...)means a route can ask for it, and a test can replace it. The provider is a constructor argument of the composition, not a global. - Nothing calls the model. The route that reports the assistant's status asks the facade for
health(), which is metadata: provider name, model, status. No prompt leaves the process.
How to run it¶
What it prints¶
tickets: ['printer is on fire', 'badge reader is broken']
assistant: {'provider': 'fake', 'model': 'fake-model', 'status': 'ready', 'detail': ''}
model calls: 0
What the numbers say¶
| Line | What it establishes |
|---|---|
tickets: [...] |
the application's own work, served with no model involved |
assistant: {'provider': 'fake', ...} |
the facade is reachable, and reports what it is connected to |
model calls: 0 |
and this lesson made none: the seam exists before it is used |
FakeModelProvider([], capabilities=ModelCapabilities()) is a provider with an empty script and no
declared capabilities. It is a working provider — that is why health() answers — and it fails loudly
if something asks it for a call it was not scripted for, which is the property a test wants.
Common mistakes¶
- Reaching for a model before the application works. The first version of a feature is code that does not need one; the model is added to a seam that already exists.
- Constructing a provider inside a service. A service that builds its own provider cannot be tested without one, which is the coupling this lesson's composition avoids.
- Reading the API key or base URL at import time. Configuration belongs at composition, so a test composes a different provider without touching the process environment.
- Treating the fake provider as a mock. It implements the full provider contract and records what it was asked, so a test asserts behaviour rather than call counts on a stub.
Next¶
Lesson 2 looks at what a provider declares, because the facade checks those declarations before it makes a call rather than discovering the mismatch in production.