DEV Community

Cover image for How to build a crashproof customer service agent in <80 lines with Swarm πŸ’ͺ🐝
Qian Li for DBOS, Inc.

Posted on

How to build a crashproof customer service agent in <80 lines with Swarm πŸ’ͺ🐝

πŸš€ Want to build a crashproof AI agent in <80 lines of Python?

This tutorial shows you how to build a reliable AI-powered customer service agent with OpenAI's Swarm framework, and then enhance it to be crashproof with DBOS durable execution πŸ’ͺ🐝

This agent takes in a user's name, processes a refund for the user, and then applies a discount. Even if the agent is interrupted during refund processing, upon restart it automatically recovers, finishes processing the refund, then proceeds to the next step in its workflow.

Try running this agent and pressing Ctrl+C at any time. You can see that when it restarts, it automatically resumes from the last completed step!

All source code is available on GitHub.

Writing an AI-Powered Refund Agent

Let's start off with creating an AI-powered refund agent using OpenAI's lightweight multi-agent orchestration framework, Swarm. This agent contains two functions: process_refund to help a user to return an item, and apply_discount to apply a discount to the user's future purchases. These functions may be invoked based on OpenAI LLM's output. In addition, the process_refund function invokes several sub-steps (refund_step) which simulates a complex refund workflow.

from swarm import Agent
from dbos import DBOS

def process_refund(context_variables, item_id, reason="NOT SPECIFIED"):
    """Refund an item. Refund an item. Make sure you have the item_id of the form item_... Ask for user confirmation before processing the refund."""
    user_name = context_variables.get("user_name", "user")
    print(f"[mock] Refunding for {user_name}, item {item_id}, because {reason}...")
    for i in range(1, 6):
        refund_step(i)
        DBOS.sleep(1)
    print("[mock] Refund successfully processed!")
    return "Success!"

@DBOS.step()
def refund_step(step_id):
    print(f"[mock] Processing refund step {step_id}... Press Control + C to quit")

@DBOS.step()
def apply_discount():
    """Apply a discount to the user's cart."""
    print("[mock] Applying discount...")
    return "Applied discount of 11%"

refunds_agent = Agent(
    name="Refunds Agent",
    instructions="Help the user with a refund. If the reason is that it was too expensive, offer the user a refund code. If they insist, then process the refund.",
    functions=[process_refund, apply_discount],
)
Enter fullscreen mode Exit fullscreen mode

We decorate the refund_step and apply_discount functions as DBOS steps in the agent's workflow. This way, if the agent's workflow is interrupted while processing a refund, when it restarts, it will resume from the last completed step. DBOS guarantees that once the agent's workflow starts, you will always get a refund, and never be refunded twice or get the discount twice!

Reliable Agentic Workflow Orchestration

Next, let's enhance Swarm with a few lines of DBOS code to make it resilient to any failure.

This code declares the main loop of Swarm (run) to be a durable DBOS workflow and each chat completion to be a DBOS step in that workflow. Therefore, if a workflow is interrupted, it will skip already finished chat completion steps and use the recorded outputs of those steps.

from dbos import DBOS, DBOSConfiguredInstance
from swarm import Swarm
from swarm.repl.repl import pretty_print_messages

DBOS()

@DBOS.dbos_class()
class DurableSwarm(Swarm, DBOSConfiguredInstance):
    def __init__(self, client=None):
        Swarm.__init__(self, client)
        DBOSConfiguredInstance.__init__(self, "openai_client")

    @DBOS.step()
    def get_chat_completion(self, *args, **kwargs):
        return super().get_chat_completion(*args, **kwargs)

    @DBOS.workflow()
    def run(self, *args, **kwargs):
        response = super().run(*args, **kwargs)
        pretty_print_messages(response.messages)
        return response

DBOS.launch()
Enter fullscreen mode Exit fullscreen mode

Finally, let's create a DurableSwarm instance and use the refund agent to process refunds! This script creates an interactive CLI for your agent.

from agents import refunds_agent

def main():
    client = DurableSwarm()
    print("Connecting to Durable Refund Agent πŸ’ͺ🐝")

    user_name = input("\033[90mWhat's your name\033[0m: \n")
    if user_name.strip() == "":
        return  # Exit if user doesn't provide a name

    query = "I want to refund item 99 because it's too expensive and I don't like its color! I want to proceed with the refund and also get a discount for my next purchase!"
    context_variables = {"user_name": user_name}

    client.run(
        agent=refunds_agent,
        messages=[{"role": "user", "content": query}],
        context_variables=context_variables,
    )

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Try it Yourself!

To run this app, you need an OpenAI developer account. Obtain an API key here and set up a payment method for your account here.

Set your API key as an environment variable:

export OPENAI_API_KEY=<your_openai_key>
Enter fullscreen mode Exit fullscreen mode

Then, clone and enter the durable-swarm repository:

git clone https://github.com/dbos-inc/durable-swarm.git
cd examples/reliable_refund
Enter fullscreen mode Exit fullscreen mode

Next, create a virtual environment and install Swarm and DBOS. Swarm requires Python >=3.10.

python3 -m venv .venv
source .venv/bin/activate
pip install dbos git+https://github.com/openai/swarm.git
Enter fullscreen mode Exit fullscreen mode

DBOS requires a Postgres database. If you don't already have one, you can start one with Docker:

export PGPASSWORD=dbos
python3 start_postgres_docker.py
Enter fullscreen mode Exit fullscreen mode

Finally, run the app in the virtual environment with a single command python3 main.py. You can crash and restart this application as many times as you want. An example output:

> python3 main.py

Connecting to Durable Refund Agent πŸ’ͺ🐝
What's your name:
Qian
[mock] Refunding for Qian, item item_99, because Too expensive and I don't like its color...
[mock] Processing refund step 1... Press Control + C to quit
[mock] Processing refund step 2... Press Control + C to quit
[mock] Processing refund step 3... Press Control + C to quit
^C⏎

# Resume from where the last completed step (step 3), continuing with step 4.
> python3 main.py

Connecting to Durable Refund Agent πŸ’ͺ🐝
[mock] Refunding for Qian, item item_99, because Too expensive and I don't like its color...
[mock] Processing refund step 4... Press Control + C to quit
[mock] Processing refund step 5... Press Control + C to quit
[mock] Refund successfully processed!
[mock] Applying discount...
Refunds Agent:
process_refund("item_id"= "item_99", "reason"= "Too expensive and I don't like its color")
apply_discount()
Refunds Agent: I've processed the refund for item 99 and also applied a discount of 11% for your next purchase. If there's anything else you need, feel free to ask!
Enter fullscreen mode Exit fullscreen mode

Next Steps

Check out how DBOS can make your applications more scalable and resilient:

Top comments (12)

Collapse
 
andre_adpc profile image
Andre Du Plessis

Thank you for sharing this valuable and very well-set-out example of using Swarm and DBOS, Qian LI.

Now I need to look for a solution where one can use features like Vector or NoSQL table queries. Postgres is not a bad SQL Engine at all, it has multi-mode features, and I like the idea of having the ability DBOS allows you to use a local config of Postgres enabling you to keep sensitive data off-cloud during the Dev-cycle, however, the speed at which things are growing, compounded with the increasing needed to have our app's "perform" & deliver a good UX, we need to get as close to the web-clients as possible.

Distributed Cloud-based DB platforms like Clickhouse, SingleStore, etc. are increasingly not only becoming popular but almost a necessity.

When one compares various DB Table Engines (objectively, mind you) it's clear to spot why one would want to leverage these "feature-rich" DB Table processing Engines / DB platforms.

I discovered that Peter Kraft is involved in doing something about the limitation as in an interesting paper called "Epoxy: ACID Transactions Across Diverse Data Stores", as shim protocol to make it a possibility. Incedenlty (or maybe not), one of the co-authors is also a "Qian Li". Are you part of the team with Peter Kraft, perhaps?

Although Swarm is not production-ready, and DBOS can currently only use Postgres, the options it presents, as discovered via your post about this tech stack, is a great starting point for me personally, so this post of yours is a keeper.

Thank you again for the share.

Collapse
 
qianl15 profile image
Qian Li

@andre_adpc Thank you for your kind words and thoughtful questions!

DBOS uses Postgres to manage your app's execution state (e.g., workflow status, step outputs, queues, ...), but doesn’t restrict you to just Postgres. Because it’s a lightweight library, you can seamlessly integrate it with your favorite tools and data stores. For example, one of our early customers built a dynamic integration platform using DBOS to reliably export Shopify events (via Kafka) to external ERM and CMS systems. You can read about their journey moving from AWS Lambda to DBOS.

And yes, I'm the "Qian Li" from the Epoxy paper! We both worked on the DBOS research project and explored the synergy between applications and databases. After Peter and I graduated, we co-founded DBOS alongside a few professors and tech leaders like the Postgres creator Mike Stonebraker, and Databricks CTO & co-founder Matei Zaharia.

Collapse
 
andre_adpc profile image
Andre Du Plessis

Thank you for the elaboration and providing insight into where this all is coming from. I'm humbled by the company I'm finding myself in here. And from the looks of it, DBOS has the potential to become disruptive tech in its own right.

Collapse
 
andre_adpc profile image
Andre Du Plessis

OK, the penny dropped, you are indeed a contributor to the paper I discovered. May there be many more to come.

Collapse
 
qianl15 profile image
Qian Li

πŸ€— Stay tuned for more updates! We’d also love to hear your thoughts on DBOS if you have a chance to give it a try. Your feedback would be invaluable.

Thread Thread
 
andre_adpc profile image
Andre Du Plessis

I'm definitely going to give it a try, thank you. I'm setting time aside to work through your documentation to understand its features, functionality and capabilities better. After reading the article you shared I came to realise DBOS is more than what I initially deduced it to be from your article above.

At this stage, after reviewing all I still need to do to get my "pet project" turn into a reality, it feels like I'm still at the foothill level of the Himalayas, looking up at where it all needs to go. Being a one-man band, dancing on a shoestring budget, and with cognitive loads of note, time will tell...

I will come hollering for help and guidance on your Discord channel.

Collapse
 
programmerraja profile image
Boopathi

This is a great example of how to make an AI agent more resilient using DBOS. I especially appreciate the clear explanations and code examples. The "Try it Yourself!" section makes it easy to follow along and try it out.

Collapse
 
qianl15 profile image
Qian Li

Thank you! Would love to hear your thoughts and feedback after you give it a try.

Collapse
 
arindam_1729 profile image
Arindam Majumder

You made it looks so simple Qian!

Great Share!

Collapse
 
qianl15 profile image
Qian Li

Thank you!!

Collapse
 
fotiecodes profile image
fotiecodes

This is incredible explanation, straightforward and clear. most definitely give this a try!

Thanks for sharing!

Collapse
 
qianl15 profile image
Qian Li

Thank you!! I'm inspired a lot by your posts πŸ€—