Human-in-the-loop for LangGraph and LangChain: a blocking ask_human, plus a durable interrupt()/Command resume that reaches your user on their phone.
Project description
pushary-langgraph
Human-in-the-loop for LangGraph and LangChain. Ask a real human to approve, and get the answer on their phone. Two seams:
- A blocking
ask_humanyou call from inside a node. - A durable
pushary_interruptthat parks the graph with LangGraph's nativeinterrupt()and resumes on a signed webhook, so a long wait holds no compute and survives a restart.
Requires the Pushary Partner plan.
Install
pip install pushary-langgraph
Set PUSHARY_API_KEY (get it in your dashboard).
Connect a phone once
from pushary_langgraph import connect
link = connect("user_123") # show this to your end-user; one tap connects their phone
Ask a human inside a node
from pushary_langgraph import ask_human
def approval_node(state):
d = ask_human("Approve this transfer?", external_id=state["user_id"], node="approval")
return {"approved": d["approved"]}
ask_human blocks, polls durably, and fails closed. The idempotency key is derived
from external_id + node + question, so a node that re-runs on resume hits the same
decision instead of paging the human twice.
Durable interrupt
from pushary_langgraph import pushary_interrupt
def approval_node(state):
answer = pushary_interrupt(
"Approve this transfer?",
external_id=state["user_id"],
node="approval",
callback_url=os.environ["PUSHARY_CALLBACK_URL"], # omit to block instead of park
)
return {"approved": answer == "yes"}
With a callback_url, the node opens the decision and calls LangGraph's interrupt()
to park the graph (a checkpointer is required). Keep any code before the call
idempotent, the whole node re-runs on resume.
Resume from the webhook
from pushary_langgraph import resolve_pushary_callback, SIGNATURE_HEADER
from langgraph.types import Command
# POST /pushary/callback
def callback(request):
raw = request.body
cb = resolve_pushary_callback(raw, request.headers.get(SIGNATURE_HEADER), os.environ["PUSHARY_WEBHOOK_SECRET"])
if not cb:
return ("bad signature", 401)
thread_id = lookup_thread(cb["correlationId"]) # your own correlationId -> thread_id map
graph.invoke(Command(resume=cb["answer"]), {"configurable": {"thread_id": thread_id}})
return ("ok", 200)
API
connect(external_id, *, api_key=None, base_url=None)— enroll an end-user's phone, returns the link.ask_human(question, *, external_id, type="confirm", options=None, node=..., ...)— blocking, returns the decision dict.pushary_interrupt(question, *, external_id, node=..., callback_url=None, ...)— blocking, or durable whencallback_urlis set.resolve_pushary_callback(raw_body, signature, secret)— verify + parse a callback into{correlationId, answer, approved, ...}.describe_answer(type, result),is_affirmative(answer),deterministic_key(parts),SIGNATURE_HEADER.
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 pushary_langgraph-0.1.0.tar.gz.
File metadata
- Download URL: pushary_langgraph-0.1.0.tar.gz
- Upload date:
- Size: 7.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6ae8de8cbd4993b38bd5429966e6bd52165d4abb455cf01319d00fb06116d60
|
|
| MD5 |
0abce0969e44ae7a716a87388cd32d40
|
|
| BLAKE2b-256 |
b5a59d5d517ef7132d5c3715a6c1c54f77fa74b67f3e8e5e108a7577c8883545
|
File details
Details for the file pushary_langgraph-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pushary_langgraph-0.1.0-py3-none-any.whl
- Upload date:
- Size: 6.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6443083555eafa132b572038529a43a9fc80e2538d209a91d2409abdc6916f19
|
|
| MD5 |
a51cf22b51b0640a733c1ead37e5a8fb
|
|
| BLAKE2b-256 |
69c0983169f5ddcb783fb81e0b4dc540aae92d0667325a8bcdfaf59de22be9a4
|