๐Ÿค–๐ŸชŸ Agents Deconstructed

Deconstructing agents to let you harness their power in a more understandable and customizable way.

๐Ÿš€ Overview

LangChain Agents (and agents in other frameworks) are powerful but tough to customize. At the heart of those agents lies a while loop. This package deconstructs agents by providing helper functions and then forcing the user to implement the while loop. This makes agents both more understandable and more customizable.

๐Ÿ“„ Installation

pip install agents_deconstructed

๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Quickstart

We currently have two notebooks showing how use agents_deconstructed:

  • XML Agent: An agent that uses XML formatting (tested with OpenAI and Anthropic Models.)
  • OpenAI Functions: An agent that uses OpenAI Functions

๐Ÿ”ง Components

agents_deconstructed provides a few types of helper functions:

Tool Formatters

Logic for converting tools into a format such that the language model can understand how to work with them.

Output Parsers

Logic for converting the output of an LLM into an AgentAction or AgentFinish.

Intermediate Steps

Logic for converting intermediate steps (tuples of AgentAction and observation) into a format such that the language model can understand how to work with them.

Prompts

The main instructions to the language model, this tells it out to think and how to output it's decisions. This is pretty tightly coupled to a Output Parser and way of formatting Intermediate Steps

๐Ÿ’ป Algorithm

With the above components, we can now easily create agent-like logic. Pseudo-code for that logic:

# Create agent as a prompt + LLM + output parser
# This uses LangChain Expression Language for ease of composability
agent = prompt | llm | output_parser
# Initialize empty list to track steps
steps = []
# Use this to group all traces together
with trace_as_chain_group("agent-run") as group_manager:
    # Get the first prediction
    inputs = {
        "input": ...,
        # Format intermediate steps as expected
        "intermediate_steps": format_steps(steps),
        # Format tools as expected
        "tools": format_tools(tools),
    }
    action = agent.invoke(inputs, config={"callbacks": group_manager})
    # Enter a loop until an AgentFinish is reached
    while not isinstance(action, AgentFinish):
        # Select the tool to use
        tool = ...
        # Run the tool
        observation = tool.run(action.tool_input, callbacks=group_manager)
        # Construct intermediate steps
        steps += [(action, observation)]
        # Do the next iteration
        inputs = {
            "input": ...,
            # Format intermediate steps as expected
            "intermediate_steps": format_steps(steps),
            # Format tools as expected
            "tools": format_tools(tools),
        }
        action = agent.invoke(inputs, config={"callbacks": group_manager})

โš–๏ธ Comparison to LangChain Agents

Here is a (biased) comparison against LangChain agents:

Pros:

  • Uses LangChain Expression Language (LangChain Agents haven't switched to use this)
  • More customizable
  • More understandable

Cons:

  • Doesn't yet have same level of robustness (LangChain agents have safeguards against tools erroring, selecting tools that don't exist, etc)
  • More work to set up

The Same:

  • Logging to LangSmith (since we can use trace_as_chain_group)