Skip to content
Draft
1 change: 1 addition & 0 deletions a2a-samples
Submodule a2a-samples added at 834c1e
Empty file added bee_platform/__init__.py
Empty file.
48 changes: 48 additions & 0 deletions bee_platform/bee_examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Mellea-BeeAI

Mellea is a library for writing generative programs.
BeeAI Framework is an open-source framework for building production-grade multi-agent systems.
This example serves to merge both libraries with a simple module that will allow users to transform
their Mellea programs into BeeAI agentic interfaces with structured (form) inputs.

We provide the example of an email writer. Only text inputs are currently supported.


# Initialization

First, install BeeAI, instructions available here: https://framework.beeai.dev/introduction/quickstart
Then, add the BeeAI-sdk to your local environment.
```bash
uv add beeai-sdk
```

# Running the example

Then, in order to run the example email writer, run:
```bash
uv run --with mellea docs/examples/bee_agent.py
```

In a separate terminal, either run
```bash
beeai run mellea_agent
```

OR open the UI and select the **mellea-agent**.

```bash
beeai ui
```

# Creating your own examples

To create your own BeeAI agent with Mellea, write a traditional program with Mellea.

Ensure that the first parameter is an **m** object.

Wrap your Mellea program with ```@bee_app```.

Place your example in the ```docs/examples/``` folder.



88 changes: 88 additions & 0 deletions bee_platform/bee_platform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os
import asyncio
from typing import Annotated, Callable

from a2a.types import Message
from a2a.utils.message import get_message_text
from beeai_sdk.server import Server
from beeai_sdk.a2a.types import AgentMessage
from beeai_sdk.a2a.extensions import (
LLMServiceExtensionServer, LLMServiceExtensionSpec,
TrajectoryExtensionServer, TrajectoryExtensionSpec,
AgentDetail
)
from beeai_sdk.a2a.extensions.ui.form import (
FormExtensionServer, FormExtensionSpec, FormRender, TextField
)
from mellea import MelleaSession, start_session
from mellea.backends.openai import OpenAIBackend
from mellea.stdlib.requirement import req, Requirement, simple_validate
from mellea.stdlib.sampling import RejectionSamplingStrategy
from mellea.stdlib.base import ChatContext
import inspect


def bee_app(func: Callable) -> Callable:
"""Serves as a wrapper that takes any Mellea program and converts it to a BeeAI Agent. This is an example for an email writer."""
server = Server()

params : dict = inspect.signature(func).parameters # Mapping params from Mellea function onto form inputs
form_fields : list[str] = list(params.keys())[1:-1]
print(params)
all_fields : list[TextField] = []

for field in form_fields:
all_fields.append(TextField(id=field, label=field, col_span=2)) #Maps all input params from Mellea agent into BeeAI Forms

form_render = FormRender(
id="input_form",
title="Please provide your information",
columns=2,
fields=all_fields
)
form_extension_spec = FormExtensionSpec(form_render)


@server.agent(name="mellea_agent", detail=AgentDetail(interaction_mode="single-turn"))

async def wrapper(input: Message,
llm: Annotated[LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand()],
trajectory: Annotated[TrajectoryExtensionServer, TrajectoryExtensionSpec()],
form: Annotated[FormExtensionServer,
form_extension_spec]):


form_data = form.parse_form_response(message=input)
inputs = [form_data.values[key].value for key in form_data.values] # Extracting all of the user inputs from the form
llm_config = llm.data.llm_fulfillments.get("default")

for i in range(2): #Fixed loop budget to two iterations
yield trajectory.trajectory_metadata(
title=f"Attempt {i + 1}/2",
content=f"Generating message...")
m = MelleaSession(OpenAIBackend(
model_id=llm_config.api_model,
api_key=llm_config.api_key,
base_url=llm_config.api_base
))

sampling = await asyncio.to_thread(func, m, *inputs)

validations = sampling.sample_validations[0]
all_passed = all(bool(val_result) for _, val_result in validations)

if all_passed:
yield trajectory.trajectory_metadata(title=f"✓ Attempt {i + 1} succeeded!")
yield AgentMessage(text=sampling.value)
return

status = "\n".join(f"{'✓' if bool(v) else '✗'} {getattr(r, 'description', str(r))}" for r, v in validations)
yield trajectory.trajectory_metadata(title=f"✗ Attempt {i + 1} failed", content=status)

yield AgentMessage(text=sampling.value)

server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)))

return wrapper


37 changes: 37 additions & 0 deletions cli/serve/bee_playform/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class RangeType:
"""A custom range class that mimics the built-in range() behavior."""

def __init__(self, start, stop=None, step=1):
if step == 0:
raise ValueError("RangeType() arg 3 must not be zero")

if stop is None:
self.start = 0
self.stop = start
else:
self.start = start
self.stop = stop

self.step = step
self.current = self.start

def __iter__(self):
"""Returns the iterator object."""
self.current = self.start # Reset iterator for each new loop
return self

def __next__(self):
"""Returns the next value in the sequence."""
if self.step > 0 and self.current >= self.stop:
raise StopIteration
if self.step < 0 and self.current <= self.stop:
raise StopIteration

value = self.current
self.current += self.step
return value

def __repr__(self):
"""Official string representation of the object."""
return f"RangeType({self.start}, {self.stop}, {self.step})"

47 changes: 47 additions & 0 deletions docs/examples/bee_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Example use case for BeeAI integration: utilizing a Mellea program to write an email with an IVF loop.
Also demo of RangeType to demonstrate random selection of a integer from a given range
"""
import os
import asyncio
import sys
from typing import Annotated

from mellea import MelleaSession, start_session
from mellea.stdlib.base import ChatContext, ModelOutputThunk

from mellea.stdlib.sampling import RejectionSamplingStrategy
from mellea.stdlib.sampling.types import SamplingResult
from mellea.stdlib.sampling.base import Context
from mellea.stdlib.requirement import req, Requirement, simple_validate
#from cli.serve.bee_playform.types import RangeType
from bee_platform.bee_platform import bee_app


@bee_app
def mellea_func(m: MelleaSession, sender: str, recipient, subject: str, topic: str, sampling_iters : int = 3) -> tuple[ModelOutputThunk, Context] | SamplingResult:
"""
Example email writing module that utilizes an IVR loop in Mellea to generate an email with a specific list of requirements.
Inputs:
sender: str
recipient: str
subject: str
topic: str
Output:
sampling: tuple[ModelOutputThunk, Context] | SamplingResult
"""
requirements = [
req("Be formal."),
req("Be funny."),
req(f"Make sure that the email is from {sender}, is towards {recipient}, has {subject} as the subject, and is focused on {topic} as a topic"),
Requirement("Use less than 100 words.",
validation_fn=simple_validate(lambda o: len(o.split()) < 100))
]
sampling = m.instruct(f"Write an email from {sender}. Subject of email is {subject}. Name of recipient is {recipient}. Topic of email should be {topic}.", requirements=requirements, strategy=RejectionSamplingStrategy(loop_budget=1), return_sampling_results=True)

return sampling





40 changes: 40 additions & 0 deletions new_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import random

class RangeType:
"""A custom range class that mimics the built-in range() behavior."""

def __init__(self, start, stop=None, step=1):
if step == 0:
raise ValueError("RangeType() arg 3 must not be zero")

if stop is None:
self.start = 0
self.stop = start
else:
self.start = start
self.stop = stop

self.step = step
self.current = self.start

def __iter__(self):
"""Returns the iterator object."""
self.current = self.start # Reset iterator for each new loop
return self

def __next__(self):
"""Returns the next value in the sequence."""
if self.step > 0 and self.current >= self.stop:
raise StopIteration
if self.step < 0 and self.current <= self.stop:
raise StopIteration

value = self.current
self.current += self.step
return value

def __repr__(self):
"""Official string representation of the object. Randomly selected age from a given interval."""

return str(random.choice(list(self)))