File size: 9,166 Bytes
e858920
 
fb13605
e858920
 
 
12654ba
 
 
67cba93
 
 
12654ba
aa5aec8
67cba93
 
12654ba
67cba93
12654ba
aa5aec8
 
 
957f6d4
aa5aec8
 
12654ba
 
 
 
86bac8b
767cab0
aa5aec8
767cab0
 
aa5aec8
767cab0
e85b8cc
f5b0efb
e858920
 
767cab0
e858920
 
767cab0
e858920
 
767cab0
e858920
747720a
e858920
 
 
 
26255a3
aa5aec8
12654ba
 
aa5aec8
12654ba
 
aa5aec8
12654ba
e858920
 
 
bef3e08
efdd6c6
 
bef3e08
e858920
12654ba
bef3e08
12654ba
3951ea6
12654ba
 
 
aa5aec8
12654ba
 
1fbce43
aa5aec8
fb13605
 
 
 
 
 
 
 
 
 
26255a3
fb13605
 
 
 
 
 
 
 
 
 
 
 
 
aa5aec8
2193cd3
 
aa5aec8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80d5999
aa5aec8
2193cd3
 
 
 
aa5aec8
 
 
 
 
 
 
 
 
 
 
 
12654ba
 
 
 
aa5aec8
 
 
 
 
747720a
12654ba
 
 
 
 
 
 
 
 
 
 
aa5aec8
747720a
12654ba
 
aa5aec8
12654ba
747720a
aa5aec8
 
1606748
 
 
 
 
 
 
 
 
 
 
 
 
 
fb13605
a5efc48
 
fb13605
747720a
aa5aec8
12654ba
aa5aec8
fc84509
 
aa5aec8
fc84509
12654ba
a77a669
2193cd3
a5efc48
 
a77a669
aa5aec8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”>
# Comment: various test-blocks to prevent session-outage with more recent UI;
# chainlit==1.1.404
# new imports: user_session, init_ws_context, chainlit.session/ WebsocketSession
# ref--'Session is disconnected' rt7E1rI_uhficQm8AAAA ..
# <β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”

import os
import re
import uuid
import json
import asyncio
import requests

from pathlib import Path
from datetime import datetime
from dotenv import load_dotenv

import chainlit as cl

from concurrent.futures import ThreadPoolExecutor

from chainlit import user_session
from chainlit.session import WebsocketSession

from langchain_openai import OpenAI
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain.memory.buffer import ConversationBufferMemory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory

from langchain_core.messages import BaseMessage
from typing import List
from pydantic import BaseModel, Field


# -------------------------------=== class action ===------------------------------
class InMemoryHistory(BaseChatMessageHistory, BaseModel):
    messages: List[BaseMessage] = Field(default_factory=list)

    def add_messages(self, messages: List[BaseMessage]) -> None:
        self.messages.extend(messages)

    def clear(self) -> None:
        self.messages = []

chat_histories = {}

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in chat_histories:
        chat_histories[session_id] = InMemoryHistory()
    return chat_histories[session_id]

# -------------------------------=== environment ===------------------------------
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
auth_token = os.getenv("DAYSOFF_API_TOKEN")
API_URL = "https://aivisions.no/data/daysoff/api/v1/booking/"

# ----------------------------=== system-instruct ===------------------------------
daysoff_assistant_template = """
You are a customer support assistant for Daysoff ('Daysoff Kundeservice AI Support') who helps users retrieve booking information based on their bookingnummer. 
You should concisely use the term ’bookingnummer’. Maintain a friendly and professional tone, **reflecting the warmth of a female customer support 
representative archetype.** By default, you answer in **Norwegian**.
============================
Chat History: {chat_history}
Question: {question}
============================
Answer:
"""

daysoff_assistant_prompt = PromptTemplate(
    input_variables=["chat_history", "question"],
    template=daysoff_assistant_template,
)

# --------------------------------=== API Call ===---------------------------------
async def async_post_request(url, headers, data):
    return await asyncio.to_thread(requests.post, url, headers=headers, json=data)

# ----------------------------=== @cl.set_starters ===------------------------------
@cl.set_starters
async def set_starters():
    return [
        cl.Starter(
            label="Booking ID request",
            message="Kan du gi meg info om en reservasjon?",
            icon="/public/booking_id.svg",
            ),
        cl.Starter(
            label="Metric Space Self-Identity Framework",
            message="Explain the Metric Space Self-Identity Framework like I'm six years old.",
            icon="/public/learn.svg",
            ),
        cl.Starter(
            label="Python script for daily email reports",
            message="Write a script to automate sending daily email reports in Python, and walk me through how I would set it up.",
            icon="/public/terminal.svg",
            ),
        cl.Starter(
            label="Morning routine ideation",
            message="Can you help me create a personalized Yoga/pranayama/meditation morning routine that would help increase my productivity throughout the day? Start by asking me about my current habits and what activities energize me in the morning.",
            icon="/public/idea.svg",
            )
        ]
# ----------------------------=== @cl.on_chat_start ===------------------------------
@cl.on_chat_start
async def setup():
    try:
        cl.user_session.set("socket_auth", True)
        cl.user_session.set("max_retries", 3)
        cl.user_session.set("connection_attempts", 0)

        llm = OpenAI(
            model="gpt-3.5-turbo-instruct",
            temperature=0.7,
            openai_api_key=OPENAI_API_KEY,
            max_tokens=2048,
            top_p=0.9,
            frequency_penalty=0.1,
            presence_penalty=0.1,
        )

        conversation_memory = ConversationBufferMemory(
            memory_key="chat_history",
            max_len=30,
            return_messages=True
        )

        llm_chain = LLMChain(
            llm=llm,
            prompt=daysoff_assistant_prompt,
            memory=conversation_memory,
        )

        cl.user_session.set("llm_chain", llm_chain)

    except Exception as e:
        await cl.Message(content=f"Errorisme in init chat session: {str(e)}").send()

# ----------------------------=== long_running_task ===------------------------------
async def long_running_task(message_content: str):
    loop = asyncio.get_running_loop()
    with ThreadPoolExecutor() as pool:
        llm_chain = cl.user_session.get("llm_chain")
        if llm_chain:
            return await loop.run_in_executor(
                pool,
                lambda: llm_chain.invoke({
                    "question": message_content,
                    "chat_history": ""
                })
            )
        else:
            return {"text": "Errorism: LLM Chain is not init."}

# ----------------------------=== @cl.on_message ===------------------------------
@cl.on_message
async def handle_message(message: cl.Message):
    user_message = message.content
    llm_chain = cl.user_session.get("llm_chain")

    if not llm_chain:
        await cl.Message(content="Errorism: Chat session not properly init. Consider a restart.").send()
        return

    booking_pattern = r'\b[A-Z]{6}\d{6}\b'
    match = re.search(booking_pattern, user_message)

    if match:
        bestillingskode = match.group()
        headers = {
            "Authorization": auth_token,
            "Content-Type": "application/json"
        }
        payload = {"booking_id": bestillingskode}

        try:
            response = await async_post_request(API_URL, headers, payload)
            response.raise_for_status()
            booking_data = response.json()

            #if "order_number" in booking_data.get("data", {}):
            if "booking_id" in booking_data:
                try:
                    data = booking_data["data"]
                    
                    table = (
                        "| π‘­π’Šπ’†π’π’…      | π—œπ—»π—³π—Ό                |\n"
                        "|:-----------|:---------------------|\n"
                        f"| π™±πšŽπšœπšπš’πš•πš•πš’πš—πšπšœπš”πš˜πšπšŽ | {booking_data.get('booking_id', 'N/A')} |\n"
                        f"| 𝙁π™ͺ𝙑𝙑 π™‰π™–π™’π™š  | {booking_data.get('full_name', 'N/A')} |\n"
                        f"| π˜Όπ™’π™€π™ͺ𝙣𝙩     | {booking_data.get('amount', 0)} kr |\n"
                        f"| π˜Ύπ™π™šπ™˜π™ -π™žπ™£   | {booking_data.get('checkin', 'N/A')} |\n"
                        f"| π˜Ύπ™π™šπ™˜π™ -𝙀π™ͺ𝙩  | {booking_data.get('checkout', 'N/A')} |\n"
                        f"| π˜Όπ™™π™™π™§π™šπ™¨π™¨    | {booking_data.get('address', 'N/A')} |\n"
                        f"| π™π™¨π™šπ™§ π™„π˜Ώ    | {booking_data.get('user_id', 0)} |\n"
                        f"| 𝙄𝙣𝙛𝙀 π™π™šπ™­π™©  | {booking_data.get('infotext', 'N/A')} |\n"
                        f"| π™„π™£π™˜π™‘π™ͺπ™™π™šπ™™   | {booking_data.get('included', 'N/A')} |"
                    )

                    combined_message = f"### Her er informasjon for {bestillingskode}:\n\n{table}"
                    await cl.Message(content=combined_message).send()
                    

                except Exception as e:
                    await cl.Message(content=f"En uventet feil oppsto: {str(e)}").send()
            else:
                await cl.Message(content="Ingen bookinginformasjon funnet.").send()

        except requests.exceptions.RequestException as e:
            await cl.Message(content=f"API tilkoblingen ble ikke utfΓΈrt: {str(e)}").send()

    else:
        try:
            response = await long_running_task(message.content)
            await cl.Message(content=response["text"]).send()
            
        except Exception as e:
            await cl.Message(content=f"Errorism!: {str(e)}").send()