Install any skill in seconds. Free to start, no credit card required.
Get Started Free →LangGraph Functional API with @entrypoint and @task decorators. Use when building workflows with the modern LangGraph pattern, enabling parallel execution, persistence, and human-in-the-loop.
.claude/skills/majiayu000-langgraph-functional/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 6% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 64% | 0% |
Build workflows using decorators instead of explicit graph construction.
Graph API (explicit): Functional API (implicit):
StateGraph → add_node → @task functions +
add_edge → compile @entrypoint orchestrationWhen to Use Functional API:
pythonfrom langgraph.func import entrypoint, task @task def step_one(data: str) -> str: """Task returns a future - call .result() to block""" return process(data) @task def step_two(result: str) -> str: return transform(result) @entrypoint() def my_workflow(input_data: str) -> str: # Tasks return futures - enables parallel execution result1 = step_one(input_data).result() result2 = step_two(result1).result() return result2 # Invoke output = my_workflow.invoke("hello")
.result() to get valuepython@task def fetch_source_a(query: str) -> dict: return api_a.search(query) @task def fetch_source_b(query: str) -> dict: return api_b.search(query) @task def merge_results(results: list[dict]) -> dict: return {"combined": results} @entrypoint() def parallel_search(query: str) -> dict: # Launch in parallel - futures start immediately future_a = fetch_source_a(query) future_b = fetch_source_b(query) # Block on both results results = [future_a.result(), future_b.result()] return merge_results(results).result()
python@task def process_item(item: dict) -> dict: return transform(item) @entrypoint() def batch_workflow(items: list[dict]) -> list[dict]: # Launch all in parallel futures = [process_item(item) for item in items] # Collect results return [f.result() for f in futures]
pythonfrom langgraph.checkpoint.memory import InMemorySaver checkpointer = InMemorySaver() @entrypoint(checkpointer=checkpointer) def resumable_workflow(data: str) -> str: # Workflow state is automatically saved after each task result = expensive_task(data).result() return result # Use thread_id for persistence config = {"configurable": {"thread_id": "session-123"}} result = resumable_workflow.invoke("input", config)
pythonfrom typing import Optional @entrypoint(checkpointer=checkpointer) def stateful_workflow(data: str, previous: Optional[dict] = None) -> dict: """previous contains last return value for this thread_id""" if previous and previous.get("step") == "complete": return previous # Already done result = process(data).result() return {"step": "complete", "result": result}
pythonfrom langgraph.types import interrupt, Command @entrypoint(checkpointer=checkpointer) def approval_workflow(request: dict) -> dict: # Process request result = analyze_request(request).result() # Pause for human approval approved = interrupt({ "question": "Approve this action?", "details": result }) if approved: return execute_action(result).result() else: return {"status": "rejected"} # Initial run - pauses at interrupt config = {"configurable": {"thread_id": "approval-1"}} for chunk in approval_workflow.stream(request, config): print(chunk) # Resume after human review for chunk in approval_workflow.stream(Command(resume=True), config): print(chunk)
python@task def classify(text: str) -> str: return llm.invoke(f"Classify: {text}") # "positive" or "negative" @task def handle_positive(text: str) -> str: return "Thank you for the positive feedback!" @task def handle_negative(text: str) -> str: return "We're sorry to hear that. Creating support ticket..." @entrypoint() def feedback_workflow(text: str) -> str: sentiment = classify(text).result() if sentiment == "positive": return handle_positive(text).result() else: return handle_negative(text).result()
python@task def call_llm(messages: list) -> dict: return llm_with_tools.invoke(messages) @task def call_tool(tool_call: dict) -> str: tool = tools[tool_call["name"]] return tool.invoke(tool_call["args"]) @entrypoint() def agent_loop(query: str) -> str: messages = [{"role": "user", "content": query}] while True: response = call_llm(messages).result() if not response.get("tool_calls"): return response["content"] # Execute tools in parallel tool_futures = [call_tool(tc) for tc in response["tool_calls"]] tool_results = [f.result() for f in tool_futures] messages.extend([response, *tool_results])
python@entrypoint() def streaming_workflow(data: str) -> str: step1 = task_one(data).result() step2 = task_two(step1).result() return step2 # Stream task completion updates for update in streaming_workflow.stream("input", stream_mode="updates"): print(f"Task completed: {update}")
python# "updates" - task completion events for chunk in workflow.stream(input, stream_mode="updates"): print(chunk) # "values" - full state after each task for chunk in workflow.stream(input, stream_mode="values"): print(chunk) # "custom" - custom events from your code for chunk in workflow.stream(input, stream_mode="custom"): print(chunk)
typescriptimport { entrypoint, task, MemorySaver } from "@langchain/langgraph"; const processData = task("processData", async (data: string) => { return await transform(data); }); const workflow = entrypoint( { name: "myWorkflow", checkpointer: new MemorySaver() }, async (input: string) => { const result = await processData(input); return result; } ); // Invoke const config = { configurable: { thread_id: "session-1" } }; const result = await workflow.invoke("hello", config);
typescriptconst fetchA = task("fetchA", async (q: string) => api.fetchA(q)); const fetchB = task("fetchB", async (q: string) => api.fetchB(q)); const parallelWorkflow = entrypoint("parallel", async (query: string) => { // Launch in parallel using Promise.all const [resultA, resultB] = await Promise.all([ fetchA(query), fetchB(query) ]); return { a: resultA, b: resultB }; });
python@task def plan(topic: str) -> list[str]: """Orchestrator creates work items""" sections = planner.invoke(f"Create outline for: {topic}") return sections @task def write_section(section: str) -> str: """Worker processes one item""" return llm.invoke(f"Write section: {section}") @task def synthesize(sections: list[str]) -> str: """Combine results""" return "\n\n".join(sections) @entrypoint() def report_workflow(topic: str) -> str: sections = plan(topic).result() # Fan-out to workers section_futures = [write_section(s) for s in sections] completed = [f.result() for f in section_futures] # Fan-in return synthesize(completed).result()
python@task def unreliable_api(data: str) -> dict: return external_api.call(data) @entrypoint() def retry_workflow(data: str, max_retries: int = 3) -> dict: for attempt in range(max_retries): try: return unreliable_api(data).result() except Exception as e: if attempt == max_retries - 1: raise continue
.result()python# Graph API (before) from langgraph.graph import StateGraph def node_a(state): return {"data": process(state["input"])} def node_b(state): return {"result": transform(state["data"])} graph = StateGraph(State) graph.add_node("a", node_a) graph.add_node("b", node_b) graph.add_edge("a", "b") app = graph.compile() # Functional API (after) @task def process_data(input: str) -> str: return process(input) @task def transform_data(data: str) -> str: return transform(data) @entrypoint() def workflow(input: str) -> str: data = process_data(input).result() return transform_data(data).result()
langgraph-state - State management patterns for complex workflow datalanggraph-routing - Conditional routing and branching decisionslanggraph-parallel - Advanced parallel execution and fan-out patternslanggraph-checkpoints - Persistence and recovery for long-running workflows| Decision | Choice | Rationale | |----------|--------|-----------| | API Style | Functional over Graph | Simpler debugging, familiar Python patterns, implicit graph construction | | Task Returns | Futures with .result() | Enables parallel execution without explicit async/await | | Checkpointing | Optional per-entrypoint | Flexibility for stateless vs. resumable workflows | | Human-in-Loop | interrupt() function | Clean pause/resume semantics with Command pattern |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 24,102 | 12,877 | -47% | 1 | 1 | 0% | 2,964 | 4,410 | +49% | 0 | 0 | — |
case-02 | fail→pass | 22,376 | 16,486 | -26% | 1 | 1 | 0% | 3,595 | 6,144 | +71% | 0 | 0 | — |
case-03 | fail→pass | 17,840 | 11,075 | -38% | 1 | 1 | 0% | 3,214 | 4,837 | +50% | 0 | 0 | — |
case-04 | pass→pass | 10,611 | 9,577 | -10% | 1 | 1 | 0% | 1,989 | 4,669 | +135% | 0 | 0 | — |
case-05 | pass→pass | 9,762 | 9,833 | +1% | 1 | 1 | 0% | 2,105 | 4,677 | +122% | 0 | 0 | — |
case-06 | pass→pass | 16,283 | 17,747 | +9% | 1 | 1 | 0% | 2,581 | 5,605 | +117% | 0 | 0 | — |
case-07 | pass→pass | 14,148 | 5,252 | -63% | 1 | 1 | 0% | 2,045 | 3,946 | +93% | 0 | 0 | — |
case-08 | fail→pass | 28,246 | 10,721 | -62% | 1 | 1 | 0% | 4,423 | 4,695 | +6% | 0 | 0 | — |
case-09 | pass→pass | 13,048 | 7,196 | -45% | 1 | 1 | 0% | 2,420 | 4,182 | +73% | 0 | 0 | — |
case-15 | pass→pass | 11,875 | 5,210 | -56% | 1 | 1 | 0% | 2,227 | 3,878 | +74% | 0 | 0 | — |
case-10 | pass→pass | 11,679 | 9,980 | -15% | 1 | 1 | 0% | 2,149 | 4,748 | +121% | 0 | 0 | — |
case-11 | fail→pass | 14,593 | 8,023 | -45% | 1 | 1 | 0% | 2,600 | 4,268 | +64% | 0 | 0 | — |
case-12 | pass→pass | 9,893 | 3,119 | -68% | 1 | 1 | 0% | 1,765 | 3,360 | +90% | 0 | 0 | — |
case-13 | fail→pass | 11,875 | 5,940 | -50% | 1 | 1 | 0% | 2,161 | 3,994 | +85% | 0 | 0 | — |
case-14 | pass→pass | 18,287 | 12,228 | -33% | 1 | 1 | 0% | 3,565 | 5,330 | +50% | 0 | 0 | — |
case-16 | fail→pass | 10,057 | 7,319 | -27% | 1 | 1 | 0% | 1,883 | 4,224 | +124% | 0 | 0 | — |
case-17 | pass→pass | 10,502 | 5,338 | -49% | 1 | 1 | 0% | 2,013 | 3,873 | +92% | 0 | 0 | — |
case-18 | pass→pass | 5,686 | 3,002 | -47% | 1 | 1 | 0% | 1,043 | 3,355 | +222% | 0 | 0 | — |
case-19 | pass→pass | 21,574 | 6,150 | -71% | 1 | 1 | 0% | 3,087 | 3,997 | +29% | 0 | 0 | — |
case-20 | pass→pass | 10,180 | 5,250 | -48% | 1 | 1 | 0% | 1,901 | 3,856 | +103% | 0 | 0 | — |
case-21 | pass→pass | 8,905 | 4,824 | -46% | 1 | 1 | 0% | 1,737 | 3,679 | +112% | 0 | 0 | — |
case-22 | pass→pass | 8,351 | 7,623 | -9% | 1 | 1 | 0% | 1,485 | 4,118 | +177% | 0 | 0 | — |
case-23 | pass→pass | 4,885 | 4,241 | -13% | 1 | 1 | 0% | 899 | 3,317 | +269% | 0 | 0 | — |
case-24 | fail→pass | 10,022 | 13,429 | +34% | 1 | 1 | 0% | 1,777 | 4,792 | +170% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +33 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.