Run The Demo API¶
This tutorial uses the self-contained project under demo/api. It serves
several LangGraph graphs through the OpenAI-compatible /v1 interface.
Prerequisites¶
- Python 3.11 or newer
uv- Bash and Just 1.58.0 or newer
- PostgreSQL (the included Compose service requires Docker)
- An OpenAI-compatible upstream model only if you call the LLM-backed graphs
Start without an upstream model
Several deterministic graphs do not require provider credentials. Use the graph matrix to choose one and see its other dependencies.
Start PostgreSQL And The API¶
Demo environment settings
The API reads DEMO_API_POSTGRES_URI from the demo environment. Use
.env.example
for the supplied connection settings.
LLM-backed graphs additionally read DEMO_API_OPENAI_BASE_URL,
DEMO_API_OPENAI_API_KEY, and DEMO_API_OPENAI_MODEL. The
lgos-rag graph also reads DEMO_API_OPENAI_EMBEDDING_MODEL. Its corpus is
packaged with the API. The server-tool graph reads
DEMO_API_WEB_SEARCH_BACKEND and DEMO_API_WEB_SEARCH_URL to choose its
web-search execution backend. These settings and dependencies belong to the
API project and are not installed with the library.
The direct lgos-a base URL is http://localhost:3004/v1. Compose also runs
the same image as independently addressable lgos-b on port 3005; the two
services expose the same graph set under separate provider identities.
The separate lgos-files-api project and image serve the central S3-backed
Files API on port 3006. It is not mounted into either graph API; see its
run guide and settings.
Inspect registered graphs:
Each demo graph publishes its API-owned description and feature names in the
lightweight lgos list extension.
The complete model and requirement matrix is in Example Graphs.
Call A Graph¶
from openai import OpenAI
client = OpenAI(base_url="http://localhost:3004/v1", api_key="DUMMY")
response = client.responses.create(
model="custom-input-output-context",
input="Show me the custom adapter.",
store=False,
user="demo-user",
)
print(response.output_text)
Try the citation graph:
response = client.responses.create(
model="citation-events",
input="Show me a cited answer.",
store=False,
)
print(response.output_text)
print(response.output[0].content[0].annotations)
See Events And Citations for this graph's output and Citation ownership for the normative transport boundary.
Ask the RAG graph about the packaged LGOS overview and demo documentation with real-time token streaming:
stream = client.responses.create(
model="lgos-rag",
input="How does LGOS streaming work?",
store=False,
stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
See LGOS RAG for its retrieval flow, bounded rewrite, and process-local index lifetime.
Start with the dependency-free MCP-shaped example:
response = client.responses.create(
model="mcp-mock",
input="What is the weather in Istanbul?",
store=False,
)
print(response.output_text)
mcp-mock uses a stand-in client and deterministic fake model, so it teaches
async tool discovery and the agent tool loop without requiring an MCP server,
gateway, database, or provider credential. See
Core Graph Patterns.
The real mcp-postgres graph expects its OpenAI client to discover and execute
tools through the selected gateway's MCP endpoint. Use the maintained Chainlit
or Open WebUI client for the complete native tool loop; see
PostgreSQL Through Native MCP.
advanced-graph uses the same client-owned loop for any tools authorized by the
gateway; mcp-postgres remains the narrower database-focused example.
Try the deterministic status-event showcase:
stream = client.responses.create(
model="status-events",
input="Prepare the media workflow.",
store=False,
stream=True,
user="demo-user",
)
phases = {}
for event in stream:
if event.type == "response.output_item.added" and event.item.type == "message":
phases[event.output_index] = event.item.phase
elif event.type == "response.output_text.done":
print(f"{phases[event.output_index]}: {event.text}")
See Events And Citations for the status and custom-event flows and their client behavior.
Try the deterministic response-outcome showcase:
refusal = client.responses.create(
model="response-outcomes",
input="refusal",
store=False,
)
print(refusal.status, refusal.output[0].content[0].refusal)
stream = client.responses.create(
model="response-outcomes",
input="incomplete",
store=False,
stream=True,
)
for event in stream:
if event.type == "response.incomplete":
print(event.response.incomplete_details.reason)
See Core Graph Patterns for when a refusal differs from an incomplete response and which terminal events clients must handle.
Try A Demo Client¶
The demo includes optional Chainlit and
Open WebUI clients. The Compose stack routes both through the
LiteLLM or Bifrost gateway selected by
OPENAI_GATEWAY_TYPE, never directly to an API or Files container. See
Demo Architecture for the shared request and ownership
flows, then use each client guide for its adapter-specific behavior.