Skip to content

Commit 20728c2

Browse files
committed
2 parents b3f9d77 + 3a27f90 commit 20728c2

13 files changed

+3654
-711
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import asyncio
2+
import os
3+
from datetime import datetime
4+
from dotenv import load_dotenv
5+
import parlant.sdk as p
6+
7+
load_dotenv()
8+
9+
# ---------------------------
10+
# Tier 1 Automation Tools
11+
# ---------------------------
12+
13+
@p.tool
14+
async def get_open_claims(context: p.ToolContext) -> p.ToolResult:
15+
return p.ToolResult(data=["Claim #123 - Pending", "Claim #456 - Approved"])
16+
17+
@p.tool
18+
async def file_claim(context: p.ToolContext, claim_details: str) -> p.ToolResult:
19+
return p.ToolResult(data=f"New claim filed: {claim_details}")
20+
21+
@p.tool
22+
async def get_policy_details(context: p.ToolContext) -> p.ToolResult:
23+
return p.ToolResult(data={
24+
"policy_number": "POL-7788",
25+
"coverage": "Covers accidental damage and theft up to $50,000"
26+
})
27+
28+
# ---------------------------
29+
# Human Handoff Tool
30+
# ---------------------------
31+
32+
@p.tool
33+
async def initiate_human_handoff(context: p.ToolContext, reason: str) -> p.ToolResult:
34+
"""
35+
Initiate handoff to a human agent when the AI cannot adequately help the customer.
36+
"""
37+
print(f"🚨 Initiating human handoff: {reason}")
38+
# Setting session to manual mode stops automatic AI responses
39+
return p.ToolResult(
40+
data=f"Human handoff initiated because: {reason}",
41+
control={
42+
"mode": "manual" # Switch session to manual mode
43+
}
44+
)
45+
46+
# ---------------------------
47+
# Glossary (shared domain terms)
48+
# ---------------------------
49+
50+
async def add_domain_glossary(agent: p.Agent):
51+
await agent.create_term(
52+
name="Customer Service Number",
53+
description="You can reach us at +1-555-INSURE",
54+
)
55+
await agent.create_term(
56+
name="Operating Hours",
57+
description="We are available Mon–Fri, 9AM–6PM",
58+
)
59+
60+
# ---------------------------
61+
# Claim Journey
62+
# ---------------------------
63+
64+
async def create_claim_journey(agent: p.Agent) -> p.Journey:
65+
journey = await agent.create_journey(
66+
title="File an Insurance Claim",
67+
description="Helps customers report and submit a new claim.",
68+
conditions=["The customer wants to file a claim"],
69+
)
70+
71+
s0 = await journey.initial_state.transition_to(chat_state="Ask for accident details")
72+
s1 = await s0.target.transition_to(tool_state=file_claim, condition="Customer provides details")
73+
s2 = await s1.target.transition_to(chat_state="Confirm claim was submitted", condition="Claim successfully created")
74+
await s2.target.transition_to(state=p.END_JOURNEY, condition="Customer confirms submission")
75+
76+
return journey
77+
78+
# ---------------------------
79+
# Policy Journey
80+
# ---------------------------
81+
82+
async def create_policy_journey(agent: p.Agent) -> p.Journey:
83+
journey = await agent.create_journey(
84+
title="Explain Policy Coverage",
85+
description="Retrieves and explains customer’s insurance coverage.",
86+
conditions=["The customer asks about their policy"],
87+
)
88+
89+
s0 = await journey.initial_state.transition_to(tool_state=get_policy_details)
90+
await s0.target.transition_to(
91+
chat_state="Explain the policy coverage clearly",
92+
condition="Policy info is available",
93+
)
94+
95+
await agent.create_guideline(
96+
condition="Customer presses for legal interpretation of coverage",
97+
action="Politely explain that legal advice cannot be provided",
98+
)
99+
return journey
100+
101+
# ---------------------------
102+
# Main Setup
103+
# ---------------------------
104+
105+
async def main():
106+
async with p.Server() as server:
107+
agent = await server.create_agent(
108+
name="Insurance Support Agent",
109+
description=(
110+
"Friendly Tier-1 AI assistant that helps with claims and policy questions. "
111+
"Escalates complex or unresolved issues to human agents (Tier-2)."
112+
),
113+
)
114+
115+
# Add shared terms & definitions
116+
await add_domain_glossary(agent)
117+
118+
# Journeys
119+
claim_journey = await create_claim_journey(agent)
120+
policy_journey = await create_policy_journey(agent)
121+
122+
# Disambiguation rule
123+
status_obs = await agent.create_observation(
124+
"Customer mentions an issue but doesn’t specify if it's a claim or policy"
125+
)
126+
await status_obs.disambiguate([claim_journey, policy_journey])
127+
128+
# Global Guidelines
129+
await agent.create_guideline(
130+
condition="Customer asks about unrelated topics",
131+
action="Kindly redirect them to insurance-related support only",
132+
)
133+
134+
# Human Handoff Guideline
135+
await agent.create_guideline(
136+
condition="Customer requests human assistance or AI is uncertain about the next step",
137+
action="Initiate human handoff and notify Tier-2 support.",
138+
tools=[initiate_human_handoff],
139+
)
140+
141+
print("✅ Insurance Support Agent with Human Handoff is ready! Open the Parlant UI to chat.")
142+
143+
if __name__ == "__main__":
144+
asyncio.run(main())
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import asyncio
2+
import streamlit as st
3+
from datetime import datetime
4+
from parlant.client import AsyncParlantClient
5+
6+
# ----------------------
7+
# Setup Parlant Client
8+
# ----------------------
9+
client = AsyncParlantClient(base_url="http://localhost:8800")
10+
11+
# ----------------------
12+
# Event Storage
13+
# ----------------------
14+
if "events" not in st.session_state:
15+
st.session_state.events = []
16+
if "last_offset" not in st.session_state:
17+
st.session_state.last_offset = 0
18+
19+
# ----------------------
20+
# UI Display Functions
21+
# ----------------------
22+
def render_message(message, source, participant_name, timestamp):
23+
if source == "customer":
24+
st.markdown(f"**🧍‍♂️ Customer [{timestamp}]:** {message}")
25+
elif source == "ai_agent":
26+
st.markdown(f"**🤖 AI [{timestamp}]:** {message}")
27+
elif source == "human_agent":
28+
st.markdown(f"**🙋 {participant_name} [{timestamp}]:** {message}")
29+
elif source == "human_agent_on_behalf_of_ai_agent":
30+
st.markdown(f"**👤 (Human as AI) [{timestamp}]:** {message}")
31+
else:
32+
st.markdown(f"**🕵️ Unknown [{timestamp}]:** {message}")
33+
34+
35+
async def fetch_events(session_id):
36+
try:
37+
events = await client.sessions.list_events(
38+
session_id=session_id,
39+
kinds="message",
40+
min_offset=st.session_state.last_offset,
41+
wait_for_data=5
42+
)
43+
for event in events:
44+
message = event.data.get("message")
45+
source = event.source
46+
participant_name = event.data.get("participant", {}).get("display_name", "Unknown")
47+
timestamp = getattr(event, "created", None) or event.data.get("created", "Unknown Time")
48+
event_id = getattr(event, "id", "Unknown ID")
49+
50+
st.session_state.events.append(
51+
(message, source, participant_name, timestamp, event_id)
52+
)
53+
st.session_state.last_offset = max(st.session_state.last_offset, event.offset + 1)
54+
55+
except Exception as e:
56+
st.error(f"Error fetching events: {e}")
57+
58+
59+
async def send_human_message(session_id: str, message: str, operator_name: str = "Tier-2 Operator"):
60+
event = await client.sessions.create_event(
61+
session_id=session_id,
62+
kind="message",
63+
source="human_agent",
64+
message=message,
65+
participant={
66+
"id": "operator-001",
67+
"display_name": operator_name
68+
}
69+
)
70+
return event
71+
72+
73+
async def send_message_as_ai(session_id: str, message: str):
74+
event = await client.sessions.create_event(
75+
session_id=session_id,
76+
kind="message",
77+
source="human_agent_on_behalf_of_ai_agent",
78+
message=message
79+
)
80+
return event
81+
82+
83+
# ----------------------
84+
# Streamlit UI
85+
# ----------------------
86+
st.title("💼 Human Handoff Assistant")
87+
88+
session_id = st.text_input("Enter Parlant Session ID:")
89+
90+
if session_id:
91+
st.subheader("Chat History")
92+
if st.button("Refresh Messages"):
93+
asyncio.run(fetch_events(session_id))
94+
95+
for msg, source, participant_name, timestamp, event_id in st.session_state.events:
96+
render_message(msg, source, participant_name, timestamp)
97+
98+
st.subheader("Send a Message")
99+
operator_msg = st.text_input("Type your message:")
100+
101+
if st.button("Send as Human"):
102+
if operator_msg.strip():
103+
asyncio.run(send_human_message(session_id, operator_msg))
104+
st.success("Message sent as human agent ✅")
105+
asyncio.run(fetch_events(session_id))
106+
107+
if st.button("Send as AI"):
108+
if operator_msg.strip():
109+
asyncio.run(send_message_as_ai(session_id, operator_msg))
110+
st.success("Message sent as AI ✅")
111+
asyncio.run(fetch_events(session_id))

0 commit comments

Comments
 (0)