A high-level DSL that compiles to LangGraph graphs
Project description
langtrans
A high-level Python DSL that compiles to LangGraph graphs.
Inspired by trans-dsl, langtrans lets you declaratively describe agent workflows using composable primitives — and compiles them into LangGraph's CompiledStateGraph with full access to persistence, streaming, human-in-the-loop, and observability.
Why?
LangGraph gives you powerful runtime capabilities (checkpointing, streaming, time-travel debugging). But graph construction is low-level — you wire nodes and edges imperatively, and the control flow is hidden in add_edge / add_conditional_edges calls:
# Raw LangGraph — what's the workflow here?
graph = StateGraph(AgentState)
graph.add_node("agent", call_llm)
graph.add_node("tools", tool_node)
graph.add_edge(START, "agent")
graph.add_conditional_edges(
"agent",
lambda s: "tools" if has_tool_calls(s) else END,
{"tools": "tools", END: END},
)
graph.add_edge("tools", "agent")
app = graph.compile()
# langtrans — the structure IS the workflow
from langtrans import Trans, sequential, loop
app = (
Trans(state_schema=AgentState)
.sequential(call_llm)
.loop(
until=lambda s: not has_tool_calls(s),
body=sequential(tool_node, call_llm),
)
.compile()
)
Both produce the same CompiledStateGraph with the same runtime superpowers. The difference is readability: langtrans makes the workflow structure — sequential, concurrent, conditional, loop — visible in the code itself, instead of buried in edge-wiring.
Install
pip install langtrans-dsl
Or for development:
uv sync
Requires Python 3.11+ and langgraph.
Quick Start
import operator
from typing import Annotated, TypedDict
from langtrans import Trans
class State(TypedDict):
messages: Annotated[list, operator.add]
metadata: dict
def fetch_data(state):
meta = dict(state["metadata"])
meta["data"] = "fetched"
return {"metadata": meta}
def process(state):
meta = dict(state["metadata"])
meta["result"] = f"processed {meta['data']}"
return {"metadata": meta}
def respond(state):
meta = dict(state["metadata"])
meta["response"] = f"Done: {meta['result']}"
return {"metadata": meta}
app = (
Trans(state_schema=State)
.sequential(fetch_data, process, respond)
.compile()
)
result = app.invoke({"messages": [], "metadata": {}})
print(result["metadata"]["response"])
# Done: processed fetched
Primitives
Sequential
Run steps in order:
Trans(state_schema=State).sequential(step_a, step_b, step_c).compile()
Concurrent
Run steps in parallel (fan-out / fan-in):
Trans(state_schema=State).concurrent(search_web, search_db, search_docs).compile()
Optional
Conditional branching:
Trans(state_schema=State).optional(
is_confident, then_=respond, else_=ask_clarification
).compile()
Guards can be plain functions or composable Spec objects:
from langtrans import Spec
confident = Spec(lambda s: s["metadata"]["confidence"] > 0.8)
has_data = Spec(lambda s: "data" in s["metadata"])
Trans(state_schema=State).optional(
confident & has_data, then_=respond, else_=gather_more
).compile()
Loop
Repeat a body N times or until a condition:
# Fixed count
Trans(state_schema=State).loop(body=retry_step, times=3).compile()
# Until condition
Trans(state_schema=State).loop(
body=agent_step,
until=lambda s: s["metadata"].get("done"),
).compile()
Switch
Multi-way branching — the key to arbitrary state machines:
from langtrans import Trans, switch
Trans(state_schema=State).loop(
until=lambda s: s["metadata"].get("done"),
body=switch(
key=classify_situation,
cases={
"need_tools": execute_tools,
"need_research": do_research,
"need_approval": wait_for_human,
"ready": generate_response,
},
),
).compile()
Nesting
All primitives compose via top-level functions (sequential, concurrent, optional, loop, switch) for building sub-workflows:
from langtrans import Trans, concurrent, optional, sequential
app = (
Trans(state_schema=State)
.sequential(
fetch_data,
concurrent(
sequential(call_llm, parse_result),
search_web,
),
optional(
is_confident,
then_=respond,
else_=escalate,
),
)
.compile()
)
Named Groups (Proc)
Use Proc("name") when you want to label a group for debugging or reuse:
from langtrans import Trans, Proc
ingest = Proc("ingest").sequential(fetch, validate, transform)
Trans(state_schema=State)
.sequential(ingest, respond)
.compile()
Rollback / Saga Pattern
Actions can declare rollback functions for automatic compensation on failure:
from langtrans import Trans, action
@action(rollback=undo_write_db)
def write_db(state): ...
@action(rollback=undo_notify)
def notify_service(state): ...
app = (
Trans(state_schema=State)
.sequential(validate, write_db, notify_service, confirm)
.compile()
)
# If notify_service fails: undo_write_db runs automatically
LangGraph Pass-Through
compile() passes all kwargs directly to LangGraph:
from langgraph.checkpoint.memory import MemorySaver
app = flow.compile(
checkpointer=MemorySaver(),
interrupt_before=["human_review"],
)
You get the full CompiledStateGraph — use it exactly as you would any LangGraph graph.
State Management
Define state as a TypedDict with LangGraph-style annotations:
class MyState(TypedDict):
messages: Annotated[list, operator.add] # accumulates via +
metadata: dict # replaced on update
Actions return partial state updates: {"metadata": new_meta}. The library doesn't transform state — it's a direct pass-through to LangGraph.
Examples
| Example | What it shows |
|---|---|
sequential_agent.py |
Simple 3-step pipeline |
parallel_tools.py |
Concurrent search + conditional routing |
react_agent.py |
ReAct loop (simulated LLM) |
react_agent_openai.py |
Real OpenAI ReAct agent vs raw LangGraph |
rollback_pipeline.py |
Saga-pattern rollback on failure |
smart_assistant.py |
Domain-driven state machine via loop + switch |
python examples/sequential_agent.py
python examples/smart_assistant.py
When to Use langtrans vs Plain Python
Use plain Python when you don't need failure recovery, human-in-the-loop, streaming, or observability.
Use langtrans (compiling to LangGraph) when you need:
- Checkpoint/resume after failures
- Human approval gates
- Time-travel debugging
- LangSmith tracing
- Token streaming from LLM nodes
- Workflow visualization
langtrans doesn't add computation power over Python. It makes LangGraph workflows readable — you see the control flow in the code instead of reconstructing it from edge tables.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langtrans_dsl-0.1.1.tar.gz.
File metadata
- Download URL: langtrans_dsl-0.1.1.tar.gz
- Upload date:
- Size: 111.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d0808a0170e6262fdad59d1a265e83e24644797d7613ef7b942880a2b5435268
|
|
| MD5 |
ef8cc81cfab0657bd5e206c24641fb72
|
|
| BLAKE2b-256 |
812dd6194dfb6c07370a6eb550e2c90bb7aa1a5bf3bab5651b0c5ff9fd03daa1
|
Provenance
The following attestation bundles were made for langtrans_dsl-0.1.1.tar.gz:
Publisher:
release.yml on austinzh/langtrans
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langtrans_dsl-0.1.1.tar.gz -
Subject digest:
d0808a0170e6262fdad59d1a265e83e24644797d7613ef7b942880a2b5435268 - Sigstore transparency entry: 1395397988
- Sigstore integration time:
-
Permalink:
austinzh/langtrans@a836c24c10e79fcc0261e9edec18c3e25383007e -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/austinzh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a836c24c10e79fcc0261e9edec18c3e25383007e -
Trigger Event:
release
-
Statement type:
File details
Details for the file langtrans_dsl-0.1.1-py3-none-any.whl.
File metadata
- Download URL: langtrans_dsl-0.1.1-py3-none-any.whl
- Upload date:
- Size: 10.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bb8390e13f82506584b44e8e423f045309b40527dfeac2bebe456f314128dcc
|
|
| MD5 |
88661474db6f3a8400dd021038460acc
|
|
| BLAKE2b-256 |
175e4458a61cae2f4472f28245fa09143b14836d9cfd81f4d610ff6a6dc49248
|
Provenance
The following attestation bundles were made for langtrans_dsl-0.1.1-py3-none-any.whl:
Publisher:
release.yml on austinzh/langtrans
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langtrans_dsl-0.1.1-py3-none-any.whl -
Subject digest:
1bb8390e13f82506584b44e8e423f045309b40527dfeac2bebe456f314128dcc - Sigstore transparency entry: 1395398052
- Sigstore integration time:
-
Permalink:
austinzh/langtrans@a836c24c10e79fcc0261e9edec18c3e25383007e -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/austinzh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a836c24c10e79fcc0261e9edec18c3e25383007e -
Trigger Event:
release
-
Statement type: