├── .env.example ├── .gitignore ├── LICENSE ├── LangChain Data Demo.ipynb ├── README.md ├── agent_steps.png ├── data └── .gitkeep └── requirements.txt /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=SETME 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | build 3 | dist 4 | _build 5 | docs/man/*.gz 6 | docs/source/api/generated 7 | docs/source/config.rst 8 | docs/gh-pages 9 | notebook/i18n/*/LC_MESSAGES/*.mo 10 | notebook/i18n/*/LC_MESSAGES/nbjs.json 11 | notebook/static/components 12 | notebook/static/style/*.min.css* 13 | notebook/static/*/js/built/ 14 | notebook/static/*/built/ 15 | notebook/static/built/ 16 | notebook/static/*/js/main.min.js* 17 | notebook/static/lab/*bundle.js 18 | node_modules 19 | *.py[co] 20 | __pycache__ 21 | *.egg-info 22 | *~ 23 | *.bak 24 | .ipynb_checkpoints 25 | .tox 26 | .DS_Store 27 | \#*# 28 | .#* 29 | .coverage 30 | src 31 | 32 | *.swp 33 | *.map 34 | .idea/ 35 | Read the Docs 36 | config.rst 37 | 38 | /.project 39 | /.pydevproject 40 | 41 | package-lock.json 42 | 43 | .vscode* 44 | 45 | .env 46 | data/*.txt 47 | data/*.db* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Matt Hodges 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of LangChain-Data-Demo nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /LangChain Data Demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "31caaf76", 7 | "metadata": {}, 8 | "source": [ 9 | "# LangChain Data Demo\n", 10 | "\n", 11 | "_2023-04-01_\n", 12 | "\n", 13 | "**By Matt Hodges**\n", 14 | "\n", 15 | "This Jupyter Notebook demonstrates a variety of data tasks that can be accomplished with the assistance of the [LangChain](https://python.langchain.com/en/latest/index.html) framework. It shows how you can utilize [Chains](https://python.langchain.com/en/latest/modules/chains/getting_started.html), [Agents](https://python.langchain.com/en/latest/modules/agents/getting_started.html), and [Tools](https://python.langchain.com/en/latest/modules/agents/tools/getting_started.html) to work with voter data.\n", 16 | "\n", 17 | "LangChain is a flexible framework that can integrate with a [variety of LLMs](https://python.langchain.com/en/latest/reference/integrations.html). This Notebook integrates with OpenAI. To continue, obtain an [OpenAI API Key](https://platform.openai.com/). Copy the `.env.example` file to `.env` at the root of this respository, and set your key there. Since it can be hard to gauge utilization, I also recommend setting spending limits on your OpenAI account.\n", 18 | "\n", 19 | "This Notebook was written with LangChain version `0.0.128` using the `text-davinci-003` model from OpenAI.\n", 20 | "\n", 21 | "Also, be sure to install the requirements defined in `requirements.txt`:\n", 22 | "\n", 23 | "```\n", 24 | "pip install -r requirements.txt\n", 25 | "```" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "id": "b677a202", 31 | "metadata": {}, 32 | "source": [ 33 | "## Gathering Data\n", 34 | "\n", 35 | "This Notebook utilizes the [public voter data](https://dl.ncsbe.gov/index.html?prefix=data/) provided by the North Carolina State Board of Elections. We will work with a small subset of the NC voter file." 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 2, 41 | "id": "5820f828", 42 | "metadata": {}, 43 | "outputs": [ 44 | { 45 | "data": { 46 | "text/plain": [ 47 | "True" 48 | ] 49 | }, 50 | "execution_count": 2, 51 | "metadata": {}, 52 | "output_type": "execute_result" 53 | } 54 | ], 55 | "source": [ 56 | "import sqlite3\n", 57 | "import tempfile\n", 58 | "import urllib.request\n", 59 | "import zipfile\n", 60 | "\n", 61 | "import dotenv\n", 62 | "import pandas as pd\n", 63 | "from langchain import LLMChain, OpenAI, SQLDatabase, SQLDatabaseChain\n", 64 | "from langchain.agents import (AgentExecutor, Tool, ZeroShotAgent,\n", 65 | " initialize_agent, load_tools)\n", 66 | "\n", 67 | "dotenv.load_dotenv(override=False)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "id": "37333925", 73 | "metadata": {}, 74 | "source": [ 75 | "Loading the data into the database is the most code we'll write in this project:" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 2, 81 | "id": "4118f0d2", 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "# Public data from NC Elections Board\n", 86 | "# https://dl.ncsbe.gov/index.html?prefix=data/\n", 87 | "nc_voter_url = \"https://s3.amazonaws.com/dl.ncsbe.gov/data/ncvoter29.zip\"\n", 88 | "nc_vhis_url = \"https://s3.amazonaws.com/dl.ncsbe.gov/data/ncvhis29.zip\"\n", 89 | "\n", 90 | "\n", 91 | "# Download the data and extract the Zip\n", 92 | "extracted = []\n", 93 | "for datafile in (nc_voter_url, nc_vhis_url):\n", 94 | "\n", 95 | " with tempfile.NamedTemporaryFile(suffix=\".zip\") as tmp_file:\n", 96 | " urllib.request.urlretrieve(datafile, tmp_file.name)\n", 97 | "\n", 98 | " with zipfile.ZipFile(tmp_file.name, \"r\") as zip_ref:\n", 99 | " zip_ref.extractall(\"./data/\")\n", 100 | " extracted += zip_ref.namelist()\n", 101 | "\n", 102 | "# Load the data into a local SQLite database\n", 103 | "# We are not concerned with setting Primary Keys or Foreign Keys\n", 104 | "for filename in extracted:\n", 105 | " table_name = filename.split(\".\")[0]\n", 106 | " path = f\"./data/{filename}\"\n", 107 | "\n", 108 | " df = pd.read_csv(path, delimiter=\"\\t\", encoding=\"latin1\")\n", 109 | " cols = list(df.columns)\n", 110 | " preliminary_col_types = [str(dt) for dt in list(df.dtypes)]\n", 111 | " col_types = []\n", 112 | " for col in preliminary_col_types:\n", 113 | " if col == \"int64\":\n", 114 | " col_types.append(\"INTEGER\")\n", 115 | " elif col == \"float64\":\n", 116 | " col_types.append(\"REAL\")\n", 117 | " else:\n", 118 | " col_types.append(\"TEXT\")\n", 119 | "\n", 120 | " column_def = ', '.join([f\"{col} {t}\" for col, t in zip(cols, col_types)])\n", 121 | "\n", 122 | " con = sqlite3.connect(\"./data/ncv.db\")\n", 123 | " cur = con.cursor()\n", 124 | " cur.execute(f\"CREATE TABLE IF NOT EXISTS {table_name} ({column_def});\")\n", 125 | "\n", 126 | " df.to_sql(table_name, con, if_exists=\"replace\", index=False)\n", 127 | " con.commit()\n", 128 | " con.close()" 129 | ] 130 | }, 131 | { 132 | "attachments": {}, 133 | "cell_type": "markdown", 134 | "id": "c3540d18", 135 | "metadata": {}, 136 | "source": [ 137 | "## Using the LLM to Write and Run SQL\n", 138 | "\n", 139 | "Now that our voter data is loaded into the database, we're ready to start working with it! We'll start by using the [SQLDatabaseChain](https://python.langchain.com/en/latest/modules/chains/examples/sqlite.html). We'll use it to craft and execute SQL queries. And we'll set it to `verbose` so that we can see and verify its work.\n", 140 | "\n", 141 | "**NOTE:** For data-sensitive projects, you can specify `return_direct=True` in the SQLDatabaseChain initialization to directly return the output of the SQL query without any additional formatting. This prevents the LLM from seeing any additional contents within the database (though it does default [init with 3 sample rows](https://github.com/hwchase17/langchain/blob/v0.0.128/langchain/sql_database.py#L22)). Note, however, the LLM still has access to the database scheme (i.e. dialect, table and key names) by default. If this were not set, the results of the query would be sent to the LLM for further summarization." 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 4, 147 | "id": "8f1c066a", 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "db = SQLDatabase.from_uri(\"sqlite:///./data/ncv.db\")\n", 152 | "llm = OpenAI(temperature=0)\n", 153 | "db_chain = SQLDatabaseChain(\n", 154 | " llm=llm,\n", 155 | " database=db,\n", 156 | " verbose=True, # Show its work\n", 157 | " return_direct=True, # Return the results without sending back to the LLM\n", 158 | ")" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "id": "54682a22", 164 | "metadata": {}, 165 | "source": [ 166 | "And now we're ready to roll! Let's try a few!" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 4, 172 | "id": "fc15c0a0", 173 | "metadata": {}, 174 | "outputs": [ 175 | { 176 | "name": "stdout", 177 | "output_type": "stream", 178 | "text": [ 179 | "\n", 180 | "\n", 181 | "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", 182 | "How many voters are there? \n", 183 | "SQLQuery:\u001b[32;1m\u001b[1;3m SELECT COUNT(*) FROM ncvoter29;\u001b[0m\n", 184 | "SQLResult: \u001b[33;1m\u001b[1;3m[(125937,)]\u001b[0m\n", 185 | "\u001b[1m> Finished chain.\u001b[0m\n" 186 | ] 187 | }, 188 | { 189 | "data": { 190 | "text/plain": [ 191 | "{'query': 'How many voters are there?', 'result': '[(125937,)]'}" 192 | ] 193 | }, 194 | "execution_count": 4, 195 | "metadata": {}, 196 | "output_type": "execute_result" 197 | } 198 | ], 199 | "source": [ 200 | "db_chain(\"How many voters are there?\")" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 6, 206 | "id": "2f6ab346", 207 | "metadata": {}, 208 | "outputs": [ 209 | { 210 | "name": "stdout", 211 | "output_type": "stream", 212 | "text": [ 213 | "\n", 214 | "\n", 215 | "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", 216 | "What is the average age for each party? \n", 217 | "SQLQuery:\u001b[32;1m\u001b[1;3m SELECT party_cd, AVG(age_at_year_end) FROM ncvoter29 GROUP BY party_cd;\u001b[0m\n", 218 | "SQLResult: \u001b[33;1m\u001b[1;3m[('DEM', 57.10139401882521), ('GRE', 20.0), ('LIB', 38.924418604651166), ('REP', 55.279202607493254), ('UNA', 47.284671532846716)]\u001b[0m\n", 219 | "\u001b[1m> Finished chain.\u001b[0m\n" 220 | ] 221 | }, 222 | { 223 | "data": { 224 | "text/plain": [ 225 | "{'query': 'What is the average age for each party?',\n", 226 | " 'result': \"[('DEM', 57.10139401882521), ('GRE', 20.0), ('LIB', 38.924418604651166), ('REP', 55.279202607493254), ('UNA', 47.284671532846716)]\"}" 227 | ] 228 | }, 229 | "execution_count": 6, 230 | "metadata": {}, 231 | "output_type": "execute_result" 232 | } 233 | ], 234 | "source": [ 235 | "db_chain(\"What is the average age for each party?\")" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 7, 241 | "id": "d4122699", 242 | "metadata": {}, 243 | "outputs": [ 244 | { 245 | "name": "stdout", 246 | "output_type": "stream", 247 | "text": [ 248 | "\n", 249 | "\n", 250 | "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", 251 | "What is the most common first name of DEM voters who voted in 2020? \n", 252 | "SQLQuery:\u001b[32;1m\u001b[1;3m SELECT first_name, COUNT(*) AS count FROM ncvoter29 JOIN ncvhis29 ON ncvoter29.voter_reg_num = ncvhis29.voter_reg_num WHERE ncvhis29.election_lbl = '11/03/2020' AND ncvoter29.party_cd = 'DEM' GROUP BY first_name ORDER BY count DESC LIMIT 5;\u001b[0m\n", 253 | "SQLResult: \u001b[33;1m\u001b[1;3m[('JAMES', 268), ('MARY', 221), ('ROBERT', 215), ('MICHAEL', 174), ('WILLIAM', 167)]\u001b[0m\n", 254 | "\u001b[1m> Finished chain.\u001b[0m\n" 255 | ] 256 | }, 257 | { 258 | "data": { 259 | "text/plain": [ 260 | "{'query': 'What is the most common first name of DEM voters who voted in 2020?',\n", 261 | " 'result': \"[('JAMES', 268), ('MARY', 221), ('ROBERT', 215), ('MICHAEL', 174), ('WILLIAM', 167)]\"}" 262 | ] 263 | }, 264 | "execution_count": 7, 265 | "metadata": {}, 266 | "output_type": "execute_result" 267 | } 268 | ], 269 | "source": [ 270 | "db_chain(\"What is the most common first name of DEM voters who voted in 2020?\")" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": 8, 276 | "id": "cc77811d", 277 | "metadata": {}, 278 | "outputs": [ 279 | { 280 | "name": "stdout", 281 | "output_type": "stream", 282 | "text": [ 283 | "\n", 284 | "\n", 285 | "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", 286 | "The unique id is ncid. How many voters have no voting history? \n", 287 | "SQLQuery:\u001b[32;1m\u001b[1;3m SELECT COUNT(DISTINCT ncvoter29.ncid) FROM ncvoter29 LEFT JOIN ncvhis29 ON ncvoter29.ncid = ncvhis29.ncid WHERE ncvhis29.ncid IS NULL;\u001b[0m\n", 288 | "SQLResult: \u001b[33;1m\u001b[1;3m[(18215,)]\u001b[0m\n", 289 | "\u001b[1m> Finished chain.\u001b[0m\n" 290 | ] 291 | }, 292 | { 293 | "data": { 294 | "text/plain": [ 295 | "{'query': 'The unique id is ncid. How many voters have no voting history?',\n", 296 | " 'result': '[(18215,)]'}" 297 | ] 298 | }, 299 | "execution_count": 8, 300 | "metadata": {}, 301 | "output_type": "execute_result" 302 | } 303 | ], 304 | "source": [ 305 | "db_chain(\"The unique id is ncid. How many voters have no voting history?\")" 306 | ] 307 | }, 308 | { 309 | "attachments": {}, 310 | "cell_type": "markdown", 311 | "id": "8e841de0", 312 | "metadata": {}, 313 | "source": [ 314 | "## Using the LLM to Visualize the Data\n", 315 | "\n", 316 | "This is really cool! As you can see, we didn't have to actually tell the LLM much about the database to start querying it with natural language. It was able to infer the schema largely on its own!\n", 317 | "\n", 318 | "But the power here doesn't stop just at writing and executing SQL. We can actually _chain_ more utility. Let's get the LLM to use Python to do some data viz. To do that, we'll leverage the [Python REPL](https://python.langchain.com/en/latest/modules/agents/tools/examples/python.html) tool, and define a [custom agent](https://python.langchain.com/en/latest/modules/agents/agents/custom_agent.html) that uses our SQLite database as a tool.\n", 319 | "\n", 320 | "⚠️ **NOTE:** this executes LLM-generated Python code! This can be bad if the LLM-generated Python code is harmful. You may wish to execute this in a Virtual Machine or other sandboxed environment." 321 | ] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": 5, 326 | "id": "55cec215", 327 | "metadata": {}, 328 | "outputs": [], 329 | "source": [ 330 | "# Add python_repl to our list of tools\n", 331 | "tools = load_tools([\"python_repl\"])\n", 332 | "\n", 333 | "# Define our voter_data tool\n", 334 | "\n", 335 | "# Set a description to help the LLM know when and how to use it.\n", 336 | "description = (\n", 337 | " \"Useful for when you need to answer questions about voters. \"\n", 338 | " \"You must not input SQL. Use this more than the Python tool if the question \"\n", 339 | " \"is about voter data, like 'how many DEM voters are there?' or 'count the number of precincts'\"\n", 340 | ")\n", 341 | "\n", 342 | "voter_data = Tool(\n", 343 | " name=\"Data\", # We'll just call it 'Data'\n", 344 | " func=db_chain.run,\n", 345 | " description=description,\n", 346 | ")\n", 347 | "\n", 348 | "tools.append(voter_data)" 349 | ] 350 | }, 351 | { 352 | "cell_type": "markdown", 353 | "id": "549046a4", 354 | "metadata": {}, 355 | "source": [ 356 | "We create the custom agent by working with the `ZeroShotAgent`. Most of the modifications come from updating the prompt and specifying the tools." 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": 11, 362 | "id": "633fa84e", 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "# Standard prefix\n", 367 | "prefix = \"Fulfill the following request as best you can. You have access to the following tools:\"\n", 368 | "\n", 369 | "# Remind the agent of the Data tool, and what types of input it expects\n", 370 | "suffix = (\n", 371 | " \"Begin! When looking for data, do not write a SQL query. \"\n", 372 | " \"Pass the relevant portion of the request directly to the Data tool in its entirety.\"\n", 373 | " \"\\n\\n\"\n", 374 | " \"Request: {input}\\n\"\n", 375 | " \"{agent_scratchpad}\"\n", 376 | ")\n", 377 | "\n", 378 | "# The agent's prompt is built with the list of tools, prefix, suffix, and input variables\n", 379 | "prompt = ZeroShotAgent.create_prompt(\n", 380 | " tools, prefix=prefix, suffix=suffix, input_variables=[\"input\", \"agent_scratchpad\"]\n", 381 | ")\n", 382 | "\n", 383 | "# Set up the llm_chain\n", 384 | "llm_chain = LLMChain(llm=llm, prompt=prompt)\n", 385 | "\n", 386 | "# Specify the tools the agent may use\n", 387 | "tool_names = [tool.name for tool in tools]\n", 388 | "agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names)\n", 389 | "\n", 390 | "# Create the AgentExecutor\n", 391 | "agent_executor = AgentExecutor.from_agent_and_tools(\n", 392 | " agent=agent, tools=tools, verbose=True\n", 393 | ")" 394 | ] 395 | }, 396 | { 397 | "cell_type": "markdown", 398 | "id": "556525b3", 399 | "metadata": {}, 400 | "source": [ 401 | "And we can look at the prompt:" 402 | ] 403 | }, 404 | { 405 | "cell_type": "code", 406 | "execution_count": 12, 407 | "id": "d09702d3", 408 | "metadata": {}, 409 | "outputs": [ 410 | { 411 | "name": "stdout", 412 | "output_type": "stream", 413 | "text": [ 414 | "Fulfill the following request as best you can. You have access to the following tools:\n", 415 | "\n", 416 | "Python REPL: A Python shell. Use this to execute python commands. Input should be a valid python command. If you want to see the output of a value, you should print it out with `print(...)`.\n", 417 | "Data: Useful for when you need to answer questions about voters. You must not input SQL. Use this more than the Python tool if the question is about voter data, like 'how many DEM voters are there?' or 'count the number of precints'\n", 418 | "\n", 419 | "Use the following format:\n", 420 | "\n", 421 | "Question: the input question you must answer\n", 422 | "Thought: you should always think about what to do\n", 423 | "Action: the action to take, should be one of [Python REPL, Data]\n", 424 | "Action Input: the input to the action\n", 425 | "Observation: the result of the action\n", 426 | "... (this Thought/Action/Action Input/Observation can repeat N times)\n", 427 | "Thought: I now know the final answer\n", 428 | "Final Answer: the final answer to the original input question\n", 429 | "\n", 430 | "Begin! When looking for data, do not write a SQL query. Pass the relevant portion of the request directly to the Data tool in its entirety.\n", 431 | "\n", 432 | "Request: {input}\n", 433 | "{agent_scratchpad}\n" 434 | ] 435 | } 436 | ], 437 | "source": [ 438 | "print(prompt.template)" 439 | ] 440 | }, 441 | { 442 | "attachments": {}, 443 | "cell_type": "markdown", 444 | "id": "f85138b4", 445 | "metadata": {}, 446 | "source": [ 447 | "Now let's give it a go! Below you can see the choice the agent makes based on its inputs and observations. Here it decides to query the database, pass the results to python, and utilize `matplotlib` to graph a chart." 448 | ] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "execution_count": 15, 453 | "id": "73401e11", 454 | "metadata": {}, 455 | "outputs": [ 456 | { 457 | "name": "stdout", 458 | "output_type": "stream", 459 | "text": [ 460 | "\n", 461 | "\n", 462 | "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", 463 | "\u001b[32;1m\u001b[1;3m\n", 464 | "Thought: I need to find the most common first names of DEM voters who voted in 2020\n", 465 | "Action: Data\n", 466 | "Action Input: Find the most common first names of DEM voters who voted in 2020\u001b[0m\n", 467 | "\n", 468 | "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", 469 | "Find the most common first names of DEM voters who voted in 2020 \n", 470 | "SQLQuery:\u001b[32;1m\u001b[1;3m SELECT first_name, COUNT(*) AS count FROM ncvoter29 JOIN ncvhis29 ON ncvoter29.voter_reg_num = ncvhis29.voter_reg_num WHERE ncvhis29.election_lbl = '11/03/2020' AND ncvoter29.party_cd = 'DEM' GROUP BY first_name ORDER BY count DESC LIMIT 5;\u001b[0m\n", 471 | "SQLResult: \u001b[33;1m\u001b[1;3m[('JAMES', 268), ('MARY', 221), ('ROBERT', 215), ('MICHAEL', 174), ('WILLIAM', 167)]\u001b[0m\n", 472 | "\u001b[1m> Finished chain.\u001b[0m\n", 473 | "\n", 474 | "Observation: \u001b[33;1m\u001b[1;3m[('JAMES', 268), ('MARY', 221), ('ROBERT', 215), ('MICHAEL', 174), ('WILLIAM', 167)]\u001b[0m\n", 475 | "Thought:\u001b[32;1m\u001b[1;3m I need to visualize this data\n", 476 | "Action: Python REPL\n", 477 | "Action Input: import matplotlib.pyplot as plt; names = [('JAMES', 268), ('MARY', 221), ('ROBERT', 215), ('MICHAEL', 174), ('WILLIAM', 167)]; x, y = zip(*names); plt.bar(x, y)\u001b[0m\n", 478 | "Observation: \u001b[36;1m\u001b[1;3m\u001b[0m\n", 479 | "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", 480 | "Final Answer: A bar graph visualizing the most common first names of DEM voters who voted in 2020.\u001b[0m\n", 481 | "\n", 482 | "\u001b[1m> Finished chain.\u001b[0m\n" 483 | ] 484 | }, 485 | { 486 | "data": { 487 | "text/plain": [ 488 | "'A bar graph visualizing the most common first names of DEM voters who voted in 2020.'" 489 | ] 490 | }, 491 | "execution_count": 15, 492 | "metadata": {}, 493 | "output_type": "execute_result" 494 | }, 495 | { 496 | "data": { 497 | "image/png": "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kJCgli1bKjg4WLNnz1ZVVdWP3xoAANAk1ClQCgoKlJKSol27dmnz5s2qrKzUsGHDVF5ebo154okn9NZbb+nVV19VQUGBTpw4oZ/97GfW+suXLyshIUGXLl3Szp07tXr1auXk5Gj+/Pn1t1UAAKBR86rL4E2bNjldz8nJUXBwsPbu3avBgwertLRU2dnZys3N1X333SdJWrVqlbp3765du3bp7rvv1t///nd9/PHHevfddxUSEqK+ffvq6aef1pNPPqmMjAx5e3vX39YBAIBG6Uedg1JaWipJCgoKkiTt3btXlZWVio2NtcZERESoXbt2KiwslCQVFhaqV69eCgkJscbExcWprKxMhw4d+jHTAQAATUSdjqBcqbq6WjNmzNCgQYMUGRkpSSoqKpK3t7cCAwOdxoaEhKioqMgac2Wc1KyvWVebiooKVVRUWNfLyspudNoAAKARuOEjKCkpKTp48KDWrl1bn/OpVWZmpgICAqxLeHh4g39PAADgPjcUKKmpqcrLy9O2bdvUtm1ba7ndbtelS5dUUlLiNL64uFh2u90a8/1X9dRcrxnzfenp6SotLbUux48fv5FpAwCARqJOgeJwOJSamqrXX39dW7duVceOHZ3W9+/fX82aNdOWLVusZYcPH9axY8cUFRUlSYqKitKBAwd06tQpa8zmzZvl7++vHj161Pp9fXx85O/v73QBAABNV53OQUlJSVFubq7eeOMN+fn5WeeMBAQEqEWLFgoICNDkyZOVlpamoKAg+fv7a/r06YqKitLdd98tSRo2bJh69OihsWPHavHixSoqKtK8efOUkpIiHx+f+t9CAADQ6NQpUJYvXy5Jio6Odlq+atUqTZgwQZK0dOlSeXh4KDExURUVFYqLi9OyZcussZ6ensrLy9O0adMUFRWlVq1aafz48Vq0aNGP2xIAANBk1ClQHA7HD45p3ry5srKylJWVddUx7du319tvv12Xbw0AAG4hfBYPAAAwDoECAACMQ6AAAADjECgAAMA4BAoAADAOgQIAAIxDoAAAAOMQKAAAwDgECgAAMA6BAgAAjEOgAAAA4xAoAADAOAQKAAAwDoECAACMQ6AAAADjECgAAMA4BAoAADAOgQIAAIxDoAAAAOMQKAAAwDgECgAAMA6BAgAAjEOgAAAA4xAoAADAOAQKAAAwDoECAACMQ6AAAADjECgAAMA4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/17iCEoDqa6uloeHh/785z9f9Tn6xMRErVmzRpWVlS7rWrRocc04kb47+TY0NNTlcurUKfn6+mrgwIFaunSpBg8erMjISD311FOaMmVKreezNHX+/v61vpomOztbsbGxLnEifffz2bNnzzVPLB4/frwqKytvyX0qfXcoOTU1VYsXL1Z5eblmzJih119/XTt27NCdd96pyMhI5ebmavny5VqyZInL7WNjYxUaGqoOHTpo6tSpGjFihNatW+c05tChQy6P8St/ydcmKChI48aNU0ZGhqqrq+t1m93hao/f2nTq1EkfffSRYmJiNHPmTEVGRur+++/Xli1btHz58jp93+zsbPXo0aPWk24feughnTp1Sm+//ba1bOLEiS4/q5deesnpdoMHD1a/fv2sS//+/es0p5slJiZGFy5cUJcuXZxieMiQITp37pz1cuRrWbJkidO29uvXT3/961+t9aNHj3ZZX1xc7HQfa9asUatWrZxOxq8xdOhQtWjRQn/+859r/f6tW7d2Oi+xsbE5msIxUAPFx8erS5cut+x/XAAA/BgcQalnZ8+eVV5envLz85vECXoAALgD56DUs0mTJunDDz/UzJkzb7lzPQAAqC88xQMAAIzDUzwAAMA4BAoAADAOgQIAAIxDoAAAAOMQKAAAwDgECgAAMA6BAgAAjEOgAAAA4xAoAADAOP8PBS9iaCs19F8AAAAASUVORK5CYII=", 498 | "text/plain": [ 499 | "
" 500 | ] 501 | }, 502 | "metadata": {}, 503 | "output_type": "display_data" 504 | } 505 | ], 506 | "source": [ 507 | "request = \"Show a bar graph visualizing the answer to the following question:\" \\\n", 508 | " \"What are the most common first names of DEM voters who voted in 2020?\"\n", 509 | "\n", 510 | "agent_executor.run(request)" 511 | ] 512 | }, 513 | { 514 | "cell_type": "markdown", 515 | "id": "52ee8cd7", 516 | "metadata": {}, 517 | "source": [ 518 | "Very cool! As we see, it took the problem statement, crafted the SQL query, realized it could use the results within Python, and then wrote and executed the code to draw the graph!\n", 519 | "\n", 520 | "Let's do one more:" 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": 16, 526 | "id": "333228d6", 527 | "metadata": {}, 528 | "outputs": [ 529 | { 530 | "name": "stdout", 531 | "output_type": "stream", 532 | "text": [ 533 | "\n", 534 | "\n", 535 | "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", 536 | "\u001b[32;1m\u001b[1;3m\n", 537 | "Thought: I need to get the data first\n", 538 | "Action: Data\n", 539 | "Action Input: What are the most common voting methods in 2020\u001b[0m\n", 540 | "\n", 541 | "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", 542 | "What are the most common voting methods in 2020 \n", 543 | "SQLQuery:\u001b[32;1m\u001b[1;3m SELECT voting_method, COUNT(*) AS count FROM ncvhis29 WHERE election_desc = '11/03/2020 GENERAL' GROUP BY voting_method ORDER BY count DESC LIMIT 5;\u001b[0m\n", 544 | "SQLResult: \u001b[33;1m\u001b[1;3m[('ABSENTEE ONESTOP', 56553), ('IN-PERSON', 17872), ('ABSENTEE BY MAIL', 12568), ('ABSENTEE CURBSIDE', 2101), ('TRANSFER', 413)]\u001b[0m\n", 545 | "\u001b[1m> Finished chain.\u001b[0m\n", 546 | "\n", 547 | "Observation: \u001b[33;1m\u001b[1;3m[('ABSENTEE ONESTOP', 56553), ('IN-PERSON', 17872), ('ABSENTEE BY MAIL', 12568), ('ABSENTEE CURBSIDE', 2101), ('TRANSFER', 413)]\u001b[0m\n", 548 | "Thought:" 549 | ] 550 | }, 551 | { 552 | "data": { 553 | "image/png": "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Ch9//LF++ZQpU1CvXj089dRT2L59O0aOHKl/bsaMGXjnnXcwd+5cNGvWrFKfNxERVQyPbBAAQKlUYtmyZVi/fj3i4+OrtS07OzvodDpotdoqvT49PV1/ikWtLp6x1NHREY6Ojvjyyy+Rn59f6uv27duHlJQUzJkzp8RzgwcPRpMmTbBlyxaD5Z07d8agQYPw+uuvVykrERGVj2WD9J5++mm0atUKCxcurPI2rl69io8++ght27aFk9Nfk4aNGjVKXxgePG7dumXwWl9fXzg6OsLV1RWff/45nnrqKYSEhAAAVCoVoqKisGnTJri6uqJz586YN28ezpw5o3/9lStXAABNmzYtNVtISIh+nYctX74ce/fuxaFDh6r8eRMR0aOxbJCBlStXYtOmTbh48WKJ5x4uCtOnT9cvT09Ph6OjI+zt7REcHIx69erhs88+M3jtmjVrcOrUKYOHj4+PwTqHDh3C8ePHERUVhSZNmuCjjz4yeH7YsGFISEjA7t270a9fP0RHR6NNmzaIiooyWK+y0/2EhoZi/PjxPLpBRGQivGaDDHTt2hV9+/bFG2+8UeKOjFOnTun/7OzsrP+zk5MTTpw4AYVCAW9vb/11Gg/z8vJCUFBQmftu2LAhXF1dERwcjOTkZIwcORI///yzwToajQZ9+vRBnz59MH/+fEyZMgULFy7EhAkT0KRJEwDAxYsX0alTpxLbv3jxIkJDQ0vd9+LFi9GkSRN8+eWXZWYkIqLK45ENKmHFihX4+uuvceTIEYPlQUFB+kfdunX1yxUKBYKCgtCoUaNSi0ZVvPjiizh37hx27dpV5nqhoaHIzs4GADz55JNwd3fH6tWrS6y3e/duXL16FaNGjSp1O35+fpgxYwbmzZtX4hZZIiKqHpYNKqF58+YYM2YM1q1bZ7RtpqWlISkpyeDxoCSUxt7eHlOnTsXChQshyzJSUlLQs2dPfPrppzhz5gxiYmKwY8cOrFq1CkOGDAEAODg44D//+Q+++uor/OMf/8CZM2cQGxuLyMhITJgwAc8++yxGjBjxyH2+8cYbSEhIwP79+432eRMREcsGPcKSJUug0+mMtr2JEyfC29vb4LF+/foyXzNjxgxcvHgRO3bsgKOjIzp06IA1a9aga9euaNasGebPn4+pU6fivffe07/m2WefxU8//YRbt26hS5cuCA4Oxpo1a/Dmm29i69atZQ5S5u7ujtdeew15eXlG+7yJiAiQ5MpeTUdERERUCTyyQURERCbFskFEREQmxbJBREREJsWyQURERCbFskFEREQmxbJBREREJsWyQURERCbFskFEREQmxbJBREREJsWyQURERCbFskFEREQmxbJBREREJsWyQURERCbFskFEREQmxbJBREREJsWyQURERCbFskFEREQmxbJBREREJsWyQURERCbFskFEREQmxbJBREREJsWyQURERCbFskFEREQmxbJBREREJsWyQURERCbFskFEREQmxbJBREREJsWyQURERCb1/zcNiZv5JMJlAAAAAElFTkSuQmCC", 554 | "text/plain": [ 555 | "
" 556 | ] 557 | }, 558 | "metadata": {}, 559 | "output_type": "display_data" 560 | }, 561 | { 562 | "name": "stdout", 563 | "output_type": "stream", 564 | "text": [ 565 | "\u001b[32;1m\u001b[1;3m I need to plot this data\n", 566 | "Action: Python REPL\n", 567 | "Action Input: import matplotlib.pyplot as plt; labels = [x[0] for x in [('ABSENTEE ONESTOP', 56553), ('IN-PERSON', 17872), ('ABSENTEE BY MAIL', 12568), ('ABSENTEE CURBSIDE', 2101), ('TRANSFER', 413)]]; sizes = [x[1] for x in [('ABSENTEE ONESTOP', 56553), ('IN-PERSON', 17872), ('ABSENTEE BY MAIL', 12568), ('ABSENTEE CURBSIDE', 2101), ('TRANSFER', 413)]]; plt.pie(sizes, labels=labels); plt.show()\u001b[0m\n", 568 | "Observation: \u001b[36;1m\u001b[1;3m\u001b[0m\n", 569 | "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", 570 | "Final Answer: A pie chart showing the most common voting methods in 2020.\u001b[0m\n", 571 | "\n", 572 | "\u001b[1m> Finished chain.\u001b[0m\n" 573 | ] 574 | }, 575 | { 576 | "data": { 577 | "text/plain": [ 578 | "'A pie chart showing the most common voting methods in 2020.'" 579 | ] 580 | }, 581 | "execution_count": 16, 582 | "metadata": {}, 583 | "output_type": "execute_result" 584 | } 585 | ], 586 | "source": [ 587 | "agent_executor.run(\"Plot a pie chart of the most common voting methods in 2020\")" 588 | ] 589 | }, 590 | { 591 | "attachments": {}, 592 | "cell_type": "markdown", 593 | "id": "2a9c17a1", 594 | "metadata": {}, 595 | "source": [ 596 | "## Some parting thoughts\n", 597 | "\n", 598 | "That OpenAI's LLM is robust enough to craft valid SQL with minimal guidance is astounding. That it can then pipe the query results to a Python environment and build out data viz is almost impossible to believe until you see it.\n", 599 | "\n", 600 | "That said, the examples in this Notebook are the product of many hours of trial an error. I'm sure I'll be more efficient next time around. But some of the queries just did not work. Similarly, the LLM sometimes got stuck debugging its own Python code (also astounding that it can do that!) before I decided to move on to other ideas. **This is a tool that should not be left unsupervised.** It may produce bad code. It may hallucinate. The efficiency gains are apparent, but many things just don't work yet. Similarly, the LangChain library is very much still in its infancy, and is rough around the endges.\n", 601 | "\n", 602 | "**Most importantly:** We must continue to study and remain vigilant against the unknown biases built into the systems. Neural Networks and LLMs are already black boxes that are hard to assess. Singular platforms acting as the gateway to them only obscurs that more. And then adding all that into auto-generated code only obfuscates the risks further. This Notebook uses real voter data because its accessible and of personal interest to me. Any organizaiton hoping to use these tools for civic impact mush devote significant resources to safety, risk prevention and mitiation, and set hard ethical red lines." 603 | ] 604 | } 605 | ], 606 | "metadata": { 607 | "kernelspec": { 608 | "display_name": "Python 3 (ipykernel)", 609 | "language": "python", 610 | "name": "python3" 611 | }, 612 | "language_info": { 613 | "codemirror_mode": { 614 | "name": "ipython", 615 | "version": 3 616 | }, 617 | "file_extension": ".py", 618 | "mimetype": "text/x-python", 619 | "name": "python", 620 | "nbconvert_exporter": "python", 621 | "pygments_lexer": "ipython3", 622 | "version": "3.10.0" 623 | } 624 | }, 625 | "nbformat": 4, 626 | "nbformat_minor": 5 627 | } 628 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LangChain Data Demo 2 | 3 | This is a [Jupyter Notebook](https://github.com/hodgesmr/LangChain-Data-Demo/blob/main/LangChain%20Data%20Demo.ipynb) that demonstrates a variety of data engineering and anaysis tasks one can tackle with [LangChain](https://python.langchain.com/en/latest/index.html). It walks through using the LLM (via OpenAI) to write and execute SQL queries, and then pass the results of those queries to Python for data visualization. It uses public data voter files. 4 | 5 | This Notebook was written with LangChain version `0.0.128` using the `text-davinci-003` model from OpenAI. 6 | 7 | ![The LangChain agent executing SQL and Python to generate data visualization against the voter file](https://raw.githubusercontent.com/hodgesmr/LangChain-Data-Demo/main/agent_steps.png) 8 | 9 | ⚠️ **This Notebook generates and executes Python code written by an LLM. This has the potential to run unpredictable, buggy, or even harmful executions. Take caution to run this Notebook only in a sandboxed or appropriately controlled environment.** ⚠️ 10 | 11 | ## Setup 12 | 13 | Before you can run the [Notebook](https://github.com/hodgesmr/LangChain-Data-Demo/blob/main/LangChain%20Data%20Demo.ipynb), you need to copy [.env.example](./.env.example) to [.env](./.env) (which is ignored by git) and fill in the `OPENAI_API_KEY` environment variable. 14 | 15 | Also, be sure to install the requirements: 16 | 17 | ```sh 18 | pip install -r requirements.txt 19 | ``` 20 | 21 | I wrote and tested this Notebook in Python 3.10. 22 | 23 | ## License 24 | 25 | All code is provided under the [BSD 3-Clause license](https://github.com/hodgesmr/LangChain-Data-Demo/blob/main/LICENSE). 26 | 27 | ## North Carolina Voter Data 28 | 29 | The data used by the code in this repository originated from the North Carolina State Board of Elections, with the following [license](https://s3.amazonaws.com/dl.ncsbe.gov/data/ReadMe_PUBLIC_DATA.txt): 30 | 31 | ``` 32 | /* ******************************************************************************* 33 | * name: ReadMe_PUBLIC_DATA.txt 34 | * purpose: Notification to the public, media, and interested parties. 35 | * The data and documents contained within this publicly accessible site 36 | * and all subforders herein provided by the NC State Board of Elections 37 | * are considered public information per NC General Statutes. 38 | * URL: https://dl.ncsbe.gov/list.html 39 | * updated: 09/16/2020 40 | ******************************************************************************* */ 41 | 42 | Citations: 43 | 44 | § 132-1. Public Records. 45 | https://www.ncleg.gov/EnactedLegislation/Statutes/PDF/BySection/Chapter_132/GS_132-1.pdf 46 | 47 | § 163-82.10. Official record of voter registration. 48 | https://www.ncleg.gov/EnactedLegislation/Statutes/PDF/BySection/Chapter_163/GS_163-82.10.pdf 49 | ``` 50 | 51 | ## A Matt Hodges project 52 | 53 | This project is maintained by [@MattHodges](https://mastodon.social/@MattHodges). 54 | 55 | _Please use it for good, not evil._ 56 | -------------------------------------------------------------------------------- /agent_steps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hodgesmr/LangChain-Data-Demo/d910e1fde45e1abe5f86800aa8a17274a6822e89/agent_steps.png -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hodgesmr/LangChain-Data-Demo/d910e1fde45e1abe5f86800aa8a17274a6822e89/data/.gitkeep -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | langchain==0.0.128 2 | matplotlib==3.7.* 3 | notebook==6.5.* 4 | openai==0.27.2 5 | pandas==1.5.* 6 | python-dotenv==1.0.* 7 | --------------------------------------------------------------------------------