SDK de Python
Escribe flujos de trabajo de JamJet en Python usando decoradores y el constructor de workflows.
SDK de Python
El SDK de Python de JamJet te permite escribir flujos de trabajo en Python en lugar de YAML. Ambos se compilan a la misma IR y se ejecutan en el mismo runtime de Rust.
Instalación
pip install jamjetAPI de Decoradores
La API de decoradores es la forma más concisa de escribir flujos de trabajo. Decora una función con @node y JamJet infiere el tipo de nodo a partir de la firma de la función y el tipo de retorno.
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}Ejecútalo:
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())Constructor de flujos de trabajo
Para mayor control, usa la API de constructor:
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()
)Acceso al estado
State es un objeto tipo dict tipado. Accede a las claves con state["key"] o state.key:
@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}consejo: Los nodos devuelven parches de estado, no el estado completo. Solo necesitas devolver las claves que quieres actualizar. Las claves existentes se conservan.
Llamadas al modelo
Usa self.model() dentro de cualquier nodo para llamar a un LLM:
@node
async def think(self, state: State) -> State:
response = await self.model(
model="claude-sonnet-4-6",
prompt=f"Answer: {state['query']}",
system="You are concise and accurate.",
temperature=0.3,
max_tokens=512,
)
# response.text — texto completo
# response.usage.input_tokens
# response.usage.output_tokens
# response.model
return {"answer": response.text}Llamadas a herramientas (MCP)
Usa self.tool() para llamar a una herramienta desde un servidor MCP conectado:
@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}Llamadas HTTP
@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()}Ramificación
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 {} # el nodo de ramificación lee el estado existente — no requiere salidaEjecución paralela
from jamjet.workflows import ParallelNode
.add_node(
ParallelNode("gather")
.branches(["search", "fetch-docs", "check-cache"])
.join("synthesize")
)Políticas de reintento
from jamjet.workflows import RetryPolicy
ModelNode("think")
.model("claude-haiku-4-5-20251001")
.prompt("...")
.retry(RetryPolicy(
max_attempts=3,
backoff="exponential",
delay_ms=500,
))Ejecutar flujos de trabajo
from jamjet import JamJetClient
client = JamJetClient(base_url="http://localhost:7700")
# Ejecutar y esperar la finalización
result = await client.run(wf, input={"query": "..."})
print(result.state)
print(result.execution_id)
print(result.duration_ms)
# Ejecutar sin esperar — recibe un ID de ejecución inmediatamente
exec_id = await client.submit(wf, input={"query": "..."})
# Consultar el estado
status = await client.get_execution(exec_id)
print(status.status) # running | completed | failed
# Transmitir eventos en tiempo real
async for event in client.stream(wf, input={"query": "..."}):
print(event.type, event.node_id)Anotaciones de tipos
El SDK incluye stubs de tipos completos. En modo estricto:
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)Configuración
from jamjet import JamJetClient, JamJetConfig
client = JamJetClient(config=JamJetConfig(
base_url="http://localhost:7700",
api_key="YOUR_API_KEY", # para hosted/producción
timeout_ms=30_000,
default_model="claude-haiku-4-5-20251001",
))O mediante variables de entorno:
export JAMJET_URL=http://localhost:7700
export JAMJET_API_KEY=...