Where DataDesk stands
v0.2 can X-ray its own messages and parse messy asks into typed forms. But it still has one tool and a one-line system prompt, and it answers in a single silent blob. Tonight it becomes v1: multi-tool, properly governed, streaming. This is the last session on the high-level API before b4 adds the production layer - so we make sure you know exactly what the high-level API is doing for you.
Inside the harness 9 min live
create_agent is not magic and it is not a black box. It is your b1 while-loop rebuilt as two nodes on the LangGraph runtime - and that one implementation choice is why checkpoints, streaming and human-in-the-loop show up later without rewrites.
LiveWhat create_agent actually runs3 min▶
Line up the b1 scratch loop against the diagram and the mapping is one-to-one:
- Your while-loop body → the model node: call the model with the message stack, inspect the reply for tool calls.
- Your "run the function, append the result" branch → the tool node: execute, wrap the result as a tool message, hand back.
- Your break statement → the exit edge: no tool requested means the answer is final.
The difference is where it runs. Your loop ran on bare Python; this loop runs on the LangGraph runtime, which is why persistence, streaming, interrupts and time travel are configuration rather than construction. You paid one abstraction layer and bought four production features - that trade is the honest pitch, and now you can audit it.
LiveThe system prompt is DataDesk's constitution3 min▶
The system_prompt parameter is the highest-leverage string in your codebase: it rides at the top of the stack on every single turn. For an agent, it needs three sections a chat prompt does not:
- Role: who it is and for whom. "You are DataDesk, a data team assistant" scopes the persona and the tone.
- Tool guidance: when to reach for which tool, and what to do first. "Use list_datasets before guessing a path" prevents an entire class of silent failures.
- Refusal rules: what it must NOT do. "If the data cannot answer, say 'not in the data' - never estimate" is the line between an assistant and a liability in front of a stakeholder.
The agent that invented a column. A team's assistant was asked for "churn by region" on a dataset with no region column. Without a refusal rule it averaged something plausible and presented it confidently. One sentence in the system prompt - cite your tool, or say "not in the data" - turned the same failure into a trust-building answer.
Self-studyWhere the defaults end and LangGraph begins2 min read▶
create_agent's defaults carry you far: single agent, flat tool list, linear conversation, middleware for cross-cutting concerns (b4). You eject down to raw LangGraph (b5) when the SHAPE of the flow itself must change:
| Need | Stay on create_agent | Eject to LangGraph |
|---|---|---|
| More tools, better prompt | Yes - just configuration | |
| PII scrubbing, retries, summarization | Yes - middleware, b4 | |
| Branching flows, custom routing | Yes - you own the graph, b5 | |
| Multi-agent teams, map-reduce | Yes - b9 |
Because create_agent already runs ON LangGraph, ejecting is a rewrite of the wiring, not the parts: your tools, prompt and middleware all carry over. That is the payoff of the b1 stack diagram.
Streaming and the user experience 6 min live
A 20-second silent wait reads as broken; the same 20 seconds with visible progress reads as thinking. Streaming is not decoration - for agents that use tools, it is the difference between users trusting the system and users killing the tab.
Liveinvoke vs stream - and the two modes that matter4 min▶
invoke blocks until everything - tool calls included - is done, then returns the full result. stream yields as work happens, and the stream_mode argument picks what "work" means:
| Mode | You receive | Build with it |
|---|---|---|
stream_mode="messages" | Message chunks - tokens as the model generates them | The typing effect: chat UIs, CLIs, anything human-facing |
stream_mode="updates" | One update per agent step - model node fired, tool node fired, with payloads | Progress panes: "calling csv_stats...", logs, debugging tool choice |
- Rule of thumb: humans watching the answer want
messages; humans (or logs) watching the AGENT wantupdates. Production UIs often consume both. - It is the rails again: streaming comes from the LangGraph runtime underneath - the same events will keep working unchanged when you eject to raw graphs in b5.
The support-bot team that shipped the progress pane first. A team instrumenting a tool-heavy agent found users abandoned sessions during tool calls, not during generation. They shipped the updates-mode progress pane a sprint before the polished typing effect - abandonment dropped, and the typing effect became a nice-to-have instead of a rescue mission.
Self-studyMulti-tool agents - how the model chooses3 min read▶
With three tools bound, nothing routes but the model's own reading of your tool descriptions against the user's question. There is no dispatcher to configure - the descriptions ARE the router.
- Name + docstring do the work:
column_meanwith "Mean of a numeric column in a CSV file" gets chosen for "average order value". A vague description sends the model to the wrong tool, and no amount of model quality fully compensates. - Overlap is the enemy: if two tools plausibly answer the same question, the model will be inconsistent between runs. Sharpen the boundary in the docstrings ("use X for single columns, Y for whole-file stats") before reaching for anything fancier.
- Chaining is free: the loop just keeps going - tool result comes back, the model decides it needs a second tool, the loop runs again. You never wrote "first call list_datasets, then column_mean"; the model plans that. Demo 1 shows it live.
- When descriptions stop being enough (dozens of tools, hard business rules about routing), that is a graph-shape problem: conditional edges in b5, supervisor patterns in b9.
DataDesk v1 ★ 12 min · everyone builds
Three tools, a constitution-grade system prompt, and a question that forces the model to chain two tools on its own judgment. This file is the trunk every later session grows from.
Add two tools next to csv_stats: column_mean(path, column) and a list_datasets() stub returning a dict of the CSVs DataDesk may touch. The stub matters - it is the catalog seam where a real metadata store plugs in later.
Replace the one-liner system prompt with the constitution below: role, tool guidance, refusal rules. Read it out loud - if a sentence would embarrass you in design review, fix it now.
Ask the two-tool question: "What is the average order value in the orders data, and how many rows is that over?" - it cannot be answered without list_datasets (or csv_stats) AND column_mean.
Watch the loop decide: which tool fired first? Did it cite the tools in the answer, as instructed? Nothing you wrote sequenced those calls - the model planned the chain.
Stress the refusal rule: ask for "churn by region". There is no such column - v1 must answer "not in the data", not improvise. If it improvises, tighten the prompt and re-run.
column_mean's description says "single numeric column" - vague descriptions make the model try to do everything with csv_stats.
Make it feel alive ★ 10 min · build your own
Same v1 file, three changed lines. First tokens as they generate, then the agent's own heartbeat - tool calls visible as they happen - and finally the engine-swap test to prove the streaming code is engine-agnostic.
Swap invoke for stream with stream_mode="messages" and print tokens as they arrive. Run the v1 question again - the typing effect appears, tool pauses and all.
Now run the same call with stream_mode="updates". Each yielded update is one agent step: watch the model node hand off to the tool node and back - your Part 1 diagram, printed live.
Narrate what a user would see in each mode: messages is the answer being typed; updates is "DataDesk is checking list_datasets...". Decide which your future UI needs. (Often: both.)
Engine-swap test: flip the model line to "ollama:llama3.1" and re-run both modes. Streaming code untouched. Local tokens arrive at your GPU's pace - feel the latency difference you would be shipping.
The demo that failed silently and the one that did not. Two teams demoed agents to the same executive. Team A's answer took 25 silent seconds; the exec reached for their phone and the moment died. Team B streamed the tool narration - "checking the orders dataset... computing the mean..." - and the exec leaned in and asked what else it could check. Identical model, identical latency. The stream made the wait legible.
Try it yourself - this week ◐ 30-45 min total
- Finish DataDesk v1 with all three tools if you did not complete it live, and make the refusal test ("churn by region") pass reliably on both engines.
- Add a fourth tool of your own -
column_max,null_count, whatever your real work needs - and verify the model routes to it from the docstring alone. - Run the system-prompt attack prompt from Part 1 against your constitution and patch the best hole it finds.
- Time a hard question under
invokevsstreamand note when you FELT the answer had started. Bring the number - it becomes the UX argument in b7's approval-gate design. - Optional reading: the create_agent + streaming pages at docs.langchain.com - the diagrams there are the ones you just built by hand.
Official sources covered
This track teaches from the official docs and the free LangChain Academy curricula (login required for lesson content; certificates stay with the Academy - all free). This page covers:
Three questions before you go 🎯 ◐ 90 seconds
1 · What is create_agent actually running under the hood?
Same two beats as your scratch loop, running on rails. The rails are what you actually bought - and they are why b5's eject-to-LangGraph is a rewiring, not a rewrite.
2 · With three tools bound, what decides which tool handles a question?
No dispatcher exists to configure. Sharp, non-overlapping docstrings are the routing layer - which is why "gets stats" is a production incident waiting to happen.
3 · Your UI needs a "DataDesk is calling csv_stats..." progress pane. Which streaming mode feeds it?
messages streams the answer's tokens; updates streams the agent's steps. Humans watching the answer want messages; humans watching the agent want updates.