Agent Server¶
Drop-in FastAPI wrapper — POST /invoke, POST /stream,
GET/DELETE /threads/{id}, GET /health.
For the concepts, start with Agent Server.
AgentServer ¶
AgentServer(agent: Any, title: str = 'Tulip Agent Server', description: str = 'HTTP API for a Tulip AI Agent', api_key: str | None = None, allow_unauthenticated: bool = False, *, stream_tokens: bool = True, metadata_resolver: MetadataResolver | None = None, decision_handler: DecisionHandler | None = None)
Wrap a Tulip Agent as a FastAPI application.
Example
from tulip.agent import Agent, AgentConfig from tulip.server import AgentServer
agent = Agent(config=AgentConfig(system_prompt="Hello", model=model)) server = AgentServer(agent=agent, api_key="secret") server.run(host="127.0.0.1", port=8000)
Wrap agent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stream_tokens
|
bool
|
Stream model token deltas on |
True
|
metadata_resolver
|
MetadataResolver | None
|
Server-side source of trusted run metadata,
called per request with |
None
|
decision_handler
|
DecisionHandler | None
|
Records the |
None
|
Source code in .sdk/src/tulip/server/app.py
run ¶
Run the server with uvicorn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
Bind address. Defaults to loopback — using a
non-loopback host requires either |
'127.0.0.1'
|
port
|
int
|
Bind port. |
8000
|
**kwargs
|
Any
|
Additional uvicorn.run() arguments. |
{}
|
Source code in .sdk/src/tulip/server/app.py
Serving a graph¶
GraphRunnable adapts a compiled StateGraph to the runnable interface
AgentServer and A2AServer expect. The value at output_key in
the graph's final state becomes the reply; with no output_key, the whole
final state is stringified. The server's thread_id and metadata are
accepted but not yet passed to the graph, and the GET/DELETE /threads/{id}
routes don't work for a served graph: they read the wrapped agent's
config.checkpointer, which GraphRunnable doesn't have.
GraphRunnable ¶
GraphRunnable(graph: StateGraph | Any, *, input_key: str = 'prompt', output_key: str | None = None, name: str | None = None)
Wrap a graph so :class:AgentServer / :class:A2AServer can publish it.
The adapter speaks the Agent contract — async def run(prompt, *,
thread_id=None, metadata=None) -> AsyncIterator[TulipEvent] — by
driving graph.stream({input_key: prompt}) underneath. Each per-node
StreamEvent becomes a :class:ThinkEvent so SSE / A2A clients see
activity in real time; the terminal final-state event becomes a
:class:TerminateEvent with final_message set to
final_state[output_key] (or the full state stringified when
output_key is unset).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
StateGraph | Any
|
A compiled :class: |
required |
input_key
|
str
|
Key under which the user prompt is placed in the
graph's initial state. Defaults to |
'prompt'
|
output_key
|
str | None
|
Key in the graph's final state that holds the
user-visible reply. |
None
|
name
|
str | None
|
Optional display name (echoed into |
None
|
Notes
thread_idandmetadatafrom the server are accepted but not propagated yet — graphs don't have a native notion of a server-managed thread. Per-graph checkpointers handle the same concern at a different layer.- The adapter does NOT touch the agent loop; agents inside the
graph keep emitting their own
TulipEventstream onto the tulip event bus. The shim only translates the graph's outer stream.
Source code in .sdk/src/tulip/server/adapters.py
run
async
¶
run(prompt: str, *, thread_id: str | None = None, metadata: dict[str, Any] | None = None) -> AsyncIterator[TulipEvent]
Drive self.graph.stream(...) and yield TulipEvent shapes.
Yields:
| Name | Type | Description |
|---|---|---|
One |
AsyncIterator[TulipEvent]
|
class: |
AsyncIterator[TulipEvent]
|
consumers see node-by-node progress on the SSE wire), then a |
|
terminal |
AsyncIterator[TulipEvent]
|
class: |
AsyncIterator[TulipEvent]
|
extracted from the graph's final state. |