These are the slides and notebook I’ve used during my talk on how to build an Internet-connected search assistant almost from scratch. AKA Poor Man’s BingChat.
First time I talked about it was at Codecamp Iasi, where it’s gotten a lot of positive feedback, plus it was awesome to share the stage with established speakers (and personal heroes of mine) like Mark Richards, Venkat Subramaniam, Eoin Woods, and Dylan Beattie. Yes, you can see them in the hero picture 😱.
I’ve also decided to leave the outputs as they were at Codecamp, if only to give you an idea of what things looked like during the talk.
Update 2023-11-27: Due to “APIConnectionError: Error communicating with OpenAI: No connection adapters were found for …” errors when trying to call Azure OpenAI instances using openai==0.28.1
😒, I’ve updated the library and code to openai==1.3.5
.
Slides
The Code
Environment
Before you do anything, create and activate a new virtual environment. Personally, I’m using miniconda with Python 3.10, but if venv is more of your thing then go ahead and use that.
Then, it’s just a matter of creating a requirements.txt
file with the contents below, and then pip install -r requirements.txt
. Make sure to run the notebook using the environment you’ve just created.
tiktoken==0.5.1
openai==1.3.5
html2text==2020.1.16
python-dotenv==1.0.0
beautifulsoup4==4.12.2
Now it’s off to the races. Here’s the notebook:
Imports
import requests, json
from datetime import datetime
import urllib.request
import html2text
import tiktoken
import openai
from bs4 import BeautifulSoup
from dotenv import dotenv_values
Setup
You’ll need to create an .env
file containing your Azure OpenAI API key, the endpoint, api version, and model name. It should look like this:
OPENAI_API_KEY="<API-KEY>"
OPENAI_API_BASE="https://<BASE-ENDPOINT>.openai.azure.com/"
OPENAI_MODEL_NAME="<MODEL-NAME>"
OPENAI_API_VERSION="2023-07-01-preview"
Then we’ll just load it using python-dotenv
and not bother with environment variables and all of that.
config = dotenv_values(".env")
client = AzureOpenAI(
api_key=config["OPENAI_API_KEY"],
azure_endpoint=config["OPENAI_API_BASE"],
api_version=config["OPENAI_API_VERSION"])
encoding = tiktoken.encoding_for_model('gpt-4')
openai_chatmodel = config["OPENAI_MODEL_NAME"]
openai_embeddingsmodel="embeddings"
def ask(message: str):
completion = client.chat.completions.create(
model=openai_chatmodel,
messages=[{"role": "user", "content": message}]
)
return completion.choices[0].message.content
def token_length(text: str):
return len(encoding.encode(text))
Quick test
query = 'Give me the list of speakers for Codecamp Iasi 2023'
print(ask(query))
I'm sorry, as an AI developed by OpenAI, I currently don't have real-time information or the ability to browse the internet to provide updates on specific events, including the speakers list for Codecamp Iasi 2023. Please check the official event webpage or other resources for this information.
The process
Step 1. Optimize the query
optimized_query = ask(f"""
Convert the instruction surrounded by triple backticks into the best
Google query you can think of, one that would return ideal results
when used with Google's `I'm feeling lucky` button.
Don't format it in any way, not even surrounding it by double quotes.
Just return the raw query.
```{query}```""")
print(f'Using "{optimized_query}" instead of "{query}"')
Using "Codecamp Iasi 2023 speakers list" instead of "Give me the list of speakers for Codecamp Iasi 2023"
Step 2. Scrape Google results
def get_google_results_for(query):
"""
mostly borrowed from https://stackoverflow.com/a/65564157
"""
encoded_query = urllib.parse.urlencode({'q': query})
url = f'https://google.com/search?{encoded_query}'
request = urllib.request.Request(url)
request.add_header('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36')
raw_response = urllib.request.urlopen(request).read()
html = raw_response.decode("utf-8")
soup = BeautifulSoup(html, 'html.parser')
divs = soup.select("#search div.g")
links = []
for div in divs:
results = div.select("a h3")
if len(results) == 0:
continue
h3 = results[0]
a = h3.find_parent()
title = h3.get_text()
url = a.attrs['href']
links.append({"title": title, "url": url} )
return links
links = get_google_results_for(optimized_query)
for link in links:
print(f'{link["title"]} \n\t{link["url"]}')
url = links[0]["url"]
title = links[0]["title"]
Codecamp_Iasi - Codecamp
https://codecamp.ro/conferences/codecamp_iasi/
Index of /codecamp.ro/
https://www.codecamp.ro/codecamp.ro/
Codecamp: Home
https://codecamp.ro/
Codecamp Iasi (Oct 2023), Iași Romania - Conference
https://10times.com/codecamp-iasi
Codecamp_Iasi - Registration
https://registration.socio.events/e/codecampiasi2023
Codecamp Romania
https://www.facebook.com/CodecampRO/?locale=ro_RO
Codecamp Iasi Romania 2023 - YouTube
https://www.youtube.com/watch?v=Utem39pG_9s
Anunț publicat de Dan Zaharia
https://ro.linkedin.com/posts/danzaharia_o-veste-bun%C4%83-amazon-se-extinde-la-ia%C5%9Fi-%C5%9Fi-activity-6490868702546923520-GsiK?trk=public_profile_like_view
Voxxed Days Iași 2023
https://romania.voxxeddays.com/voxxed-days-iasi-2023/
Step 3. Get the “best” result
def load_page_content(url):
print(f'Downloading {url} ...')
response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'})
page_content = response.content.decode('utf-8')
text_processor = html2text.HTML2Text()
text_processor.ignore_links = True
text_processor.ignore_images = True
text_processor.ignore_mailto_links = True
text_processor.bypass_tables = False
page_content_md = text_processor.handle(page_content)
print('Done')
return page_content, page_content_md
page_content, page_content_md = load_page_content(url)
Downloading https://codecamp.ro/conferences/codecamp_iasi/ ...
Done
Stats
print(f"""
==============## 💡=======
Unprocessed Page:
{len(page_content):,} bytes
{token_length(page_content):,} tokens
=====================
Processed Page:
{len(page_content_md):,} bytes
{token_length(page_content_md):,} tokens
=====================
Processed Page Content:
{page_content_md}
""")
==============## 💡=======
Unprocessed Page:
531,435 bytes
147,967 tokens
=====================
Processed Page:
26,251 bytes
5,759 tokens
=====================
Processed Page Content:
Skip to content
Youtube __ Linkedin __ Facebook __ Instagram __ Twitter __
the festival
burger_menu-btn
Conference
# Codecamp_Iasi
__
26 October 2023
* __ Iasi
* __ Ticket: 99 euro
register
watch now
## Mark Richards
## Hands-On Software Architect, Independent Consultant, Author
## Venkat Subramaniam
## Award-winning author, founder of Agile Developer, Inc.
## Eoin Woods
## Chief Engineer, Endava
## Alexandru Andriesei
## Senior Engineering Manager, Tremend
## Dylan Beattie
## Technology strategist, Director, Ursatile Ltd.
## Vlad Iliescu
## Microsoft MVP on AI
## The speakers
book now
## Fundamentals of Software Architecture
By Mark Richards
24
\- 25 October 2023
## Hotel International, Iasi
__
## Distributed Systems with C# and .NET
By Dylan Beattie
24
\- 25 October 2023
## Hotel International, Iasi
__
## Towards a Better Code Quality: Ways to Improve
By Venkat Subramaniam
27 October 2023
## Hotel International, Iasi
__
## Masterclasses
These high-end workshops allow you to dive deeper into specific topics related
to software development. The masterclasses are taught by experts in the field
and offer a more personalized and interactive learning experience. You get to
work closely with the instructor and other colleagues in a small-group setting
and ask questions and get feedback in real time. Overall, they are a unique
and valuable opportunity for anyone looking to expand their knowledge and
expertise in software development and IT.
book now
Agenda
9:45 - 10:00
Hello, Iasi!
* __ 26/10/2023
* __ 9:45 - 10:00
## Hello, Iasi!
Download presentation
10:00 - 10:45
Don't Walk Away From Complexity, Run
* __ 26/10/2023
* __ 10:00 - 10:45
## Don't Walk Away From Complexity, Run
We constantly hear that change should be affordable and cost effective. True,
but, in reality, that is easily said than done. Complexity makes change hard.
We can't shy away from the hard problems posed by domains and business needs.
So, how can we solve complicated problems without getting dragged into the
quagmire of what appears to be an inevitable complexity? In this keynote, an
award winning author and software practitioner will share experiences and
observations from working on multiple software projects, about what leads to
complexities, the traps developers and organizations fall into, and what we
can do to effectively deal with these common, recurring issues we see across
domains and products.
Download presentation
Venkat Subramaniam
11:00 - 11:45
Building the Quality Ecosystem Our Clients Truly Need
* __ 26/10/2023
* __ 11:00 - 11:45
## Building the Quality Ecosystem Our Clients Truly Need
In Quality Engineering, we strive to deliver excellence to our customers.
However, do we consistently provide what they truly need? This is a question
we should always consider as we build our testing ecosystem.
In today's era of Digital Transformation, which has become a primary business
objective for most of our customers, creating the right technical ecosystem is
paramount. Our quality-driven approach can truly distinguish between average
and great results.
I understand that technology is rapidly evolving, and there are various
trends, enticing everyone to implement the latest and shiniest options on the
market. Yet, I firmly believe that adaptability and openness are essential in
achieving balance in everything we do. With this perspective in mind, let us
explore how we can best blend the technological DNA of our customers with the
insights of our consultants.
Download presentation
Alexandru Andriesei
12:00 - 12:45
Practices for Effective Continuous Software Architecture
* __ 26/10/2023
* __ 12:00 - 12:45
## Practices for Effective Continuous Software Architecture
Continuous Software Architecture is a philosophy and approach to software
architecture that embraces the fact that doing most of the design before the
implementation does not work very well, and perhaps never did. The approach
tries to move architecture from a set of up-front blueprints to a continually
developed set of architectural knowledge and decisions, stressing collective
ownership of the resulting architecture. While a simple idea, actually putting
it into practice can be difficult. In this talk we will briefly recap the idea
of Continuous Software Architecture and then explore the key practices that
are usually needed to achieve it, as well as the common problems and how to
address them.
Download presentation
Eoin Woods
12:45 - 14:00
Lunch break
* __ 03/11/2022
* __ 12:45 - 14:00
## Lunch break
Download presentation
14:00 - 14:45
Modern Practices in Microservices: Lessons Learned
* __ 26/10/2023
* __ 14:00 - 14:45
## Modern Practices in Microservices: Lessons Learned
Microservices has been around for over a decade now. Over the years we’ve
learned a lot, and have developed new practices, processes, techniques, and
tools to wrangle this highly complicated architecture style. In this
informative and entertaining session I discuss the current state of
microservices, including some of the lessons learned over the years. In the
session I also talk about some of today's current techniques, tips, and
practices to help maneuver around the complexity surrounding microservices.
Download presentation
Mark Richards
15:00 - 15:45
Poor Man's BingChat - Building an Internet-connected Search Assistant from
scratch*
* __ 26/10/2023
* __ 15:00 - 15:45
## Poor Man's BingChat - Building an Internet-connected Search Assistant from
scratch*
Have you seen BingChat's/ChatGPT's ability to reason about current events,
even though the underlying models have been trained with data only up to
September 2021? Have you wondered how it works behind the scenes, what did
they do to make it work?
During this talk Vlad will be talking about the ways to achieve this,
including possible options such as fine-tuning and retrieval augmented
generation, but also limitations such as models' slow inference times and
limited context window sizes. He will demo an app that can talk about current
events, and you will learn how to build your own, from scratch*.
*from scratch = with nothing but Python, pandas, and the Azure OpenAI APIs
Download presentation
Vlad Iliescu
16:00 - 16:45
Email vs. Capitalism: A Story About Why We Can't Have Nice Things
* __ 26/10/2023
* __ 16:00 - 16:45
## Email vs. Capitalism: A Story About Why We Can't Have Nice Things
We’re not quite sure exactly when email was invented. Sometime around 1971.
But we know exactly when junk email was invented: May 3rd, 1978, when Gary
Thuerk emailed 400 people an advertisement for DEC computers. It made a lot of
people very angry… but it also sold a few computers, and so junk email was
born.
Fast forward half a century, and the relationship between email and commerce
has never been more complicated. In one sense, the utopian ideal of free,
decentralised, electronic communication has come true… email is the ultimate
cross-network, cross-platform communication protocol. In another sense, it’s
an arms race: mail providers and ISPs implement ever more stringent checks and
policies to prevent junk mail, and if that means the occasional important
message gets sent to junk by mistake, then hey, no big deal… until you’re
trying to send out e-tickets and discover that every company who uses Mimecast
has decided your mail relay is sending junk. Marketing teams want beautiful,
colourful, responsive emails, but their customers’ mail clients are still
using a subset of HTML 3.2 that doesn’t even support CSS rules. And let’s not
even get started on how you design an email when half your readers will be
using “dark mode” so everything ends up on a black background.
Email is too big to change, too broken to fix… and too important to ignore. So
let’s look at what we need to know to get it right. We’ll learn about DNS,
about MX and DKIM and SPF records. We’ll learn about how MIME actually works
(and what happens when it doesn’t). We’ll learn about tools like Papercut,
Mailtrap, Mailjet, Foundation, and how to incorporate them into your
development process. If you’re lucky, you’ll even learn about UTF-7, the most
cursed encoding in the history of information systems. Modern email is hacks
top of hacks on top of hacks… but, hey, it’s also how you got your ticket to
be here today, so why not come along and find out how it actually works?
Download presentation
Dylan Beattie
16:45 - 17:00
….
The chatbot
system_prompt="""You are a search assistant that helps users find
information from a series of curated documents.
You are given a query and a document using the following format:
Query: {query}
Document title: {title}
Document url: {url}
Document content: {content}
You need to think step by step and find the best answer to the query.
Do not make stuff up.
If you don't know the answer then say you don't know.
Answer in the following format:
{answer}
Reference: {document url}
"""
user_prompt=f"""Query: {query}
Document title: {title}
Document url: {url}
Document content:{page_content_md}"""
encoding = tiktoken.encoding_for_model('gpt-4')
user_prompt_tokens = encoding.encode(user_prompt)
user_prompt_trimmed = encoding.decode(user_prompt_tokens[:8000])
def chat(messages: []):
completion = client.chat.completions.create(
model=openai_chatmodel,
messages=messages)
return completion.choices[0].message.content, completion.usage
def print_human_message(message):
print(f"\033[94m \n>> 🧔🏻♂️ {message}")
def print_ai_message(message, usage):
print(f"""\033[91m \n>> 🤖 {message}
\033[90m[Total tokens: {usage.total_tokens} ({usage.prompt_tokens} + {usage.completion_tokens})]
""")
conversation=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt_trimmed}
]
print_human_message(query)
while True:
response, usage = chat(conversation)
print_ai_message(response, usage)
conversation.append({"role": "assistant", "content": response})
user_input = input("Enter your query or press 'Enter' to exit:")
print_human_message(user_input)
conversation.append({"role": "user", "content": user_input})
if(user_input == ''):
break
>> 🧔🏻♂️ Give me the list of speakers for Codecamp Iasi 2023
>> 🤖 The speakers for Codecamp Iasi 2023 are:
1. Mark Richards - Hands-On Software Architect, Independent Consultant, Author
2. Venkat Subramaniam - Award-winning author, founder of Agile Developer, Inc.
3. Eoin Woods - Chief Engineer, Endava
4. Alexandru Andriesei - Senior Engineering Manager, Tremend
5. Dylan Beattie - Technology strategist, Director, Ursatile Ltd.
6. Vlad Iliescu - Microsoft MVP on AI
Reference: https://codecamp.ro/conferences/codecamp_iasi/
[Total tokens: 6040 (5923 + 117)]
Enter your query or press 'Enter' to exit:Cool, what are they talking about?
>> 🧔🏻♂️ Cool, what are they talking about?
>> 🤖 The speakers at Codecamp Iasi 2023 are discussing the following topics:
1. Mark Richards - Modern Practices in Microservices: Lessons Learned
2. Venkat Subramaniam - Towards a Better Code Quality: Ways to Improve
3. Eoin Woods - Practices for Effective Continuous Software Architecture
4. Alexandru Andriesei - Building the Quality Ecosystem Our Clients Truly Need
5. Dylan Beattie - Email vs. Capitalism: A Story About Why We Can't Have Nice Things and Distributed Systems with C# and .NET
6. Vlad Iliescu - Poor Man's BingChat - Building an Internet-connected Search Assistant from scratch
Reference: https://codecamp.ro/conferences/codecamp_iasi/
[Total tokens: 6206 (6056 + 150)]
Enter your query or press 'Enter' to exit:just the talks please no masterclasses
>> 🧔🏻♂️ just the talks please no masterclasses
>> 🤖 The speakers at Codecamp Iasi 2023 are discussing the following topics during their talks:
1. Mark Richards - "Modern Practices in Microservices: Lessons Learned"
2. Venkat Subramaniam - "Don't Walk Away From Complexity, Run"
3. Alexandru Andriesei - "Building the Quality Ecosystem Our Clients Truly Need"
4. Eoin Woods - "Practices for Effective Continuous Software Architecture"
5. Dylan Beattie - "Email vs. Capitalism: A Story About Why We Can't Have Nice Things"
6. Vlad Iliescu - "Poor Man's BingChat - Building an Internet-connected Search Assistant from scratch"
Reference: https://codecamp.ro/conferences/codecamp_iasi/
[Total tokens: 6371 (6221 + 150)]
Enter your query or press 'Enter' to exit:what time is Vlad speaking?
>> 🧔🏻♂️ what time is Vlad speaking?
>> 🤖 Vlad Iliescu is scheduled to give his talk titled "Poor Man's BingChat - Building an Internet-connected Search Assistant from scratch" from 15:00 to 15:45 on 26th October 2023.
Reference: https://codecamp.ro/conferences/codecamp_iasi/
[Total tokens: 6446 (6385 + 61)]
Enter your query or press 'Enter' to exit:
>> 🧔🏻♂️
Functions 😱
# More documentation here: https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling
functions= [
{
"name": "get_time_until",
"description": "Returns the difference between a given time and now, without taking into consideration any timezones and assuming the same day.",
"parameters": {
"type": "object",
"properties": {
"target": {
"type": "string",
"description": "The reference time, formatted as HH:MM:SS"
},
},
"required": ["target_date_time"]
}
}
]
def get_time_until(target: str):
target_time = datetime.strptime(target, "%H:%M:%S").time()
target_datetime = datetime.combine(datetime.today(), target_time)
time_diff = target_datetime - datetime.now()
total_seconds = int(time_diff.total_seconds())
minutes, seconds = divmod(total_seconds, 60)
return f"{minutes} minutes, {seconds} seconds"
available_functions = {
"get_time_until": get_time_until,
}
def respond_with_functions(messages):
response = client.chat.completions.create(model=openai_chatmodel,
messages=messages,
functions=functions,
function_call="auto")
response_message = response.choices[0].message
if not hasattr(response_message, "function_call"):
print(response.choices[0].message.content)
else:
# Call the function. The JSON response may not always be valid so make sure to handle errors
function_name = response_message.function_call.name
function_to_call = available_functions[function_name]
function_args = json.loads(response_message.function_call.arguments)
function_response = function_to_call(**function_args)
# Add the assistant response and function response to the messages
messages.append( # adding assistant response to messages
{
"role": response_message.role,
"function_call": {
"name": function_name,
"arguments": response_message.function_call.arguments,
},
"content": None
}
)
messages.append( # adding function response to messages
{
"role": "function",
"name": function_name,
"content": function_response,
}
)
# Call the API again to get the final response from the model
second_response = client.chat.completions.create(
messages=messages,
model=openai_chatmodel
# optionally, you could provide functions in the second call as well
)
print(second_response.choices[0].message.content)
conversation.append({
"role": "user",
"content": "Hi, it's Vlad. How much time do I have until the end of my session?"
})
respond_with_functions(conversation)
Your session ended approximately 2 minutes and 40 seconds ago.
Ouch.