Files
wehub-resource-sync 177604c7d1
Verify and Test / build (3.10) (push) Has been cancelled
Lint / build (3.10) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:42:59 +08:00

284 lines
7.8 KiB
Python

# Copyright 2026 Emcie Co Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
from dataclasses import dataclass
from lagom import Container
from pytest import fixture
from parlant.core.async_utils import Timeout
from parlant.core.application import Application
from parlant.core.agents import AgentId, AgentStore
from parlant.core.customers import CustomerId, CustomerStore
from parlant.core.guidelines import GuidelineStore
from parlant.core.sessions import EventKind, EventSource, Session, SessionStore
from parlant.core.tags import Tag
from parlant.core.tools import ToolResult
from tests.test_utilities import create_guideline, nlp_test
REASONABLE_AMOUNT_OF_TIME = 10
@dataclass
class ContextOfTest:
container: Container
app: Application
customer_id: CustomerId
@fixture
async def context(
container: Container,
customer_id: CustomerId,
) -> ContextOfTest:
return ContextOfTest(
container=container,
app=container[Application],
customer_id=customer_id,
)
@fixture
async def agent_id(container: Container) -> AgentId:
store = container[AgentStore]
agent = await store.create_agent(
name="test-agent",
max_engine_iterations=2,
)
return agent.id
@fixture
async def proactive_agent_id(
container: Container,
agent_id: AgentId,
) -> AgentId:
guideline = await container[GuidelineStore].create_guideline(
condition="The customer hasn't engaged yet",
action="Greet the customer",
)
await container[GuidelineStore].upsert_tag(
guideline_id=guideline.id,
tag_id=Tag.for_agent_id(agent_id).id,
)
return agent_id
@fixture
async def session(
container: Container,
customer_id: CustomerId,
agent_id: AgentId,
) -> Session:
store = container[SessionStore]
session = await store.create_session(
customer_id=customer_id,
agent_id=agent_id,
)
return session
@fixture
async def customer_id(container: Container) -> CustomerId:
store = container[CustomerStore]
customer = await store.create_customer("Larry David", extra={"email": "larry@seinfeld.com"})
return customer.id
async def test_that_a_new_customer_session_can_be_created(
context: ContextOfTest,
agent_id: AgentId,
) -> None:
created_session = await context.app.sessions.create(
customer_id=context.customer_id,
agent_id=agent_id,
)
session_in_db = await context.container[SessionStore].read_session(
created_session.id,
)
assert created_session == session_in_db
async def test_that_a_new_customer_session_with_a_proactive_agent_contains_a_message(
context: ContextOfTest,
proactive_agent_id: AgentId,
) -> None:
session = await context.app.sessions.create(
customer_id=context.customer_id,
agent_id=proactive_agent_id,
allow_greeting=True,
)
assert await context.app.sessions.wait_for_more_events(
session_id=session.id,
min_offset=0,
kinds=[EventKind.MESSAGE],
timeout=Timeout(REASONABLE_AMOUNT_OF_TIME),
)
events = list(await context.container[SessionStore].list_events(session.id))
assert len([e for e in events if e.kind == EventKind.MESSAGE]) == 1
async def test_that_when_a_client_event_is_posted_then_new_server_events_are_emitted(
context: ContextOfTest,
session: Session,
) -> None:
event = await context.app.sessions.create_event(
session_id=session.id,
kind=EventKind.MESSAGE,
data={
"message": "Hey there",
"participant": {
"display_name": "Johnny Boy",
},
},
metadata={},
)
await context.app.sessions.wait_for_more_events(
session_id=session.id,
min_offset=1 + event.offset,
kinds=[EventKind.MESSAGE],
timeout=Timeout(REASONABLE_AMOUNT_OF_TIME),
)
events = list(await context.container[SessionStore].list_events(session.id))
assert len(events) > 1
async def test_that_a_session_update_is_detected_as_soon_as_a_client_event_is_posted(
context: ContextOfTest,
session: Session,
) -> None:
event = await context.app.sessions.create_event(
session_id=session.id,
kind=EventKind.MESSAGE,
data={
"message": "Hey there",
"participant": {
"display_name": "Johnny Boy",
},
},
metadata={},
)
assert await context.app.sessions.wait_for_more_events(
session_id=session.id,
min_offset=event.offset,
kinds=[],
timeout=Timeout.none(),
)
async def test_that_when_a_customer_quickly_posts_more_than_one_message_then_only_one_message_is_emitted_as_a_reply_to_the_last_message(
context: ContextOfTest,
session: Session,
) -> None:
messages = [
"What are bananas?",
"Scratch that; what are apples?",
"Actually scratch that too. What are pineapples?",
]
for m in messages:
await context.app.sessions.create_event(
session_id=session.id,
kind=EventKind.MESSAGE,
data={
"message": m,
"participant": {
"display_name": "Johnny Boy",
},
},
metadata={},
)
await asyncio.sleep(1)
await asyncio.sleep(REASONABLE_AMOUNT_OF_TIME)
events = list(await context.container[SessionStore].list_events(session.id))
message_events = [e for e in events if e.kind == EventKind.MESSAGE]
assert len(message_events) == 4
assert await nlp_test(str(message_events[-1].data), "It talks about pineapples")
def hand_off_to_human_operator() -> ToolResult:
return ToolResult(data=None, control={"mode": "manual"})
async def test_that_a_response_is_not_generated_automatically_after_a_tool_switches_the_session_to_manual_mode(
context: ContextOfTest,
session: Session,
) -> None:
await create_guideline(
container=context.container,
agent_id=session.agent_id,
condition="the customer expresses dissatisfaction",
action="immediately hand off to a human operator, explaining this just before you sign off",
tool_function=hand_off_to_human_operator,
)
event = await context.app.sessions.create_event(
session_id=session.id,
kind=EventKind.MESSAGE,
data={
"message": "I'm extremely dissatisfied with your service!",
"participant": {
"display_name": "Johnny Boy",
},
},
metadata={},
)
await context.app.sessions.wait_for_more_events(
session_id=session.id,
min_offset=event.offset,
kinds=[EventKind.MESSAGE],
source=EventSource.AI_AGENT,
timeout=Timeout(30),
)
updated_session = await context.container[SessionStore].read_session(session.id)
assert session.mode == "auto"
assert updated_session.mode == "manual"
event = await context.app.sessions.create_event(
session_id=session.id,
kind=EventKind.MESSAGE,
data={
"message": "Well?",
"participant": {
"display_name": "Johnny Boy",
},
},
metadata={},
)
assert not await context.app.sessions.wait_for_more_events(
session_id=session.id,
min_offset=event.offset + 1,
timeout=Timeout(3),
)