Skip to content

Commit 389163b

Browse files
fix: fix context propagation (#190)
* fix context propagation * updated docs
1 parent cf06697 commit 389163b

File tree

9 files changed

+77
-38
lines changed

9 files changed

+77
-38
lines changed

app/admin/test/routes.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from fastapi import APIRouter, Depends
1+
from fastapi import APIRouter, Depends, HTTPException
22
from app.bot.dialogue_manager.models import UserMessage
33
from app.dependencies import get_dialogue_manager
4-
from app.bot.dialogue_manager.dialogue_manager import DialogueManager
4+
from app.bot.dialogue_manager.dialogue_manager import (
5+
DialogueManager,
6+
DialogueManagerException,
7+
)
58

69
router = APIRouter(prefix="/test", tags=["test"])
710

@@ -20,5 +23,8 @@ async def chat(
2023
user_message = UserMessage(
2124
thread_id=body["thread_id"], text=body["text"], context=body["context"]
2225
)
23-
new_state = await dialogue_manager.process(user_message)
26+
try:
27+
new_state = await dialogue_manager.process(user_message)
28+
except DialogueManagerException as e:
29+
raise HTTPException(status_code=400, detail=str(e))
2430
return new_state.to_dict()

app/bot/channels/rest/routes.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from fastapi import APIRouter, Depends
1+
from fastapi import APIRouter, Depends, HTTPException
22
from app.bot.dialogue_manager.models import UserMessage
33
from app.dependencies import get_dialogue_manager
4-
from app.bot.dialogue_manager.dialogue_manager import DialogueManager
4+
from app.bot.dialogue_manager.dialogue_manager import (
5+
DialogueManager,
6+
DialogueManagerException,
7+
)
58

69
router = APIRouter(prefix="/rest", tags=["rest"])
710

@@ -20,5 +23,8 @@ async def webbook(
2023
user_message = UserMessage(
2124
thread_id=body["thread_id"], text=body["text"], context=body["context"]
2225
)
23-
new_state = await dialogue_manager.process(user_message)
26+
try:
27+
new_state = await dialogue_manager.process(user_message)
28+
except DialogueManagerException as e:
29+
raise HTTPException(status_code=400, message=str(e))
2430
return new_state.bot_message

app/bot/dialogue_manager/dialogue_manager.py

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
ParameterModel,
1616
UserMessage,
1717
)
18-
from app.bot.dialogue_manager.http_client import call_api
18+
from app.bot.dialogue_manager.http_client import call_api, APICallExcetion
1919
from app.config import app_config
2020
from app.database import client
2121

2222
logger = logging.getLogger("dialogue_manager")
2323

2424

25+
class DialogueManagerException(Exception):
26+
pass
27+
28+
2529
class DialogueManager:
2630
def __init__(
2731
self,
@@ -77,7 +81,9 @@ def update_model(self, models_dir):
7781
Reloads ML models and synonyms.
7882
"""
7983
# Load models
80-
self.nlu_pipeline.load(models_dir)
84+
ok = self.nlu_pipeline.load(models_dir)
85+
if not ok:
86+
self.nlu_pipeline = None
8187
logger.info("NLU Pipeline models updated")
8288

8389
async def process(self, message: UserMessage) -> State:
@@ -88,6 +94,11 @@ async def process(self, message: UserMessage) -> State:
8894
:return: current state of the conversation including the bot response
8995
"""
9096

97+
if self.nlu_pipeline is None:
98+
raise DialogueManagerException(
99+
"NLU pipeline is not initialized. Please build the models."
100+
)
101+
91102
# Step 1: Get current state
92103
current_state = await self.memory_saver.get(message.thread_id)
93104

@@ -249,9 +260,6 @@ def _process_intent(
249260
entities_by_type[param.type].pop(0)
250261
)
251262

252-
# Update context with extracted_parameters
253-
current_state.context["parameters"] = current_state.extracted_parameters
254-
255263
# Handle missing parameters
256264
current_state = self._handle_missing_parameters(parameters, current_state)
257265

@@ -301,57 +309,72 @@ async def _handle_api_trigger(
301309
"""
302310
if intent.api_trigger and intent.api_details:
303311
try:
304-
result = await self._call_intent_api(intent, current_state.context)
305-
current_state.context["result"] = result
312+
result = await self._call_intent_api(intent, current_state)
306313
template = Template(
307314
intent.speech_response,
308315
undefined=SilentUndefined,
309316
enable_async=True,
310317
)
311-
rendered_text = await template.render_async(**current_state.context)
318+
rendered_text = await template.render_async(
319+
context=current_state.context,
320+
parameters=current_state.extracted_parameters,
321+
result=result,
322+
)
323+
312324
current_state.bot_message = [
313325
{"text": msg} for msg in split_sentence(rendered_text)
314326
]
315-
except Exception as e:
327+
328+
except DialogueManagerException as e:
316329
logger.warning(f"API call failed: {e}")
317330
current_state.bot_message = [
318331
{"text": "Service is not available. Please try again later."}
319332
]
320333
else:
321-
current_state.context["result"] = {}
322334
template = Template(
323335
intent.speech_response,
324336
undefined=SilentUndefined,
325337
enable_async=True,
326338
)
327-
rendered_text = await template.render_async(**current_state.context)
339+
rendered_text = await template.render_async(
340+
context=current_state.context,
341+
parameters=current_state.extracted_parameters,
342+
)
328343
current_state.bot_message = [
329344
{"text": msg} for msg in split_sentence(rendered_text)
330345
]
331-
332346
return current_state
333347

334-
async def _call_intent_api(self, intent: IntentModel, context: Dict):
348+
async def _call_intent_api(self, intent: IntentModel, current_state: State):
335349
"""
336350
Call the API associated with the intent.
337351
"""
338352
api_details = intent.api_details
339353
headers = api_details.get_headers()
340354
url_template = Template(api_details.url, undefined=SilentUndefined)
341-
rendered_url = url_template.render(**context)
342-
355+
rendered_url = url_template.render(
356+
context=current_state.context, parameters=current_state.extracted_parameters
357+
)
343358
if api_details.is_json:
344359
request_template = Template(
345360
api_details.json_data, undefined=SilentUndefined
346361
)
347-
parameters = json.loads(request_template.render(**context))
362+
request_json = request_template.render(
363+
context=current_state.context,
364+
parameters=current_state.extracted_parameters,
365+
)
366+
parameters = json.loads(request_json)
348367
else:
349-
parameters = context.get("parameters", {})
350-
351-
return await call_api(
352-
rendered_url,
353-
api_details.request_type,
354-
headers,
355-
parameters,
356-
api_details.is_json,
357-
)
368+
parameters = current_state.extracted_parameters
369+
370+
try:
371+
return await call_api(
372+
rendered_url,
373+
api_details.request_type,
374+
headers,
375+
parameters,
376+
api_details.is_json,
377+
)
378+
except APICallExcetion as e:
379+
logger.warning(f"API call failed: {e}")
380+
raise DialogueManagerException("API call failed")

app/bot/dialogue_manager/http_client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
logger = logging.getLogger("http_client")
88

99

10+
class APICallExcetion(Exception):
11+
pass
12+
13+
1014
async def call_api(
1115
url: str,
1216
method: str,
@@ -73,10 +77,10 @@ async def call_api(
7377

7478
except aiohttp.ClientError as e:
7579
logger.error(f"HTTP error occurred: {str(e)}")
76-
raise
80+
raise APICallExcetion(f"HTTP error occurred: {str(e)}")
7781
except asyncio.TimeoutError:
7882
logger.error(f"Request timed out after {timeout} seconds")
79-
raise
83+
raise APICallExcetion(f"Request timed out after {timeout} seconds")
8084
except Exception as e:
8185
logger.error(f"Unexpected error during API call: {str(e)}")
8286
raise

app/bot/memory/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def from_dict(cls, state_dict: Dict) -> "State":
6464
def update(self, user_message: UserMessage):
6565
self.user_message = user_message
6666
self.date = datetime.now(UTC)
67+
self.context.update(user_message.context)
6768

6869
if self.complete:
6970
self.bot_message = []

app/bot/nlu/pipeline.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,10 @@ def train(self, training_data: List[Dict[str, Any]], model_path: str) -> None:
4444

4545
def load(self, model_path: str) -> bool:
4646
"""Load all components from model path."""
47-
success = True
4847
for component in self.components:
4948
if not component.load(model_path):
50-
success = False
51-
return success
49+
return False
50+
return True
5251

5352
def process(self, message: Dict[str, Any]) -> Dict[str, Any]:
5453
"""Process message through all components in sequence."""
120 KB
Loading

examples/order_status.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"intents": [{"name": "Default Fallback intent", "userDefined": false, "intentId": "fallback", "apiTrigger": false, "apiDetails": null, "speechResponse": "{{ \n\n[\n \"Sorry ### I'm having trouble understanding you.\",\n \"Hmm ### I cant handle that yet.\",\n \"Can you please re-phrase your query ?\"\n] | random \n\n}}\ufeff\n\n", "parameters": [], "labeledSentences": [], "trainingData": []}, {"name": "cancel", "userDefined": false, "intentId": "cancel", "apiTrigger": false, "apiDetails": null, "speechResponse": "Ok. Canceled.", "parameters": [], "labeledSentences": [], "trainingData": [{"text": "i want to cancel", "entities": []}, {"text": "cancel that", "entities": []}, {"text": "cancel", "entities": []}]}, {"name": "Welcome message", "userDefined": false, "intentId": "init_conversation", "apiTrigger": false, "apiDetails": null, "speechResponse": "Hi {{context[\"username\"] }} ### What can i do for you ?", "parameters": [], "labeledSentences": [], "trainingData": [{"text": "hello there", "entities": []}, {"text": "hey there", "entities": []}, {"text": "hii", "entities": []}, {"text": "heyy", "entities": []}, {"text": "howdy", "entities": []}, {"text": "hey", "entities": []}, {"text": "hello", "entities": []}, {"text": "hi", "entities": []}]}, {"name": "Check Order Status", "userDefined": true, "intentId": "check_order_status", "apiTrigger": true, "apiDetails": {"url": "https://fake-store-api.mock.beeceptor.com/api/orders/status?order_id={{ parameters['order_number'] }}", "requestType": "GET", "headers": [], "isJson": false, "jsonData": ""}, "speechResponse": "Your order status is {{ result['status'] }}", "parameters": [{"name": "order_number", "required": true, "type": "order_number", "prompt": "Please provide your order number"}], "labeledSentences": [], "trainingData": [{"text": "my order number is ORD113134", "entities": [{"value": "ORD113134", "begin": 19, "end": 28, "name": "order_number"}]}, {"text": "What is my order status", "entities": []}, {"text": "I want to know about order ORD123456", "entities": [{"value": "ORD123456", "begin": 27, "end": 36, "name": "order_number"}]}, {"text": "Can you check order ORD789012 for me ?", "entities": [{"value": "ORD789012", "begin": 20, "end": 29, "name": "order_number"}]}, {"text": "Where is my order ORD123456 ?", "entities": [{"value": "ORD123456", "begin": 18, "end": 27, "name": "order_number"}]}, {"text": "Track order ORDER789012", "entities": [{"value": "ORDER789012", "begin": 12, "end": 23, "name": "order_number"}]}, {"text": "What's the status of my order ORD123456 ?", "entities": [{"value": "ORD123456", "begin": 30, "end": 39, "name": "order_number"}]}, {"text": "Tell me order status", "entities": []}]}], "entities": [{"name": "order_number", "entity_values": []}]}
1+
{"intents": [{"name": "Default Fallback intent", "userDefined": false, "intentId": "fallback", "apiTrigger": false, "apiDetails": null, "speechResponse": "{{ \n\n[\n \"Sorry ### I'm having trouble understanding you.\",\n \"Hmm ### I cant handle that yet.\",\n \"Can you please re-phrase your query ?\"\n] | random \n\n}}\ufeff\n\n", "parameters": [], "labeledSentences": [], "trainingData": []}, {"name": "cancel", "userDefined": false, "intentId": "cancel", "apiTrigger": false, "apiDetails": null, "speechResponse": "Ok. Canceled.", "parameters": [], "labeledSentences": [], "trainingData": [{"text": "i want to cancel", "entities": []}, {"text": "cancel that", "entities": []}, {"text": "cancel", "entities": []}]}, {"name": "Welcome message", "userDefined": false, "intentId": "init_conversation", "apiTrigger": false, "apiDetails": null, "speechResponse": "Hi {{context[\"username\"] }} ### What can i do for you ?", "parameters": [], "labeledSentences": [], "trainingData": [{"text": "hello there", "entities": []}, {"text": "hey there", "entities": []}, {"text": "hii", "entities": []}, {"text": "heyy", "entities": []}, {"text": "howdy", "entities": []}, {"text": "hey", "entities": []}, {"text": "hello", "entities": []}, {"text": "hi", "entities": []}]}, {"name": "Check Order Status", "userDefined": true, "intentId": "check_order_status", "apiTrigger": true, "apiDetails": {"url": "https://fake-store-api.mock.beeceptor.com/api/orders/status?order_id={{ parameters['order_number'] }}", "requestType": "GET", "headers": [], "isJson": false, "jsonData": ""}, "speechResponse": " Let me check the status of order {{ parameters.order_number }} ###\n Your order status is <b>{{ result.status }}</b> and is expected to arrive in 2-3 business days.", "parameters": [{"name": "order_number", "required": true, "type": "order_number", "prompt": "Sure ### Can you please give me your order number ?"}], "labeledSentences": [], "trainingData": [{"text": "my order number is ORD113134", "entities": [{"value": "ORD113134", "begin": 19, "end": 28, "name": "order_number"}]}, {"text": "What is my order status", "entities": []}, {"text": "I want to know about order ORD123456", "entities": [{"value": "ORD123456", "begin": 27, "end": 36, "name": "order_number"}]}, {"text": "Can you check order ORD789012 for me ?", "entities": [{"value": "ORD789012", "begin": 20, "end": 29, "name": "order_number"}]}, {"text": "Where is my order ORD123456 ?", "entities": [{"value": "ORD123456", "begin": 18, "end": 27, "name": "order_number"}]}, {"text": "Track order ORDER789012", "entities": [{"value": "ORDER789012", "begin": 12, "end": 23, "name": "order_number"}]}, {"text": "What's the status of my order ORD123456 ?", "entities": [{"value": "ORD123456", "begin": 30, "end": 39, "name": "order_number"}]}, {"text": "Tell me order status", "entities": []}]}], "entities": [{"name": "order_number", "entity_values": []}]}

frontend/.env.development

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080/
1+
NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8080

0 commit comments

Comments
 (0)