LangChain adapter¶
tachyone.integrations.langchain wraps a Tachyone backend as a LangChain/LangGraph Runnable, so a
graph node can make a typed choice / score / noul decision and keep the canonical response.
- Module:
tachyone.integrations.langchain - Extra:
langchain(uv sync --extra langchain→langchain-core+langgraph) - Tests:
tests/test_langchain.py
Install¶
Without the extra, create_runnable() raises the langchain extra is required: uv sync --extra langchain.
predict() needs no extra — it is plain Python over tachyone.wire.answer.
Two entry points¶
predict(backend, state, questions, *, model="tachyone-latest") -> dict¶
Runs one request and returns the canonical payload. Verified output with the model-free backend:
from tachyone.backends.fake import FakeBackend
from tachyone.integrations.langchain import predict
questions = {
"urgent": {"type": "noul", "instructions": "Is it urgent?"},
"team": {
"type": "choice",
"instructions": "Which team?",
"criteria": {"billing": "refunds", "technical": "bugs"},
},
}
predict(FakeBackend(), "please refund", questions)
# {'model': 'tachyone-latest',
# 'answers': {'urgent': {'type': 'noul', 'noul': 0.5},
# 'team': {'type': 'choice', 'choice': 'billing',
# 'probabilities': {'billing': 0.5, 'technical': 0.5},
# 'confidence': 0.5}},
# 'usage': {'input_tokens': 3, 'output_tokens': 2}}
create_runnable(backend, *, model="tachyone-latest") -> Runnable¶
from tachyone.backends import build_backend
from tachyone.config import Config
from tachyone.integrations.langchain import create_runnable
backend = build_backend(Config.from_env()) # TACHYONE_BACKEND decides
runnable = create_runnable(backend, model="tachyone-latest")
runnable.invoke({
"state": "please refund",
"questions": questions,
})
# -> {'model': ..., 'answers': {...}, 'usage': {...}}
Input contract for invoke:
| Key | Type | Required |
|---|---|---|
state |
str |
yes |
questions |
dict of canonical question objects |
yes |
model |
str |
no (falls back to the model= passed to create_runnable) |
Inside LangGraph:
from langchain_core.runnables import RunnableLambda
decide = RunnableLambda(lambda payload: runnable.invoke(payload))
graph.add_node("decide", decide)
Choosing a backend¶
# model-free (tests, demos)
from tachyone.backends.fake import FakeBackend
runnable = create_runnable(FakeBackend())
# whatever TACHYONE_BACKEND says (fake / encoder / onnx / llm)
backend = build_backend(Config.from_env())
runnable = create_runnable(backend)
Boundary rules¶
- The adapter is additive: it calls
tachyone.wire.answer, so field names, nesting and error statuses are identical toPOST /v1/systemone. It never invents a LangChain-specific shape. predict()/create_runnable()runasyncio.run(...)internally — do not call them from inside a running event loop.- Invalid questions raise the contract
422; let it propagate so the graph sees the failure. - Optional deps stay optional: importing the module without the extra is fine, only
create_runnable()needslangchain-core(ADR: no hosted/heavy dependency in core).
Related¶
docs/mcp.md— the same capability over the MCP stdio protocol.docs/protocol.md— the response shape.docs/adr/ADR-0009-extension-endpoints.md— why extensions must not alter the wire.