Skip to content

Commit

Permalink
FS-64: Add question suggestions generator from PoC repo
Browse files Browse the repository at this point in the history
  • Loading branch information
CLeopard99 committed Oct 28, 2024
1 parent 97a7e8a commit 6755e36
Show file tree
Hide file tree
Showing 14 changed files with 238 additions and 54 deletions.
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ WEB_AGENT_LLM="openai"
CHART_GENERATOR_LLM="openai"
ROUTER_LLM="openai"
FILE_AGENT_LLM="openai"
SUGGESTIONS_LLM="openai"

# model
ANSWER_AGENT_MODEL="gpt-4o mini"
Expand All @@ -57,6 +58,6 @@ WEB_AGENT_MODEL="gpt-4o mini"
CHART_GENERATOR_MODEL="gpt-4o mini"
ROUTER_MODEL="gpt-4o mini"
FILE_AGENT_MODEL="gpt-4o mini"

SUGGESTIONS_MODEL="gpt-4o mini"
REDIS_HOST="redis"
REDIS_CACHE_DURATION=3600
REDIS_CACHE_DURATION=3600
6 changes: 4 additions & 2 deletions backend/src/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .agent import Agent, agent
from .datastore_agent import DatastoreAgent
from .web_agent import WebAgent
from .intent_agent import IntentAgent
from .intent_agent import IntentAgent, read_file_core

Check failure on line 6 in backend/src/agents/__init__.py

View workflow job for this annotation

GitHub Actions / Type Checking Backend

"read_file_core" is unknown import symbol (reportAttributeAccessIssue)
from .tool import tool, Parameter
from .validator_agent import ValidatorAgent
from .answer_agent import AnswerAgent
Expand Down Expand Up @@ -33,7 +33,8 @@ def agent_details(agent) -> dict:
def get_available_agents() -> List[Agent]:
return [DatastoreAgent(config.datastore_agent_llm, config.datastore_agent_model),
WebAgent(config.web_agent_llm, config.web_agent_model),
ChartGeneratorAgent(config.chart_generator_llm, config.chart_generator_model),
ChartGeneratorAgent(config.chart_generator_llm,
config.chart_generator_model),
FileAgent(config.file_agent_llm, config.file_agent_model),
# FS-63 Silencing Math agent - tool is not optimised.
# MathsAgent(config.maths_agent_llm, config.maths_agent_model),
Expand All @@ -56,4 +57,5 @@ def get_agent_details():
"get_validator_agent",
"Parameter",
"tool",
"read_file_core",
]
14 changes: 14 additions & 0 deletions backend/src/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from src.websockets.connection_manager import connection_manager, parse_message
from src.session import RedisSessionMiddleware
from src.utils.cyper_import_data_from_csv import import_data_from_csv_script
from src.suggestions_generator import generate_suggestions

config_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "config.ini"))
logging.config.fileConfig(fname=config_file_path, disable_existing_loggers=False)
Expand Down Expand Up @@ -66,6 +67,7 @@ async def lifespan(app: FastAPI):
unhealthy_neo4j_response = health_prefix + "backend is healthy. Neo4J is unhealthy. " + further_guidance

chat_fail_response = "Unable to generate a response. Check the service by using the keyphrase 'healthcheck'"
suggestions_failed_response = "Unable to generate suggestions. Check the service by using the keyphrase 'healthcheck'"


@app.get("/health")
Expand All @@ -91,6 +93,18 @@ async def chat(utterance: str):
return JSONResponse(status_code=500, content=chat_fail_response)


@app.get("/suggestions")
async def suggestions():
logger.info("Requesting chat suggestions")
try:
final_result = await generate_suggestions()
logger.info(f"Chat suggestions: {final_result}")
return JSONResponse(status_code=200, content=final_result)
except Exception as e:
logger.exception(e)
return JSONResponse(status_code=500, content=suggestions_failed_response)


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> NoReturn:
await connection_manager.connect(websocket)
Expand Down
32 changes: 23 additions & 9 deletions backend/src/prompts/templates/director.j2
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
You are a Chat Bot called InferESG.
Your sole purpose is to get the user to tell you their intention. The intention has to be specifically related
to the user. For example:
-spending
-personal interests
Your sole purpose is to assist the user in understanding sustainabliity of companies through ESG (Environment, Social, Governance) reporting. The users queries should be specifically related
to a investment fund, company or industry.

You only reply with either one word "TRUE" or one word "FALSE"
For background ESG is a measured assessment of sustainability using benchmarks and metrics of operating a business in a way that meets the economic, social and environmental needs.

Companies will report in the three key areas, environment, social and governance. Further details on each key area are:
- Environmental: How a company performs as a steward of nature. This includes climate change efforts, resource depletion, waste, pollution, deforestation, and other environmental impacts. Factors refer to an organisation's environmental impact and risk management practices. These include direct and indirect greenhouse gas emissions, management's stewardship over natural resources, and the firm's overall resiliency against physical climate risks like climate change, flooding, and fires.
- Social: How a company manages relationships with employees, suppliers, customers, and the communities where it operates. Firms may be measured against human capital management (HCM) like fair wages and employee engagement, also community impact.
- Governance: How a company's leadership, executive pay, audits, internal controls, and shareholder rights are handled. Corporate Governance refers to how an organisation is led and managed. ESG analysts will seek to understand better how leadership's incentives are aligned with stakeholder expectations, how shareholder rights are viewed and honoured, and what types of internal controls exist to promote transparency and accountability on the part of leadership.

You understand the term Greenwashing is when an organisation makes false, unsubstantiated, or outright misleading statements or claims about the sustainability of a product or a service, or even about business operations more broadly and specialise in detecting inaccuracies in reporting and datasets. Examples of greenwashing are:
- Hidden trade-offs: Where a firm may emphasize that a product is produced or packaged using recycled materials, however, they neglect to mention that it was sourced from a supplier with a history or coercive labour practices or humanitarian issues.
- Baseless claims: For instance, a company advertises a product as being "ethically sourced" but is unable to provide specific information about its procurement or about how it arrived at that conclusion.
- Presentation of partial truths: Consider an oil & gas producer that presents its 5-year emissions trend as declining. Of course, that's good, but it only captures that firm's scope 1 emissions (those it produces directly); this disclosure neglects to address the eventual combustion of that fuel downstream by the eventual consumer (scope 3 emissions).

If the user does not provide an intention or the intention isn't directly related to the user,
You only reply with either one word "TRUE" or one word "FALSE"
If the user does not ask a question related to a investment fund, company, or industry about ESG (Environment, Social, Governance) data or scoring or greenwashing then,
reply with the single word "FALSE"

Otherwise reply with the single word "TRUE"

Eg.
An intention: (Reply: "TRUE")
A valid question related to ESG or Greenwashing: (Reply: "TRUE")
What are ryanair's esg scores for January 2023?
Can you compare ryanair and easyjet esg scores.
Regarding esg scores, how many companies do you have scores for in the Aviation Industry?
Regarding esg scores for the Aviation Industry, and you give me the scores for each company.
Regarding the Aviation industry and Construction industry, which has the best esg scores?

Not a valid question: (Reply: "FALSE")
I want to save for a house
How much did I spend last month?
What did I spend on chocolate?

Not an intention: (Reply: "FALSE")
How are you?
How many grams are there in an ounce?
22 changes: 22 additions & 0 deletions backend/src/prompts/templates/generate_message_suggestions.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
You are part of an AI-powered application that assists users in understanding the sustainability of companies through ESG (Environment, Social, Governance) reporting. The application has access to a database that contains ESG scores of various funds, companies and the industries they operate. The application also is able to search the internet and retrieve relevant articles.

Your purpose is to suggest the user with possible questions they could ask the main Chat Bot, based on the conversation history. You are provided only with the last few messages and your suggestions should be logical follow-up questions to the conversation. Your suggestions should not include questions that have already been asked.

The conversation history is:
{{ chat_history }}

Here are some examples of questions you could suggest:
- (Assuming the user was talking about Ryanair) Can you compare the ESG scores of Ryanair and EasyJet?
- What is the average ESG score of the companies in the Construction industry?
- (Assuming the user was talking about ExxonMobil) Can you find articles about ExxonMobil's ESG practices?
- (Assuming the user previously asked about a company in the Energy industry) What are the average ESG scores of companies in the Energy industry?

Your response must be given in JSON with the following format.
{
"suggestions": [
"suggested question 1",
"suggested question 2",
"suggested question 3",
"suggested question 4"
]
}
24 changes: 5 additions & 19 deletions backend/src/prompts/templates/validator.j2
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,14 @@ Task: What is 2 + 2?
Answer: 5
Response: False

Task: Find all spending transactions last month on Amazon.
Answer: Last month you spend £64.21 on Amazon
Task: What are Apple's ESG scores?
Answer: Apple's ESG (Environmental, Social, and Governance) scores area as follows: an Environmental Score of 95.0, a Social Score of 90.0, and a Governance Score of 92.0.
Response: True

Task: Find all spending transactions last month on Amazon.
Answer: Last month you spend £64.21 on Spotify
Task: What are Apple's ESG scores?
Answer: Microsoft's ESG (Environmental, Social, and Governance) scores area as follows: an Environmental Score of 95.0, a Social Score of 90.0, and a Governance Score of 92.0.
Response: False
Reasoning: The answer is for Spotify not Amazon.

Task: Please find tesla's revenue every year since its creation.
Answer: Tesla's annual revenue history from FY 2008 to FY 2023 is available, with figures for 2008 through 2020 taken from previous annual reports.
Response: False
Reasoning: The answer is not providing any actual figures but just talk about the figures.

Task: Please find tesla's revenue every year since its creation in the US dollars.
Answer: Tesla's annual revenue in USD since its creation is as follows: 2024 (TTM) $75.92 billion, 2023 $75.95 billion, 2022 $67.33 billion, 2021 $39.76 billion, 2020 $23.10 billion, 2019 $18.52 billion, 2018 $16.81 billion, 2017 $8.70 billion, 2016 $5.67 billion, 2015 $2.72 billion, 2014 $2.05 billion, 2013 $1.21 billion, 2012 $0.25 billion, 2011 $0.13 billion, 2010 $75.88 million, 2009 $69.73 million.
Response: False
Reasoning: The answer is providing the revenue in GBP not USD.

Task: Round the following numbers to the nearest million dollars: 96.77B, 81.46B, 53.82B, 31.54B, 24.58B, 21.46B
Answer: 96,770 million, 81,460 million, 53,820 million, 31,540 million, 24,580 million, 21,460 million
Reponse: True
Reasoning: The answer is for Microsoft not Apple.

You must always return a single boolean value as the response.
Do not return any additional information, just the boolean value.
Expand Down
66 changes: 66 additions & 0 deletions backend/src/suggestions_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import json
from typing import List
from src.llm.factory import get_llm
from src.prompts.prompting import PromptEngine
from src.session import Message, get_session_chat
from src.utils.config import Config

import logging

config = Config()
engine = PromptEngine()
logger = logging.getLogger(__name__)
suggestions_prompt = engine.load_prompt("generate_message_suggestions")
model = config.suggestions_model


async def generate_suggestions() -> List[str]:
llm = get_llm(config.suggestions_llm)
model = get_suggestions_model()
chat_history = get_chat_history()
suggestions_prompt = engine.load_prompt(
"generate_message_suggestions", chat_history=chat_history)
response = await llm.chat(model, suggestions_prompt, user_prompt="Give me 5 suggestions.", return_json=True)
try:
response_json = json.loads(response)
except json.JSONDecodeError:
response_json = {"suggestions": []}
return response_json["suggestions"]


def get_suggestions_model() -> str:
model = config.suggestions_model
if model is None:
raise ValueError("No model name found for the Suggestions LLM.")
return model


def get_chat_history() -> List[str] | str:
MAX_HISTORY_LENGTH = 4

Check failure on line 39 in backend/src/suggestions_generator.py

View workflow job for this annotation

GitHub Actions / Linting Backend

Ruff (N806)

backend/src/suggestions_generator.py:39:5: N806 Variable `MAX_HISTORY_LENGTH` in function should be lowercase
raw_history = get_session_chat()
logger.info(f"Raw history: {raw_history}")
if raw_history is None:
return "No chat history available."

if len(raw_history) > MAX_HISTORY_LENGTH:
raw_history = raw_history[-MAX_HISTORY_LENGTH:]

natural_language_history = remove_datasets_from_history(raw_history)
return natural_language_history


def remove_datasets_from_history(history: list[Message]) -> List[str]:
filtered = []
for message in history:
if message["role"] == "user":
filtered.append(f"User: {message['content']}")
else:
if message["content"] is not None:
try:
natural_language_answer = json.loads(message["content"])
filtered.append(
f"System: {natural_language_answer['final_answer']}")
except:

Check failure on line 63 in backend/src/suggestions_generator.py

View workflow job for this annotation

GitHub Actions / Linting Backend

Ruff (E722)

backend/src/suggestions_generator.py:63:17: E722 Do not use bare `except`
filtered.append(f"System: {message['content']}")

return filtered
4 changes: 4 additions & 0 deletions backend/src/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__(self):
self.chart_generator_llm = None
self.file_agent_llm = None
self.router_llm = None
self.suggestions_llm = None
self.validator_agent_model = None
self.intent_agent_model = None
self.answer_agent_model = None
Expand All @@ -41,6 +42,7 @@ def __init__(self):
self.file_agent_model = None
self.redis_host = default_redis_host
self.redis_cache_duration = default_redis_cache_duration
self.suggestions_model = None
self.load_env()

def load_env(self):
Expand Down Expand Up @@ -70,6 +72,7 @@ def load_env(self):
self.web_agent_llm = os.getenv("WEB_AGENT_LLM")
self.maths_agent_llm = os.getenv("MATHS_AGENT_LLM")
self.router_llm = os.getenv("ROUTER_LLM")
self.suggestions_llm = os.getenv("SUGGESTIONS_LLM")
self.answer_agent_model = os.getenv("ANSWER_AGENT_MODEL")
self.intent_agent_model = os.getenv("INTENT_AGENT_MODEL")
self.validator_agent_model = os.getenv("VALIDATOR_AGENT_MODEL")
Expand All @@ -81,6 +84,7 @@ def load_env(self):
self.file_agent_model = os.getenv("FILE_AGENT_MODEL")
self.redis_host = os.getenv("REDIS_HOST", default_redis_host)
self.redis_cache_duration = os.getenv("REDIS_CACHE_DURATION", default_redis_cache_duration)
self.suggestions_model = os.getenv("SUGGESTIONS_MODEL")
except FileNotFoundError:
raise FileNotFoundError("Please provide a .env file. See the Getting Started guide on the README.md")
except Exception:
Expand Down
3 changes: 3 additions & 0 deletions frontend/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Ignore following files:
**/*.html
**/*.json
2 changes: 1 addition & 1 deletion frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const App = () => {
return (
<div className={styles.container}>
<Chat messages={messages} waiting={waiting} />
<Input sendMessage={sendMessage} />
<Input sendMessage={sendMessage} waiting={waiting} />
</div>
);
};
45 changes: 25 additions & 20 deletions frontend/src/components/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import React, {
import styles from './input.module.css';
import RightArrow from '../icons/map-arrow-right.svg';
import classNames from 'classnames';
import { Suggestions } from './suggestions';

export interface InputProps {
sendMessage: (message: string) => void;
waiting: boolean;
}

export const Input = ({ sendMessage }: InputProps) => {
export const Input = ({ sendMessage, waiting }: InputProps) => {
const [userInput, setUserInput] = useState<string>('');

const onChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
Expand All @@ -32,24 +34,27 @@ export const Input = ({ sendMessage }: InputProps) => {
const sendDisabled = useMemo(() => userInput.length === 0, [userInput]);

return (
<form onSubmit={onSend} className={styles.inputContainer}>
<input
className={styles.input}
onChange={onChange}
onSubmit={onSend}
placeholder="Send a Message..."
type="text"
value={userInput}
/>
<button
className={classNames(styles.sendButton, {
[styles.disabled]: sendDisabled,
})}
onClick={onSend}
disabled={sendDisabled}
>
<img src={RightArrow} />
</button>
</form>
<>
<form onSubmit={onSend} className={styles.inputContainer}>
<input
className={styles.input}
onChange={onChange}
onSubmit={onSend}
placeholder="Send a Message..."
type="text"
value={userInput}
/>
<button
className={classNames(styles.sendButton, {
[styles.disabled]: sendDisabled,
})}
onClick={onSend}
disabled={sendDisabled}
>
<img src={RightArrow} />
</button>
</form>
<Suggestions loadPrompt={setUserInput} waiting={waiting} />
</>
);
};
20 changes: 20 additions & 0 deletions frontend/src/components/suggestions.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.container {
align-items: stretch;
display: flex;
flex-direction: row;
width: 100%;
justify-content: space-evenly;
cursor: pointer;
}

.container > p {
background-color: color-mix(
in srgb,
transparent,
var(--text-color-primary) 7%
);
margin: 15px 5px;
padding: 15px;
border: 1px solid var(--border-primary);
border-radius: 5px;
}
Loading

0 comments on commit 6755e36

Please sign in to comment.