/* global React */

function Chapter09() {
  return (
    <section className="chapter" id="ch-09" data-screen-label="09 Streaming & tracing">
      <div className="chapter-header">
        <div className="eyebrow">Chapter 09 · Observability</div>
        <h1 className="chapter-title">Streaming, callbacks, and LangSmith.</h1>
        <p className="chapter-lede">
          You can't tune what you can't see. This chapter covers the three things you need to debug a real agent:
          token-level streaming, runtime callbacks, and a hosted trace viewer.
        </p>
      </div>

      <SectionTitle num="9.1">Streaming events from an agent</SectionTitle>
      <p>
        Agents produce many kinds of events: model tokens, tool start/end, intermediate AIMessages, final answer.
        <code>astream_events</code> gives you a unified async stream of all of them:
      </p>
      <CodeBlock file="stream_events.py">{`async for event in executor.astream_events({"input": "..."}, version="v2"):
    kind = event["event"]
    if kind == "on_chat_model_stream":
        chunk = event["data"]["chunk"]
        print(chunk.content, end="", flush=True)
    elif kind == "on_tool_start":
        print(f"\\n[calling {event['name']} with {event['data']['input']}]")
    elif kind == "on_tool_end":
        print(f"[result: {event['data']['output']}]")`}</CodeBlock>

      <p>The event types you'll touch most:</p>
      <ul>
        <li><code>on_chat_model_start / _stream / _end</code> — every LLM call.</li>
        <li><code>on_tool_start / _end / _error</code> — every tool invocation.</li>
        <li><code>on_chain_start / _end</code> — every Runnable boundary (useful for nested chains).</li>
        <li><code>on_retriever_start / _end</code> — RAG retrievals.</li>
      </ul>

      <Callout kind="tip" title="Always include version='v2'">
        The <code>v2</code> event schema is the modern one with consistent field names. The implicit default is the
        legacy <code>v1</code> stream — different fields, different ordering, will eventually be removed.
      </Callout>

      <SectionTitle num="9.2">Callbacks — hooks for everything</SectionTitle>
      <p>
        Callbacks are the lower-level mechanism behind streaming. You implement a handler class and pass it via
        <code>config={"{'callbacks': [handler]}"}</code>. Every Runnable in the chain fires events through it:
      </p>
      <CodeBlock file="custom_callback.py">{`from langchain_core.callbacks import BaseCallbackHandler

class CostTracker(BaseCallbackHandler):
    def __init__(self):
        self.tokens_in = 0
        self.tokens_out = 0

    def on_llm_end(self, response, **kw):
        usage = response.llm_output.get("token_usage", {})
        self.tokens_in  += usage.get("prompt_tokens", 0)
        self.tokens_out += usage.get("completion_tokens", 0)

    def on_tool_start(self, serialized, input_str, **kw):
        print(f"→ {serialized['name']}({input_str})")

tracker = CostTracker()
executor.invoke({"input": "..."}, config={"callbacks": [tracker]})
print(f"Cost: in={tracker.tokens_in} out={tracker.tokens_out}")`}</CodeBlock>

      <SectionTitle num="9.3">LangSmith — hosted tracing</SectionTitle>
      <p>
        LangSmith is the hosted dashboard from the LangChain team. Set two env vars and every run shows up as an
        interactive trace tree — every LLM call, every tool, every chain, with token counts and latency:
      </p>
      <CodeBlock file=".env" lang="bash">{`LANGSMITH_TRACING=true
LANGSMITH_API_KEY=lsv2_pt_...
LANGSMITH_PROJECT=my-agent-prod`}</CodeBlock>
      <p>
        That's literally all the wiring. Any LangChain Runnable picks the env vars up and ships traces. You get:
      </p>
      <ul>
        <li>Full prompt at every LLM call (not "the abstract prompt template" — the actual rendered messages).</li>
        <li>Tool inputs and outputs, including raw errors with stack traces.</li>
        <li>Per-step latency and token cost. Easy to spot a tool that runs 14 times with the same args.</li>
        <li>Side-by-side trace comparison across prompt versions.</li>
      </ul>

      <Callout kind="intuition" title="If you debug agents with print(), you'll lose your mind">
        Agents have non-trivial control flow. The single highest-leverage thing you can do as an LLM developer is wire
        up a trace viewer on day one. LangSmith is the easy path; OpenTelemetry exporters exist if you'd rather self-host.
      </Callout>

      <SectionTitle num="9.4">Verbose mode (free, low-fi)</SectionTitle>
      <CodeBlock file="verbose.py">{`import langchain
langchain.debug = True       # extreme: every event, every dict
# or
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)`}</CodeBlock>
      <p>
        Verbose dumps colored ASCII traces to stdout. Good enough for quick iteration; pales next to LangSmith for
        anything beyond a few turns.
      </p>
    </section>
  );
}

window.Chapter09 = Chapter09;
