Бесшовная передача человеку для 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 и управления диалогом. Рекомендуется дополнить систему аудитом, контролем доступа и инструкциями для операторов при работе со сложными или чувствительными запросами.