Skip to content

Commit 6d26cb0

Browse files
committed
Refactor the Chatbot class to have a compisition relationship with APICall instead of inheritance. Implement and test chatbot component by writing the functions to interact with the API in the generated app script
1 parent 4c86e89 commit 6d26cb0

File tree

6 files changed

+127
-55
lines changed

6 files changed

+127
-55
lines changed

report_config_micw2graph.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ sections:
8282
file_format: "csv"
8383
delimiter: ","
8484
caption: "This is the edge list of the network"
85-
- title: "APICall test"
85+
- title: "ChatBot test"
8686
subsections:
8787
- title: "Simple test"
8888
components:
89-
- title: "APICall test"
90-
component_type: "apicall"
91-
api_url: "https://jsonplaceholder.typicode.com/todos/1"
89+
- title: "ChatBot test"
90+
component_type: "chatbot"
91+
api_url: "http://localhost:11434/api/chat"
92+
model: "llama3.2"

vuegen/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
report_config = load_yaml_config(config_path)
1212

1313
# Define logger suffix based on report type and name
14-
report_name = report_config['report'].get('name')
15-
logger_suffix = f"{report_type}_report_{report_name}"
14+
report_title = report_config['report'].get('title')
15+
logger_suffix = f"{report_type}_report_{report_title}"
1616

1717
# Initialize logger
1818
logger = get_logger(f"{logger_suffix}")

vuegen/report.py

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -404,68 +404,84 @@ def make_api_request(self, method: str, request_body: Optional[dict] = None) ->
404404
self.logger.error(f"API request failed: {e}")
405405
return None
406406

407-
class ChatBot(APICall):
407+
class ChatBot(Component):
408408
"""
409-
A specialized component for creating a ChatBot.
409+
A component for creating a ChatBot that interacts with an API.
410+
This component uses an APICall instance to send requests to the chatbot API and receive responses.
410411
411412
Attributes
412413
----------
413414
model : str
414-
The language model to use.
415+
The language model to use for the chatbot.
416+
api_call : APICall
417+
An instance of the APICall class used to interact with the API for fetching chatbot responses.
418+
headers : Optional[dict]
419+
Headers to include in the API request (default is None).
420+
params : Optional[dict]
421+
Query parameters to include in the API request (default is None).
415422
"""
416423
def __init__(self, title: str, logger: logging.Logger, api_url: str, model: str,
417424
caption: str = None, headers: Optional[dict] = None, params: Optional[dict] = None):
418-
super().__init__(title = title, logger = logger, api_url = api_url,
419-
caption=caption, headers=headers, params=params)
425+
super().__init__(title=title, logger=logger, component_type=ComponentType.CHATBOT, caption=caption)
420426
self.model = model
427+
self.api_call = APICall(
428+
title=title,
429+
logger=logger,
430+
api_url=api_url,
431+
caption=caption,
432+
headers=headers,
433+
params=params
434+
)
421435

422436
def get_chatbot_answer(self, prompt: str) -> dict:
423437
"""
424-
Sends a RAG query and retrieves the resulting documents.
438+
Sends a query to the chatbot API and retrieves the resulting response. This method constructs
439+
the request body for the chatbot, sends the request to the API, and parses the response.
425440
426441
Parameters
427442
----------
428443
prompt : str
429-
The prompt for asking the chatbot.
444+
The prompt or message to send to the chatbot for a response.
430445
431446
Returns
432447
-------
433-
parsed_response : dict
434-
The chabtbot answer.
448+
dict
449+
The parsed response from the chatbot API, containing the chatbot's answer.
435450
"""
436451
request_body = self._generate_query(prompt)
437-
response = self.make_api_request(method="POST", request_body=request_body)
452+
response = self.api_call.make_api_request(method="POST", request_body=request_body) # Delegate API call
438453
if response:
439-
self.logger.info(f"Request successful")
454+
self.logger.info("Request successful")
440455
else:
441-
self.logger.warning("Nothing retreived.")
456+
self.logger.warning("No response retrieved.")
442457
parsed_response = self._parse_api_response(response)
443458
return parsed_response
444459

460+
445461
def _generate_query(self, messages: str) -> dict:
446462
"""
447-
Constructs the request body for a question to the chatbot.
463+
Constructs the request body for the chatbot query.
448464
449465
Parameters
450466
----------
451467
messages : str
452-
The messages for retrieval.
468+
The messages to send to the chatbot.
453469
454470
Returns
455471
-------
456-
request_body : dict
457-
The request body for the question to the chatbot.
472+
dict
473+
The constructed request body for the chatbot query.
458474
"""
459475
self.logger.info(f"Generating request body for message: {messages}")
460476
return {
461477
"model": self.model,
462478
"messages": messages,
463479
"stream": True
464480
}
465-
481+
466482
def _parse_api_response(self, response: dict) -> dict:
467483
"""
468-
Extracts and processes data from the API response.
484+
Processes and extracts relevant data from the API response.
469485
470486
Parameters
471487
----------
@@ -474,8 +490,8 @@ def _parse_api_response(self, response: dict) -> dict:
474490
475491
Returns
476492
-------
477-
output : dict
478-
The extracted data from the response.
493+
dict
494+
The processed response, including the role and content of the assistant's reply.
479495
"""
480496
output = ""
481497
for line in response.iter_lines():
@@ -590,8 +606,8 @@ class ReportView(ABC):
590606
The name of the view.
591607
report : Report
592608
The report that this ABC is associated with.
593-
columns : List[str], optional
594-
Column names used in the report view ABC (default is None).
609+
report_type : ReportType
610+
The report type. It should be one of the values of the ReportType Enum.
595611
596612
"""
597613
def __init__(self, report: 'Report', report_type: 'ReportType'):

vuegen/report_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from streamlit_reportview import StreamlitReportView
22
from quarto_reportview import QuartoReportView
33
from metadata_manager import MetadataManager
4-
from report import ReportType
54
from utils import assert_enum_value
5+
from report import ReportType
66
import logging
77

88
def get_report(config: dict, report_type: str, logger: logging.Logger) -> None:

vuegen/streamlit_reportview.py

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class StreamlitReportView(r.WebAppReportView):
1515
REPORT_MANAG_SCRIPT = 'report_manager.py'
1616

1717
def __init__(self, report: r.Report, report_type: r.ReportType):
18-
super().__init__(report=report, report_type = report_type)
18+
super().__init__(report = report, report_type = report_type)
1919

2020
def generate_report(self, output_dir: str = SECTIONS_DIR, static_dir: str = STATIC_FILES_DIR) -> None:
2121
"""
@@ -479,34 +479,86 @@ def _generate_apicall_content(self, apicall) -> List[str]:
479479

480480
def _generate_chatbot_content(self, chatbot) -> List[str]:
481481
"""
482-
Generate content for a Markdown component.
482+
Generate content for a ChatBot component.
483483
484484
Parameters
485485
----------
486486
chatbot : ChatBot
487-
The chatbot component to generate content for.
487+
The ChatBot component to generate content for.
488488
489489
Returns
490490
-------
491491
list : List[str]
492-
The list of content lines for the chatbot.
492+
The list of content lines for the ChatBot.
493493
"""
494494
chatbot_content = []
495495

496496
# Add title
497497
chatbot_content.append(self._format_text(text=chatbot.title, type='header', level=4, color='#2b8cbe'))
498-
try:
499-
apicall_response = chatbot.get_chatbot_answer()
500-
chatbot_content.append(f"""st.write({apicall_response})\n""")
501-
except Exception as e:
502-
self.report.logger.error(f"Error generating content for APICall: {chatbot.title}. Error: {str(e)}")
503-
raise
504-
498+
499+
# Chatbot logic for embedding in the web application
500+
chatbot_content.append(f"""
501+
def generate_query(messages):
502+
response = requests.post(
503+
"{chatbot.api_call.api_url}",
504+
json={{"model": "{chatbot.model}", "messages": messages, "stream": True}},
505+
)
506+
response.raise_for_status()
507+
return response
508+
509+
def parse_api_response(response):
510+
try:
511+
output = ""
512+
for line in response.iter_lines():
513+
body = json.loads(line)
514+
if "error" in body:
515+
raise Exception(f"API error: {{body['error']}}")
516+
if body.get("done", False):
517+
return {{"role": "assistant", "content": output}}
518+
output += body.get("message", {{}}).get("content", "")
519+
except Exception as e:
520+
return {{"role": "assistant", "content": f"Error while processing API response: {{str(e)}}"}}
521+
522+
def response_generator(msg_content):
523+
for word in msg_content.split():
524+
yield word + " "
525+
time.sleep(0.1)
526+
yield "\\n"
527+
528+
# Chatbot interaction in the app
529+
if 'messages' not in st.session_state:
530+
st.session_state['messages'] = []
531+
532+
# Display chat history
533+
for message in st.session_state['messages']:
534+
with st.chat_message(message['role']):
535+
st.write(message['content'])
536+
537+
# Handle new input from the user
538+
if prompt := st.chat_input("Enter your prompt here:"):
539+
# Add user's question to the session state
540+
st.session_state.messages.append({{"role": "user", "content": prompt}})
541+
with st.chat_message("user"):
542+
st.write(prompt)
543+
544+
# Retrieve question and generate answer
545+
combined = "\\n".join(msg["content"] for msg in st.session_state.messages if msg["role"] == "user")
546+
messages = [{{"role": "user", "content": combined}}]
547+
with st.spinner('Generating answer...'):
548+
response = generate_query(messages)
549+
parsed_response = parse_api_response(response)
550+
551+
# Add the assistant's response to the session state and display it
552+
st.session_state.messages.append(parsed_response)
553+
with st.chat_message("assistant"):
554+
st.write_stream(response_generator(parsed_response["content"]))
555+
""")
556+
505557
# Add caption if available
506558
if chatbot.caption:
507-
chatbot_content.append(self._format_text(text=chatbot.caption, type='caption', text_align="left"))
559+
chatbot_content.append(self._format_text(text=chatbot.caption, type='caption', text_align="left"))
508560

509-
self.report.logger.info(f"Successfully generated content for APICall: '{chatbot.title}'")
561+
self.report.logger.info(f"Successfully generated content for ChatBot: '{chatbot.title}'")
510562
return chatbot_content
511563

512564
def _generate_component_imports(self, component: r.Component) -> List[str]:
@@ -531,7 +583,8 @@ def _generate_component_imports(self, component: r.Component) -> List[str]:
531583
r.PlotType.ALTAIR: ['import json', 'import altair as alt'],
532584
r.PlotType.PLOTLY: ['import json']
533585
},
534-
'dataframe': ['import pandas as pd']
586+
'dataframe': ['import pandas as pd'],
587+
'chatbot': ['import time', 'import json', 'import requests']
535588
}
536589

537590
component_type = component.component_type
@@ -544,6 +597,8 @@ def _generate_component_imports(self, component: r.Component) -> List[str]:
544597
component_imports.extend(components_imports['plot'][plot_type])
545598
elif component_type == r.ComponentType.DATAFRAME:
546599
component_imports.extend(components_imports['dataframe'])
600+
elif component_type == r.ComponentType.CHATBOT:
601+
component_imports.extend(components_imports['chatbot'])
547602

548603
# Return the list of import statements
549604
return component_imports

vuegen/utils.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def check_path(filepath: str) -> bool:
1212
"""
1313
Checks if the given file or folder path exists.
1414
15-
PARAMETERS
15+
Parameters
1616
---------
1717
filepath : str
1818
The file or folder path to check.
@@ -155,12 +155,12 @@ def load_yaml_config(file_path: str) -> dict:
155155
"""
156156
Load a YAML configuration file and return its contents as a dictionary.
157157
158-
PARAMETERS
158+
Parameters
159159
----------
160160
file_path : str
161161
The path to the YAML configuration file.
162162
163-
RETURNS
163+
Returns
164164
-------
165165
config : dict
166166
The contents of the YAML file as a dictionary.
@@ -199,7 +199,7 @@ def get_basename(fname: None | str = None) -> str:
199199
-----
200200
- basename of given filepath or the current file the function is executed
201201
202-
EXAMPLES
202+
Examples
203203
-----
204204
1)
205205
>>> get_basename()
@@ -223,17 +223,17 @@ def get_time(incl_time: bool = True, incl_timezone: bool = True) -> str:
223223
"""
224224
Gets current date, time (optional) and timezone (optional) for file naming
225225
226-
PARAMETERS
226+
Parameters
227227
-----
228228
- incl_time (bool): whether to include timestamp in the string
229229
- incl_timezone (bool): whether to include the timezone in the string
230230
231-
RETURNS
231+
Returns
232232
-----
233233
- fname (str): includes date, timestamp and/or timezone
234234
connected by '_' in one string e.g. yyyyMMdd_hhmm_timezone
235235
236-
EXAMPLES
236+
Examples
237237
-----
238238
1)
239239
>>> get_time()
@@ -286,12 +286,12 @@ def generate_log_filename(folder: str = "logs", suffix: str = "") -> str:
286286
"""
287287
Creates log file name and path
288288
289-
PARAMETERS
289+
Parameters
290290
-----
291291
folder (str): name of the folder to put the log file in
292292
suffix (str): anything else you want to add to the log file name
293293
294-
RETURNS
294+
Returns
295295
-----
296296
log_filepath (str): the file path to the log file
297297
"""
@@ -313,18 +313,18 @@ def init_log(filename: str, display: bool = False, logger_id: str | None = None)
313313
- Keeps a log record file of the python application, with option to
314314
display in stdout
315315
316-
PARAMETERS
316+
Parameters
317317
-----
318318
- filename (str): filepath to log record file
319319
- display (bool): whether to print the logs to whatever standard output
320320
- logger_id (str): an optional identifier for yourself,
321321
if None then defaults to 'root'
322322
323-
RETURNS
323+
Returns
324324
-----
325325
- logger object
326326
327-
EXAMPLE
327+
Examples
328328
-----
329329
>>> logger = init_log('logs/tmp.log', display=True)
330330
>>> logger.info('Loading things')

0 commit comments

Comments
 (0)