Skip to content

Commit

Permalink
Add Node data model and Session support (#11)
Browse files Browse the repository at this point in the history
* Add NodeData model for client connection properties
Update MQServiceManager to manage Sessions

* Remove WIP voice_client

---------

Co-authored-by: Daniel McKnight <daniel@neon.ai>
  • Loading branch information
NeonDaniel and NeonDaniel authored Jan 26, 2024
1 parent 367887d commit 930222d
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 8 deletions.
8 changes: 5 additions & 3 deletions neon_hana/app/routers/assist.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Request
from neon_hana.schema.assist_requests import *
from neon_hana.app.dependencies import jwt_bearer, mq_connector

Expand All @@ -44,5 +44,7 @@ async def get_tts(request: TTSRequest) -> TTSResponse:


@assist_route.post("/get_response")
async def get_response(request: SkillRequest) -> SkillResponse:
return mq_connector.get_response(**dict(request))
async def get_response(skill_request: SkillRequest,
request: Request) -> SkillResponse:
skill_request.node_data.networking.public_ip = request.client.host
return mq_connector.get_response(**dict(skill_request))
31 changes: 28 additions & 3 deletions neon_hana/mq_service_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from uuid import uuid4
from fastapi import HTTPException

from neon_hana.schema.node_model import NodeData
from neon_hana.schema.user_profile import UserProfile
from neon_mq_connector.utils.client_utils import send_mq_request

Expand All @@ -48,8 +49,10 @@ def __init__(self, config: dict):
self.stt_max_length = config.get('stt_max_length_encoded') or 500000
self.tts_max_words = config.get('tts_max_words') or 128
self.email_enabled = config.get('enable_email')
self.sessions_by_id = dict()

def _validate_api_proxy_response(self, response: dict):
@staticmethod
def _validate_api_proxy_response(response: dict):
if response['status_code'] == 200:
try:
resp = json.loads(response['content'])
Expand All @@ -67,6 +70,18 @@ def _validate_api_proxy_response(self, response: dict):
code = response['status_code'] if response['status_code'] > 200 else 500
raise APIError(status_code=code, detail=response['content'])

def get_session(self, node_data: NodeData) -> dict:
"""
Get a serialized Session object for the specified Node.
@param node_data: NodeData received from client
@returns: Serialized session, possibly cached from previous a response
"""
session_id = node_data.device_id
self.sessions_by_id.setdefault(session_id,
{"session_id": session_id,
"site_id": node_data.location.site_id})
return self.sessions_by_id[session_id]

def query_api_proxy(self, service_name: str, query_params: dict,
timeout: int = 10):
query_params['service'] = service_name
Expand Down Expand Up @@ -165,18 +180,28 @@ def get_tts(self, to_speak: str, lang_code: str, gender: str):
return {"encoded_audio": audio}

def get_response(self, utterance: str, lang_code: str,
user_profile: UserProfile):
user_profile: UserProfile, node_data: NodeData):
session = self.get_session(node_data)
user_profile.user.username = (user_profile.user.username or
self.mq_cliend_id)

request_data = {"msg_type": "recognizer_loop:utterance",
"data": {"utterances": [utterance],
"lang": lang_code},
"context": {"username": user_profile.user.username,
"user_profiles": [user_profile.model_dump(mode="json")],
"user_profiles": [
user_profile.model_dump(mode="json")],
"source": "hana",
"session": session,
"node_data": node_data.model_dump(
mode="json"),
"ident": f"{self.mq_cliend_id}{time()}"}}
response = send_mq_request("/neon_chat_api", request_data,
"neon_chat_api_request",
timeout=self.mq_default_timeout)

# Update session data for future inputs
self.sessions_by_id[session['session_id']] = \
response['context']['session']
sentence = response['data']['responses'][lang_code]['sentence']
return {"answer": sentence, "lang_code": lang_code}
6 changes: 4 additions & 2 deletions neon_hana/schema/assist_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from typing import List
from typing import List, Optional
from pydantic import BaseModel

from neon_hana.schema.node_model import NodeData
from neon_hana.schema.user_profile import UserProfile


Expand Down Expand Up @@ -82,7 +83,8 @@ class TTSResponse(BaseModel):
class SkillRequest(BaseModel):
utterance: str
lang_code: str
user_profile: UserProfile
user_profile: UserProfile = UserProfile()
node_data: Optional[NodeData] = NodeData()

model_config = {
"json_schema_extra": {
Expand Down
57 changes: 57 additions & 0 deletions neon_hana/schema/node_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
# All trademark and other rights reserved by their respective owners
# Copyright 2008-2021 Neongecko.com Inc.
# BSD-3
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from uuid import uuid4

from pydantic import BaseModel, Field
from typing import Optional, Dict


class NodeSoftware(BaseModel):
operating_system: str = ""
os_version: str = ""
neon_packages: Optional[Dict[str, str]] = None


class NodeNetworking(BaseModel):
local_ip: str = "127.0.0.1"
public_ip: str = ""
mac_address: str = ""


class NodeLocation(BaseModel):
lat: Optional[float] = None
lon: Optional[float] = None
site_id: Optional[str] = None


class NodeData(BaseModel):
device_id: str = Field(default_factory=lambda: str(uuid4()))
device_name: str = ""
device_description: str = ""
platform: str = ""
networking: NodeNetworking = NodeNetworking()
software: NodeSoftware = NodeSoftware()
location: NodeLocation = NodeLocation()

0 comments on commit 930222d

Please sign in to comment.