Skip to main content

Langur makes it simple to build consistent, observable, and portable LLM agents

Project description

Langur

Langur makes it simple to build consistent, observable, and portable LLM agents.

Discord | Blog

⚠️ Langur is early in development. Expect frequent breaking changes. Depending on how it's configured Langur may be able to access arbitrary files or run code - use at your own risk.

Setup

Install:

pip install langur

Langur uses Anthropic by default, and it's recommended you use larger models for the best results. If you want though, you can easily swap the LLM backend. Otherwise, setup your anthropic key ANTHROPIC_API_KEY before proceeding!

Getting Started

Here's a simple task solved using a Langur agent:

from langur import Langur
from langur.connectors import Workspace

agent = Langur("Grade quizzes")
agent.use(
    Workspace(path="./workspace")
)
agent.run()

This is an example that automatically identifies quizzes in the provided filesystem, grades them, and puts them into an empty grades.csv file.

The agent will:

  1. Plan out how to "Grade quizzes" based on what we told it to .use() (just Workspace in this case).
  2. Execute those actions

Signals

But maybe instead of executing the plan right way, we might want to observe it first. Instead of directly running agent.run(), we can pass a signal when running the agent:

from langur import Signal

agent.run(until=Signal.PLAN_DONE)

Visualization

To observe the agent's plan, we can look at the corresponding behavioral graph. This can be done like so:

agent.save_graph_html("agent.html")
# ^ and then open the html with your browser.
# Or, in a Jupyter notebook:
agent.show()

For this simple example, the behavior graph might look something like:

Each node represents an action the agent plans to execute.

Caching

Let's say we like this behavior, and want to be able to re-use it - both to have consistent behavior and so the LLM doesn't have to plan out the same task again.

To do this, we can save the entire agent behavior graph in whatever state it's in, and re-run it later!

To save the agent behavior:

agent.save("agent_with_great_plan.json")

then later, wherever and whenever you want:

agent = Langur.load("agent_with_great_plan.json")

You could then continue the agent's execution from this point, where it has the plan you like in its behavioral state, by just running agent.run() once loaded.

Since this agent representation is plain JSON, you can even send agents over the wire, store them in a database, or do whatever you like really! They are completely portable and can be run on any system with Python and Langur installed.

Available Connectors

Actions / connectors are available in any of these ways:

  • langur.connectors contains a few built-in connectors which will be optimized for Langur's architecture. This will grow significantly over time
  • Using langchain tools / toolkits: You can pass these directly to agent.use(...) and Langur will adapt them.
  • Build your own! See Building Connectors.

Going Further

Currently there is no documentation, however you can check out more examples by checking out the challenges - which are different tasks meant to evaluate Langur's abilities in various capacities. You can also ask any questions you have in the Langur Discord.

If this plan/execute behavior just doesn't work for your agent use case, another key tenet of Langur's design is to have modular behavior - so you can customize it to fit your needs. Learn more in Customizing Behavior.

Building Connectors

Here's a simple example demonstrating how to build a custom connector:

from langur import Langur, Connector, action
from langur.connectors import Terminal

class Calculator(Connector):
    @action
    def add(self, x: int, y: int):
        '''Add two numbers'''
        return x + y

    @action
    def multiply(self, x: int, y: int):
        '''Multiply two numbers'''
        return x * y

agent = Langur("What is (1494242 + 12482489284) * 24?")
agent.use(
    Calculator(),
    Terminal().disable("input")
)
agent.run()

>>> [OUTPUT] 299615604624

If you're familiar with existing LLM paradigms, the connector actions are generally similar to the concept of tools. However, connectors provide a convenient way to (1) package related actions together and (2) provides extra capabilities that aren't possible with the usual tool interface.

For example, if we want an action that actually makes another LLM call, we would want to have context about what's actually going on. Here's an example, the built-in LLM connector which allows the agent to do natural language processing as an action:

from langur.actions import ActionContext
from langur.connector import Connector, action
import langur.baml_client as baml

class LLM(Connector):
    @action
    async def think(self, ctx: ActionContext) -> str:
        '''Do purely cognitive processing'''
        return await baml.b.Think(
            context=ctx.ctx,
            description=ctx.purpose,
            baml_options={"client_registry": ctx.cg.get_client_registry()}
        )

BAML is being used here to handle the prompting backend

The parameter ctx: ActionContext is a special parameter that includes information relevant to the action being executed. The LLM is unaware of this parameter - it is automatically injected if found in the signature of an action function definition.

  • ctx.purpose contains a short natural language description of the specific purpose of a particular action use.
  • ctx.ctx is any prompting context normally used to construct inputs for an action, but you can hook into it and use it as well. Print it out to see a bit of what's going on under the hood!

Customizing Behavior

To customize agent behavior, pass AgentBehavior to the behavior kwarg when creating your agent instead of just passing in instructions:

from langur import Langur
from langur.connectors import Terminal
from langur.behavior import *

agent = Langur(
    behavior=AgentBehavior(
        Assume(),
        Plan(Task("Say Hi"), Task("Make a Meow")),
        Execute()
    )
)
agent.use(Terminal().disable("input"))
agent.run()
agent.save_graph_html("meow.html")

This example plans for two different tasks instead of one, and also includes an additional behavior Assume which isn't included by default. The Assume behavior make several assumptions about any provided tasks before planning actually starts, for example "verbal_output_required: The output should be a verbal/textual representation of a cat's meow sound".

In the future, there will be much more interesting and nontrivial variations of behavior available to use.

If you want to define completely new behaviors, that's also possible within the Langur framework. Currently it may be a bit tricky, so if you're interested in learning more about that, please reach out to me on the Langur Discord.

Configuring LLM

Langur is usually tested and developed with Anthropic models (Claude Sonnet 3.5). Therefore it is recommended you use the default Anthropic LLM configuration. However, you can customize the LLM used by the agent by passing an LLMConfig to your agent:

from langur.llm import LLMConfig

agent = Langur(
    "Say hi",
    llm_config=LLMConfig(
        provider="openai",
        options={"model": "gpt-4o", "temperature": 0.0}
    )
)

In this example, we configured Langur to use OpenAI's gpt-4o (which also tends to work fairly well). You can use open source LLMs by using Ollama / vLLM providers for example. Langur uses BAML for its prompting/LLM backend, so see https://docs.boundaryml.com/guide/baml-basics/switching-llms for more info on how to set up this configuration.

Running Challenges

If you clone the repo, you can run the included challenges like so:

python ./challenges/challenge_runner.py <challenge_dir>
# For example:
python ./challenges/challenge_runner.py grader

These challenges are designed to test the abilities of the Langur system in various ways - many more will be added over time. If you have ideas for a challenge or use case you want to try, let me know!

How it Works

Langur's behavior is driven entirely by various "metacognitive workers" operating on a shared "cognition graph". These workers might manipulate the graph itself, or they might be interacting with the real-world and relaying that information to the graph. Workers that interact with the real-world are also called Connectors, and Langur is designed to make these Connectors easy to implement to define new modes of interaction with the world.

Why this approach?

  1. Modular behavior - Agent behavior is driven by several workers instead of a centralized system. This makes it easier to add new behavior without accidentally affecting all other facets of behavior, and makes it possible for advanced users to design and include their own custom behavior.
  2. Observability - Being able to see a graph representing the agent's behavior is incredibly useful for visually understanding the agent's intent, and promotes sanity as a developer designing systems that operate on the graph.
  3. Parallelizability - Workers can potentially run in parallel (or concurrently really) - and since many of these workers are using LLMs to operate on the graph, we can speed up these I/O bound prompts.
  4. Serializability - Since all behavior state is stored in the graph, we can save and load the graph in order to save and load agent behavior.

Resources

Docs: Coming soon

Discord: https://discord.gg/wSBSP56V7U - Join the Discord to ask me any questions, report issues, or make suggestions!

Known Issues

  • Does not properly support multiple connectors of the same type (will be implemented)
  • Behaviors cannot yet be arbitrarily combined, different combinations may not work as expected

Roadmap

  • Docs
  • CLI Tool
  • Improved agent behavior debugger / graph visualizer
  • Adapter for langchain tools
  • More native connectors
  • Continuously improve agent behavior

And much more! Langur aims to be the go-to library for easily building reliable LLM agents that actually have practical real-world use.

If you have suggestions, let me know in the Discord!

Contact Me

Please feel free to reach out if you have questions about Langur, are wondering about whether it can fit your use case, or anything else!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langur-0.1.1.tar.gz (226.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langur-0.1.1-py3-none-any.whl (56.4 kB view details)

Uploaded Python 3

File details

Details for the file langur-0.1.1.tar.gz.

File metadata

  • Download URL: langur-0.1.1.tar.gz
  • Upload date:
  • Size: 226.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.11.10

File hashes

Hashes for langur-0.1.1.tar.gz
Algorithm Hash digest
SHA256 5d229351aaa248d11606a98bc5b51a9c65c5f829fa037e0d1f108ab4d306534e
MD5 6692fd4913b8efa9a847caa93e3cc709
BLAKE2b-256 f49735988fea6b419c8005eaf0be5ac8d8bb1ce1e806b811d7208bae8da705df

See more details on using hashes here.

File details

Details for the file langur-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: langur-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 56.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.11.10

File hashes

Hashes for langur-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 723891957a80db5eabc4be08ab62a51a161811a228eea2559bf40ec0ec62ffd0
MD5 6db3852422c8af3aeddbabb16fcca67e
BLAKE2b-256 93c81ec3de1127a6d3e6bbc128e2bb64ef901f05272a604a89887cd50276d1bc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page