<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Reaminated</title>
    <description>The latest articles on DEV Community by Reaminated (@reaminated).</description>
    <link>https://dev.to/reaminated</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1054043%2Fcd3da1ea-6c85-4bdb-9dc9-55014f3591da.jpeg</url>
      <title>DEV Community: Reaminated</title>
      <link>https://dev.to/reaminated</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reaminated"/>
    <language>en</language>
    <item>
      <title>Run ChatGPT-Style Questions Over Your Own Files Using the OpenAI API and LangChain!</title>
      <dc:creator>Reaminated</dc:creator>
      <pubDate>Sat, 22 Apr 2023 17:55:43 +0000</pubDate>
      <link>https://dev.to/reaminated/run-chatgpt-style-questions-over-your-own-files-using-the-openai-api-and-langchain-1ii7</link>
      <guid>https://dev.to/reaminated/run-chatgpt-style-questions-over-your-own-files-using-the-openai-api-and-langchain-1ii7</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/fo0cg5pAh2g"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I'm sure you've all heard about &lt;a href="https://openai.com/blog/chatgpt"&gt;ChatGPT&lt;/a&gt; by now. It's an amazing Large Language Model (LLM) system that is opening up new and exciting innovative capabilities. However, it's been trained over a huge corpus of text from across the internet but what if you want to query your &lt;strong&gt;own&lt;/strong&gt; file or files? Thanks to the simple (but powerful!) OpenAI API and the amazing work done by the team at &lt;a href="https://github.com/hwchase17/langchain"&gt;LangChain&lt;/a&gt;, we can knock up a basic Question and Answering application that answers questions from &lt;strong&gt;your&lt;/strong&gt; files. This is all very new technology so I'm also learning as I go along and am always open to hearing feedback and improvements I can make - feel free to comment!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goal of the article is to get you started with Question and Answering your own document(s). However, as described in the Improvements section below, various aspects can be optimised. If there's enough interest, I can go into more detail about those topics in future articles.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sound good? Let's get to it! (Full code is on my &lt;a href="https://github.com/keyboardP/ChatGPMe"&gt;GitHub&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  High-Level Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Set up our development environment, API Key, and dependencies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Load in our file or directory containing multiple files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create and persist (optional) our database of embeddings (will briefly explain what they are later)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up our chain and ask questions about the document(s) we loaded in&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You'll need an &lt;a href="https://platform.openai.com/account/api-keys"&gt;OpenAI API Key&lt;/a&gt; (I recommend putting a &lt;a href="https://platform.openai.com/account/billing/limits"&gt;hard limit&lt;/a&gt; on pricing so you don't accidentally go over, especially when experimenting with code (you may automatically get free credit for new users but I've had my account for more than 3 months so those credits expired for me). You can also use the &lt;a href="https://gptforwork.com/tools/openai-chatgpt-api-pricing-calculator"&gt;OpenAI calculator&lt;/a&gt; to estimate costs - we'll be using &lt;em&gt;gpt-3.5-turbo&lt;/em&gt; model in this article)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Developer Environment (of course). I'm using &lt;a href="https://github.com/openai/openai-python"&gt;OpenAI Python SDK&lt;/a&gt;, &lt;a href="https://github.com/hwchase17/langchain"&gt;LangChain&lt;/a&gt;, and VS Code for the IDE. The requirements.txt file is available &lt;a href="https://github.com/keyboardP/ChatGPMe/blob/main/requirements.txt"&gt;in the GitHub repo&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A file or files to test with. I recommend starting with a single file to test with. As someone with a quant fund background and using this for trading information, I'll be using the &lt;a href="http://view.officeapps.live.com/op/view.aspx?src=https://c.s-microsoft.com/en-us/CMSFiles/TranscriptFY23Q2.docx?version=00d0cbc4-3046-8134-cbd0-669a0d279c30"&gt;Microsoft Q2 FY23 Earnings Call Transcript&lt;/a&gt; (from &lt;a href="https://www.microsoft.com/en-us/investor/earnings/fy-2023-q2/press-release-webcast"&gt;this page&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Set up the OpenAI Key
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you haven't done so already, create an account over at &lt;a href="https://platform.openai.com/account/usage"&gt;OpenAI&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(Optional but recommended) - Go to &lt;a href="https://platform.openai.com/account/billing/limits"&gt;&lt;strong&gt;Billing...Usage Limits...&lt;/strong&gt;&lt;/a&gt; and set your Soft and Hard limits. I used £10 but feel free to use whatever you're comfortable with. This prevents you from overspending more than you expected, especially useful when prototyping and experimenting with the API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you haven't got free credits, you may need to enter your payment details to gain access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Head over to the &lt;a href="https://platform.openai.com/account/api-keys"&gt;API Keys&lt;/a&gt; section and generate a new secret - &lt;strong&gt;Copy this secret before closing the window otherwise you won't get a chance to see it in full again&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When dealing with API keys and secrets, I like to use environment variables for security. So in your directory, create a file called ".env" (note the full-stop/period at the beginning)&lt;/p&gt;

&lt;p&gt;In the .env file, type &lt;strong&gt;OPENAI_API_KEY = '&amp;lt;your secret key from above&amp;gt;'&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# [.env file]&lt;/span&gt;
OPENAI_API_KEY &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sk-....'&lt;/span&gt; &lt;span class="c"&gt;# enter your entire key here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using Git, create a .gitignore file and add ".env" in the file as we don't want to commit this to our repo on accident and leak our secret key! I've also added "db/" which will be our database folder. I don't want to commit the database which could contain personal document data so ensuring that doesn't get committed either.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# [.gitignore file]&lt;/span&gt;
.env &lt;span class="c"&gt;# This will prevent the .env file from being commmitted to your repo&lt;/span&gt;
db/ &lt;span class="c"&gt;# This will be our database folder. I don't want to commit it so adding here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install all the required dependencies. Download the requirements.txt file &lt;a href="https://github.com/keyboardP/ChatGPMe"&gt;from here&lt;/a&gt; and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pip3&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;alternatively, you can manually use pip to install the dependencies below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;chromadb==0.3.21&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;langchain==0.0.146&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;python-dotenv==1.0.0&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's open our main Python file and load our dependencies. I'm calling the app "ChatGPMe" (sorry, couldn't resist the pun...😁) but feel free to name it what you like. In this article, I have removed the type annotations for clarity but the GitHub version contains the strongly typed version (I think it's good practice to add strong typing to Python code, I miss it from C#!)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# dotenv is a library that allows us to securely load env variables
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt; 

&lt;span class="c1"&gt;# used to load an individual file (TextLoader) or multiple files (DirectoryLoader)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DirectoryLoader&lt;/span&gt;

&lt;span class="c1"&gt;# used to split the text within documents and chunk the data
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CharacterTextSplitter&lt;/span&gt;

&lt;span class="c1"&gt;# use embedding from OpenAI (but others available)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.embeddings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;

&lt;span class="c1"&gt;# using Chroma database to store our vector embeddings
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;

&lt;span class="c1"&gt;# use this to configure the Chroma database  
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;chromadb.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;

&lt;span class="c1"&gt;# we'll use the chain that allows Question and Answering and provides source of where it got the data from. This is useful if you have multiple files. If you don't need the source, you can use RetrievalQA
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrievalQAWithSourcesChain&lt;/span&gt;

&lt;span class="c1"&gt;# we'll use the OpenAI Chat model to interact with the embeddings. This is the model that allows us to query in a similar way to ChatGPT
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chat_models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;

&lt;span class="c1"&gt;# we'll need this for reading/storing from directories
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may notice that many of the LangChain libraries above end in the plural. This is because LangChain is a framework for apps powered by language models so it allows numerous different chains, database stores, chat models and such, not just OpenAI/ChatGPT ones! This opens up huge possibilities for running offline models, open-source models and other great features.&lt;/p&gt;

&lt;p&gt;We'll load the .env file using dotenv. This library makes it easier and more secure to work with environment files to help secure secret keys and such. You could hardcode the API key directly in your file but this way is more secure and generally considered good practice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# looks for the .env file and loads the variable(s) 
&lt;/span&gt;&lt;span class="n"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Excellent, we now have our dependencies and API key set up, let's get to the fun bit!&lt;/p&gt;

&lt;h2&gt;
  
  
  Load the Files and Embeddings
&lt;/h2&gt;

&lt;p&gt;This is optional but I found it worthwhile. By default, if you don't persist the database, it will be transient which means that the database is deleted when your program ends. Your documents will have to be analysed every time you run the program. For a small number of files, it's fine, but can quickly add to the loading time if you need to analyse multiple files every time you run the app. So let's create a couple of variables we'll use to store the database in a folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# get the absolute path of this Python file
&lt;/span&gt;&lt;span class="n"&gt;FULL_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# get the full path with a folder called "db" appended
# this is where the database and index will be persisted
&lt;/span&gt;&lt;span class="n"&gt;DB_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FULL_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's load in the file we want to query. I'm going to query the &lt;a href="https://view.officeapps.live.com/op/view.aspx?src=https://c.s-microsoft.com/en-us/CMSFiles/TranscriptFY23Q2.docx?version=00d0cbc4-3046-8134-cbd0-669a0d279c30"&gt;Microsoft's Earnings Call transcript from Q2 2023&lt;/a&gt; but feel free to load whatever document(s) you like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# use TextLoader for an individual file
# explicitly stating the encoding is also recommmended
&lt;/span&gt;&lt;span class="n"&gt;doc_loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'MSFT_Call_Transcript.txt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"utf8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# if you want to load multiple files, place them in a directory 
# and use DirectoryLoader; comment above and uncomment below
#doc_loader = DirectoryLoader('my_directory')
&lt;/span&gt;
&lt;span class="c1"&gt;# load the document
&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc_loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll only be using &lt;strong&gt;TextLoader&lt;/strong&gt; but the syntax is the same for &lt;strong&gt;DirectoryLoader&lt;/strong&gt; so you can do a drop-in replacement with the &lt;em&gt;load()&lt;/em&gt; method.&lt;/p&gt;

&lt;p&gt;We've loaded the files but now we need to split the text into what's called chunks. Essentially, chunking allows you to group words into "chunks" to allow more meaning to a sentence. For example, the sentence below in the context of a football (soccer) game:&lt;/p&gt;

&lt;p&gt;"The striker scored a goal in the final minute of the game."&lt;/p&gt;

&lt;p&gt;One possible way to chunk this sentence is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Chunk 1: "The striker"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chunk 2: "scored"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chunk 3: "a goal in the final minute"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chunk 4: "of the game"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, notice that Chunk 3 and Chunk 4 share the words "final minute" contextually. This is an example of chunk overlap. While this chunking still conveys the essential information of the sentence, it is not as precise as it could be. A better way to chunk the sentence would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Chunk 1: "The striker"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chunk 2: "scored"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chunk 3: "a goal"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chunk 4: "in the final minute"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chunk 5: "of the game"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this revised version, there is no overlap between the chunks, and each chunk conveys a more distinct and specific idea. Ideally, when you chunk, you choose values that prevent chunk overlap. However, chunking is a whole topic of its own so will leave it there. If you want to find out more, you can search for chunking in Natural Language Processing (NLP) where good chunking is critical to the optimum usage of NLP models.&lt;/p&gt;

&lt;p&gt;So, with the quick chunking detour above, let's split our document with 512 as a chunk size and 0 as the overlap - feel free to play with these depending on your document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# obtain an instance of the splitter with the relevant parameters 
&lt;/span&gt;&lt;span class="n"&gt;text_splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# split the document data
&lt;/span&gt;&lt;span class="n"&gt;split_docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text_splitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now want to load the OpenAI embeddings. An embedding is essentially converting language as we use it to numerical values (vectors) so that a computer understands the words and their relationship to other words. Words with similar meanings will have a similar representation. Like chunking, Embedding is a huge topic but here's a &lt;a href="https://jalammar.github.io/illustrated-word2vec/"&gt;nice article on Word2Vec&lt;/a&gt; which is one way to create word embeddings. Let's get back on track with using embeddings created by OpenAI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# load the embeddings from OpenAI
&lt;/span&gt;&lt;span class="n"&gt;openai_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple! Let's now create our Chroma database to store these embeddings. &lt;a href="https://www.trychroma.com/"&gt;Chroma&lt;/a&gt; was written from the ground up to be an AI-native database and works well with LangChain to quickly develop and iterate AI applications.&lt;/p&gt;

&lt;p&gt;We'll start by configuring the parameters of the database&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# configure our database
&lt;/span&gt;&lt;span class="n"&gt;client_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chroma_db_impl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"duckdb+parquet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#we'll store as parquet files/DuckDB
&lt;/span&gt;    &lt;span class="n"&gt;persist_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DB_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#location to store 
&lt;/span&gt;    &lt;span class="n"&gt;anonymized_telemetry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt; &lt;span class="c1"&gt;# optional but showing how to toggle telemetry
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's create the actual vector store (i.e. the database storing our embeddings).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# create a class level variable for the vector store
&lt;/span&gt;&lt;span class="n"&gt;vector_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# check if the database exists already
# if not, create it, otherwise read from the database
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB_DIR&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Create the database from the document(s) above and use the OpenAI embeddings for the word to vector conversions. We also pass the "persist_directory" parameter which means this won't be a transient database, it will be stored on the hard drive at the DB_DIR location. We also pass the settings we created earlier and give the collection a name
&lt;/span&gt;    &lt;span class="n"&gt;vector_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;persist_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DB_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_settings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client_settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Transcripts_Store"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# It's key to called the persist() method otherwise it won't be saved 
&lt;/span&gt;    &lt;span class="n"&gt;vector_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# As the database already exists, load the collection from there
&lt;/span&gt;    &lt;span class="n"&gt;vector_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Transcripts_Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;persist_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DB_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding_function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_settings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client_settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have our embeddings stored! The final step is to load our chain and start querying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Chain and Query
&lt;/h2&gt;

&lt;p&gt;LangChain, as the name implies, has main chains to use and experiment with. Chains essentially allow you to "chain" together multiple components, such as taking input data, formatting it to a prompt template, and then passing it to an LLM. You can create your own chains or, as I'm doing here, use pre-existing chains which cover common use cases. For our case, I'm going to use &lt;strong&gt;RetrievalQAWithSourcesChain&lt;/strong&gt;. As the name implies, it also returns the source(s) used to obtain the answer. I'm doing this to show that the demo you see above is only using my document and not reaching out to the web for answers (shown by the Google question at the end).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# create and configure our chain
# we're using ChatOpenAI LLM with the 'gpt-3.5-turbo' model
# we're setting the temperature to 0. The higher the temperature, the more 'creative' the answers. In my case, I want as factual and direct from source info as possible
# 'stuff' is the default chain_type which means it uses all the data from the document
# set the retriever to be our embeddings database
&lt;/span&gt;&lt;span class="n"&gt;qa_with_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RetrievalQAWithSourcesChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_chain_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'gpt-3.5-turbo'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="n"&gt;chain_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stuff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     
     &lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vector_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are currently &lt;a href="https://python.langchain.com/en/latest/modules/chains/index_examples/qa_with_sources.html"&gt;four chain types&lt;/a&gt; but we're using the default one, 'stuff', which uses the entire document in one go. However, other methods like map_reduce can help with batching documents so you don't surpass token limits but that's a whole other topic.&lt;/p&gt;

&lt;p&gt;We're almost there! Let's create a quick function which handles the answering of the question and then create a loop for the user to ask questions to the document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# function to use our RetrievalQAWithSourcesChain
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qa_with_source&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# loop through to allow the user to ask questions until they type in 'quit'
&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# make the user input yellow using ANSI codes
&lt;/span&gt;    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"What is your query? "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[33m"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[0m"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"quit"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# make the answer green and source blue using ANSI codes
&lt;/span&gt;    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'Answer: &lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[32m&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[0m'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[34mSources: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[0m'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Hope that starts you what is an exciting field of development. Please feel free to comment and provide feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;p&gt;This is just the tip of the iceberg! For me personally, automating and running this with preset prompts across transcripts from various companies can provide good insights to help with trading decisions. For those interested in the financial/trading aspects of AI, you might like to read my short post on &lt;a href="https://www.reaminated.com/thoughts-on-bloomberggpt-and-domain-specific-llms"&gt;BloombergGPT&lt;/a&gt;. There is so much potential for alternative data and fundamentals analysis, it's a very exciting field. However, outside of that, it's also useful for your own personal files and organisation/searching and almost limitless other possibilities!&lt;/p&gt;

&lt;p&gt;Specifically, there are several improvements to be made, here are a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Offline - This is a big one and maybe a topic for another blog if there's interest. Your data is still sent to OpenAI unless you opt-out or use the Azure version of the API which has a more strict usage policy for your data. A great open-source project called &lt;a href="https://huggingface.co/"&gt;Hugging Face&lt;/a&gt; has numerous models and datasets to get your AI projects up and running. LangChain also supports Hugging Face so you could start experimenting with using offline Hugging Face models with LangChain to run everything without the internet or API costs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automate - Individually querying is useful but some situations may require a large number of actions or sequential actions. This is where &lt;a href="https://github.com/Significant-Gravitas/Auto-GPT"&gt;AutoGPT&lt;/a&gt; can come in,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chunking - I've hardcoded 512 and you may have seen messages saying that some of the chunking surpassed that. An improvement would be to use more dynamic chunking numbers tailored to the input documents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Token management and Prompt Templates - tokens are key to the API and you can optimise them such that you don't waste unnecessary tokens in your API call and still get the same results. This saves you money as you're using less of the limit and also allows more tailored prompts to provide better answers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As I say, many more features can be explored but this was my first foray into trying to utilise OpenAI models for my personal documents and trading data. A lot of documentation, bug tickets, and workaround reading was involved so I hope I've saved you some time!&lt;/p&gt;

&lt;p&gt;The full code can be found on my &lt;a href="https://github.com/keyboardP/ChatGPMe"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoy :)&lt;/p&gt;

</description>
      <category>python</category>
      <category>chatgpt</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Are your PDFs Actually Redacted? Double Check!</title>
      <dc:creator>Reaminated</dc:creator>
      <pubDate>Sat, 08 Apr 2023 16:34:25 +0000</pubDate>
      <link>https://dev.to/reaminated/are-you-pdfs-actually-redacted-double-check-3ce3</link>
      <guid>https://dev.to/reaminated/are-you-pdfs-actually-redacted-double-check-3ce3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;PDF is a very common file format for documents but one thing people may not realise is that the complexity the format provides can mean some things aren't quite as they seem. Specifically, I've seen numerous "redacted" documents where the user has drawn over the text and saved the file. However, under the hood, PDFs have layers which means that the block or marker you used is actually may actually be saved as a separate layer from the text it's covering. This means anyone who opens the file can simply move the block away and see the text that was underneath!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drawing over the text that should be redacted&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hRQEg7Qy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nl07sfig6a5kpb6e2s0x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hRQEg7Qy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nl07sfig6a5kpb6e2s0x.gif" alt="Drawing over the text that should be redacted" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After saving the file as "Seemingly_Redacted.pdf", and opening it in Acrobat, a user can easily remove the line to expose the text that should be hidden&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hszCyTia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb0f1t5ni4nvqmxawcvt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hszCyTia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb0f1t5ni4nvqmxawcvt.gif" alt="Uncovering" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Fix
&lt;/h2&gt;

&lt;p&gt;Adobe Acrobat Pro (paid versions) supports proper redacting of text but it can be too expensive to buy for people to justify their use, depending on how often you used advanced features. Fortunately, the free version of Adobe Acrobat can help with that.&lt;/p&gt;

&lt;p&gt;Instead of saving the PDF by either using the "Save" option or "Save As" option, go to "File...Print" in Adobe Acrobat. Set the printer to "Microsoft Print to PDF" (or a similar PDF-related name). If you have comments that you would like appended within the document, you can click the "Summarize Comments" button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YxzJ1foI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/96cdvyyhukqfsmynxhw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YxzJ1foI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/96cdvyyhukqfsmynxhw7.png" alt="Summarize Comments" width="753" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;"Print"&lt;/strong&gt; and it should ask to save the file to a location and once you choose a location, it will create a PDF file that is now flattened. &lt;strong&gt;I recommend creating a new final file, instead of overwriting the original version, in case further edits need to be made or data went missing during flattening&lt;/strong&gt;. That's it! The text under your drawing is no longer visible by moving the line or block out of the way. This also means nothing can be edited, so it's advisable to use it once you have the final version you want to send as opposed to constantly flattening it after every edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to be aware of
&lt;/h2&gt;

&lt;p&gt;Flattening PDFs can cause a decrease in quality but generally should retain a high enough quality for screen usage.&lt;/p&gt;

&lt;p&gt;Flattening a PDF file can sometimes lead to errors or inconsistencies in the document, especially if there are complex graphics. Always proofread after to ensure everything looks as expected.&lt;/p&gt;

&lt;p&gt;Finally, flattening can also affect OCR and accessibility tools making them harder to use. Consider your audience and requirements for flattening.&lt;/p&gt;

&lt;p&gt;However, for a lot of use cases, the downsides to flattening are outweighed by the pros. I had to use it to send a bank statement for proof of address but there was no need for the company to be able to see all my transactions on the statement.&lt;/p&gt;

&lt;p&gt;The paid version of Acrobat, and other readers, feature more advanced levels of redaction and such so depending on your requirements, it may be better value to use that. Additionally, PDFs offer other security aspects such as encryption and password-protection but this post was specifically for ensuring text that's been drawn over stays hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced usage using ImageMagick
&lt;/h2&gt;

&lt;p&gt;For those who want to automate this, you can use a command line too like ImageMagick. The following command flattens the PDF with a DPI of 300. You can toy around with the DPI value if you need to, the higher the value, the bigger the file size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;convert &lt;span class="nt"&gt;-density&lt;/span&gt; 300 &lt;span class="nt"&gt;-quality&lt;/span&gt; 100 input.pdf &lt;span class="nt"&gt;-flatten&lt;/span&gt; flattened_output.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope that helps, feel free to leave any comments or questions below.&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>documentation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Thoughts on BloombergGPT and Domain Specific LLMs</title>
      <dc:creator>Reaminated</dc:creator>
      <pubDate>Wed, 05 Apr 2023 19:39:04 +0000</pubDate>
      <link>https://dev.to/reaminated/thoughts-on-bloomberggpt-and-domain-specific-llms-1mon</link>
      <guid>https://dev.to/reaminated/thoughts-on-bloomberggpt-and-domain-specific-llms-1mon</guid>
      <description>&lt;p&gt;&lt;a href="https://www.bloomberg.com/company/press/bloomberggpt-50-billion-parameter-llm-tuned-finance/"&gt;Bloomberg&lt;/a&gt; announced BloombergGPT which looks incredible (&lt;a href="https://arxiv.org/abs/2303.17564"&gt;direct link to the paper&lt;/a&gt;). I think this is a glimpse into the future of LLM models - the idea of domain-specific models, as suggested in the paper, to optimise processes and output.&lt;/p&gt;

&lt;p&gt;What will be interesting to see in the finance/trading field is how democratised data insight through LLM, and the ease of access to a larger number of people, would affect the alpha.&lt;/p&gt;

&lt;p&gt;On the one hand, having raw data analysed differently by different companies means that some companies may identify insights others haven't and could potentially trade on them, thus outperforming the market. On the other hand, it now potentially means that pressure is put on the rest of the trading pipeline, something that LLMs may not necessarily be able to do (?) (e.g. how quickly can your systems trade on the data based on your preferred horizons, how well can you update and run backtesters to work efficiently with LLM data, what's the optimal parameterization of a portfolio to trade with etc...)&lt;/p&gt;

&lt;p&gt;I think this is a microcosm of the bigger effects of domain specific LLMs - they'll put pressure, and create new job roles and remits, on not just the data side that LLMs produce but the rest of the technical and business pipelines to optimise their functions to capitalise on the LLM data - e.g. imagine combining trained decision trees to generate specific prompts with LLMs to reduce hallucination or gather hidden insights.&lt;/p&gt;

&lt;p&gt;LLMs being just one of many forms of AI models, it'll be interesting to see if other forms of AI models are implemented elsewhere in the system's execution path to collaborate with the new LLMs coming out to take full advantage. A lot of focus on data at the moment, which is critical, but the knock on effects on data utilisation research will be interesting to watch.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Discipline and Development</title>
      <dc:creator>Reaminated</dc:creator>
      <pubDate>Thu, 30 Mar 2023 21:06:28 +0000</pubDate>
      <link>https://dev.to/reaminated/discipline-and-development-2e4f</link>
      <guid>https://dev.to/reaminated/discipline-and-development-2e4f</guid>
      <description>&lt;p&gt;I posted a &lt;a href="https://old.reddit.com/r/gamedev/comments/ubv6x0/how_did_you_overcome_analysis_paralysis/i66hvg2/"&gt;comment over on Reddit&lt;/a&gt; where someone was asking how people maintain discipline when developing games (although a lot of the theory could be applicable in other fields). I posted an answer with a few points and figured I’d post it here for future reference too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I find discipline becomes easier when seeing progress so I always try to look back at what’s been achieved every few weeks, however small, and be proud of it. Not to make it sound like a feel-good wishy-washy thing but I think it’s easy to forget how much you’ve achieved relative to your skillset and resources when it’s so often compared to other people’s work (which may be further down the line).&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you still like the game idea, clearly something else is stopping you. Identify what it is. For me, as more of a programmer than an artist, testing the visual side of things seemed like a pain (setting up Unity, making sure camera is correct, finding an image to use etc..). To counter this, I’ve now created an ever-growing folder of assets I accumulate from the web and set up a prototype project for my game where I can quickly test ideas. This includes backgrounds, sprite-sheets, random images, sound effects, music etc… Therefore whenever I want to prototype anything, I can focus on the programming more than the environment setup.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find out what your bottlenecks are during development. For me, my PC was slowing down too much and Unity would take a while to load up (didn’t take too long but when you’re not motivated enough anyway, it’s another excuse to ‘do it later’), which took me out my zone so I upgraded the RAM and installed an SSD drive. A faster machine makes so much of a difference to me when developing. &lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I now maintain a personal dev diary with screenshots. In order to be good at this, I customised my workflow to help take GIFs quickly without losing focus (&lt;a href="http://keyboardp.tumblr.com/post/147897009991/improve-workflow-to-maintain-discipline-unity3d"&gt;wrote a post here&lt;/a&gt;).&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The internet is probably the biggest distraction so I now make time at the end of the day to catch up on articles that look interesting but aren’t necessarily pertinent to what I’m doing at that time. I wrote a &lt;a href="http://keyboardp.tumblr.com/post/147904135266/toview-folders-simple-browser-feature-to-help"&gt;post here&lt;/a&gt; on how I did that.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Play to your mood. If you’re really motivated, work on the more 'boring’ things if possible such as non-visual/backend stuff. When you’re not in the mood, work on something that can give instant feedback which I usually find has visual aspects. The latter also works for when you want to share your work with others here or on Twitter or something and get feedback. (Of course, that’s just for me, you may find the backend aspects much more rewarding)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially investing time up front on improving work flow and reducing friction is what got me out of a rut. Doing this felt productive, even if it was something simple as gathering assets for my prototype folder. Then it’s a positive cycle as you develop more, you become more self-motivated and I find it perpetuates - just be sure not to burn out and pace yourself; nothing wrong with taking up other hobbies IMHO.&lt;/p&gt;

&lt;p&gt;The system’s not perfect and I still hit developer’s block but it’s the best thing I’ve found that works for me personally. I've also started to spend time on blogging (wrote my first &lt;a href="https://dev.to/reaminated/transform-your-viewing-experience-how-to-create-an-immersive-ambient-monitor-with-simple-led-lights-and-code-magic-2i45"&gt;dev.to post here&lt;/a&gt;) which I find helps become productive.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>programming</category>
      <category>help</category>
    </item>
    <item>
      <title>Transform Your Viewing Experience: How to Create an Immersive Ambient Monitor with Simple LED Lights and Code Magic</title>
      <dc:creator>Reaminated</dc:creator>
      <pubDate>Thu, 30 Mar 2023 17:32:17 +0000</pubDate>
      <link>https://dev.to/reaminated/transform-your-viewing-experience-how-to-create-an-immersive-ambient-monitor-with-simple-led-lights-and-code-magic-2i45</link>
      <guid>https://dev.to/reaminated/transform-your-viewing-experience-how-to-create-an-immersive-ambient-monitor-with-simple-led-lights-and-code-magic-2i45</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/az-va7ofef8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;For those not familiar with Ambient TVs, it’s a way to soften the jump from the edge of the TV screen and its immediate surroundings to provide a more immersive experience. I had some LED lights lying around and decided to see if it was possible to control the lights through code and, in turn, make my computer screen an ambient monitor. Whilst I wanted to use it for my monitor, it can be used anywhere and with whatever colours you can send it, including other features your lights may have such as audio reaction or random patterns. I’ve been meaning to write this post for a while as I’ve been using it on an earlier monitor but I never got round to adding it to my new monitor so I documented as I went along for anyone who might find it useful. So let’s get to it! (Please note, LED lights are likely to be Bluetooth Low Energy (BLE) so your computer will need to support BLE in order to interact with them). Full code is on &lt;a href="https://github.com/keyboardP/AmbientMonitor" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  High Level Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Find out what commands the LED light’s Bluetooth receiver accepts&lt;/li&gt;
&lt;li&gt;Send commands to the LED lights via my computer’s Bluetooth&lt;/li&gt;
&lt;li&gt;Obtain the dominant colour of the current screen&lt;/li&gt;
&lt;li&gt;Send the dominant colour to the LED lights&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bluetooth-supported RGB LED lights and accompanying app (I’m using Android, iOS would likely require an alternative approach than the one described here but should be possible using Wireshark directly to monitor Bluetooth traffic). I've attached these lights to the back of my monitor&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wireshark.org/" rel="noopener noreferrer"&gt;Wireshark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/studio/command-line/adb" rel="noopener noreferrer"&gt;Android’s SDK tools (specifically adb.exe)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Developer tools (I’ll be using Python 3.10, though any 3.x versions should work, but the principles should be the same whatever for language you prefer)&lt;/li&gt;
&lt;li&gt;A device to send BLE commands from (e.g. a laptop that supports BLE)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Bluetooth data
&lt;/h2&gt;

&lt;p&gt;The first step we need to do is ensure that the app that comes with the lights is working as expected. This can easily be tested by running the light’s original app and making sure that the lights react accordingly depending on the on/off/lighting buttons you’re pressing on your app. We do this because we will shortly be pressing and detecting the specific codes sent to the Bluetooth receiver on the lights.&lt;/p&gt;

&lt;p&gt;There are two approaches that I could take. One was to decompile the app’s JAR file and find the codes that were being sent, but I wanted to learn more about the Bluetooth protocol so opted to log all Bluetooth activity on my Android and extract it from there. Here’s how:&lt;/p&gt;

&lt;p&gt;1) Enable &lt;a href="https://developer.android.com/studio/debug/dev-options" rel="noopener noreferrer"&gt;Developer Options&lt;/a&gt; on your Android device&lt;/p&gt;

&lt;p&gt;2) Enable Bluetooth HCI snoop log (HCI stands for Host-Controller Interface). You can find this option in &lt;strong&gt;Settings &amp;gt; System &amp;gt; Developer&lt;/strong&gt; or search for it in settings as in the image below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4wpnmi2rw8z9kgslkxy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4wpnmi2rw8z9kgslkxy.gif" alt="Enabling Bluetooh HCI snoop log"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3) We now need to perform specific actions so we can identify what each action sends to the light’s Bluetooth receiver. I’m going to keep it simple to On/Red/Green/Blue/Off, in that order, but if your lights support other features, you can toy around with those too. &lt;/p&gt;

&lt;p&gt;4) Run the app and press &lt;strong&gt;On&lt;/strong&gt;, &lt;span&gt;&lt;strong&gt;Red&lt;/strong&gt;&lt;/span&gt;, &lt;span&gt;&lt;strong&gt;Green&lt;/strong&gt;&lt;/span&gt;, &lt;span&gt;&lt;strong&gt;Blue&lt;/strong&gt;&lt;/span&gt;, and &lt;strong&gt;Off&lt;/strong&gt;. It may also be useful to keep an eye on the approximate time to make it easier to filter if you have a lot of Bluetooth activity on your device. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2izzstczrcbnu46e3263.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2izzstczrcbnu46e3263.gif" alt="Using the app to press the commands we're looking for"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5) Turn Bluetooth off so we don’t get any more noise. In the following steps, we’ll analyse the Bluetooth commands and, as we know the order of what we pressed, we can find out which values correspond to which button press.&lt;/p&gt;

&lt;p&gt;6) We now need to access to the Bluetooth logs on the phone. There are several ways to do this, but I will generate and export a bug report. To do this, enable &lt;strong&gt;USB Debugging&lt;/strong&gt; in the phone’s &lt;strong&gt;Settings&lt;/strong&gt;, connect the phone to the computer, and use the adb.exe command line tool.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;               adb bugreport led_bluetooth_report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;7) This will generate a zip file on your computer’s local directory with the filename “led_bluetooth_report.zip”. You can specify a path if you prefer (e.g. C:\MyPath\led_bluetooth_report”)&lt;/p&gt;

&lt;p&gt;8) Within this zip are the logs that we need. This may vary device to device (please comment if you found it elsewhere on your device). On my Google Pixel phone, it was in &lt;strong&gt;FS\data\misc\bluetooth\logs\btsnoop_hci.log&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;9) Now we have the log files, let’s analyse them! To do this, I decided to use Wireshark so start Wireshark and go to File...Open... and select the btsnoop_hci log file.&lt;/p&gt;

&lt;p&gt;Whilst it may look daunting, let’s make it easy for ourselves to find what we’re looking for by filtering the &lt;a&gt;BTL2CAP&lt;/a&gt; on &lt;strong&gt;0x0004&lt;/strong&gt; which is the Attribute Protocol in the &lt;a href="https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-btl2cap.c#L426" rel="noopener noreferrer"&gt;Wireshark source code&lt;/a&gt;. The attribute protocol defines the way two BLE devices talk to each other, so this is what we need to help find how the app talks to the lights. You can filter the logs in Wireshark by typing &lt;strong&gt;btl2cap.cid == 0x0004&lt;/strong&gt; in the “&lt;strong&gt;Apply a display filter&lt;/strong&gt;” bar near the top and press Enter&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Froaq4y4jmibetth6cl9h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Froaq4y4jmibetth6cl9h.gif" alt="Wireshark Filter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have filtered the log, it should make looking for the commands easier. We can look at the timestamps (Go to View…Time Display Format…Time of Day to convert the time if it’s the wrong format). We want to look at the &lt;strong&gt;Sent Write Command&lt;/strong&gt; logs as those are the ones where we sent a value to the lights. Assuming your most recent time is at the bottom, scroll down to the last five events. These should be On, Red, Green, Blue, and Off in that order with Off being last.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvv9f4k0xmfjsf8h0r2xj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvv9f4k0xmfjsf8h0r2xj.jpg" alt="Wireshark results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take note of the &lt;strong&gt;Destination BD_ADDR&lt;/strong&gt; as we'll need that shortly and don on your Sherlock Holmes hat as this is where we need to unlock the pattern of how the colours and on/off commands are encoded within the message. This will vary depending on the light manufacturer but here’s the list of values I got for my device:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On: 7e0404f00001ff00ef&lt;/li&gt;
&lt;li&gt;Red: 7e070503ff000010ef&lt;/li&gt;
&lt;li&gt;Green: 7e07050300ff0010ef&lt;/li&gt;
&lt;li&gt;Blue: 7e0705030000ff10ef&lt;/li&gt;
&lt;li&gt;Off: 7e0404000000ff00ef&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are clearly hexadecimal values and if you look carefully, you’ll see there are some fixed patterns. Let’s split the patterns out as this should make things much clearer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On: 7e0404 &lt;strong&gt;f00001&lt;/strong&gt;     ff00ef&lt;/li&gt;
&lt;li&gt;Red: 7e070503     &lt;strong&gt;ff0000&lt;/strong&gt;     10ef&lt;/li&gt;
&lt;li&gt;Green: 7e070503     &lt;strong&gt;00ff00&lt;/strong&gt;     10ef&lt;/li&gt;
&lt;li&gt;Blue: 7e070503     &lt;strong&gt;0000ff&lt;/strong&gt;     10ef&lt;/li&gt;
&lt;li&gt;Off: 7e0404 &lt;strong&gt;000000&lt;/strong&gt;     ff00ef&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those familiar with hexadecimal values of pure red, green, and blue, you’ll know that the values are #FF000, #00FF00, and #0000FF respectively, which is exactly what we can see above. This means we now know the format to change the colours to whatever we want! (or at least to what the lights themselves are capable of). We can also see that On and Off have a different format from the colours and similar to each other with On having f00001 and Off having 00000.&lt;/p&gt;

&lt;p&gt;That’s it! We now have enough information to start coding and interacting with the lights. &lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to LED lights
&lt;/h2&gt;

&lt;p&gt;There are three key things we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The address of the device (this is the &lt;strong&gt;Destination BD_ADDR&lt;/strong&gt; from above)&lt;/li&gt;
&lt;li&gt;The values to send to the device (the hexadecimal values obtained above)&lt;/li&gt;
&lt;li&gt;The characteristic we want to change. A Bluetooth LE characteristic is a data structure that essentially defines data that can be sent between a host and client Bluetooth devices. We need to find the characteristic (a 16-bit or 128-bit UUID) that refers to the lights. There are some commonly used assigned numbers that can be &lt;a href="https://www.bluetooth.com/specifications/assigned-numbers/" rel="noopener noreferrer"&gt;found here&lt;/a&gt; but unless the device conforms to those, they could be using a custom UUID. As my lights aren’t in the assigned numbers list, let’s find it via code. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m using Python 3.10 and &lt;a href="https://github.com/hbldh/bleak" rel="noopener noreferrer"&gt;Bleak 0.20.1&lt;/a&gt;. Ensure Bluetooth on your computer is turned on (no need to pair with the device, we’ll connect to it through code).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Function to create a BleakClient and connect it to the address of the light's Bluetooth reciever
async def init_client(address: str) -&amp;gt; BleakClient:
    client =  BleakClient(address)  
    print("Connecting")
    await client.connect()
    print(f"Connected to {address}")
    return client

# Function we can call to make sure we disconnect properly otherwise there could be caching and other issues if you disconnect and reconnect quickly
async def disconnect_client(client: Optional[BleakClient] = None) -&amp;gt; None:
    if client is not None :
        print("Disconnecting")
        if characteristic_uuid is not None:
            print(f"charUUID: {characteristic_uuid}")
            await toggle_off(client, characteristic_uuid)
        await client.disconnect()
        print("Client Disconnected")
    print("Exited")

# Get the characteristic UUID of the lights. You don't need to run this every time
async def get_characteristics(client: BleakClient) -&amp;gt; None:
    # Get all the services the device (lights in this case) 
    services = await client.get_services() 
    # Iterate the services. Each service will have characteristics
    for service in services: 
        # Iterate and subsequently print the characteristic UUID
        for characteristic in service.characteristics: 
            print(f"Characteristic: {characteristic.uuid}") 
    print("Please test these characteristics to identify the correct one")
    await disconnect_client(client)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve commented to the code so should be self-explanatory but essentially,  we connect to the lights and find all the characteristics it exposes. My output was:&lt;/p&gt;

&lt;p&gt;Characteristic: 00002a00-0000-1000-8000-00805f9b34fb&lt;br&gt;
Characteristic: 00002a01-0000-1000-8000-00805f9b34fb&lt;br&gt;
Characteristic: 0000fff3-0000-1000-8000-00805f9b34fb&lt;br&gt;
Characteristic: 0000fff4-0000-1000-8000-00805f9b34fb&lt;/p&gt;

&lt;p&gt;A quick Google of the first two UUIDs shows this refers to the name and appearance of the service which is irrelevant for us. However, the third and fourth seem the most suitable with the third (&lt;strong&gt;0000fff3-0000-1000-8000-00805f9b34fb&lt;/strong&gt;) being the write characteristic according to &lt;a href="https://zvislog.wordpress.com/topics/ti-devkit-gatt-with-zvl/" rel="noopener noreferrer"&gt;this page&lt;/a&gt;.&lt;br&gt;
Excellent, we now have the characteristic we need for this particular device to write to with a value (the colour hexadecimal).&lt;/p&gt;
&lt;h2&gt;
  
  
  Controlling LED lights
&lt;/h2&gt;

&lt;p&gt;We finally have all the pieces this we need. At this stage, you can get creative with what colour input you’d like to use. You could, for example, connect the lights to a trading market API to change colours according to how your portfolio is doing. In this case, we want to make our monitors ambient aware, so we need to obtain the dominant colour of the screen and send that through. &lt;/p&gt;

&lt;p&gt;There are many ways to do this so feel free to experiment with whatever algorithms you would like. One of the simplest approaches would be to iterate every X number of pixels across the screen and take an average while more complicated solutions would look for colours human eyes perceive to be more dominant. Feel free to comment any findings you’d like to share! &lt;/p&gt;

&lt;p&gt;For the sake of this blog post, I’m going to keep it simple by using the &lt;a href="https://github.com/bedapisl/fast-colorthief" rel="noopener noreferrer"&gt;fast_colorthief&lt;/a&gt; library’s get_dominant_color method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'''
Instead of taking the whole screensize into account, I'm going to take a 640x480 resolution from the middle. 
This should make it faster but you can toy around depending on what works for you. You may, for example, want
to take the outer edge colours instead so it the ambience blends to the outer edges and not the main screen colour 
'''
screen_width, screen_height = ImageGrab.grab().size #get the overall resolution size 
region_width = 640
region_height = 480
region_left = (screen_width - region_width) // 2
region_top = (screen_height - region_height) // 2
screen_region = (region_left, region_top, region_left + region_width, region_top + region_height)

# Create an BytesIO object to reuse
screenshot_memory = io.BytesIO(b"")

# Method to get the dominant colour on screen. You can change this method to return whatever colour you like
def get_dominant_colour() -&amp;gt; str:
    # Take a screenshot of the region specified earlier
    screenshot = ImageGrab.grab(screen_region)
    '''
    The fast_colorthief library doesn't work directly with PIL images but we can use an in memory buffer (BytesIO) to store the picture
    This saves us writing then reading from the disk which is costly
    '''

    # Save screenshot region to in-memory bytes buffer (instead of to disk)
    # Seeking and truncating fo performance rather than using "with" and creating/closing BytesIO object
    screenshot_memory.seek(0)
    screenshot_memory.truncate(0)
    screenshot.save(screenshot_memory, "PNG") 
    # Get the dominant colour
    dominant_color = fast_colorthief.get_dominant_color(screenshot_memory, quality=1) 
    # Return the colour in the form of hex (without the # prefix as our Bluetooth device doesn't use it)
    return '{:02x}{:02x}{:02x}'.format(*dominant_color)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is commented so hopefully it should be clear as to what’s happening but we’re taking a smaller region of the screen from the middle then getting the dominant colour from that region. The reason I’m taking a smaller region is for performance; fewer pixels would need to be analysed.&lt;/p&gt;

&lt;p&gt;We’re almost there! We now know what to send it and where to send it. Let’s finish the last major part of this challenge which is to actually send it. Fortunately, with the Bleak library, this is quite straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async def send_colour_to_device(client: BleakClient, uuid: str, value: str) -&amp;gt; None:
    #write to the characteristic we found, in the format that was obtained from the Bluetooth logs
    await client.write_gatt_char(uuid, bytes.fromhex(f"7e070503{value}10ef"))

async def toggle_on(client: BleakClient, uuid: str) -&amp;gt; None:
    await client.write_gatt_char(uuid, bytes.fromhex(ON_HEX))
    print("Turned on")

async def toggle_off(client: BleakClient, uuid: str) -&amp;gt; None:
    await client.write_gatt_char(uuid, bytes.fromhex(OFF_HEX))
    print("Turned off")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we discovered from the logs, each colour has a fixed template so we can use f-strings to hardcode the common part and simply pass a hexadecimal of a colour for the value in the middle. This can be called from our loop. On and Off had unique hexademicals so I created individual functions and passed in a constant value that contained the relevant hex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; while True: 
         # send the dominant colour to the device
         await send_colour_to_device(client, characteristic_uuid, get_dominant_colour())
         # allow a small amount of time before update
         time.sleep(0.1)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we have it, our Bluetooth LED lights are now controlled by the colours on the screen creating our own Ambient Monitor.&lt;/p&gt;

&lt;p&gt;You can see the full code on &lt;a href="https://github.com/keyboardP/AmbientMonitor" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; which has a small amount of infrastructure code that wasn't specific to this post. I’ve tried to comment the code to be self-explanatory but feel free to ask any questions or make suggestions. &lt;/p&gt;

&lt;p&gt;Hopefully this gives you an idea on how you can start getting creative with your LED lights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-colour&lt;/strong&gt; - My light strips can only have one colour at a time, but I have another set that can have four quadrants, each with their own colours. This means it could be possible to have sections of the monitor match four sections on screen giving an even more accurate ambience setting. Those lights run on Wifi instead of Bluetooth and could be a future project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brightness&lt;/strong&gt; – to keep it simple, I just looked for the colour changing and the on and off commands. However, this can easily be improved by detecting the brightness control commands and throwing that into the colour algorithm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; - As we want to get the lights to change in realtime, performance is critical. There are some complex algorithms to detect which colour would be considered the most dominant, especially when perceived by humans (which leads to a whole world of colour conversions). However, since this needs to run quite quickly, there needs to be a balance between performance and accuracy. A future improvement could be to try and access the graphics card directly to read from the buffer rather than analysing the pixels on the screen directly. If this is possible, you would also eliminate the time taken from the graphics buffer to the screen which could optimise the reaction of the lights.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to comment below if you have any feedback or questions.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
