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.