Skip to content

[RFC] Remote/Distributed Agent via AgentBus #1179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions RFCs/001/[RFC] - AgentBus: Inter Agent Communication Model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# [RFC] - AgentBus: Inter Agent Communication Model

Author: Davor Runje, Tvrtko Sternak, Davorin Ruševljan

Status: Discussion

## Introduction

Current ag2 model has enabled fast experimentation and development of proof of concept implementations of various
agentic patterns. As such it provided solid ground to spawn many new agentic concepts and patterns. However, now when our focus broadens to put those concept and patterns in production environment we are facing new challenges with:
- support for agents with different prerequisites, like different versions of some package, or even python versions
- interoperability with non ag2 agents
- ability to split agent workflow into several processes, possibly running on different machines
- development of new agent flow patterns while definitely possible (groupchat, swarm) has somewhat high barrier, since
framework does not provide some common building blocks, so there is small commonality between differing flow patterns even when there could be.
- incorporation of non LLM agents


## Pain points in current model

Most of the challenges described in introduction seem to have following causes in common:
- agents are referred to as direct python object reference
- communication between agents is performed with direct method calls

Additionally new agentic flow patterns are developend basically from the scratch, because there are no
common building blocks from the framework.

## What we would like to achieve (Goals)

Inter agent communication model that:
- does not rely on direct python references and method calls
- is able to be split over the process barrier
- lends itself to elegant implementation of current ag2 flow patterns, but also
provides base for development of new ones
- can incorporate existing ag2 agents (possibly via proxies)
- can incorporate "foreign" agents from different frameworks (possibly via proxy)
- can incorporate non llm agents

## AgentBus - Basic Concept
In order to achieve goals mentioned we propose AgentBus concept, in which Agentic system is represented
as collection of Agents, all connected to the AgentBus.

AgentBus carries events, which themselves are structured objects that are like JSON objects.

Each agent can emit event to the AgentBus. Once emitted, event is presented to all Agents
connected to the AgentBus. Specially, agentic flow could be started by initial event provided by
starter of the flow.

Once presented with the event, Agent can select to be activated by the event and perform action.
Agent can be possibly activated by different events, and perform different actions, during which
Agent can emit new events.

## Agent Activation - Event Selectors

In order to provide elegant way for Agents to activate on certain events, we propose concept
of selectors, that take event and choose whether event should activate agent or not. Attached to
this selector would be code to be executed if agent is activated by the event. Agent can have
several selectors, so that it can be activated by different events, and perform different actions.

Two forms of Event Selectors are envisioned:
- pattern matching selectors
- universal selectors

### Pattern Matching Selectors

Pattern matching selectors (PMS) take the event, and check if event conforms to certain pattern.

Syntax for PMS would be proposed in following RFC, but it should provide means to check in arbitrary depth:
- if event has particular key
- if value under some key has particular value
- if some value is list, dictionary or atomic value
- if some element of list has particular value

Few examples of one possible style:
``` python
@select("{role:'critic', task: 'pedantic_review', _ }")
def pedantic_review(self, event: Event):
review = self._pedantic_review(event)
self.emit(review)

@select("{role:'critic', task: 'improvement_suggestions', _ }")
def suggest_imporovements(self, event: Event):
# do stuff needed..
...

@select("{text: content, _}")
def on_text(self, event: Event):
if self.belongs_to_history(event):
content = event['content']
self.add_to_history(content)
else:
self.reject(event)

```

The actual capabilities and syntax to be proposed will take inspiration from pattern matching capabilities of
- Common web server routing selectors
- Python
- Prolog
- Elixir
- Rust

### Universal Selectors

Take event and use callable to decide if event is selected or rejected.

### Selectors TBD

- are selectors evaluated:
- sequentially, and only after some selector is rejected next one is evaluated
- concurrently

## Common Agentic Flow Patterns
here are some examples how common Agentic flow might be implemented using AgentBus

### Bucket Passing

### Chat Master

## Tools
tbd:
- Agent that executes functions for others?
- Each agent can execute function calls
- Combination?

## State/Stateless
are agents stateless or statefull?


## Interfacing to Outer World
### To Alien Agents
agent that acts as proxy for external agent
### To executors
agent that listens to the ui events and passes them to external world. Also - it accepts responses
from outer world and emits them as events to the AgentBus
### Execution logging
agent that listens everything

## EventBus Possible implementations

- in process emulation
- message queue based system


{'role': 'critic', ...}
34 changes: 34 additions & 0 deletions RFCs/001/impl/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from selector_parser import SelectorParser
from selector_transformer import SelectorTransformer

parser = SelectorParser()

test_json = """
{
name: $nameVar,
age: 30,
isStudent: false,
address: {
street: "123 Main St",
city: $cityVar
},
grades: [$grade1, $grade2, 78],
nullable: None,
specialIdentifier
}
"""

test_json2 = """{"kljuc": 123, k2: "peor", **resto}"""
test_json3 = r""" ["k1", 123, =varko, "k2", 43, *resto] """

try:
tree = parser.parse(test_json3)
print(tree.pretty())
#print(tree)
transformed = parser.transform(tree, SelectorTransformer)
print(transformed)


except Exception as e:
print(f"Error parsing selector: {e}")

133 changes: 133 additions & 0 deletions RFCs/001/impl/matcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@

class Matcher:

def add_var(self, name, value, ctx):
if name in ctx:
return ctx[name] == value
ctx[name] = value
return True

class NumberMatcher(Matcher):
def __init__(self, number):
self.number = number

def match(self, value, ctx):
return self.number == value

def __repr__(self):
return f"NumberMatcher({self.number})"

class StringMatcher(Matcher):
def __init__(self, string):
self.string = string

def match(self, value, ctx):
return self.string == value

def __repr__(self):
return f"StringMatcher({self.string})"


class NullMatcher(Matcher):
def match(self, value, ctx):
return value is None

def __repr__(self):
return f"NullMatcher()"

class TrueMatcher(Matcher):
def match(self, value, ctx):
return value is True

def __repr__(self):
return f"TrueMatcher()"

class FalseMatcher(Matcher):
def match(self, value, ctx):
return value is False

def __repr__(self):
return f"FalseMatcher()"

class VarMatcher(Matcher):
def __init__(self, name):
self.name = name

def match(self, value, ctx):
return self.add_var(self.name, value, ctx)

def __repr__(self):
return f"VarMatcher({self.name})"

class ListRestMatcher(Matcher):
def __init__(self, name):
self.name = name

def match(self, value, ctx):
return self.add_var(self.name, value, ctx)

def __repr__(self):
return f"ListRestMatcher({self.name})"

class ObjectRestMatcher(Matcher):
def __init__(self, name):
self.name = name

def match(self, value, ctx):

return self.add_var(self.name, value, ctx)

def __repr__(self):
return f"ObjectRestMatcher({self.name})"

class ListMatcher(Matcher):
def __init__(self, matchers, rest=None):
self.matchers = matchers
self.rest = rest

def match(self, value, ctx):
if not isinstance(value, list):
return False
if len(self.matchers) > len(value):
return False
lix = 0
for m in self.matchers:
list_item = value[lix]
if not m.match(list_item, ctx):
return False
lix += 1
if self.rest is None:
return len(value) == len(self.matchers)

return self.rest.match(value[lix:], ctx)

def __repr__(self):
return f"ListMatcher({self.matchers}, {self.rest})"


class ObjectMatcher(Matcher):
def __init__(self, matchers, rest=None):
self.matchers = matchers
self.rest = rest

def match(self, value, ctx):
if not isinstance(value, dict):
return False
checked = []
for k, m in self.matchers.items():
key = k.string
if key not in value:
return False
if not m.match(value[key], ctx):
return False
checked.append(key)
if self.rest is None:
return len(value.keys()) == len(self.matchers)
rest = dict(value)
for k in checked:
del rest[k]

return self.rest.match(rest, ctx)

def __repr__(self):
return f"ObjectMatcher(items: {self.matchers}, rest:{self.rest})"
Loading