Skip to content

Commit

Permalink
Merge pull request #206 from emcie-co/feature/mc-490-return-event-id-…
Browse files Browse the repository at this point in the history
…instead-of-correlation-id-from-utterance-api

Return event ID instead of correlation ID from utterance API
  • Loading branch information
kichanyurd authored Dec 27, 2024
2 parents 5da2a90 + 0ee7db6 commit 3fa994a
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 71 deletions.
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

<div align="center">
<img alt="Parlant Logo" src="https://github.com/emcie-co/parlant/blob/e0fefba25efd1b956ead881964025baae5dc4119/banner.png" />
<h2>A Client/Server Framework for Building Guided LLM Agents</h2>
<h2>Parlant: A behavioral control system for customer-facing LLM agents </h2>
<p>
<a href="https://www.parlant.io/" target="_blank">Website</a> |
<a href="https://www.parlant.io/docs/quickstart/introduction" target="_blank">Introduction</a> |
Expand All @@ -20,9 +20,13 @@
</div>

## ✨ What is Parlant?
Parlant is a fully open-source (Apache 2.0) client/server framework for building and serving guided customer-facing agents based on LLMs (Large Language Models).
Parlant is a framework that transforms how AI agents make decisions in customer-facing scenarios.

It comes pre-built with responsive session (conversation) management, content-filtering, jailbreak protection, an integrated sandbox UI for behavioral testing, and other goodies.
Unlike traditional approaches that rely on prompt engineering or conversational flow charts, Parlant implements a dynamic control system that ensures agents follow your specific business rules by matching and activating the appropriate combination of behavioral guidelines for every specific context.

When an agent needs to respond to a customer, Parlant's engine evaluates the situation, checks relevant guidelines, gathers necessary information through your tools, and continuously re-evaluates its approach based on your guidelines as new information emerges. When it's time to generate a message, Parlant implements self-critique mechanisms to ensure that the agent's responses precisely align with your intended behavior as given by the contextually-matched guidelines.

Parlant comes pre-built with responsive session (conversation) management, a detection mechanism for incoherence and contradictions in guidelines, content-filtering, jailbreak protection, an integrated sandbox UI for behavioral testing, native API clients in Python and TypeScript, and other goodies.

## 📦 Quickstart
```bash
Expand All @@ -36,10 +40,10 @@ $ # Open the sandbox UI at http://localhost:8000 and play
## 🙋‍♂️🙋‍♀️ Who Is Parlant For?
Parlant is the right tool for the job if you're building an LLM-based chat agent, and:

1. You require a high degree of behavioral precision and consistency
1. You require a high degree of behavioral precision and consistency, particularly in customer-facing scenarios
1. Your agent is expected to undergo many behavioral refinements and changes, and you need a way to implement those changes efficiently and confidently
1. You would benefit from assistance in maintaining coherence and disentangling subtleties in numerous agent instructions
1. Conversational UX and user-engagmeent is an important concern for your use case
1. You're expected to maintain a large set of behavioral guidelines, and you want to maintain them coherently and with version-tracking
1. Conversational UX and user-engagmeent is an important concern for your use case, and you want to easily control the flow and language of conversations

## 🤔 What Makes Parlant Different?

Expand Down Expand Up @@ -94,15 +98,15 @@ By giving structure to behavioral guidelines, and _granularizing_ guidelines (i.

To start learning and building with Parlant, visit our [documentation portal](https://parlant.io/docs/quickstart/introduction).

Need help? Send us a message on [Discord](https://discord.gg/duxWqxKk6J). We're happy to answer questions and help you get up and running!
Need help? Ask us anything on [Discord](https://discord.gg/duxWqxKk6J). We're happy to answer questions and help you get up and running!

## 💻 Usage Example
Adding a guideline for an agent—for example, to ask a counter-question to get more info when a customer asks a question:
```bash
parlant guideline create \
--agent-id CUSTOMER_SUCCESS_AGENT_ID \
--condition "a free-tier customer is asking how to use our product" \
--action "first seek to understsand what they're trying to achieve"
--action "first seek to understand what they're trying to achieve"
```

In Parlant, Customer-Agent interaction happens asynchronously, to enable more natural customer interactions, rather than forcing a strict and unnatural request-reply mode.
Expand Down
33 changes: 19 additions & 14 deletions src/parlant/api/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,27 +1340,32 @@ async def _add_agent_message(
if params.actions:
actions = [utterance_request_dto_to_utterance_request(a) for a in params.actions]
correlation_id = await application.utter(session, actions)

event, *_ = await session_store.list_events(
session_id=session_id,
correlation_id=correlation_id,
kinds=["message"],
)
return event_to_dto(event)
else:
correlation_id = await application.dispatch_processing_task(session)

await session_listener.wait_for_events(
session_id=session_id,
correlation_id=correlation_id,
timeout=Timeout(60),
)
await session_listener.wait_for_events(
session_id=session_id,
correlation_id=correlation_id,
timeout=Timeout(60),
)

event = next(
iter(
await session_store.list_events(
session_id=session_id,
correlation_id=correlation_id,
kinds=["status"],
event = next(
iter(
await session_store.list_events(
session_id=session_id,
correlation_id=correlation_id,
kinds=["status"],
)
)
)
)

return event_to_dto(event)
return event_to_dto(event)

async def _add_human_agent_message_on_behalf_of_ai_agent(
session_id: SessionIdPath,
Expand Down
8 changes: 4 additions & 4 deletions src/parlant/core/engines/alpha/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,19 @@ async def utter(
interaction_state = await self._load_interaction_state(context)
try:
with self._logger.operation(
f"Uttering actions '{[r.action for r in requests]}' for session {context.session_id}"
f"Uttering in session {context.session_id} using actions '{[r.action for r in requests]}'"
):
await self._do_utter(context, interaction_state, event_emitter, requests)
return True

except asyncio.CancelledError:
self._logger.warning(f"Uttering for session {context.session_id} was cancelled.")
self._logger.warning(f"Uttering in session {context.session_id} was cancelled.")
return False

except Exception as exc:
formatted_exception = traceback.format_exception(type(exc), exc, exc.__traceback__)
self._logger.error(
f"Error during uttering for session {context.session_id}: {formatted_exception}"
f"Error during uttering in session {context.session_id}: {formatted_exception}"
)

await event_emitter.emit_status_event(
Expand All @@ -176,7 +176,7 @@ async def utter(

except BaseException as exc:
self._logger.critical(
f"Critical error during uttering for session {context.session_id}: "
f"Critical error during uttering in session {context.session_id}: "
f"{traceback.format_exception(type(exc), exc, exc.__traceback__)}"
)
raise
Expand Down
1 change: 1 addition & 0 deletions tests/api/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,4 +1033,5 @@ async def test_that_an_agent_message_can_be_generated_from_utterance_requests(
)

assert len(events) == 1
assert events[0]["id"] == event["id"]
assert "thinking" in events[0]["data"]["message"].lower()
24 changes: 22 additions & 2 deletions tests/core/common/engines/alpha/steps/engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

import asyncio
from typing import cast
from pytest_bdd import given, when
from pytest_bdd import given, when, parsers
from unittest.mock import AsyncMock

from parlant.core.agents import Agent, AgentId, AgentStore
from parlant.core.customers import CustomerStore
from parlant.core.engines.alpha.engine import AlphaEngine
from parlant.core.engines.alpha.message_event_generator import MessageEventGenerator
from parlant.core.emissions import EmittedEvent
from parlant.core.engines.types import Context
from parlant.core.engines.types import Context, UtteranceReason, UtteranceRequest
from parlant.core.emission.event_buffer import EventBuffer
from parlant.core.sessions import SessionId, SessionStore

Expand All @@ -45,6 +45,26 @@ def given_a_faulty_message_production_mechanism(
generator.generate_events = AsyncMock(side_effect=Exception()) # type: ignore


@step(
given,
parsers.parse('an utterance request "{action}", to {do_something}'),
)
def given_a_follow_up_utterance_request(
context: ContextOfTest, action: str, do_something: str
) -> UtteranceRequest:
utterance_request = UtteranceRequest(
action=action,
reason={
"follow up with the customer": UtteranceReason.FOLLOW_UP,
"buy time": UtteranceReason.BUY_TIME,
}[do_something],
)

context.actions.append(utterance_request)

return utterance_request


@step(when, "processing is triggered", target_fixture="emitted_events")
def when_processing_is_triggered(
context: ContextOfTest,
Expand Down
31 changes: 0 additions & 31 deletions tests/core/common/engines/alpha/steps/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from parlant.core.customers import CustomerStore
from parlant.core.engines.alpha.utils import emitted_tool_event_to_dict
from parlant.core.emissions import EmittedEvent
from parlant.core.engines.types import UtteranceReason, UtteranceRequest
from parlant.core.nlp.moderation import ModerationTag
from parlant.core.sessions import (
MessageEventData,
Expand Down Expand Up @@ -195,36 +194,6 @@ def given_a_flagged_customer_message(
return session.id


@step(
given,
parsers.parse('an utterance with action of "{action}", to follow up with the customer'),
)
def given_a_follow_up_utterance_request(
context: ContextOfTest,
action: str,
) -> UtteranceRequest:
utterance_request = UtteranceRequest(action=action, reason=UtteranceReason.FOLLOW_UP)

context.actions.append(utterance_request)

return utterance_request


@step(
given,
parsers.parse('an utterance with action of "{action}", to buy time'),
)
def given_a_buy_time_utterance_request(
context: ContextOfTest,
action: str,
) -> UtteranceRequest:
utterance_request = UtteranceRequest(action=action, reason=UtteranceReason.BUY_TIME)

context.actions.append(utterance_request)

return utterance_request


@step(
when,
parsers.parse("the last {num_messages:d} messages are deleted"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@ Feature: Utterances
And an agent
And an empty session

Scenario: A buy-time message is determined by the actions sent from utter engine operation
Given an utterance with action of "inform the user that more information is coming", to buy time
Scenario: The agent utters a message aligned with an action to buy time
Given an utterance request "inform the customer that more information is coming", to buy time
When uttering is triggered
Then a single message event is emitted
And the message contains that more information is coming
And the message mentions that more information is coming

Scenario: A buy-time message of thinking as an utter action
Given an utterance with action of "tell the user 'Thinking...'", to buy time
Scenario: The agent utters a message aligned with an action to follow up with the customer
Given an utterance request "suggest proceeding to checkout", to follow up with the customer
When uttering is triggered
Then a single message event is emitted
And the message contains thinking

Scenario: A follow-up message is determined by the actions sent from utter engine operation
Given an utterance with action of "ask the user if he need assistant with the blue-yellow feature", to follow up with the customer
When uttering is triggered
Then a single message event is emitted
And the message contains asking the user if he need help with the blue-yellow feature
And the message mentions proceeding to checkout

0 comments on commit 3fa994a

Please sign in to comment.