Observed since 20 Oct, 21:25 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
agensflow-langgraph
AgensFlow adapter for LangGraph — decorator-based routing across a declared model pool
Decorator-based routing for LangGraph agents. Learn which model in your declared pool each node of your graph should use — automatically, per node, from feedback.
Status: v0.1.3 (alpha).
Everything needed to run an adaptive MAS end-to-end, no hosted service, no external dependencies:
| Piece | What it is |
|---|---|
agensflow-mcp | The policy server. UCB1 bandits, tenant isolation, HTTP + MCP surfaces. Self-hostable (SQLite or Postgres) or hosted. |
agensflow-langgraph | This package. The @agensflow decorator wraps any LangGraph node and routes its LLM calls through the server. |
| Starter policies | examples/starter_policies/ — two importable starters for the security-domain MAS: paper priors (model-agnostic) and paper priors + 120 modern-model runs. |
Two notebooks, one warm-start path each. Both run the same 6-node security-domain MAS on the same paper task; only Section 4 (the starter import) differs.
Both boot the policy server in-process (no separate uvicorn), issue an API
key, import the chosen starter, build a real 6-node MAS with verifier gate +
revision loop, and run against OpenRouter. Runtime ~5-8 min. Cost ~$1-2.
pip install agensflow-langgraph
# For the notebook / examples:
pip install agensflow-mcp langchain-openai jupyter
from langgraph.graph import StateGraph
from langchain_openai import ChatOpenAI
from agensflow_langgraph import agensflow, record_reward
# OpenRouter unifies every provider under one API key — one env var, many models
def or_model(model_id: str):
return ChatOpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
model=model_id,
)
# Declare a per-node pool. The substrate learns which arm wins.
@agensflow(pool={
"cheap": or_model("openai/gpt-4o-mini"),
"balanced": or_model("openai/gpt-4o"),
"deep": or_model("anthropic/claude-sonnet-4"),
})
async def classify_intent(state, model, config=None):
return {"messages": [await model.ainvoke(state["messages"])]}
graph = StateGraph(State)
graph.add_node("classify_intent", classify_intent)
# ... rest of your graph unchanged
# Attach an explicit reward at the graph terminal
def terminal(state):
record_reward(thread_id=state.get("thread_id"), quality=0.85)
return {...}
Every model call inside a decorated node routes through the policy server;
UCB1 over per-node signatures picks the pool arm. Cost + tokens are captured
via LangChain's AIMessage.usage_metadata callback path.
examples/evidence_heavy_mas/ — linear
planner → memory → solver → verifier → evaluator with a verifier-gate
revision loop. Exercises conditional edges + revision cycles.examples/parallel_critic_mas/ — solver
fans out to critic + verifier in parallel, evaluator merges both
signals. Exercises parallel node execution + fan-in state reducers.Both use real ChatOpenAI(base_url=openrouter) calls, structured Pydantic
handoffs via .with_structured_output(method="function_calling"), and
InMemorySaver checkpointing.
export AGENSFLOW_SERVER_URL="https://mcp.agensflow.ai" # or your self-hosted URL
export AGENSFLOW_API_KEY="agf_..." # bearer token
export OPENROUTER_API_KEY="sk-or-..." # for the examples
Or pass them per-decorator:
@agensflow(pool={...}, server_url="http://localhost:8000", tenant_key="agf_...")
examples/*/README.mdApache-2.0
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
httpxEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | httpx>=0.27 | runtime |
langchain-coreEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | langchain-core>=0.3 | runtime |
langgraphEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | langgraph>=0.2 | runtime |
pydanticEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pydantic>=2.5 | runtime |
tenacityEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | tenacity>=9.0 | runtime |
mypyEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | mypy>=1.13; extra == "dev" | runtime |
pytest-asyncioEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pytest-asyncio>=0.24; extra == "dev" | runtime |
pytest-covEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pytest-cov>=5.0; extra == "dev" | runtime |
pytestEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pytest>=8.0; extra == "dev" | runtime |
respxEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | respx>=0.21; extra == "dev" | runtime |
ruffEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | ruff>=0.7; extra == "dev" | runtime |
langsmithEnvironment{
"extras": "",
"marker": "extra == \"langsmith\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | langsmith>=0.1; extra == "langsmith" | runtime |
bdist_wheel · 30.79 KB · Python py3
sdist · 250.15 KB · Python source
Shared repository data · GitHub
Observed since 20 Oct, 21:25 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:ec3be49b51429b74cdb8f59aa663b628b4ed042d04a1cf449d0064b01bc1c24d