Skip to content

Commit 9f8b4de

Browse files
Update README and documentation to enhance examples and clarify agent serving via API (#92)
1 parent f266104 commit 9f8b4de

File tree

3 files changed

+169
-92
lines changed

3 files changed

+169
-92
lines changed

README.md

Lines changed: 120 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -32,59 +32,121 @@ pip install sdialog
3232

3333
## 🏁 Quickstart tour
3434

35-
Here's a short, hands-on example showing personas, agents, a simple rule (orchestrator), and a tool.
35+
Here's a short, hands-on example where a support agent helps a customer disputing a double charge—we'll add a tiny rule to steer refund behavior and a simple tool to check order status, generate three dialogs for later evaluation, and then serve the support agent on port 1333 for Open WebUI or any OpenAI‑compatible client.
3636

3737
```python
3838
import sdialog
3939
from sdialog import Context
4040
from sdialog.agents import Agent
41-
from sdialog.personas import Persona
41+
from sdialog.personas import SupportAgent, Customer
4242
from sdialog.orchestrators import SimpleReflexOrchestrator
4343

44-
# First, let's set our preferred backend/model and parameters
45-
sdialog.config.llm("openai:gpt-4.1", temperature=0.9)
44+
# First, let's set our preferred default backend:model and parameters
45+
sdialog.config.llm("openai:gpt-4.1", temperature=0.7)
46+
# sdialog.config.llm("ollama:qwen3:14b") # etc.
47+
48+
# Let's define our personas (use built-ins like in this example, or create your own!)
49+
support_persona = SupportAgent(
50+
name="Ava",
51+
role="Customer Support Agent",
52+
product_scope="Subscriptions and Billing",
53+
communication_style="clear and empathetic",
54+
resolution_authority_level="standard",
55+
escalation_policy="Escalate to Billing Specialist if refund exceptions are requested",
56+
)
4657

47-
# Let's define our personas
48-
alice = Persona(name="Alice", role="barista", personality="cheerful")
49-
bob = Persona(name="Bob", role="customer", personality="curious")
58+
customer_persona = Customer(
59+
name="Riley",
60+
issue="Charged twice this month",
61+
issue_category="billing",
62+
issue_description="I see two charges for October on my card",
63+
anger_level="medium",
64+
times_called=1,
65+
desired_outcome="refund the duplicate charge",
66+
)
5067

5168
# (Optional) Let's add a concrete conversational context
5269
ctx = Context(
53-
location="Downtown cafe",
54-
environment="noisy, aromatic cafe with occasional grinder sounds",
55-
circumstances="Morning rush hour",
56-
objects=["espresso machine", "menu board", "tip jar"]
70+
location="Online chat",
71+
environment="support portal",
72+
circumstances="Billing cycle just renewed",
5773
)
5874

59-
# (Optional) Let's add a simple tool (just a plain Python function)
60-
# We'll use a tiny mock function our agent can call as a tool
61-
def lookup_menu(item: str) -> dict:
62-
return {"item": item, "specials": ["vanilla latte", "cold brew"]}
75+
# (Optional) Let's add a simple tool (just a plain Python function) for our support agent
76+
def check_order_status(order_id: str) -> dict:
77+
# In production, connect to your DB or API here
78+
return {"order_id": order_id, "status": "paid", "last_charge": "2025-10-01"}
6379

6480
# (Optional) Let's include a small rule-based orchestrator
6581
react = SimpleReflexOrchestrator(
66-
condition=lambda utt: "decaf" in utt.lower(),
67-
instruction="Explain decaf options and suggest one."
82+
condition=lambda utt: "refund" in utt.lower() or "charged twice" in utt.lower(),
83+
instruction=(
84+
"Follow the refund policy. Verify account, apologize briefly, "
85+
"explain next steps clearly, and offer to create a ticket if needed."
86+
),
6887
)
6988

7089
# Now we create the agents
71-
barista = Agent(persona=alice, tools=[lookup_menu])
72-
customer = Agent(persona=bob, first_utterance="Hi!")
90+
support_agent = Agent(persona=support_persona,
91+
tools=[check_order_status],
92+
name="Support")
93+
simulated_customer = Agent(
94+
persona=customer_persona,
95+
first_utterance="Hi, I was charged twice this month.",
96+
name="Customer",
97+
)
7398

7499
# (Optional) We can attach orchestrators to an agent using pipe-like composition
75-
barista = barista | react
100+
support_agent = support_agent | react
76101

77-
# Let's generate three dialogs!
102+
# Let's generate three dialogs between them! (so we can evaluate them later, see evaluation section)
78103
for ix in range(3):
79-
dialog = customer.dialog_with(barista, context=ctx)
80-
dialog.print(orchestration=True)
81-
dialog.to_file(f"dialog_{ix}.json")
104+
dialog = simulated_customer.dialog_with(support_agent, context=ctx)
105+
dialog.to_file(f"dialog_{ix}.json")
106+
dialog.print(orchestration=True) # pretty print each dialog
107+
108+
# Finally, let's serve the support agent to interact with real users (OpenAI-compatible API)
109+
# Point Open WebUI or any OpenAI-compatible client to: http://localhost:1333
110+
# Model name will appear as "Support:latest" (AGENT_NAME:latest).
111+
support_agent.serve(1333)
82112
```
83113
> [!NOTE]
84114
> - See [orchestration tutorial](https://github.com/idiap/sdialog/blob/main/tutorials/3.multi-agent%2Borchestrator_generation.ipynb) and [agents with tools and thoughts](https://github.com/idiap/sdialog/blob/main/tutorials/7.agents_with_tools_and_thoughts.ipynb).
115+
> - Serving agents: more details on the OpenAI/Ollama-compatible API in the docs: [Serving Agents](https://sdialog.readthedocs.io/en/latest/sdialog/index.html#serving-agents).
85116
> - Dialogs are [rich objects](https://sdialog.readthedocs.io/en/latest/api/sdialog.html#sdialog.Dialog) with helper methods (filter, slice, transform, etc.) that can be easily exported and loaded.
86117
> - Next: see [Loading and saving dialogs](#loading-and-saving-dialogs) and [Auto-generating personas and contexts](#auto-generating-personas-and-contexts) for persistence and controlled diversity.
87118
119+
### 🧪 Testing remote systems with simulated users
120+
121+
Use SDialog as a controllable test harness for any OpenAI‑compatible system such as vLLM-based ones—role‑play realistic or adversarial users against your deployed system:
122+
123+
* Black‑box functional checks (Does the system follow instructions? Handle edge cases?)
124+
* Persona / use‑case coverage (Different goals, emotions, domains)
125+
* Regression testing (Run the same persona batch each release; diff dialogs)
126+
* Safety / robustness probing (Angry, confused, or noisy users)
127+
* Automated evaluation (Pipe generated dialogs directly into evaluators below)
128+
129+
Core idea: wrap your system as an `Agent`, talk to it with simulated user `Agent`s, and capture `Dialog`s you can save, diff, and score.
130+
131+
Below is a minimal example where our simulated customer interacts once with your hypothetical remote endpoint:
132+
133+
```python
134+
# Our remote system (your conversational backend exposing an OpenAI-compatible API)
135+
system = Agent(
136+
model="your/model", # Model name exposed by your server
137+
openai_api_base="http://your-endpoint.com:8000/v1", # Base URL of the service
138+
openai_api_key="EMPTY", # Or a real key if required
139+
name="System"
140+
)
141+
142+
# Let's make the system talk to our simulated customer defined in the example above.
143+
dialog = system.dialog_with(simulated_customer)
144+
dialog.to_file("dialog_0.json")
145+
```
146+
147+
Next, evaluate these dialogs or orchestrate agents with more complex flows using rule/LLM hybrid orchestrators (see [tutorials 3 & 7](https://github.com/idiap/sdialog/tree/main/tutorials)).
148+
149+
88150
### 💾 Loading and saving dialogs
89151

90152
[Dialog](https://sdialog.readthedocs.io/en/latest/sdialog/index.html#dialog)s are JSON‑serializable and can be created from multiple formats. After generating one you can persist it, then reload later for evaluation, transformation, or mixing with real data.
@@ -117,73 +179,6 @@ dialog.filter("Alice").rename_speaker("Alice", "Customer").upper().to_file("proc
117179
avg_words_turn = sum(len(turn) for turn in dialog) / len(dialog)
118180
```
119181

120-
### 🧬 Auto-generating personas and contexts
121-
122-
Use [generators](https://sdialog.readthedocs.io/en/latest/sdialog/index.html#attribute-generators) to fill in (or selectively control) persona/context attributes using LLMs or other data sources (functions, CSV files, inline prompts). The `.set()` method lets you override how individual attributes are produced.
123-
124-
```python
125-
from sdialog.personas import Doctor, Patient
126-
from sdialog.generators import PersonaGenerator, ContextGenerator
127-
from sdialog import Context
128-
129-
# By default, unspecified attributes are LLM generated
130-
doc = PersonaGenerator(Doctor(specialty="Cardiology")).generate()
131-
pat = PersonaGenerator(Patient(symptoms="chest pain")).generate()
132-
133-
# Optionally specify generation sources per attribute
134-
ctx_gen = ContextGenerator(Context(location="emergency room"))
135-
ctx_gen.set(
136-
objects=get_random_object, # user-defined function
137-
circumstances="{csv:circumstance:./data/circumstances.csv}", # CSV file values
138-
goals="{llm:Suggest a realistic goal for the context}" # targeted LLM instruction
139-
)
140-
ctx = ctx_gen.generate()
141-
```
142-
143-
> [!TIP]
144-
> 🕹️ 👉 Try the [demo notebook](https://colab.research.google.com/github/idiap/sdialog/blob/main/tutorials/0.demo.ipynb) to experiment with generators.
145-
146-
### 🧪 Testing remote systems with simulated users
147-
148-
SDialog can also easily act as a controllable test harness for any (OpenAI‑compatible) conversational backend. Create realistic or adversarial user personas to role‑play against your deployed system:
149-
150-
* Black‑box functional checks (Does the system follow instructions? Handle edge cases?)
151-
* Persona / use‑case coverage (Different goals, emotions, domains)
152-
* Regression testing (Run the same persona batch each release; diff dialogs)
153-
* Safety / robustness probing (Angry, confused, or noisy users)
154-
* Automated evaluation (Pipe generated dialogs directly into evaluators below)
155-
156-
Core idea: your remote system is wrapped as an `Agent`; simulated users are `Agent`s with personas producing diverse conversation trajectories, all recorded as `Dialog` objects you can save, diff, and score.
157-
158-
Below is a minimal example where an "angry customer" interacts once with a mock remote endpoint:
159-
160-
```python
161-
# Our remote system (your conversational backend exposing an OpenAI-compatible API)
162-
system = Agent(
163-
model="my/super-llm", # Model name exposed by your server
164-
openai_api_base="http://my-endpoint.com:8000/v1", # Base URL of the service
165-
openai_api_key="EMPTY", # Or a real key if required
166-
name="System"
167-
)
168-
169-
# Let's manually define one (minimal) synthetic customer persona
170-
angry_customer = Customer(
171-
name="Riley",
172-
issue="Billing error on last invoice",
173-
issue_description="Charged twice for the same month",
174-
anger_level="high",
175-
times_called=3,
176-
)
177-
178-
simulated_customer = Agent(persona=angry_customer, name="Customer")
179-
180-
# Let's make the system talk to our simulated customer once
181-
dialog = system.dialog_with(simulated_customer)
182-
dialog.to_file("dialog_0.json")
183-
```
184-
185-
Next, evaluate these dialogs or orchestrate agents with more complex flows using rule/LLM hybrid orchestrators (see [tutorials 3 & 7](https://github.com/idiap/sdialog/tree/main/tutorials)).
186-
187182
## 📊 Evaluate and compare
188183

189184
Use [built‑in metrics](https://sdialog.readthedocs.io/en/latest/api/sdialog.html#module-sdialog.evaluation) (readability, flow, linguistic features, LLM judges) or easily create new ones, then aggregate and compare datasets via `DatasetComparator`.
@@ -212,6 +207,34 @@ comparator.plot()
212207
> [!TIP]
213208
> See [evaluation tutorial](https://github.com/idiap/sdialog/blob/main/tutorials/5.evaluation.ipynb).
214209
210+
211+
### 🧬 Auto-generating personas and contexts
212+
213+
Use [generators](https://sdialog.readthedocs.io/en/latest/sdialog/index.html#attribute-generators) to fill in (or selectively control) persona/context attributes using LLMs or other data sources (functions, CSV files, inline prompts). The `.set()` method lets you override how individual attributes are produced.
214+
215+
```python
216+
from sdialog.personas import Doctor, Patient
217+
from sdialog.generators import PersonaGenerator, ContextGenerator
218+
from sdialog import Context
219+
220+
# By default, unspecified attributes are LLM generated
221+
doc = PersonaGenerator(Doctor(specialty="Cardiology")).generate()
222+
pat = PersonaGenerator(Patient(symptoms="chest pain")).generate()
223+
224+
# Optionally specify generation sources per attribute
225+
ctx_gen = ContextGenerator(Context(location="emergency room"))
226+
ctx_gen.set(
227+
objects=get_random_object, # user-defined function
228+
circumstances="{csv:circumstance:./data/circumstances.csv}", # CSV file values
229+
goals="{llm:Suggest a realistic goal for the context}" # targeted LLM instruction
230+
)
231+
ctx = ctx_gen.generate()
232+
```
233+
234+
> [!TIP]
235+
> 🕹️ 👉 Try the [demo notebook](https://colab.research.google.com/github/idiap/sdialog/blob/main/tutorials/0.demo.ipynb) to experiment with generators.
236+
237+
215238
## 🧠 Mechanistic interpretability
216239

217240
Attach Inspectors to capture per‑token activations and optionally steer (add/ablate directions) to analyze or intervene in model behavior.
@@ -244,7 +267,8 @@ agent_steered("You are an extremely upset assistant") # Agent "can't get angry
244267
> [!TIP]
245268
> See [the tutorial](https://github.com/idiap/sdialog/blob/main/tutorials/6.agent%2Binspector_refusal.ipynb) on using SDialog to remove the refusal capability from LLaMA 3.2.
246269
247-
## 🔧 Interoperability
270+
271+
## 🔌 Backends and configuration
248272

249273
Many [backends supported](https://sdialog.readthedocs.io/en/latest/sdialog/index.html#configuration-layer), just use `"BACKEND:MODEL"` string format to either set a global default LLM for all components or pass one to each component:
250274

@@ -266,6 +290,7 @@ my_agent = Agent(model="amazon:anthropic.claude-3-5-sonnet-20240620-v1:0",
266290
region_name="us-east-1")
267291
```
268292

293+
269294
## 📖 Documentation and tutorials
270295

271296
- [Demo notebook](https://colab.research.google.com/github/idiap/sdialog/blob/main/tutorials/0.demo.ipynb)
@@ -278,6 +303,7 @@ my_agent = Agent(model="amazon:anthropic.claude-3-5-sonnet-20240620-v1:0",
278303
Your prompt to use sdialog here...
279304
```
280305

306+
281307
## 🌍 Project Vision & Community Call
282308

283309
To accelerate open, rigorous, and reproducible conversational AI research, SDialog invites the community to collaborate and help shape the future of open dialogue generation.
@@ -342,12 +368,14 @@ If you use SDialog in academic work, please cite:
342368
}
343369
``` -->
344370

371+
345372
## 🙏 Acknowledgments
346373

347374
This work was supported by the European Union Horizon 2020 project [ELOQUENCE](https://eloquenceai.eu/about/) (grant number 101070558).
348375

349376
The initial development of this project began in preparation for the 2025 Jelinek Memorial Summer Workshop on Speech and Language Technologies ([JSALT 2025](https://jsalt2025.fit.vut.cz/)) as part of the ["Play your Part" research group](https://jsalt2025.fit.vut.cz/play-your-part).
350377

378+
351379
## 📝 License
352380

353381
[MIT License](LICENSE)

docs/examples/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Then in your scripts below make sure to set your default LLM backend, model, and
2020
2121
(You may substitute :ref:`any supported backend string <backend_list>`: ``huggingface:...``, ``ollama:...``, ``amazon:...``, ``google:...``.)
2222

23+
2324
----------
2425
Generation
2526
----------
@@ -87,6 +88,8 @@ Let's start with something fun and straightforward—creating a simple dialogue
8788
dialog = alice.dialog_with(mentor, max_turns=6)
8889
dialog.print()
8990
91+
Individual agents can be served and exposed as a OpenAI compatible API endpoint with the :meth:`~sdialog.agents.Agent.serve` method (e.g. ``mentor.serve(1333)``), see :ref:`here <serving_agents>` for more details.
92+
9093
Few-Shot Learning with Example Dialogs
9194
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9295
Now let's explore one of SDialog's most powerful features! We can guide our dialogues by providing examples that show the system what style, structure, or format we want. This technique, called few-shot learning, works by supplying ``example_dialogs`` to generation components. These exemplar dialogs are injected into the system prompt to steer tone, task format, and conversation flow.

docs/sdialog/index.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ Key Methods:
234234
- :meth:`~sdialog.agents.Agent.prompt`: get the underlying system prompt used by the agent.
235235
- :meth:`~sdialog.agents.Agent.json`: export the agent as a JSON object.
236236

237+
237238
Orchestration
238239
-------------
239240
Orchestrators are lightweight controllers that examine the current dialog state and the last utterance from the other agent, optionally returning an instruction. They can be **ephemeral** (one-time) or **persistent** (lasting across multiple turns). Orchestrators are composed using the pipe operator:
@@ -317,6 +318,51 @@ This simple example shows the minimal pattern: return an instruction once when a
317318
recap_orch = EarlyRecapOrchestrator(question_threshold=3)
318319
agent = agent | recap_orch
319320
321+
.. _serving_agents:
322+
323+
Serving Agents via REST API
324+
---------------------------
325+
326+
You can expose any Agent over an OpenAI/Ollama-compatible REST API using the :meth:`~sdialog.agents.Agent.serve` method to talk to it from tools like Open WebUI, Ollama GUI, or simple HTTP clients.
327+
328+
.. code-block:: python
329+
330+
from sdialog.agents import Agent
331+
332+
# Let's create an example agent
333+
support = Agent(name="Support")
334+
335+
# And serve it on port 1333 (default host 0.0.0.0)
336+
support.serve(1333)
337+
# Connect client to base URL localhost:1333
338+
339+
For example, to run Open WebUI in docker locally, just set OLLAMA_BASE_URL to point to port 1333 in the same machine when launching the container:
340+
341+
.. code-block:: bash
342+
343+
docker run -e OLLAMA_BASE_URL=http://host.docker.internal:1333 \
344+
-p 3030:8080 \
345+
-v open-webui:/app/backend/data --name open-webui \
346+
--restart always ghcr.io/open-webui/open-webui:main -d
347+
348+
Then open http://localhost:3030 in your browser to chat with the agent!
349+
350+
351+
For serving multiple agents under a single endpoint, use ``Server``'s :meth:`~sdialog.server.Server.serve`, as in the following example:
352+
353+
.. code-block:: python
354+
355+
from sdialog.agents import Agent
356+
from sdialog.server import Server
357+
358+
# Create multiple agents
359+
agent1 = Agent(name="AgentOne")
360+
agent2 = Agent(name="AgentTwo")
361+
362+
# Serve both agents on port 1333 (select them by name from the GUI)
363+
Server.serve([agent1, agent2], port=1333)
364+
365+
320366
----
321367

322368
Generation

0 commit comments

Comments
 (0)