├── .changelog
├── .gitignore
└── template.md
├── .env.template
├── .envrc
├── .github
├── dependabot.yml
└── workflows
│ ├── constraints.txt
│ └── main.yaml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── .secrets.baseline
├── .venv
├── .vscode
├── extensions.json
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── chatefficient
├── __init__.py
├── app_langchain.py
├── app_openai.py
├── app_translator.py
└── streamlit_demo.py
├── data
├── README.md
├── external
│ └── .gitkeep
├── interim
│ └── .gitkeep
├── processed
│ └── .gitkeep
└── raw
│ └── .gitkeep
├── docs
├── aws_sagemaker_quickstart.md
├── detect_secrets.md
├── getting_started.md
├── project_specific_setup.md
├── quickstart.md
├── release_process.md
├── updating_requirements.md
├── using_poetry.md
└── using_towncrier.md
├── images
├── about-me.jpeg
├── civil-service.png
├── coefficient-aidl.png
├── eh-logo.png
├── eh.png
├── ml.png
├── obt-banner.png
├── one-big-thing.png
├── pydata.png
└── vicuna-chat.gif
├── models
└── .gitkeep
├── notebooks
├── Chroma.ipynb
├── LangChain.ipynb
├── __init__.py
├── frontier-ai-paper.txt
├── openai.ipynb
└── utils.py
├── poetry.lock
├── pyproject.toml
├── requirements.txt
└── setup.cfg
/.changelog/.gitignore:
--------------------------------------------------------------------------------
1 | !.gitignore
2 |
--------------------------------------------------------------------------------
/.changelog/template.md:
--------------------------------------------------------------------------------
1 | {% for section, _ in sections.items() %}
2 | {% if section %}## {{section}}{% endif %}
3 |
4 | {% if sections[section] %}
5 | {% for category, val in definitions.items() if category in sections[section]%}
6 | ### {{ definitions[category]['name'] }}
7 |
8 | {% if definitions[category]['showcontent'] %}
9 | {% for text, values in sections[section][category].items() %}
10 | - {{ text }} ({{ values|join(', ') }})
11 | {% endfor %}
12 |
13 | {% else %}
14 | - {{ sections[section][category]['']|join(', ') }}
15 |
16 | {% endif %}
17 | {% if sections[section][category]|length == 0 %}
18 | No significant changes.
19 |
20 | {% else %}
21 | {% endif %}
22 |
23 | {% endfor %}
24 | {% else %}
25 | No significant changes.
26 |
27 | {% endif %}
28 | {% endfor %}
29 |
--------------------------------------------------------------------------------
/.env.template:
--------------------------------------------------------------------------------
1 | export OPENAI_API_KEY="sk-********************"
2 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | dotenv
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Basic dependabot.yml file with minimum configuration
2 |
3 | version: 2
4 | updates:
5 | - package-ecosystem: github-actions
6 | directory: "/"
7 | schedule:
8 | interval: daily
9 | - package-ecosystem: pip
10 | directory: "/.github/workflows"
11 | schedule:
12 | interval: daily
13 | - package-ecosystem: pip
14 | directory: "/"
15 | schedule:
16 | interval: daily
17 | versioning-strategy: lockfile-only
18 | allow:
19 | - dependency-type: "all"
20 |
--------------------------------------------------------------------------------
/.github/workflows/constraints.txt:
--------------------------------------------------------------------------------
1 | pip==24.0
2 | poetry==1.8.2
3 | virtualenv==20.25.1
4 |
--------------------------------------------------------------------------------
/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | workflow_dispatch:
8 |
9 | concurrency:
10 | group: ${{ github.head_ref || github.run_id }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | timeout-minutes: 30
17 | strategy:
18 | matrix:
19 | python-version: ["3.10"]
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 |
24 | - name: Set up Python ${{ matrix.python-version }}
25 | uses: actions/setup-python@v5
26 | with:
27 | python-version: ${{ matrix.python-version }}
28 |
29 | - name: Create virtualenv
30 | run: |
31 | which python
32 | python -m venv venv
33 | source venv/bin/activate
34 | python -m pip install --constraint=.github/workflows/constraints.txt --upgrade pip
35 | which python
36 |
37 | - name: Set up Poetry cache
38 | uses: actions/cache@v4.0.2
39 | with:
40 | path: venv
41 | key: venv-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
42 |
43 | - name: Install Poetry and Python dependencies
44 | run: |
45 | curl -sSL https://install.python-poetry.org | python3 -
46 | poetry --version
47 | poetry config virtualenvs.in-project true
48 | poetry config virtualenvs.create false
49 | poetry config virtualenvs.path venv
50 | source venv/bin/activate
51 | which python
52 | poetry install --no-root
53 | poetry self add poetry-plugin-export
54 |
55 | - name: Compute pre-commit cache key
56 | id: pre-commit-cache
57 | shell: python
58 | run: |
59 | import hashlib
60 | import sys
61 | python = "py{}.{}".format(*sys.version_info[:2])
62 | payload = sys.version.encode() + sys.executable.encode()
63 | digest = hashlib.sha256(payload).hexdigest()
64 | result = "${{ runner.os }}-{}-{}-pre-commit".format(python, digest[:8])
65 | print("::set-output name=result::{}".format(result))
66 |
67 | - name: Restore pre-commit cache
68 | uses: actions/cache@v4.0.2
69 | with:
70 | path: ~/.cache/pre-commit
71 | key: ${{ steps.pre-commit-cache.outputs.result }}-${{ hashFiles('.pre-commit-config.yaml') }}
72 | restore-keys: |
73 | ${{ steps.pre-commit-cache.outputs.result }}-
74 |
75 | - name: pre-commit
76 | run: |
77 | source venv/bin/activate
78 | pre-commit run --hook-stage=manual --show-diff-on-failure --all-files
79 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Caches
2 | **/.ipynb_checkpoints/*
3 | **/__pycache__/*
4 | **/joblib/*
5 | *.pyc
6 |
7 | # Data
8 | *.sqlite3
9 | *.bin
10 | ./data/
11 | !**/data/**/README.md
12 | !**/data/**/*.gitkeep
13 |
14 | # Models
15 | models/*.bin
16 | models/*.gguf
17 | !models/**/*.gitkeep
18 |
19 | # Secrets
20 | .env
21 |
22 | # Personal TODO files
23 | TODO.txt
24 |
25 | # VisualStudioCode
26 | .vscode/*
27 | !.vscode/settings.json
28 | !.vscode/tasks.json
29 | !.vscode/launch.json
30 | !.vscode/extensions.json
31 | .RData
32 |
33 | # Environment
34 | venv/
35 |
36 | # coverage
37 | .coverage
38 | .coverage.*
39 | htmlcov
40 |
41 | # IDE config
42 | .idea/
43 | ipython_config.py
44 | profile_default/
45 |
46 | # Other
47 | .DS_Store
48 | ignore/
49 | *.log
50 | *.pem
51 |
52 | # Editor
53 | pyrightconfig.json
54 |
55 | # https://www.gitignore.io/
56 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | # Update all versions in this file by running:
2 | # $ pre-commit autoupdate
3 | minimum_pre_commit_version: 3.5.0
4 | default_language_version:
5 | python: python3.10
6 | repos:
7 | - repo: https://github.com/pre-commit/pre-commit-hooks
8 | rev: v4.5.0
9 | hooks:
10 | - id: end-of-file-fixer
11 | name: Check for a blank line at the end of scripts (auto-fixes)
12 | - id: trailing-whitespace
13 | - id: check-builtin-literals
14 | - id: check-byte-order-marker
15 | - id: check-case-conflict
16 | - id: check-merge-conflict
17 | - id: check-symlinks
18 | - id: check-toml
19 | - id: check-vcs-permalinks
20 | - id: check-xml
21 | - id: debug-statements
22 | - id: detect-private-key
23 | - id: mixed-line-ending
24 | - id: fix-encoding-pragma
25 | args: ["--remove"]
26 | - id: check-yaml
27 | - id: check-added-large-files
28 | name: Check for files larger than 5 MB
29 | args: ["--maxkb=5120"]
30 | - id: check-ast
31 | - id: check-docstring-first
32 | - id: name-tests-test
33 | args: ["--django"]
34 | - repo: https://github.com/myint/autoflake
35 | rev: v2.2.1
36 | hooks:
37 | - id: autoflake
38 | args: &autoflake
39 | - --in-place
40 | - --remove-all-unused-imports
41 | - --expand-star-imports
42 | - --remove-duplicate-keys
43 | - --remove-unused-variables
44 | - repo: https://github.com/pycqa/flake8
45 | rev: 6.1.0
46 | hooks:
47 | - &flake8
48 | id: flake8
49 | additional_dependencies:
50 | - flake8-bugbear==23.9.16
51 | - flake8-comprehensions==3.14.0
52 | - flake8-docstrings==1.7.0
53 | - flake8-eradicate==1.5.0
54 | - flake8-fixme==1.1.1
55 | - flake8-implicit-str-concat==0.4.0
56 | - flake8-mutable==1.2.0
57 | - flake8-no-pep420==2.7.0
58 | - flake8-print==5.0.0
59 | - flake8-return==1.2.0
60 | - flake8-simplify==0.21.0
61 | args: ["--config=setup.cfg"]
62 | - repo: https://github.com/psf/black
63 | rev: 23.11.0
64 | hooks:
65 | - id: black
66 | - repo: https://github.com/asottile/add-trailing-comma
67 | rev: v3.1.0
68 | hooks:
69 | - id: add-trailing-comma
70 | args: [--py36-plus]
71 | - repo: https://github.com/pycqa/isort
72 | rev: 5.12.0
73 | hooks:
74 | - id: isort
75 | name: isort (python)
76 | types: [python]
77 | - repo: https://github.com/asottile/pyupgrade
78 | rev: v3.15.0
79 | hooks:
80 | - id: pyupgrade
81 | args:
82 | - "--py310-plus"
83 | - repo: https://github.com/pycqa/bandit
84 | rev: 1.7.5
85 | hooks:
86 | - id: bandit
87 | args: ["-ii", "-ll"]
88 | - repo: https://github.com/Yelp/detect-secrets
89 | rev: v1.4.0
90 | hooks:
91 | - id: detect-secrets
92 | name: detect-secrets - Detect secrets in staged code
93 | args: [
94 | "--baseline",
95 | ".secrets.baseline",
96 | # https://regex101.com/
97 | "--exclude-files 'poetry\\.lock'",
98 | "--exclude-files '\\.secrets\\.baseline'",
99 | "--exclude-files '\\.env\\.template'",
100 | # "--exclude-files '.*\\.ipynb$'",
101 | # "--exclude-files '.*build/'",
102 | "--exclude-secrets 'password|ENTER_PASSWORD_HERE|INSERT_API_KEY_HERE'",
103 | "--exclude-lines 'integrity=*sha'",
104 | ]
105 | # https://pre-commit.com/#regular-expressions
106 | exclude: |
107 | (?x)^(
108 | poetry\.lock|
109 | \.secrets\.baseline|
110 | \.env\.template
111 | )$
112 | # - repo: https://github.com/python-poetry/poetry
113 | # rev: 1.4.0
114 | # hooks:
115 | # - id: poetry-check
116 | # - id: poetry-lock
117 | # args: ["--no-update"]
118 | # - id: poetry-export
119 | # args:
120 | # [
121 | # "-f",
122 | # "requirements.txt",
123 | # "-o",
124 | # "requirements.txt",
125 | # "--without-hashes",
126 | # ]
127 | - repo: https://github.com/pre-commit/mirrors-prettier
128 | rev: v3.1.0
129 | hooks:
130 | - id: prettier
131 | types_or: [yaml]
132 | additional_dependencies:
133 | - "prettier@2.8.4"
134 | - repo: local
135 | hooks:
136 | - id: pylint
137 | name: pylint
138 | entry: pylint chatefficient
139 | # entry: bash -c 'pylint ./path/package1/; pylint ./path/package2/'
140 | language: system
141 | types: [python]
142 | always_run: true
143 | pass_filenames: false
144 | stages: [manual]
145 | - id: pip-audit
146 | name: pip-audit
147 | # entry: pip-audit
148 | # langchain exclusions
149 | entry: pip-audit --ignore-vuln PYSEC-2023-91 --ignore-vuln PYSEC-2023-92
150 | language: system
151 | always_run: true
152 | pass_filenames: false
153 | stages: [manual]
154 | # TODO: Remove once this is resolved
155 | # https://github.com/python-poetry/poetry/issues/2765
156 | # https://github.com/python-poetry/poetry/issues/7075
157 | - id: poetry-lock
158 | name: poetry-lock
159 | # entry: bash -c 'poetry lock && python add_markers.py'
160 | entry: bash -c 'poetry lock --no-update'
161 | language: system
162 | always_run: true
163 | pass_filenames: false
164 | - id: poetry-export
165 | name: poetry-export
166 | entry: poetry export -f requirements.txt --output requirements.txt --without-hashes
167 | language: system
168 | always_run: true
169 | pass_filenames: false
170 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.10.13
2 |
--------------------------------------------------------------------------------
/.secrets.baseline:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.2.0",
3 | "plugins_used": [
4 | {
5 | "name": "ArtifactoryDetector"
6 | },
7 | {
8 | "name": "AWSKeyDetector"
9 | },
10 | {
11 | "name": "AzureStorageKeyDetector"
12 | },
13 | {
14 | "name": "Base64HighEntropyString",
15 | "limit": 4.5
16 | },
17 | {
18 | "name": "BasicAuthDetector"
19 | },
20 | {
21 | "name": "CloudantDetector"
22 | },
23 | {
24 | "name": "GitHubTokenDetector"
25 | },
26 | {
27 | "name": "HexHighEntropyString",
28 | "limit": 3.0
29 | },
30 | {
31 | "name": "IbmCloudIamDetector"
32 | },
33 | {
34 | "name": "IbmCosHmacDetector"
35 | },
36 | {
37 | "name": "JwtTokenDetector"
38 | },
39 | {
40 | "name": "KeywordDetector",
41 | "keyword_exclude": ""
42 | },
43 | {
44 | "name": "MailchimpDetector"
45 | },
46 | {
47 | "name": "NpmDetector"
48 | },
49 | {
50 | "name": "PrivateKeyDetector"
51 | },
52 | {
53 | "name": "SendGridDetector"
54 | },
55 | {
56 | "name": "SlackDetector"
57 | },
58 | {
59 | "name": "SoftlayerDetector"
60 | },
61 | {
62 | "name": "SquareOAuthDetector"
63 | },
64 | {
65 | "name": "StripeDetector"
66 | },
67 | {
68 | "name": "TwilioKeyDetector"
69 | }
70 | ],
71 | "filters_used": [
72 | {
73 | "path": "detect_secrets.filters.allowlist.is_line_allowlisted"
74 | },
75 | {
76 | "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
77 | "min_level": 2
78 | },
79 | {
80 | "path": "detect_secrets.filters.heuristic.is_indirect_reference"
81 | },
82 | {
83 | "path": "detect_secrets.filters.heuristic.is_likely_id_string"
84 | },
85 | {
86 | "path": "detect_secrets.filters.heuristic.is_lock_file"
87 | },
88 | {
89 | "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
90 | },
91 | {
92 | "path": "detect_secrets.filters.heuristic.is_potential_uuid"
93 | },
94 | {
95 | "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
96 | },
97 | {
98 | "path": "detect_secrets.filters.heuristic.is_sequential_string"
99 | },
100 | {
101 | "path": "detect_secrets.filters.heuristic.is_swagger_file"
102 | },
103 | {
104 | "path": "detect_secrets.filters.heuristic.is_templated_secret"
105 | },
106 | {
107 | "path": "detect_secrets.filters.regex.should_exclude_file",
108 | "pattern": [
109 | "poetry\\.lock",
110 | "\\.secrets\\.baseline",
111 | "\\.env\\.template"
112 | ]
113 | },
114 | {
115 | "path": "detect_secrets.filters.regex.should_exclude_line",
116 | "pattern": [
117 | "integrity=*sha"
118 | ]
119 | },
120 | {
121 | "path": "detect_secrets.filters.regex.should_exclude_secret",
122 | "pattern": [
123 | "password|ENTER_PASSWORD_HERE|INSERT_API_KEY_HERE"
124 | ]
125 | }
126 | ],
127 | "results": {},
128 | "generated_at": "2023-06-21T19:45:25Z"
129 | }
130 |
--------------------------------------------------------------------------------
/.venv:
--------------------------------------------------------------------------------
1 | chatefficient
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-python.python",
4 | "ms-python.vscode-pylance",
5 | "esbenp.prettier-vscode",
6 | "wayou.vscode-todo-highlight",
7 | "bungcip.better-toml"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // editor & autoformatter settings
3 | "editor.bracketPairColorization.enabled": true,
4 | "editor.formatOnSave": true,
5 | "editor.guides.bracketPairs": "active",
6 | "editor.trimAutoWhitespace": true,
7 | "files.trimTrailingWhitespace": true,
8 | "prettier.enable": true,
9 |
10 | // python - black
11 | "python.formatting.provider": "none",
12 | "python.formatting.blackArgs": [],
13 |
14 | // python - other
15 | "python.languageServer": "Pylance",
16 |
17 | // python - linting & static analysis
18 | "python.analysis.extraPaths": ["chatefficient"],
19 | "python.analysis.typeCheckingMode": "off",
20 | "python.linting.enabled": true,
21 | "python.linting.flake8Enabled": true,
22 | "python.linting.flake8Args": [],
23 | "python.linting.mypyEnabled": true,
24 | "python.linting.pylintEnabled": true,
25 | "python.linting.pylintArgs": [],
26 |
27 | // Python files only
28 | "[python]": {
29 | // isort on save
30 | "editor.codeActionsOnSave": {
31 | "source.organizeImports": "explicit"
32 | },
33 | // Stop the 'Black does not support "Format Selection"' message every
34 | // time you paste something (https://stackoverflow.com/a/63612401/3279076)
35 | "editor.formatOnPaste": false,
36 | "editor.defaultFormatter": "ms-python.black-formatter"
37 | },
38 |
39 | // python - pytest (https://code.visualstudio.com/docs/python/testing)
40 | "python.testing.unittestEnabled": false,
41 | "python.testing.pytestEnabled": true,
42 | "python.testing.pytestArgs": ["tests"],
43 |
44 | // git
45 | "git.enabled": true,
46 |
47 | // file associations
48 | "files.associations": {
49 | "**/*.html": "html",
50 | "**/*.js": "javascript",
51 | "**/requirements{/**,*}.{txt,in}": "pip-requirements"
52 | },
53 |
54 | // markdownlint
55 | "markdownlint.run": "onSave",
56 | "markdownlint.ignore": [],
57 | "markdownlint.config": {
58 | // MD003 - Heading style
59 | "MD003": {
60 | "style": "atx"
61 | },
62 | // MD007 - Unordered list indentation
63 | "MD007": {
64 | "start_indented": true,
65 | "indent": 2
66 | },
67 | // MD012 - Multiple consecutive blank lines
68 | "MD012": false,
69 | // MD022 - Headings should be surrounded by blank lines
70 | "MD022": false,
71 | // MD024 - Multiple headings with the same content
72 | "MD024": false,
73 | // MD032 - Lists should be surrounded by blank lines
74 | "MD032": false,
75 | // MD046 - Code block style
76 | "MD046": {
77 | "style": "fenced"
78 | },
79 | "no-hard-tabs": true
80 | },
81 | }
82 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Coefficient
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ChatEfficient: How To Build Your Own Private ChatGPT Using Streamlit, LangChain & Vicuna
2 |
3 | [](https://github.com/CoefficientSystems/chat-efficient/actions/workflows/main.yaml)
4 |
5 | DIY ChatGPT using Streamlit, LangChain and open-source LLMs
6 |
7 | 
8 |
9 | ## 🎬 Watch again
10 |
11 | You can watch the [webinar recording via this link](https://app.session.com/coefficientai/Munch-and-Learn-Tutorial:-How-To-Build-Your-Own-Private-ChatGPT-Using-Streamlit-LangChain-and-Vicuna?s=1&passcode=308697).
12 |
13 | You can see all our [upcoming and previous events here](https://coefficient.ai/events).
14 |
15 | If you'd like to work with Coefficient to build your own AI-powered apps, please reach out to us at
16 | [contact@coefficient.ai](contact@coefficient.ai) or [complete our enquiry form](https://coefficient.ai/contact).
17 |
18 | If you'd like to register to be notified about upcoming events, you can also do that [here](https://coefficient.ai/contact).
19 |
20 | ## 💪 Learn by doing
21 |
22 | 1. [Learn OpenAI](notebooks/openai.ipynb)
23 | 2. [Learn Streamlit](chatefficient/streamlit_demo.py)
24 | 3. [OpenAI + Streamlit = GPT4 chatbot](chatefficient/app_openai.py)
25 | 4. [Learn LangChain](notebooks/LangChain.ipynb)
26 | 5. [OpenAI + LangChain = local chatbot](chatefficient/app_langchain.py)
27 | 6. [Learn Chroma](notebooks/Chroma.ipynb)
28 |
29 | ## ✅ Project cheatsheet
30 |
31 | - **pre-commit:** `pre-commit run --all-files`
32 | - **pytest:** `pytest` or `pytest -s`
33 | - **coverage:** `coverage run -m pytest` or `coverage html`
34 | - **poetry sync:** `poetry install --no-root --sync`
35 | - **updating requirements:** see [docs/updating_requirements.md](docs/updating_requirements.md)
36 | - **create towncrier entry:** `towncrier create 123.added --edit`
37 |
38 |
39 | ## 🏗 Initial project setup
40 |
41 | 1. See [docs/getting_started.md](docs/getting_started.md) or [docs/quickstart.md](docs/quickstart.md)
42 | for how to get up & running.
43 | 2. Check [docs/project_specific_setup.md](docs/project_specific_setup.md) for project specific setup.
44 | 3. See [docs/using_poetry.md](docs/using_poetry.md) for how to update Python requirements using
45 | [Poetry](https://python-poetry.org/).
46 | 4. See [docs/detect_secrets.md](docs/detect_secrets.md) for more on creating a `.secrets.baseline`
47 | file using [detect-secrets](https://github.com/Yelp/detect-secrets).
48 | 5. See [docs/using_towncrier.md](docs/using_towncrier.md) for how to update the `CHANGELOG.md`
49 | using [towncrier](https://github.com/twisted/towncrier).
50 |
--------------------------------------------------------------------------------
/chatefficient/__init__.py:
--------------------------------------------------------------------------------
1 | """Generated using coefficient-cookiecutter"""
2 |
--------------------------------------------------------------------------------
/chatefficient/app_langchain.py:
--------------------------------------------------------------------------------
1 | """Streamlit app using LangChain + locally-running Vicuna.
2 |
3 | Examples:
4 | $ streamlit run chatefficient/app_langchain.py
5 | """
6 | import streamlit as st
7 | from joblib import Memory
8 | from langchain.callbacks.manager import CallbackManager
9 | from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
10 | from langchain.chains import LLMChain
11 | from langchain.llms import LlamaCpp
12 | from langchain.memory import ConversationBufferWindowMemory
13 | from langchain.prompts import PromptTemplate
14 | from streamlit_chat import message
15 |
16 | LOCATION = "./cachedir"
17 | MEMORY = Memory(LOCATION, verbose=0)
18 |
19 | # Make sure the model path is correct for your system!
20 | llm = LlamaCpp(
21 | n_ctx=512 * 2,
22 | model_path="./models/llama-2-7b-chat.Q4_K_M.gguf",
23 | n_gpu_layers=40, # Change this value based on your model and your GPU VRAM pool.
24 | n_batch=512, # Should be between 1 and n_ctx, consider the amount of VRAM in your GPU.
25 | callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),
26 | verbose=True,
27 | model_kwargs={"max_new_tokens": 64 * 4},
28 | stop=["Human:", "Input:", "Mind Reader:"],
29 | )
30 |
31 |
32 | # @MEMORY.cache
33 | def generate_response(human_input):
34 | """Prompt LangChain for a chat completion response."""
35 | chain = st.session_state["chain"]
36 | response = chain.predict(human_input=human_input)
37 | st.session_state["chain"] = chain
38 |
39 | return response
40 |
41 |
42 | # Initialize session state variables
43 | if "chain" not in st.session_state:
44 | template = """
45 | You are a mindreader with magical abilities.
46 | You are very over-dramatic and speak like a mysterious shaman.
47 | You will be given something to guess, such as an animal, or a famous person.
48 | You will ask a question, I will provide an answer, and then you will ask another question.
49 | Try to ask questions that narrow down what the answer might be.
50 | If you are very confident, you can guess what it is.
51 | If your guess is wrong, then you must ask another question to help narrow it down.
52 | Repeat this until you I tell you that you have the right answer.
53 | Your goal is to find the right answer in as few questions as possible. Only make a guess
54 | when you are confident, otherwise ask more questions that narrow down the possibilities.
55 |
56 | {history}
57 | Human: {human_input}
58 | Assistant:"""
59 |
60 | prompt = PromptTemplate(input_variables=["history", "human_input"], template=template)
61 |
62 | chatgpt_chain = LLMChain(
63 | llm=llm,
64 | prompt=prompt,
65 | verbose=True,
66 | memory=ConversationBufferWindowMemory(k=10),
67 | )
68 | st.session_state["chain"] = chatgpt_chain
69 |
70 |
71 | if "generated" not in st.session_state:
72 | st.session_state["generated"] = []
73 |
74 | if "past" not in st.session_state:
75 | st.session_state["past"] = []
76 |
77 |
78 | # Containers
79 | response_container = st.container()
80 | chat_container = st.container()
81 |
82 |
83 | with chat_container:
84 | if prompt := st.chat_input("Say something"):
85 | output = generate_response(prompt).strip()
86 | st.session_state["past"].append(prompt)
87 | st.session_state["generated"].append(output)
88 |
89 |
90 | INITIAL_MESSAGE = """
91 | 🧞♂ I am a mind reader with magical abilities! 🔮
92 | 🤔 Give me a category e.g. animal, or a famous person.
93 | 💬 I will ask questions and guess what you are thinking of!
94 | """
95 |
96 | with response_container:
97 | message(INITIAL_MESSAGE)
98 | if st.session_state["generated"]:
99 | for i in range(len(st.session_state["generated"])):
100 | st.chat_message("user").write(st.session_state["past"][i])
101 | st.chat_message("ai").write(st.session_state["generated"][i])
102 |
--------------------------------------------------------------------------------
/chatefficient/app_openai.py:
--------------------------------------------------------------------------------
1 | """app.py
2 |
3 | Examples:
4 | $ streamlit run chatefficient/app_openai.py
5 | """
6 | import streamlit as st
7 | from joblib import Memory
8 | from openai import OpenAI
9 |
10 | LOCATION = "./cachedir"
11 | MEMORY = Memory(LOCATION, verbose=0)
12 | CLIENT = OpenAI()
13 |
14 |
15 | @MEMORY.cache
16 | def generate_response(prompt, model="gpt-3.5-turbo"):
17 | """Prompt GPT API for a chat completion response."""
18 | st.session_state["context"].append({"role": "user", "content": prompt})
19 |
20 | completion = CLIENT.chat.completions.create(model=model, messages=st.session_state["context"])
21 | response = completion.choices[0].message.content
22 | st.session_state["context"].append({"role": "assistant", "content": response})
23 |
24 | return response
25 |
26 |
27 | # Initialize session state variables
28 | if "context" not in st.session_state:
29 | st.session_state["context"] = [{"role": "system", "content": "You are a helpful assistant."}]
30 |
31 | if "generated" not in st.session_state:
32 | st.session_state["generated"] = []
33 |
34 | if "past" not in st.session_state:
35 | st.session_state["past"] = []
36 |
37 |
38 | # Containers
39 | response_container = st.container()
40 | chat_container = st.container()
41 |
42 |
43 | with chat_container:
44 | if prompt := st.chat_input("Say something"):
45 | output = generate_response(prompt)
46 | st.session_state["past"].append(prompt)
47 | st.session_state["generated"].append(output)
48 |
49 | if st.session_state["generated"]:
50 | with response_container:
51 | for i in range(len(st.session_state["generated"])):
52 | st.chat_message("user").write(st.session_state["past"][i])
53 | st.chat_message("ai").write(st.session_state["generated"][i])
54 |
--------------------------------------------------------------------------------
/chatefficient/app_translator.py:
--------------------------------------------------------------------------------
1 | """Streamlit app using LangChain + locally-running Vicuna.
2 |
3 | Examples:
4 | $ streamlit run chatefficient/app_translator.py
5 | """
6 | import streamlit as st
7 | from joblib import Memory
8 | from langchain.callbacks.manager import CallbackManager
9 | from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
10 | from langchain.chains import LLMChain
11 | from langchain.llms import LlamaCpp
12 | from langchain.prompts import PromptTemplate
13 | from streamlit_chat import message
14 |
15 | LOCATION = "./cachedir"
16 | MEMORY = Memory(LOCATION, verbose=0)
17 |
18 | llm = LlamaCpp(
19 | n_ctx=512 * 2,
20 | model_path="./models/llama-2-13b-chat.Q4_K_M.gguf",
21 | n_gpu_layers=20, # Change this value based on your model and your GPU VRAM pool.
22 | n_batch=512, # Should be between 1 and n_ctx, consider the amount of VRAM in your GPU.
23 | callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),
24 | verbose=True,
25 | model_kwargs={"max_new_tokens": 64 * 4},
26 | stop=["Human:", "Input:"],
27 | )
28 |
29 |
30 | # @MEMORY.cache
31 | def generate_response(human_input):
32 | """Prompt LangChain for a chat completion response."""
33 | chain = st.session_state["chain"]
34 | response = chain.predict(human_input=human_input)
35 | st.session_state["chain"] = chain
36 |
37 | return response
38 |
39 |
40 | # Initialize session state variables
41 | if "chain" not in st.session_state:
42 | template = """
43 | You are my Mandarin Chinese teacher. I will give you an input in English, and you will
44 | respond with the corresponding translation in Mandarin Chinese in both pinyin and hanzi.
45 |
46 | Input: {human_input}
47 | Response:"""
48 |
49 | prompt = PromptTemplate(input_variables=["human_input"], template=template)
50 | chatgpt_chain = LLMChain(llm=llm, prompt=prompt, verbose=True)
51 | st.session_state["chain"] = chatgpt_chain
52 |
53 |
54 | if "generated" not in st.session_state:
55 | st.session_state["generated"] = []
56 |
57 | if "past" not in st.session_state:
58 | st.session_state["past"] = []
59 |
60 |
61 | # Containers
62 | response_container = st.container()
63 | chat_container = st.container()
64 |
65 |
66 | with chat_container:
67 | with st.form(key="my_form", clear_on_submit=True):
68 | user_input = st.text_area("You:", key="input", height=100)
69 | submit_button = st.form_submit_button(label="Send")
70 |
71 | if submit_button and user_input:
72 | output = generate_response(user_input).strip()
73 | st.session_state["past"].append(user_input)
74 | st.session_state["generated"].append(output)
75 |
76 |
77 | INITIAL_MESSAGE = (
78 | "You are my Mandarin Chinese teacher. I will give you an input in English, and "
79 | "you will respond with the corresponding translation in Mandarin Chinese in "
80 | "both pinyin and hanzi."
81 | )
82 |
83 | with response_container:
84 | message(INITIAL_MESSAGE)
85 | if st.session_state["generated"]:
86 | for i in range(len(st.session_state["generated"])):
87 | message(st.session_state["past"][i], is_user=True, key=f"{i}_user")
88 | message(st.session_state["generated"][i], key=f"{i}")
89 |
--------------------------------------------------------------------------------
/chatefficient/streamlit_demo.py:
--------------------------------------------------------------------------------
1 | """Streamlit 101.
2 |
3 | Docs:
4 | - https://docs.streamlit.io/library/get-started
5 | - https://docs.streamlit.io/library/api-reference/session-state
6 | - https://discuss.streamlit.io/t/new-component-streamlit-chat-a-new-way-to-create-chatbots/20412
7 |
8 | Examples:
9 | $ streamlit hello
10 | $ streamlit run chatefficient/streamlit_demo.py
11 |
12 | Visit https://YOUR-CUSTOM-URL.sagemaker.aws/proxy/{YOUR-PROXY-HERE}/
13 | e.g. https://obt-workshop.notebook.eu-west-2.sagemaker.aws/proxy/8501/
14 | """
15 |
16 |
17 | import pandas as pd
18 | import streamlit as st
19 |
20 | df = pd.DataFrame({"first column": [1, 2, 3, 4], "second column": [10, 20, 30, 40]})
21 |
22 |
23 | # ######### Part 1: st.write() #########
24 | # st.write(df)
25 |
26 | ######### Part 2: Magic Commands #########
27 | # df
28 |
29 |
30 | ######### Part 3: Plotting Charts #########
31 | # import numpy as np
32 | # chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])
33 | # st.line_chart(chart_data)
34 |
35 |
36 | ######### Part 4: Maps #########
37 | # old_street = [51.525, -0.088]
38 | # map_data = pd.DataFrame(
39 | # np.random.randn(50, 2) / [150, 150] + old_street,
40 | # columns=["lat", "lon"]
41 | # )
42 | # st.map(map_data)
43 |
44 |
45 | ######### Part 5: Widgets #########
46 | # x = st.slider("x") # 👈 this is a widget
47 | # st.write(x, "squared is", x * x)
48 |
49 | # df = pd.DataFrame({"first column": [1, 2, 3, 4], "second column": [10, 20, 30, 40]})
50 | # option = st.selectbox("Which number do you like best?", df["first column"])
51 | # st.write("You selected: ", option)
52 |
53 |
54 | ######### Part 6: Sidebar #########
55 | # # Add a selectbox to the sidebar:
56 | # add_selectbox = st.sidebar.selectbox(
57 | # "How would you like to be contacted?",
58 | # ("Email", "Home phone", "Mobile phone"),
59 | # )
60 |
61 | # # Add a slider to the sidebar:
62 | # add_slider = st.sidebar.slider("Select a range of values", 0.0, 100.0, (25.0, 75.0))
63 |
64 |
65 | ######### Part 7: Session State #########
66 | # # Initialization
67 | # if "name" not in st.session_state:
68 | # st.session_state["name"] = "DEFAULT NAME"
69 |
70 | # st.sidebar.text_input("Your name", key="name")
71 |
72 | # # You can access the value at any point with:
73 | # st.session_state.name
74 |
75 |
76 | ######### Part 8: Streamlit Chat #########
77 |
78 | st.chat_message("ai").write("Hello human 👋")
79 |
80 | st.chat_message("human").write("Hey there bot!") # user input
81 |
82 | st.chat_message("ai").write("I hope your talk is going OK?")
83 |
84 | st.chat_message("human").write("Me too!")
85 |
--------------------------------------------------------------------------------
/data/README.md:
--------------------------------------------------------------------------------
1 | # `data` folder overview
2 |
3 | Any data that needs to be stored locally should be saved in this location. This folder,
4 | and its sub-folders, are not version-controlled.
5 |
6 | The sub-folders should be used as follows:
7 |
8 | - `external`: any data that will not be processed at all, such as reference data;
9 | - `raw`: any raw data before any processing;
10 | - `interim`: any raw data that has been partially processed and, for whatever reason,
11 | needs to be stored before further processing is completed; and
12 | - `processed`: any raw or interim data that has been fully processed into its final
13 | state.
14 |
--------------------------------------------------------------------------------
/data/external/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/data/external/.gitkeep
--------------------------------------------------------------------------------
/data/interim/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/data/interim/.gitkeep
--------------------------------------------------------------------------------
/data/processed/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/data/processed/.gitkeep
--------------------------------------------------------------------------------
/data/raw/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/data/raw/.gitkeep
--------------------------------------------------------------------------------
/docs/aws_sagemaker_quickstart.md:
--------------------------------------------------------------------------------
1 | # Quickstart
2 |
3 | This document contains instructions to get up & running with this repo using AWS SageMaker.
4 |
5 | ## Part 1: Create a SageMaker Notebook
6 |
7 | From the AWS Console:
8 | - Go to Service Quotas and request a GPU quota increase for `ml.g4dn.xlarge` for 1 GPU for both
9 | notebook instance usage and for Studio KernelGateway Apps.
10 | - Launch a SageMaker Notebook from SageMaker > Notebook > Notebook instances > Create notebook instance
11 | - Select `ml.g4dn.xlarge` instance type (see [https://aws.amazon.com/sagemaker/pricing/] for pricing)
12 |
13 |
14 | ## Part 2: Install Python dependencies
15 |
16 | Create a new terminal and run the following:
17 |
18 | ```sh
19 | # Switch to a bash shell
20 | bash
21 |
22 | # Git clone the repo
23 | cd ~/SageMaker
24 | git clone https://github.com/CoefficientSystems/chat-efficient
25 |
26 | # Change to the repo root
27 | cd ~/SageMaker/chat-efficient
28 |
29 | # Activate a Python 3.10 environment pre-configured with PyTorch
30 | conda activate pytorch_p310
31 |
32 | # Check Python version
33 | python --version
34 |
35 | # Install the repo's declared dependencies
36 | pip install -r requirements.txt
37 |
38 | # What GPUs are installed on this instance?
39 | nvidia-smi
40 |
41 | # Install llama-cpp-python
42 | # Reference: https://llama-cpp-python.readthedocs.io/en/latest/
43 | pip uninstall llama-cpp-python -y
44 | CMAKE_ARGS="-DLLAMA_CUBLAS=on" pip install llama-cpp-python
45 |
46 | # Download the Llama-2 model from Hugging Face (7 billion parameters, 4.08GB file size, up to 7.2GB RAM required to run)
47 | huggingface-cli download TheBloke/Llama-2-7b-Chat-GGUF llama-2-7b-chat.Q4_K_M.gguf --local-dir . --local-dir-use-symlinks False
48 | mv ./llama-2-7b-chat.Q4_K_M.gguf ./models/
49 |
50 | # Alternatively, if you have enough memory, you can download the 13B Llama-2 model (7.87GB file size, up to 10.37GB RAM required)
51 | huggingface-cli download TheBloke/Llama-2-13b-Chat-GGUF llama-2-13b-chat.Q4_K_M.gguf --local-dir . --local-dir-use-symlinks False
52 | mv ./llama-2-13b-chat.Q4_K_M.gguf ./models/
53 | ```
54 |
55 | Test it out in `ipython`:
56 |
57 | ```python
58 | from llama_cpp import Llama
59 | llm = Llama(model_path="./models/llama-2-7b-chat.Q4_K_M.gguf")
60 | output = llm("Q: Name the planets in the solar system? A: ", max_tokens=64, stop=["Q:", "\n"], echo=True)
61 | print(output)
62 | print(output['choices'][0]['text'])
63 | ```
64 |
65 | Type ctrl+D to quit.
66 |
67 |
68 | ## Part 3: Add OpenAI key
69 |
70 | ```sh
71 | cp .env.template env
72 | # Edit the file "env" to specify your OpenAI key. Save & close.
73 | mv env .env
74 |
75 | # These will now be loaded as environment variables when we run `load_dotenv()` from the python-dotenv package.
76 | ```
77 |
--------------------------------------------------------------------------------
/docs/detect_secrets.md:
--------------------------------------------------------------------------------
1 | # Using the `detect-secrets` pre-commit hook
2 |
3 | [We use `detect-secrets` to check that no secrets are accidentally committed](detect-secrets). The
4 | `detect-secrets` package does its best to prevent accidental committing of secrets, but it may miss
5 | things. Instead, focus on good software development practices! See the [definition of a secret for
6 | further information](#definition-of-a-secret-according-to-detect-secrets).
7 |
8 |
9 | ## Initial setup
10 |
11 | This pre-commit hook requires you to generate a baseline file if one is not already present within
12 | the root directory. To create the baseline file, run the following at the root of the repository:
13 |
14 | ```shell
15 | detect-secrets scan > .secrets.baseline
16 | ```
17 |
18 | Next, audit the baseline that has been generated by running:
19 |
20 | ```shell
21 | detect-secrets audit .secrets.baseline
22 | ```
23 |
24 | When you run this command, you'll enter an interactive console. This will present you with a list
25 | of high-entropy string and/or anything which could be a secret. It will then ask you to verify
26 | whether this is the case. This allows the hook to remember false positives in the future, and alert
27 | you to new secrets.
28 |
29 |
30 | ## Definition of a "secret" according to `detect-secrets`
31 |
32 | The `detect-secrets` documentation, as of January 2021, says it works:
33 |
34 | > ...by running periodic diff outputs against heuristically crafted \[regular
35 | > expression\] statements, to identify whether any new secret has been committed.
36 |
37 | This means it uses regular expression patterns to scan your code changes for anything that looks
38 | like a secret according to the patterns. By definition, there are only a limited number of
39 | patterns, so the `detect-secrets` package cannot detect every conceivable type of secret.
40 |
41 | To understand what types of secrets will be detected, read the `detect-secrets` documentation on
42 | caveats, and the list of supported plugins. Also, you should use secret variable names with words
43 | that will trip the KeywordDetector plugin; see the
44 | [`DENYLIST` variable for the full list of words][detect-secrets-keyword-detector].
45 |
46 |
47 | ## If `pre-commit` detects secrets during commit
48 |
49 | If `pre-commit` detects any secrets when you try to create a commit, it will detail what it found
50 | and where to go to check the secret.
51 |
52 | If the detected secret is a false positive, there are two options to resolve this, and prevent your
53 | commit from being blocked:
54 |
55 | - [inline allowlisting of false positives (recommended)](#inline-allowlisting-recommended); or
56 | - [updating the `.secrets.baseline` to include the false positives](#updating-secretsbaseline).
57 |
58 | In either case, if an actual secret is detected (or a combination of actual secrets and false
59 | positives), first remove the actual secret. Then following either of these processes.
60 |
61 | ### Inline allowlisting (recommended)
62 |
63 | To exclude a false positive, add a `pragma` comment such as:
64 |
65 | ```python
66 | secret = "Password123" # pragma: allowlist secret
67 | ```
68 |
69 | or
70 |
71 | ```python
72 | # pragma: allowlist nextline secret
73 | secret = "Password123"
74 | ```
75 |
76 | If the detected secret is actually a secret (or other sensitive information), remove the secret and
77 | re-commit; there is no need to add any `pragma` comments.
78 |
79 | If your commit contains a mixture of false positives and actual secrets, remove the actual secrets
80 | first before adding `pragma` comments to the false positives.
81 |
82 |
83 | ### Updating `.secrets.baseline`
84 |
85 | To exclude a false positive, you can also [update the `.secrets.baseline` by repeating the same two
86 | commands as in the initial setup](#using-the-detect-secrets-pre-commit-hook).
87 |
88 | During auditing, if the detected secret is actually a secret (or other sensitive information),
89 | remove the secret and re-commit. There is no need to update the `.secrets.baseline` file in this
90 | case.
91 |
92 | If your commit contains a mixture of false positives and actual secrets, remove the actual secrets
93 | first before updating and auditing the `.secrets.baseline` file.
94 |
95 |
96 | [detect-secrets]: https://github.com/Yelp/detect-secrets
97 | [detect-secrets-plugins]: https://github.com/Yelp/detect-secrets#currently-supported-plugins
98 |
99 | ---
100 |
101 | Credit: This document has been adapted from the
102 | [govukcookiecutter](https://github.com/best-practice-and-impact/govcookiecutter/).
103 |
--------------------------------------------------------------------------------
/docs/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | This document contains instructions to get a fully working development environment for running this repo.
4 |
5 |
6 | ## 1. pyenv
7 |
8 | Install here: [https://github.com/pyenv/pyenv#homebrew-on-macos]
9 |
10 | Configure by adding the following to your `~/.zshrc` or equivalent:
11 |
12 | ```sh
13 | # Pyenv environment variables
14 | export PYENV_ROOT="$HOME/.pyenv"
15 | export PATH="$PYENV_ROOT/bin:$PATH"
16 |
17 | # Pyenv initialization
18 | eval "$(pyenv init --path)"
19 | eval "$(pyenv init -)"
20 | ```
21 |
22 | Basic usage:
23 |
24 | ```sh
25 | # Check Python versions
26 | pyenv install --list
27 |
28 | # Install the Python version defined in this repo
29 | pyenv install $(cat .python-version)
30 |
31 | # See installed Python versions
32 | pyenv versions
33 | ```
34 |
35 |
36 | ## 2. [pyenv-virtualenvwrapper](https://github.com/pyenv/pyenv-virtualenvwrapper)
37 |
38 | ```sh
39 | # Install with homebrew (recommended if you installed pyenv with homebrew)
40 | brew install pyenv-virtualenvwrapper
41 | ```
42 |
43 | Configure by adding the following to your `~/.zshrc` or equivalent:
44 |
45 | ```sh
46 | # pyenv-virtualenvwrapper
47 | export PYENV_VIRTUALENVWRAPPER_PREFER_PYVENV="true"
48 | export WORKON_HOME=$HOME/.virtualenvs
49 | export PROJECT_HOME=$HOME/code # <- change this to wherever you store your repos
50 | export VIRTUALENVWRAPPER_PYTHON=$HOME/.pyenv/shims/python
51 | pyenv virtualenvwrapper_lazy
52 | ```
53 |
54 | Test everything is working by opening a new shell (e.g. new Terminal window):
55 |
56 | ```sh
57 | # Change to the Python version you just installed
58 | pyenv shell $(cat .python-version)
59 | # This only needs to be run once after installing a new Python version through pyenv
60 | # in order to initialise virtualenvwrapper for this Python version
61 | python -m pip install --upgrade pip
62 | python -m pip install virtualenvwrapper
63 | pyenv virtualenvwrapper_lazy
64 |
65 | # Create test virtualenv (if this doesn't work, try sourcing ~/.zshrc or opening new shell)
66 | mkvirtualenv venv_test
67 | which python
68 | python -V
69 |
70 | # Deactivate & remove test virtualenv
71 | deactivate
72 | rmvirtualenv venv_test
73 | ```
74 |
75 |
76 | ## 3. Get the repo & initialise the repo environment
77 |
78 | ⚠️ N.B. You should replace `REPO_GIT_URL` here with your actual URL to your GitHub repo.
79 |
80 | ```sh
81 | git clone ${REPO_GIT_URL}
82 | pyenv shell $(cat .python-version)
83 |
84 | # Make a new virtual environment using the Python version & environment name specified in the repo
85 | mkvirtualenv -p python$(cat .python-version) $(cat .venv)
86 | python -V # check this is the correct version of Python
87 | python -m pip install --upgrade pip
88 | ```
89 |
90 |
91 | ## 4. Install Python requirements into the virtual environment using [Poetry](https://python-poetry.org/docs/)
92 |
93 | Install Poetry onto your system by following the instructions here: [https://python-poetry.org/docs/]
94 |
95 | Note that Poetry "lives" outside of project/environment, and if you follow the recommended install
96 | process it will be installed isolated from the rest of your system.
97 |
98 | ```sh
99 | # Update Poetry regularly as you would any other system-level tool. Poetry is environment agnostic,
100 | # it doesn't matter if you run this command inside/outside the virtualenv.
101 | poetry self update
102 |
103 | # This command should be run inside the virtualenv.
104 | poetry install --no-root --sync
105 | ```
106 |
107 |
108 | ## 5. [zsh-autoswitch-virtualenv](https://github.com/MichaelAquilina/zsh-autoswitch-virtualenv)
109 |
110 | Download with `git clone "https://github.com/MichaelAquilina/zsh-autoswitch-virtualenv.git" "$ZSH_CUSTOM/plugins/autoswitch_virtualenv"`
111 |
112 | Configure by adding the following to your `~/.zshrc` or equivalent:
113 |
114 | ```sh
115 | # Find line containing plugins=(git) and replace with below
116 | plugins=(git autoswitch_virtualenv)
117 | ```
118 |
119 | Check it's working by cd-ing into & out of the repo. The environment should load & unload respectively.
120 |
121 |
122 | ## 6. Install [Poetry Up](https://github.com/MousaZeidBaker/poetry-plugin-up)
123 |
124 | This is a useful Poetry plugin that updates dependencies and bumps their versions in the
125 | pyproject.toml file. The version constraints are respected, unless the `--latest` flag is passed, in
126 | which case dependencies are updated to latest available compatible versions.
127 |
128 | ```sh
129 | # Installation
130 | poetry self add poetry-plugin-up
131 |
132 | # Usage
133 | poetry up
134 | poetry up --latest
135 | ```
136 |
137 |
138 | ## 7. Add secrets into .env
139 |
140 | - Run `cp .env.template .env` and update the secrets.
141 | - [Install direnv](https://direnv.net/) to autoload environment variables specified in `.env`
142 | - Run `direnv allow` to authorise direnv to load the secrets from `.env` into the environment
143 | (these will unload when you `cd` out of the repo; note that you will need to re-run this
144 | command whenever you change `.env`)
145 |
146 |
147 | ## 8. Initialise the `detect-secrets` pre-commit hook
148 |
149 | We use [`detect-secrets`](https://github.com/Yelp/detect-secrets) to check that no secrets are
150 | accidentally committed. Please read [docs/detect_secrets.md](docs/detect_secrets.md) for more information.
151 |
152 |
153 | ```shell
154 | # Generate a baseline
155 | detect-secrets scan > .secrets.baseline
156 |
157 | # You may want to check/amend the exclusions in `.pre-commit-config.yaml` e.g.
158 | detect-secrets --verbose scan \
159 | --exclude-files 'poetry\.lock' \
160 | --exclude-files '\.secrets\.baseline' \
161 | --exclude-files '\.env\.template' \
162 | --exclude-secrets 'password|ENTER_PASSWORD_HERE|INSERT_API_KEY_HERE' \
163 | --exclude-lines 'integrity=*sha' \
164 | > .secrets.baseline
165 |
166 | # Audit the generated baseline
167 | detect-secrets audit .secrets.baseline
168 | ```
169 |
170 | When you run this command, you'll enter an interactive console. This will present you with a list
171 | of high-entropy string and/or anything which could be a secret. It will then ask you to verify
172 | whether this is the case. This allows the hook to remember false positives in the future, and alert
173 | you to new secrets.
174 |
175 |
176 | ## 9. Project-specific setup
177 |
178 | Please check [docs/project_specific_setup.md](docs/project_specific_setup.md) for further instructions.
179 |
--------------------------------------------------------------------------------
/docs/project_specific_setup.md:
--------------------------------------------------------------------------------
1 | # Project-specific setup
2 |
3 | This document contains setup instructions specifically for this project only. This design enables
4 | us to keep other docs easily aligned with future upstream changes to
5 | [coefficient-cookiecutter](https://github.com/CoefficientSystems/coefficient-cookiecutter/).
6 |
7 |
8 | ## llama-cpp-python
9 |
10 | Follow the instructions here: https://abetlen.github.io/llama-cpp-python/macos_install/
11 |
12 | ```sh
13 | pip uninstall llama-cpp-python -y
14 | CMAKE_ARGS="-DLLAMA_METAL=on" FORCE_CMAKE=1 pip install -U llama-cpp-python --no-cache-dir
15 | pip install 'llama-cpp-python[server]'
16 | ```
17 |
18 | Download a model file, see the following for advice:
19 | - [https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF#provided-files]
20 | - [https://huggingface.co/TheBloke/Llama-2-13B-Chat-GGUF#provided-files]
21 | - [https://huggingface.co/TheBloke/Llama-2-70B-Chat-GGUF#provided-files]
22 |
23 | ```sh
24 | # Llama-2 (7 billion parameters, 4.08GB file size, up to 7.2GB RAM required to run)
25 | huggingface-cli download TheBloke/Llama-2-7b-Chat-GGUF llama-2-7b-chat.Q4_K_M.gguf --local-dir . --local-dir-use-symlinks False
26 | mv ./llama-2-7b-chat.Q4_K_M.gguf ./models/
27 |
28 | # Llama-2 (13 billion parameters, 7.87GB file size, up to 10.37GB RAM required to run)
29 | huggingface-cli download TheBloke/Llama-2-13b-Chat-GGUF llama-2-13b-chat.Q4_K_M.gguf --local-dir . --local-dir-use-symlinks False
30 | mv ./llama-2-13b-chat.Q4_K_M.gguf ./models/
31 |
32 | # Llama-2 (70 billion parameters, 41.42GB file size, up to 44GB RAM required to run)
33 | huggingface-cli download TheBloke/Llama-2-70b-Chat-GGUF llama-2-70b-chat.Q4_K_M.gguf --local-dir . --local-dir-use-symlinks False
34 | mv ./llama-2-70b-chat.Q4_K_M.gguf ./models/
35 | ```
36 |
37 | Run the webserver:
38 |
39 | ```sh
40 | # config your gguf model path
41 | # make sure it is gguf and Q3
42 | export MODEL=./models/llama-2-7b-chat.Q4_K_M.gguf
43 | python3 -m llama_cpp.server --model $MODEL --n_gpu_layers 1
44 |
45 | # Note: If you omit the --n_gpu_layers 1 then CPU will be used
46 | ```
47 |
48 | Try the Python API:
49 |
50 | ```python
51 | from llama_cpp import Llama
52 | llm = Llama(model_path="./models/llama-2-7b-chat.Q4_K_M.gguf")
53 | output = llm("Q: Name the planets in the solar system? A: ", max_tokens=64, stop=["Q:", "\n"], echo=True)
54 | print(output)
55 | print(output['choices'][0]['text'])
56 | ```
57 |
58 |
59 | ## Jupyter kernel
60 |
61 | ```sh
62 | python -m ipykernel install --user --name chatefficient --display-name "Python (chatefficient)"
63 | ```
64 |
--------------------------------------------------------------------------------
/docs/quickstart.md:
--------------------------------------------------------------------------------
1 | # Quickstart
2 |
3 | This document contains instructions _only_ to get a fully working development environment for
4 | running this repo. For pre-requisites (e.g. `pyenv` install instructions) plus details on what's
5 | being installed and why, please see [docs/getting_started.md](docs/getting_started.md).
6 |
7 | We assume the following are installed and configured:
8 | - [pyenv](https://github.com/pyenv/pyenv)
9 | - [pyenv-virtualenvwrapper](https://github.com/pyenv/pyenv-virtualenvwrapper)
10 | - [Poetry](https://python-poetry.org/docs/)
11 | - [zsh-autoswitch-virtualenv](https://github.com/MichaelAquilina/zsh-autoswitch-virtualenv)
12 | - [direnv](https://direnv.net/)
13 | - [poetry up](https://github.com/MousaZeidBaker/poetry-plugin-up)
14 |
15 |
16 | ## Part 1: Generic Python setup
17 |
18 | ```sh
19 | # Get the repo
20 | git clone ${REPO_GIT_URL}
21 |
22 | # Install Python
23 | pyenv install $(cat .python-version)
24 | pyenv shell $(cat .python-version)
25 | python -m pip install --upgrade pip
26 | python -m pip install virtualenvwrapper
27 | pyenv virtualenvwrapper
28 |
29 | # Setup the virtualenv
30 | mkvirtualenv -p python$(cat .python-version) $(cat .venv)
31 | python -V
32 | python -m pip install --upgrade pip
33 |
34 | # Install dependencies with Poetry
35 | poetry self update
36 | poetry install --no-root --sync
37 |
38 | # Create templated .env for storing secrets
39 | cp .env.template .env
40 | direnv allow
41 |
42 | # Create and audit secrets baseline
43 | # N.B. Adjust the exclusions here depending on your needs (check .pre-commit-config.yaml)
44 | detect-secrets --verbose scan \
45 | --exclude-files 'poetry\.lock' \
46 | --exclude-files '\.secrets\.baseline' \
47 | --exclude-files '\.env\.template' \
48 | --exclude-secrets 'password|ENTER_PASSWORD_HERE|INSERT_API_KEY_HERE' \
49 | --exclude-lines 'integrity=*sha' \
50 | > .secrets.baseline
51 |
52 | detect-secrets audit .secrets.baseline
53 | ```
54 |
55 |
56 | ## Part 2: Project-specific setup
57 |
58 | Please check [docs/project_specific_setup.md](project_specific_setup.md) for further instructions.
59 |
--------------------------------------------------------------------------------
/docs/release_process.md:
--------------------------------------------------------------------------------
1 | # Release Process
2 |
3 | > This process follows [this Git Flow release model](https://nvie.com/posts/a-successful-git-branching-model/).
4 | > There is an excellent [Git Flow cheatsheet here](https://danielkummer.github.io/git-flow-cheatsheet/).
5 | > You will need to install the Git Flow helper extension: `brew install git-flow-avh` and
6 | > initialise your repo (see the cheatsheet above, or this can also be done from within Sourcetree).
7 |
8 | **Replace VERSION with the relevant milestone, e.g. `0.3`**
9 |
10 |
11 | ## 1. CLI Steps
12 |
13 | Sourcetree has great built-in support for Git Flow from the `Repository > Git flow` menu. We list
14 | the commands below, but this can also be done via the GUI if you prefer.
15 |
16 | ```sh
17 | # Ensure your working copy is clean before starting (e.g. stash any WIP).
18 | # Fetch & pull latest origin/develop
19 | git fetch && git checkout develop && git pull
20 |
21 | # Locally, start a new release
22 | git flow release start VERSION
23 |
24 | # Summary of actions:
25 | # - A new branch 'release/VERSION' was created, based on 'develop'
26 | # - You are now on branch 'release/VERSION'
27 |
28 | # Follow-up actions:
29 | # - Bump the version number now!
30 | # - Start committing last-minute fixes in preparing your release (e.g. towncrier)
31 | # - Use amend commits here if possible to keep commits to a minimum
32 | # git commit --amend -m "updated commit message"
33 | # - You don't have to push the release branch unless a) you'd like it reviewed, b) to run CI, c) others may wish to add commits to this release.
34 |
35 | # Towncrier update
36 | # - Review all towncrier entries.
37 | # - Any missing vs issues closed within the milestone (don't forget bugs & maintenance)? Do the entries look good?
38 | # - (Optional) towncrier preview: `towncrier build --version=VERSION --draft`
39 | # - Publish towncrier update: `towncrier build --version=VERSION`
40 | # - Add any additional notes to the CHANGELOG.md as required
41 |
42 | # Update project version number
43 | # - This is in [poetry] section at the top of pyproject.toml
44 |
45 | # (Optional) pre-commit
46 | # - You shouldn't need to run pre-commit unless you've changed things manually
47 | # - If you're seeing changes to poetry.lock, try clearing your Poetry cache & run again
48 | poetry cache clear pypi --all
49 | pre-commit run --all-files --hook-stage=manual
50 |
51 | # Commit & amend commit as required
52 |
53 | # (Optional) If others have commits to add to this release, you can push as follows
54 | git flow release publish VERSION
55 |
56 | # Complete the release by merging back into `main` and `develop`
57 | # - Add -k if you do not want to auto-delete the release branch
58 | # - Add -p if you want to auto-push to origin
59 | # - Just use "Release VERSION" as commit messages
60 | git flow release finish -n VERSION
61 |
62 | # Summary of actions:
63 | # - Release branch 'release/VERSION' has been merged into 'main'
64 | # - Master branch 'main' has been back-merged into 'develop'
65 | # - Release branch 'release/VERSION' is still locally available
66 | # - You are now on branch 'develop'
67 |
68 | # Tag the release
69 | git checkout main
70 | git tag VERSION
71 | git push origin --tags
72 |
73 | # Check everything over, if you're happy, push `develop`, push `main` and delete your release branch.
74 | git checkout develop && git push
75 | git checkout main && git push
76 | git branch -D release/VERSION
77 | ```
78 |
79 | ## 2. GitHub Steps
80 |
81 | - Copy the **raw markdown** for the release notes in CHANGELOG: [https://github.com/CoefficientSystems/coefficient-cookiecutter/blob/main/CHANGELOG.md]
82 | - Once you've pushed the tag, you will see it on this page: [https://github.com/CoefficientSystems/coefficient-cookiecutter/tags]
83 | - Edit the tag and add the release notes
84 | - You will then see the release appear here: [https://github.com/CoefficientSystems/coefficient-cookiecutter/releases]
85 | - This also sends an email update to anyone on the team who has subscribed containing formatted release notes.
86 | - Once the release is created, edit the release and assign the milestone to the release. Save changes.
87 |
88 | To finish, copy the release notes and post in any relevant Slack channel or email lists to inform members about the release.
89 |
--------------------------------------------------------------------------------
/docs/updating_requirements.md:
--------------------------------------------------------------------------------
1 | # Updating requirements
2 |
3 | ```sh
4 | # Create feature branch
5 | poetry-sync
6 | poetryup --latest
7 | # If any issues, likely due to two "latest" packages conflicting. See example below.
8 | poetry-regenerate
9 | poetry-sync
10 | pre-commit run --all-files --hook-stage=manual
11 | poetry-sync
12 | pytest
13 | # Commit & push
14 | ```
15 |
16 | If the latest version of `green` requires `blue (>=1.2, <1.3)` and the latest version of `blue` is
17 | `1.4` then you will encounter a `SolverProblemError`, for example:
18 |
19 | ```sh
20 | SolverProblemError
21 |
22 | Because green (0.8) depends on blue (>=1.2,<1.3)
23 | and no versions of green match (>=0.8,<1.0) requires blue (>=1.2,<1.3).
24 | So, because src depends on both blue (^1.4) and green (^0.8), version solving failed.
25 | ```
26 |
27 | In this situation, do the following:
28 | - Comment out `blue`
29 | - Re-run `poetryup --latest`
30 | - Handle any other new package conflicts the same way until poetryup resolves
31 | - Uncomment out `blue` with package version that works with `green`, e.g. `blue = "^1.2"`
32 | - Run `poetry-regenerate` onwards
33 |
--------------------------------------------------------------------------------
/docs/using_poetry.md:
--------------------------------------------------------------------------------
1 | # Updating Python requirements using Poetry
2 |
3 | This project uses Poetry to manage dependencies:
4 | [https://python-poetry.org/docs/](https://python-poetry.org/docs/)
5 |
6 | The Poetry virtualenv should activate automatically if you are using
7 | [zsh-autoswitch-virtualenv](https://github.com/MichaelAquilina/zsh-autoswitch-virtualenv). You can
8 | also activate it manually by running `workon chatefficient` (preferred) or `poetry shell` (Poetry
9 | created/managed virtualenv).
10 |
11 |
12 | ## Poetry tl;dr
13 |
14 | ```bash
15 | # Sync your environment with poetry.lock
16 | poetry install --no-root --sync
17 |
18 | # Add and install a new package into your environment (or update an existing one)
19 | # 1. Add a package to pyproject.toml
20 | # 2. Run this
21 | poetry add package@version
22 |
23 | # N.B. We lock & export to requirements when you run `pre-commit run --all-files` so you
24 | # shouldn't need to run the commands below yourself.
25 |
26 | # Compile all poetry.lock file
27 | poetry update
28 |
29 | # Export to requirements.txt (this is for compatibility with team members who may not
30 | # have Poetry; Poetry itself uses poetry.lock when you run `poetry install`)
31 | poetry export -f requirements.txt --output requirements.txt
32 | ```
33 |
34 |
35 | ## Poetry FAQ
36 |
37 | **How do I sync my environment with the latest `poetry.lock`?**
38 | To install dependencies, simply `cd backend-security` and run `poetry install --no-root --sync`.
39 | This will resolve & install all deps in `pyproject.toml` via `poetry.lock`. Options:
40 | - `--no-root` skips installing the repo itself as a Python package
41 | - `--sync` removes old dependencies no longer present in the lock file
42 |
43 | You may wish to set an alias e.g. `alias poetry-sync='poetry install --no-root --sync'`
44 |
45 | **How do I add/change packages?**
46 | In order to install a new package or remove an old one, please edit `pyproject.toml`
47 | or use either `poetry add package` or `pipx run poetry add package@version` as desired.
48 |
49 | **How do I update `poetry.lock` to match new changes in `pyproject.toml`?**
50 | You can run `poetry update`, although this will also get run when you run pre-commit. This fetches
51 | the latest matching versions (according to `pyproject.toml`) and updates `poetry.lock`, and is
52 | equivalent to deleting `poetry.lock` and running `poetry install --no-root` again from scratch.
53 |
54 | **What do `~`, `^` and `*` mean?**
55 | Check out the Poetry docs page for [specifying version constraints](https://python-poetry.org/docs/dependency-specification/#version-constraints).
56 | Environment markers are great, e.g. this means "for Python 2.7.* OR Windows, install pathlib2 >=2.2, <3.0"
57 | `pathlib2 = { version = "^2.2", markers = "python_version ~= '2.7' or sys_platform == 'win32'" }`
58 |
--------------------------------------------------------------------------------
/docs/using_towncrier.md:
--------------------------------------------------------------------------------
1 | # Changelog & releases
2 |
3 | The format of `CHANGELOG.md` is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
4 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5 |
6 |
7 | ## Using towncrier to create news entries
8 |
9 | We use [towncrier](https://github.com/twisted/towncrier) to reduce merge conflicts by generating
10 | `CHANGELOG.md` from news fragments, rather than maintaining it directly. Create a news fragment for
11 | each MR if you would like to ensure your changes are communicated to other project contributors.
12 |
13 | ```bash
14 | # To create a news entry for an added feature relating to MR !123
15 | # Adding --edit is optional and will open in your default shell's $EDITOR
16 | towncrier create 123.added --edit
17 | ```
18 |
19 | Top tips:
20 | - You may wish to add `export EDITOR="code -w"` to your `.zshrc` file to open this directly in VS Code.
21 | - News fragments should be written in markdown.
22 | - The generated news fragments live in `.changelog/` and can be easily rewritten as an MR evolves.
23 |
24 | We use the following custom types (adapted from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)):
25 | - `.added` for new features
26 | - `.changed` for changes in existing functionality
27 | - `.deprecated` for soon-to-be removed features
28 | - `.removed` for now removed features
29 | - `.fixed` for any bug fixes
30 | - `.security` in case of vulnerabilities
31 | - `.analysis` for data analyses
32 | - `.docs` for documentation improvements
33 | - `.maintenance` for maintenance tasks & upgrades
34 |
35 |
36 | ## Releasing a new version & updating `CHANGELOG.md`
37 |
38 | Release versions are tied to Gitlab milestones and sprints. Release checklist:
39 |
40 | 1. Review MRs assigned to the release milestone in Gitlab & reallocate to the next release.
41 | 2. Run `towncrier build --version=VERSION` (preview with `--draft`)
42 | 3. Add a git tag for the release.
43 |
--------------------------------------------------------------------------------
/images/about-me.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/about-me.jpeg
--------------------------------------------------------------------------------
/images/civil-service.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/civil-service.png
--------------------------------------------------------------------------------
/images/coefficient-aidl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/coefficient-aidl.png
--------------------------------------------------------------------------------
/images/eh-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/eh-logo.png
--------------------------------------------------------------------------------
/images/eh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/eh.png
--------------------------------------------------------------------------------
/images/ml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/ml.png
--------------------------------------------------------------------------------
/images/obt-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/obt-banner.png
--------------------------------------------------------------------------------
/images/one-big-thing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/one-big-thing.png
--------------------------------------------------------------------------------
/images/pydata.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/pydata.png
--------------------------------------------------------------------------------
/images/vicuna-chat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/images/vicuna-chat.gif
--------------------------------------------------------------------------------
/models/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/models/.gitkeep
--------------------------------------------------------------------------------
/notebooks/Chroma.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "368dccec-1d58-4167-a50d-01c56d98c24e",
6 | "metadata": {},
7 | "source": [
8 | ""
9 | ]
10 | },
11 | {
12 | "cell_type": "markdown",
13 | "id": "db52fb18-e4c3-4d38-86d6-cf10abeca5e8",
14 | "metadata": {},
15 | "source": [
16 | "# Build Your Own Private ChatGPT Super-Assistant Using Streamlit, LangChain, Chroma & Llama 2\n",
17 | "## Chroma Demo\n",
18 | "**Questions?** contact@coefficient.ai / [@CoefficientData](https://twitter.com/CoefficientData)\n",
19 | "\n",
20 | "---"
21 | ]
22 | },
23 | {
24 | "cell_type": "markdown",
25 | "id": "af9dfd7f-dcc6-418c-bc6b-02c7771e07fa",
26 | "metadata": {},
27 | "source": [
28 | "## 0. Imports"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "id": "d73809ac-30df-4182-8d69-e49d9ccc60b0",
35 | "metadata": {},
36 | "outputs": [],
37 | "source": [
38 | "import chromadb\n",
39 | "from dotenv import load_dotenv\n",
40 | "\n",
41 | "from utils import scrape_page"
42 | ]
43 | },
44 | {
45 | "cell_type": "markdown",
46 | "id": "f5ecd369-baf5-4c19-9245-1cad96fbd17a",
47 | "metadata": {},
48 | "source": [
49 | "## 1. Chroma Basics"
50 | ]
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": null,
55 | "id": "bbae2d37-e3fe-4fab-89d4-16cdda33b808",
56 | "metadata": {},
57 | "outputs": [],
58 | "source": [
59 | "# Get the Chroma client\n",
60 | "chroma_client = chromadb.Client()"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "id": "62019417-87d1-4187-b3a9-2ee3d91aa01e",
67 | "metadata": {},
68 | "outputs": [],
69 | "source": [
70 | "# Create a collection\n",
71 | "collection = chroma_client.create_collection(name=\"my_collection\")"
72 | ]
73 | },
74 | {
75 | "cell_type": "markdown",
76 | "id": "0ccbc06d-74ed-4657-9468-a4ae0d3be0d8",
77 | "metadata": {},
78 | "source": [
79 | "Collections are where you'll store your embeddings, documents, and any additional metadata. "
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "id": "9063c858-e0bf-4933-b0d0-29e178c18bec",
86 | "metadata": {},
87 | "outputs": [],
88 | "source": [
89 | "# Add some text documents to the collection\n",
90 | "collection.add(\n",
91 | " documents=[\"This is a document\", \"This is another document\"],\n",
92 | " metadatas=[{\"source\": \"my_source\"}, {\"source\": \"my_source\"}],\n",
93 | " ids=[\"id1\", \"id2\"],\n",
94 | ")"
95 | ]
96 | },
97 | {
98 | "cell_type": "markdown",
99 | "id": "43e82e2a-f63b-4594-9ce5-25c7908ce1fd",
100 | "metadata": {},
101 | "source": [
102 | "Chroma will store your text, and handle tokenization, embedding, and indexing automatically."
103 | ]
104 | },
105 | {
106 | "cell_type": "code",
107 | "execution_count": null,
108 | "id": "920b8ae0-c0de-4450-bb41-14e6f2ab0b67",
109 | "metadata": {},
110 | "outputs": [],
111 | "source": [
112 | "collection2 = chroma_client.create_collection(name=\"another_collection\")"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": null,
118 | "id": "0e0d0636-443d-4309-b47a-25d009533044",
119 | "metadata": {},
120 | "outputs": [],
121 | "source": [
122 | "# Load in pre-generated embeddings\n",
123 | "collection2.add(\n",
124 | " embeddings=[[1.2, 2.3, 4.5], [6.7, 8.2, 9.2]],\n",
125 | " documents=[\"This is a document\", \"This is another document\"],\n",
126 | " metadatas=[{\"source\": \"my_source\"}, {\"source\": \"my_source\"}],\n",
127 | " ids=[\"id1\", \"id2\"],\n",
128 | ")"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "id": "d3a57272-0353-4332-9ee5-2109a33a3842",
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "# Query the collection\n",
139 | "results = collection.query(query_texts=[\"This is a query document\"], n_results=2)"
140 | ]
141 | },
142 | {
143 | "cell_type": "code",
144 | "execution_count": null,
145 | "id": "0e240a34-329a-47b3-8581-b359d35489c9",
146 | "metadata": {},
147 | "outputs": [],
148 | "source": [
149 | "results"
150 | ]
151 | },
152 | {
153 | "cell_type": "markdown",
154 | "id": "8b15485f-7519-46ad-bb15-5d2f351b22b8",
155 | "metadata": {},
156 | "source": [
157 | "- **Where is data stored?** By default data stored in Chroma is ephemeral making it easy to prototype scripts.\n",
158 | "- **Can data be persisted?** It's easy to make Chroma persistent so you can reuse every collection you create and add more documents to it later. It will load your data automatically when you start the client, and save it automatically when you close it.\n",
159 | "\n",
160 | "Check out the [Usage Guide](https://docs.trychroma.com/usage-guide) for more info."
161 | ]
162 | },
163 | {
164 | "cell_type": "code",
165 | "execution_count": null,
166 | "id": "2e24f61d-1db6-4021-a851-866b5dd013be",
167 | "metadata": {},
168 | "outputs": [],
169 | "source": [
170 | "persistent_client = chromadb.PersistentClient(path=\".\")\n",
171 | "persistent_collection = persistent_client.create_collection(name=\"example_collection\")"
172 | ]
173 | },
174 | {
175 | "cell_type": "markdown",
176 | "id": "3e12c1ac-bd27-442d-8d1a-5439d5a30abc",
177 | "metadata": {},
178 | "source": [
179 | "---"
180 | ]
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "id": "68b5edd4-ddb4-4f43-9488-f2028670190c",
185 | "metadata": {},
186 | "source": [
187 | "## 2. Create embeddings with LangChain"
188 | ]
189 | },
190 | {
191 | "cell_type": "markdown",
192 | "id": "66d53907-b103-4c48-bad2-88b89de95134",
193 | "metadata": {},
194 | "source": [
195 | "### Create embeddings with Llama"
196 | ]
197 | },
198 | {
199 | "cell_type": "code",
200 | "execution_count": null,
201 | "id": "a4790923-7eb0-4f37-a975-a2d6d353b4f7",
202 | "metadata": {},
203 | "outputs": [],
204 | "source": [
205 | "from langchain.embeddings.llamacpp import LlamaCppEmbeddings"
206 | ]
207 | },
208 | {
209 | "cell_type": "code",
210 | "execution_count": null,
211 | "id": "049bedfa-b52a-4c00-af91-4d861708b957",
212 | "metadata": {},
213 | "outputs": [],
214 | "source": [
215 | "# Make sure the model path is correct!\n",
216 | "llama_embedder = LlamaCppEmbeddings(model_path=\"../models/llama-2-7b-chat.Q4_K_M.gguf\")"
217 | ]
218 | },
219 | {
220 | "cell_type": "code",
221 | "execution_count": null,
222 | "id": "caa5eada-90c6-4972-9ea0-0ea98d0027f3",
223 | "metadata": {},
224 | "outputs": [],
225 | "source": [
226 | "text = \"This is a test document.\"\n",
227 | "query_result = llama_embedder.embed_query(text)"
228 | ]
229 | },
230 | {
231 | "cell_type": "code",
232 | "execution_count": null,
233 | "id": "0216db12-4768-4765-9d88-5f838a374abd",
234 | "metadata": {},
235 | "outputs": [],
236 | "source": [
237 | "len(query_result)"
238 | ]
239 | },
240 | {
241 | "cell_type": "code",
242 | "execution_count": null,
243 | "id": "882f79cd-dbe5-411d-ab12-d0b6d8395f9b",
244 | "metadata": {},
245 | "outputs": [],
246 | "source": [
247 | "query_result[:10]"
248 | ]
249 | },
250 | {
251 | "cell_type": "code",
252 | "execution_count": null,
253 | "id": "c202e270-f760-480f-bb1f-301eac40a3ea",
254 | "metadata": {},
255 | "outputs": [],
256 | "source": [
257 | "doc_result = llama_embedder.embed_documents([text])"
258 | ]
259 | },
260 | {
261 | "cell_type": "code",
262 | "execution_count": null,
263 | "id": "4a83562b-c29c-41da-91c9-daa43bb071cd",
264 | "metadata": {},
265 | "outputs": [],
266 | "source": [
267 | "len(doc_result)"
268 | ]
269 | },
270 | {
271 | "cell_type": "code",
272 | "execution_count": null,
273 | "id": "92eb9ae9-5540-4579-94ef-f390a2bbc4d6",
274 | "metadata": {},
275 | "outputs": [],
276 | "source": [
277 | "doc_result[0][:10]"
278 | ]
279 | },
280 | {
281 | "cell_type": "markdown",
282 | "id": "91bbb194-b084-4949-b9c8-f965e28a5403",
283 | "metadata": {},
284 | "source": [
285 | "### Create embeddings using LangChain"
286 | ]
287 | },
288 | {
289 | "cell_type": "code",
290 | "execution_count": null,
291 | "id": "4ac59e22-a591-4a97-a69a-a19c34a902a5",
292 | "metadata": {},
293 | "outputs": [],
294 | "source": [
295 | "# Let's get some more interesting data\n",
296 | "url = \"https://www.gov.uk/government/publications/frontier-ai-capabilities-and-risks-discussion-paper/frontier-ai-capabilities-and-risks-discussion-paper\"\n",
297 | "paper = scrape_page(url)"
298 | ]
299 | },
300 | {
301 | "cell_type": "code",
302 | "execution_count": null,
303 | "id": "a2bfc5ee-cdee-4aa4-ba71-8e5450ca39e7",
304 | "metadata": {},
305 | "outputs": [],
306 | "source": [
307 | "# Take a peek\n",
308 | "print(f\"{len(paper)=}\\n\\nExtract:\")\n",
309 | "print(paper[10000:15000])"
310 | ]
311 | },
312 | {
313 | "cell_type": "code",
314 | "execution_count": null,
315 | "id": "a7553911-ab93-408f-afbf-0f4a17e14f6a",
316 | "metadata": {},
317 | "outputs": [],
318 | "source": [
319 | "# Save it to disk - we only do 5000 characters as Llama is very slow at embedding\n",
320 | "with open(\"frontier-ai-paper.txt\", \"w\") as f:\n",
321 | " f.write(paper)"
322 | ]
323 | },
324 | {
325 | "cell_type": "code",
326 | "execution_count": null,
327 | "id": "ab3dc727-51ca-4f67-974b-8b8209f9b75c",
328 | "metadata": {},
329 | "outputs": [],
330 | "source": [
331 | "from langchain.document_loaders import TextLoader\n",
332 | "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
333 | "\n",
334 | "# Load the document\n",
335 | "raw_documents = TextLoader(\"frontier-ai-paper.txt\").load()\n",
336 | "\n",
337 | "# Split it into chunks\n",
338 | "text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)\n",
339 | "documents = text_splitter.split_documents(raw_documents)"
340 | ]
341 | },
342 | {
343 | "cell_type": "code",
344 | "execution_count": null,
345 | "id": "94779f67-1c94-4a81-875f-bfa8d016108f",
346 | "metadata": {},
347 | "outputs": [],
348 | "source": [
349 | "len(documents)"
350 | ]
351 | },
352 | {
353 | "cell_type": "code",
354 | "execution_count": null,
355 | "id": "dfc2d338-61ba-4990-8f10-f40bd2c8fd05",
356 | "metadata": {},
357 | "outputs": [],
358 | "source": [
359 | "documents[:2]"
360 | ]
361 | },
362 | {
363 | "cell_type": "code",
364 | "execution_count": null,
365 | "id": "493fd741-9062-4063-97dd-2365e0f2d877",
366 | "metadata": {},
367 | "outputs": [],
368 | "source": [
369 | "%%time\n",
370 | "from langchain.vectorstores import Chroma\n",
371 | "\n",
372 | "# Embed each chunk and load it into the vector store\n",
373 | "# db = Chroma.from_documents(documents, llama_embedder)"
374 | ]
375 | },
376 | {
377 | "cell_type": "markdown",
378 | "id": "bc87d1e4-d83e-4144-86e9-7df7b6cdd498",
379 | "metadata": {},
380 | "source": [
381 | "### Similarity search"
382 | ]
383 | },
384 | {
385 | "cell_type": "code",
386 | "execution_count": null,
387 | "id": "7d6d7eb3-83cc-4317-925f-fbd791cdfc18",
388 | "metadata": {},
389 | "outputs": [],
390 | "source": [
391 | "query = \"What are scaffolds in AI?\"\n",
392 | "docs = db.similarity_search(query)\n",
393 | "print(docs[0].page_content.replace(\"\\n\", \" \"))"
394 | ]
395 | },
396 | {
397 | "cell_type": "markdown",
398 | "id": "8a787276-c0f2-4449-898b-d8c226f4b25e",
399 | "metadata": {},
400 | "source": [
401 | "## Using SentenceTransformerEmbeddings"
402 | ]
403 | },
404 | {
405 | "cell_type": "code",
406 | "execution_count": null,
407 | "id": "d878b1b6-1253-458d-b5c6-1d6f2e5a4f45",
408 | "metadata": {},
409 | "outputs": [],
410 | "source": [
411 | "# Initialise the new embedder\n",
412 | "from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings\n",
413 | "\n",
414 | "st_embedder = SentenceTransformerEmbeddings(model_name=\"all-MiniLM-L6-v2\")"
415 | ]
416 | },
417 | {
418 | "cell_type": "code",
419 | "execution_count": null,
420 | "id": "30a8b09c-40c2-4734-901b-eefeb39ef43e",
421 | "metadata": {},
422 | "outputs": [],
423 | "source": [
424 | "%%time\n",
425 | "# Compare this with SentenceTransformerEmbeddings\n",
426 | "db2 = Chroma.from_documents(documents, st_embedder, collection_name=\"st_embeddings\")"
427 | ]
428 | },
429 | {
430 | "cell_type": "markdown",
431 | "id": "a7581e9a-34f5-4e16-a10a-3ba8d96c971c",
432 | "metadata": {},
433 | "source": [
434 | "**Note: It takes `SentenceTransformerEmbeddings` <1 second, and Llama 2 several minutes!**"
435 | ]
436 | },
437 | {
438 | "cell_type": "code",
439 | "execution_count": null,
440 | "id": "8503e648-e7a2-4f36-8545-08c4c9776da6",
441 | "metadata": {},
442 | "outputs": [],
443 | "source": [
444 | "# Save the whole paper this time, Sentence-Transformers can handle it\n",
445 | "print(f\"{len(paper)=}\")\n",
446 | "with open(\"frontier-ai-paper.txt\", \"w\") as f:\n",
447 | " f.write(paper)"
448 | ]
449 | },
450 | {
451 | "cell_type": "code",
452 | "execution_count": null,
453 | "id": "b2684eca-aeb0-43e5-b65f-b81b53fca512",
454 | "metadata": {},
455 | "outputs": [],
456 | "source": [
457 | "raw_documents = TextLoader(\"frontier-ai-paper.txt\").load()\n",
458 | "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n",
459 | "documents = text_splitter.split_documents(raw_documents)"
460 | ]
461 | },
462 | {
463 | "cell_type": "code",
464 | "execution_count": null,
465 | "id": "7910e392-b4fd-40a4-a602-10957c701469",
466 | "metadata": {},
467 | "outputs": [],
468 | "source": [
469 | "len(documents)"
470 | ]
471 | },
472 | {
473 | "cell_type": "code",
474 | "execution_count": null,
475 | "id": "0fc4052d-f962-4580-bf3a-49d3ac224756",
476 | "metadata": {},
477 | "outputs": [],
478 | "source": [
479 | "%%time\n",
480 | "db2 = Chroma.from_documents(documents, st_embedder, collection_name=\"st_embeddings\")"
481 | ]
482 | },
483 | {
484 | "cell_type": "code",
485 | "execution_count": null,
486 | "id": "e8c9f18e-9f93-42d0-9abc-dcfa40d74cec",
487 | "metadata": {},
488 | "outputs": [],
489 | "source": [
490 | "docs = db2.similarity_search(\"What are scaffolds in AI?\")\n",
491 | "print(docs[0].page_content.replace(\"\\n\", \" \"))"
492 | ]
493 | },
494 | {
495 | "cell_type": "code",
496 | "execution_count": null,
497 | "id": "9b514178-3b7c-4f56-b96f-5489f87c856c",
498 | "metadata": {},
499 | "outputs": [],
500 | "source": [
501 | "docs = db2.similarity_search(\"What are the top risks of frontier models?\")\n",
502 | "print(docs[0].page_content.replace(\"\\n\", \" \"), \"\\n\\n\")\n",
503 | "print(docs[1].page_content.replace(\"\\n\", \" \"))"
504 | ]
505 | },
506 | {
507 | "cell_type": "markdown",
508 | "id": "f44934f7-6077-4e87-a172-5367d6163f53",
509 | "metadata": {},
510 | "source": [
511 | "### Maximum marginal relevance search (MMR)\n",
512 | "Maximal marginal relevance optimizes for similarity to query and diversity among selected documents. It is also supported in async API."
513 | ]
514 | },
515 | {
516 | "cell_type": "code",
517 | "execution_count": null,
518 | "id": "a08a828a-21ef-4e64-a50a-38e7c85262b4",
519 | "metadata": {},
520 | "outputs": [],
521 | "source": [
522 | "query = \"What are the top risks of frontier models?\"\n",
523 | "retriever = db2.as_retriever(search_type=\"mmr\")\n",
524 | "docs = retriever.get_relevant_documents(query)\n",
525 | "\n",
526 | "print(docs[0].page_content.replace(\"\\n\", \" \"), \"\\n\\n\")\n",
527 | "print(docs[1].page_content.replace(\"\\n\", \" \"))"
528 | ]
529 | },
530 | {
531 | "cell_type": "markdown",
532 | "id": "a84226ab-965a-4001-b4de-5a1215ea8632",
533 | "metadata": {},
534 | "source": [
535 | "### Deep linking"
536 | ]
537 | },
538 | {
539 | "cell_type": "code",
540 | "execution_count": null,
541 | "id": "324306fd-c201-4fe4-b33c-d41dd94d2744",
542 | "metadata": {},
543 | "outputs": [],
544 | "source": [
545 | "docs = db2.similarity_search(\"Which model has the best benchmark?\")\n",
546 | "result = docs[1].page_content\n",
547 | "print(result.replace(\"\\n\", \" \"))"
548 | ]
549 | },
550 | {
551 | "cell_type": "code",
552 | "execution_count": null,
553 | "id": "75935907-78c2-478b-834c-2f0ad7a9c394",
554 | "metadata": {},
555 | "outputs": [],
556 | "source": [
557 | "import urllib.parse"
558 | ]
559 | },
560 | {
561 | "cell_type": "code",
562 | "execution_count": null,
563 | "id": "8f50e576-79cd-4a7b-968b-73873d55ce19",
564 | "metadata": {},
565 | "outputs": [],
566 | "source": [
567 | "encoded_result = urllib.parse.quote(result[:50])\n",
568 | "encoded_result"
569 | ]
570 | },
571 | {
572 | "cell_type": "code",
573 | "execution_count": null,
574 | "id": "e11de357-9267-4b34-94cc-0f40d527fdc3",
575 | "metadata": {},
576 | "outputs": [],
577 | "source": [
578 | "deeplink = f\"{url}#:~:text={encoded_result}\"\n",
579 | "deeplink"
580 | ]
581 | },
582 | {
583 | "cell_type": "markdown",
584 | "id": "f8646efa-78bd-4ef8-baee-31aff6165fa7",
585 | "metadata": {},
586 | "source": [
587 | "---"
588 | ]
589 | },
590 | {
591 | "cell_type": "markdown",
592 | "id": "f3b2a2e3-c1a4-41ab-9541-3f04d8cd4bb7",
593 | "metadata": {},
594 | "source": [
595 | "## 3. Exercise: Q&A bot with vector database\n",
596 | "\n",
597 | "> Combine the Chroma vector database with a Llama-based LangChain LLM to create a Q&A bot for the provided (or any other) URL.\n",
598 | "> Tips:\n",
599 | "> - Encode your queries using the Sentence-Transformer embedding & return the top documents\n",
600 | "> - Include the question alongside the top N documents into your LangChain LLM's context window\n",
601 | "> - Use Llama 2 to synthesise a coherent answer\n",
602 | ">\n",
603 | "> This approach enables LLMs to answer questions to things they haven't been pre-trained on by using the vector database as an \"encyclopedia\" that it can reference as needed. This is known as \"retrieval-augmented generation\" or \"RAG\"."
604 | ]
605 | },
606 | {
607 | "cell_type": "code",
608 | "execution_count": null,
609 | "id": "c27e74d5-fde3-4e0a-b4bd-e61b4d41079a",
610 | "metadata": {},
611 | "outputs": [],
612 | "source": []
613 | },
614 | {
615 | "cell_type": "markdown",
616 | "id": "9bdb3b81-ca58-43fc-9d50-a68395b79e37",
617 | "metadata": {},
618 | "source": [
619 | "---"
620 | ]
621 | },
622 | {
623 | "cell_type": "markdown",
624 | "id": "42846d93-782b-4f49-9080-a5149d26fac5",
625 | "metadata": {},
626 | "source": [
627 | "## Where next?\n",
628 | "\n",
629 | "LangChain is far more powerful than we've seen so far! Here's an idea of what else you can do:\n",
630 | "- [Learn to use agents and tools with LangChain](https://python.langchain.com/docs/modules/agents/tools/) such as searching the web, querying APIs, reading papers on ArXiv, checking the weather, digesting articles on Wikipedia, making (and transcribing) calls with Twilio, accessing financial data and much more. Check out the [list of integrations here](https://python.langchain.com/docs/integrations/tools).\n",
631 | "- [Query a SQL database](https://python.langchain.com/docs/expression_language/cookbook/sql_db) with LangChain Runnables\n",
632 | "- [Write Python code](https://python.langchain.com/docs/expression_language/cookbook/code_writing) with LangChain\n",
633 | "- [Learn more about RAG](https://python.langchain.com/docs/expression_language/cookbook/retrieval) or use [this example to combine agents with the Chroma vector store](https://python.langchain.com/docs/modules/agents/how_to/agent_vectorstore)"
634 | ]
635 | }
636 | ],
637 | "metadata": {
638 | "kernelspec": {
639 | "display_name": "Python (chatefficient)",
640 | "language": "python",
641 | "name": "chatefficient"
642 | },
643 | "language_info": {
644 | "codemirror_mode": {
645 | "name": "ipython",
646 | "version": 3
647 | },
648 | "file_extension": ".py",
649 | "mimetype": "text/x-python",
650 | "name": "python",
651 | "nbconvert_exporter": "python",
652 | "pygments_lexer": "ipython3",
653 | "version": "3.10.13"
654 | }
655 | },
656 | "nbformat": 4,
657 | "nbformat_minor": 5
658 | }
659 |
--------------------------------------------------------------------------------
/notebooks/LangChain.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "368dccec-1d58-4167-a50d-01c56d98c24e",
6 | "metadata": {},
7 | "source": [
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "markdown",
13 | "id": "db52fb18-e4c3-4d38-86d6-cf10abeca5e8",
14 | "metadata": {},
15 | "source": [
16 | "# Build Your Own Private ChatGPT Super-Assistant Using Streamlit, LangChain, Chroma & Llama 2\n",
17 | "## LangChain Demo\n",
18 | "**Questions?** contact@coefficient.ai / [@CoefficientData](https://twitter.com/CoefficientData)\n",
19 | "\n",
20 | "---"
21 | ]
22 | },
23 | {
24 | "cell_type": "markdown",
25 | "id": "af9dfd7f-dcc6-418c-bc6b-02c7771e07fa",
26 | "metadata": {},
27 | "source": [
28 | "## 0. Imports"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "id": "d73809ac-30df-4182-8d69-e49d9ccc60b0",
35 | "metadata": {},
36 | "outputs": [],
37 | "source": [
38 | "import random\n",
39 | "\n",
40 | "from dotenv import load_dotenv\n",
41 | "\n",
42 | "from utils import scrape_page\n",
43 | "\n",
44 | "import warnings\n",
45 | "\n",
46 | "warnings.simplefilter(\"ignore\", UserWarning)\n",
47 | "\n",
48 | "load_dotenv();"
49 | ]
50 | },
51 | {
52 | "cell_type": "markdown",
53 | "id": "c8a2f7f0-8d61-4feb-b4c7-6cfff3c7cf9b",
54 | "metadata": {},
55 | "source": [
56 | "## 1. LangChain Basics"
57 | ]
58 | },
59 | {
60 | "cell_type": "code",
61 | "execution_count": null,
62 | "id": "76396764-2c53-4653-a085-6ad0c5a784e7",
63 | "metadata": {},
64 | "outputs": [],
65 | "source": [
66 | "from langchain_openai import OpenAI"
67 | ]
68 | },
69 | {
70 | "cell_type": "code",
71 | "execution_count": null,
72 | "id": "e0c3a224-ad9e-4fc9-abef-1a731af16952",
73 | "metadata": {},
74 | "outputs": [],
75 | "source": [
76 | "llm = OpenAI(model=\"gpt-3.5-turbo-instruct\", temperature=0.9)"
77 | ]
78 | },
79 | {
80 | "cell_type": "code",
81 | "execution_count": null,
82 | "id": "99a511fe-afa5-417b-869a-cebda93113f5",
83 | "metadata": {},
84 | "outputs": [],
85 | "source": [
86 | "food1 = llm.predict(\"Give me a random food ingredient.\").strip().lower()\n",
87 | "food2 = llm.predict(\"Give me a random food ingredient.\").strip().lower()\n",
88 | "colour = llm.predict(\"Give me a random colour.\").strip().lower()\n",
89 | "\n",
90 | "prompt = f\"\"\"\n",
91 | "Write a recipe for a {colour} cocktail featuring {food1} and {food2}.\n",
92 | "Please come up with an fun name for the mocktail.\n",
93 | "\"\"\"\n",
94 | "print(prompt)"
95 | ]
96 | },
97 | {
98 | "cell_type": "code",
99 | "execution_count": null,
100 | "id": "a31743ec-2003-4917-a31c-a36271f2eafc",
101 | "metadata": {},
102 | "outputs": [],
103 | "source": [
104 | "cocktail = llm.predict(prompt).strip()\n",
105 | "print(cocktail)"
106 | ]
107 | },
108 | {
109 | "cell_type": "markdown",
110 | "id": "dc892754-8ee8-4ab2-ac95-590cce5f23c4",
111 | "metadata": {},
112 | "source": [
113 | "## 2. Chatting with LangChain 🦜"
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": null,
119 | "id": "acead2b3-ad7b-45d5-bc27-e5b0fefdf0e6",
120 | "metadata": {},
121 | "outputs": [],
122 | "source": [
123 | "from langchain_openai import OpenAI\n",
124 | "\n",
125 | "llm = OpenAI(temperature=0)"
126 | ]
127 | },
128 | {
129 | "cell_type": "code",
130 | "execution_count": null,
131 | "id": "c5b849c9-213d-46ca-88a9-81b9d6df32ff",
132 | "metadata": {},
133 | "outputs": [],
134 | "source": [
135 | "from langchain import PromptTemplate\n",
136 | "\n",
137 | "template = \"\"\"\n",
138 | "You are a mindreader with magical abilities.\n",
139 | "You are very over-dramatic and speak like a mysterious shaman.\n",
140 | "You will be given something to guess, such as an animal, or a famous person.\n",
141 | "You will ask a question, I will provide an answer, and then you will ask another question.\n",
142 | "Try to ask questions that narrow down what the answer might be.\n",
143 | "If you are very confident, you can guess what it is.\n",
144 | "If your guess is wrong, then you must ask another question to help narrow it down.\n",
145 | "Repeat this until you I tell you that you have the right answer.\n",
146 | "\n",
147 | "{history}\n",
148 | "Human: {human_input}\n",
149 | "Assistant:\"\"\"\n",
150 | "\n",
151 | "prompt = PromptTemplate(input_variables=[\"history\", \"human_input\"], template=template)"
152 | ]
153 | },
154 | {
155 | "cell_type": "code",
156 | "execution_count": null,
157 | "id": "254210fc-057c-4e23-87cd-fdeebccafa80",
158 | "metadata": {},
159 | "outputs": [],
160 | "source": [
161 | "from langchain.memory import ConversationBufferWindowMemory\n",
162 | "\n",
163 | "short_memory = ConversationBufferWindowMemory(k=5)"
164 | ]
165 | },
166 | {
167 | "cell_type": "code",
168 | "execution_count": null,
169 | "id": "f49dfd50-ca44-4dc2-b14d-70b1c75231b0",
170 | "metadata": {},
171 | "outputs": [],
172 | "source": [
173 | "from langchain import LLMChain\n",
174 | "\n",
175 | "chatgpt_chain = LLMChain(\n",
176 | " llm=llm,\n",
177 | " prompt=prompt,\n",
178 | " verbose=True,\n",
179 | " memory=short_memory,\n",
180 | ")"
181 | ]
182 | },
183 | {
184 | "cell_type": "code",
185 | "execution_count": null,
186 | "id": "620f59f4-5d00-4890-95f4-6a69422b60ce",
187 | "metadata": {},
188 | "outputs": [],
189 | "source": [
190 | "chatgpt_chain.predict(human_input=\"I'm thinking of an animal.\")"
191 | ]
192 | },
193 | {
194 | "cell_type": "code",
195 | "execution_count": null,
196 | "id": "e21ee9e5-ef8c-4481-80b7-875b68070f6b",
197 | "metadata": {},
198 | "outputs": [],
199 | "source": [
200 | "chatgpt_chain.predict(human_input=\"It is not a mammal. Ask another question.\")"
201 | ]
202 | },
203 | {
204 | "cell_type": "code",
205 | "execution_count": null,
206 | "id": "08bc72d5-1e51-4d0e-a62f-63ae295627e6",
207 | "metadata": {},
208 | "outputs": [],
209 | "source": [
210 | "chatgpt_chain.predict(human_input=\"Yes its a bird. Either guess, or ask another question.\")"
211 | ]
212 | },
213 | {
214 | "cell_type": "code",
215 | "execution_count": null,
216 | "id": "9719a278-5f85-4ec4-a307-3f3d72fa0c5f",
217 | "metadata": {},
218 | "outputs": [],
219 | "source": [
220 | "chatgpt_chain.predict(human_input=\"Yes it's a parrot. Either guess, or ask another question.\")"
221 | ]
222 | },
223 | {
224 | "cell_type": "code",
225 | "execution_count": null,
226 | "id": "ad4c3814-d967-43f5-8b5f-0c247ec149c2",
227 | "metadata": {},
228 | "outputs": [],
229 | "source": [
230 | "chatgpt_chain.predict(human_input=\"Yes it's a macaw! Would you like to give it a name?\")"
231 | ]
232 | },
233 | {
234 | "cell_type": "code",
235 | "execution_count": null,
236 | "id": "5d73bfa9-637c-4cdf-98ed-cee7638c7c19",
237 | "metadata": {},
238 | "outputs": [],
239 | "source": [
240 | "chatgpt_chain.predict(human_input=\"How about something a little more creative and fun?\")"
241 | ]
242 | },
243 | {
244 | "cell_type": "markdown",
245 | "id": "382f5f30-c4cd-4480-92f9-a9a602ca290a",
246 | "metadata": {},
247 | "source": [
248 | "---"
249 | ]
250 | },
251 | {
252 | "cell_type": "markdown",
253 | "id": "558e007e-76fd-4c8d-800c-010088701237",
254 | "metadata": {},
255 | "source": [
256 | "## 3. Llama-2 (via llama-cpp) 🦙\n",
257 | "\n",
258 | "### LLM ➡️ LLaMA 🦙 ➡️ Vicuna 🦙 ➡️ Llama-2\n",
259 | "See here for a macOS install guide: https://abetlen.github.io/llama-cpp-python/macos_install/"
260 | ]
261 | },
262 | {
263 | "cell_type": "code",
264 | "execution_count": null,
265 | "id": "ec0feacc-6455-45ef-a6ee-02092fb56fa0",
266 | "metadata": {},
267 | "outputs": [],
268 | "source": [
269 | "from llama_cpp import Llama"
270 | ]
271 | },
272 | {
273 | "cell_type": "code",
274 | "execution_count": null,
275 | "id": "cf9952cb-21e7-4f6a-9e36-ad17933f0971",
276 | "metadata": {},
277 | "outputs": [],
278 | "source": [
279 | "%pwd\n",
280 | "# Where are we?"
281 | ]
282 | },
283 | {
284 | "cell_type": "code",
285 | "execution_count": null,
286 | "id": "cdb86a28-7333-41a3-a954-2f207b691515",
287 | "metadata": {},
288 | "outputs": [],
289 | "source": [
290 | "llm = Llama(model_path=\"../models/llama-2-7b-chat.Q4_K_M.gguf\")"
291 | ]
292 | },
293 | {
294 | "cell_type": "code",
295 | "execution_count": null,
296 | "id": "80761117-0861-4ca1-ad01-db2febd66024",
297 | "metadata": {},
298 | "outputs": [],
299 | "source": [
300 | "output = llm(\n",
301 | " \"Q: Name five UK government departments? A: \", max_tokens=64, stop=[\"Q:\", \"\\n\"], echo=True\n",
302 | ")"
303 | ]
304 | },
305 | {
306 | "cell_type": "code",
307 | "execution_count": null,
308 | "id": "87ca5287-5b6d-497d-b02d-e20e2e1beff6",
309 | "metadata": {},
310 | "outputs": [],
311 | "source": [
312 | "output"
313 | ]
314 | },
315 | {
316 | "cell_type": "code",
317 | "execution_count": null,
318 | "id": "d56b72a7-aec6-4b2d-a00a-14817d228bb3",
319 | "metadata": {},
320 | "outputs": [],
321 | "source": [
322 | "output[\"choices\"][0][\"text\"]"
323 | ]
324 | },
325 | {
326 | "cell_type": "code",
327 | "execution_count": null,
328 | "id": "e2b29744-5eea-44a8-9852-e65290fa05a6",
329 | "metadata": {},
330 | "outputs": [],
331 | "source": [
332 | "llm(\n",
333 | " \"Q: What's the least known UK government department or agency? A: \",\n",
334 | " max_tokens=128,\n",
335 | " stop=[\"Q:\"],\n",
336 | " echo=True,\n",
337 | ")[\"choices\"][0][\"text\"]"
338 | ]
339 | },
340 | {
341 | "cell_type": "markdown",
342 | "id": "e9d75b70-dab5-460d-a1ec-d0a2ece129c3",
343 | "metadata": {},
344 | "source": [
345 | "---"
346 | ]
347 | },
348 | {
349 | "cell_type": "markdown",
350 | "id": "1f074c53-23ab-4f09-a32a-8a928b3f4f38",
351 | "metadata": {},
352 | "source": [
353 | "## 4. Llama-2 + LangChain"
354 | ]
355 | },
356 | {
357 | "cell_type": "code",
358 | "execution_count": null,
359 | "id": "a4e36637-61e9-4e16-babd-d0aec19dd58e",
360 | "metadata": {},
361 | "outputs": [],
362 | "source": [
363 | "from langchain.llms import LlamaCpp\n",
364 | "from langchain import PromptTemplate, LLMChain\n",
365 | "from langchain.callbacks.manager import CallbackManager\n",
366 | "from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler"
367 | ]
368 | },
369 | {
370 | "cell_type": "markdown",
371 | "id": "de7f26a7-8205-4098-a817-974017fa3443",
372 | "metadata": {},
373 | "source": [
374 | "### Define a LlamaCpp llm object"
375 | ]
376 | },
377 | {
378 | "cell_type": "code",
379 | "execution_count": null,
380 | "id": "967887fe-deab-4826-83e9-8478b695f7a9",
381 | "metadata": {},
382 | "outputs": [],
383 | "source": [
384 | "# Callbacks support token-wise streaming\n",
385 | "callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])"
386 | ]
387 | },
388 | {
389 | "cell_type": "code",
390 | "execution_count": null,
391 | "id": "c95049a5-f8f7-4bbc-9959-31341a721210",
392 | "metadata": {},
393 | "outputs": [],
394 | "source": [
395 | "n_gpu_layers = 40 # Change this value based on your model and your GPU VRAM pool.\n",
396 | "n_batch = 512 # Should be between 1 and n_ctx, consider the amount of VRAM in your GPU.\n",
397 | "n_ctx = 512 * 2\n",
398 | "\n",
399 | "# Make sure the model path is correct for your system!\n",
400 | "llm = LlamaCpp(\n",
401 | " n_ctx=n_ctx,\n",
402 | " model_path=\"../models/llama-2-7b-chat.Q4_K_M.gguf\",\n",
403 | " n_gpu_layers=n_gpu_layers,\n",
404 | " n_batch=n_batch,\n",
405 | " callback_manager=callback_manager,\n",
406 | " verbose=True,\n",
407 | " model_kwargs={\"max_new_tokens\": 64 * 4},\n",
408 | " stop=[\"Human:\", \"Input:\"],\n",
409 | ")"
410 | ]
411 | },
412 | {
413 | "cell_type": "markdown",
414 | "id": "811658c8-860d-45cf-ba05-6ee48066567a",
415 | "metadata": {},
416 | "source": [
417 | "### Use the LlamaCpp llm object with LangChain's PromptTemplate"
418 | ]
419 | },
420 | {
421 | "cell_type": "code",
422 | "execution_count": null,
423 | "id": "690ca30a-70de-45da-901c-d8234bfc0f6d",
424 | "metadata": {},
425 | "outputs": [],
426 | "source": [
427 | "template = \"\"\"Question: {question}\n",
428 | "\n",
429 | "Answer: Let's work this out in a step by step way to be sure we have the right answer.\"\"\"\n",
430 | "\n",
431 | "prompt = PromptTemplate(template=template, input_variables=[\"question\"])"
432 | ]
433 | },
434 | {
435 | "cell_type": "code",
436 | "execution_count": null,
437 | "id": "dedc0889-46f4-455d-9e2f-4058fd0cf232",
438 | "metadata": {},
439 | "outputs": [],
440 | "source": [
441 | "%%time\n",
442 | "llm_chain = LLMChain(prompt=prompt, llm=llm)\n",
443 | "question = \"What is a vicuna? (Clue, the animal!)\"\n",
444 | "\n",
445 | "print(llm_chain.run(question))"
446 | ]
447 | },
448 | {
449 | "cell_type": "markdown",
450 | "id": "3f967839-abd4-474f-a45d-46e51a014818",
451 | "metadata": {},
452 | "source": [
453 | "### Use a ConversationBufferWindowMemory"
454 | ]
455 | },
456 | {
457 | "cell_type": "code",
458 | "execution_count": null,
459 | "id": "ac4fa27e-6246-4fe3-b0ec-90674088a28c",
460 | "metadata": {},
461 | "outputs": [],
462 | "source": [
463 | "# This is the exact same code we saw before using the OpenAI LLM.\n",
464 | "# LangChain gives us code consistency across LLM models.\n",
465 | "\n",
466 | "chatgpt_chain = LLMChain(\n",
467 | " llm=llm,\n",
468 | " prompt=prompt,\n",
469 | " verbose=True,\n",
470 | " memory=ConversationBufferWindowMemory(k=5),\n",
471 | ")"
472 | ]
473 | },
474 | {
475 | "cell_type": "code",
476 | "execution_count": null,
477 | "id": "fe448a8f-5760-4fc9-a5a0-af35308252d8",
478 | "metadata": {},
479 | "outputs": [],
480 | "source": [
481 | "chatgpt_chain.predict(\n",
482 | " question=\"What NFL team won the Super Bowl in the year Justin Bieber was born?\"\n",
483 | ");"
484 | ]
485 | },
486 | {
487 | "cell_type": "markdown",
488 | "id": "e9ec07ef-4b73-4dd8-b18f-ec614becef65",
489 | "metadata": {},
490 | "source": [
491 | "### Mind-reading comparison: Llama2 (7B) vs OpenAI"
492 | ]
493 | },
494 | {
495 | "cell_type": "code",
496 | "execution_count": null,
497 | "id": "490688d6-3e90-429d-9fa9-d7d0f4aa2122",
498 | "metadata": {},
499 | "outputs": [],
500 | "source": [
501 | "template = \"\"\"\n",
502 | "You are a mindreader with magical abilities.\n",
503 | "You are very over-dramatic and speak like a mysterious shaman.\n",
504 | "You will be given something to guess, such as an animal, or a famous person.\n",
505 | "You will ask a question, I will provide an answer, and then you will ask another question.\n",
506 | "Try to ask questions that narrow down what the answer might be.\n",
507 | "If you are very confident, you can guess what it is.\n",
508 | "If your guess is wrong, then you must ask another question to help narrow it down.\n",
509 | "Repeat this until you I tell you that you have the right answer.\n",
510 | "\n",
511 | "{history}\n",
512 | "Human: {human_input}\n",
513 | "Assistant:\"\"\"\n",
514 | "\n",
515 | "prompt = PromptTemplate(input_variables=[\"history\", \"human_input\"], template=template)"
516 | ]
517 | },
518 | {
519 | "cell_type": "code",
520 | "execution_count": null,
521 | "id": "8b5f501e-c6fe-45b1-be10-73bed4b3047f",
522 | "metadata": {},
523 | "outputs": [],
524 | "source": [
525 | "# Let's turn verbose off, we don't need llama_print_timings\n",
526 | "chatgpt_chain = LLMChain(\n",
527 | " llm=llm,\n",
528 | " prompt=prompt,\n",
529 | " verbose=False,\n",
530 | " memory=ConversationBufferWindowMemory(k=10),\n",
531 | ")"
532 | ]
533 | },
534 | {
535 | "cell_type": "code",
536 | "execution_count": null,
537 | "id": "dc8f234e-3e10-4988-971e-8b6415929cd9",
538 | "metadata": {},
539 | "outputs": [],
540 | "source": [
541 | "chatgpt_chain.predict(human_input=\"I'm thinking of an animal.\");"
542 | ]
543 | },
544 | {
545 | "cell_type": "code",
546 | "execution_count": null,
547 | "id": "7eb18396-9cd9-401c-88c8-ca165681089b",
548 | "metadata": {},
549 | "outputs": [],
550 | "source": [
551 | "chatgpt_chain.predict(human_input=\"No, its an animal.\");"
552 | ]
553 | },
554 | {
555 | "cell_type": "code",
556 | "execution_count": null,
557 | "id": "20e95071-d7db-4e90-b721-0c76a482b35d",
558 | "metadata": {},
559 | "outputs": [],
560 | "source": [
561 | "chatgpt_chain.predict(human_input=\"No\");"
562 | ]
563 | },
564 | {
565 | "cell_type": "code",
566 | "execution_count": null,
567 | "id": "11499b05-0a43-4c1c-96d2-7095099d4371",
568 | "metadata": {},
569 | "outputs": [],
570 | "source": [
571 | "chatgpt_chain.predict(human_input=\"Yes\");"
572 | ]
573 | },
574 | {
575 | "cell_type": "code",
576 | "execution_count": null,
577 | "id": "272390d0-0a35-4f34-94d6-3b95e4ce7f08",
578 | "metadata": {},
579 | "outputs": [],
580 | "source": [
581 | "chatgpt_chain.predict(human_input=\"Yes, it's a bird. Can you guess the bird?\");"
582 | ]
583 | },
584 | {
585 | "cell_type": "code",
586 | "execution_count": null,
587 | "id": "f695a1af-7275-40e2-a727-6a86a9abe7b4",
588 | "metadata": {},
589 | "outputs": [],
590 | "source": [
591 | "chatgpt_chain.predict(human_input=\"Yes. Try asking a question that would narrow it down?\");"
592 | ]
593 | },
594 | {
595 | "cell_type": "code",
596 | "execution_count": null,
597 | "id": "7f664d09-6358-4360-b8c5-5cb010457c7a",
598 | "metadata": {},
599 | "outputs": [],
600 | "source": [
601 | "chatgpt_chain.predict(human_input=\"Yes. Can you guess what kind of bird it is?\");"
602 | ]
603 | },
604 | {
605 | "cell_type": "code",
606 | "execution_count": null,
607 | "id": "4a2d61c2-0880-47f9-aa6b-e7568441c280",
608 | "metadata": {},
609 | "outputs": [],
610 | "source": [
611 | "chatgpt_chain.predict(human_input=\"Maybe. Perhaps you can ask where it lives?\");"
612 | ]
613 | },
614 | {
615 | "cell_type": "code",
616 | "execution_count": null,
617 | "id": "fbd707b3-2a69-4a15-a986-5febeea65c10",
618 | "metadata": {},
619 | "outputs": [],
620 | "source": [
621 | "chatgpt_chain.predict(human_input=\"Yes, it lives in the jungle.\");"
622 | ]
623 | },
624 | {
625 | "cell_type": "code",
626 | "execution_count": null,
627 | "id": "0f5d22e1-6528-4c4b-bd77-f8e47387def9",
628 | "metadata": {},
629 | "outputs": [],
630 | "source": [
631 | "chatgpt_chain.predict(human_input=\"Yes! Would you like to give it a name?\");"
632 | ]
633 | },
634 | {
635 | "cell_type": "code",
636 | "execution_count": null,
637 | "id": "f2fdac5e-5da6-448f-8f9b-0236fa38e099",
638 | "metadata": {},
639 | "outputs": [],
640 | "source": [
641 | "chatgpt_chain.predict(human_input=\"Can you suggest something a little more creative and fun?\");"
642 | ]
643 | },
644 | {
645 | "cell_type": "code",
646 | "execution_count": null,
647 | "id": "86b25439-6393-4755-94f3-71e8e645519c",
648 | "metadata": {},
649 | "outputs": [],
650 | "source": [
651 | "chatgpt_chain.predict(human_input=\"I love it. Can you summarise this conversation using emoji?\");"
652 | ]
653 | },
654 | {
655 | "cell_type": "markdown",
656 | "id": "f8646efa-78bd-4ef8-baee-31aff6165fa7",
657 | "metadata": {},
658 | "source": [
659 | "---"
660 | ]
661 | },
662 | {
663 | "cell_type": "markdown",
664 | "id": "f3b2a2e3-c1a4-41ab-9541-3f04d8cd4bb7",
665 | "metadata": {},
666 | "source": [
667 | "## 5. Exercise: Adapt your OpenAI chatbot to use LangChain + Llama-2\n",
668 | "\n",
669 | "> In the last notebook, you created a chatbot \"application\" using the `openai` library. You should be able to adapt this to use LangChain + Llama-2 with a little effort."
670 | ]
671 | },
672 | {
673 | "cell_type": "code",
674 | "execution_count": null,
675 | "id": "c27e74d5-fde3-4e0a-b4bd-e61b4d41079a",
676 | "metadata": {},
677 | "outputs": [],
678 | "source": []
679 | }
680 | ],
681 | "metadata": {
682 | "kernelspec": {
683 | "display_name": "Python (chatefficient)",
684 | "language": "python",
685 | "name": "chatefficient"
686 | },
687 | "language_info": {
688 | "codemirror_mode": {
689 | "name": "ipython",
690 | "version": 3
691 | },
692 | "file_extension": ".py",
693 | "mimetype": "text/x-python",
694 | "name": "python",
695 | "nbconvert_exporter": "python",
696 | "pygments_lexer": "ipython3",
697 | "version": "3.10.13"
698 | }
699 | },
700 | "nbformat": 4,
701 | "nbformat_minor": 5
702 | }
703 |
--------------------------------------------------------------------------------
/notebooks/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoefficientSystems/chat-efficient/89e659374a81d9915052386968545bf74280472b/notebooks/__init__.py
--------------------------------------------------------------------------------
/notebooks/openai.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "368dccec-1d58-4167-a50d-01c56d98c24e",
6 | "metadata": {},
7 | "source": [
8 | "
"
9 | ]
10 | },
11 | {
12 | "cell_type": "markdown",
13 | "id": "dcaa8042-8f66-4de9-b9cb-3a2383c2e5e0",
14 | "metadata": {},
15 | "source": [
16 | "# Build Your Own Private ChatGPT Super-Assistant Using Streamlit, LangChain, Chroma & Llama 2\n",
17 | "## OpenAI Demo\n",
18 | "**Questions?** contact@coefficient.ai / [@CoefficientData](https://twitter.com/CoefficientData)\n",
19 | "\n",
20 | "---"
21 | ]
22 | },
23 | {
24 | "cell_type": "markdown",
25 | "id": "af9dfd7f-dcc6-418c-bc6b-02c7771e07fa",
26 | "metadata": {},
27 | "source": [
28 | "## 0. Imports"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "id": "d73809ac-30df-4182-8d69-e49d9ccc60b0",
35 | "metadata": {},
36 | "outputs": [],
37 | "source": [
38 | "import time\n",
39 | "\n",
40 | "from dotenv import load_dotenv\n",
41 | "from openai import OpenAI\n",
42 | "\n",
43 | "from utils import scrape_page\n",
44 | "\n",
45 | "load_dotenv();"
46 | ]
47 | },
48 | {
49 | "cell_type": "markdown",
50 | "id": "c8a2f7f0-8d61-4feb-b4c7-6cfff3c7cf9b",
51 | "metadata": {},
52 | "source": [
53 | "## 1. OpenAI API Basics"
54 | ]
55 | },
56 | {
57 | "cell_type": "markdown",
58 | "id": "42da2e1d-4461-4299-899b-f6f3ac083e04",
59 | "metadata": {},
60 | "source": [
61 | "### Set up client and context"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": null,
67 | "id": "298d371f-4ae0-48ca-ae4b-c1c82b0801e7",
68 | "metadata": {},
69 | "outputs": [],
70 | "source": [
71 | "# Ensure you've entered your OpenAI API key in the .env file\n",
72 | "client = OpenAI()"
73 | ]
74 | },
75 | {
76 | "cell_type": "code",
77 | "execution_count": null,
78 | "id": "a9141d9e-4d5d-4419-bbde-e957dc656842",
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "model = \"gpt-3.5-turbo\"\n",
83 | "\n",
84 | "messages = [\n",
85 | " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"}\n",
86 | "]"
87 | ]
88 | },
89 | {
90 | "cell_type": "markdown",
91 | "id": "79a55942-8bd7-4433-9b0f-86dab89e8092",
92 | "metadata": {},
93 | "source": [
94 | "### Add prompt"
95 | ]
96 | },
97 | {
98 | "cell_type": "code",
99 | "execution_count": null,
100 | "id": "76cc208d-9693-4787-8edd-1fcfb438655c",
101 | "metadata": {},
102 | "outputs": [],
103 | "source": [
104 | "prompt = \"Explain how GPT works using a limerick.\""
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": null,
110 | "id": "e5b4aae4-96e9-4e05-b992-420d471a74b8",
111 | "metadata": {},
112 | "outputs": [],
113 | "source": [
114 | "messages.append({\"role\": \"user\", \"content\": prompt})\n",
115 | "\n",
116 | "messages"
117 | ]
118 | },
119 | {
120 | "cell_type": "markdown",
121 | "id": "b0151377-a83a-4925-a665-df7e1347b46d",
122 | "metadata": {},
123 | "source": [
124 | "### Get completion"
125 | ]
126 | },
127 | {
128 | "cell_type": "code",
129 | "execution_count": null,
130 | "id": "e9ec337b-822a-440a-95c5-e42614e78ff9",
131 | "metadata": {},
132 | "outputs": [],
133 | "source": [
134 | "%%time\n",
135 | "completion = client.chat.completions.create(model=model, messages=messages)\n",
136 | "response = completion.choices[0].message.content"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": null,
142 | "id": "68e679ed-8154-4af7-8286-2afc1041aba0",
143 | "metadata": {},
144 | "outputs": [],
145 | "source": [
146 | "print(response)"
147 | ]
148 | },
149 | {
150 | "cell_type": "markdown",
151 | "id": "ae5acf31-517b-4f0b-bb2e-7923a7a1ad5a",
152 | "metadata": {},
153 | "source": [
154 | "### Add response to context"
155 | ]
156 | },
157 | {
158 | "cell_type": "code",
159 | "execution_count": null,
160 | "id": "a8ef9052-b131-46ba-80a7-38340fdfd391",
161 | "metadata": {},
162 | "outputs": [],
163 | "source": [
164 | "messages.append({\"role\": \"assistant\", \"content\": response})"
165 | ]
166 | },
167 | {
168 | "cell_type": "code",
169 | "execution_count": null,
170 | "id": "f1e00765-ea23-407b-9436-24b3f4754b62",
171 | "metadata": {},
172 | "outputs": [],
173 | "source": [
174 | "messages"
175 | ]
176 | },
177 | {
178 | "cell_type": "markdown",
179 | "id": "9f1c3199-6f13-4d0f-9deb-bddf263da6a6",
180 | "metadata": {},
181 | "source": [
182 | "### New prompt"
183 | ]
184 | },
185 | {
186 | "cell_type": "code",
187 | "execution_count": null,
188 | "id": "c4d2cf9d-66c0-47d0-aee0-ff49b443d333",
189 | "metadata": {},
190 | "outputs": [],
191 | "source": [
192 | "prompt = \"That's great! Now do it as a haiku.\""
193 | ]
194 | },
195 | {
196 | "cell_type": "code",
197 | "execution_count": null,
198 | "id": "d81dfb7f-891f-4f4c-909b-bb3f16bef4e1",
199 | "metadata": {},
200 | "outputs": [],
201 | "source": [
202 | "messages.append({\"role\": \"user\", \"content\": prompt})\n",
203 | "messages"
204 | ]
205 | },
206 | {
207 | "cell_type": "markdown",
208 | "id": "b2e28200-3c2b-45dc-a8bc-b0c9c17b7ea6",
209 | "metadata": {},
210 | "source": [
211 | "### Get completion"
212 | ]
213 | },
214 | {
215 | "cell_type": "code",
216 | "execution_count": null,
217 | "id": "b120704a-b7b1-4a39-befe-2f486abbd7a5",
218 | "metadata": {},
219 | "outputs": [],
220 | "source": [
221 | "%%time\n",
222 | "completion = client.chat.completions.create(model=model, messages=messages)\n",
223 | "response = completion.choices[0].message.content"
224 | ]
225 | },
226 | {
227 | "cell_type": "code",
228 | "execution_count": null,
229 | "id": "ac8c11b2-00ce-4278-8c35-1f495d0e00ff",
230 | "metadata": {},
231 | "outputs": [],
232 | "source": [
233 | "print(response)"
234 | ]
235 | },
236 | {
237 | "cell_type": "markdown",
238 | "id": "de67362c-797d-4473-aa47-c207b1ad7bfc",
239 | "metadata": {},
240 | "source": [
241 | "### Add response to context"
242 | ]
243 | },
244 | {
245 | "cell_type": "code",
246 | "execution_count": null,
247 | "id": "312b3f5a-4601-467a-9140-7ecd54ace14a",
248 | "metadata": {},
249 | "outputs": [],
250 | "source": [
251 | "messages.append({\"role\": \"assistant\", \"content\": response})\n",
252 | "messages"
253 | ]
254 | },
255 | {
256 | "cell_type": "markdown",
257 | "id": "f7075a0d-cf15-478e-8949-2e82b8a82969",
258 | "metadata": {},
259 | "source": [
260 | "---"
261 | ]
262 | },
263 | {
264 | "cell_type": "markdown",
265 | "id": "567d5091-3573-40f1-b14c-ec2feb9e7af8",
266 | "metadata": {},
267 | "source": [
268 | "## 2. How to cache with joblib"
269 | ]
270 | },
271 | {
272 | "cell_type": "code",
273 | "execution_count": null,
274 | "id": "1d95f8d1-0b19-4306-8edc-55e23d730ab7",
275 | "metadata": {},
276 | "outputs": [],
277 | "source": [
278 | "def calculate(x):\n",
279 | " time.sleep(3)\n",
280 | " return x * 10"
281 | ]
282 | },
283 | {
284 | "cell_type": "code",
285 | "execution_count": null,
286 | "id": "5fb41ea3-6633-4451-937a-a7d439c1a97c",
287 | "metadata": {},
288 | "outputs": [],
289 | "source": [
290 | "%%time\n",
291 | "calculate(666) # slow 😴"
292 | ]
293 | },
294 | {
295 | "cell_type": "markdown",
296 | "id": "f62ca113-dbd7-470a-80a8-8bbbb90be9f5",
297 | "metadata": {},
298 | "source": [
299 | "⬇\n",
300 | "
\n",
301 | "⬇"
302 | ]
303 | },
304 | {
305 | "cell_type": "code",
306 | "execution_count": null,
307 | "id": "c07529c2-c8f6-447d-ae00-e1704c7f4245",
308 | "metadata": {},
309 | "outputs": [],
310 | "source": [
311 | "from joblib import Memory\n",
312 | "\n",
313 | "memory = Memory(\"./cache\", verbose=0)"
314 | ]
315 | },
316 | {
317 | "cell_type": "code",
318 | "execution_count": null,
319 | "id": "d1e4989d-d5e4-4aa2-ba5b-9305939462d9",
320 | "metadata": {},
321 | "outputs": [],
322 | "source": [
323 | "@memory.cache\n",
324 | "def calculate(x):\n",
325 | " time.sleep(3)\n",
326 | " return x * 10"
327 | ]
328 | },
329 | {
330 | "cell_type": "code",
331 | "execution_count": null,
332 | "id": "90923412-f2cb-462d-a61f-4a5e0ee5553b",
333 | "metadata": {},
334 | "outputs": [],
335 | "source": [
336 | "%%time\n",
337 | "calculate(666) # slow 😴"
338 | ]
339 | },
340 | {
341 | "cell_type": "code",
342 | "execution_count": null,
343 | "id": "1989009a-033e-43e2-ae8a-a30fcf4df457",
344 | "metadata": {},
345 | "outputs": [],
346 | "source": [
347 | "%%time\n",
348 | "calculate(666) # fast! 🏃♂️"
349 | ]
350 | },
351 | {
352 | "cell_type": "markdown",
353 | "id": "f6047548-7d9d-43d5-acf8-4ba98469a028",
354 | "metadata": {},
355 | "source": [
356 | "⬇\n",
357 | "
\n",
358 | "⬇"
359 | ]
360 | },
361 | {
362 | "cell_type": "code",
363 | "execution_count": null,
364 | "id": "0c9be681-2363-43a3-be45-657d84c3c3e8",
365 | "metadata": {},
366 | "outputs": [],
367 | "source": [
368 | "%%time\n",
369 | "calculate(42)"
370 | ]
371 | },
372 | {
373 | "cell_type": "code",
374 | "execution_count": null,
375 | "id": "bda8e53d-7b30-47f3-9489-b202c2fb9b19",
376 | "metadata": {},
377 | "outputs": [],
378 | "source": [
379 | "%%time\n",
380 | "calculate(42)"
381 | ]
382 | },
383 | {
384 | "cell_type": "markdown",
385 | "id": "1de5fede-4f2c-41df-8881-2831308b539f",
386 | "metadata": {},
387 | "source": [
388 | "\n",
389 | "⬇\n",
390 | "
\n",
391 | "⬇"
392 | ]
393 | },
394 | {
395 | "cell_type": "code",
396 | "execution_count": null,
397 | "id": "2e985e43-d8ef-4c31-bbb7-15965ca5c9a1",
398 | "metadata": {},
399 | "outputs": [],
400 | "source": [
401 | "# Clear the cache so I can give this talk again!\n",
402 | "memory.clear()"
403 | ]
404 | },
405 | {
406 | "cell_type": "markdown",
407 | "id": "885f8c47-c10c-4e3f-8e77-7c910b69579a",
408 | "metadata": {},
409 | "source": [
410 | "---"
411 | ]
412 | },
413 | {
414 | "cell_type": "markdown",
415 | "id": "e10fc707-5254-4c62-8bc3-fdd49e019a02",
416 | "metadata": {},
417 | "source": [
418 | "## 3. Make a cached OpenAI function"
419 | ]
420 | },
421 | {
422 | "cell_type": "code",
423 | "execution_count": null,
424 | "id": "ac3200cf-3246-4f85-89be-5832a556b2b1",
425 | "metadata": {},
426 | "outputs": [],
427 | "source": [
428 | "@memory.cache\n",
429 | "def generate_response(prompt, context):\n",
430 | " context.append({\"role\": \"user\", \"content\": prompt})\n",
431 | "\n",
432 | " completion = client.chat.completions.create(model=model, messages=messages)\n",
433 | " response = completion.choices[0].message.content\n",
434 | " context.append({\"role\": \"assistant\", \"content\": response})\n",
435 | "\n",
436 | " return response, context"
437 | ]
438 | },
439 | {
440 | "cell_type": "code",
441 | "execution_count": null,
442 | "id": "a4cced3e-8d2e-4c1b-89ff-2cfd0137ebb6",
443 | "metadata": {},
444 | "outputs": [],
445 | "source": [
446 | "model = \"gpt-3.5-turbo\"\n",
447 | "messages = [\n",
448 | " {\"role\": \"system\", \"content\": \"You are a sales assistant for the awesome machine learning consultancy, Coefficient.\"}\n",
449 | "]"
450 | ]
451 | },
452 | {
453 | "cell_type": "code",
454 | "execution_count": null,
455 | "id": "0658db76-a1d2-465e-877d-55b856521578",
456 | "metadata": {},
457 | "outputs": [],
458 | "source": [
459 | "# Write a prompt\n",
460 | "prompt = f\"\"\"\n",
461 | "Here's some information about GOV.UK:\n",
462 | "\n",
463 | "{scrape_page('https://gov.uk')}\n",
464 | "\n",
465 | "Please summarise this country's services in 3 bullet points.\n",
466 | "\"\"\""
467 | ]
468 | },
469 | {
470 | "cell_type": "code",
471 | "execution_count": null,
472 | "id": "24dfa44c-842b-49cf-a592-de71bfbbe94e",
473 | "metadata": {},
474 | "outputs": [],
475 | "source": [
476 | "%%time\n",
477 | "\n",
478 | "# Get response & update context\n",
479 | "response, context = generate_response(prompt, context=messages)"
480 | ]
481 | },
482 | {
483 | "cell_type": "code",
484 | "execution_count": null,
485 | "id": "854c24b4-a388-4f47-8899-9540cb53cbb0",
486 | "metadata": {},
487 | "outputs": [],
488 | "source": [
489 | "print(response)"
490 | ]
491 | },
492 | {
493 | "cell_type": "code",
494 | "execution_count": null,
495 | "id": "ce1fa4b3-e4ac-468d-bd3b-0ebf7448b5c0",
496 | "metadata": {},
497 | "outputs": [],
498 | "source": [
499 | "%%time\n",
500 | "\n",
501 | "# That doesn't quite hit the mark, let's try again...\n",
502 | "\n",
503 | "response, context = generate_response(\n",
504 | " prompt=\"Can you make it sound more fun? Use some emoji?\",\n",
505 | " context=messages\n",
506 | ")"
507 | ]
508 | },
509 | {
510 | "cell_type": "code",
511 | "execution_count": null,
512 | "id": "07c59dda-7d3b-48f8-ad54-31591b04adb1",
513 | "metadata": {},
514 | "outputs": [],
515 | "source": [
516 | "print(response)"
517 | ]
518 | },
519 | {
520 | "cell_type": "markdown",
521 | "id": "d19aad1c-c564-4919-aec2-e7d856c3f281",
522 | "metadata": {},
523 | "source": [
524 | "---"
525 | ]
526 | },
527 | {
528 | "cell_type": "markdown",
529 | "id": "bbf10192-c811-4238-8194-ebd4e8a7d07a",
530 | "metadata": {},
531 | "source": [
532 | "## 4. Exercise: Build your own LLM tool\n",
533 | "\n",
534 | "> **Exercise**\n",
535 | "> Time to get creative! Adapt the cached OpenAI function to make it do something else. Here's some ideas to get you thinking:\n",
536 | "> - Ask it to summarise a movie using emoji (or the reverse, to guess a movie from emoji).\n",
537 | "> - Read in a CSV using pandas `pd.read_csv()` containing customer feedback, and ask it to summarise the top 5 issues with recommended actions.\n",
538 | "> - Use the provided `scrape_page()` function to build an auto-summariser for any scrapable webpage, such as GOV.UK pages or articles.\n",
539 | "> - Adapt it to generate a list of ideas for any problem you supply, and then ask it to provide ideas for this exercise."
540 | ]
541 | },
542 | {
543 | "cell_type": "code",
544 | "execution_count": null,
545 | "id": "e23a31a7-8007-49c1-82ef-03ad938a834c",
546 | "metadata": {},
547 | "outputs": [],
548 | "source": [
549 | "\n",
550 | "\n",
551 | "\n"
552 | ]
553 | },
554 | {
555 | "cell_type": "markdown",
556 | "id": "61f14200-dfc9-436d-b624-b7c061c57736",
557 | "metadata": {},
558 | "source": [
559 | "---\n",
560 | "\n",
561 | "
\n", 564 | " This notebook has been made by @John_Sandall. I run training workshops in Python, data science and data engineering.\n", 565 | "
\n", 567 | " You can follow my free First Steps with Python and First Steps with pandas workshops for free as part of PyData Bristol's Zero To Hero workshop series. I now run the PyData London Meetup, a great place to learn more and meet other data professionals every month.\n", 568 | "
\n", 570 | " I am the Founder of data science consultancy Coefficient. If you would like to work with us, our team can help you with your data science, software engineering, modern data stack or machine learning projects as an on-demand resource. We can also create bespoke training workshops adapted to your industry, virtual or in-person, with training clients currently including BNP Paribas, EY, the Met Police and the BBC.\n", 571 | "
\n", 572 | "