Python SDK
Schreibe JamJet-Workflows in Python mit Decorators und dem Workflow Builder.
Python SDK
Das JamJet Python SDK ermöglicht es dir, Workflows in Python statt in YAML zu schreiben. Beide kompilieren zur gleichen IR und laufen auf derselben Rust-Runtime.
Installation
pip install jamjetDecorator API
Die Decorator API ist die prägnanteste Art, Workflows zu schreiben. Dekoriere eine Funktion mit @node und JamJet leitet den Node-Typ aus der Funktionssignatur und dem Rückgabetyp ab.
from jamjet import workflow, node, State
@workflow(id="hello-agent", version="0.1.0")
class HelloWorkflow:
@node(start=True)
async def think(self, state: State) -> State:
response = await self.model(
model="claude-haiku-4-5-20251001",
prompt=f"Answer clearly: {state['query']}",
)
return {"answer": response.text}Ausführen:
import asyncio
from jamjet import JamJetClient
async def main():
client = JamJetClient() # connects to http://localhost:7700
result = await client.run(
HelloWorkflow,
input={"query": "What is JamJet?"}
)
print(result.state["answer"])
asyncio.run(main())Workflow-Builder
Für mehr Kontrolle nutze die Builder API:
from jamjet.workflows import WorkflowBuilder, ModelNode, ToolNode, BranchNode
wf = (
WorkflowBuilder("research-agent", version="0.2.0")
.state_schema(query=str, results=list, answer=str)
.add_node(
ToolNode("search")
.server("brave-search")
.tool("web_search")
.arguments({"query": "{{ state.query }}", "count": 5})
.output_key("results")
.next("draft")
)
.add_node(
ModelNode("draft")
.model("claude-sonnet-4-6")
.prompt("""
Search results: {{ state.results | join('\\n') }}
Answer: {{ state.query }}
""")
.output_key("answer")
.next("end")
)
.start("search")
.build()
)State-Zugriff
State ist ein typisiertes dict-ähnliches Objekt. Greife auf Keys mit state["key"] oder state.key zu:
@node
async def process(self, state: State) -> State:
query = state["query"] # raises KeyError if missing
context = state.get("context") # returns None if missing
# Return a partial state patch — only keys you include are updated
return {"answer": "...", "confidence": 0.95}Tipp: Nodes geben State-Patches zurück, nicht den vollständigen State. Du musst nur die Keys zurückgeben, die du aktualisieren möchtest. Bestehende Keys bleiben erhalten.
Modellaufrufe
Verwenden Sie self.model() innerhalb eines beliebigen Knotens, um ein LLM aufzurufen:
@node
async def think(self, state: State) -> State:
response = await self.model(
model="claude-sonnet-4-6",
prompt=f"Antwort: {state['query']}",
system="Du bist prägnant und präzise.",
temperature=0.3,
max_tokens=512,
)
# response.text — vollständiger Text
# response.usage.input_tokens
# response.usage.output_tokens
# response.model
return {"answer": response.text}Tool-Aufrufe (MCP)
Verwenden Sie self.tool(), um ein Tool von einem verbundenen MCP-Server aufzurufen:
@node
async def search(self, state: State) -> State:
result = await self.tool(
server="brave-search",
tool="web_search",
arguments={"query": state["query"], "count": 5},
)
return {"results": result.content}HTTP-Aufrufe
@node
async def fetch(self, state: State) -> State:
result = await self.http(
method="GET",
url=f"https://api.example.com/items/{state['item_id']}",
headers={"Authorization": f"Bearer {self.env('API_KEY')}"},
)
return {"raw": result.json()}Verzweigung
from jamjet import node, branch
@node
@branch(
conditions=[
("state['confidence'] >= 0.9", "done"),
("state['confidence'] >= 0.5", "refine"),
],
default="escalate",
)
async def route(self, state: State) -> State:
return {} # Verzweigungsknoten liest vorhandenen State — keine Ausgabe erforderlichParallele Ausführung
from jamjet.workflows import ParallelNode
.add_node(
ParallelNode("gather")
.branches(["search", "fetch-docs", "check-cache"])
.join("synthesize")
)Retry-Richtlinien
from jamjet.workflows import RetryPolicy
ModelNode("think")
.model("claude-haiku-4-5-20251001")
.prompt("...")
.retry(RetryPolicy(
max_attempts=3,
backoff="exponential",
delay_ms=500,
))Workflows ausführen
from jamjet import JamJetClient
client = JamJetClient(base_url="http://localhost:7700")
# Ausführen und auf Abschluss warten
result = await client.run(wf, input={"query": "..."})
print(result.state)
print(result.execution_id)
print(result.duration_ms)
# Fire and Forget — sofort eine Execution-ID zurückerhalten
exec_id = await client.submit(wf, input={"query": "..."})
# Status abfragen
status = await client.get_execution(exec_id)
print(status.status) # running | completed | failed
# Ereignisse in Echtzeit streamen
async for event in client.stream(wf, input={"query": "..."}):
print(event.type, event.node_id)Typannotationen
Das SDK enthält vollständige Type Stubs. Im Strict Mode:
from jamjet import State, NodeResult
from typing import TypedDict
class MyState(TypedDict):
query: str
answer: str
confidence: float
@node(start=True)
async def think(self, state: MyState) -> NodeResult[MyState]:
...
return NodeResult(answer="...", confidence=0.9)Konfiguration
from jamjet import JamJetClient, JamJetConfig
client = JamJetClient(config=JamJetConfig(
base_url="http://localhost:7700",
api_key="YOUR_API_KEY", # für Hosted/Production
timeout_ms=30_000,
default_model="claude-haiku-4-5-20251001",
))Oder über Umgebungsvariablen:
export JAMJET_URL=http://localhost:7700
export JAMJET_API_KEY=...