Бесшовная передача человеку для AI-агента по страхованию на Parlant и Streamlit
Почему важна передача человеку
Автоматическая поддержка справляется с типовыми запросами, но иногда требуется человеческое решение. Передача человеку гарантирует, что когда AI достигает своих ограничений, оператор может бесшовно продолжить диалог без потери контекста. В этом материале показано, как реализовать такую систему для AI-агента по страхованию с использованием Parlant и как построить интерфейс Streamlit для операторов Tier-2.
Установка и зависимости
Убедитесь, что у вас есть действующий OpenAI API ключ и сохраните его в файле .env в корне проекта. Храните креденшелы вне кода и контроля версий.
OPENAI_API_KEY=your_api_key_here
Установите необходимые пакеты:
pip install parlant dotenv streamlit
Реализация агента (agent.py)
Скрипт агента определяет поведение AI, инструменты, сценарии разговоров, глоссарий и механизм передачи человеку. Когда агент сможет переключаться в ручной режим, вы сможете создать UI на Streamlit для операторов.
Загрузка библиотек
import asyncio
import os
from datetime import datetime
from dotenv import load_dotenv
import parlant.sdk as p
load_dotenv()
Определение инструментов агента
Инструменты моделируют действия помощника: получение открытых заявок, оформление новой заявки и получение деталей полиса.
@p.tool
async def get_open_claims(context: p.ToolContext) -> p.ToolResult:
return p.ToolResult(data=["Claim #123 - Pending", "Claim #456 - Approved"])
@p.tool
async def file_claim(context: p.ToolContext, claim_details: str) -> p.ToolResult:
return p.ToolResult(data=f"New claim filed: {claim_details}")
@p.tool
async def get_policy_details(context: p.ToolContext) -> p.ToolResult:
return p.ToolResult(data={
"policy_number": "POL-7788",
"coverage": "Covers accidental damage and theft up to $50,000"
})
Инициация передачи человеку
Если AI понимает, что не может помочь, он вызывает инструмент, переключающий сессию в ручной режим, чтобы человек мог взять контроль.
@p.tool
async def initiate_human_handoff(context: p.ToolContext, reason: str) -> p.ToolResult:
"""
Initiate handoff to a human agent when the AI cannot adequately help the customer.
"""
print(f" Initiating human handoff: {reason}")
# Setting session to manual mode stops automatic AI responses
return p.ToolResult(
data=f"Human handoff initiated because: {reason}",
control={
"mode": "manual" # Switch session to manual mode
}
)
Глоссарий и термины
Общие термины помогают агенту давать консистентные ответы на частые вопросы.
async def add_domain_glossary(agent: p.Agent):
await agent.create_term(
name="Customer Service Number",
description="You can reach us at +1-555-INSURE",
)
await agent.create_term(
name="Operating Hours",
description="We are available Mon-Fri, 9AM-6PM",
)
Сценарии разговоров
Сценарии кодируют потоки диалогов, например оформление заявки или объяснение покрытия, включая вызов инструментов и эскалацию.
# ---------------------------
# Claim Journey
# ---------------------------
async def create_claim_journey(agent: p.Agent) -> p.Journey:
journey = await agent.create_journey(
title="File an Insurance Claim",
description="Helps customers report and submit a new claim.",
conditions=["The customer wants to file a claim"],
)
s0 = await journey.initial_state.transition_to(chat_state="Ask for accident details")
s1 = await s0.target.transition_to(tool_state=file_claim, condition="Customer provides details")
s2 = await s1.target.transition_to(chat_state="Confirm claim was submitted", condition="Claim successfully created")
await s2.target.transition_to(state=p.END_JOURNEY, condition="Customer confirms submission")
return journey
# ---------------------------
# Policy Journey
# ---------------------------
async def create_policy_journey(agent: p.Agent) -> p.Journey:
journey = await agent.create_journey(
title="Explain Policy Coverage",
description="Retrieves and explains customer's insurance coverage.",
conditions=["The customer asks about their policy"],
)
s0 = await journey.initial_state.transition_to(tool_state=get_policy_details)
await s0.target.transition_to(
chat_state="Explain the policy coverage clearly",
condition="Policy info is available",
)
await agent.create_guideline(
condition="Customer presses for legal interpretation of coverage",
action="Politely explain that legal advice cannot be provided",
)
return journey
Главный запуск
Сервер Parlant запускает агента локально, регистрирует глоссарий, сценарии и правило передачи человеку.
async def main():
async with p.Server() as server:
agent = await server.create_agent(
name="Insurance Support Agent",
description=(
"Friendly Tier-1 AI assistant that helps with claims and policy questions. "
"Escalates complex or unresolved issues to human agents (Tier-2)."
),
)
# Add shared terms & definitions
await add_domain_glossary(agent)
# Journeys
claim_journey = await create_claim_journey(agent)
policy_journey = await create_policy_journey(agent)
# Disambiguation rule
status_obs = await agent.create_observation(
"Customer mentions an issue but doesn't specify if it's a claim or policy"
)
await status_obs.disambiguate([claim_journey, policy_journey])
# Global Guidelines
await agent.create_guideline(
condition="Customer asks about unrelated topics",
action="Kindly redirect them to insurance-related support only",
)
# Human Handoff Guideline
await agent.create_guideline(
condition="Customer requests human assistance or AI is uncertain about the next step",
action="Initiate human handoff and notify Tier-2 support.",
tools=[initiate_human_handoff],
)
print(" Insurance Support Agent with Human Handoff is ready! Open the Parlant UI to chat.")
if __name__ == "__main__":
asyncio.run(main())
Запуск агента
Выполните:
python agent.py
Parlant будет доступен локально по умолчанию на http://localhost:8800. После запуска агента можно подключать интерфейс для передачи человеку.
Интерфейс передачи (handoff.py)
Streamlit-приложение подключается к Parlant и позволяет оператору Tier-2 просматривать сессии, сообщения и отправлять ответы.
Импорт библиотек
import asyncio
import streamlit as st
from datetime import datetime
from parlant.client import AsyncParlantClient
Создание клиента Parlant
client = AsyncParlantClient(base_url="http://localhost:8800")
Состояние сессии
Используйте st.session_state для хранения событий и смещения, чтобы подгружать только новые сообщения.
if "events" not in st.session_state:
st.session_state.events = []
if "last_offset" not in st.session_state:
st.session_state.last_offset = 0
Отрисовка сообщений
Функция форматирует отображение сообщений в зависимости от источника.
def render_message(message, source, participant_name, timestamp):
if source == "customer":
st.markdown(f"** Customer [{timestamp}]:** {message}")
elif source == "ai_agent":
st.markdown(f"** AI [{timestamp}]:** {message}")
elif source == "human_agent":
st.markdown(f"** {participant_name} [{timestamp}]:** {message}")
elif source == "human_agent_on_behalf_of_ai_agent":
st.markdown(f"** (Human as AI) [{timestamp}]:** {message}")
Получение событий
Асинхронная функция опрашивает Parlant и добавляет новые события в session_state.
async def fetch_events(session_id):
try:
events = await client.sessions.list_events(
session_id=session_id,
kinds="message",
min_offset=st.session_state.last_offset,
wait_for_data=5
)
for event in events:
message = event.data.get("message")
source = event.source
participant_name = event.data.get("participant", {}).get("display_name", "Unknown")
timestamp = getattr(event, "created", None) or event.data.get("created", "Unknown Time")
event_id = getattr(event, "id", "Unknown ID")
st.session_state.events.append(
(message, source, participant_name, timestamp, event_id)
)
st.session_state.last_offset = max(st.session_state.last_offset, event.offset + 1)
except Exception as e:
st.error(f"Error fetching events: {e}")
Отправка сообщений
Два хелпера позволяют отправлять сообщение как человек или от имени AI.
async def send_human_message(session_id: str, message: str, operator_name: str = "Tier-2 Operator"):
event = await client.sessions.create_event(
session_id=session_id,
kind="message",
source="human_agent",
message=message,
participant={
"id": "operator-001",
"display_name": operator_name
}
)
return event
async def send_message_as_ai(session_id: str, message: str):
event = await client.sessions.create_event(
session_id=session_id,
kind="message",
source="human_agent_on_behalf_of_ai_agent",
message=message
)
return event
Интерфейс Streamlit
UI позволяет ввести session ID, обновить историю и отправить сообщение.
st.title(" Human Handoff Assistant")
session_id = st.text_input("Enter Parlant Session ID:")
if session_id:
st.subheader("Chat History")
if st.button("Refresh Messages"):
asyncio.run(fetch_events(session_id))
for msg, source, participant_name, timestamp, event_id in st.session_state.events:
render_message(msg, source, participant_name, timestamp)
st.subheader("Send a Message")
operator_msg = st.text_input("Type your message:")
if st.button("Send as Human"):
if operator_msg.strip():
asyncio.run(send_human_message(session_id, operator_msg))
st.success("Message sent as human agent ")
asyncio.run(fetch_events(session_id))
if st.button("Send as AI"):
if operator_msg.strip():
asyncio.run(send_message_as_ai(session_id, operator_msg))
st.success("Message sent as AI ")
asyncio.run(fetch_events(session_id))
Такой подход даёт операторам простой и понятный инструмент для подключения к живым сессиям Parlant и управления диалогом. Рекомендуется дополнить систему аудитом, контролем доступа и инструкциями для операторов при работе со сложными или чувствительными запросами.