So I am developing an AI agent chatbot for a bank website. I am using dialogflow and Python with FastAPI to create the endpoints and the webhook for the dialogflow fulfillment. There is an intent that collects the bank ID, another one after it that verifies the bank ID, and then if valid, it asks integrity questions, if not, it prompts "Bank ID is invalid". So the bank ID verification and Integrity Questions shoul trigger automatically and not wait for a user prompt message. I already achieved and when I test it inside dialogflow, it works. However, when I test it using Postman or on the frontend, it prompts the default static responses of the intent instead of the webhook responses. Why do you think this is happening?
This is my endpoints.py file:
@router.post("/webhook")
async def dialogflow_webhook(request: dict):
session = request.get("session")
query_result = request.get("queryResult", {})
intent_name = query_result.get("intent", {}).get("displayName", "")
parameters = query_result.get("parameters", {})
if intent_name == "Bank ID Collection":
bank_id = parameters.get("bank_id")
if not bank_id:
return {
"fulfillmentText": "I didn't get your bank ID. Could you please provide it?"
}
# Trigger a follow-up event for an automatic transition to bank ID verification.
return {
"fulfillmentText": f"Got your bank ID ({bank_id}). Please wait while I verify it.",
"followupEventInput": {
"name": "BANK_ID_VERIFICATION_EVENT",
"languageCode": "en-US",
"parameters": {"bank_id": bank_id}
}
}
elif intent_name == "Bank ID Verification":
bank_id = parameters.get("bank_id")
if not bank_id:
return {
"fulfillmentText": "I didn't get your bank ID. Could you please provide it?"
}
is_valid = bank_client.check_bank_id(bank_id)
if is_valid:
return {
"fulfillmentText": "Your bank ID is valid. Please answer the following security questions.",
"followupEventInput": {
"name": "INTEGRITY_QUESTIONS",
"languageCode": "en-US",
"parameters": {"bank_id": bank_id}
},
"outputContexts": [
{
"name": f"{session}/contexts/awaiting_integrity",
"lifespanCount": 5,
"parameters": {"bank_id": bank_id}
}
]
}
else:
return {
"fulfillmentText": "The bank ID provided is invalid. Please try again."
}
elif intent_name == "Integrity Questions":
# Check if we have the maiden name; if not, ask for it.
maiden_name = parameters.get("maiden_name")
if not maiden_name:
return {
"fulfillmentText": "What is your mother's maiden name?",
"outputContexts": [
{
"name": f"{session}/contexts/awaiting_maiden_name",
"lifespanCount": 5,
"parameters": parameters
}
]
}
else:
# Once the maiden name is provided, ask for the first pet's name.
return {
"fulfillmentText": "What was the name of your first pet?",
"outputContexts": [
{
"name": f"{session}/contexts/awaiting_first_pet",
"lifespanCount": 5,
"parameters": parameters
}
]
}
elif intent_name == "First Pet Answer":
# This branch handles the user's answer to the first pet question.
first_pet = parameters.get("first_pet")
if not first_pet:
return {
"fulfillmentText": "I didn't catch the name of your first pet. Could you please say it again?"
}
# You can add more logic here if needed.
return {
"fulfillmentText": "Good! I have verified your identity. How can I help you today?",
}
else:
return {"fulfillmentText": "I'm not sure how to handle that."}
@router.post("/detect_intent")
async def detect_intent(payload: dict):
"""
Detects user intent by calling Dialogflow's detect_intent API.
It automatically chains follow-up events by looping until no event is returned.
Accepts an optional "contexts" field to preserve conversation state.
"""
from google.cloud import dialogflow_v2 as dialogflow
from google.cloud.dialogflow_v2.types import QueryParameters, Context, EventInput
from google.protobuf.struct_pb2 import Struct
from google.protobuf.json_format import MessageToDict
import asyncio
def safe_convert(message):
"""
Safely converts a protobuf message (or a map-like object) to a dict.
"""
if message is None:
return {}
try:
if hasattr(message, "DESCRIPTOR"):
return MessageToDict(message)
else:
return dict(message)
except Exception:
return dict(message)
try:
# Extract fields from payload
session_id = payload.get("session", "default-session")
query_text = payload.get("queryText")
language_code = payload.get("languageCode", "en-US")
if not query_text:
raise HTTPException(status_code=400, detail="queryText must be provided")
# Get Dialogflow project ID from environment
project_id = os.getenv("DIALOGFLOW_PROJECT_ID")
if not project_id:
raise HTTPException(
status_code=500,
detail="Dialogflow project ID is not configured in the environment"
)
# Create session client and session path
session_client = dialogflow.SessionsClient()
session_path = f"projects/{project_id}/agent/sessions/{session_id}"
# Process any provided contexts
contexts_payload = payload.get("contexts", [])
contexts = []
for ctx in contexts_payload:
full_context_name = f"{session_path}/contexts/{ctx.get('name')}"
parameters_struct = Struct()
params = ctx.get("parameters", {})
if params:
for key, value in params.items():
parameters_struct[key] = value
context_obj = Context(
name=full_context_name,
lifespan_count=ctx.get("lifespanCount", 5),
parameters=parameters_struct
)
contexts.append(context_obj)
query_params = QueryParameters(contexts=contexts) if contexts else None
# Build initial text query input and request
text_input = dialogflow.TextInput(text=query_text, language_code=language_code)
query_input = dialogflow.QueryInput(text=text_input)
request_obj = {"session": session_path, "query_input": query_input}
if query_params:
request_obj["query_params"] = query_params
# Loop: call detect_intent repeatedly if a follow-up event is returned
while True:
response = session_client.detect_intent(request=request_obj)
# Wait a short time to ensure Dialogflow has updated the state
await asyncio.sleep(0.5)
# Convert webhook payload safely
webhook_payload = safe_convert(response.query_result.webhook_payload)
# Check if a follow-up event exists
if webhook_payload and "followupEventInput" in webhook_payload:
followup_event = webhook_payload["followupEventInput"]
event_name = followup_event.get("name")
event_language_code = followup_event.get("languageCode", language_code)
event_params = followup_event.get("parameters", {})
# Build an EventInput using the event info
event_parameters = Struct()
for key, value in event_params.items():
event_parameters[key] = value
event_input = EventInput(
name=event_name,
language_code=event_language_code,
parameters=event_parameters
)
new_query_input = dialogflow.QueryInput(event=event_input)
# Update the request_obj with the event input for the next call
request_obj = {"session": session_path, "query_input": new_query_input}
if query_params:
request_obj["query_params"] = query_params
# Loop again to process the follow-up event
continue
# No follow-up event; break out of loop
break
# Process output contexts and parameters for final response
output_contexts = []
for ctx in response.query_result.output_contexts or []:
output_contexts.append({
"name": ctx.name,
"lifespanCount": ctx.lifespan_count,
"parameters": safe_convert(ctx.parameters)
})
parameters = safe_convert(response.query_result.parameters)
webhook_payload = safe_convert(response.query_result.webhook_payload)
return {
"queryText": response.query_result.query_text,
"fulfillmentText": response.query_result.fulfillment_text,
"webhookPayload": webhook_payload,
"intent": response.query_result.intent.display_name,
"parameters": parameters,
"outputContexts": output_contexts,
}
except Exception as e:
print("Error in detect_intent endpoint:", e)
raise HTTPException(status_code=500, detail=str(e))