LangChain Academy Notes

These are my notes from working through LangChain Academy. The courses follow the lifecycle of an agent:

Table of contents

Introduction to LangChain - Python

Module 1: Create Agent

  • Can create chat model with init_chat_model or agent with create_agent
    • Able to customize parameters directly within init or invoke as needed (stream mode)
    • HumanMessage and AIMessage can be passed in directly to agent for context
  • Can define tools for agents to use + provide descriptions for them to understand + invoke similarly how we invoke functions
    • Pass tools in via create_agent and get back ToolMessage if invoked
    • Good one to mention is Tavily - its search given a search query allows you to search the web for most up to date info
  • Agents don’t have memory so can use InMemorySaver to record context automatically
  • Can pass in image/audio via base64 to agent for multimodal messages

Module 2: Advanced Agent

  • MCP framework agnostic standard for agents to connect to tools and data sources ~= USB
    • AI apps used to have differing ways of getting resources tools they wanted which made them act irrationally in different ways
    • Now agent can act as MCP Client and connect to MCP server (local or online) which exposes tools/resources/prompts available for Client to utilize
  • Can define context for agents to refer to as needed for more accurate responses
    • Need to pass directly to agent to define shape + define tools that get/set the context
  • Can define subagents with create_agent and then call/pass them as tools normally to main agent to divide and conquer work

Module 3: Production-Ready Agent

  • Middleware allows you to inspect current data within your agent state diagram and perform actions on it in some way (don’t show PII, get human involved if X shown, etc)

    • Can run middleware once, before, or after each run AND multiple times before or after each model call
    • For example too many messages passed to the agent can overload its context window
      • Can use SummarizationMiddleware to summarize messages once context window hits certain limit
        • Can define a custom middleware to trim messages from state if ToolMessage
  • Include human in the loop for agentic workflows if sensitive actions, missing context, or debugging agents are required

    • Can use HumanInTheLoopMiddleware to interrupt on certain tool calls with certain messages which will generate Interrupt events for humans to act on

      • Can approve decision by invoking agent with decision of type approve
      • Can reject decision by invoking agent with decision of type reject
      • Can edit decision and run tool call with changes with decision of type edit and specifying edited_action with tool name and tool args so don’t have to rely on rejecting model and model needing to retry
      from langgraph.types import Command
      
      agent.invoke(
          Command(resume={
              "decisions": [
                  {
                      "type": "edit",
                      "edited_action": {
                          "name": "send_email", # tool name
                          "args": { #tool args
                              "to": "robert@acme.com",
                              "subject": "Hi",
                              "body": "...",
                          },
                      },
                  }
              ]
          }),
          config=config,
      )
  • Normally middleware is node style as explained above but is inefficient if you want to change the underlying model itself so would want to use wrap style middleware instead

    • Would pass in the ModelRequest as a param to middleware function alongside a handler and decide either to return a custom prompt or invoke handler a certain way
  • Middleware Comparisons

    • Wrap Style Middleware takes in a ModelRequest and will adjust an agent’s overall tools, prompt, model while running
    @wrap_model_call
    def retry_then_fallback(request, handler):
        for attempt in range(2):
            try:
                return handler(request)
            except Exception:
                pass
        # Both attempts failed, so try a different model for this one call
        return handler(request.override(model=fallback_model))
    @wrap_model_call
    def pick_model(request, handler):
        if len(request.state["messages"]) > 10:
            return handler(request.override(model=big_model))
        return handler(request.override(model=small_model))
    @dynamic_prompt
    def role_based_prompt(request) -> str:
        ctx = request.runtime.context
        base = f"You are a helpful assistant. The user's name is {ctx.user_name}."
        if ctx.role == "admin":
            return base + " You may discuss internal system details."
        return base + " Do not reveal internal system details."
    • Node Style Middleware takes in an AgentState and Runtime and will adjust an agent’s state while running
    @before_model(can_jump_to=["end"])
    def stop_if_too_long(state: AgentState, runtime: Runtime) -> dict | None:
        if len(state["messages"]) >= 50:
            return {
                "messages": [{"role": "assistant", "content": "Conversation limit reached."}],
                "jump_to": "end",
            }
        return None   # no change, continue as normal
    @after_model
    def log_usage(state: AgentState, runtime: Runtime) -> None:
        last = state["messages"][-1]
        print("tool calls requested:", [tc["name"] for tc in last.tool_calls])
        return None
    • Tool Calls takes in a ToolRuntime and allows an agent to adjust its own state or retrieve runtime context
    @tool
    def get_weather(city: str, units: str = "fahrenheit") -> str:
        """Get the current weather for a city.
    
        Args:
            city: City name, e.g. "New York".
            units: "fahrenheit" or "celsius".
        """
        # call your real API here
        return f"72 degrees {units} and sunny in {city}"

← All posts