Skip to content

Commit

Permalink
Adding Geo Location and improve internet responses
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Saporetti Junior committed Mar 28, 2024
1 parent 580bd0d commit 6857592
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 34 deletions.
10 changes: 9 additions & 1 deletion docs/devel/askai-questions.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Questions:

en_US

1. Summarize my markdown files at my HomeSetup docs folder.
2. What are the current weather conditions in San Francisco, U.S today?
3. When is the upcoming Los Angeles Lakers match?
4. Who currently holds the office of President of the United States?
4. Who currently holds the office of President of the United States?

pt_BR

1. Qual a previsao do tempo hoje para Belo Horizonte?
2. Quem e o atual presidente do Brasil?
3. Quem e Hugo Saporetti junior?
20 changes: 14 additions & 6 deletions src/demo/components/internet-demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,33 @@
Copyright·(c)·2024,·HSPyLib
"""
from askai.core.component.cache_service import cache
from askai.core.component.internet_service import internet
from askai.core.support.shared_instances import shared
import logging as log
import re

from clitt.core.tui.line_input.line_input import line_input
from hspylib.core.tools.commons import log_init, sysout

import logging as log
from askai.core.component.cache_service import cache
from askai.core.component.geo_location import geo_location
from askai.core.component.internet_service import internet
from askai.core.model.search_result import SearchResult
from askai.core.support.shared_instances import shared

if __name__ == "__main__":

log_init("internet-demo.log", level=log.INFO)
cache.read_query_history()
sysout("-=" * 40)
sysout("AskAI Internet Demo")
sysout("-=" * 40)
shared.create_engine(engine_name="openai", model_name="gpt-3.5-turbo")
urls = ["https://www.accuweather.com"]
sysout(f"READY to search")
sysout("--" * 40)

while (query := line_input("You: ")) not in ["exit", "q", "quit"]:
answer = internet.search_google(query, *urls)
kw: list[str] = re.split('[ ,;]', query)
sites: list[str] = ['accuweather.com', 'weather.com']
q = SearchResult(query, geo_location.now, kw, sites)
answer = internet.search_google(q)
sysout(f"%GREEN%AI: {answer}")
cache.save_query_history()
97 changes: 97 additions & 0 deletions src/main/askai/core/component/geo_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import json
import logging as log
from datetime import datetime
from json import JSONDecodeError
from textwrap import dedent

import pytz
from hspylib.core.metaclass.singleton import Singleton
from hspylib.core.namespace import Namespace
from hspylib.modules.fetch import fetch
from requests.exceptions import ConnectionError


class GeoLocation(metaclass=Singleton):
"""TODO"""

INSTANCE: 'GeoLocation' = None

GEO_LOC_URL: str = "http://ip-api.com/json"

EMPTY_JSON_RESP: str = dedent('''
{
"status": "failure", "country": "", "countryCode": "", "region": "", "regionName": "",
"city": "", "zip": "", "lat": 0.0, "lon": 0.0, "timezone": "",
"isp": "", "org": "", "as": "", "query": ""
}
''')

# Date format used in prompts, e.g: Fri 22 Mar 19:47 2024.
DATE_FMT: str = "%a %d %b %-H:%M %Y"

@classmethod
def get_location(cls, ip: str = None) -> Namespace:
"""TODO"""
try:
url = f"{cls.GEO_LOC_URL}{'/' + ip if ip else ''}"
log.debug('Fetching the Geo Position from: %s', url)
geo_req = fetch.get(url)
except (JSONDecodeError, ConnectionError) as err:
log.error('Failed to retrieve geo location => %s', str(err))
geo_req = Namespace(body=cls.EMPTY_JSON_RESP)
geo_json = json.loads(geo_req.body)
geo_location: Namespace = Namespace(**geo_json)
return geo_location

def __init__(self, ip: str = None):
self._geo_location = self.get_location(ip)

def __str__(self):
geo_loc = self._geo_location
geo_loc.setattr('zoned_datetime', self.now)
return str(self._geo_location)

@property
def latitude(self) -> float:
return self._geo_location.lat

@property
def longitude(self) -> float:
return self._geo_location.lon

@property
def country(self) -> str:
return self._geo_location.country

@property
def country_code(self) -> str:
return self._geo_location.country_code

@property
def region(self) -> str:
return self._geo_location.region

@property
def region_name(self) -> str:
return self._geo_location.region_name

@property
def city(self) -> float:
return self._geo_location.city

@property
def zip(self) -> str:
return self._geo_location.zip

@property
def timezone(self) -> str:
return self._geo_location.timezone

@property
def now(self) -> str:
utc_datetime = datetime.utcnow().replace(tzinfo=pytz.utc)
zoned_datetime = utc_datetime.astimezone(pytz.timezone(self.timezone))
return zoned_datetime.strftime(GeoLocation.DATE_FMT)


assert (geo_location := GeoLocation().INSTANCE) is not None
20 changes: 17 additions & 3 deletions src/main/askai/core/component/internet_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,35 @@ def __init__(self):

def search_google(self, search: SearchResult) -> Optional[str]:
"""Search the web using google search API.
Google search operators: https://ahrefs.com/blog/google-advanced-search-operators/
:param search: The AI search parameters.
"""
query = '+'.join(search.keywords)
if len(search.sites) > 0:
search_results: List[Document] = []
websites: str = ' OR '.join(['site: ' + url for url in search.sites])
query = self._build_query(search.keywords, search.filters, search.sites)
AskAiEvents.ASKAI_BUS.events.reply.emit(message=msg.searching())
log.info("Searching GOOGLE for '%s' url: '%s'", query, str(', '.join(search.sites)))
content = str(self._tool.run(f"{query} {websites}"))
content = str(self._tool.run(query))
search_results.append(Document(content))
prompt = ChatPromptTemplate.from_messages([("system", "{query}\n\n{context}")])
chain = create_stuff_documents_chain(lc_llm.create_chat_model(), prompt)
return chain.invoke({"query": query, "context": search_results})

return None

def _build_query(self, keywords: List[str], filters: List[str], sites: List[str]) -> str:
"""TODO"""
query = ''
# Weather is a filter that does not require any other search parameter.
if filters and any(f.find("weather:") >= 0 for f in filters):
return ' AND '.join(filters)
if sites:
query += ' OR '.join(['site:' + url for url in sites])
if filters and any(f.find("people:") >= 0 for f in filters):
query += f" intext:\"{' + '.join([f.split(':')[1] for f in filters])}\" "
if keywords:
query += ' + '.join(keywords)
return query


assert (internet := InternetService().INSTANCE) is not None
1 change: 1 addition & 0 deletions src/main/askai/core/model/search_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class SearchResult:
datetime: str = None
keywords: List[str] = None
sites: str | List[str] = None
filters: List[str] = None
response: str = None

def __str__(self):
Expand Down
5 changes: 3 additions & 2 deletions src/main/askai/core/processor/instances/generic_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from askai.core.askai_messages import msg
from askai.core.askai_prompt import prompt
from askai.core.component.cache_service import cache
from askai.core.component.geo_location import geo_location
from askai.core.engine.openai.temperatures import Temperatures
from askai.core.model.chat_context import ContextRaw
from askai.core.model.processor_response import ProcessorResponse
Expand Down Expand Up @@ -69,10 +70,10 @@ def template(self) -> str:
def process(self, query_response: ProcessorResponse) -> Tuple[bool, Optional[str]]:
status = False
template = PromptTemplate(input_variables=['user', 'datetime', 'idiom'], template=self.template())
final_prompt: str = template.format(user=prompt.user, datetime=shared.now, idiom=shared.idiom)
final_prompt: str = template.format(user=prompt.user, datetime=geo_location.now, idiom=shared.idiom)
shared.context.set("SETUP", final_prompt, "system")
shared.context.set("QUESTION", f"\n\nQuestion: {query_response.question}\n\nHelpful Answer:")
context: ContextRaw = shared.context.join("GENERAL", "SETUP", "QUESTION")
context: ContextRaw = shared.context.join("SETUP", "GENERAL", "QUESTION")
log.info("Setup::[GENERIC] '%s' context=%s", query_response.question, context)

if (response := shared.engine.ask(context, *Temperatures.CREATIVE_WRITING.value)) and response.is_success:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from askai.core.askai_messages import msg
from askai.core.askai_prompt import prompt
from askai.core.component.cache_service import cache
from askai.core.component.geo_location import geo_location
from askai.core.component.internet_service import internet
from askai.core.engine.openai.temperatures import Temperatures
from askai.core.model.chat_context import ContextRaw
Expand Down Expand Up @@ -60,7 +61,7 @@ def template(self) -> str:
def process(self, query_response: ProcessorResponse) -> Tuple[bool, Optional[str]]:
status = False
template = PromptTemplate(input_variables=['idiom', 'datetime'], template=self.template())
final_prompt: str = template.format(idiom=shared.idiom, datetime=shared.now)
final_prompt: str = template.format(idiom=shared.idiom, datetime=geo_location.now)
shared.context.set("SETUP", final_prompt, "system")
shared.context.set("QUESTION", f"\n\nQuestion: {query_response.question}\n\nHelpful Answer:")
context: ContextRaw = shared.context.join("SETUP", "QUESTION")
Expand All @@ -74,6 +75,7 @@ def process(self, query_response: ProcessorResponse) -> Tuple[bool, Optional[str
output = response.message.strip()
else:
if output := internet.search_google(search):
output = msg.translate(output)
shared.context.push("GENERAL", query_response.question)
shared.context.push("GENERAL", output, "assistant")
cache.save_reply(query_response.question, output)
Expand Down
8 changes: 0 additions & 8 deletions src/main/askai/core/support/shared_instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from clitt.core.tui.line_input.line_input import line_input
from hspylib.core.metaclass.singleton import Singleton
from hspylib.core.preconditions import check_state
from hspylib.core.zoned_datetime import now
from hspylib.modules.cli.keyboard import Keyboard

from askai.core.askai_configs import configs
Expand All @@ -26,9 +25,6 @@ class SharedInstances(metaclass=Singleton):
# This is the uuid used in prompts that require internet.
INTERNET_ID: str = 'e35057db-f690-4299-ad4d-147d6124184c'

# Date format used in prompts, e.g: Fri 22 Mar 19:47 2024.
DATE_FMT: str = "%a %d %b %-H:%M %Y"

def __init__(self) -> None:
self._engine: Optional[AIEngine] = None
self._context: Optional[ChatContext] = None
Expand Down Expand Up @@ -64,10 +60,6 @@ def nickname(self) -> str:
def username(self) -> str:
return f"%WHITE% {prompt.user.title()}%NC%"

@property
def now(self) -> str:
return now(self.DATE_FMT)

def create_engine(self, engine_name: str, model_name: str) -> AIEngine:
"""TODO"""
if self._engine is None:
Expand Down
10 changes: 3 additions & 7 deletions src/main/askai/resources/assets/prompts/generic-prompt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,11 @@ Before responding to the user, it is imperative that you follow the step-by-step

1. Review the entire conversation history thoroughly, but refrain from generating a response at this time.

2. Determine if you can craft your response using solely the information available in the conversation history.
2. Determine if you can craft your response using the conversation history or using the information available in your database. Remember that your training data includes information up to January 2022, and we are currently in: '{datetime}'.

3. Determine if you can craft your response from your database. Remember that your training data includes information up to January 2022, and we are currently in: '{datetime}'.
3. If you possess pertinent information rather than responses like 'I don't have details...' or 'I lack information about...', please proceed with completing all tasks delineated in 'WORKFLOW 1'.

4. If you're certain of the accuracy of your response, proceed with all tasks outlined in "WORKFLOW 1".

5. If you're unsure or lack an answer, please refrain from guessing. Instead, proceed with all tasks outlined in "WORKFLOW 2".

6. Ensure completion of all tasks assigned to you in the previous steps, following the designated workflow.
4. If you don't know the answer, or if you don't have a useful answer, please refrain from guessing. Instead, proceed with all tasks outlined in "WORKFLOW 2".


"WORKFLOW 1":
Expand Down
12 changes: 6 additions & 6 deletions src/main/askai/resources/assets/prompts/internet-prompt.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Act as an internet facilitator by performing tasks such as web crawling, page scraping, and conducting google searches. Respond consistently in the following idiom: '{idiom}'.
Act as an internet facilitator by performing tasks such as web crawling, page scraping, and conducting Google searches. Respond consistently in the following idiom: '{idiom}'.

Before responding to the user, it is imperative that you follow the step-by-step instructions provided below in sequential order:
Before responding to the user, you must follow the step-by-step instructions provided below in sequential order:

1. Interpret the question thoroughly to discern the user's underlying intent.

Expand All @@ -10,16 +10,16 @@ Before responding to the user, it is imperative that you follow the step-by-step

4. Locate credible sources relevant to '{idiom}' to collect essential information for creating a thoroughly researched response. Offer a curated list comprising a minimum of three and a maximum of five website URLs tailored to the user's locale. Please include only the base URL of each website without specifying any particular paths.

5. If the inquiry pertains to programming languages, operating systems, or other IT topics, append 'stackoverflow.com' and 'github.com' to your list.
5. If the inquiry pertains to programming languages, operating systems, or other IT topics, append 'stackoverflow.com' and 'github.com' to your sites list.

6. If the inquiry pertains to whether forecast or related topics, append 'accuweather.com' to your list.
6. If the inquiry pertains to general information about individuals who are not notable, extract the name of the person mentioned in the prompt and append this filter: 'people:name' to your list. Where name represents the extracted person's name. In addition to that, append 'linkedin.com', 'facebook.com', and 'instagram.com' to your sites list.

7. If the inquiry pertains to general information about individuals who are not notable, append 'linkedin.com', 'facebook.com', and 'instagram.com' to your list.
7. If the inquiry pertains to weather forecasts or related topics, extract the city location mentioned in the prompt and append this filter: 'weather:city' to your list. Where city represents the extracted city name.

8. The final response is a formatted JSON with no additional description or context.

9. Do not use markdown to format the response message. Use plain JSON.

10. The final response 'JSON' must contain the string fields: 'question', 'datetime'.

11. The final response 'JSON' must contain the array fields, 'keywords' and 'sites'.
11. The final response 'JSON' must contain the array fields, 'keywords', 'filters', and 'sites'.

0 comments on commit 6857592

Please sign in to comment.