Compare commits

...

125 Commits

Author SHA1 Message Date
Eugene Yurtsev
b47148bbed how to: Update streaming LLM information (#21381)
Update information in streaming llm how-to.

This is mirroring the changes in how to stream chat models.
2024-05-07 14:40:13 -04:00
Eugene Yurtsev
a27cab6af0 how to: stream chat models (#21380)
- paraphrase overview
- add links to api reference
- show astream and astream event 
- update to partner package model
2024-05-07 11:10:36 -04:00
ccurme
da48378ade (new docs): fix links (#21348) 2024-05-06 18:25:42 -04:00
ccurme
4792f0575c (new docs): fix links (#21345) 2024-05-06 17:52:54 -04:00
Eugene Yurtsev
5d5492ebb4 Fix formatting of bullet points in Conversational RAG (#21341)
Need extra \n to get it to render the bullet points.
2024-05-06 17:03:34 -04:00
Eugene Yurtsev
2275f05775 RAG: A few formatting fixes (#21340)
Formatting fixes for bullet points. Add missing whitespace.
2024-05-06 17:03:18 -04:00
Chester Curme
50e34f1bdf add link 2024-05-06 16:35:38 -04:00
ccurme
c44287ebfe (new docs): update sidebars (#21329) 2024-05-06 16:09:40 -04:00
ccurme
58e91eaca9 (new docs): update SQL how-tos (#21325) 2024-05-06 13:41:15 -04:00
Harrison Chase
dc2491eb58 cr 2024-05-06 08:38:03 -07:00
Harrison Chase
02e86d5c9a cr 2024-05-06 08:19:35 -07:00
Harrison Chase
716a8c8ad7 cr 2024-05-06 08:13:36 -07:00
ccurme
93544443ea (new docs): fix build and resolve feedback (#21253) 2024-05-03 11:17:48 -04:00
ccurme
e894559fed (new docs) update links (#21228)
Done with a script + manual review: 

1. Map unique file names to new paths;
2. Where those file names have old links, update them.
2024-05-03 10:28:12 -04:00
ccurme
507fa9439b (new docs): format (#21226) 2024-05-02 17:46:58 -04:00
ccurme
d5b89f3a4f (new docs): add agents to sidebar (#21221) 2024-05-02 17:26:23 -04:00
ccurme
b8bd9edb2f (new docs): update extraction how-to guides (#21195) 2024-05-02 16:27:16 -04:00
ccurme
8987aaf8b7 (new docs): fix (#21217)
Builds broken by
a70459f54f
2024-05-02 16:00:33 -04:00
Harrison Chase
a70459f54f cr 2024-05-01 16:24:47 -07:00
Harrison Chase
0522e9def3 cr 2024-05-01 16:07:43 -07:00
Harrison Chase
cf866efb78 Merge branch 'harrison/new-docs' of github.com:hwchase17/langchain into harrison/new-docs 2024-05-01 16:07:31 -07:00
Harrison Chase
8e8a03d61b cr 2024-05-01 16:07:24 -07:00
ccurme
c77debf870 (new docs): update rag use-case docs (#21164) 2024-05-01 16:25:14 -04:00
ccurme
6a20856fab (new docs): embedding how-to guides (#21106) 2024-04-30 14:49:06 -04:00
Chester Curme
7f4397c94a format 2024-04-30 12:44:14 -04:00
Chester Curme
7285370328 update tutorial 2024-04-30 12:43:54 -04:00
ccurme
df8a2cdc96 (new docs): update text splitter how-to guides (#21087) 2024-04-30 11:34:42 -04:00
ccurme
c3b7933d98 (new docs): update how-to guides (#21073) 2024-04-30 08:21:09 -04:00
Harrison Chase
8a0e71d27b Merge branch 'master' into harrison/new-docs 2024-04-29 16:30:10 -07:00
Harrison Chase
86bb3aa45b Merge branch 'harrison/new-docs' of github.com:hwchase17/langchain into harrison/new-docs 2024-04-29 16:29:52 -07:00
Harrison Chase
55dd2ea57d cr 2024-04-29 16:29:47 -07:00
ccurme
bc4bb49451 (new docs): remove agents from sidebar (#21046) 2024-04-29 19:18:08 -04:00
ccurme
392b842a59 (new docs): organize how-to sidebars (#21029)
```python
import json
import re
from pathlib import Path

def parse_markdown_to_sidebar(markdown_content):
    lines = markdown_content.splitlines()
    sidebar = []
    current_category = None
    current_subcategory = None

    for line in lines:
        if line.startswith('### '):
            # Subcategory
            if current_subcategory is not None:
                current_category['items'].append(current_subcategory)
            subcategory_title = line.strip('# ').strip()
            current_subcategory = {
                "type": "category",
                "label": subcategory_title,
                "collapsed": True,
                "items": [],
                "link": {"type": "generated-index"}
            }
        elif line.startswith('## '):
            # Category
            if current_category is not None:
                if current_subcategory is not None:
                    current_category['items'].append(current_subcategory)
                    current_subcategory = None
                sidebar.append(current_category)
            category_title = line.strip('# ').strip()
            current_category = {
                "type": "category",
                "label": category_title,
                "collapsed": True,
                "items": [],
                "link": {"type": "generated-index"}
            }
        elif line.startswith('- ['):
            # Link
            match = re.match(r'- \[(.*?)\]\((.*?)\)', line)
            if match:
                title, link = match.groups()
                link = link.replace('/docs/', '')  # Remove '/docs/' prefix
                if current_subcategory is not None:
                    current_subcategory['items'].append(link)
                elif current_category is not None:
                    current_category['items'].append(link)

    # Add the last category and subcategory if they exist
    if current_subcategory is not None:
        current_category['items'].append(current_subcategory)
    if current_category is not None:
        sidebar.append(current_category)

    return sidebar

def generate_sidebar_json(file_path):
    with open(file_path, 'r') as md_file:
        markdown_content = md_file.read()
    sidebar = parse_markdown_to_sidebar(markdown_content)
    sidebar_json = json.dumps({"items": sidebar}, indent=2)
    return sidebar_json
```
2024-04-29 19:00:06 -04:00
Harrison Chase
e037446ca3 cr 2024-04-29 15:40:00 -07:00
Harrison Chase
8920bcd263 Merge branch 'harrison/new-docs' of github.com:hwchase17/langchain into harrison/new-docs 2024-04-29 15:39:55 -07:00
Harrison Chase
81a7868c57 cr 2024-04-29 15:39:50 -07:00
ccurme
d99a7a6b44 (new docs): update how-to guides (#21039) 2024-04-29 16:27:58 -04:00
ccurme
38bd7f4dd6 (new docs): update sidebars alt (#21024) 2024-04-29 11:57:30 -04:00
Jacob Lee
fd7f041d6b docs[patch]: Increase line height (#20960)
Help with readability, reduce intimidation factor

@hwchase17
2024-04-26 18:42:03 -07:00
Jacob Lee
19a2f59713 docs[patch]: Hide navbar item on old versions (#20953)
@hwchase17 @ccurme @efriis
2024-04-26 18:41:53 -07:00
Harrison Chase
be73daaa64 cr 2024-04-26 17:57:16 -07:00
ccurme
1dc9232b9a (new docs): updates (#20897) 2024-04-25 13:35:01 -04:00
Harrison Chase
66b2ac62eb cr 2024-04-24 17:07:56 -07:00
Harrison Chase
e9eb1e29c8 cr 2024-04-24 16:34:50 -07:00
Harrison Chase
88bbabae28 Merge branch 'master' into harrison/new-docs 2024-04-24 14:59:08 -07:00
Harrison Chase
b0a3e12c8f cr 2024-04-24 14:24:59 -07:00
Harrison Chase
a1e769251c cr 2024-04-24 14:09:21 -07:00
Harrison Chase
3aee2fac83 cr 2024-04-24 13:52:02 -07:00
Harrison Chase
c47f4e668b Merge branch 'harrison/new-docs' of github.com:hwchase17/langchain into harrison/new-docs 2024-04-24 13:51:55 -07:00
Harrison Chase
601fa45eda Merge branch 'master' into harrison/new-docs 2024-04-24 13:40:07 -07:00
Harrison Chase
5aa134799c stash 2024-04-24 13:40:02 -07:00
Harrison Chase
f04f012658 stash 2024-04-24 13:24:15 -07:00
ccurme
294e81df5d update tutorials (#20854) 2024-04-24 15:39:11 -04:00
Harrison Chase
0ff7a4054b cr 2024-04-24 11:29:24 -07:00
Harrison Chase
4448825414 cr 2024-04-24 08:19:49 -07:00
Harrison Chase
c6b63c5bf1 cr 2024-04-24 08:18:12 -07:00
Harrison Chase
eec4d5802e cr 2024-04-24 07:59:00 -07:00
Harrison Chase
a915f16ead cr 2024-04-23 21:27:55 -07:00
Harrison Chase
95f7055478 cr 2024-04-23 18:06:12 -07:00
Harrison Chase
a19c9cddc2 cr 2024-04-23 17:35:41 -07:00
Harrison Chase
efa3e67bd7 cr 2024-04-23 17:32:51 -07:00
Harrison Chase
b3997501e1 cr 2024-04-23 15:01:57 -07:00
Harrison Chase
ea83256a22 stash 2024-04-23 13:44:08 -07:00
Harrison Chase
2280fc63a2 cr 2024-04-23 13:02:49 -07:00
Harrison Chase
cc9bba53dc Merge branch 'master' into harrison/new-docs 2024-04-23 12:58:13 -07:00
Harrison Chase
b390f58604 Merge branch 'harrison/new-docs' of github.com:hwchase17/langchain into harrison/new-docs 2024-04-22 15:59:48 -07:00
jacoblee93
234a671f7b Merge 2024-04-22 15:33:36 -07:00
Harrison Chase
abec5726a7 Merge branch 'master' into harrison/new-docs 2024-04-22 15:16:18 -07:00
jacoblee93
78ff0392d2 Update chat model tabs to use one code block 2024-04-22 10:11:59 -07:00
jacoblee93
5a78b090ad Update more output parser how to guides 2024-04-22 01:20:42 -07:00
jacoblee93
a6895f7a10 Fix prerequisite link component, add to more guides 2024-04-21 20:27:52 -07:00
jacoblee93
103b275d47 Fix 2024-04-20 22:07:03 -07:00
jacoblee93
1927977fd7 More updates 2024-04-20 21:32:01 -07:00
jacoblee93
f2fc84c4c5 Consolidate multiple chains how-to guide 2024-04-20 20:49:49 -07:00
jacoblee93
8c4b283fe8 Consolidate decorator how to guide 2024-04-20 19:03:21 -07:00
jacoblee93
1385cfa55f Merge branch 'harrison/new-docs' of https://github.com/langchain-ai/langchain into harrison/new-docs 2024-04-20 18:25:17 -07:00
jacoblee93
94c339e3d1 More how tos 2024-04-20 18:25:12 -07:00
Harrison Chase
5bfa57a7e4 cr 2024-04-20 16:49:45 -07:00
Harrison Chase
ddea36514f cr 2024-04-20 16:47:03 -07:00
Harrison Chase
51f1f4b045 cr 2024-04-20 16:18:15 -07:00
Harrison Chase
c6d0993c93 cr 2024-04-20 14:22:29 -07:00
Harrison Chase
1ca2138720 cr 2024-04-20 09:30:16 -07:00
Harrison Chase
ed207b365b cr 2024-04-20 09:20:51 -07:00
jacoblee93
d962d97952 More runnable how-to guides 2024-04-19 18:11:17 -07:00
jacoblee93
c6e4459c33 Add passthrough guide 2024-04-19 17:43:41 -07:00
jacoblee93
f53b395c19 More runnable how tos 2024-04-19 17:31:02 -07:00
jacoblee93
9bd75f371b Update some LCEL how-tos 2024-04-19 16:41:06 -07:00
jacoblee93
e021f4727b Add streaming, structured output, and tool calling how tos 2024-04-19 15:10:30 -07:00
jacoblee93
53a85cd5cd Update RAG tutorial 2024-04-19 12:03:22 -07:00
jacoblee93
a2de1f5134 Update RAG guide 2024-04-19 11:32:35 -07:00
jacoblee93
88ba5e1366 Add prereq component, update guide 2024-04-19 11:09:04 -07:00
jacoblee93
367dbeff8a Remove old sidebars 2024-04-18 23:00:39 -07:00
jacoblee93
be2f0802fd Ignore broken links for now 2024-04-18 22:20:21 -07:00
jacoblee93
3cf267770f Fix sidebars 2024-04-18 22:14:47 -07:00
jacoblee93
b44767867a Revert sidebar for now 2024-04-18 20:45:00 -07:00
jacoblee93
1ee05512cb Merge branch 'harrison/new-docs' of https://github.com/langchain-ai/langchain into harrison/new-docs 2024-04-18 20:42:01 -07:00
jacoblee93
803b2b37e2 Merge, update structured output guide 2024-04-18 20:41:49 -07:00
jacoblee93
72482a95d1 Merge branch 'master' of https://github.com/langchain-ai/langchain into harrison/new-docs 2024-04-18 20:00:14 -07:00
Harrison Chase
c9a7225aa1 cr 2024-04-18 19:11:56 -07:00
Harrison Chase
4b937ec67c cr 2024-04-18 18:48:16 -07:00
Harrison Chase
2a99bc6971 cr 2024-04-18 18:39:19 -07:00
Harrison Chase
3bfb519fe6 cr 2024-04-18 18:26:14 -07:00
Harrison Chase
92eb0cdd25 cr 2024-04-18 18:23:28 -07:00
Harrison Chase
4111112386 cr 2024-04-18 18:20:31 -07:00
Harrison Chase
4ec3126d46 cr 2024-04-18 18:18:17 -07:00
Harrison Chase
d3f1ad966b cr 2024-04-18 18:12:50 -07:00
Harrison Chase
aa65827ee5 cr 2024-04-18 18:05:39 -07:00
jacoblee93
dc212edcf3 Fix scripts 2024-04-18 16:01:50 -07:00
jacoblee93
8e238e4a42 Revert core docs 2024-04-18 11:26:48 -07:00
jacoblee93
75ffacf1c2 Merge 2024-04-18 11:20:18 -07:00
jacoblee93
74ccad7474 Merge 2024-04-18 11:18:45 -07:00
Jacob Lee
aff771923a Jacob/new docs (#20570)
Use docusaurus versioning with a callout, merged master as well

@hwchase17 @baskaryan

---------

Signed-off-by: Weichen Xu <weichen.xu@databricks.com>
Signed-off-by: Rahul Tripathi <rauhl.psit.ec@gmail.com>
Co-authored-by: Leonid Ganeline <leo.gan.57@gmail.com>
Co-authored-by: Leonid Kuligin <lkuligin@yandex.ru>
Co-authored-by: Averi Kitsch <akitsch@google.com>
Co-authored-by: Erick Friis <erick@langchain.dev>
Co-authored-by: Nuno Campos <nuno@langchain.dev>
Co-authored-by: Nuno Campos <nuno@boringbits.io>
Co-authored-by: Bagatur <22008038+baskaryan@users.noreply.github.com>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
Co-authored-by: Martín Gotelli Ferenaz <martingotelliferenaz@gmail.com>
Co-authored-by: Fayfox <admin@fayfox.com>
Co-authored-by: Eugene Yurtsev <eugene@langchain.dev>
Co-authored-by: Dawson Bauer <105886620+djbauer2@users.noreply.github.com>
Co-authored-by: Ravindu Somawansa <ravindu.somawansa@gmail.com>
Co-authored-by: Dhruv Chawla <43818888+Dominastorm@users.noreply.github.com>
Co-authored-by: ccurme <chester.curme@gmail.com>
Co-authored-by: Bagatur <baskaryan@gmail.com>
Co-authored-by: WeichenXu <weichen.xu@databricks.com>
Co-authored-by: Benito Geordie <89472452+benitoThree@users.noreply.github.com>
Co-authored-by: kartikTAI <129414343+kartikTAI@users.noreply.github.com>
Co-authored-by: Kartik Sarangmath <kartik@thirdai.com>
Co-authored-by: Sevin F. Varoglu <sfvaroglu@octoml.ai>
Co-authored-by: MacanPN <martin.triska@gmail.com>
Co-authored-by: Prashanth Rao <35005448+prrao87@users.noreply.github.com>
Co-authored-by: Hyeongchan Kim <kozistr@gmail.com>
Co-authored-by: sdan <git@sdan.io>
Co-authored-by: Guangdong Liu <liugddx@gmail.com>
Co-authored-by: Rahul Triptahi <rahul.psit.ec@gmail.com>
Co-authored-by: Rahul Tripathi <rauhl.psit.ec@gmail.com>
Co-authored-by: pjb157 <84070455+pjb157@users.noreply.github.com>
Co-authored-by: Eun Hye Kim <ehkim1440@gmail.com>
Co-authored-by: kaijietti <43436010+kaijietti@users.noreply.github.com>
Co-authored-by: Pengcheng Liu <pcliu.fd@gmail.com>
Co-authored-by: Tomer Cagan <tomer@tomercagan.com>
Co-authored-by: Christophe Bornet <cbornet@hotmail.com>
2024-04-18 11:10:55 -07:00
jacoblee93
914e9654c9 Naming 2024-04-16 10:50:03 -07:00
jacoblee93
cb2ee22df6 Adjust headers 2024-04-16 10:48:01 -07:00
Harrison Chase
2bd051d7cf cr 2024-04-15 17:19:16 -07:00
Harrison Chase
353c75f4a9 cr 2024-04-15 16:58:45 -07:00
Harrison Chase
debb5f7a0c cr 2024-04-15 16:43:39 -07:00
Harrison Chase
11bc17ce93 cr 2024-04-15 15:50:06 -07:00
jacoblee93
374016e227 Merge branch 'master' of https://github.com/langchain-ai/langchain into harrison/new-docs 2024-04-15 11:32:49 -07:00
jacoblee93
f9d91e97b2 Fix links, group components by section 2024-04-15 11:32:35 -07:00
jacoblee93
a16d409da4 Link 2024-04-13 11:27:50 -07:00
jacoblee93
b34ef12265 Adds component concepts 2024-04-13 10:47:45 -07:00
Harrison Chase
d4276560c6 cr 2024-04-12 18:14:22 -07:00
Harrison Chase
79a713c2c7 cr 2024-04-11 19:32:18 -07:00
Harrison Chase
ccf695ed88 new docs stuff 2024-04-11 19:31:31 -07:00
1190 changed files with 278989 additions and 56 deletions

View File

@@ -19,6 +19,15 @@ poetry run python scripts/copy_templates.py
wget -q https://raw.githubusercontent.com/langchain-ai/langserve/main/README.md -O docs/langserve.md
wget -q https://raw.githubusercontent.com/langchain-ai/langgraph/main/README.md -O docs/langgraph.md
# Duplicate changes to 0.2.x version
cp docs/integrations/llms/index.mdx versioned_docs/version-0.2.x/integrations/llms/
cp docs/integrations/chat/index.mdx versioned_docs/version-0.2.x/integrations/chat/
mkdir -p versioned_docs/version-0.2.x/templates
cp -r docs/templates/* versioned_docs/version-0.2.x/templates/
cp docs/langserve.md versioned_docs/version-0.2.x/
cp docs/langgraph.md versioned_docs/version-0.2.x/
poetry run python scripts/resolve_versioned_links_in_markdown.py versioned_docs/version-0.2.x/ /docs/0.2.x/
poetry run quarto render docs
poetry run python scripts/generate_api_reference_links.py --docs_dir docs

View File

@@ -529,7 +529,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.0"
"version": "3.10.1"
}
},
"nbformat": 4,

View File

@@ -302,7 +302,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
"version": "3.10.1"
}
},
"nbformat": 4,

View File

@@ -153,7 +153,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
"version": "3.10.1"
}
},
"nbformat": 4,

View File

@@ -221,7 +221,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -235,7 +235,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.5"
"version": "3.10.1"
}
},
"nbformat": 4,

View File

@@ -20,7 +20,7 @@ That's a fair amount to cover! Let's dive in.
This guide (and most of the other guides in the documentation) uses [Jupyter notebooks](https://jupyter.org/) and assumes the reader is as well. Jupyter notebooks are perfect for learning how to work with LLM systems because oftentimes things can go wrong (unexpected output, API down, etc) and going through guides in an interactive environment is a great way to better understand them.
You do not NEED to go through the guide in a Jupyter Notebook, but it is recommended. See [here](https://jupyter.org/install) for instructions on how to install.
This and other tutorials are perhaps most conveniently run in a Jupyter notebook. See [here](https://jupyter.org/install) for instructions on how to install.
### Installation

View File

@@ -685,9 +685,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "poetry-venv-2",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "poetry-venv-2"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
@@ -699,7 +699,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.1"
"version": "3.10.1"
}
},
"nbformat": 4,

View File

@@ -20,8 +20,8 @@ const config = {
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: "/",
trailingSlash: true,
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "throw",
onBrokenLinks: "warn",
onBrokenMarkdownLinks: "warn",
themes: ["@docusaurus/theme-mermaid"],
markdown: {
@@ -81,6 +81,17 @@ const config = {
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
lastVersion: "current",
versions: {
current: {
label: '0.1.x',
badge: false,
},
"0.2.x": {
label: "0.2.x",
banner: "unreleased",
}
},
sidebarPath: require.resolve("./sidebars.js"),
remarkPlugins: [
[require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }],
@@ -149,9 +160,11 @@ const config = {
logo: {src: "img/brand/wordmark.png", srcDark: "img/brand/wordmark-dark.png"},
items: [
{
to: "/docs/modules",
type: "doc",
docId: "modules/index",
label: "Components",
position: "left",
className: "hidden-0\.2\.x",
},
{
type: "docSidebar",
@@ -159,11 +172,6 @@ const config = {
sidebarId: "integrations",
label: "Integrations",
},
{
to: "/docs/guides",
label: "Guides",
position: "left",
},
{
href: "https://api.python.langchain.com",
label: "API Reference",
@@ -175,15 +183,18 @@ const config = {
position: "left",
items: [
{
to: "/docs/people/",
type: "doc",
docId: "people",
label: "People",
},
{
to: "/docs/packages",
type: "doc",
docId: "packages",
label: "Versioning",
},
{
to: "/docs/contributing",
type: "doc",
docId: "contributing/index",
label: "Contributing",
},
{
@@ -196,11 +207,13 @@ const config = {
href: "https://github.com/langchain-ai/langchain/blob/master/cookbook/README.md"
},
{
to: "/docs/additional_resources/tutorials",
type: "doc",
docId: "additional_resources/tutorials",
label: "Tutorials"
},
{
to: "/docs/additional_resources/youtube",
type: "doc",
docId: "additional_resources/youtube",
label: "YouTube"
},
]

View File

@@ -30,6 +30,7 @@
"@supabase/supabase-js": "^2.39.7",
"clsx": "^1.2.1",
"cookie": "^0.6.0",
"isomorphic-dompurify": "^2.7.0",
"json-loader": "^0.5.7",
"process": "^0.11.10",
"react": "^17.0.2",
@@ -50,6 +51,7 @@
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"marked": "^12.0.1",
"prettier": "^2.7.1",
"supabase": "^1.148.6",
"typedoc": "^0.24.4",

View File

@@ -0,0 +1,23 @@
import os
import re
import sys
from pathlib import Path
DOCS_DIR = Path(os.path.abspath(__file__)).parents[1]
def update_links(doc_path, docs_link):
for path in (DOCS_DIR / doc_path).glob("**/*"):
if path.is_file() and path.suffix in [".md", ".mdx", ".ipynb"]:
with open(path, "r") as f:
content = f.read()
# replace relative links
content = re.sub("\]\(\/docs\/(?!0\.2\.x)", f"]({docs_link}", content)
with open(path, "w") as f:
f.write(content)
if __name__ == "__main__":
update_links(sys.argv[1], sys.argv[2])

View File

@@ -36,7 +36,7 @@
--ifm-code-font-size: 95%;
--ifm-font-family-base: 'Public Sans';
--ifm-menu-link-padding-horizontal: 0.5rem;
--ifm-menu-link-padding-vertical: 0.375rem;
--ifm-menu-link-padding-vertical: 0.5rem;
--doc-sidebar-width: 275px !important;
}
@@ -55,6 +55,10 @@ nav, h1, h2, h3, h4 {
font-family: 'Manrope';
}
.docs-version-0\.2\.x .hidden-0\.2\.x {
display: none;
}
.footer__links {
margin-top: 1rem;
margin-bottom: 3rem;
@@ -197,6 +201,10 @@ nav, h1, h2, h3, h4 {
opacity: 0.5;
}
.markdown {
line-height: 2em;
}
.markdown > h2 {
margin-top: 2rem;
border-bottom-color: var(--ifm-color-primary);

View File

@@ -4,21 +4,6 @@ import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import CodeBlock from "@theme-original/CodeBlock";
function Setup({ apiKeyName, packageName }) {
const apiKeyText = `import getpass
import os
os.environ["${apiKeyName}"] = getpass.getpass()`;
return (
<>
<h5>Install dependencies</h5>
<CodeBlock language="bash">{`pip install -qU ${packageName}`}</CodeBlock>
<h5>Set environment variables</h5>
<CodeBlock language="python">{apiKeyText}</CodeBlock>
</>
);
}
/**
* @typedef {Object} ChatModelTabsProps - Component props.
* @property {string} [openaiParams] - Parameters for OpenAI chat model. Defaults to `model="gpt-3.5-turbo-0125"`
@@ -146,19 +131,23 @@ export default function ChatModelTabs(props) {
<Tabs groupId="modelTabs">
{tabItems
.filter((tabItem) => !tabItem.shouldHide)
.map((tabItem) => (
<TabItem
value={tabItem.value}
label={tabItem.label}
default={tabItem.default}
>
<Setup
apiKeyName={tabItem.apiKeyName}
packageName={tabItem.packageName}
/>
<CodeBlock language="python">{tabItem.text}</CodeBlock>
</TabItem>
))}
.map((tabItem) => {
const apiKeyText = `import getpass
import os
os.environ["${tabItem.apiKeyName}"] = getpass.getpass()`;
return (
<TabItem
value={tabItem.value}
label={tabItem.label}
default={tabItem.default}
>
<CodeBlock language="bash">{`pip install -qU ${tabItem.packageName}`}</CodeBlock>
<CodeBlock language="python">{apiKeyText + "\n\n" + tabItem.text}</CodeBlock>
</TabItem>
);
})
}
</Tabs>
);
}

View File

@@ -0,0 +1,201 @@
// Swizzled class to show custom text for canary version.
// Should be removed in favor of the stock implementation.
import React from 'react';
import clsx from 'clsx';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Link from '@docusaurus/Link';
import Translate from '@docusaurus/Translate';
import {
useActivePlugin,
useDocVersionSuggestions,
} from '@docusaurus/plugin-content-docs/client';
import {ThemeClassNames} from '@docusaurus/theme-common';
import {
useDocsPreferredVersion,
useDocsVersion,
} from '@docusaurus/theme-common/internal';
function UnreleasedVersionLabel({siteTitle, versionMetadata}) {
return (
<Translate
id="theme.docs.versions.unreleasedVersionLabel"
description="The label used to tell the user that he's browsing an unreleased doc version"
values={{
siteTitle,
versionLabel: <b>{versionMetadata.label}</b>,
}}>
{
'This is unreleased documentation for {siteTitle}\'s {versionLabel} version.'
}
</Translate>
);
}
function UnmaintainedVersionLabel({siteTitle, versionMetadata}) {
return (
<Translate
id="theme.docs.versions.unmaintainedVersionLabel"
description="The label used to tell the user that he's browsing an unmaintained doc version"
values={{
siteTitle,
versionLabel: <b>{versionMetadata.label}</b>,
}}>
{
'This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.'
}
</Translate>
);
}
const BannerLabelComponents = {
unreleased: UnreleasedVersionLabel,
unmaintained: UnmaintainedVersionLabel,
};
function BannerLabel(props) {
const BannerLabelComponent =
BannerLabelComponents[props.versionMetadata.banner];
return <BannerLabelComponent {...props} />;
}
function LatestVersionSuggestionLabel({versionLabel, to, onClick}) {
return (
<Translate
id="theme.docs.versions.latestVersionSuggestionLabel"
description="The label used to tell the user to check the latest version"
values={{
versionLabel,
latestVersionLink: (
<b>
<Link to={to} onClick={onClick}>
<Translate
id="theme.docs.versions.latestVersionLinkLabel"
description="The label used for the latest version suggestion link label">
this version
</Translate>
</Link>
</b>
),
}}>
{
'For the current stable version, see {latestVersionLink} ({versionLabel}).'
}
</Translate>
);
}
function DocVersionBannerEnabled({className, versionMetadata}) {
const {
siteConfig: {title: siteTitle},
} = useDocusaurusContext();
const {pluginId} = useActivePlugin({failfast: true});
const getVersionMainDoc = (version) =>
version.docs.find((doc) => doc.id === version.mainDocId);
const {savePreferredVersionName} = useDocsPreferredVersion(pluginId);
const {latestDocSuggestion, latestVersionSuggestion} =
useDocVersionSuggestions(pluginId);
// Try to link to same doc in latest version (not always possible), falling
// back to main doc of latest version
const latestVersionSuggestedDoc =
latestDocSuggestion ?? getVersionMainDoc(latestVersionSuggestion);
return (
<div
className={clsx(
className,
ThemeClassNames.docs.docVersionBanner,
'alert alert--warning margin-bottom--md',
)}
role="alert">
<div>
<BannerLabel siteTitle={siteTitle} versionMetadata={versionMetadata} />
</div>
<div className="margin-top--md">
<LatestVersionSuggestionLabel
versionLabel={latestVersionSuggestion.label}
to={latestVersionSuggestedDoc.path}
onClick={() => savePreferredVersionName(latestVersionSuggestion.name)}
/>
</div>
</div>
);
}
function LatestDocVersionBanner({className, versionMetadata}) {
const {
siteConfig: {title: siteTitle},
} = useDocusaurusContext();
const {pluginId} = useActivePlugin({failfast: true});
const getVersionMainDoc = (version) =>
version.docs.find((doc) => doc.id === version.mainDocId);
const {savePreferredVersionName} = useDocsPreferredVersion(pluginId);
const {latestDocSuggestion, latestVersionSuggestion} =
useDocVersionSuggestions(pluginId);
// Try to link to same doc in latest version (not always possible), falling
// back to main doc of latest version
const latestVersionSuggestedDoc =
latestDocSuggestion ?? getVersionMainDoc(latestVersionSuggestion);
const canaryPath = `/docs/0.2.x/${latestVersionSuggestedDoc.path.slice("/docs/".length)}`;
return (
<div
className={clsx(
className,
ThemeClassNames.docs.docVersionBanner,
'alert alert--info margin-bottom--md',
)}
role="alert">
<div>
<Translate
id="theme.docs.versions.unmaintainedVersionLabel"
description="The label used to encourage the user to view the experimental 0.2.x version"
values={{
siteTitle,
versionLabel: <b>{versionMetadata.label}</b>,
}}>
{
'This is a stable version of documentation for {siteTitle}\'s version {versionLabel}.'
}
</Translate>
</div>
<div className="margin-top--md">
<Translate
id="theme.docs.versions.latestVersionSuggestionLabel"
description="The label used to tell the user to check the experimental version"
values={{
versionLabel: <b>{versionMetadata.label}</b>,
latestVersionLink: (
<b>
<Link to={canaryPath} onClick={() => savePreferredVersionName("0.2.x")}>
<Translate
id="theme.docs.versions.latestVersionLinkLabel"
description="The label used for the latest version suggestion link label">
this experimental version
</Translate>
</Link>
</b>
),
}}>
{
'You can also check out {latestVersionLink} for an updated experience.'
}
</Translate>
</div>
</div>
);
}
export default function DocVersionBanner({className}) {
const versionMetadata = useDocsVersion();
if (versionMetadata.banner) {
return (
<DocVersionBannerEnabled
className={className}
versionMetadata={versionMetadata}
/>
);
} else if (versionMetadata.isLast) {
// Uncomment when we are ready to direct people to new build
// return (
// <LatestDocVersionBanner
// className={className}
// versionMetadata={versionMetadata}
// />
// );
return null;
}
return null;
}

View File

@@ -0,0 +1,19 @@
import React from "react";
import { marked } from "marked";
import DOMPurify from "isomorphic-dompurify";
import Admonition from '@theme/Admonition';
export default function PrerequisiteLinks({ content }) {
return (
<Admonition type="info" title="Prerequisites">
<div style={{ marginTop: "8px" }}>
This guide will assume familiarity with the following concepts:
</div>
<div style={{ marginTop: "16px" }}
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(marked.parse(content))
}}
/>
</Admonition>
);
}

View File

@@ -35,6 +35,21 @@ python3 scripts/resolve_local_links.py docs/langserve.md https://github.com/lang
wget -q https://raw.githubusercontent.com/langchain-ai/langgraph/main/README.md -O docs/langgraph.md
python3 scripts/resolve_local_links.py docs/langgraph.md https://github.com/langchain-ai/langgraph/tree/main/
# Duplicate changes to 0.2.x versioned docs
cp docs/integrations/llms/index.mdx versioned_docs/version-0.2.x/integrations/llms/
cp docs/integrations/chat/index.mdx versioned_docs/version-0.2.x/integrations/chat/
mkdir -p versioned_docs/version-0.2.x/templates
cp -r docs/templates/* versioned_docs/version-0.2.x/templates/
wget -q https://raw.githubusercontent.com/langchain-ai/langserve/main/README.md -O versioned_docs/version-0.2.x/langserve.md
python3 scripts/resolve_local_links.py versioned_docs/version-0.2.x/langserve.md https://github.com/langchain-ai/langserve/tree/main/
wget -q https://raw.githubusercontent.com/langchain-ai/langgraph/main/README.md -O versioned_docs/version-0.2.x/langgraph.md
python3 scripts/resolve_local_links.py versioned_docs/version-0.2.x/langgraph.md https://github.com/langchain-ai/langgraph/tree/main/
# render
quarto render docs/
python3 scripts/generate_api_reference_links.py --docs_dir docs
quarto render versioned_docs/version-0.2.x/
python3 scripts/resolve_versioned_links_in_markdown.py versioned_docs/version-0.2.x/ /docs/0.2.x/
python3 scripts/generate_api_reference_links.py --docs_dir docs

View File

@@ -0,0 +1,7 @@
.yarn/
node_modules/
.docusaurus
.cache-loader
docs/api

View File

@@ -0,0 +1,60 @@
[comment: Please, a reference example here "docs/integrations/arxiv.md"]::
[comment: Use this template to create a new .md file in "docs/integrations/"]::
# Title_REPLACE_ME
[comment: Only one Tile/H1 is allowed!]::
>
[comment: Description: After reading this description, a reader should decide if this integration is good enough to try/follow reading OR]::
[comment: go to read the next integration doc. ]::
[comment: Description should include a link to the source for follow reading.]::
## Installation and Setup
[comment: Installation and Setup: All necessary additional package installations and setups for Tokens, etc]::
```bash
pip install package_name_REPLACE_ME
```
[comment: OR this text:]::
There isn't any special setup for it.
[comment: The next H2/## sections with names of the integration modules, like "LLM", "Text Embedding Models", etc]::
[comment: see "Modules" in the "index.html" page]::
[comment: Each H2 section should include a link to an example(s) and a Python code with the import of the integration class]::
[comment: Below are several example sections. Remove all unnecessary sections. Add all necessary sections not provided here.]::
## LLM
See a [usage example](/docs/integrations/llms/INCLUDE_REAL_NAME).
```python
from langchain_community.llms import integration_class_REPLACE_ME
```
## Text Embedding Models
See a [usage example](/docs/integrations/text_embedding/INCLUDE_REAL_NAME).
```python
from langchain_community.embeddings import integration_class_REPLACE_ME
```
## Chat models
See a [usage example](/docs/integrations/chat/INCLUDE_REAL_NAME).
```python
from langchain_community.chat_models import integration_class_REPLACE_ME
```
## Document Loader
See a [usage example](/docs/integrations/document_loaders/INCLUDE_REAL_NAME).
```python
from langchain_community.document_loaders import integration_class_REPLACE_ME
```

View File

@@ -0,0 +1,554 @@
# Dependents
Dependents stats for `langchain-ai/langchain`
[![](https://img.shields.io/static/v1?label=Used%20by&message=41717&color=informational&logo=slickpic)](https://github.com/langchain-ai/langchain/network/dependents)
[![](https://img.shields.io/static/v1?label=Used%20by%20(public)&message=538&color=informational&logo=slickpic)](https://github.com/langchain-ai/langchain/network/dependents)
[![](https://img.shields.io/static/v1?label=Used%20by%20(private)&message=41179&color=informational&logo=slickpic)](https://github.com/langchain-ai/langchain/network/dependents)
[update: `2023-12-08`; only dependent repositories with Stars > 100]
| Repository | Stars |
| :-------- | -----: |
|[AntonOsika/gpt-engineer](https://github.com/AntonOsika/gpt-engineer) | 46514 |
|[imartinez/privateGPT](https://github.com/imartinez/privateGPT) | 44439 |
|[LAION-AI/Open-Assistant](https://github.com/LAION-AI/Open-Assistant) | 35906 |
|[hpcaitech/ColossalAI](https://github.com/hpcaitech/ColossalAI) | 35528 |
|[moymix/TaskMatrix](https://github.com/moymix/TaskMatrix) | 34342 |
|[geekan/MetaGPT](https://github.com/geekan/MetaGPT) | 31126 |
|[streamlit/streamlit](https://github.com/streamlit/streamlit) | 28911 |
|[reworkd/AgentGPT](https://github.com/reworkd/AgentGPT) | 27833 |
|[StanGirard/quivr](https://github.com/StanGirard/quivr) | 26032 |
|[OpenBB-finance/OpenBBTerminal](https://github.com/OpenBB-finance/OpenBBTerminal) | 24946 |
|[run-llama/llama_index](https://github.com/run-llama/llama_index) | 24859 |
|[jmorganca/ollama](https://github.com/jmorganca/ollama) | 20849 |
|[openai/chatgpt-retrieval-plugin](https://github.com/openai/chatgpt-retrieval-plugin) | 20249 |
|[chatchat-space/Langchain-Chatchat](https://github.com/chatchat-space/Langchain-Chatchat) | 19305 |
|[mindsdb/mindsdb](https://github.com/mindsdb/mindsdb) | 19172 |
|[PromtEngineer/localGPT](https://github.com/PromtEngineer/localGPT) | 17528 |
|[cube-js/cube](https://github.com/cube-js/cube) | 16575 |
|[mlflow/mlflow](https://github.com/mlflow/mlflow) | 16000 |
|[mudler/LocalAI](https://github.com/mudler/LocalAI) | 14067 |
|[logspace-ai/langflow](https://github.com/logspace-ai/langflow) | 13679 |
|[GaiZhenbiao/ChuanhuChatGPT](https://github.com/GaiZhenbiao/ChuanhuChatGPT) | 13648 |
|[arc53/DocsGPT](https://github.com/arc53/DocsGPT) | 13423 |
|[openai/evals](https://github.com/openai/evals) | 12649 |
|[airbytehq/airbyte](https://github.com/airbytehq/airbyte) | 12460 |
|[langgenius/dify](https://github.com/langgenius/dify) | 11859 |
|[databrickslabs/dolly](https://github.com/databrickslabs/dolly) | 10672 |
|[AIGC-Audio/AudioGPT](https://github.com/AIGC-Audio/AudioGPT) | 9437 |
|[langchain-ai/langchainjs](https://github.com/langchain-ai/langchainjs) | 9227 |
|[gventuri/pandas-ai](https://github.com/gventuri/pandas-ai) | 9203 |
|[aws/amazon-sagemaker-examples](https://github.com/aws/amazon-sagemaker-examples) | 9079 |
|[h2oai/h2ogpt](https://github.com/h2oai/h2ogpt) | 8945 |
|[PipedreamHQ/pipedream](https://github.com/PipedreamHQ/pipedream) | 7550 |
|[bentoml/OpenLLM](https://github.com/bentoml/OpenLLM) | 6957 |
|[THUDM/ChatGLM3](https://github.com/THUDM/ChatGLM3) | 6801 |
|[microsoft/promptflow](https://github.com/microsoft/promptflow) | 6776 |
|[cpacker/MemGPT](https://github.com/cpacker/MemGPT) | 6642 |
|[joshpxyne/gpt-migrate](https://github.com/joshpxyne/gpt-migrate) | 6482 |
|[zauberzeug/nicegui](https://github.com/zauberzeug/nicegui) | 6037 |
|[embedchain/embedchain](https://github.com/embedchain/embedchain) | 6023 |
|[mage-ai/mage-ai](https://github.com/mage-ai/mage-ai) | 6019 |
|[assafelovic/gpt-researcher](https://github.com/assafelovic/gpt-researcher) | 5936 |
|[sweepai/sweep](https://github.com/sweepai/sweep) | 5855 |
|[wenda-LLM/wenda](https://github.com/wenda-LLM/wenda) | 5766 |
|[zilliztech/GPTCache](https://github.com/zilliztech/GPTCache) | 5710 |
|[pdm-project/pdm](https://github.com/pdm-project/pdm) | 5665 |
|[GreyDGL/PentestGPT](https://github.com/GreyDGL/PentestGPT) | 5568 |
|[gkamradt/langchain-tutorials](https://github.com/gkamradt/langchain-tutorials) | 5507 |
|[Shaunwei/RealChar](https://github.com/Shaunwei/RealChar) | 5501 |
|[facebookresearch/llama-recipes](https://github.com/facebookresearch/llama-recipes) | 5477 |
|[serge-chat/serge](https://github.com/serge-chat/serge) | 5221 |
|[run-llama/rags](https://github.com/run-llama/rags) | 4916 |
|[openchatai/OpenChat](https://github.com/openchatai/OpenChat) | 4870 |
|[danswer-ai/danswer](https://github.com/danswer-ai/danswer) | 4774 |
|[langchain-ai/opengpts](https://github.com/langchain-ai/opengpts) | 4709 |
|[postgresml/postgresml](https://github.com/postgresml/postgresml) | 4639 |
|[MineDojo/Voyager](https://github.com/MineDojo/Voyager) | 4582 |
|[intel-analytics/BigDL](https://github.com/intel-analytics/BigDL) | 4581 |
|[yihong0618/xiaogpt](https://github.com/yihong0618/xiaogpt) | 4359 |
|[RayVentura/ShortGPT](https://github.com/RayVentura/ShortGPT) | 4357 |
|[Azure-Samples/azure-search-openai-demo](https://github.com/Azure-Samples/azure-search-openai-demo) | 4317 |
|[madawei2699/myGPTReader](https://github.com/madawei2699/myGPTReader) | 4289 |
|[apache/nifi](https://github.com/apache/nifi) | 4098 |
|[langchain-ai/chat-langchain](https://github.com/langchain-ai/chat-langchain) | 4091 |
|[aiwaves-cn/agents](https://github.com/aiwaves-cn/agents) | 4073 |
|[krishnaik06/The-Grand-Complete-Data-Science-Materials](https://github.com/krishnaik06/The-Grand-Complete-Data-Science-Materials) | 4065 |
|[khoj-ai/khoj](https://github.com/khoj-ai/khoj) | 4016 |
|[Azure/azure-sdk-for-python](https://github.com/Azure/azure-sdk-for-python) | 3941 |
|[PrefectHQ/marvin](https://github.com/PrefectHQ/marvin) | 3915 |
|[OpenBMB/ToolBench](https://github.com/OpenBMB/ToolBench) | 3799 |
|[marqo-ai/marqo](https://github.com/marqo-ai/marqo) | 3771 |
|[kyegomez/tree-of-thoughts](https://github.com/kyegomez/tree-of-thoughts) | 3688 |
|[Unstructured-IO/unstructured](https://github.com/Unstructured-IO/unstructured) | 3543 |
|[llm-workflow-engine/llm-workflow-engine](https://github.com/llm-workflow-engine/llm-workflow-engine) | 3515 |
|[shroominic/codeinterpreter-api](https://github.com/shroominic/codeinterpreter-api) | 3425 |
|[openchatai/OpenCopilot](https://github.com/openchatai/OpenCopilot) | 3418 |
|[josStorer/RWKV-Runner](https://github.com/josStorer/RWKV-Runner) | 3297 |
|[whitead/paper-qa](https://github.com/whitead/paper-qa) | 3280 |
|[homanp/superagent](https://github.com/homanp/superagent) | 3258 |
|[ParisNeo/lollms-webui](https://github.com/ParisNeo/lollms-webui) | 3199 |
|[OpenBMB/AgentVerse](https://github.com/OpenBMB/AgentVerse) | 3099 |
|[project-baize/baize-chatbot](https://github.com/project-baize/baize-chatbot) | 3090 |
|[OpenGVLab/InternGPT](https://github.com/OpenGVLab/InternGPT) | 2989 |
|[xlang-ai/OpenAgents](https://github.com/xlang-ai/OpenAgents) | 2825 |
|[dataelement/bisheng](https://github.com/dataelement/bisheng) | 2797 |
|[Mintplex-Labs/anything-llm](https://github.com/Mintplex-Labs/anything-llm) | 2784 |
|[OpenBMB/BMTools](https://github.com/OpenBMB/BMTools) | 2734 |
|[run-llama/llama-hub](https://github.com/run-llama/llama-hub) | 2721 |
|[SamurAIGPT/EmbedAI](https://github.com/SamurAIGPT/EmbedAI) | 2647 |
|[NVIDIA/NeMo-Guardrails](https://github.com/NVIDIA/NeMo-Guardrails) | 2637 |
|[X-D-Lab/LangChain-ChatGLM-Webui](https://github.com/X-D-Lab/LangChain-ChatGLM-Webui) | 2532 |
|[GerevAI/gerev](https://github.com/GerevAI/gerev) | 2517 |
|[keephq/keep](https://github.com/keephq/keep) | 2448 |
|[yanqiangmiffy/Chinese-LangChain](https://github.com/yanqiangmiffy/Chinese-LangChain) | 2397 |
|[OpenGVLab/Ask-Anything](https://github.com/OpenGVLab/Ask-Anything) | 2324 |
|[IntelligenzaArtificiale/Free-Auto-GPT](https://github.com/IntelligenzaArtificiale/Free-Auto-GPT) | 2241 |
|[YiVal/YiVal](https://github.com/YiVal/YiVal) | 2232 |
|[jupyterlab/jupyter-ai](https://github.com/jupyterlab/jupyter-ai) | 2189 |
|[Farama-Foundation/PettingZoo](https://github.com/Farama-Foundation/PettingZoo) | 2136 |
|[microsoft/TaskWeaver](https://github.com/microsoft/TaskWeaver) | 2126 |
|[hwchase17/notion-qa](https://github.com/hwchase17/notion-qa) | 2083 |
|[FlagOpen/FlagEmbedding](https://github.com/FlagOpen/FlagEmbedding) | 2053 |
|[paulpierre/RasaGPT](https://github.com/paulpierre/RasaGPT) | 1999 |
|[hegelai/prompttools](https://github.com/hegelai/prompttools) | 1984 |
|[mckinsey/vizro](https://github.com/mckinsey/vizro) | 1951 |
|[vocodedev/vocode-python](https://github.com/vocodedev/vocode-python) | 1868 |
|[dot-agent/openAMS](https://github.com/dot-agent/openAMS) | 1796 |
|[explodinggradients/ragas](https://github.com/explodinggradients/ragas) | 1766 |
|[AI-Citizen/SolidGPT](https://github.com/AI-Citizen/SolidGPT) | 1761 |
|[Kav-K/GPTDiscord](https://github.com/Kav-K/GPTDiscord) | 1696 |
|[run-llama/sec-insights](https://github.com/run-llama/sec-insights) | 1654 |
|[avinashkranjan/Amazing-Python-Scripts](https://github.com/avinashkranjan/Amazing-Python-Scripts) | 1635 |
|[microsoft/WhatTheHack](https://github.com/microsoft/WhatTheHack) | 1629 |
|[noahshinn/reflexion](https://github.com/noahshinn/reflexion) | 1625 |
|[psychic-api/psychic](https://github.com/psychic-api/psychic) | 1618 |
|[Forethought-Technologies/AutoChain](https://github.com/Forethought-Technologies/AutoChain) | 1611 |
|[pinterest/querybook](https://github.com/pinterest/querybook) | 1586 |
|[refuel-ai/autolabel](https://github.com/refuel-ai/autolabel) | 1553 |
|[jina-ai/langchain-serve](https://github.com/jina-ai/langchain-serve) | 1537 |
|[jina-ai/dev-gpt](https://github.com/jina-ai/dev-gpt) | 1522 |
|[agiresearch/OpenAGI](https://github.com/agiresearch/OpenAGI) | 1493 |
|[ttengwang/Caption-Anything](https://github.com/ttengwang/Caption-Anything) | 1484 |
|[greshake/llm-security](https://github.com/greshake/llm-security) | 1483 |
|[promptfoo/promptfoo](https://github.com/promptfoo/promptfoo) | 1480 |
|[milvus-io/bootcamp](https://github.com/milvus-io/bootcamp) | 1477 |
|[richardyc/Chrome-GPT](https://github.com/richardyc/Chrome-GPT) | 1475 |
|[melih-unsal/DemoGPT](https://github.com/melih-unsal/DemoGPT) | 1428 |
|[YORG-AI/Open-Assistant](https://github.com/YORG-AI/Open-Assistant) | 1419 |
|[101dotxyz/GPTeam](https://github.com/101dotxyz/GPTeam) | 1416 |
|[jina-ai/thinkgpt](https://github.com/jina-ai/thinkgpt) | 1408 |
|[mmz-001/knowledge_gpt](https://github.com/mmz-001/knowledge_gpt) | 1398 |
|[intel/intel-extension-for-transformers](https://github.com/intel/intel-extension-for-transformers) | 1387 |
|[Azure/azureml-examples](https://github.com/Azure/azureml-examples) | 1385 |
|[lunasec-io/lunasec](https://github.com/lunasec-io/lunasec) | 1367 |
|[eyurtsev/kor](https://github.com/eyurtsev/kor) | 1355 |
|[xusenlinzy/api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm) | 1325 |
|[griptape-ai/griptape](https://github.com/griptape-ai/griptape) | 1323 |
|[SuperDuperDB/superduperdb](https://github.com/SuperDuperDB/superduperdb) | 1290 |
|[cofactoryai/textbase](https://github.com/cofactoryai/textbase) | 1284 |
|[psychic-api/rag-stack](https://github.com/psychic-api/rag-stack) | 1260 |
|[filip-michalsky/SalesGPT](https://github.com/filip-michalsky/SalesGPT) | 1250 |
|[nod-ai/SHARK](https://github.com/nod-ai/SHARK) | 1237 |
|[pluralsh/plural](https://github.com/pluralsh/plural) | 1234 |
|[cheshire-cat-ai/core](https://github.com/cheshire-cat-ai/core) | 1194 |
|[LC1332/Chat-Haruhi-Suzumiya](https://github.com/LC1332/Chat-Haruhi-Suzumiya) | 1184 |
|[poe-platform/server-bot-quick-start](https://github.com/poe-platform/server-bot-quick-start) | 1182 |
|[microsoft/X-Decoder](https://github.com/microsoft/X-Decoder) | 1180 |
|[juncongmoo/chatllama](https://github.com/juncongmoo/chatllama) | 1171 |
|[visual-openllm/visual-openllm](https://github.com/visual-openllm/visual-openllm) | 1156 |
|[alejandro-ao/ask-multiple-pdfs](https://github.com/alejandro-ao/ask-multiple-pdfs) | 1153 |
|[ThousandBirdsInc/chidori](https://github.com/ThousandBirdsInc/chidori) | 1152 |
|[irgolic/AutoPR](https://github.com/irgolic/AutoPR) | 1137 |
|[SamurAIGPT/Camel-AutoGPT](https://github.com/SamurAIGPT/Camel-AutoGPT) | 1083 |
|[ray-project/llm-applications](https://github.com/ray-project/llm-applications) | 1080 |
|[run-llama/llama-lab](https://github.com/run-llama/llama-lab) | 1072 |
|[jiran214/GPT-vup](https://github.com/jiran214/GPT-vup) | 1041 |
|[MetaGLM/FinGLM](https://github.com/MetaGLM/FinGLM) | 1035 |
|[peterw/Chat-with-Github-Repo](https://github.com/peterw/Chat-with-Github-Repo) | 1020 |
|[Anil-matcha/ChatPDF](https://github.com/Anil-matcha/ChatPDF) | 991 |
|[langchain-ai/langserve](https://github.com/langchain-ai/langserve) | 983 |
|[THUDM/AgentTuning](https://github.com/THUDM/AgentTuning) | 976 |
|[rlancemartin/auto-evaluator](https://github.com/rlancemartin/auto-evaluator) | 975 |
|[codeacme17/examor](https://github.com/codeacme17/examor) | 964 |
|[all-in-aigc/gpts-works](https://github.com/all-in-aigc/gpts-works) | 946 |
|[Ikaros-521/AI-Vtuber](https://github.com/Ikaros-521/AI-Vtuber) | 946 |
|[microsoft/Llama-2-Onnx](https://github.com/microsoft/Llama-2-Onnx) | 898 |
|[cirediatpl/FigmaChain](https://github.com/cirediatpl/FigmaChain) | 895 |
|[ricklamers/shell-ai](https://github.com/ricklamers/shell-ai) | 893 |
|[modelscope/modelscope-agent](https://github.com/modelscope/modelscope-agent) | 893 |
|[seanpixel/Teenage-AGI](https://github.com/seanpixel/Teenage-AGI) | 886 |
|[ajndkr/lanarky](https://github.com/ajndkr/lanarky) | 880 |
|[kennethleungty/Llama-2-Open-Source-LLM-CPU-Inference](https://github.com/kennethleungty/Llama-2-Open-Source-LLM-CPU-Inference) | 872 |
|[corca-ai/EVAL](https://github.com/corca-ai/EVAL) | 846 |
|[hwchase17/chat-your-data](https://github.com/hwchase17/chat-your-data) | 841 |
|[kreneskyp/ix](https://github.com/kreneskyp/ix) | 821 |
|[Link-AGI/AutoAgents](https://github.com/Link-AGI/AutoAgents) | 820 |
|[truera/trulens](https://github.com/truera/trulens) | 794 |
|[Dataherald/dataherald](https://github.com/Dataherald/dataherald) | 788 |
|[sunlabuiuc/PyHealth](https://github.com/sunlabuiuc/PyHealth) | 783 |
|[jondurbin/airoboros](https://github.com/jondurbin/airoboros) | 783 |
|[pyspark-ai/pyspark-ai](https://github.com/pyspark-ai/pyspark-ai) | 782 |
|[confident-ai/deepeval](https://github.com/confident-ai/deepeval) | 780 |
|[billxbf/ReWOO](https://github.com/billxbf/ReWOO) | 777 |
|[langchain-ai/streamlit-agent](https://github.com/langchain-ai/streamlit-agent) | 776 |
|[akshata29/entaoai](https://github.com/akshata29/entaoai) | 771 |
|[LambdaLabsML/examples](https://github.com/LambdaLabsML/examples) | 770 |
|[getmetal/motorhead](https://github.com/getmetal/motorhead) | 768 |
|[Dicklesworthstone/swiss_army_llama](https://github.com/Dicklesworthstone/swiss_army_llama) | 757 |
|[ruoccofabrizio/azure-open-ai-embeddings-qna](https://github.com/ruoccofabrizio/azure-open-ai-embeddings-qna) | 757 |
|[msoedov/langcorn](https://github.com/msoedov/langcorn) | 754 |
|[e-johnstonn/BriefGPT](https://github.com/e-johnstonn/BriefGPT) | 753 |
|[microsoft/sample-app-aoai-chatGPT](https://github.com/microsoft/sample-app-aoai-chatGPT) | 749 |
|[explosion/spacy-llm](https://github.com/explosion/spacy-llm) | 731 |
|[MiuLab/Taiwan-LLM](https://github.com/MiuLab/Taiwan-LLM) | 716 |
|[whyiyhw/chatgpt-wechat](https://github.com/whyiyhw/chatgpt-wechat) | 702 |
|[Azure-Samples/openai](https://github.com/Azure-Samples/openai) | 692 |
|[iusztinpaul/hands-on-llms](https://github.com/iusztinpaul/hands-on-llms) | 687 |
|[safevideo/autollm](https://github.com/safevideo/autollm) | 682 |
|[OpenGenerativeAI/GenossGPT](https://github.com/OpenGenerativeAI/GenossGPT) | 669 |
|[NoDataFound/hackGPT](https://github.com/NoDataFound/hackGPT) | 663 |
|[AILab-CVC/GPT4Tools](https://github.com/AILab-CVC/GPT4Tools) | 662 |
|[langchain-ai/auto-evaluator](https://github.com/langchain-ai/auto-evaluator) | 657 |
|[yvann-ba/Robby-chatbot](https://github.com/yvann-ba/Robby-chatbot) | 639 |
|[alexanderatallah/window.ai](https://github.com/alexanderatallah/window.ai) | 635 |
|[amosjyng/langchain-visualizer](https://github.com/amosjyng/langchain-visualizer) | 630 |
|[microsoft/PodcastCopilot](https://github.com/microsoft/PodcastCopilot) | 621 |
|[aws-samples/aws-genai-llm-chatbot](https://github.com/aws-samples/aws-genai-llm-chatbot) | 616 |
|[NeumTry/NeumAI](https://github.com/NeumTry/NeumAI) | 605 |
|[namuan/dr-doc-search](https://github.com/namuan/dr-doc-search) | 599 |
|[plastic-labs/tutor-gpt](https://github.com/plastic-labs/tutor-gpt) | 595 |
|[marimo-team/marimo](https://github.com/marimo-team/marimo) | 591 |
|[yakami129/VirtualWife](https://github.com/yakami129/VirtualWife) | 586 |
|[xuwenhao/geektime-ai-course](https://github.com/xuwenhao/geektime-ai-course) | 584 |
|[jonra1993/fastapi-alembic-sqlmodel-async](https://github.com/jonra1993/fastapi-alembic-sqlmodel-async) | 573 |
|[dgarnitz/vectorflow](https://github.com/dgarnitz/vectorflow) | 568 |
|[yeagerai/yeagerai-agent](https://github.com/yeagerai/yeagerai-agent) | 564 |
|[daveebbelaar/langchain-experiments](https://github.com/daveebbelaar/langchain-experiments) | 563 |
|[traceloop/openllmetry](https://github.com/traceloop/openllmetry) | 559 |
|[Agenta-AI/agenta](https://github.com/Agenta-AI/agenta) | 546 |
|[michaelthwan/searchGPT](https://github.com/michaelthwan/searchGPT) | 545 |
|[jina-ai/agentchain](https://github.com/jina-ai/agentchain) | 544 |
|[mckaywrigley/repo-chat](https://github.com/mckaywrigley/repo-chat) | 533 |
|[marella/chatdocs](https://github.com/marella/chatdocs) | 532 |
|[opentensor/bittensor](https://github.com/opentensor/bittensor) | 532 |
|[DjangoPeng/openai-quickstart](https://github.com/DjangoPeng/openai-quickstart) | 527 |
|[freddyaboulton/gradio-tools](https://github.com/freddyaboulton/gradio-tools) | 517 |
|[sidhq/Multi-GPT](https://github.com/sidhq/Multi-GPT) | 515 |
|[alejandro-ao/langchain-ask-pdf](https://github.com/alejandro-ao/langchain-ask-pdf) | 514 |
|[sajjadium/ctf-archives](https://github.com/sajjadium/ctf-archives) | 507 |
|[continuum-llms/chatgpt-memory](https://github.com/continuum-llms/chatgpt-memory) | 502 |
|[steamship-core/steamship-langchain](https://github.com/steamship-core/steamship-langchain) | 494 |
|[mpaepper/content-chatbot](https://github.com/mpaepper/content-chatbot) | 493 |
|[langchain-ai/langchain-aiplugin](https://github.com/langchain-ai/langchain-aiplugin) | 492 |
|[logan-markewich/llama_index_starter_pack](https://github.com/logan-markewich/llama_index_starter_pack) | 483 |
|[datawhalechina/llm-universe](https://github.com/datawhalechina/llm-universe) | 475 |
|[leondz/garak](https://github.com/leondz/garak) | 464 |
|[RedisVentures/ArXivChatGuru](https://github.com/RedisVentures/ArXivChatGuru) | 461 |
|[Anil-matcha/Chatbase](https://github.com/Anil-matcha/Chatbase) | 455 |
|[Aiyu-awa/luna-ai](https://github.com/Aiyu-awa/luna-ai) | 450 |
|[DataDog/dd-trace-py](https://github.com/DataDog/dd-trace-py) | 450 |
|[Azure-Samples/miyagi](https://github.com/Azure-Samples/miyagi) | 449 |
|[poe-platform/poe-protocol](https://github.com/poe-platform/poe-protocol) | 447 |
|[onlyphantom/llm-python](https://github.com/onlyphantom/llm-python) | 446 |
|[junruxiong/IncarnaMind](https://github.com/junruxiong/IncarnaMind) | 441 |
|[CarperAI/OpenELM](https://github.com/CarperAI/OpenELM) | 441 |
|[daodao97/chatdoc](https://github.com/daodao97/chatdoc) | 437 |
|[showlab/VLog](https://github.com/showlab/VLog) | 436 |
|[wandb/weave](https://github.com/wandb/weave) | 420 |
|[QwenLM/Qwen-Agent](https://github.com/QwenLM/Qwen-Agent) | 419 |
|[huchenxucs/ChatDB](https://github.com/huchenxucs/ChatDB) | 416 |
|[jerlendds/osintbuddy](https://github.com/jerlendds/osintbuddy) | 411 |
|[monarch-initiative/ontogpt](https://github.com/monarch-initiative/ontogpt) | 408 |
|[mallorbc/Finetune_LLMs](https://github.com/mallorbc/Finetune_LLMs) | 406 |
|[JayZeeDesign/researcher-gpt](https://github.com/JayZeeDesign/researcher-gpt) | 405 |
|[rsaryev/talk-codebase](https://github.com/rsaryev/talk-codebase) | 401 |
|[langchain-ai/langsmith-cookbook](https://github.com/langchain-ai/langsmith-cookbook) | 398 |
|[mtenenholtz/chat-twitter](https://github.com/mtenenholtz/chat-twitter) | 398 |
|[morpheuslord/GPT_Vuln-analyzer](https://github.com/morpheuslord/GPT_Vuln-analyzer) | 391 |
|[MagnivOrg/prompt-layer-library](https://github.com/MagnivOrg/prompt-layer-library) | 387 |
|[JohnSnowLabs/langtest](https://github.com/JohnSnowLabs/langtest) | 384 |
|[mrwadams/attackgen](https://github.com/mrwadams/attackgen) | 381 |
|[codefuse-ai/Test-Agent](https://github.com/codefuse-ai/Test-Agent) | 380 |
|[personoids/personoids-lite](https://github.com/personoids/personoids-lite) | 379 |
|[mosaicml/examples](https://github.com/mosaicml/examples) | 378 |
|[steamship-packages/langchain-production-starter](https://github.com/steamship-packages/langchain-production-starter) | 370 |
|[FlagAI-Open/Aquila2](https://github.com/FlagAI-Open/Aquila2) | 365 |
|[Mintplex-Labs/vector-admin](https://github.com/Mintplex-Labs/vector-admin) | 365 |
|[NimbleBoxAI/ChainFury](https://github.com/NimbleBoxAI/ChainFury) | 357 |
|[BlackHC/llm-strategy](https://github.com/BlackHC/llm-strategy) | 354 |
|[lilacai/lilac](https://github.com/lilacai/lilac) | 352 |
|[preset-io/promptimize](https://github.com/preset-io/promptimize) | 351 |
|[yuanjie-ai/ChatLLM](https://github.com/yuanjie-ai/ChatLLM) | 347 |
|[andylokandy/gpt-4-search](https://github.com/andylokandy/gpt-4-search) | 346 |
|[zhoudaquan/ChatAnything](https://github.com/zhoudaquan/ChatAnything) | 343 |
|[rgomezcasas/dotfiles](https://github.com/rgomezcasas/dotfiles) | 343 |
|[tigerlab-ai/tiger](https://github.com/tigerlab-ai/tiger) | 342 |
|[HumanSignal/label-studio-ml-backend](https://github.com/HumanSignal/label-studio-ml-backend) | 334 |
|[nasa-petal/bidara](https://github.com/nasa-petal/bidara) | 334 |
|[momegas/megabots](https://github.com/momegas/megabots) | 334 |
|[Cheems-Seminar/grounded-segment-any-parts](https://github.com/Cheems-Seminar/grounded-segment-any-parts) | 330 |
|[CambioML/pykoi](https://github.com/CambioML/pykoi) | 326 |
|[Nuggt-dev/Nuggt](https://github.com/Nuggt-dev/Nuggt) | 326 |
|[wandb/edu](https://github.com/wandb/edu) | 326 |
|[Haste171/langchain-chatbot](https://github.com/Haste171/langchain-chatbot) | 324 |
|[sugarforever/LangChain-Tutorials](https://github.com/sugarforever/LangChain-Tutorials) | 322 |
|[liangwq/Chatglm_lora_multi-gpu](https://github.com/liangwq/Chatglm_lora_multi-gpu) | 321 |
|[ur-whitelab/chemcrow-public](https://github.com/ur-whitelab/chemcrow-public) | 320 |
|[itamargol/openai](https://github.com/itamargol/openai) | 318 |
|[gia-guar/JARVIS-ChatGPT](https://github.com/gia-guar/JARVIS-ChatGPT) | 304 |
|[SpecterOps/Nemesis](https://github.com/SpecterOps/Nemesis) | 302 |
|[facebookresearch/personal-timeline](https://github.com/facebookresearch/personal-timeline) | 302 |
|[hnawaz007/pythondataanalysis](https://github.com/hnawaz007/pythondataanalysis) | 301 |
|[Chainlit/cookbook](https://github.com/Chainlit/cookbook) | 300 |
|[airobotlab/KoChatGPT](https://github.com/airobotlab/KoChatGPT) | 300 |
|[GPT-Fathom/GPT-Fathom](https://github.com/GPT-Fathom/GPT-Fathom) | 299 |
|[kaarthik108/snowChat](https://github.com/kaarthik108/snowChat) | 299 |
|[kyegomez/swarms](https://github.com/kyegomez/swarms) | 296 |
|[LangStream/langstream](https://github.com/LangStream/langstream) | 295 |
|[genia-dev/GeniA](https://github.com/genia-dev/GeniA) | 294 |
|[shamspias/customizable-gpt-chatbot](https://github.com/shamspias/customizable-gpt-chatbot) | 291 |
|[TsinghuaDatabaseGroup/DB-GPT](https://github.com/TsinghuaDatabaseGroup/DB-GPT) | 290 |
|[conceptofmind/toolformer](https://github.com/conceptofmind/toolformer) | 283 |
|[sullivan-sean/chat-langchainjs](https://github.com/sullivan-sean/chat-langchainjs) | 283 |
|[AutoPackAI/beebot](https://github.com/AutoPackAI/beebot) | 282 |
|[pablomarin/GPT-Azure-Search-Engine](https://github.com/pablomarin/GPT-Azure-Search-Engine) | 282 |
|[gkamradt/LLMTest_NeedleInAHaystack](https://github.com/gkamradt/LLMTest_NeedleInAHaystack) | 280 |
|[gustavz/DataChad](https://github.com/gustavz/DataChad) | 280 |
|[Safiullah-Rahu/CSV-AI](https://github.com/Safiullah-Rahu/CSV-AI) | 278 |
|[hwchase17/chroma-langchain](https://github.com/hwchase17/chroma-langchain) | 275 |
|[AkshitIreddy/Interactive-LLM-Powered-NPCs](https://github.com/AkshitIreddy/Interactive-LLM-Powered-NPCs) | 268 |
|[ennucore/clippinator](https://github.com/ennucore/clippinator) | 267 |
|[artitw/text2text](https://github.com/artitw/text2text) | 264 |
|[anarchy-ai/LLM-VM](https://github.com/anarchy-ai/LLM-VM) | 263 |
|[wpydcr/LLM-Kit](https://github.com/wpydcr/LLM-Kit) | 262 |
|[streamlit/llm-examples](https://github.com/streamlit/llm-examples) | 262 |
|[paolorechia/learn-langchain](https://github.com/paolorechia/learn-langchain) | 262 |
|[yym68686/ChatGPT-Telegram-Bot](https://github.com/yym68686/ChatGPT-Telegram-Bot) | 261 |
|[PradipNichite/Youtube-Tutorials](https://github.com/PradipNichite/Youtube-Tutorials) | 259 |
|[radi-cho/datasetGPT](https://github.com/radi-cho/datasetGPT) | 259 |
|[ur-whitelab/exmol](https://github.com/ur-whitelab/exmol) | 259 |
|[ml6team/fondant](https://github.com/ml6team/fondant) | 254 |
|[bborn/howdoi.ai](https://github.com/bborn/howdoi.ai) | 254 |
|[rahulnyk/knowledge_graph](https://github.com/rahulnyk/knowledge_graph) | 253 |
|[recalign/RecAlign](https://github.com/recalign/RecAlign) | 248 |
|[hwchase17/langchain-streamlit-template](https://github.com/hwchase17/langchain-streamlit-template) | 248 |
|[fetchai/uAgents](https://github.com/fetchai/uAgents) | 247 |
|[arthur-ai/bench](https://github.com/arthur-ai/bench) | 247 |
|[miaoshouai/miaoshouai-assistant](https://github.com/miaoshouai/miaoshouai-assistant) | 246 |
|[RoboCoachTechnologies/GPT-Synthesizer](https://github.com/RoboCoachTechnologies/GPT-Synthesizer) | 244 |
|[langchain-ai/web-explorer](https://github.com/langchain-ai/web-explorer) | 242 |
|[kaleido-lab/dolphin](https://github.com/kaleido-lab/dolphin) | 242 |
|[PJLab-ADG/DriveLikeAHuman](https://github.com/PJLab-ADG/DriveLikeAHuman) | 241 |
|[stepanogil/autonomous-hr-chatbot](https://github.com/stepanogil/autonomous-hr-chatbot) | 238 |
|[WongSaang/chatgpt-ui-server](https://github.com/WongSaang/chatgpt-ui-server) | 236 |
|[nexus-stc/stc](https://github.com/nexus-stc/stc) | 235 |
|[yeagerai/genworlds](https://github.com/yeagerai/genworlds) | 235 |
|[Gentopia-AI/Gentopia](https://github.com/Gentopia-AI/Gentopia) | 235 |
|[alphasecio/langchain-examples](https://github.com/alphasecio/langchain-examples) | 235 |
|[grumpyp/aixplora](https://github.com/grumpyp/aixplora) | 232 |
|[shaman-ai/agent-actors](https://github.com/shaman-ai/agent-actors) | 232 |
|[darrenburns/elia](https://github.com/darrenburns/elia) | 231 |
|[orgexyz/BlockAGI](https://github.com/orgexyz/BlockAGI) | 231 |
|[handrew/browserpilot](https://github.com/handrew/browserpilot) | 226 |
|[su77ungr/CASALIOY](https://github.com/su77ungr/CASALIOY) | 225 |
|[nicknochnack/LangchainDocuments](https://github.com/nicknochnack/LangchainDocuments) | 225 |
|[dbpunk-labs/octogen](https://github.com/dbpunk-labs/octogen) | 224 |
|[langchain-ai/weblangchain](https://github.com/langchain-ai/weblangchain) | 222 |
|[CL-lau/SQL-GPT](https://github.com/CL-lau/SQL-GPT) | 222 |
|[alvarosevilla95/autolang](https://github.com/alvarosevilla95/autolang) | 221 |
|[showlab/UniVTG](https://github.com/showlab/UniVTG) | 220 |
|[edreisMD/plugnplai](https://github.com/edreisMD/plugnplai) | 219 |
|[hardbyte/qabot](https://github.com/hardbyte/qabot) | 216 |
|[microsoft/azure-openai-in-a-day-workshop](https://github.com/microsoft/azure-openai-in-a-day-workshop) | 215 |
|[Azure-Samples/chat-with-your-data-solution-accelerator](https://github.com/Azure-Samples/chat-with-your-data-solution-accelerator) | 214 |
|[amadad/agentcy](https://github.com/amadad/agentcy) | 213 |
|[snexus/llm-search](https://github.com/snexus/llm-search) | 212 |
|[afaqueumer/DocQA](https://github.com/afaqueumer/DocQA) | 206 |
|[plchld/InsightFlow](https://github.com/plchld/InsightFlow) | 205 |
|[yasyf/compress-gpt](https://github.com/yasyf/compress-gpt) | 205 |
|[benthecoder/ClassGPT](https://github.com/benthecoder/ClassGPT) | 205 |
|[voxel51/voxelgpt](https://github.com/voxel51/voxelgpt) | 204 |
|[jbrukh/gpt-jargon](https://github.com/jbrukh/gpt-jargon) | 204 |
|[emarco177/ice_breaker](https://github.com/emarco177/ice_breaker) | 204 |
|[tencentmusic/supersonic](https://github.com/tencentmusic/supersonic) | 202 |
|[Azure-Samples/azure-search-power-skills](https://github.com/Azure-Samples/azure-search-power-skills) | 202 |
|[blob42/Instrukt](https://github.com/blob42/Instrukt) | 201 |
|[langchain-ai/langsmith-sdk](https://github.com/langchain-ai/langsmith-sdk) | 200 |
|[SamPink/dev-gpt](https://github.com/SamPink/dev-gpt) | 200 |
|[ju-bezdek/langchain-decorators](https://github.com/ju-bezdek/langchain-decorators) | 198 |
|[KMnO4-zx/huanhuan-chat](https://github.com/KMnO4-zx/huanhuan-chat) | 196 |
|[Azure-Samples/jp-azureopenai-samples](https://github.com/Azure-Samples/jp-azureopenai-samples) | 192 |
|[hongbo-miao/hongbomiao.com](https://github.com/hongbo-miao/hongbomiao.com) | 190 |
|[CakeCrusher/openplugin](https://github.com/CakeCrusher/openplugin) | 190 |
|[PaddlePaddle/ERNIE-Bot-SDK](https://github.com/PaddlePaddle/ERNIE-Bot-SDK) | 189 |
|[retr0reg/Ret2GPT](https://github.com/retr0reg/Ret2GPT) | 189 |
|[AmineDiro/cria](https://github.com/AmineDiro/cria) | 187 |
|[lancedb/vectordb-recipes](https://github.com/lancedb/vectordb-recipes) | 186 |
|[vaibkumr/prompt-optimizer](https://github.com/vaibkumr/prompt-optimizer) | 185 |
|[aws-ia/ecs-blueprints](https://github.com/aws-ia/ecs-blueprints) | 184 |
|[ethanyanjiali/minChatGPT](https://github.com/ethanyanjiali/minChatGPT) | 183 |
|[MuhammadMoinFaisal/LargeLanguageModelsProjects](https://github.com/MuhammadMoinFaisal/LargeLanguageModelsProjects) | 182 |
|[shauryr/S2QA](https://github.com/shauryr/S2QA) | 181 |
|[summarizepaper/summarizepaper](https://github.com/summarizepaper/summarizepaper) | 180 |
|[NomaDamas/RAGchain](https://github.com/NomaDamas/RAGchain) | 179 |
|[pnkvalavala/repochat](https://github.com/pnkvalavala/repochat) | 179 |
|[ibiscp/LLM-IMDB](https://github.com/ibiscp/LLM-IMDB) | 177 |
|[fengyuli-dev/multimedia-gpt](https://github.com/fengyuli-dev/multimedia-gpt) | 177 |
|[langchain-ai/text-split-explorer](https://github.com/langchain-ai/text-split-explorer) | 175 |
|[iMagist486/ElasticSearch-Langchain-Chatglm2](https://github.com/iMagist486/ElasticSearch-Langchain-Chatglm2) | 175 |
|[limaoyi1/Auto-PPT](https://github.com/limaoyi1/Auto-PPT) | 175 |
|[Open-Swarm-Net/GPT-Swarm](https://github.com/Open-Swarm-Net/GPT-Swarm) | 175 |
|[morpheuslord/HackBot](https://github.com/morpheuslord/HackBot) | 174 |
|[v7labs/benchllm](https://github.com/v7labs/benchllm) | 174 |
|[Coding-Crashkurse/Langchain-Full-Course](https://github.com/Coding-Crashkurse/Langchain-Full-Course) | 174 |
|[dongyh20/Octopus](https://github.com/dongyh20/Octopus) | 173 |
|[kimtth/azure-openai-llm-vector-langchain](https://github.com/kimtth/azure-openai-llm-vector-langchain) | 173 |
|[mayooear/private-chatbot-mpt30b-langchain](https://github.com/mayooear/private-chatbot-mpt30b-langchain) | 173 |
|[zilliztech/akcio](https://github.com/zilliztech/akcio) | 172 |
|[jmpaz/promptlib](https://github.com/jmpaz/promptlib) | 172 |
|[ccurme/yolopandas](https://github.com/ccurme/yolopandas) | 172 |
|[joaomdmoura/CrewAI](https://github.com/joaomdmoura/CrewAI) | 170 |
|[katanaml/llm-mistral-invoice-cpu](https://github.com/katanaml/llm-mistral-invoice-cpu) | 170 |
|[chakkaradeep/pyCodeAGI](https://github.com/chakkaradeep/pyCodeAGI) | 170 |
|[mudler/LocalAGI](https://github.com/mudler/LocalAGI) | 167 |
|[dssjon/biblos](https://github.com/dssjon/biblos) | 165 |
|[kjappelbaum/gptchem](https://github.com/kjappelbaum/gptchem) | 165 |
|[xxw1995/chatglm3-finetune](https://github.com/xxw1995/chatglm3-finetune) | 164 |
|[ArjanCodes/examples](https://github.com/ArjanCodes/examples) | 163 |
|[AIAnytime/Llama2-Medical-Chatbot](https://github.com/AIAnytime/Llama2-Medical-Chatbot) | 163 |
|[RCGAI/SimplyRetrieve](https://github.com/RCGAI/SimplyRetrieve) | 162 |
|[langchain-ai/langchain-teacher](https://github.com/langchain-ai/langchain-teacher) | 162 |
|[menloparklab/falcon-langchain](https://github.com/menloparklab/falcon-langchain) | 162 |
|[flurb18/AgentOoba](https://github.com/flurb18/AgentOoba) | 162 |
|[homanp/vercel-langchain](https://github.com/homanp/vercel-langchain) | 161 |
|[jiran214/langup-ai](https://github.com/jiran214/langup-ai) | 160 |
|[JorisdeJong123/7-Days-of-LangChain](https://github.com/JorisdeJong123/7-Days-of-LangChain) | 160 |
|[GoogleCloudPlatform/data-analytics-golden-demo](https://github.com/GoogleCloudPlatform/data-analytics-golden-demo) | 159 |
|[positive666/Prompt-Can-Anything](https://github.com/positive666/Prompt-Can-Anything) | 159 |
|[luisroque/large_laguage_models](https://github.com/luisroque/large_laguage_models) | 159 |
|[mlops-for-all/mlops-for-all.github.io](https://github.com/mlops-for-all/mlops-for-all.github.io) | 158 |
|[wandb/wandbot](https://github.com/wandb/wandbot) | 158 |
|[elastic/elasticsearch-labs](https://github.com/elastic/elasticsearch-labs) | 157 |
|[shroominic/funcchain](https://github.com/shroominic/funcchain) | 157 |
|[deeppavlov/dream](https://github.com/deeppavlov/dream) | 156 |
|[mluogh/eastworld](https://github.com/mluogh/eastworld) | 154 |
|[georgesung/llm_qlora](https://github.com/georgesung/llm_qlora) | 154 |
|[RUC-GSAI/YuLan-Rec](https://github.com/RUC-GSAI/YuLan-Rec) | 153 |
|[KylinC/ChatFinance](https://github.com/KylinC/ChatFinance) | 152 |
|[Dicklesworthstone/llama2_aided_tesseract](https://github.com/Dicklesworthstone/llama2_aided_tesseract) | 152 |
|[c0sogi/LLMChat](https://github.com/c0sogi/LLMChat) | 152 |
|[eunomia-bpf/GPTtrace](https://github.com/eunomia-bpf/GPTtrace) | 152 |
|[ErikBjare/gptme](https://github.com/ErikBjare/gptme) | 152 |
|[Klingefjord/chatgpt-telegram](https://github.com/Klingefjord/chatgpt-telegram) | 152 |
|[RoboCoachTechnologies/ROScribe](https://github.com/RoboCoachTechnologies/ROScribe) | 151 |
|[Aggregate-Intellect/sherpa](https://github.com/Aggregate-Intellect/sherpa) | 151 |
|[3Alan/DocsMind](https://github.com/3Alan/DocsMind) | 151 |
|[tangqiaoyu/ToolAlpaca](https://github.com/tangqiaoyu/ToolAlpaca) | 150 |
|[kulltc/chatgpt-sql](https://github.com/kulltc/chatgpt-sql) | 150 |
|[mallahyari/drqa](https://github.com/mallahyari/drqa) | 150 |
|[MedalCollector/Orator](https://github.com/MedalCollector/Orator) | 149 |
|[Teahouse-Studios/akari-bot](https://github.com/Teahouse-Studios/akari-bot) | 149 |
|[realminchoi/babyagi-ui](https://github.com/realminchoi/babyagi-ui) | 148 |
|[ssheng/BentoChain](https://github.com/ssheng/BentoChain) | 148 |
|[solana-labs/chatgpt-plugin](https://github.com/solana-labs/chatgpt-plugin) | 147 |
|[aurelio-labs/arxiv-bot](https://github.com/aurelio-labs/arxiv-bot) | 147 |
|[Jaseci-Labs/jaseci](https://github.com/Jaseci-Labs/jaseci) | 146 |
|[menloparklab/langchain-cohere-qdrant-doc-retrieval](https://github.com/menloparklab/langchain-cohere-qdrant-doc-retrieval) | 146 |
|[trancethehuman/entities-extraction-web-scraper](https://github.com/trancethehuman/entities-extraction-web-scraper) | 144 |
|[peterw/StoryStorm](https://github.com/peterw/StoryStorm) | 144 |
|[grumpyp/chroma-langchain-tutorial](https://github.com/grumpyp/chroma-langchain-tutorial) | 144 |
|[gh18l/CrawlGPT](https://github.com/gh18l/CrawlGPT) | 142 |
|[langchain-ai/langchain-aws-template](https://github.com/langchain-ai/langchain-aws-template) | 142 |
|[yasyf/summ](https://github.com/yasyf/summ) | 141 |
|[petehunt/langchain-github-bot](https://github.com/petehunt/langchain-github-bot) | 141 |
|[hirokidaichi/wanna](https://github.com/hirokidaichi/wanna) | 140 |
|[jina-ai/fastapi-serve](https://github.com/jina-ai/fastapi-serve) | 139 |
|[zenml-io/zenml-projects](https://github.com/zenml-io/zenml-projects) | 139 |
|[jlonge4/local_llama](https://github.com/jlonge4/local_llama) | 139 |
|[smyja/blackmaria](https://github.com/smyja/blackmaria) | 138 |
|[ChuloAI/BrainChulo](https://github.com/ChuloAI/BrainChulo) | 137 |
|[log1stics/voice-generator-webui](https://github.com/log1stics/voice-generator-webui) | 137 |
|[davila7/file-gpt](https://github.com/davila7/file-gpt) | 137 |
|[dcaribou/transfermarkt-datasets](https://github.com/dcaribou/transfermarkt-datasets) | 136 |
|[ciare-robotics/world-creator](https://github.com/ciare-robotics/world-creator) | 135 |
|[Undertone0809/promptulate](https://github.com/Undertone0809/promptulate) | 134 |
|[fixie-ai/fixie-examples](https://github.com/fixie-ai/fixie-examples) | 134 |
|[run-llama/ai-engineer-workshop](https://github.com/run-llama/ai-engineer-workshop) | 133 |
|[definitive-io/code-indexer-loop](https://github.com/definitive-io/code-indexer-loop) | 131 |
|[mortium91/langchain-assistant](https://github.com/mortium91/langchain-assistant) | 131 |
|[baidubce/bce-qianfan-sdk](https://github.com/baidubce/bce-qianfan-sdk) | 130 |
|[Ngonie-x/langchain_csv](https://github.com/Ngonie-x/langchain_csv) | 130 |
|[IvanIsCoding/ResuLLMe](https://github.com/IvanIsCoding/ResuLLMe) | 130 |
|[AnchoringAI/anchoring-ai](https://github.com/AnchoringAI/anchoring-ai) | 129 |
|[Azure/business-process-automation](https://github.com/Azure/business-process-automation) | 128 |
|[athina-ai/athina-sdk](https://github.com/athina-ai/athina-sdk) | 126 |
|[thunlp/ChatEval](https://github.com/thunlp/ChatEval) | 126 |
|[prof-frink-lab/slangchain](https://github.com/prof-frink-lab/slangchain) | 126 |
|[vietanhdev/pautobot](https://github.com/vietanhdev/pautobot) | 125 |
|[awslabs/generative-ai-cdk-constructs](https://github.com/awslabs/generative-ai-cdk-constructs) | 124 |
|[sdaaron/QueryGPT](https://github.com/sdaaron/QueryGPT) | 124 |
|[rabbitmetrics/langchain-13-min](https://github.com/rabbitmetrics/langchain-13-min) | 124 |
|[AutoLLM/AutoAgents](https://github.com/AutoLLM/AutoAgents) | 122 |
|[nicknochnack/Nopenai](https://github.com/nicknochnack/Nopenai) | 122 |
|[wombyz/HormoziGPT](https://github.com/wombyz/HormoziGPT) | 122 |
|[dotvignesh/PDFChat](https://github.com/dotvignesh/PDFChat) | 122 |
|[topoteretes/PromethAI-Backend](https://github.com/topoteretes/PromethAI-Backend) | 121 |
|[nftblackmagic/flask-langchain](https://github.com/nftblackmagic/flask-langchain) | 121 |
|[vishwasg217/finsight](https://github.com/vishwasg217/finsight) | 120 |
|[snap-stanford/MLAgentBench](https://github.com/snap-stanford/MLAgentBench) | 120 |
|[Azure/app-service-linux-docs](https://github.com/Azure/app-service-linux-docs) | 120 |
|[nyanp/chat2plot](https://github.com/nyanp/chat2plot) | 120 |
|[ant4g0nist/polar](https://github.com/ant4g0nist/polar) | 119 |
|[aws-samples/cdk-eks-blueprints-patterns](https://github.com/aws-samples/cdk-eks-blueprints-patterns) | 119 |
|[aws-samples/amazon-kendra-langchain-extensions](https://github.com/aws-samples/amazon-kendra-langchain-extensions) | 119 |
|[Xueheng-Li/SynologyChatbotGPT](https://github.com/Xueheng-Li/SynologyChatbotGPT) | 119 |
|[CodeAlchemyAI/ViLT-GPT](https://github.com/CodeAlchemyAI/ViLT-GPT) | 117 |
|[Lin-jun-xiang/docGPT-langchain](https://github.com/Lin-jun-xiang/docGPT-langchain) | 117 |
|[ademakdogan/ChatSQL](https://github.com/ademakdogan/ChatSQL) | 116 |
|[aniketmaurya/llm-inference](https://github.com/aniketmaurya/llm-inference) | 115 |
|[xuwenhao/mactalk-ai-course](https://github.com/xuwenhao/mactalk-ai-course) | 115 |
|[cmooredev/RepoReader](https://github.com/cmooredev/RepoReader) | 115 |
|[abi/autocommit](https://github.com/abi/autocommit) | 115 |
|[MIDORIBIN/langchain-gpt4free](https://github.com/MIDORIBIN/langchain-gpt4free) | 114 |
|[finaldie/auto-news](https://github.com/finaldie/auto-news) | 114 |
|[Anil-matcha/Youtube-to-chatbot](https://github.com/Anil-matcha/Youtube-to-chatbot) | 114 |
|[avrabyt/MemoryBot](https://github.com/avrabyt/MemoryBot) | 114 |
|[Capsize-Games/airunner](https://github.com/Capsize-Games/airunner) | 113 |
|[atisharma/llama_farm](https://github.com/atisharma/llama_farm) | 113 |
|[mbchang/data-driven-characters](https://github.com/mbchang/data-driven-characters) | 112 |
|[fiddler-labs/fiddler-auditor](https://github.com/fiddler-labs/fiddler-auditor) | 112 |
|[dirkjbreeuwer/gpt-automated-web-scraper](https://github.com/dirkjbreeuwer/gpt-automated-web-scraper) | 111 |
|[Appointat/Chat-with-Document-s-using-ChatGPT-API-and-Text-Embedding](https://github.com/Appointat/Chat-with-Document-s-using-ChatGPT-API-and-Text-Embedding) | 111 |
|[hwchase17/langchain-gradio-template](https://github.com/hwchase17/langchain-gradio-template) | 111 |
|[artas728/spelltest](https://github.com/artas728/spelltest) | 110 |
|[NVIDIA/GenerativeAIExamples](https://github.com/NVIDIA/GenerativeAIExamples) | 109 |
|[Azure/aistudio-copilot-sample](https://github.com/Azure/aistudio-copilot-sample) | 108 |
|[codefuse-ai/codefuse-chatbot](https://github.com/codefuse-ai/codefuse-chatbot) | 108 |
|[apirrone/Memento](https://github.com/apirrone/Memento) | 108 |
|[e-johnstonn/GPT-Doc-Summarizer](https://github.com/e-johnstonn/GPT-Doc-Summarizer) | 108 |
|[salesforce/BOLAA](https://github.com/salesforce/BOLAA) | 107 |
|[Erol444/gpt4-openai-api](https://github.com/Erol444/gpt4-openai-api) | 106 |
|[linjungz/chat-with-your-doc](https://github.com/linjungz/chat-with-your-doc) | 106 |
|[crosleythomas/MirrorGPT](https://github.com/crosleythomas/MirrorGPT) | 106 |
|[panaverse/learn-generative-ai](https://github.com/panaverse/learn-generative-ai) | 105 |
|[Azure/azure-sdk-tools](https://github.com/Azure/azure-sdk-tools) | 105 |
|[malywut/gpt_examples](https://github.com/malywut/gpt_examples) | 105 |
|[ritun16/chain-of-verification](https://github.com/ritun16/chain-of-verification) | 104 |
|[langchain-ai/langchain-benchmarks](https://github.com/langchain-ai/langchain-benchmarks) | 104 |
|[lightninglabs/LangChainBitcoin](https://github.com/lightninglabs/LangChainBitcoin) | 104 |
|[flepied/second-brain-agent](https://github.com/flepied/second-brain-agent) | 103 |
|[llmapp/openai.mini](https://github.com/llmapp/openai.mini) | 102 |
|[gimlet-ai/tddGPT](https://github.com/gimlet-ai/tddGPT) | 102 |
|[jlonge4/gpt_chatwithPDF](https://github.com/jlonge4/gpt_chatwithPDF) | 102 |
|[agentification/RAFA_code](https://github.com/agentification/RAFA_code) | 101 |
|[pacman100/DHS-LLM-Workshop](https://github.com/pacman100/DHS-LLM-Workshop) | 101 |
|[aws-samples/private-llm-qa-bot](https://github.com/aws-samples/private-llm-qa-bot) | 101 |
_Generated by [github-dependents-info](https://github.com/nvuillam/github-dependents-info)_
`github-dependents-info --repo "langchain-ai/langchain" --markdownfile dependents.md --minstars 100 --sort stars`

View File

@@ -0,0 +1,55 @@
# Tutorials
## Books and Handbooks
- [Generative AI with LangChain](https://www.amazon.com/Generative-AI-LangChain-language-ChatGPT/dp/1835083463/ref=sr_1_1?crid=1GMOMH0G7GLR&keywords=generative+ai+with+langchain&qid=1703247181&sprefix=%2Caps%2C298&sr=8-1) by [Ben Auffrath](https://www.amazon.com/stores/Ben-Auffarth/author/B08JQKSZ7D?ref=ap_rdr&store_ref=ap_rdr&isDramIntegrated=true&shoppingPortalEnabled=true), ©️ 2023 Packt Publishing
- [LangChain AI Handbook](https://www.pinecone.io/learn/langchain/) By **James Briggs** and **Francisco Ingham**
- [LangChain Cheatsheet](https://pub.towardsai.net/langchain-cheatsheet-all-secrets-on-a-single-page-8be26b721cde) by **Ivan Reznikov**
## Tutorials
### [LangChain v 0.1 by LangChain.ai](https://www.youtube.com/playlist?list=PLfaIDFEXuae0gBSJ9T0w7cu7iJZbH3T31)
### [Build with Langchain - Advanced by LangChain.ai](https://www.youtube.com/playlist?list=PLfaIDFEXuae06tclDATrMYY0idsTdLg9v)
### [LangGraph by LangChain.ai](https://www.youtube.com/playlist?list=PLfaIDFEXuae16n2TWUkKq5PgJ0w6Pkwtg)
### [by Greg Kamradt](https://www.youtube.com/playlist?list=PLqZXAkvF1bPNQER9mLmDbntNfSpzdDIU5)
### [by Sam Witteveen](https://www.youtube.com/playlist?list=PL8motc6AQftk1Bs42EW45kwYbyJ4jOdiZ)
### [by James Briggs](https://www.youtube.com/playlist?list=PLIUOU7oqGTLieV9uTIFMm6_4PXg-hlN6F)
### [by Prompt Engineering](https://www.youtube.com/playlist?list=PLVEEucA9MYhOu89CX8H3MBZqayTbcCTMr)
### [by Mayo Oshin](https://www.youtube.com/@chatwithdata/search?query=langchain)
### [by 1 little Coder](https://www.youtube.com/playlist?list=PLpdmBGJ6ELUK-v0MK-t4wZmVEbxM5xk6L)
## Courses
### Featured courses on Deeplearning.AI
- [LangChain for LLM Application Development](https://www.deeplearning.ai/short-courses/langchain-for-llm-application-development/)
- [LangChain Chat with Your Data](https://www.deeplearning.ai/short-courses/langchain-chat-with-your-data/)
- [Functions, Tools and Agents with LangChain](https://www.deeplearning.ai/short-courses/functions-tools-agents-langchain/)
- [Build LLM Apps with LangChain.js](https://www.deeplearning.ai/short-courses/build-llm-apps-with-langchain-js/)
### Online courses
- [Udemy](https://www.udemy.com/courses/search/?q=langchain)
- [Pluralsight](https://www.pluralsight.com/search?q=langchain)
- [Coursera](https://www.coursera.org/search?query=langchain)
- [Maven](https://maven.com/courses?query=langchain)
- [Udacity](https://www.udacity.com/catalog/all/any-price/any-school/any-skill/any-difficulty/any-duration/any-type/relevance/page-1?searchValue=langchain)
- [LinkedIn Learning](https://www.linkedin.com/search/results/learning/?keywords=langchain)
- [edX](https://www.edx.org/search?q=langchain)
- [freeCodeCamp](https://www.youtube.com/@freecodecamp/search?query=langchain)
## Short Tutorials
- [by Nicholas Renotte](https://youtu.be/MlK6SIjcjE8)
- [by Patrick Loeber](https://youtu.be/LbT1yp6quS8)
- [by Rabbitmetrics](https://youtu.be/aywZrzNaKjs)
- [by Ivan Reznikov](https://medium.com/@ivanreznikov/langchain-101-course-updated-668f7b41d6cb)
## [Documentation: Use cases](/docs/use_cases)
---------------------

View File

@@ -0,0 +1,137 @@
# YouTube videos
⛓ icon marks a new addition [last update 2023-09-21]
### [Official LangChain YouTube channel](https://www.youtube.com/@LangChain)
### Introduction to LangChain with Harrison Chase, creator of LangChain
- [Building the Future with LLMs, `LangChain`, & `Pinecone`](https://youtu.be/nMniwlGyX-c) by [Pinecone](https://www.youtube.com/@pinecone-io)
- [LangChain and Weaviate with Harrison Chase and Bob van Luijt - Weaviate Podcast #36](https://youtu.be/lhby7Ql7hbk) by [Weaviate • Vector Database](https://www.youtube.com/@Weaviate)
- [LangChain Demo + Q&A with Harrison Chase](https://youtu.be/zaYTXQFR0_s?t=788) by [Full Stack Deep Learning](https://www.youtube.com/@The_Full_Stack)
- [LangChain Agents: Build Personal Assistants For Your Data (Q&A with Harrison Chase and Mayo Oshin)](https://youtu.be/gVkF8cwfBLI) by [Chat with data](https://www.youtube.com/@chatwithdata)
## Videos (sorted by views)
- [Using `ChatGPT` with YOUR OWN Data. This is magical. (LangChain OpenAI API)](https://youtu.be/9AXP7tCI9PI) by [TechLead](https://www.youtube.com/@TechLead)
- [First look - `ChatGPT` + `WolframAlpha` (`GPT-3.5` and Wolfram|Alpha via LangChain by James Weaver)](https://youtu.be/wYGbY811oMo) by [Dr Alan D. Thompson](https://www.youtube.com/@DrAlanDThompson)
- [LangChain explained - The hottest new Python framework](https://youtu.be/RoR4XJw8wIc) by [AssemblyAI](https://www.youtube.com/@AssemblyAI)
- [Chatbot with INFINITE MEMORY using `OpenAI` & `Pinecone` - `GPT-3`, `Embeddings`, `ADA`, `Vector DB`, `Semantic`](https://youtu.be/2xNzB7xq8nk) by [David Shapiro ~ AI](https://www.youtube.com/@DaveShap)
- [LangChain for LLMs is... basically just an Ansible playbook](https://youtu.be/X51N9C-OhlE) by [David Shapiro ~ AI](https://www.youtube.com/@DaveShap)
- [Build your own LLM Apps with LangChain & `GPT-Index`](https://youtu.be/-75p09zFUJY) by [1littlecoder](https://www.youtube.com/@1littlecoder)
- [`BabyAGI` - New System of Autonomous AI Agents with LangChain](https://youtu.be/lg3kJvf1kXo) by [1littlecoder](https://www.youtube.com/@1littlecoder)
- [Run `BabyAGI` with Langchain Agents (with Python Code)](https://youtu.be/WosPGHPObx8) by [1littlecoder](https://www.youtube.com/@1littlecoder)
- [How to Use Langchain With `Zapier` | Write and Send Email with GPT-3 | OpenAI API Tutorial](https://youtu.be/p9v2-xEa9A0) by [StarMorph AI](https://www.youtube.com/@starmorph)
- [Use Your Locally Stored Files To Get Response From GPT - `OpenAI` | Langchain | Python](https://youtu.be/NC1Ni9KS-rk) by [Shweta Lodha](https://www.youtube.com/@shweta-lodha)
- [`Langchain JS` | How to Use GPT-3, GPT-4 to Reference your own Data | `OpenAI Embeddings` Intro](https://youtu.be/veV2I-NEjaM) by [StarMorph AI](https://www.youtube.com/@starmorph)
- [The easiest way to work with large language models | Learn LangChain in 10min](https://youtu.be/kmbS6FDQh7c) by [Sophia Yang](https://www.youtube.com/@SophiaYangDS)
- [4 Autonomous AI Agents: “Westworld” simulation `BabyAGI`, `AutoGPT`, `Camel`, `LangChain`](https://youtu.be/yWbnH6inT_U) by [Sophia Yang](https://www.youtube.com/@SophiaYangDS)
- [AI CAN SEARCH THE INTERNET? Langchain Agents + OpenAI ChatGPT](https://youtu.be/J-GL0htqda8) by [tylerwhatsgood](https://www.youtube.com/@tylerwhatsgood)
- [Query Your Data with GPT-4 | Embeddings, Vector Databases | Langchain JS Knowledgebase](https://youtu.be/jRnUPUTkZmU) by [StarMorph AI](https://www.youtube.com/@starmorph)
- [`Weaviate` + LangChain for LLM apps presented by Erika Cardenas](https://youtu.be/7AGj4Td5Lgw) by [`Weaviate` • Vector Database](https://www.youtube.com/@Weaviate)
- [Langchain Overview — How to Use Langchain & `ChatGPT`](https://youtu.be/oYVYIq0lOtI) by [Python In Office](https://www.youtube.com/@pythoninoffice6568)
- [Langchain Overview - How to Use Langchain & `ChatGPT`](https://youtu.be/oYVYIq0lOtI) by [Python In Office](https://www.youtube.com/@pythoninoffice6568)
- [LangChain Tutorials](https://www.youtube.com/watch?v=FuqdVNB_8c0&list=PL9V0lbeJ69brU-ojMpU1Y7Ic58Tap0Cw6) by [Edrick](https://www.youtube.com/@edrickdch):
- [LangChain, Chroma DB, OpenAI Beginner Guide | ChatGPT with your PDF](https://youtu.be/FuqdVNB_8c0)
- [LangChain 101: The Complete Beginner's Guide](https://youtu.be/P3MAbZ2eMUI)
- [Custom langchain Agent & Tools with memory. Turn any `Python function` into langchain tool with Gpt 3](https://youtu.be/NIG8lXk0ULg) by [echohive](https://www.youtube.com/@echohive)
- [Building AI LLM Apps with LangChain (and more?) - LIVE STREAM](https://www.youtube.com/live/M-2Cj_2fzWI?feature=share) by [Nicholas Renotte](https://www.youtube.com/@NicholasRenotte)
- [`ChatGPT` with any `YouTube` video using langchain and `chromadb`](https://youtu.be/TQZfB2bzVwU) by [echohive](https://www.youtube.com/@echohive)
- [How to Talk to a `PDF` using LangChain and `ChatGPT`](https://youtu.be/v2i1YDtrIwk) by [Automata Learning Lab](https://www.youtube.com/@automatalearninglab)
- [Langchain Document Loaders Part 1: Unstructured Files](https://youtu.be/O5C0wfsen98) by [Merk](https://www.youtube.com/@heymichaeldaigler)
- [LangChain - Prompt Templates (what all the best prompt engineers use)](https://youtu.be/1aRu8b0XNOQ) by [Nick Daigler](https://www.youtube.com/@nickdaigler)
- [LangChain. Crear aplicaciones Python impulsadas por GPT](https://youtu.be/DkW_rDndts8) by [Jesús Conde](https://www.youtube.com/@0utKast)
- [Easiest Way to Use GPT In Your Products | LangChain Basics Tutorial](https://youtu.be/fLy0VenZyGc) by [Rachel Woods](https://www.youtube.com/@therachelwoods)
- [`BabyAGI` + `GPT-4` Langchain Agent with Internet Access](https://youtu.be/wx1z_hs5P6E) by [tylerwhatsgood](https://www.youtube.com/@tylerwhatsgood)
- [Learning LLM Agents. How does it actually work? LangChain, AutoGPT & OpenAI](https://youtu.be/mb_YAABSplk) by [Arnoldas Kemeklis](https://www.youtube.com/@processusAI)
- [Get Started with LangChain in `Node.js`](https://youtu.be/Wxx1KUWJFv4) by [Developers Digest](https://www.youtube.com/@DevelopersDigest)
- [LangChain + `OpenAI` tutorial: Building a Q&A system w/ own text data](https://youtu.be/DYOU_Z0hAwo) by [Samuel Chan](https://www.youtube.com/@SamuelChan)
- [Langchain + `Zapier` Agent](https://youtu.be/yribLAb-pxA) by [Merk](https://www.youtube.com/@heymichaeldaigler)
- [Connecting the Internet with `ChatGPT` (LLMs) using Langchain And Answers Your Questions](https://youtu.be/9Y0TBC63yZg) by [Kamalraj M M](https://www.youtube.com/@insightbuilder)
- [Build More Powerful LLM Applications for Businesss with LangChain (Beginners Guide)](https://youtu.be/sp3-WLKEcBg) by[ No Code Blackbox](https://www.youtube.com/@nocodeblackbox)
- [LangFlow LLM Agent Demo for 🦜🔗LangChain](https://youtu.be/zJxDHaWt-6o) by [Cobus Greyling](https://www.youtube.com/@CobusGreylingZA)
- [Chatbot Factory: Streamline Python Chatbot Creation with LLMs and Langchain](https://youtu.be/eYer3uzrcuM) by [Finxter](https://www.youtube.com/@CobusGreylingZA)
- [LangChain Tutorial - ChatGPT mit eigenen Daten](https://youtu.be/0XDLyY90E2c) by [Coding Crashkurse](https://www.youtube.com/@codingcrashkurse6429)
- [Chat with a `CSV` | LangChain Agents Tutorial (Beginners)](https://youtu.be/tjeti5vXWOU) by [GoDataProf](https://www.youtube.com/@godataprof)
- [Introdução ao Langchain - #Cortes - Live DataHackers](https://youtu.be/fw8y5VRei5Y) by [Prof. João Gabriel Lima](https://www.youtube.com/@profjoaogabriellima)
- [LangChain: Level up `ChatGPT` !? | LangChain Tutorial Part 1](https://youtu.be/vxUGx8aZpDE) by [Code Affinity](https://www.youtube.com/@codeaffinitydev)
- [KI schreibt krasses Youtube Skript 😲😳 | LangChain Tutorial Deutsch](https://youtu.be/QpTiXyK1jus) by [SimpleKI](https://www.youtube.com/@simpleki)
- [Chat with Audio: Langchain, `Chroma DB`, OpenAI, and `Assembly AI`](https://youtu.be/Kjy7cx1r75g) by [AI Anytime](https://www.youtube.com/@AIAnytime)
- [QA over documents with Auto vector index selection with Langchain router chains](https://youtu.be/9G05qybShv8) by [echohive](https://www.youtube.com/@echohive)
- [Build your own custom LLM application with `Bubble.io` & Langchain (No Code & Beginner friendly)](https://youtu.be/O7NhQGu1m6c) by [No Code Blackbox](https://www.youtube.com/@nocodeblackbox)
- [Simple App to Question Your Docs: Leveraging `Streamlit`, `Hugging Face Spaces`, LangChain, and `Claude`!](https://youtu.be/X4YbNECRr7o) by [Chris Alexiuk](https://www.youtube.com/@chrisalexiuk)
- [LANGCHAIN AI- `ConstitutionalChainAI` + Databutton AI ASSISTANT Web App](https://youtu.be/5zIU6_rdJCU) by [Avra](https://www.youtube.com/@Avra_b)
- [LANGCHAIN AI AUTONOMOUS AGENT WEB APP - 👶 `BABY AGI` 🤖 with EMAIL AUTOMATION using `DATABUTTON`](https://youtu.be/cvAwOGfeHgw) by [Avra](https://www.youtube.com/@Avra_b)
- [The Future of Data Analysis: Using A.I. Models in Data Analysis (LangChain)](https://youtu.be/v_LIcVyg5dk) by [Absent Data](https://www.youtube.com/@absentdata)
- [Memory in LangChain | Deep dive (python)](https://youtu.be/70lqvTFh_Yg) by [Eden Marco](https://www.youtube.com/@EdenMarco)
- [9 LangChain UseCases | Beginner's Guide | 2023](https://youtu.be/zS8_qosHNMw) by [Data Science Basics](https://www.youtube.com/@datasciencebasics)
- [Use Large Language Models in Jupyter Notebook | LangChain | Agents & Indexes](https://youtu.be/JSe11L1a_QQ) by [Abhinaw Tiwari](https://www.youtube.com/@AbhinawTiwariAT)
- [How to Talk to Your Langchain Agent | `11 Labs` + `Whisper`](https://youtu.be/N4k459Zw2PU) by [VRSEN](https://www.youtube.com/@vrsen)
- [LangChain Deep Dive: 5 FUN AI App Ideas To Build Quickly and Easily](https://youtu.be/mPYEPzLkeks) by [James NoCode](https://www.youtube.com/@jamesnocode)
- [LangChain 101: Models](https://youtu.be/T6c_XsyaNSQ) by [Mckay Wrigley](https://www.youtube.com/@realmckaywrigley)
- [LangChain with JavaScript Tutorial #1 | Setup & Using LLMs](https://youtu.be/W3AoeMrg27o) by [Leon van Zyl](https://www.youtube.com/@leonvanzyl)
- [LangChain Overview & Tutorial for Beginners: Build Powerful AI Apps Quickly & Easily (ZERO CODE)](https://youtu.be/iI84yym473Q) by [James NoCode](https://www.youtube.com/@jamesnocode)
- [LangChain In Action: Real-World Use Case With Step-by-Step Tutorial](https://youtu.be/UO699Szp82M) by [Rabbitmetrics](https://www.youtube.com/@rabbitmetrics)
- [Summarizing and Querying Multiple Papers with LangChain](https://youtu.be/p_MQRWH5Y6k) by [Automata Learning Lab](https://www.youtube.com/@automatalearninglab)
- [Using Langchain (and `Replit`) through `Tana`, ask `Google`/`Wikipedia`/`Wolfram Alpha` to fill out a table](https://youtu.be/Webau9lEzoI) by [Stian Håklev](https://www.youtube.com/@StianHaklev)
- [Langchain PDF App (GUI) | Create a ChatGPT For Your `PDF` in Python](https://youtu.be/wUAUdEw5oxM) by [Alejandro AO - Software & Ai](https://www.youtube.com/@alejandro_ao)
- [Auto-GPT with LangChain 🔥 | Create Your Own Personal AI Assistant](https://youtu.be/imDfPmMKEjM) by [Data Science Basics](https://www.youtube.com/@datasciencebasics)
- [Create Your OWN Slack AI Assistant with Python & LangChain](https://youtu.be/3jFXRNn2Bu8) by [Dave Ebbelaar](https://www.youtube.com/@daveebbelaar)
- [How to Create LOCAL Chatbots with GPT4All and LangChain [Full Guide]](https://youtu.be/4p1Fojur8Zw) by [Liam Ottley](https://www.youtube.com/@LiamOttley)
- [Build a `Multilingual PDF` Search App with LangChain, `Cohere` and `Bubble`](https://youtu.be/hOrtuumOrv8) by [Menlo Park Lab](https://www.youtube.com/@menloparklab)
- [Building a LangChain Agent (code-free!) Using `Bubble` and `Flowise`](https://youtu.be/jDJIIVWTZDE) by [Menlo Park Lab](https://www.youtube.com/@menloparklab)
- [Build a LangChain-based Semantic PDF Search App with No-Code Tools Bubble and Flowise](https://youtu.be/s33v5cIeqA4) by [Menlo Park Lab](https://www.youtube.com/@menloparklab)
- [LangChain Memory Tutorial | Building a ChatGPT Clone in Python](https://youtu.be/Cwq91cj2Pnc) by [Alejandro AO - Software & Ai](https://www.youtube.com/@alejandro_ao)
- [ChatGPT For Your DATA | Chat with Multiple Documents Using LangChain](https://youtu.be/TeDgIDqQmzs) by [Data Science Basics](https://www.youtube.com/@datasciencebasics)
- [`Llama Index`: Chat with Documentation using URL Loader](https://youtu.be/XJRoDEctAwA) by [Merk](https://www.youtube.com/@heymichaeldaigler)
- [Using OpenAI, LangChain, and `Gradio` to Build Custom GenAI Applications](https://youtu.be/1MsmqMg3yUc) by [David Hundley](https://www.youtube.com/@dkhundley)
- [LangChain, Chroma DB, OpenAI Beginner Guide | ChatGPT with your PDF](https://youtu.be/FuqdVNB_8c0)
- [Build AI chatbot with custom knowledge base using OpenAI API and GPT Index](https://youtu.be/vDZAZuaXf48) by [Irina Nik](https://www.youtube.com/@irina_nik)
- [Build Your Own Auto-GPT Apps with LangChain (Python Tutorial)](https://youtu.be/NYSWn1ipbgg) by [Dave Ebbelaar](https://www.youtube.com/@daveebbelaar)
- [Chat with Multiple `PDFs` | LangChain App Tutorial in Python (Free LLMs and Embeddings)](https://youtu.be/dXxQ0LR-3Hg) by [Alejandro AO - Software & Ai](https://www.youtube.com/@alejandro_ao)
- [Chat with a `CSV` | `LangChain Agents` Tutorial (Beginners)](https://youtu.be/tjeti5vXWOU) by [Alejandro AO - Software & Ai](https://www.youtube.com/@alejandro_ao)
- [Create Your Own ChatGPT with `PDF` Data in 5 Minutes (LangChain Tutorial)](https://youtu.be/au2WVVGUvc8) by [Liam Ottley](https://www.youtube.com/@LiamOttley)
- [Build a Custom Chatbot with OpenAI: `GPT-Index` & LangChain | Step-by-Step Tutorial](https://youtu.be/FIDv6nc4CgU) by [Fabrikod](https://www.youtube.com/@fabrikod)
- [`Flowise` is an open-source no-code UI visual tool to build 🦜🔗LangChain applications](https://youtu.be/CovAPtQPU0k) by [Cobus Greyling](https://www.youtube.com/@CobusGreylingZA)
- [LangChain & GPT 4 For Data Analysis: The `Pandas` Dataframe Agent](https://youtu.be/rFQ5Kmkd4jc) by [Rabbitmetrics](https://www.youtube.com/@rabbitmetrics)
- [`GirlfriendGPT` - AI girlfriend with LangChain](https://youtu.be/LiN3D1QZGQw) by [Girlfriend GPT](https://www.youtube.com/@girlfriendGPT)
- [How to build with Langchain 10x easier | ⛓️ LangFlow & `Flowise`](https://youtu.be/Ya1oGL7ZTvU) by [AI Jason](https://www.youtube.com/@AIJasonZ)
- [Getting Started With LangChain In 20 Minutes- Build Celebrity Search Application](https://youtu.be/_FpT1cwcSLg) by [Krish Naik](https://www.youtube.com/@krishnaik06)
- ⛓ [Vector Embeddings Tutorial Code Your Own AI Assistant with `GPT-4 API` + LangChain + NLP](https://youtu.be/yfHHvmaMkcA?si=5uJhxoh2tvdnOXok) by [FreeCodeCamp.org](https://www.youtube.com/@freecodecamp)
- ⛓ [Fully LOCAL `Llama 2` Q&A with LangChain](https://youtu.be/wgYctKFnQ74?si=UX1F3W-B3MqF4-K-) by [1littlecoder](https://www.youtube.com/@1littlecoder)
- ⛓ [Fully LOCAL `Llama 2` Langchain on CPU](https://youtu.be/yhECvKMu8kM?si=IvjxwlA1c09VwHZ4) by [1littlecoder](https://www.youtube.com/@1littlecoder)
- ⛓ [Build LangChain Audio Apps with Python in 5 Minutes](https://youtu.be/7w7ysaDz2W4?si=BvdMiyHhormr2-vr) by [AssemblyAI](https://www.youtube.com/@AssemblyAI)
- ⛓ [`Voiceflow` & `Flowise`: Want to Beat Competition? New Tutorial with Real AI Chatbot](https://youtu.be/EZKkmeFwag0?si=-4dETYDHEstiK_bb) by [AI SIMP](https://www.youtube.com/@aisimp)
- ⛓ [THIS Is How You Build Production-Ready AI Apps (`LangSmith` Tutorial)](https://youtu.be/tFXm5ijih98?si=lfiqpyaivxHFyI94) by [Dave Ebbelaar](https://www.youtube.com/@daveebbelaar)
- ⛓ [Build POWERFUL LLM Bots EASILY with Your Own Data - `Embedchain` - Langchain 2.0? (Tutorial)](https://youtu.be/jE24Y_GasE8?si=0yEDZt3BK5Q-LIuF) by [WorldofAI](https://www.youtube.com/@intheworldofai)
- ⛓ [`Code Llama` powered Gradio App for Coding: Runs on CPU](https://youtu.be/AJOhV6Ryy5o?si=ouuQT6IghYlc1NEJ) by [AI Anytime](https://www.youtube.com/@AIAnytime)
- ⛓ [LangChain Complete Course in One Video | Develop LangChain (AI) Based Solutions for Your Business](https://youtu.be/j9mQd-MyIg8?si=_wlNT3nP2LpDKztZ) by [UBprogrammer](https://www.youtube.com/@UBprogrammer)
- ⛓ [How to Run `LLaMA` Locally on CPU or GPU | Python & Langchain & CTransformers Guide](https://youtu.be/SvjWDX2NqiM?si=DxFml8XeGhiLTzLV) by [Code With Prince](https://www.youtube.com/@CodeWithPrince)
- ⛓ [PyData Heidelberg #11 - TimeSeries Forecasting & LLM Langchain](https://www.youtube.com/live/Glbwb5Hxu18?si=PIEY8Raq_C9PCHuW) by [PyData](https://www.youtube.com/@PyDataTV)
- ⛓ [Prompt Engineering in Web Development | Using LangChain and Templates with OpenAI](https://youtu.be/pK6WzlTOlYw?si=fkcDQsBG2h-DM8uQ) by [Akamai Developer
](https://www.youtube.com/@AkamaiDeveloper)
- ⛓ [Retrieval-Augmented Generation (RAG) using LangChain and `Pinecone` - The RAG Special Episode](https://youtu.be/J_tCD_J6w3s?si=60Mnr5VD9UED9bGG) by [Generative AI and Data Science On AWS](https://www.youtube.com/@GenerativeAIOnAWS)
- ⛓ [`LLAMA2 70b-chat` Multiple Documents Chatbot with Langchain & Streamlit |All OPEN SOURCE|Replicate API](https://youtu.be/vhghB81vViM?si=dszzJnArMeac7lyc) by [DataInsightEdge](https://www.youtube.com/@DataInsightEdge01)
- ⛓ [Chatting with 44K Fashion Products: LangChain Opportunities and Pitfalls](https://youtu.be/Zudgske0F_s?si=8HSshHoEhh0PemJA) by [Rabbitmetrics](https://www.youtube.com/@rabbitmetrics)
- ⛓ [Structured Data Extraction from `ChatGPT` with LangChain](https://youtu.be/q1lYg8JISpQ?si=0HctzOHYZvq62sve) by [MG](https://www.youtube.com/@MG_cafe)
- ⛓ [Chat with Multiple PDFs using `Llama 2`, `Pinecone` and LangChain (Free LLMs and Embeddings)](https://youtu.be/TcJ_tVSGS4g?si=FZYnMDJyoFfL3Z2i) by [Muhammad Moin](https://www.youtube.com/@muhammadmoinfaisal)
- ⛓ [Integrate Audio into `LangChain.js` apps in 5 Minutes](https://youtu.be/hNpUSaYZIzs?si=Gb9h7W9A8lzfvFKi) by [AssemblyAI](https://www.youtube.com/@AssemblyAI)
- ⛓ [`ChatGPT` for your data with Local LLM](https://youtu.be/bWrjpwhHEMU?si=uM6ZZ18z9og4M90u) by [Jacob Jedryszek](https://www.youtube.com/@jj09)
- ⛓ [Training `Chatgpt` with your personal data using langchain step by step in detail](https://youtu.be/j3xOMde2v9Y?si=179HsiMU-hEPuSs4) by [NextGen Machines](https://www.youtube.com/@MayankGupta-kb5yc)
- ⛓ [Use ANY language in `LangSmith` with REST](https://youtu.be/7BL0GEdMmgY?si=iXfOEdBLqXF6hqRM) by [Nerding I/O](https://www.youtube.com/@nerding_io)
- ⛓ [How to Leverage the Full Potential of LLMs for Your Business with Langchain - Leon Ruddat](https://youtu.be/vZmoEa7oWMg?si=ZhMmydq7RtkZd56Q) by [PyData](https://www.youtube.com/@PyDataTV)
- ⛓ [`ChatCSV` App: Chat with CSV files using LangChain and `Llama 2`](https://youtu.be/PvsMg6jFs8E?si=Qzg5u5gijxj933Ya) by [Muhammad Moin](https://www.youtube.com/@muhammadmoinfaisal)
- ⛓ [Build Chat PDF app in Python with LangChain, OpenAI, Streamlit | Full project | Learn Coding](https://www.youtube.com/watch?v=WYzFzZg4YZI) by [Jutsupoint](https://www.youtube.com/@JutsuPoint)
- ⛓ [Build Eminem Bot App with LangChain, Streamlit, OpenAI | Full Python Project | Tutorial | AI ChatBot](https://www.youtube.com/watch?v=a2shHB4MRZ4) by [Jutsupoint](https://www.youtube.com/@JutsuPoint)
### [Prompt Engineering and LangChain](https://www.youtube.com/watch?v=muXbPpG_ys4&list=PLEJK-H61Xlwzm5FYLDdKt_6yibO33zoMW) by [Venelin Valkov](https://www.youtube.com/@venelin_valkov)
- [Getting Started with LangChain: Load Custom Data, Run OpenAI Models, Embeddings and `ChatGPT`](https://www.youtube.com/watch?v=muXbPpG_ys4)
- [Loaders, Indexes & Vectorstores in LangChain: Question Answering on `PDF` files with `ChatGPT`](https://www.youtube.com/watch?v=FQnvfR8Dmr0)
- [LangChain Models: `ChatGPT`, `Flan Alpaca`, `OpenAI Embeddings`, Prompt Templates & Streaming](https://www.youtube.com/watch?v=zy6LiK5F5-s)
- [LangChain Chains: Use `ChatGPT` to Build Conversational Agents, Summaries and Q&A on Text With LLMs](https://www.youtube.com/watch?v=h1tJZQPcimM)
- [Analyze Custom CSV Data with `GPT-4` using Langchain](https://www.youtube.com/watch?v=Ew3sGdX8at4)
- [Build ChatGPT Chatbots with LangChain Memory: Understanding and Implementing Memory in Conversations](https://youtu.be/CyuUlf54wTs)
---------------------
⛓ icon marks a new addition [last update 2024-02-04]

View File

@@ -0,0 +1,27 @@
# langchain-core
## 0.1.7 (Jan 5, 2024)
#### Deleted
No deletions.
#### Deprecated
- `BaseChatModel` methods `__call__`, `call_as_llm`, `predict`, `predict_messages`. Will be removed in 0.2.0. Use `BaseChatModel.invoke` instead.
- `BaseChatModel` methods `apredict`, `apredict_messages`. Will be removed in 0.2.0. Use `BaseChatModel.ainvoke` instead.
- `BaseLLM` methods `__call__, `predict`, `predict_messages`. Will be removed in 0.2.0. Use `BaseLLM.invoke` instead.
- `BaseLLM` methods `apredict`, `apredict_messages`. Will be removed in 0.2.0. Use `BaseLLM.ainvoke` instead.
#### Fixed
- Restrict recursive URL scraping: [#15559](https://github.com/langchain-ai/langchain/pull/15559)
#### Added
No additions.
#### Beta
- Marked `langchain_core.load.load` and `langchain_core.load.loads` as beta.
- Marked `langchain_core.beta.runnables.context.ContextGet` and `langchain_core.beta.runnables.context.ContextSet` as beta.

View File

@@ -0,0 +1,36 @@
# langchain
## 0.1.0 (Jan 5, 2024)
#### Deleted
No deletions.
#### Deprecated
Deprecated classes and methods will be removed in 0.2.0
| Deprecated | Alternative | Reason |
|---------------------------------|-----------------------------------|------------------------------------------------|
| ChatVectorDBChain | ConversationalRetrievalChain | More general to all retrievers |
| create_ernie_fn_chain | create_ernie_fn_runnable | Use LCEL under the hood |
| created_structured_output_chain | create_structured_output_runnable | Use LCEL under the hood |
| NatBotChain | | Not used |
| create_openai_fn_chain | create_openai_fn_runnable | Use LCEL under the hood |
| create_structured_output_chain | create_structured_output_runnable | Use LCEL under the hood |
| load_query_constructor_chain | load_query_constructor_runnable | Use LCEL under the hood |
| VectorDBQA | RetrievalQA | More general to all retrievers |
| Sequential Chain | LCEL | Obviated by LCEL |
| SimpleSequentialChain | LCEL | Obviated by LCEL |
| TransformChain | LCEL/RunnableLambda | Obviated by LCEL |
| create_tagging_chain | create_structured_output_runnable | Use LCEL under the hood |
| ChatAgent | create_react_agent | Use LCEL builder over a class |
| ConversationalAgent | create_react_agent | Use LCEL builder over a class |
| ConversationalChatAgent | create_json_chat_agent | Use LCEL builder over a class |
| initialize_agent | Individual create agent methods | Individual create agent methods are more clear |
| ZeroShotAgent | create_react_agent | Use LCEL builder over a class |
| OpenAIFunctionsAgent | create_openai_functions_agent | Use LCEL builder over a class |
| OpenAIMultiFunctionsAgent | create_openai_tools_agent | Use LCEL builder over a class |
| SelfAskWithSearchAgent | create_self_ask_with_search | Use LCEL builder over a class |
| StructuredChatAgent | create_structured_chat_agent | Use LCEL builder over a class |
| XMLAgent | create_xml_agent | Use LCEL builder over a class |

View File

@@ -0,0 +1,542 @@
# Conceptual guide
import ThemedImage from '@theme/ThemedImage';
This section contains introductions to key parts of LangChain.
## Architecture
LangChain as a framework consists of several pieces. The below diagram shows how they relate.
<ThemedImage
alt="Diagram outlining the hierarchical organization of the LangChain framework, displaying the interconnected parts across multiple layers."
sources={{
light: '/svg/langchain_stack.svg',
dark: '/svg/langchain_stack_dark.svg',
}}
title="LangChain Framework Overview"
/>
### `langchain-core`
This package contains base abstractions of different components and ways to compose them together.
The interfaces for core components like LLMs, vectorstores, retrievers and more are defined here.
No third party integrations are defined here.
The dependencies are kept purposefully very lightweight.
### `langchain-community`
This package contains third party integrations that are maintained by the LangChain community.
Key partner packages are separated out (see below).
This contains all integrations for various components (LLMs, vectorstores, retrievers).
All dependencies in this package are optional to keep the package as lightweight as possible.
### Partner packages
While the long tail of integrations are in `langchain-community`, we split popular integrations into their own packages (e.g. `langchain-openai`, `langchain-anthropic`, etc).
This was done in order to improve support for these important integrations.
### `langchain`
The main `langchain` package contains chains, agents, and retrieval strategies that make up an application's cognitive architecture.
These are NOT third party integrations.
All chains, agents, and retrieval strategies here are NOT specific to any one integration, but rather generic across all integrations.
### [LangGraph](/docs/langgraph)
Not currently in this repo, `langgraph` is an extension of `langchain` aimed at
building robust and stateful multi-actor applications with LLMs by modeling steps as edges and nodes in a graph.
LangGraph exposes high level interfaces for creating common types of agents, as well as a low-level API for constructing more contr
### [langserve](/docs/langserve)
A package to deploy LangChain chains as REST APIs. Makes it easy to get a production ready API up and running.
### [LangSmith](/docs/langsmith)
A developer platform that lets you debug, test, evaluate, and monitor LLM applications.
## Installation
If you want to work with high level abstractions, you should install the `langchain` package.
```shell
pip install langchain
```
If you want to work with specific integrations, you will need to install them separately.
See [here](/docs/integrations/platforms/) for a list of integrations and how to install them.
For working with LangSmith, you will need to set up a LangSmith developer account [here](https://smith.langchain.com) and get an API key.
After that, you can enable it by setting environment variables:
```shell
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=ls__...
```
## LangChain Expression Language
LangChain Expression Language, or LCEL, is a declarative way to easily compose chains together.
LCEL was designed from day 1 to **support putting prototypes in production, with no code changes**, from the simplest “prompt + LLM” chain to the most complex chains (weve seen folks successfully run LCEL chains with 100s of steps in production). To highlight a few of the reasons you might want to use LCEL:
**First-class streaming support**
When you build your chains with LCEL you get the best possible time-to-first-token (time elapsed until the first chunk of output comes out). For some chains this means eg. we stream tokens straight from an LLM to a streaming output parser, and you get back parsed, incremental chunks of output at the same rate as the LLM provider outputs the raw tokens.
**Async support**
Any chain built with LCEL can be called both with the synchronous API (eg. in your Jupyter notebook while prototyping) as well as with the asynchronous API (eg. in a [LangServe](/docs/langsmith) server). This enables using the same code for prototypes and in production, with great performance, and the ability to handle many concurrent requests in the same server.
**Optimized parallel execution**
Whenever your LCEL chains have steps that can be executed in parallel (eg if you fetch documents from multiple retrievers) we automatically do it, both in the sync and the async interfaces, for the smallest possible latency.
**Retries and fallbacks**
Configure retries and fallbacks for any part of your LCEL chain. This is a great way to make your chains more reliable at scale. Were currently working on adding streaming support for retries/fallbacks, so you can get the added reliability without any latency cost.
**Access intermediate results**
For more complex chains its often very useful to access the results of intermediate steps even before the final output is produced. This can be used to let end-users know something is happening, or even just to debug your chain. You can stream intermediate results, and its available on every [LangServe](/docs/langserve) server.
**Input and output schemas**
Input and output schemas give every LCEL chain Pydantic and JSONSchema schemas inferred from the structure of your chain. This can be used for validation of inputs and outputs, and is an integral part of LangServe.
[**Seamless LangSmith tracing**](/docs/langsmith)
As your chains get more and more complex, it becomes increasingly important to understand what exactly is happening at every step.
With LCEL, **all** steps are automatically logged to [LangSmith](/docs/langsmith/) for maximum observability and debuggability.
[**Seamless LangServe deployment**](/docs/langserve)
Any chain created with LCEL can be easily deployed using [LangServe](/docs/langserve).
### Interface
To make it as easy as possible to create custom chains, we've implemented a ["Runnable"](https://api.python.langchain.com/en/stable/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable) protocol. Many LangChain components implement the `Runnable` protocol, including chat models, LLMs, output parsers, retrievers, prompt templates, and more. There are also several useful primitives for working with runnables, which you can read about below.
This is a standard interface, which makes it easy to define custom chains as well as invoke them in a standard way.
The standard interface includes:
- [`stream`](#stream): stream back chunks of the response
- [`invoke`](#invoke): call the chain on an input
- [`batch`](#batch): call the chain on a list of inputs
These also have corresponding async methods that should be used with [asyncio](https://docs.python.org/3/library/asyncio.html) `await` syntax for concurrency:
- `astream`: stream back chunks of the response async
- `ainvoke`: call the chain on an input async
- `abatch`: call the chain on a list of inputs async
- `astream_log`: stream back intermediate steps as they happen, in addition to the final response
- `astream_events`: **beta** stream events as they happen in the chain (introduced in `langchain-core` 0.1.14)
The **input type** and **output type** varies by component:
| Component | Input Type | Output Type |
| --- | --- | --- |
| Prompt | Dictionary | PromptValue |
| ChatModel | Single string, list of chat messages or a PromptValue | ChatMessage |
| LLM | Single string, list of chat messages or a PromptValue | String |
| OutputParser | The output of an LLM or ChatModel | Depends on the parser |
| Retriever | Single string | List of Documents |
| Tool | Single string or dictionary, depending on the tool | Depends on the tool |
All runnables expose input and output **schemas** to inspect the inputs and outputs:
- `input_schema`: an input Pydantic model auto-generated from the structure of the Runnable
- `output_schema`: an output Pydantic model auto-generated from the structure of the Runnable
## Components
LangChain provides standard, extendable interfaces and external integrations for various components useful for building with LLMs.
Some components LangChain implements, some components we rely on third-party integrations for, and others are a mix.
### LLMs
Language models that takes a string as input and returns a string.
These are traditionally older models (newer models generally are `ChatModels`, see below).
Although the underlying models are string in, string out, the LangChain wrappers also allow these models to take messages as input.
This makes them interchangeable with ChatModels.
When messages are passed in as input, they will be formatted into a string under the hood before being passed to the underlying model.
LangChain does not provide any LLMs, rather we rely on third party integrations.
### Chat models
Language models that use a sequence of messages as inputs and return chat messages as outputs (as opposed to using plain text).
These are traditionally newer models (older models are generally `LLMs`, see above).
Chat models support the assignment of distinct roles to conversation messages, helping to distinguish messages from the AI, users, and instructions such as system messages.
Although the underlying models are messages in, message out, the LangChain wrappers also allow these models to take a string as input.
This makes them interchangeable with LLMs (and simpler to use).
When a string is passed in as input, it will be converted to a HumanMessage under the hood before being passed to the underlying model.
LangChain does not provide any ChatModels, rather we rely on third party integrations.
We have some standardized parameters when constructing ChatModels:
- `model`: the name of the model
ChatModels also accept other parameters that are specific to that integration.
### Function/Tool Calling
:::info
We use the term tool calling interchangeably with function calling. Although
function calling is sometimes meant to refer to invocations of a single function,
we treat all models as though they can return multiple tool or function calls in
each message.
:::
Tool calling allows a model to respond to a given prompt by generating output that
matches a user-defined schema. While the name implies that the model is performing
some action, this is actually not the case! The model is coming up with the
arguments to a tool, and actually running the tool (or not) is up to the user -
for example, if you want to [extract output matching some schema](/docs/tutorial/extraction/)
from unstructured text, you could give the model an "extraction" tool that takes
parameters matching the desired schema, then treat the generated output as your final
result.
A tool call includes a name, arguments dict, and an optional identifier. The
arguments dict is structured `{argument_name: argument_value}`.
Many LLM providers, including [Anthropic](https://www.anthropic.com/),
[Cohere](https://cohere.com/), [Google](https://cloud.google.com/vertex-ai),
[Mistral](https://mistral.ai/), [OpenAI](https://openai.com/), and others,
support variants of a tool calling feature. These features typically allow requests
to the LLM to include available tools and their schemas, and for responses to include
calls to these tools. For instance, given a search engine tool, an LLM might handle a
query by first issuing a call to the search engine. The system calling the LLM can
receive the tool call, execute it, and return the output to the LLM to inform its
response. LangChain includes a suite of [built-in tools](/docs/integrations/tools/)
and supports several methods for defining your own [custom tools](/docs/how_to/custom_tools).
There are two main use cases for function/tool calling:
- [How to return structured data from an LLM](/docs/how_to/structured_output/)
- [How to use a model to call tools](/docs/how_to/tool_calling/)
### Message types
Some language models take a list of messages as input and return a message.
There are a few different types of messages.
All messages have a `role`, `content`, and `response_metadata` property.
The `role` describes WHO is saying the message.
LangChain has different message classes for different roles.
The `content` property describes the content of the message.
This can be a few different things:
- A string (most models deal this type of content)
- A List of dictionaries (this is used for multi-modal input, where the dictionary contains information about that input type and that input location)
#### HumanMessage
This represents a message from the user.
#### AIMessage
This represents a message from the model. In addition to the `content` property, these messages also have:
**`response_metadata`**
The `response_metadata` property contains additional metadata about the response. The data here is often specific to each model provider.
This is where information like log-probs and token usage may be stored.
**`tool_calls`**
These represent a decision from an language model to call a tool. They are included as part of an `AIMessage` output.
They can be accessed from there with the `.tool_calls` property.
This property returns a list of dictionaries. Each dictionary has the following keys:
- `name`: The name of the tool that should be called.
- `args`: The arguments to that tool.
- `id`: The id of that tool call.
#### SystemMessage
This represents a system message, which tells the model how to behave. Not every model provider supports this.
#### FunctionMessage
This represents the result of a function call. In addition to `role` and `content`, this message has a `name` parameter which conveys the name of the function that was called to produce this result.
#### ToolMessage
This represents the result of a tool call. This is distinct from a FunctionMessage in order to match OpenAI's `function` and `tool` message types. In addition to `role` and `content`, this message has a `tool_call_id` parameter which conveys the id of the call to the tool that was called to produce this result.
### Prompt templates
Prompt templates help to translate user input and parameters into instructions for a language model.
This can be used to guide a model's response, helping it understand the context and generate relevant and coherent language-based output.
Prompt Templates take as input a dictionary, where each key represents a variable in the prompt template to fill in.
Prompt Templates output a PromptValue. This PromptValue can be passed to an LLM or a ChatModel, and can also be cast to a string or a list of messages.
The reason this PromptValue exists is to make it easy to switch between strings and messages.
There are a few different types of prompt templates
#### String PromptTemplates
These prompt templates are used to format a single string, and generally are used for simpler inputs.
For example, a common way to construct and use a PromptTemplate is as follows:
```python
from langchain_core.prompts import PromptTemplate
prompt_template = PromptTemplate.from_template("Tell me a joke about {topic}")
prompt_template.invoke({"topic": "cats"})
```
#### ChatPromptTemplates
These prompt templates are used to format a list of messages. These "templates" consist of a list of templates themselves.
For example, a common way to construct and use a ChatPromptTemplate is as follows:
```python
from langchain_core.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
("user", "Tell me a joke about {topic}"
])
prompt_template.invoke({"topic": "cats"})
```
In the above example, this ChatPromptTemplate will construct two messages when called.
The first is a system message, that has no variables to format.
The second is a HumanMessage, and will be formatted by the `topic` variable the user passes in.
#### MessagesPlaceholder
This prompt template is responsible for adding a list of messages in a particular place.
In the above ChatPromptTemplate, we saw how we could format two messages, each one a string.
But what if we wanted the user to pass in a list of messages that we would slot into a particular spot?
This is how you use MessagesPlaceholder.
```python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage
prompt_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
MessagesPlaceholder("msgs")
])
prompt_template.invoke({"msgs": [HumanMessage(content="hi!")]})
```
This will produce a list of two messages, the first one being a system message, and the second one being the HumanMessage we passed in.
If we had passed in 5 messages, then it would have produced 6 messages in total (the system message plus the 5 passed in).
This is useful for letting a list of messages be slotted into a particular spot.
An alternative way to accomplish the same thing without using the `MessagesPlaceholder` class explicitly is:
```python
prompt_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
("placeholder", "{msgs}") # <-- This is the changed part
])
```
### Example Selectors
One common prompting technique for achieving better performance is to include examples as part of the prompt.
This gives the language model concrete examples of how it should behave.
Sometimes these examples are hardcoded into the prompt, but for more advanced situations it may be nice to dynamically select them.
Example Selectors are classes responsible for selecting and then formatting examples into prompts.
### Output parsers
:::note
The information here refers to parsers that take a text output from a model try to parse it into a more structured representation.
More and more models are supporting function (or tool) calling, which handles this automatically.
It is recommended to use function/tool calling rather than output parsing.
See documentation for that [here](/docs/concepts/#function-tool-calling).
:::
Responsible for taking the output of a model and transforming it to a more suitable format for downstream tasks.
Useful when you are using LLMs to generate structured data, or to normalize output from chat models and LLMs.
LangChain has lots of different types of output parsers. This is a list of output parsers LangChain supports. The table below has various pieces of information:
**Name**: The name of the output parser
**Supports Streaming**: Whether the output parser supports streaming.
**Has Format Instructions**: Whether the output parser has format instructions. This is generally available except when (a) the desired schema is not specified in the prompt but rather in other parameters (like OpenAI function calling), or (b) when the OutputParser wraps another OutputParser.
**Calls LLM**: Whether this output parser itself calls an LLM. This is usually only done by output parsers that attempt to correct misformatted output.
**Input Type**: Expected input type. Most output parsers work on both strings and messages, but some (like OpenAI Functions) need a message with specific kwargs.
**Output Type**: The output type of the object returned by the parser.
**Description**: Our commentary on this output parser and when to use it.
| Name | Supports Streaming | Has Format Instructions | Calls LLM | Input Type | Output Type | Description |
|-----------------|--------------------|-------------------------------|-----------|----------------------------------|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [JSON](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.json.JsonOutputParser.html#langchain_core.output_parsers.json.JsonOutputParser) | ✅ | ✅ | | `str` \| `Message` | JSON object | Returns a JSON object as specified. You can specify a Pydantic model and it will return JSON for that model. Probably the most reliable output parser for getting structured data that does NOT use function calling. |
| [XML](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.xml.XMLOutputParser.html#langchain_core.output_parsers.xml.XMLOutputParser) | ✅ | ✅ | | `str` \| `Message` | `dict` | Returns a dictionary of tags. Use when XML output is needed. Use with models that are good at writing XML (like Anthropic's). |
| [CSV](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.list.CommaSeparatedListOutputParser.html#langchain_core.output_parsers.list.CommaSeparatedListOutputParser) | ✅ | ✅ | | `str` \| `Message` | `List[str]` | Returns a list of comma separated values. |
| [OutputFixing](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.fix.OutputFixingParser.html#langchain.output_parsers.fix.OutputFixingParser) | | | ✅ | `str` \| `Message` | | Wraps another output parser. If that output parser errors, then this will pass the error message and the bad output to an LLM and ask it to fix the output. |
| [RetryWithError](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.retry.RetryWithErrorOutputParser.html#langchain.output_parsers.retry.RetryWithErrorOutputParser) | | | ✅ | `str` \| `Message` | | Wraps another output parser. If that output parser errors, then this will pass the original inputs, the bad output, and the error message to an LLM and ask it to fix it. Compared to OutputFixingParser, this one also sends the original instructions. |
| [Pydantic](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.pydantic.PydanticOutputParser.html#langchain_core.output_parsers.pydantic.PydanticOutputParser) | | ✅ | | `str` \| `Message` | `pydantic.BaseModel` | Takes a user defined Pydantic model and returns data in that format. |
| [YAML](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.yaml.YamlOutputParser.html#langchain.output_parsers.yaml.YamlOutputParser) | | ✅ | | `str` \| `Message` | `pydantic.BaseModel` | Takes a user defined Pydantic model and returns data in that format. Uses YAML to encode it. |
| [PandasDataFrame](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.pandas_dataframe.PandasDataFrameOutputParser.html#langchain.output_parsers.pandas_dataframe.PandasDataFrameOutputParser) | | ✅ | | `str` \| `Message` | `dict` | Useful for doing operations with pandas DataFrames. |
| [Enum](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.enum.EnumOutputParser.html#langchain.output_parsers.enum.EnumOutputParser) | | ✅ | | `str` \| `Message` | `Enum` | Parses response into one of the provided enum values. |
| [Datetime](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.datetime.DatetimeOutputParser.html#langchain.output_parsers.datetime.DatetimeOutputParser) | | ✅ | | `str` \| `Message` | `datetime.datetime` | Parses response into a datetime string. |
| [Structured](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.structured.StructuredOutputParser.html#langchain.output_parsers.structured.StructuredOutputParser) | | ✅ | | `str` \| `Message` | `Dict[str, str]` | An output parser that returns structured information. It is less powerful than other output parsers since it only allows for fields to be strings. This can be useful when you are working with smaller LLMs. |
### Chat History
Most LLM applications have a conversational interface.
An essential component of a conversation is being able to refer to information introduced earlier in the conversation.
At bare minimum, a conversational system should be able to access some window of past messages directly.
The concept of `ChatHistory` refers to a class in LangChain which can be used to wrap an arbitrary chain.
This `ChatHistory` will keep track of inputs and outputs of the underlying chain, and append them as messages to a message database
Future interactions will then load those messages and pass them into the chain as part of the input.
### Document
A Document object in LangChain contains information about some data. It has two attributes:
- `page_content: str`: The content of this document. Currently is only a string.
- `metadata: dict`: Arbitrary metadata associated with this document. Can track the document id, file name, etc.
### Document loaders
These classes load Document objects. LangChain has hundreds of integrations with various data sources to load data from: Slack, Notion, Google Drive, etc.
Each DocumentLoader has its own specific parameters, but they can all be invoked in the same way with the `.load` method.
An example use case is as follows:
```python
from langchain_community.document_loaders.csv_loader import CSVLoader
loader = CSVLoader(
... # <-- Integration specific parameters here
)
data = loader.load()
```
### Text splitters
Once you've loaded documents, you'll often want to transform them to better suit your application. The simplest example is you may want to split a long document into smaller chunks that can fit into your model's context window. LangChain has a number of built-in document transformers that make it easy to split, combine, filter, and otherwise manipulate documents.
When you want to deal with long pieces of text, it is necessary to split up that text into chunks. As simple as this sounds, there is a lot of potential complexity here. Ideally, you want to keep the semantically related pieces of text together. What "semantically related" means could depend on the type of text. This notebook showcases several ways to do that.
At a high level, text splitters work as following:
1. Split the text up into small, semantically meaningful chunks (often sentences).
2. Start combining these small chunks into a larger chunk until you reach a certain size (as measured by some function).
3. Once you reach that size, make that chunk its own piece of text and then start creating a new chunk of text with some overlap (to keep context between chunks).
That means there are two different axes along which you can customize your text splitter:
1. How the text is split
2. How the chunk size is measured
### Embedding models
The Embeddings class is a class designed for interfacing with text embedding models. There are lots of embedding model providers (OpenAI, Cohere, Hugging Face, etc) - this class is designed to provide a standard interface for all of them.
Embeddings create a vector representation of a piece of text. This is useful because it means we can think about text in the vector space, and do things like semantic search where we look for pieces of text that are most similar in the vector space.
The base Embeddings class in LangChain provides two methods: one for embedding documents and one for embedding a query. The former takes as input multiple texts, while the latter takes a single text. The reason for having these as two separate methods is that some embedding providers have different embedding methods for documents (to be searched over) vs queries (the search query itself).
### Vectorstores
One of the most common ways to store and search over unstructured data is to embed it and store the resulting embedding vectors,
and then at query time to embed the unstructured query and retrieve the embedding vectors that are 'most similar' to the embedded query.
A vector store takes care of storing embedded data and performing vector search for you.
Vectorstores can be converted to the retriever interface by doing:
```python
vectorstore = MyVectorStore()
retriever = vectorstore.as_retriever()
```
### Retrievers
A retriever is an interface that returns documents given an unstructured query.
It is more general than a vector store.
A retriever does not need to be able to store documents, only to return (or retrieve) them.
Retrievers can be created from vectorstores, but are also broad enough to include [Wikipedia search](/docs/integrations/retrievers/wikipedia/) and [Amazon Kendra](/docs/integrations/retrievers/amazon_kendra_retriever/).
Retrievers accept a string query as input and return a list of Document's as output.
### Advanced Retrieval Types
LangChain provides several advanced retrieval types. A full list is below, along with the following information:
**Name**: Name of the retrieval algorithm.
**Index Type**: Which index type (if any) this relies on.
**Uses an LLM**: Whether this retrieval method uses an LLM.
**When to Use**: Our commentary on when you should considering using this retrieval method.
**Description**: Description of what this retrieval algorithm is doing.
| Name | Index Type | Uses an LLM | When to Use | Description |
|---------------------------|------------------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Vectorstore](https://api.python.langchain.com/en/latest/vectorstores/langchain_core.vectorstores.VectorStoreRetriever.html#langchain_core.vectorstores.VectorStoreRetriever) | Vectorstore | No | If you are just getting started and looking for something quick and easy. | This is the simplest method and the one that is easiest to get started with. It involves creating embeddings for each piece of text. |
| [ParentDocument](https://api.python.langchain.com/en/latest/retrievers/langchain.retrievers.parent_document_retriever.ParentDocumentRetriever.html#langchain.retrievers.parent_document_retriever.ParentDocumentRetriever) | Vectorstore + Document Store | No | If your pages have lots of smaller pieces of distinct information that are best indexed by themselves, but best retrieved all together. | This involves indexing multiple chunks for each document. Then you find the chunks that are most similar in embedding space, but you retrieve the whole parent document and return that (rather than individual chunks). |
| [Multi Vector](https://api.python.langchain.com/en/latest/retrievers/langchain.retrievers.multi_vector.MultiVectorRetriever.html#langchain.retrievers.multi_vector.MultiVectorRetriever) | Vectorstore + Document Store | Sometimes during indexing | If you are able to extract information from documents that you think is more relevant to index than the text itself. | This involves creating multiple vectors for each document. Each vector could be created in a myriad of ways - examples include summaries of the text and hypothetical questions. |
| [Self Query](https://api.python.langchain.com/en/latest/retrievers/langchain.retrievers.self_query.base.SelfQueryRetriever.html#langchain.retrievers.self_query.base.SelfQueryRetriever) | Vectorstore | Yes | If users are asking questions that are better answered by fetching documents based on metadata rather than similarity with the text. | This uses an LLM to transform user input into two things: (1) a string to look up semantically, (2) a metadata filer to go along with it. This is useful because oftentimes questions are about the METADATA of documents (not the content itself). |
| [Contextual Compression](https://api.python.langchain.com/en/latest/retrievers/langchain.retrievers.contextual_compression.ContextualCompressionRetriever.html#langchain.retrievers.contextual_compression.ContextualCompressionRetriever) | Any | Sometimes | If you are finding that your retrieved documents contain too much irrelevant information and are distracting the LLM. | This puts a post-processing step on top of another retriever and extracts only the most relevant information from retrieved documents. This can be done with embeddings or an LLM. |
| [Time-Weighted Vectorstore](https://api.python.langchain.com/en/latest/retrievers/langchain.retrievers.time_weighted_retriever.TimeWeightedVectorStoreRetriever.html#langchain.retrievers.time_weighted_retriever.TimeWeightedVectorStoreRetriever) | Vectorstore | No | If you have timestamps associated with your documents, and you want to retrieve the most recent ones | This fetches documents based on a combination of semantic similarity (as in normal vector retrieval) and recency (looking at timestamps of indexed documents) |
| [Multi-Query Retriever](https://api.python.langchain.com/en/latest/retrievers/langchain.retrievers.multi_query.MultiQueryRetriever.html#langchain.retrievers.multi_query.MultiQueryRetriever) | Any | Yes | If users are asking questions that are complex and require multiple pieces of distinct information to respond | This uses an LLM to generate multiple queries from the original one. This is useful when the original query needs pieces of information about multiple topics to be properly answered. By generating multiple queries, we can then fetch documents for each of them. |
| [Ensemble](https://api.python.langchain.com/en/latest/retrievers/langchain.retrievers.ensemble.EnsembleRetriever.html#langchain.retrievers.ensemble.EnsembleRetriever) | Any | No | If you have multiple retrieval methods and want to try combining them. | This fetches documents from multiple retrievers and then combines them. |
### Tools
Tools are interfaces that an agent, chain, or LLM can use to interact with the world.
They combine a few things:
1. The name of the tool
2. A description of what the tool is
3. JSON schema of what the inputs to the tool are
4. The function to call
5. Whether the result of a tool should be returned directly to the user
It is useful to have all this information because this information can be used to build action-taking systems! The name, description, and JSON schema can be used to prompt the LLM so it knows how to specify what action to take, and then the function to call is equivalent to taking that action.
The simpler the input to a tool is, the easier it is for an LLM to be able to use it.
Many agents will only work with tools that have a single string input.
Importantly, the name, description, and JSON schema (if used) are all used in the prompt. Therefore, it is really important that they are clear and describe exactly how the tool should be used. You may need to change the default name, description, or JSON schema if the LLM is not understanding how to use the tool.
### Toolkits
Toolkits are collections of tools that are designed to be used together for specific tasks. They have convenient loading methods.
All Toolkits expose a `get_tools` method which returns a list of tools.
You can therefore do:
```python
# Initialize a toolkit
toolkit = ExampleTookit(...)
# Get list of tools
tools = toolkit.get_tools()
```
### Agents
By themselves, language models can't take actions - they just output text.
A big use case for LangChain is creating **agents**.
Agents are systems that use an LLM as a reasoning enginer to determine which actions to take and what the inputs to those actions should be.
The results of those actions can then be fed back into the agent and it determine whether more actions are needed, or whether it is okay to finish.
[LangGraph](https://github.com/langchain-ai/langgraph) is an extension of LangChain specifically aimed at creating highly controllable and customizable agents.
Please check out that documentation for a more in depth overview of agent concepts.
There is a legacy agent concept in LangChain that we are moving towards deprecating: `AgentExecutor`.
AgentExecutor was essentially a runtime for agents.
It was a great place to get started, however, it was not flexible enough as you started to have more customized agents.
In order to solve that we built LangGraph to be this flexible, highly-controllable runtime.
If you are still using AgentExecutor, do not fear: we still have a guide on [how to use AgentExecutor](/docs/how_to/agent_executor).
It is recommended, however, that you start to transition to LangGraph.
In order to assist in this we have put together a [transition guide on how to do so](/docs/how_to/migrate_agent)

View File

@@ -0,0 +1,250 @@
---
sidebar_position: 1
---
# Contribute Code
To contribute to this project, please follow the ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
Please do not try to push directly to this repo unless you are a maintainer.
Please follow the checked-in pull request template when opening pull requests. Note related issues and tag relevant
maintainers.
Pull requests cannot land without passing the formatting, linting, and testing checks first. See [Testing](#testing) and
[Formatting and Linting](#formatting-and-linting) for how to run these checks locally.
It's essential that we maintain great documentation and testing. If you:
- Fix a bug
- Add a relevant unit or integration test when possible. These live in `tests/unit_tests` and `tests/integration_tests`.
- Make an improvement
- Update any affected example notebooks and documentation. These live in `docs`.
- Update unit and integration tests when relevant.
- Add a feature
- Add a demo notebook in `docs/docs/`.
- Add unit and integration tests.
We are a small, progress-oriented team. If there's something you'd like to add or change, opening a pull request is the
best way to get our attention.
## 🚀 Quick Start
This quick start guide explains how to run the repository locally.
For a [development container](https://containers.dev/), see the [.devcontainer folder](https://github.com/langchain-ai/langchain/tree/master/.devcontainer).
### Dependency Management: Poetry and other env/dependency managers
This project utilizes [Poetry](https://python-poetry.org/) v1.7.1+ as a dependency manager.
❗Note: *Before installing Poetry*, if you use `Conda`, create and activate a new Conda env (e.g. `conda create -n langchain python=3.9`)
Install Poetry: **[documentation on how to install it](https://python-poetry.org/docs/#installation)**.
❗Note: If you use `Conda` or `Pyenv` as your environment/package manager, after installing Poetry,
tell Poetry to use the virtualenv python environment (`poetry config virtualenvs.prefer-active-python true`)
### Different packages
This repository contains multiple packages:
- `langchain-core`: Base interfaces for key abstractions as well as logic for combining them in chains (LangChain Expression Language).
- `langchain-community`: Third-party integrations of various components.
- `langchain`: Chains, agents, and retrieval logic that makes up the cognitive architecture of your applications.
- `langchain-experimental`: Components and chains that are experimental, either in the sense that the techniques are novel and still being tested, or they require giving the LLM more access than would be possible in most production systems.
- Partner integrations: Partner packages in `libs/partners` that are independently version controlled.
Each of these has its own development environment. Docs are run from the top-level makefile, but development
is split across separate test & release flows.
For this quickstart, start with langchain-community:
```bash
cd libs/community
```
### Local Development Dependencies
Install langchain-community development requirements (for running langchain, running examples, linting, formatting, tests, and coverage):
```bash
poetry install --with lint,typing,test,test_integration
```
Then verify dependency installation:
```bash
make test
```
If during installation you receive a `WheelFileValidationError` for `debugpy`, please make sure you are running
Poetry v1.6.1+. This bug was present in older versions of Poetry (e.g. 1.4.1) and has been resolved in newer releases.
If you are still seeing this bug on v1.6.1+, you may also try disabling "modern installation"
(`poetry config installer.modern-installation false`) and re-installing requirements.
See [this `debugpy` issue](https://github.com/microsoft/debugpy/issues/1246) for more details.
### Testing
_In `langchain`, `langchain-community`, and `langchain-experimental`, some test dependencies are optional; see section about optional dependencies_.
Unit tests cover modular logic that does not require calls to outside APIs.
If you add new logic, please add a unit test.
To run unit tests:
```bash
make test
```
To run unit tests in Docker:
```bash
make docker_tests
```
There are also [integration tests and code-coverage](/docs/contributing/testing/) available.
### Only develop langchain_core or langchain_experimental
If you are only developing `langchain_core` or `langchain_experimental`, you can simply install the dependencies for the respective projects and run tests:
```bash
cd libs/core
poetry install --with test
make test
```
Or:
```bash
cd libs/experimental
poetry install --with test
make test
```
### Formatting and Linting
Run these locally before submitting a PR; the CI system will check also.
#### Code Formatting
Formatting for this project is done via [ruff](https://docs.astral.sh/ruff/rules/).
To run formatting for docs, cookbook and templates:
```bash
make format
```
To run formatting for a library, run the same command from the relevant library directory:
```bash
cd libs/{LIBRARY}
make format
```
Additionally, you can run the formatter only on the files that have been modified in your current branch as compared to the master branch using the format_diff command:
```bash
make format_diff
```
This is especially useful when you have made changes to a subset of the project and want to ensure your changes are properly formatted without affecting the rest of the codebase.
#### Linting
Linting for this project is done via a combination of [ruff](https://docs.astral.sh/ruff/rules/) and [mypy](http://mypy-lang.org/).
To run linting for docs, cookbook and templates:
```bash
make lint
```
To run linting for a library, run the same command from the relevant library directory:
```bash
cd libs/{LIBRARY}
make lint
```
In addition, you can run the linter only on the files that have been modified in your current branch as compared to the master branch using the lint_diff command:
```bash
make lint_diff
```
This can be very helpful when you've made changes to only certain parts of the project and want to ensure your changes meet the linting standards without having to check the entire codebase.
We recognize linting can be annoying - if you do not want to do it, please contact a project maintainer, and they can help you with it. We do not want this to be a blocker for good code getting contributed.
#### Spellcheck
Spellchecking for this project is done via [codespell](https://github.com/codespell-project/codespell).
Note that `codespell` finds common typos, so it could have false-positive (correctly spelled but rarely used) and false-negatives (not finding misspelled) words.
To check spelling for this project:
```bash
make spell_check
```
To fix spelling in place:
```bash
make spell_fix
```
If codespell is incorrectly flagging a word, you can skip spellcheck for that word by adding it to the codespell config in the `pyproject.toml` file.
```python
[tool.codespell]
...
# Add here:
ignore-words-list = 'momento,collison,ned,foor,reworkd,parth,whats,aapply,mysogyny,unsecure'
```
## Working with Optional Dependencies
`langchain`, `langchain-community`, and `langchain-experimental` rely on optional dependencies to keep these packages lightweight.
`langchain-core` and partner packages **do not use** optional dependencies in this way.
You only need to add a new dependency if a **unit test** relies on the package.
If your package is only required for **integration tests**, then you can skip these
steps and leave all pyproject.toml and poetry.lock files alone.
If you're adding a new dependency to Langchain, assume that it will be an optional dependency, and
that most users won't have it installed.
Users who do not have the dependency installed should be able to **import** your code without
any side effects (no warnings, no errors, no exceptions).
To introduce the dependency to the pyproject.toml file correctly, please do the following:
1. Add the dependency to the main group as an optional dependency
```bash
poetry add --optional [package_name]
```
2. Open pyproject.toml and add the dependency to the `extended_testing` extra
3. Relock the poetry file to update the extra.
```bash
poetry lock --no-update
```
4. Add a unit test that the very least attempts to import the new code. Ideally, the unit
test makes use of lightweight fixtures to test the logic of the code.
5. Please use the `@pytest.mark.requires(package_name)` decorator for any tests that require the dependency.
## Adding a Jupyter Notebook
If you are adding a Jupyter Notebook example, you'll want to install the optional `dev` dependencies.
To install dev dependencies:
```bash
poetry install --with dev
```
Launch a notebook:
```bash
poetry run jupyter notebook
```
When you run `poetry install`, the `langchain` package is installed as editable in the virtualenv, so your new logic can be imported into the notebook.

View File

@@ -0,0 +1,2 @@
label: 'Documentation'
position: 3

View File

@@ -0,0 +1,138 @@
---
sidebar_label: "Style guide"
---
# LangChain Documentation Style Guide
## Introduction
As LangChain continues to grow, the surface area of documentation required to cover it continues to grow too.
This page provides guidelines for anyone writing documentation for LangChain, as well as some of our philosophies around
organization and structure.
## Philosophy
LangChain's documentation aspires to follow the [Diataxis framework](https://diataxis.fr).
Under this framework, all documentation falls under one of four categories:
- **Tutorials**: Lessons that take the reader by the hand through a series of conceptual steps to complete a project.
- An example of this is our [LCEL streaming guide](/docs/expression_language/streaming).
- Our guides on [custom components](/docs/modules/model_io/chat/custom_chat_model) is another one.
- **How-to guides**: Guides that take the reader through the steps required to solve a real-world problem.
- The clearest examples of this are our [Use case](/docs/use_cases/) quickstart pages.
- **Reference**: Technical descriptions of the machinery and how to operate it.
- Our [Runnable interface](/docs/expression_language/interface) page is an example of this.
- The [API reference pages](https://api.python.langchain.com/) are another.
- **Explanation**: Explanations that clarify and illuminate a particular topic.
- The [LCEL primitives pages](/docs/expression_language/primitives/sequence) are an example of this.
Each category serves a distinct purpose and requires a specific approach to writing and structuring the content.
## Taxonomy
Keeping the above in mind, we have sorted LangChain's docs into categories. It is helpful to think in these terms
when contributing new documentation:
### Getting started
The [getting started section](/docs/get_started/introduction) includes a high-level introduction to LangChain, a quickstart that
tours LangChain's various features, and logistical instructions around installation and project setup.
It contains elements of **How-to guides** and **Explanations**.
### Use cases
[Use cases](/docs/use_cases/) are guides that are meant to show how to use LangChain to accomplish a specific task (RAG, information extraction, etc.).
The quickstarts should be good entrypoints for first-time LangChain developers who prefer to learn by getting something practical prototyped,
then taking the pieces apart retrospectively. These should mirror what LangChain is good at.
The quickstart pages here should fit the **How-to guide** category, with the other pages intended to be **Explanations** of more
in-depth concepts and strategies that accompany the main happy paths.
:::note
The below sections are listed roughly in order of increasing level of abstraction.
:::
### Expression Language
[LangChain Expression Language (LCEL)](/docs/expression_language/) is the fundamental way that most LangChain components fit together, and this section is designed to teach
developers how to use it to build with LangChain's primitives effectively.
This section should contains **Tutorials** that teach how to stream and use LCEL primitives for more abstract tasks, **Explanations** of specific behaviors,
and some **References** for how to use different methods in the Runnable interface.
### Components
The [components section](/docs/modules) covers concepts one level of abstraction higher than LCEL.
Abstract base classes like `BaseChatModel` and `BaseRetriever` should be covered here, as well as core implementations of these base classes,
such as `ChatPromptTemplate` and `RecursiveCharacterTextSplitter`. Customization guides belong here too.
This section should contain mostly conceptual **Tutorials**, **References**, and **Explanations** of the components they cover.
:::note
As a general rule of thumb, everything covered in the `Expression Language` and `Components` sections (with the exception of the `Composition` section of components) should
cover only components that exist in `langchain_core`.
:::
### Integrations
The [integrations](/docs/integrations/platforms/) are specific implementations of components. These often involve third-party APIs and services.
If this is the case, as a general rule, these are maintained by the third-party partner.
This section should contain mostly **Explanations** and **References**, though the actual content here is more flexible than other sections and more at the
discretion of the third-party provider.
:::note
Concepts covered in `Integrations` should generally exist in `langchain_community` or specific partner packages.
:::
### Guides and Ecosystem
The [Guides](/docs/guides) and [Ecosystem](/docs/langsmith/) sections should contain guides that address higher-level problems than the sections above.
This includes, but is not limited to, considerations around productionization and development workflows.
These should contain mostly **How-to guides**, **Explanations**, and **Tutorials**.
### API references
LangChain's API references. Should act as **References** (as the name implies) with some **Explanation**-focused content as well.
## Sample developer journey
We have set up our docs to assist a new developer to LangChain. Let's walk through the intended path:
- The developer lands on https://python.langchain.com, and reads through the introduction and the diagram.
- If they are just curious, they may be drawn to the [Quickstart](/docs/get_started/quickstart) to get a high-level tour of what LangChain contains.
- If they have a specific task in mind that they want to accomplish, they will be drawn to the Use-Case section. The use-case should provide a good, concrete hook that shows the value LangChain can provide them and be a good entrypoint to the framework.
- They can then move to learn more about the fundamentals of LangChain through the Expression Language sections.
- Next, they can learn about LangChain's various components and integrations.
- Finally, they can get additional knowledge through the Guides.
This is only an ideal of course - sections will inevitably reference lower or higher-level concepts that are documented in other sections.
## Guidelines
Here are some other guidelines you should think about when writing and organizing documentation.
### Linking to other sections
Because sections of the docs do not exist in a vacuum, it is important to link to other sections as often as possible
to allow a developer to learn more about an unfamiliar topic inline.
This includes linking to the API references as well as conceptual sections!
### Conciseness
In general, take a less-is-more approach. If a section with a good explanation of a concept already exists, you should link to it rather than
re-explain it, unless the concept you are documenting presents some new wrinkle.
Be concise, including in code samples.
### General style
- Use active voice and present tense whenever possible.
- Use examples and code snippets to illustrate concepts and usage.
- Use appropriate header levels (`#`, `##`, `###`, etc.) to organize the content hierarchically.
- Use bullet points and numbered lists to break down information into easily digestible chunks.
- Use tables (especially for **Reference** sections) and diagrams often to present information visually.
- Include the table of contents for longer documentation pages to help readers navigate the content, but hide it for shorter pages.

View File

@@ -0,0 +1,171 @@
# Technical logistics
LangChain documentation consists of two components:
1. Main Documentation: Hosted at [python.langchain.com](https://python.langchain.com/),
this comprehensive resource serves as the primary user-facing documentation.
It covers a wide array of topics, including tutorials, use cases, integrations,
and more, offering extensive guidance on building with LangChain.
The content for this documentation lives in the `/docs` directory of the monorepo.
2. In-code Documentation: This is documentation of the codebase itself, which is also
used to generate the externally facing [API Reference](https://api.python.langchain.com/en/latest/langchain_api_reference.html).
The content for the API reference is autogenerated by scanning the docstrings in the codebase. For this reason we ask that
developers document their code well.
The main documentation is built using [Quarto](https://quarto.org) and [Docusaurus 2](https://docusaurus.io/).
The `API Reference` is largely autogenerated by [sphinx](https://www.sphinx-doc.org/en/master/)
from the code and is hosted by [Read the Docs](https://readthedocs.org/).
We appreciate all contributions to the documentation, whether it be fixing a typo,
adding a new tutorial or example and whether it be in the main documentation or the API Reference.
Similar to linting, we recognize documentation can be annoying. If you do not want
to do it, please contact a project maintainer, and they can help you with it. We do not want this to be a blocker for good code getting contributed.
## 📜 Main Documentation
The content for the main documentation is located in the `/docs` directory of the monorepo.
The documentation is written using a combination of ipython notebooks (`.ipynb` files)
and markdown (`.mdx` files). The notebooks are converted to markdown
using [Quarto](https://quarto.org) and then built using [Docusaurus 2](https://docusaurus.io/).
Feel free to make contributions to the main documentation! 🥰
After modifying the documentation:
1. Run the linting and formatting commands (see below) to ensure that the documentation is well-formatted and free of errors.
2. Optionally build the documentation locally to verify that the changes look good.
3. Make a pull request with the changes.
4. You can preview and verify that the changes are what you wanted by clicking the `View deployment` or `Visit Preview` buttons on the pull request `Conversation` page. This will take you to a preview of the documentation changes.
## ⚒️ Linting and Building Documentation Locally
After writing up the documentation, you may want to lint and build the documentation
locally to ensure that it looks good and is free of errors.
If you're unable to build it locally that's okay as well, as you will be able to
see a preview of the documentation on the pull request page.
### Install dependencies
- [Quarto](https://quarto.org) - package that converts Jupyter notebooks (`.ipynb` files) into mdx files for serving in Docusaurus. [Download link](https://quarto.org/docs/download/).
From the **monorepo root**, run the following command to install the dependencies:
```bash
poetry install --with lint,docs --no-root
````
### Building
The code that builds the documentation is located in the `/docs` directory of the monorepo.
In the following commands, the prefix `api_` indicates that those are operations for the API Reference.
Before building the documentation, it is always a good idea to clean the build directory:
```bash
make docs_clean
make api_docs_clean
```
Next, you can build the documentation as outlined below:
```bash
make docs_build
make api_docs_build
```
Finally, run the link checker to ensure all links are valid:
```bash
make docs_linkcheck
make api_docs_linkcheck
```
### Linting and Formatting
The Main Documentation is linted from the **monorepo root**. To lint the main documentation, run the following from there:
```bash
make lint
```
If you have formatting-related errors, you can fix them automatically with:
```bash
make format
```
## ⌨️ In-code Documentation
The in-code documentation is largely autogenerated by [sphinx](https://www.sphinx-doc.org/en/master/) from the code and is hosted by [Read the Docs](https://readthedocs.org/).
For the API reference to be useful, the codebase must be well-documented. This means that all functions, classes, and methods should have a docstring that explains what they do, what the arguments are, and what the return value is. This is a good practice in general, but it is especially important for LangChain because the API reference is the primary resource for developers to understand how to use the codebase.
We generally follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for docstrings.
Here is an example of a well-documented function:
```python
def my_function(arg1: int, arg2: str) -> float:
"""This is a short description of the function. (It should be a single sentence.)
This is a longer description of the function. It should explain what
the function does, what the arguments are, and what the return value is.
It should wrap at 88 characters.
Examples:
This is a section for examples of how to use the function.
.. code-block:: python
my_function(1, "hello")
Args:
arg1: This is a description of arg1. We do not need to specify the type since
it is already specified in the function signature.
arg2: This is a description of arg2.
Returns:
This is a description of the return value.
"""
return 3.14
```
### Linting and Formatting
The in-code documentation is linted from the directories belonging to the packages
being documented.
For example, if you're working on the `langchain-community` package, you would change
the working directory to the `langchain-community` directory:
```bash
cd [root]/libs/langchain-community
```
Set up a virtual environment for the package if you haven't done so already.
Install the dependencies for the package.
```bash
poetry install --with lint
```
Then you can run the following commands to lint and format the in-code documentation:
```bash
make format
make lint
```
## Verify Documentation Changes
After pushing documentation changes to the repository, you can preview and verify that the changes are
what you wanted by clicking the `View deployment` or `Visit Preview` buttons on the pull request `Conversation` page.
This will take you to a preview of the documentation changes.
This preview is created by [Vercel](https://vercel.com/docs/getting-started-with-vercel).

View File

@@ -0,0 +1,26 @@
---
sidebar_position: 6
sidebar_label: FAQ
---
# Frequently Asked Questions
## Pull Requests (PRs)
### How do I allow maintainers to edit my PR?
When you submit a pull request, there may be additional changes
necessary before merging it. Oftentimes, it is more efficient for the
maintainers to make these changes themselves before merging, rather than asking you
to do so in code review.
By default, most pull requests will have a
`✅ Maintainers are allowed to edit this pull request.`
badge in the right-hand sidebar.
If you do not see this badge, you may have this setting off for the fork you are
pull-requesting from. See [this Github docs page](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)
for more information.
Notably, Github doesn't allow this setting to be enabled for forks in **organizations** ([issue](https://github.com/orgs/community/discussions/5634)).
If you are working in an organization, we recommend submitting your PR from a personal
fork in order to enable this setting.

View File

@@ -0,0 +1,54 @@
---
sidebar_position: 0
---
# Welcome Contributors
Hi there! Thank you for even being interested in contributing to LangChain.
As an open-source project in a rapidly developing field, we are extremely open to contributions, whether they involve new features, improved infrastructure, better documentation, or bug fixes.
## 🗺️ Guidelines
### 👩‍💻 Ways to contribute
There are many ways to contribute to LangChain. Here are some common ways people contribute:
- [**Documentation**](/docs/contributing/documentation/style_guide): Help improve our docs, including this one!
- [**Code**](./code.mdx): Help us write code, fix bugs, or improve our infrastructure.
- [**Integrations**](integrations.mdx): Help us integrate with your favorite vendors and tools.
- [**Discussions**](https://github.com/langchain-ai/langchain/discussions): Help answer usage questions and discuss issues with users.
### 🚩 GitHub Issues
Our [issues](https://github.com/langchain-ai/langchain/issues) page is kept up to date with bugs, improvements, and feature requests.
There is a taxonomy of labels to help with sorting and discovery of issues of interest. Please use these to help organize issues.
If you start working on an issue, please assign it to yourself.
If you are adding an issue, please try to keep it focused on a single, modular bug/improvement/feature.
If two issues are related, or blocking, please link them rather than combining them.
We will try to keep these issues as up-to-date as possible, though
with the rapid rate of development in this field some may get out of date.
If you notice this happening, please let us know.
### 💭 GitHub Discussions
We have a [discussions](https://github.com/langchain-ai/langchain/discussions) page where users can ask usage questions, discuss design decisions, and propose new features.
If you are able to help answer questions, please do so! This will allow the maintainers to spend more time focused on development and bug fixing.
### 🙋 Getting Help
Our goal is to have the simplest developer setup possible. Should you experience any difficulty getting setup, please
contact a maintainer! Not only do we want to help get you unblocked, but we also want to make sure that the process is
smooth for future contributors.
In a similar vein, we do enforce certain linting, formatting, and documentation standards in the codebase.
If you are finding these difficult (or even just annoying) to work with, feel free to contact a maintainer for help -
we do not want these to get in the way of getting good code into the codebase.
# 🌟 Recognition
If your contribution has made its way into a release, we will want to give you credit on Twitter (only if you want though)!
If you have a Twitter account you would like us to mention, please let us know in the PR or through another means.

View File

@@ -0,0 +1,198 @@
---
sidebar_position: 5
---
# Contribute Integrations
To begin, make sure you have all the dependencies outlined in guide on [Contributing Code](/docs/contributing/code/).
There are a few different places you can contribute integrations for LangChain:
- **Community**: For lighter-weight integrations that are primarily maintained by LangChain and the Open Source Community.
- **Partner Packages**: For independent packages that are co-maintained by LangChain and a partner.
For the most part, new integrations should be added to the Community package. Partner packages require more maintenance as separate packages, so please confirm with the LangChain team before creating a new partner package.
In the following sections, we'll walk through how to contribute to each of these packages from a fake company, `Parrot Link AI`.
## Community package
The `langchain-community` package is in `libs/community` and contains most integrations.
It can be installed with `pip install langchain-community`, and exported members can be imported with code like
```python
from langchain_community.chat_models import ChatParrotLink
from langchain_community.llms import ParrotLinkLLM
from langchain_community.vectorstores import ParrotLinkVectorStore
```
The `community` package relies on manually-installed dependent packages, so you will see errors
if you try to import a package that is not installed. In our fake example, if you tried to import `ParrotLinkLLM` without installing `parrot-link-sdk`, you will see an `ImportError` telling you to install it when trying to use it.
Let's say we wanted to implement a chat model for Parrot Link AI. We would create a new file in `libs/community/langchain_community/chat_models/parrot_link.py` with the following code:
```python
from langchain_core.language_models.chat_models import BaseChatModel
class ChatParrotLink(BaseChatModel):
"""ChatParrotLink chat model.
Example:
.. code-block:: python
from langchain_community.chat_models import ChatParrotLink
model = ChatParrotLink()
"""
...
```
And we would write tests in:
- Unit tests: `libs/community/tests/unit_tests/chat_models/test_parrot_link.py`
- Integration tests: `libs/community/tests/integration_tests/chat_models/test_parrot_link.py`
And add documentation to:
- `docs/docs/integrations/chat/parrot_link.ipynb`
## Partner package in LangChain repo
Partner packages can be hosted in the `LangChain` monorepo or in an external repo.
Partner package in the `LangChain` repo is placed in `libs/partners/{partner}`
and the package source code is in `libs/partners/{partner}/langchain_{partner}`.
A package is
installed by users with `pip install langchain-{partner}`, and the package members
can be imported with code like:
```python
from langchain_{partner} import X
```
### Set up a new package
To set up a new partner package, use the latest version of the LangChain CLI. You can install or update it with:
```bash
pip install -U langchain-cli
```
Let's say you want to create a new partner package working for a company called Parrot Link AI.
Then, run the following command to create a new partner package:
```bash
cd libs/partners
langchain-cli integration new
> Name: parrot-link
> Name of integration in PascalCase [ParrotLink]: ParrotLink
```
This will create a new package in `libs/partners/parrot-link` with the following structure:
```
libs/partners/parrot-link/
langchain_parrot_link/ # folder containing your package
...
tests/
...
docs/ # bootstrapped docs notebooks, must be moved to /docs in monorepo root
...
scripts/ # scripts for CI
...
LICENSE
README.md # fill out with information about your package
Makefile # default commands for CI
pyproject.toml # package metadata, mostly managed by Poetry
poetry.lock # package lockfile, managed by Poetry
.gitignore
```
### Implement your package
First, add any dependencies your package needs, such as your company's SDK:
```bash
poetry add parrot-link-sdk
```
If you need separate dependencies for type checking, you can add them to the `typing` group with:
```bash
poetry add --group typing types-parrot-link-sdk
```
Then, implement your package in `libs/partners/parrot-link/langchain_parrot_link`.
By default, this will include stubs for a Chat Model, an LLM, and/or a Vector Store. You should delete any of the files you won't use and remove them from `__init__.py`.
### Write Unit and Integration Tests
Some basic tests are presented in the `tests/` directory. You should add more tests to cover your package's functionality.
For information on running and implementing tests, see the [Testing guide](/docs/contributing/testing/).
### Write documentation
Documentation is generated from Jupyter notebooks in the `docs/` directory. You should place the notebooks with examples
to the relevant `docs/docs/integrations` directory in the monorepo root.
### (If Necessary) Deprecate community integration
Note: this is only necessary if you're migrating an existing community integration into
a partner package. If the component you're integrating is net-new to LangChain (i.e.
not already in the `community` package), you can skip this step.
Let's pretend we migrated our `ChatParrotLink` chat model from the community package to
the partner package. We would need to deprecate the old model in the community package.
We would do that by adding a `@deprecated` decorator to the old model as follows, in
`libs/community/langchain_community/chat_models/parrot_link.py`.
Before our change, our chat model might look like this:
```python
class ChatParrotLink(BaseChatModel):
...
```
After our change, it would look like this:
```python
from langchain_core._api.deprecation import deprecated
@deprecated(
since="0.0.<next community version>",
removal="0.2.0",
alternative_import="langchain_parrot_link.ChatParrotLink"
)
class ChatParrotLink(BaseChatModel):
...
```
You should do this for *each* component that you're migrating to the partner package.
### Additional steps
Contributor steps:
- [ ] Add secret names to manual integrations workflow in `.github/workflows/_integration_test.yml`
- [ ] Add secrets to release workflow (for pre-release testing) in `.github/workflows/_release.yml`
Maintainer steps (Contributors should **not** do these):
- [ ] set up pypi and test pypi projects
- [ ] add credential secrets to Github Actions
- [ ] add package to conda-forge
## Partner package in external repo
Partner packages in external repos must be coordinated between the LangChain team and
the partner organization to ensure that they are maintained and updated.
If you're interested in creating a partner package in an external repo, please start
with one in the LangChain repo, and then reach out to the LangChain team to discuss
how to move it to an external repo.

View File

@@ -0,0 +1,54 @@
---
sidebar_position: 0.5
---
# Repository Structure
If you plan on contributing to LangChain code or documentation, it can be useful
to understand the high level structure of the repository.
LangChain is organized as a [monorep](https://en.wikipedia.org/wiki/Monorepo) that contains multiple packages.
Here's the structure visualized as a tree:
```text
.
├── cookbook # Tutorials and examples
├── docs # Contains content for the documentation here: https://python.langchain.com/
├── libs
│ ├── langchain # Main package
│ │ ├── tests/unit_tests # Unit tests (present in each package not shown for brevity)
│ │ ├── tests/integration_tests # Integration tests (present in each package not shown for brevity)
│ ├── langchain-community # Third-party integrations
│ ├── langchain-core # Base interfaces for key abstractions
│ ├── langchain-experimental # Experimental components and chains
│ ├── partners
│ ├── langchain-partner-1
│ ├── langchain-partner-2
│ ├── ...
├── templates # A collection of easily deployable reference architectures for a wide variety of tasks.
```
The root directory also contains the following files:
* `pyproject.toml`: Dependencies for building docs and linting docs, cookbook.
* `Makefile`: A file that contains shortcuts for building, linting and docs and cookbook.
There are other files in the root directory level, but their presence should be self-explanatory. Feel free to browse around!
## Documentation
The `/docs` directory contains the content for the documentation that is shown
at https://python.langchain.com/ and the associated API Reference https://api.python.langchain.com/en/latest/langchain_api_reference.html.
See the [documentation](/docs/contributing/documentation/style_guide) guidelines to learn how to contribute to the documentation.
## Code
The `/libs` directory contains the code for the LangChain packages.
To learn more about how to contribute code see the following guidelines:
- [Code](./code.mdx) Learn how to develop in the LangChain codebase.
- [Integrations](./integrations.mdx) to learn how to contribute to third-party integrations to langchain-community or to start a new partner package.
- [Testing](./testing.mdx) guidelines to learn how to write tests for the packages.

View File

@@ -0,0 +1,147 @@
---
sidebar_position: 2
---
# Testing
All of our packages have unit tests and integration tests, and we favor unit tests over integration tests.
Unit tests run on every pull request, so they should be fast and reliable.
Integration tests run once a day, and they require more setup, so they should be reserved for confirming interface points with external services.
## Unit Tests
Unit tests cover modular logic that does not require calls to outside APIs.
If you add new logic, please add a unit test.
To install dependencies for unit tests:
```bash
poetry install --with test
```
To run unit tests:
```bash
make test
```
To run unit tests in Docker:
```bash
make docker_tests
```
To run a specific test:
```bash
TEST_FILE=tests/unit_tests/test_imports.py make test
```
## Integration Tests
Integration tests cover logic that requires making calls to outside APIs (often integration with other services).
If you add support for a new external API, please add a new integration test.
**Warning:** Almost no tests should be integration tests.
Tests that require making network connections make it difficult for other
developers to test the code.
Instead favor relying on `responses` library and/or mock.patch to mock
requests using small fixtures.
To install dependencies for integration tests:
```bash
poetry install --with test,test_integration
```
To run integration tests:
```bash
make integration_tests
```
### Prepare
The integration tests use several search engines and databases. The tests
aim to verify the correct behavior of the engines and databases according to
their specifications and requirements.
To run some integration tests, such as tests located in
`tests/integration_tests/vectorstores/`, you will need to install the following
software:
- Docker
- Python 3.8.1 or later
Any new dependencies should be added by running:
```bash
# add package and install it after adding:
poetry add tiktoken@latest --group "test_integration" && poetry install --with test_integration
```
Before running any tests, you should start a specific Docker container that has all the
necessary dependencies installed. For instance, we use the `elasticsearch.yml` container
for `test_elasticsearch.py`:
```bash
cd tests/integration_tests/vectorstores/docker-compose
docker-compose -f elasticsearch.yml up
```
For environments that requires more involving preparation, look for `*.sh`. For instance,
`opensearch.sh` builds a required docker image and then launch opensearch.
### Prepare environment variables for local testing:
- copy `tests/integration_tests/.env.example` to `tests/integration_tests/.env`
- set variables in `tests/integration_tests/.env` file, e.g `OPENAI_API_KEY`
Additionally, it's important to note that some integration tests may require certain
environment variables to be set, such as `OPENAI_API_KEY`. Be sure to set any required
environment variables before running the tests to ensure they run correctly.
### Recording HTTP interactions with pytest-vcr
Some of the integration tests in this repository involve making HTTP requests to
external services. To prevent these requests from being made every time the tests are
run, we use pytest-vcr to record and replay HTTP interactions.
When running tests in a CI/CD pipeline, you may not want to modify the existing
cassettes. You can use the --vcr-record=none command-line option to disable recording
new cassettes. Here's an example:
```bash
pytest --log-cli-level=10 tests/integration_tests/vectorstores/test_pinecone.py --vcr-record=none
pytest tests/integration_tests/vectorstores/test_elasticsearch.py --vcr-record=none
```
### Run some tests with coverage:
```bash
pytest tests/integration_tests/vectorstores/test_elasticsearch.py --cov=langchain --cov-report=html
start "" htmlcov/index.html || open htmlcov/index.html
```
## Coverage
Code coverage (i.e. the amount of code that is covered by unit tests) helps identify areas of the code that are potentially more or less brittle.
Coverage requires the dependencies for integration tests:
```bash
poetry install --with test_integration
```
To get a report of current coverage, run the following:
```bash
make coverage
```

Binary file not shown.

View File

@@ -0,0 +1,359 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c95fcd15cd52c944",
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"source": [
"# How to split by HTML header \n",
"## Description and motivation\n",
"\n",
"[HTMLHeaderTextSplitter](https://api.python.langchain.com/en/latest/html/langchain_text_splitters.html.HTMLHeaderTextSplitter.html) is a \"structure-aware\" chunker that splits text at the HTML element level and adds metadata for each header \"relevant\" to any given chunk. It can return chunks element by element or combine elements with the same metadata, with the objectives of (a) keeping related text grouped (more or less) semantically and (b) preserving context-rich information encoded in document structures. It can be used with other text splitters as part of a chunking pipeline.\n",
"\n",
"It is analogous to the [MarkdownHeaderTextSplitter](/docs/how_to/markdown_header_metadata_splitter) for markdown files.\n",
"\n",
"To specify what headers to split on, specify `headers_to_split_on` when instantiating `HTMLHeaderTextSplitter` as shown below.\n",
"\n",
"## Usage examples\n",
"### 1) How to split HTML strings:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2e55d44c-1fff-449a-bf52-0d6df488323f",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain-text-splitters"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "initial_id",
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-02T18:57:49.208965400Z",
"start_time": "2023-10-02T18:57:48.899756Z"
},
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Foo'),\n",
" Document(page_content='Some intro text about Foo. \\nBar main section Bar subsection 1 Bar subsection 2', metadata={'Header 1': 'Foo'}),\n",
" Document(page_content='Some intro text about Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section'}),\n",
" Document(page_content='Some text about the first subtopic of Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section', 'Header 3': 'Bar subsection 1'}),\n",
" Document(page_content='Some text about the second subtopic of Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section', 'Header 3': 'Bar subsection 2'}),\n",
" Document(page_content='Baz', metadata={'Header 1': 'Foo'}),\n",
" Document(page_content='Some text about Baz', metadata={'Header 1': 'Foo', 'Header 2': 'Baz'}),\n",
" Document(page_content='Some concluding text about Foo', metadata={'Header 1': 'Foo'})]"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_text_splitters import HTMLHeaderTextSplitter\n",
"\n",
"html_string = \"\"\"\n",
"<!DOCTYPE html>\n",
"<html>\n",
"<body>\n",
" <div>\n",
" <h1>Foo</h1>\n",
" <p>Some intro text about Foo.</p>\n",
" <div>\n",
" <h2>Bar main section</h2>\n",
" <p>Some intro text about Bar.</p>\n",
" <h3>Bar subsection 1</h3>\n",
" <p>Some text about the first subtopic of Bar.</p>\n",
" <h3>Bar subsection 2</h3>\n",
" <p>Some text about the second subtopic of Bar.</p>\n",
" </div>\n",
" <div>\n",
" <h2>Baz</h2>\n",
" <p>Some text about Baz</p>\n",
" </div>\n",
" <br>\n",
" <p>Some concluding text about Foo</p>\n",
" </div>\n",
"</body>\n",
"</html>\n",
"\"\"\"\n",
"\n",
"headers_to_split_on = [\n",
" (\"h1\", \"Header 1\"),\n",
" (\"h2\", \"Header 2\"),\n",
" (\"h3\", \"Header 3\"),\n",
"]\n",
"\n",
"html_splitter = HTMLHeaderTextSplitter(headers_to_split_on)\n",
"html_header_splits = html_splitter.split_text(html_string)\n",
"html_header_splits"
]
},
{
"cell_type": "markdown",
"id": "7126f179-f4d0-4b5d-8bef-44e83b59262c",
"metadata": {},
"source": [
"To return each element together with their associated headers, specify `return_each_element=True` when instantiating `HTMLHeaderTextSplitter`:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "90c23088-804c-4c89-bd09-b820587ceeef",
"metadata": {},
"outputs": [],
"source": [
"html_splitter = HTMLHeaderTextSplitter(\n",
" headers_to_split_on,\n",
" return_each_element=True,\n",
")\n",
"html_header_splits_elements = html_splitter.split_text(html_string)"
]
},
{
"cell_type": "markdown",
"id": "b776c54e-9159-4d88-9d6c-3a1d0b639dfe",
"metadata": {},
"source": [
"Comparing with the above, where elements are aggregated by their headers:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "711abc74-a7b0-4dc5-a4bb-af3cafe4e0f4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='Foo'\n",
"page_content='Some intro text about Foo. \\nBar main section Bar subsection 1 Bar subsection 2' metadata={'Header 1': 'Foo'}\n"
]
}
],
"source": [
"for element in html_header_splits[:2]:\n",
" print(element)"
]
},
{
"cell_type": "markdown",
"id": "fe5528db-187c-418a-9480-fc0267645d42",
"metadata": {},
"source": [
"Now each element is returned as a distinct `Document`:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "24722d8e-d073-46a8-a821-6b722412f1be",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='Foo'\n",
"page_content='Some intro text about Foo.' metadata={'Header 1': 'Foo'}\n",
"page_content='Bar main section Bar subsection 1 Bar subsection 2' metadata={'Header 1': 'Foo'}\n"
]
}
],
"source": [
"for element in html_header_splits_elements[:3]:\n",
" print(element)"
]
},
{
"cell_type": "markdown",
"id": "e29b4aade2a0070c",
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"source": [
"#### 2) How to split from a URL or HTML file:\n",
"\n",
"To read directly from a URL, pass the URL string into the `split_text_from_url` method.\n",
"\n",
"Similarly, a local HTML file can be passed to the `split_text_from_file` method."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "6ecb9fb2-32ff-4249-a4b4-d5e5e191f013",
"metadata": {},
"outputs": [],
"source": [
"url = \"https://plato.stanford.edu/entries/goedel/\"\n",
"\n",
"headers_to_split_on = [\n",
" (\"h1\", \"Header 1\"),\n",
" (\"h2\", \"Header 2\"),\n",
" (\"h3\", \"Header 3\"),\n",
" (\"h4\", \"Header 4\"),\n",
"]\n",
"\n",
"html_splitter = HTMLHeaderTextSplitter(headers_to_split_on)\n",
"\n",
"# for local file use html_splitter.split_text_from_file(<path_to_file>)\n",
"html_header_splits = html_splitter.split_text_from_url(url)"
]
},
{
"cell_type": "markdown",
"id": "c6e3dd41-0c57-472a-a3d4-4e7e8ea6914f",
"metadata": {},
"source": [
"### 2) How to constrain chunk sizes:\n",
"\n",
"`HTMLHeaderTextSplitter`, which splits based on HTML headers, can be composed with another splitter which constrains splits based on character lengths, such as `RecursiveCharacterTextSplitter`.\n",
"\n",
"This can be done using the `.split_documents` method of the second splitter:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "6ada8ea093ea0475",
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-02T18:57:51.016141300Z",
"start_time": "2023-10-02T18:57:50.647495400Z"
},
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='We see that Gödel first tried to reduce the consistency problem for analysis to that of arithmetic. This seemed to require a truth definition for arithmetic, which in turn led to paradoxes, such as the Liar paradox (“This sentence is false”) and Berrys paradox (“The least number not defined by an expression consisting of just fourteen English words”). Gödel then noticed that such paradoxes would not necessarily arise if truth were replaced by provability. But this means that arithmetic truth', metadata={'Header 1': 'Kurt Gödel', 'Header 2': '2. Gödels Mathematical Work', 'Header 3': '2.2 The Incompleteness Theorems', 'Header 4': '2.2.1 The First Incompleteness Theorem'}),\n",
" Document(page_content='means that arithmetic truth and arithmetic provability are not co-extensive — whence the First Incompleteness Theorem.', metadata={'Header 1': 'Kurt Gödel', 'Header 2': '2. Gödels Mathematical Work', 'Header 3': '2.2 The Incompleteness Theorems', 'Header 4': '2.2.1 The First Incompleteness Theorem'}),\n",
" Document(page_content='This account of Gödels discovery was told to Hao Wang very much after the fact; but in Gödels contemporary correspondence with Bernays and Zermelo, essentially the same description of his path to the theorems is given. (See Gödel 2003a and Gödel 2003b respectively.) From those accounts we see that the undefinability of truth in arithmetic, a result credited to Tarski, was likely obtained in some form by Gödel by 1931. But he neither publicized nor published the result; the biases logicians', metadata={'Header 1': 'Kurt Gödel', 'Header 2': '2. Gödels Mathematical Work', 'Header 3': '2.2 The Incompleteness Theorems', 'Header 4': '2.2.1 The First Incompleteness Theorem'}),\n",
" Document(page_content='result; the biases logicians had expressed at the time concerning the notion of truth, biases which came vehemently to the fore when Tarski announced his results on the undefinability of truth in formal systems 1935, may have served as a deterrent to Gödels publication of that theorem.', metadata={'Header 1': 'Kurt Gödel', 'Header 2': '2. Gödels Mathematical Work', 'Header 3': '2.2 The Incompleteness Theorems', 'Header 4': '2.2.1 The First Incompleteness Theorem'}),\n",
" Document(page_content='We now describe the proof of the two theorems, formulating Gödels results in Peano arithmetic. Gödel himself used a system related to that defined in Principia Mathematica, but containing Peano arithmetic. In our presentation of the First and Second Incompleteness Theorems we refer to Peano arithmetic as P, following Gödels notation.', metadata={'Header 1': 'Kurt Gödel', 'Header 2': '2. Gödels Mathematical Work', 'Header 3': '2.2 The Incompleteness Theorems', 'Header 4': '2.2.2 The proof of the First Incompleteness Theorem'})]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
"\n",
"chunk_size = 500\n",
"chunk_overlap = 30\n",
"text_splitter = RecursiveCharacterTextSplitter(\n",
" chunk_size=chunk_size, chunk_overlap=chunk_overlap\n",
")\n",
"\n",
"# Split\n",
"splits = text_splitter.split_documents(html_header_splits)\n",
"splits[80:85]"
]
},
{
"cell_type": "markdown",
"id": "ac0930371d79554a",
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"source": [
"## Limitations\n",
"\n",
"There can be quite a bit of structural variation from one HTML document to another, and while `HTMLHeaderTextSplitter` will attempt to attach all \"relevant\" headers to any given chunk, it can sometimes miss certain headers. For example, the algorithm assumes an informational hierarchy in which headers are always at nodes \"above\" associated text, i.e. prior siblings, ancestors, and combinations thereof. In the following news article (as of the writing of this document), the document is structured such that the text of the top-level headline, while tagged \"h1\", is in a *distinct* subtree from the text elements that we'd expect it to be *\"above\"*&mdash;so we can observe that the \"h1\" element and its associated text do not show up in the chunk metadata (but, where applicable, we do see \"h2\" and its associated text): \n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "5a5ec1482171b119",
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-02T19:03:25.943524300Z",
"start_time": "2023-10-02T19:03:25.691641Z"
},
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"No two El Niño winters are the same, but many have temperature and precipitation trends in common. \n",
"Average conditions during an El Niño winter across the continental US. \n",
"One of the major reasons is the position of the jet stream, which often shifts south during an El Niño winter. This shift typically brings wetter and cooler weather to the South while the North becomes drier and warmer, according to NOAA. \n",
"Because the jet stream is essentially a river of air that storms flow through, they c\n"
]
}
],
"source": [
"url = \"https://www.cnn.com/2023/09/25/weather/el-nino-winter-us-climate/index.html\"\n",
"\n",
"headers_to_split_on = [\n",
" (\"h1\", \"Header 1\"),\n",
" (\"h2\", \"Header 2\"),\n",
"]\n",
"\n",
"html_splitter = HTMLHeaderTextSplitter(headers_to_split_on)\n",
"html_header_splits = html_splitter.split_text_from_url(url)\n",
"print(html_header_splits[1].page_content[:500])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,207 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c95fcd15cd52c944",
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"source": [
"# How to split by HTML sections\n",
"## Description and motivation\n",
"Similar in concept to the [HTMLHeaderTextSplitter](/docs/how_to/HTML_header_metadata_splitter), the `HTMLSectionSplitter` is a \"structure-aware\" chunker that splits text at the element level and adds metadata for each header \"relevant\" to any given chunk.\n",
"\n",
"It can return chunks element by element or combine elements with the same metadata, with the objectives of (a) keeping related text grouped (more or less) semantically and (b) preserving context-rich information encoded in document structures.\n",
"\n",
"Use `xslt_path` to provide an absolute path to transform the HTML so that it can detect sections based on provided tags. The default is to use the `converting_to_header.xslt` file in the `data_connection/document_transformers` directory. This is for converting the html to a format/layout that is easier to detect sections. For example, `span` based on their font size can be converted to header tags to be detected as a section.\n",
"\n",
"## Usage examples\n",
"### 1) How to split HTML strings:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "initial_id",
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-02T18:57:49.208965400Z",
"start_time": "2023-10-02T18:57:48.899756Z"
},
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Foo \\n Some intro text about Foo.', metadata={'Header 1': 'Foo'}),\n",
" Document(page_content='Bar main section \\n Some intro text about Bar. \\n Bar subsection 1 \\n Some text about the first subtopic of Bar. \\n Bar subsection 2 \\n Some text about the second subtopic of Bar.', metadata={'Header 2': 'Bar main section'}),\n",
" Document(page_content='Baz \\n Some text about Baz \\n \\n \\n Some concluding text about Foo', metadata={'Header 2': 'Baz'})]"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_text_splitters import HTMLSectionSplitter\n",
"\n",
"html_string = \"\"\"\n",
" <!DOCTYPE html>\n",
" <html>\n",
" <body>\n",
" <div>\n",
" <h1>Foo</h1>\n",
" <p>Some intro text about Foo.</p>\n",
" <div>\n",
" <h2>Bar main section</h2>\n",
" <p>Some intro text about Bar.</p>\n",
" <h3>Bar subsection 1</h3>\n",
" <p>Some text about the first subtopic of Bar.</p>\n",
" <h3>Bar subsection 2</h3>\n",
" <p>Some text about the second subtopic of Bar.</p>\n",
" </div>\n",
" <div>\n",
" <h2>Baz</h2>\n",
" <p>Some text about Baz</p>\n",
" </div>\n",
" <br>\n",
" <p>Some concluding text about Foo</p>\n",
" </div>\n",
" </body>\n",
" </html>\n",
"\"\"\"\n",
"\n",
"headers_to_split_on = [(\"h1\", \"Header 1\"), (\"h2\", \"Header 2\")]\n",
"\n",
"html_splitter = HTMLSectionSplitter(headers_to_split_on)\n",
"html_header_splits = html_splitter.split_text(html_string)\n",
"html_header_splits"
]
},
{
"cell_type": "markdown",
"id": "e29b4aade2a0070c",
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"source": [
"### 2) How to constrain chunk sizes:\n",
"\n",
"`HTMLSectionSplitter` can be used with other text splitters as part of a chunking pipeline. Internally, it uses the `RecursiveCharacterTextSplitter` when the section size is larger than the chunk size. It also considers the font size of the text to determine whether it is a section or not based on the determined font size threshold."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "6ada8ea093ea0475",
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-02T18:57:51.016141300Z",
"start_time": "2023-10-02T18:57:50.647495400Z"
},
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Foo \\n Some intro text about Foo.', metadata={'Header 1': 'Foo'}),\n",
" Document(page_content='Bar main section \\n Some intro text about Bar.', metadata={'Header 2': 'Bar main section'}),\n",
" Document(page_content='Bar subsection 1 \\n Some text about the first subtopic of Bar.', metadata={'Header 3': 'Bar subsection 1'}),\n",
" Document(page_content='Bar subsection 2 \\n Some text about the second subtopic of Bar.', metadata={'Header 3': 'Bar subsection 2'}),\n",
" Document(page_content='Baz \\n Some text about Baz \\n \\n \\n Some concluding text about Foo', metadata={'Header 2': 'Baz'})]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
"\n",
"html_string = \"\"\"\n",
" <!DOCTYPE html>\n",
" <html>\n",
" <body>\n",
" <div>\n",
" <h1>Foo</h1>\n",
" <p>Some intro text about Foo.</p>\n",
" <div>\n",
" <h2>Bar main section</h2>\n",
" <p>Some intro text about Bar.</p>\n",
" <h3>Bar subsection 1</h3>\n",
" <p>Some text about the first subtopic of Bar.</p>\n",
" <h3>Bar subsection 2</h3>\n",
" <p>Some text about the second subtopic of Bar.</p>\n",
" </div>\n",
" <div>\n",
" <h2>Baz</h2>\n",
" <p>Some text about Baz</p>\n",
" </div>\n",
" <br>\n",
" <p>Some concluding text about Foo</p>\n",
" </div>\n",
" </body>\n",
" </html>\n",
"\"\"\"\n",
"\n",
"headers_to_split_on = [\n",
" (\"h1\", \"Header 1\"),\n",
" (\"h2\", \"Header 2\"),\n",
" (\"h3\", \"Header 3\"),\n",
" (\"h4\", \"Header 4\"),\n",
"]\n",
"\n",
"html_splitter = HTMLSectionSplitter(headers_to_split_on)\n",
"\n",
"html_header_splits = html_splitter.split_text(html_string)\n",
"\n",
"chunk_size = 500\n",
"chunk_overlap = 30\n",
"text_splitter = RecursiveCharacterTextSplitter(\n",
" chunk_size=chunk_size, chunk_overlap=chunk_overlap\n",
")\n",
"\n",
"# Split\n",
"splits = text_splitter.split_documents(html_header_splits)\n",
"splits"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,230 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8cc82b48",
"metadata": {},
"source": [
"# How to use the MultiQueryRetriever\n",
"\n",
"Distance-based vector database retrieval embeds (represents) queries in high-dimensional space and finds similar embedded documents based on \"distance\". But, retrieval may produce different results with subtle changes in query wording or if the embeddings do not capture the semantics of the data well. Prompt engineering / tuning is sometimes done to manually address these problems, but can be tedious.\n",
"\n",
"The `MultiQueryRetriever` automates the process of prompt tuning by using an LLM to generate multiple queries from different perspectives for a given user input query. For each query, it retrieves a set of relevant documents and takes the unique union across all queries to get a larger set of potentially relevant documents. By generating multiple perspectives on the same question, the `MultiQueryRetriever` might be able to overcome some of the limitations of the distance-based retrieval and get a richer set of results."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "994d6c74",
"metadata": {},
"outputs": [],
"source": [
"# Build a sample vectorDB\n",
"from langchain_chroma import Chroma\n",
"from langchain_community.document_loaders import WebBaseLoader\n",
"from langchain_openai import OpenAIEmbeddings\n",
"from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
"\n",
"# Load blog post\n",
"loader = WebBaseLoader(\"https://lilianweng.github.io/posts/2023-06-23-agent/\")\n",
"data = loader.load()\n",
"\n",
"# Split\n",
"text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)\n",
"splits = text_splitter.split_documents(data)\n",
"\n",
"# VectorDB\n",
"embedding = OpenAIEmbeddings()\n",
"vectordb = Chroma.from_documents(documents=splits, embedding=embedding)"
]
},
{
"cell_type": "markdown",
"id": "cca8f56c",
"metadata": {},
"source": [
"#### Simple usage\n",
"\n",
"Specify the LLM to use for query generation, and the retriever will do the rest."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "edbca101",
"metadata": {},
"outputs": [],
"source": [
"from langchain.retrievers.multi_query import MultiQueryRetriever\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"question = \"What are the approaches to Task Decomposition?\"\n",
"llm = ChatOpenAI(temperature=0)\n",
"retriever_from_llm = MultiQueryRetriever.from_llm(\n",
" retriever=vectordb.as_retriever(), llm=llm\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "9e6d3b69",
"metadata": {},
"outputs": [],
"source": [
"# Set logging for the queries\n",
"import logging\n",
"\n",
"logging.basicConfig()\n",
"logging.getLogger(\"langchain.retrievers.multi_query\").setLevel(logging.INFO)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e5203612",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"INFO:langchain.retrievers.multi_query:Generated queries: ['1. How can Task Decomposition be approached?', '2. What are the different methods for Task Decomposition?', '3. What are the various approaches to decomposing tasks?']\n"
]
},
{
"data": {
"text/plain": [
"5"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"unique_docs = retriever_from_llm.get_relevant_documents(query=question)\n",
"len(unique_docs)"
]
},
{
"cell_type": "markdown",
"id": "c54a282f",
"metadata": {},
"source": [
"#### Supplying your own prompt\n",
"\n",
"You can also supply a prompt along with an output parser to split the results into a list of queries."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "d9afb0ca",
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"\n",
"from langchain.chains import LLMChain\n",
"from langchain.output_parsers import PydanticOutputParser\n",
"from langchain_core.prompts import PromptTemplate\n",
"from pydantic import BaseModel, Field\n",
"\n",
"\n",
"# Output parser will split the LLM result into a list of queries\n",
"class LineList(BaseModel):\n",
" # \"lines\" is the key (attribute name) of the parsed output\n",
" lines: List[str] = Field(description=\"Lines of text\")\n",
"\n",
"\n",
"class LineListOutputParser(PydanticOutputParser):\n",
" def __init__(self) -> None:\n",
" super().__init__(pydantic_object=LineList)\n",
"\n",
" def parse(self, text: str) -> LineList:\n",
" lines = text.strip().split(\"\\n\")\n",
" return LineList(lines=lines)\n",
"\n",
"\n",
"output_parser = LineListOutputParser()\n",
"\n",
"QUERY_PROMPT = PromptTemplate(\n",
" input_variables=[\"question\"],\n",
" template=\"\"\"You are an AI language model assistant. Your task is to generate five \n",
" different versions of the given user question to retrieve relevant documents from a vector \n",
" database. By generating multiple perspectives on the user question, your goal is to help\n",
" the user overcome some of the limitations of the distance-based similarity search. \n",
" Provide these alternative questions separated by newlines.\n",
" Original question: {question}\"\"\",\n",
")\n",
"llm = ChatOpenAI(temperature=0)\n",
"\n",
"# Chain\n",
"llm_chain = LLMChain(llm=llm, prompt=QUERY_PROMPT, output_parser=output_parser)\n",
"\n",
"# Other inputs\n",
"question = \"What are the approaches to Task Decomposition?\""
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "6660d7ee",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"INFO:langchain.retrievers.multi_query:Generated queries: [\"1. What is the course's perspective on regression?\", '2. Can you provide information on regression as discussed in the course?', '3. How does the course cover the topic of regression?', \"4. What are the course's teachings on regression?\", '5. In relation to the course, what is mentioned about regression?']\n"
]
},
{
"data": {
"text/plain": [
"11"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Run\n",
"retriever = MultiQueryRetriever(\n",
" retriever=vectordb.as_retriever(), llm_chain=llm_chain, parser_key=\"lines\"\n",
") # \"lines\" is the key (attribute name) of the parsed output\n",
"\n",
"# Results\n",
"unique_docs = retriever.get_relevant_documents(\n",
" query=\"What does the course say about regression?\"\n",
")\n",
"len(unique_docs)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,849 @@
{
"cells": [
{
"cell_type": "raw",
"id": "17546ebb",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 4\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "f4c03f40-1328-412d-8a48-1db0cd481b77",
"metadata": {},
"source": [
"# Build an Agent\n",
"\n",
"By themselves, language models can't take actions - they just output text.\n",
"A big use case for LangChain is creating **agents**.\n",
"Agents are systems that use an LLM as a reasoning enginer to determine which actions to take and what the inputs to those actions should be.\n",
"The results of those actions can then be fed back into the agent and it determine whether more actions are needed, or whether it is okay to finish.\n",
"\n",
"In this tutorial we will build an agent that can interact with multiple different tools: one being a local database, the other being a search engine. You will be able to ask this agent questions, watch it call tools, and have conversations with it.\n",
"\n",
":::{.callout-important}\n",
"This section will cover building with LangChain Agents. LangChain Agents are fine for getting started, but past a certain point you will likely want flexibility and control that they do not offer. For working with more advanced agents, we'd reccommend checking out [LangGraph](/docs/concepts/#langgraph)\n",
":::\n",
"\n",
"## Concepts\n",
"\n",
"Concepts we will cover are:\n",
"- Using [language models](/docs/concepts/#chat-models), in particular their tool calling ability\n",
"- Creating a [Retriever](/docs/concepts/#retrievers) to expose specific information to our agent\n",
"- Using a Search [Tool](/docs/concepts/#tools) to look up things online\n",
"- [`Chat History`](/docs/concepts/#chat-history), which allows a chatbot to \"remember\" past interactions and take them into account when responding to followup questions. \n",
"- Debugging and tracing your application using [LangSmith](/docs/concepts/#langsmith)\n",
"\n",
"## Setup\n",
"\n",
"### Jupyter Notebook\n",
"\n",
"This guide (and most of the other guides in the documentation) uses [Jupyter notebooks](https://jupyter.org/) and assumes the reader is as well. Jupyter notebooks are perfect for learning how to work with LLM systems because oftentimes things can go wrong (unexpected output, API down, etc) and going through guides in an interactive environment is a great way to better understand them.\n",
"\n",
"This and other tutorials are perhaps most conveniently run in a Jupyter notebook. See [here](https://jupyter.org/install) for instructions on how to install.\n",
"\n",
"### Installation\n",
"\n",
"To install LangChain run:\n",
"\n",
"```{=mdx}\n",
"import Tabs from '@theme/Tabs';\n",
"import TabItem from '@theme/TabItem';\n",
"import CodeBlock from \"@theme/CodeBlock\";\n",
"\n",
"<Tabs>\n",
" <TabItem value=\"pip\" label=\"Pip\" default>\n",
" <CodeBlock language=\"bash\">pip install langchain</CodeBlock>\n",
" </TabItem>\n",
" <TabItem value=\"conda\" label=\"Conda\">\n",
" <CodeBlock language=\"bash\">conda install langchain -c conda-forge</CodeBlock>\n",
" </TabItem>\n",
"</Tabs>\n",
"\n",
"```\n",
"\n",
"\n",
"For more details, see our [Installation guide](/docs/installation).\n",
"\n",
"### LangSmith\n",
"\n",
"Many of the applications you build with LangChain will contain multiple steps with multiple invocations of LLM calls.\n",
"As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your chain or agent.\n",
"The best way to do this is with [LangSmith](https://smith.langchain.com).\n",
"\n",
"After you sign up at the link above, make sure to set your environment variables to start logging traces:\n",
"\n",
"```shell\n",
"export LANGCHAIN_TRACING_V2=\"true\"\n",
"export LANGCHAIN_API_KEY=\"...\"\n",
"```\n",
"\n",
"Or, if in a notebook, you can set them with:\n",
"\n",
"```python\n",
"import getpass\n",
"import os\n",
"\n",
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n",
"```\n"
]
},
{
"cell_type": "markdown",
"id": "c335d1bf",
"metadata": {},
"source": [
"## Define tools\n",
"\n",
"We first need to create the tools we want to use. We will use two tools: [Tavily](/docs/integrations/tools/tavily_search) (to search online) and then a retriever over a local index we will create\n",
"\n",
"### [Tavily](/docs/integrations/tools/tavily_search)\n",
"\n",
"We have a built-in tool in LangChain to easily use Tavily search engine as tool.\n",
"Note that this requires an API key - they have a free tier, but if you don't have one or don't want to create one, you can always ignore this step.\n",
"\n",
"Once you create your API key, you will need to export that as:\n",
"\n",
"```bash\n",
"export TAVILY_API_KEY=\"...\"\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "482ce13d",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.tools.tavily_search import TavilySearchResults"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "9cc86c0b",
"metadata": {},
"outputs": [],
"source": [
"search = TavilySearchResults(max_results=2)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e593bbf6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'url': 'https://www.weatherapi.com/',\n",
" 'content': \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1714000492, 'localtime': '2024-04-24 16:14'}, 'current': {'last_updated_epoch': 1713999600, 'last_updated': '2024-04-24 16:00', 'temp_c': 15.6, 'temp_f': 60.1, 'is_day': 1, 'condition': {'text': 'Overcast', 'icon': '//cdn.weatherapi.com/weather/64x64/day/122.png', 'code': 1009}, 'wind_mph': 10.5, 'wind_kph': 16.9, 'wind_degree': 330, 'wind_dir': 'NNW', 'pressure_mb': 1018.0, 'pressure_in': 30.06, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 72, 'cloud': 100, 'feelslike_c': 15.6, 'feelslike_f': 60.1, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 14.8, 'gust_kph': 23.8}}\"},\n",
" {'url': 'https://www.weathertab.com/en/c/e/04/united-states/california/san-francisco/',\n",
" 'content': 'San Francisco Weather Forecast for Apr 2024 - Risk of Rain Graph. Rain Risk Graph: Monthly Overview. Bar heights indicate rain risk percentages. Yellow bars mark low-risk days, while black and grey bars signal higher risks. Grey-yellow bars act as buffers, advising to keep at least one day clear from the riskier grey and black days, guiding ...'}]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"search.invoke(\"what is the weather in SF\")"
]
},
{
"cell_type": "markdown",
"id": "e8097977",
"metadata": {},
"source": [
"### Retriever\n",
"\n",
"We will also create a retriever over some data of our own. For a deeper explanation of each step here, see [this tutorial](/docs/tutorials/rag)."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "9c9ce713",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import WebBaseLoader\n",
"from langchain_community.vectorstores import FAISS\n",
"from langchain_openai import OpenAIEmbeddings\n",
"from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
"\n",
"loader = WebBaseLoader(\"https://docs.smith.langchain.com/overview\")\n",
"docs = loader.load()\n",
"documents = RecursiveCharacterTextSplitter(\n",
" chunk_size=1000, chunk_overlap=200\n",
").split_documents(docs)\n",
"vector = FAISS.from_documents(documents, OpenAIEmbeddings())\n",
"retriever = vector.as_retriever()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "dae53ec6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Document(page_content='# The data to predict and grade over evaluators=[exact_match], # The evaluators to score the results experiment_prefix=\"sample-experiment\", # The name of the experiment metadata={ \"version\": \"1.0.0\", \"revision_id\": \"beta\" },)import { Client, Run, Example } from \\'langsmith\\';import { runOnDataset } from \\'langchain/smith\\';import { EvaluationResult } from \\'langsmith/evaluation\\';const client = new Client();// Define dataset: these are your test casesconst datasetName = \"Sample Dataset\";const dataset = await client.createDataset(datasetName, { description: \"A sample dataset in LangSmith.\"});await client.createExamples({ inputs: [ { postfix: \"to LangSmith\" }, { postfix: \"to Evaluations in LangSmith\" }, ], outputs: [ { output: \"Welcome to LangSmith\" }, { output: \"Welcome to Evaluations in LangSmith\" }, ], datasetId: dataset.id,});// Define your evaluatorconst exactMatch = async ({ run, example }: { run: Run; example?:', metadata={'source': 'https://docs.smith.langchain.com/overview', 'title': 'Getting started with LangSmith | \\uf8ffü¶úÔ∏è\\uf8ffüõ†Ô∏è LangSmith', 'description': 'Introduction', 'language': 'en'})"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retriever.invoke(\"how to upload a dataset\")[0]"
]
},
{
"cell_type": "markdown",
"id": "04aeca39",
"metadata": {},
"source": [
"Now that we have populated our index that we will do doing retrieval over, we can easily turn it into a tool (the format needed for an agent to properly use it)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "117594b5",
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools.retriever import create_retriever_tool"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "7280b031",
"metadata": {},
"outputs": [],
"source": [
"retriever_tool = create_retriever_tool(\n",
" retriever,\n",
" \"langsmith_search\",\n",
" \"Search for information about LangSmith. For any questions about LangSmith, you must use this tool!\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c3b47c1d",
"metadata": {},
"source": [
"### Tools\n",
"\n",
"Now that we have created both, we can create a list of tools that we will use downstream."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "b8e8e710",
"metadata": {},
"outputs": [],
"source": [
"tools = [search, retriever_tool]"
]
},
{
"cell_type": "markdown",
"id": "e00068b0",
"metadata": {},
"source": [
"## Using Language Models\n",
"\n",
"Next, let's learn how to use a language model by to call tools. LangChain supports many different language models that you can use interchangably - select the one you want to use below!\n",
"\n",
"```{=mdx}\n",
"import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
"\n",
"<ChatModelTabs openaiParams={`model=\"gpt-4\"`} />\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "69185491",
"metadata": {},
"outputs": [],
"source": [
"# | output: false\n",
"# | echo: false\n",
"\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"model = ChatOpenAI(model=\"gpt-4\")"
]
},
{
"cell_type": "markdown",
"id": "642ed8bf",
"metadata": {},
"source": [
"You can call the language model by passing in a list of messages. By default, the response is a `content` string."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "c96c960b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Hello! How can I assist you today?'"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import HumanMessage\n",
"\n",
"response = model.invoke([HumanMessage(content=\"hi!\")])\n",
"response.content"
]
},
{
"cell_type": "markdown",
"id": "47bf8210",
"metadata": {},
"source": [
"We can now see what it is like to enable this model to do tool calling. In order to enable that we use `.bind_tools` to give the language model knowledge of these tools"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "ba692a74",
"metadata": {},
"outputs": [],
"source": [
"model_with_tools = model.bind_tools(tools)"
]
},
{
"cell_type": "markdown",
"id": "fd920b69",
"metadata": {},
"source": [
"We can now call the model. Let's first call it with a normal message, and see how it responds. We can look at both the `content` field as well as the `tool_calls` field."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "b6a7e925",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ContentString: Hello! How can I assist you today?\n",
"ToolCalls: []\n"
]
}
],
"source": [
"response = model_with_tools.invoke([HumanMessage(content=\"Hi!\")])\n",
"\n",
"print(f\"ContentString: {response.content}\")\n",
"print(f\"ToolCalls: {response.tool_calls}\")"
]
},
{
"cell_type": "markdown",
"id": "e8c81e76",
"metadata": {},
"source": [
"Now, let's try calling it with some input that would expect a tool to be called."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "688b465d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ContentString: \n",
"ToolCalls: [{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_4HteVahXkRAkWjp6dGXryKZX'}]\n"
]
}
],
"source": [
"response = model_with_tools.invoke([HumanMessage(content=\"What's the weather in SF?\")])\n",
"\n",
"print(f\"ContentString: {response.content}\")\n",
"print(f\"ToolCalls: {response.tool_calls}\")"
]
},
{
"cell_type": "markdown",
"id": "83c4bcd3",
"metadata": {},
"source": [
"We can see that there's now no content, but there is a tool call! It wants us to call the Tavily Search tool.\n",
"\n",
"This isn't calling that tool yet - it's just telling us to. In order to actually calll it, we'll want to create our agent."
]
},
{
"cell_type": "markdown",
"id": "40ccec80",
"metadata": {},
"source": [
"## Create the agent\n",
"\n",
"Now that we have defined the tools and the LLM, we can create the agent. We will be using a tool calling agent - for more information on this type of agent, as well as other options, see [this guide](/docs/concepts/#agent_types/).\n",
"\n",
"We can first choose the prompt we want to use to guide the agent.\n",
"\n",
"If you want to see the contents of this prompt and have access to LangSmith, you can go to:\n",
"\n",
"[https://smith.langchain.com/hub/hwchase17/openai-functions-agent](https://smith.langchain.com/hub/hwchase17/openai-functions-agent)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "af83d3e3",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')),\n",
" MessagesPlaceholder(variable_name='chat_history', optional=True),\n",
" HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')),\n",
" MessagesPlaceholder(variable_name='agent_scratchpad')]"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain import hub\n",
"\n",
"# Get the prompt to use - you can modify this!\n",
"prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
"prompt.messages"
]
},
{
"cell_type": "markdown",
"id": "f8014c9d",
"metadata": {},
"source": [
"Now, we can initalize the agent with the LLM, the prompt, and the tools. The agent is responsible for taking in input and deciding what actions to take. Crucially, the Agent does not execute those actions - that is done by the AgentExecutor (next step). For more information about how to think about these components, see our [conceptual guide](/docs/concepts/#agents).\n",
"\n",
"Note that we are passing in the `model`, not `model_with_tools`. That is because `create_tool_calling_agent` will call `.bind_tools` for us under the hood."
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "89cf72b4-6046-4b47-8f27-5522d8cb8036",
"metadata": {},
"outputs": [],
"source": [
"from langchain.agents import create_tool_calling_agent\n",
"\n",
"agent = create_tool_calling_agent(model, tools, prompt)"
]
},
{
"cell_type": "markdown",
"id": "1a58c9f8",
"metadata": {},
"source": [
"Finally, we combine the agent (the brains) with the tools inside the AgentExecutor (which will repeatedly call the agent and execute tools)."
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "ce33904a",
"metadata": {},
"outputs": [],
"source": [
"from langchain.agents import AgentExecutor\n",
"\n",
"agent_executor = AgentExecutor(agent=agent, tools=tools)"
]
},
{
"cell_type": "markdown",
"id": "e4df0e06",
"metadata": {},
"source": [
"## Run the agent\n",
"\n",
"We can now run the agent on a few queries! Note that for now, these are all **stateless** queries (it won't remember previous interactions).\n",
"\n",
"First up, let's how it responds when there's no need to call a tool:"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "114ba50d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'hi!', 'output': 'Hello! How can I assist you today?'}"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_executor.invoke({\"input\": \"hi!\"})"
]
},
{
"cell_type": "markdown",
"id": "71493a42",
"metadata": {},
"source": [
"In order to see exactly what is happening under the hood (and to make sure it's not calling a tool) we can take a look at the [LangSmith trace](https://smith.langchain.com/public/8441812b-94ce-4832-93ec-e1114214553a/r)\n",
"\n",
"Let's now try it out on an example where it should be invoking the retriever"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "3fa4780a",
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'how can langsmith help with testing?',\n",
" 'output': 'LangSmith is a platform that aids in building production-grade Language Learning Model (LLM) applications. It can assist with testing in several ways:\\n\\n1. **Monitoring and Evaluation**: LangSmith allows close monitoring and evaluation of your application. This helps you to ensure the quality of your application and deploy it with confidence.\\n\\n2. **Tracing**: LangSmith has tracing capabilities that can be beneficial for debugging and understanding the behavior of your application.\\n\\n3. **Evaluation Capabilities**: LangSmith has built-in tools for evaluating the performance of your LLM. \\n\\n4. **Prompt Hub**: This is a prompt management tool built into LangSmith that can help in testing different prompts and their responses.\\n\\nPlease note that to use LangSmith, you would need to install it and create an API key. The platform offers Python and Typescript SDKs for utilization. It works independently and does not require the use of LangChain.'}"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_executor.invoke({\"input\": \"how can langsmith help with testing?\"})"
]
},
{
"cell_type": "markdown",
"id": "f2d94242",
"metadata": {},
"source": [
"Let's take a look at the [LangSmith trace](https://smith.langchain.com/public/762153f6-14d4-4c98-8659-82650f860c62/r) to make sure it's actually calling that.\n",
"\n",
"Now let's try one where it needs to call the search tool:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "77c2f769",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'whats the weather in sf?',\n",
" 'output': 'The current weather in San Francisco is partly cloudy with a temperature of 16.1°C (61.0°F). The wind is coming from the WNW at a speed of 10.5 mph. The humidity is at 67%. [source](https://www.weatherapi.com/)'}"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_executor.invoke({\"input\": \"whats the weather in sf?\"})"
]
},
{
"cell_type": "markdown",
"id": "c174f838",
"metadata": {},
"source": [
"We can check out the [LangSmith trace](https://smith.langchain.com/public/36df5b1a-9a0b-4185-bae2-964e1d53c665/r) to make sure it's calling the search tool effectively."
]
},
{
"cell_type": "markdown",
"id": "022cbc8a",
"metadata": {},
"source": [
"## Adding in memory\n",
"\n",
"As mentioned earlier, this agent is stateless. This means it does not remember previous interactions. To give it memory we need to pass in previous `chat_history`. Note: it needs to be called `chat_history` because of the prompt we are using. If we use a different prompt, we could change the variable name"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "c4073e35",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'hi! my name is bob',\n",
" 'chat_history': [],\n",
" 'output': 'Hello Bob! How can I assist you today?'}"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Here we pass in an empty list of messages for chat_history because it is the first message in the chat\n",
"agent_executor.invoke({\"input\": \"hi! my name is bob\", \"chat_history\": []})"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "9dc5ed68",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import AIMessage, HumanMessage"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "550e0c6e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'chat_history': [HumanMessage(content='hi! my name is bob'),\n",
" AIMessage(content='Hello Bob! How can I assist you today?')],\n",
" 'input': \"what's my name?\",\n",
" 'output': 'Your name is Bob. How can I assist you further?'}"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_executor.invoke(\n",
" {\n",
" \"chat_history\": [\n",
" HumanMessage(content=\"hi! my name is bob\"),\n",
" AIMessage(content=\"Hello Bob! How can I assist you today?\"),\n",
" ],\n",
" \"input\": \"what's my name?\",\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"id": "07b3bcf2",
"metadata": {},
"source": [
"If we want to keep track of these messages automatically, we can wrap this in a RunnableWithMessageHistory. For more information on how to use this, see [this guide](/docs/how_to/message_history). "
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "8edd96e6",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.chat_message_histories import ChatMessageHistory\n",
"from langchain_core.chat_history import BaseChatMessageHistory\n",
"from langchain_core.runnables.history import RunnableWithMessageHistory\n",
"\n",
"store = {}\n",
"\n",
"\n",
"def get_session_history(session_id: str) -> BaseChatMessageHistory:\n",
" if session_id not in store:\n",
" store[session_id] = ChatMessageHistory()\n",
" return store[session_id]"
]
},
{
"cell_type": "markdown",
"id": "c450d6a5",
"metadata": {},
"source": [
"Because we have multiple inputs, we need to specify two things:\n",
"\n",
"- `input_messages_key`: The input key to use to add to the conversation history.\n",
"- `history_messages_key`: The key to add the loaded messages into."
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "828d1e95",
"metadata": {},
"outputs": [],
"source": [
"agent_with_chat_history = RunnableWithMessageHistory(\n",
" agent_executor,\n",
" get_session_history,\n",
" input_messages_key=\"input\",\n",
" history_messages_key=\"chat_history\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 38,
"id": "1f5932b6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': \"hi! I'm bob\",\n",
" 'chat_history': [],\n",
" 'output': 'Hello Bob! How can I assist you today?'}"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_with_chat_history.invoke(\n",
" {\"input\": \"hi! I'm bob\"},\n",
" config={\"configurable\": {\"session_id\": \"<foo>\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "ae627966",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': \"what's my name?\",\n",
" 'chat_history': [HumanMessage(content=\"hi! I'm bob\"),\n",
" AIMessage(content='Hello Bob! How can I assist you today?')],\n",
" 'output': 'Your name is Bob.'}"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_with_chat_history.invoke(\n",
" {\"input\": \"what's my name?\"},\n",
" config={\"configurable\": {\"session_id\": \"<foo>\"}},\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6de2798e",
"metadata": {},
"source": [
"Example LangSmith trace: https://smith.langchain.com/public/98c8d162-60ae-4493-aa9f-992d87bd0429/r"
]
},
{
"cell_type": "markdown",
"id": "c029798f",
"metadata": {},
"source": [
"## Conclusion\n",
"\n",
"That's a wrap! In this quick start we covered how to create a simple agent. Agents are a complex topic, and there's lot to learn! \n",
"\n",
":::{.callout-important}\n",
"This section covered building with LangChain Agents. LangChain Agents are fine for getting started, but past a certain point you will likely want flexibility and control that they do not offer. For working with more advanced agents, we'd reccommend checking out [LangGraph](/docs/concepts/#langgraph)\n",
":::\n",
"\n",
"If you want to continue using LangChain agents, some good advanced guides are:\n",
"\n",
"- [How to create a custom agent](/docs/how_to/custom_agent)\n",
"- [How to stream responses from an agent](/docs/how_to/agents_streaming)\n",
"- [How to return structured output from an agent](/docs/how_to/agent_structured)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3ec3244",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,192 @@
{
"cells": [
{
"cell_type": "raw",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 6\n",
"keywords: [RunnablePassthrough, assign, LCEL]\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to add values to a chain's state\n",
"\n",
"An alternate way of [passing data through](/docs/how_to/passthrough) steps of a chain is to leave the current values of the chain state unchanged while assigning a new value under a given key. The [`RunnablePassthrough.assign()`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.passthrough.RunnablePassthrough.html#langchain_core.runnables.passthrough.RunnablePassthrough.assign) static method takes an input value and adds the extra arguments passed to the assign function.\n",
"\n",
"This is useful in the common [LangChain Expression Language](/docs/concepts/#langchain-expression-language) pattern of additively creating a dictionary to use as input to a later step.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
"- [Chaining runnables](/docs/how_to/sequence/)\n",
"- [Calling runnables in parallel](/docs/how_to/parallel/)\n",
"- [Custom functions](/docs/how_to/functions/)\n",
"- [Passing data through](/docs/how_to/passthrough)\n",
"`} />\n",
"```\n",
"\n",
"Here's an example:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install --upgrade --quiet langchain langchain-openai\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'extra': {'num': 1, 'mult': 3}, 'modified': 2}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.runnables import RunnableParallel, RunnablePassthrough\n",
"\n",
"runnable = RunnableParallel(\n",
" extra=RunnablePassthrough.assign(mult=lambda x: x[\"num\"] * 3),\n",
" modified=lambda x: x[\"num\"] + 1,\n",
")\n",
"\n",
"runnable.invoke({\"num\": 1})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's break down what's happening here.\n",
"\n",
"- The input to the chain is `{\"num\": 1}`. This is passed into a `RunnableParallel`, which invokes the runnables it is passed in parallel with that input.\n",
"- The value under the `extra` key is invoked. `RunnablePassthrough.assign()` keeps the original keys in the input dict (`{\"num\": 1}`), and assigns a new key called `mult`. The value is `lambda x: x[\"num\"] * 3)`, which is `3`. Thus, the result is `{\"num\": 1, \"mult\": 3}`.\n",
"- `{\"num\": 1, \"mult\": 3}` is returned to the `RunnableParallel` call, and is set as the value to the key `extra`.\n",
"- At the same time, the `modified` key is called. The result is `2`, since the lambda extracts a key called `\"num\"` from its input and adds one.\n",
"\n",
"Thus, the result is `{'extra': {'num': 1, 'mult': 3}, 'modified': 2}`.\n",
"\n",
"## Streaming\n",
"\n",
"One convenient feature of this method is that it allows values to pass through as soon as they are available. To show this off, we'll use `RunnablePassthrough.assign()` to immediately return source docs in a retrieval chain:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'question': 'where did harrison work?'}\n",
"{'context': [Document(page_content='harrison worked at kensho')]}\n",
"{'output': ''}\n",
"{'output': 'H'}\n",
"{'output': 'arrison'}\n",
"{'output': ' worked'}\n",
"{'output': ' at'}\n",
"{'output': ' Kens'}\n",
"{'output': 'ho'}\n",
"{'output': '.'}\n",
"{'output': ''}\n"
]
}
],
"source": [
"from langchain_community.vectorstores import FAISS\n",
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.runnables import RunnablePassthrough\n",
"from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n",
"\n",
"vectorstore = FAISS.from_texts(\n",
" [\"harrison worked at kensho\"], embedding=OpenAIEmbeddings()\n",
")\n",
"retriever = vectorstore.as_retriever()\n",
"template = \"\"\"Answer the question based only on the following context:\n",
"{context}\n",
"\n",
"Question: {question}\n",
"\"\"\"\n",
"prompt = ChatPromptTemplate.from_template(template)\n",
"model = ChatOpenAI()\n",
"\n",
"generation_chain = prompt | model | StrOutputParser()\n",
"\n",
"retrieval_chain = {\n",
" \"context\": retriever,\n",
" \"question\": RunnablePassthrough(),\n",
"} | RunnablePassthrough.assign(output=generation_chain)\n",
"\n",
"stream = retrieval_chain.stream(\"where did harrison work?\")\n",
"\n",
"for chunk in stream:\n",
" print(chunk)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that the first chunk contains the original `\"question\"` since that is immediately available. The second chunk contains `\"context\"` since the retriever finishes second. Finally, the output from the `generation_chain` streams in chunks as soon as it is available.\n",
"\n",
"## Next steps\n",
"\n",
"Now you've learned how to pass data through your chains to help to help format the data flowing through your chains.\n",
"\n",
"To learn more, see the other how-to guides on runnables in this section."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,236 @@
{
"cells": [
{
"cell_type": "raw",
"id": "fe63ffaf",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 2\n",
"keywords: [RunnableBinding, LCEL]\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "711752cb-4f15-42a3-9838-a0c67f397771",
"metadata": {},
"source": [
"# How to attach runtime arguments to a Runnable\n",
"\n",
"Sometimes we want to invoke a [`Runnable`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html) within a [RunnableSequence](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableSequence.html) with constant arguments that are not part of the output of the preceding Runnable in the sequence, and which are not part of the user input. We can use the [`Runnable.bind()`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.bind) method to set these arguments ahead of time.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
"- [Chaining runnables](/docs/how_to/sequence/)\n",
"- [Tool calling](/docs/how_to/tool_calling/)\n",
"`} />\n",
"```\n",
"\n",
"## Binding stop sequences\n",
"\n",
"Suppose we have a simple prompt + model chain:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5dad8b5",
"metadata": {},
"outputs": [],
"source": [
"# | output: false\n",
"# | echo: false\n",
"\n",
"%pip install -qU langchain langchain_openai\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "f3fdf86d-155f-4587-b7cd-52d363970c1d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"EQUATION: x^3 + 7 = 12\n",
"\n",
"SOLUTION: \n",
"Subtract 7 from both sides:\n",
"x^3 = 5\n",
"\n",
"Take the cube root of both sides:\n",
"x = ∛5\n"
]
}
],
"source": [
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.runnables import RunnablePassthrough\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"Write out the following equation using algebraic symbols then solve it. Use the format\\n\\nEQUATION:...\\nSOLUTION:...\\n\\n\",\n",
" ),\n",
" (\"human\", \"{equation_statement}\"),\n",
" ]\n",
")\n",
"\n",
"model = ChatOpenAI(temperature=0)\n",
"\n",
"runnable = (\n",
" {\"equation_statement\": RunnablePassthrough()} | prompt | model | StrOutputParser()\n",
")\n",
"\n",
"print(runnable.invoke(\"x raised to the third plus seven equals 12\"))"
]
},
{
"cell_type": "markdown",
"id": "929c9aba-a4a0-462c-adac-2cfc2156e117",
"metadata": {},
"source": [
"and want to call the model with certain `stop` words so that we shorten the output as is useful in certain types of prompting techniques. While we can pass some arguments into the constructor, other runtime args use the `.bind()` method as follows:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "32e0484a-78c5-4570-a00b-20d597245a96",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"EQUATION: x^3 + 7 = 12\n",
"\n",
"\n"
]
}
],
"source": [
"runnable = (\n",
" {\"equation_statement\": RunnablePassthrough()}\n",
" | prompt\n",
" | model.bind(stop=\"SOLUTION\")\n",
" | StrOutputParser()\n",
")\n",
"\n",
"print(runnable.invoke(\"x raised to the third plus seven equals 12\"))"
]
},
{
"cell_type": "markdown",
"id": "f07d7528-9269-4d6f-b12e-3669592a9e03",
"metadata": {},
"source": [
"What you can bind to a Runnable will depend on the extra parameters you can pass when invoking it.\n",
"\n",
"## Attaching OpenAI tools\n",
"\n",
"Another common use-case is tool calling. While you should generally use the [`.bind_tools()`](/docs/how_to/tool_calling/) method for tool-calling models, you can also bind provider-specific args directly if you want lower level control:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2cdeeb4c-0c1f-43da-bd58-4f591d9e0671",
"metadata": {},
"outputs": [],
"source": [
"tools = [\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_current_weather\",\n",
" \"description\": \"Get the current weather in a given location\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"location\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The city and state, e.g. San Francisco, CA\",\n",
" },\n",
" \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"]},\n",
" },\n",
" \"required\": [\"location\"],\n",
" },\n",
" },\n",
" }\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "2b65beab-48bb-46ff-a5a4-ef8ac95a513c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_z0OU2CytqENVrRTI6T8DkI3u', 'function': {'arguments': '{\"location\": \"San Francisco, CA\", \"unit\": \"celsius\"}', 'name': 'get_current_weather'}, 'type': 'function'}, {'id': 'call_ft96IJBh0cMKkQWrZjNg4bsw', 'function': {'arguments': '{\"location\": \"New York, NY\", \"unit\": \"celsius\"}', 'name': 'get_current_weather'}, 'type': 'function'}, {'id': 'call_tfbtGgCLmuBuWgZLvpPwvUMH', 'function': {'arguments': '{\"location\": \"Los Angeles, CA\", \"unit\": \"celsius\"}', 'name': 'get_current_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 84, 'prompt_tokens': 85, 'total_tokens': 169}, 'model_name': 'gpt-3.5-turbo-1106', 'system_fingerprint': 'fp_77a673219d', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-d57ad5fa-b52a-4822-bc3e-74f838697e18-0', tool_calls=[{'name': 'get_current_weather', 'args': {'location': 'San Francisco, CA', 'unit': 'celsius'}, 'id': 'call_z0OU2CytqENVrRTI6T8DkI3u'}, {'name': 'get_current_weather', 'args': {'location': 'New York, NY', 'unit': 'celsius'}, 'id': 'call_ft96IJBh0cMKkQWrZjNg4bsw'}, {'name': 'get_current_weather', 'args': {'location': 'Los Angeles, CA', 'unit': 'celsius'}, 'id': 'call_tfbtGgCLmuBuWgZLvpPwvUMH'}])"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = ChatOpenAI(model=\"gpt-3.5-turbo-1106\").bind(tools=tools)\n",
"model.invoke(\"What's the weather in SF, NYC and LA?\")"
]
},
{
"cell_type": "markdown",
"id": "095001f7",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You now know how to bind runtime arguments to a Runnable.\n",
"\n",
"To learn more, see the other how-to guides on runnables in this section, including:\n",
"\n",
"- [Using configurable fields and alternatives](/docs/how_to/configure) to change parameters of a step in a chain, or even swap out entire steps, at runtime"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,269 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "bf4061ce",
"metadata": {},
"source": [
"# Caching\n",
"\n",
"Embeddings can be stored or temporarily cached to avoid needing to recompute them.\n",
"\n",
"Caching embeddings can be done using a `CacheBackedEmbeddings`. The cache backed embedder is a wrapper around an embedder that caches\n",
"embeddings in a key-value store. The text is hashed and the hash is used as the key in the cache.\n",
"\n",
"The main supported way to initialize a `CacheBackedEmbeddings` is `from_bytes_store`. It takes the following parameters:\n",
"\n",
"- underlying_embedder: The embedder to use for embedding.\n",
"- document_embedding_cache: Any [`ByteStore`](/docs/integrations/stores/) for caching document embeddings.\n",
"- batch_size: (optional, defaults to `None`) The number of documents to embed between store updates.\n",
"- namespace: (optional, defaults to `\"\"`) The namespace to use for document cache. This namespace is used to avoid collisions with other caches. For example, set it to the name of the embedding model used.\n",
"\n",
"**Attention**:\n",
"\n",
"- Be sure to set the `namespace` parameter to avoid collisions of the same text embedded using different embeddings models.\n",
"- Currently `CacheBackedEmbeddings` does not cache embedding created with `embed_query()` `aembed_query()` methods."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "a463c3c2-749b-40d1-a433-84f68a1cd1c7",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain.embeddings import CacheBackedEmbeddings"
]
},
{
"cell_type": "markdown",
"id": "9ddf07dd-3e72-41de-99d4-78e9521e272f",
"metadata": {},
"source": [
"## Using with a Vector Store\n",
"\n",
"First, let's see an example that uses the local file system for storing embeddings and uses FAISS vector store for retrieval."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50183825",
"metadata": {},
"outputs": [],
"source": [
"%pip install --upgrade --quiet langchain-openai faiss-cpu"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "9e4314d8-88ef-4f52-81ae-0be771168bb6",
"metadata": {},
"outputs": [],
"source": [
"from langchain.storage import LocalFileStore\n",
"from langchain_community.document_loaders import TextLoader\n",
"from langchain_community.vectorstores import FAISS\n",
"from langchain_openai import OpenAIEmbeddings\n",
"from langchain_text_splitters import CharacterTextSplitter\n",
"\n",
"underlying_embeddings = OpenAIEmbeddings()\n",
"\n",
"store = LocalFileStore(\"./cache/\")\n",
"\n",
"cached_embedder = CacheBackedEmbeddings.from_bytes_store(\n",
" underlying_embeddings, store, namespace=underlying_embeddings.model\n",
")"
]
},
{
"cell_type": "markdown",
"id": "f8cdf33c-321d-4d2c-b76b-d6f5f8b42a92",
"metadata": {},
"source": [
"The cache is empty prior to embedding:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "f9ad627f-ced2-4277-b336-2434f22f2c8a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(store.yield_keys())"
]
},
{
"cell_type": "markdown",
"id": "a4effe04-b40f-42f8-a449-72fe6991cf20",
"metadata": {},
"source": [
"Load the document, split it into chunks, embed each chunk and load it into the vector store."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "cf958ac2-e60e-4668-b32c-8bb2d78b3c61",
"metadata": {},
"outputs": [],
"source": [
"raw_documents = TextLoader(\"../../state_of_the_union.txt\").load()\n",
"text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n",
"documents = text_splitter.split_documents(raw_documents)"
]
},
{
"cell_type": "markdown",
"id": "f526444b-93f8-423f-b6d1-dab539450921",
"metadata": {},
"source": [
"Create the vector store:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3a1d7bb8-3b72-4bb5-9013-cf7729caca61",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 218 ms, sys: 29.7 ms, total: 248 ms\n",
"Wall time: 1.02 s\n"
]
}
],
"source": [
"%%time\n",
"db = FAISS.from_documents(documents, cached_embedder)"
]
},
{
"cell_type": "markdown",
"id": "64fc53f5-d559-467f-bf62-5daef32ffbc0",
"metadata": {},
"source": [
"If we try to create the vector store again, it'll be much faster since it does not need to re-compute any embeddings."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "714cb2e2-77ba-41a8-bb83-84e75342af2d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 15.7 ms, sys: 2.22 ms, total: 18 ms\n",
"Wall time: 17.2 ms\n"
]
}
],
"source": [
"%%time\n",
"db2 = FAISS.from_documents(documents, cached_embedder)"
]
},
{
"cell_type": "markdown",
"id": "1acc76b9-9c70-4160-b593-5f932c75e2b6",
"metadata": {},
"source": [
"And here are some of the embeddings that got created:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "f2ca32dd-3712-4093-942b-4122f3dc8a8e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['text-embedding-ada-00217a6727d-8916-54eb-b196-ec9c9d6ca472',\n",
" 'text-embedding-ada-0025fc0d904-bd80-52da-95c9-441015bfb438',\n",
" 'text-embedding-ada-002e4ad20ef-dfaa-5916-9459-f90c6d8e8159',\n",
" 'text-embedding-ada-002ed199159-c1cd-5597-9757-f80498e8f17b',\n",
" 'text-embedding-ada-0021297d37a-2bc1-5e19-bf13-6c950f075062']"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(store.yield_keys())[:5]"
]
},
{
"cell_type": "markdown",
"id": "c1a7fafd",
"metadata": {},
"source": [
"# Swapping the `ByteStore`\n",
"\n",
"In order to use a different `ByteStore`, just use it when creating your `CacheBackedEmbeddings`. Below, we create an equivalent cached embeddings object, except using the non-persistent `InMemoryByteStore` instead:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "336a0538",
"metadata": {},
"outputs": [],
"source": [
"from langchain.embeddings import CacheBackedEmbeddings\n",
"from langchain.storage import InMemoryByteStore\n",
"\n",
"store = InMemoryByteStore()\n",
"\n",
"cached_embedder = CacheBackedEmbeddings.from_bytes_store(\n",
" underlying_embeddings, store, namespace=underlying_embeddings.model\n",
")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c3ee8d00",
"metadata": {},
"source": [
"# How to split by character\n",
"\n",
"This is the simplest method. This splits based on a given character sequence, which defaults to `\"\\n\\n\"`. Chunk length is measured by number of characters.\n",
"\n",
"1. How the text is split: by single character separator.\n",
"2. How the chunk size is measured: by number of characters.\n",
"\n",
"To obtain the string content directly, use `.split_text`.\n",
"\n",
"To create LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html) objects (e.g., for use in downstream tasks), use `.create_documents`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf8698ce-44b2-4944-b9a9-254344b537af",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain-text-splitters"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "313fb032",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \\n\\nLast year COVID-19 kept us apart. This year we are finally together again. \\n\\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \\n\\nWith a duty to one another to the American people to the Constitution. \\n\\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \\n\\nSix days ago, Russias Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \\n\\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \\n\\nHe met the Ukrainian people. \\n\\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.'\n"
]
}
],
"source": [
"from langchain_text_splitters import CharacterTextSplitter\n",
"\n",
"# Load an example document\n",
"with open(\"../../../docs/modules/state_of_the_union.txt\") as f:\n",
" state_of_the_union = f.read()\n",
"\n",
"text_splitter = CharacterTextSplitter(\n",
" separator=\"\\n\\n\",\n",
" chunk_size=1000,\n",
" chunk_overlap=200,\n",
" length_function=len,\n",
" is_separator_regex=False,\n",
")\n",
"texts = text_splitter.create_documents([state_of_the_union])\n",
"print(texts[0])"
]
},
{
"cell_type": "markdown",
"id": "dadcb9d6",
"metadata": {},
"source": [
"Use `.create_documents` to propagate metadata associated with each document to the output chunks:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "1affda60",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \\n\\nLast year COVID-19 kept us apart. This year we are finally together again. \\n\\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \\n\\nWith a duty to one another to the American people to the Constitution. \\n\\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \\n\\nSix days ago, Russias Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \\n\\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \\n\\nHe met the Ukrainian people. \\n\\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.' metadata={'document': 1}\n"
]
}
],
"source": [
"metadatas = [{\"document\": 1}, {\"document\": 2}]\n",
"documents = text_splitter.create_documents(\n",
" [state_of_the_union, state_of_the_union], metadatas=metadatas\n",
")\n",
"print(documents[0])"
]
},
{
"cell_type": "markdown",
"id": "ee080e12-6f44-4311-b1ef-302520a41d66",
"metadata": {},
"source": [
"Use `.split_text` to obtain the string content directly:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "2a830a9f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \\n\\nLast year COVID-19 kept us apart. This year we are finally together again. \\n\\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \\n\\nWith a duty to one another to the American people to the Constitution. \\n\\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \\n\\nSix days ago, Russias Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \\n\\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \\n\\nHe met the Ukrainian people. \\n\\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"text_splitter.split_text(state_of_the_union)[0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a9a3b9cd",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,275 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "dcf87b32",
"metadata": {},
"source": [
"# How to cache chat model responses\n",
"\n",
"LangChain provides an optional caching layer for chat models. This is useful for two main reasons:\n",
"\n",
"- It can save you money by reducing the number of API calls you make to the LLM provider, if you're often requesting the same completion multiple times. This is especially useful during app development.\n",
"- It can speed up your application by reducing the number of API calls you make to the LLM provider.\n",
"\n",
"This guide will walk you through how to enable this in your apps.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Chat models](/docs/concepts/#chat-models)\n",
"- [LLMs](/docs/concepts/#llms)\n",
"`} />\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "289b31de",
"metadata": {},
"source": [
"```{=mdx}\n",
"import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
"\n",
"<ChatModelTabs customVarName=\"llm\" />\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "c6641f37",
"metadata": {},
"outputs": [],
"source": [
"# | output: false\n",
"# | echo: false\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass()\n",
"\n",
"llm = ChatOpenAI()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "5472a032",
"metadata": {},
"outputs": [],
"source": [
"# <!-- ruff: noqa: F821 -->\n",
"from langchain.globals import set_llm_cache"
]
},
{
"cell_type": "markdown",
"id": "357b89a8",
"metadata": {},
"source": [
"## In Memory Cache\n",
"\n",
"This is an ephemeral cache that stores model calls in memory. It will be wiped when your environment restarts, and is not shared across processes."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "113e719a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 645 ms, sys: 214 ms, total: 859 ms\n",
"Wall time: 829 ms\n"
]
},
{
"data": {
"text/plain": [
"AIMessage(content=\"Why don't scientists trust atoms?\\n\\nBecause they make up everything!\", response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 11, 'total_tokens': 24}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-b6836bdd-8c30-436b-828f-0ac5fc9ab50e-0')"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"from langchain.cache import InMemoryCache\n",
"\n",
"set_llm_cache(InMemoryCache())\n",
"\n",
"# The first time, it is not yet in cache, so it should take longer\n",
"llm.invoke(\"Tell me a joke\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a2121434",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 822 µs, sys: 288 µs, total: 1.11 ms\n",
"Wall time: 1.06 ms\n"
]
},
{
"data": {
"text/plain": [
"AIMessage(content=\"Why don't scientists trust atoms?\\n\\nBecause they make up everything!\", response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 11, 'total_tokens': 24}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-b6836bdd-8c30-436b-828f-0ac5fc9ab50e-0')"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"# The second time it is, so it goes faster\n",
"llm.invoke(\"Tell me a joke\")"
]
},
{
"cell_type": "markdown",
"id": "b88ff8af",
"metadata": {},
"source": [
"## SQLite Cache\n",
"\n",
"This cache implementation uses a `SQLite` database to store responses, and will last across process restarts."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "99290ab4",
"metadata": {},
"outputs": [],
"source": [
"!rm .langchain.db"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "fe826c5c",
"metadata": {},
"outputs": [],
"source": [
"# We can do the same thing with a SQLite cache\n",
"from langchain.cache import SQLiteCache\n",
"\n",
"set_llm_cache(SQLiteCache(database_path=\".langchain.db\"))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "eb558734",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 9.91 ms, sys: 7.68 ms, total: 17.6 ms\n",
"Wall time: 657 ms\n"
]
},
{
"data": {
"text/plain": [
"AIMessage(content='Why did the scarecrow win an award? Because he was outstanding in his field!', response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 11, 'total_tokens': 28}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-39d9e1e8-7766-4970-b1d8-f50213fd94c5-0')"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"# The first time, it is not yet in cache, so it should take longer\n",
"llm.invoke(\"Tell me a joke\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "497c7000",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 52.2 ms, sys: 60.5 ms, total: 113 ms\n",
"Wall time: 127 ms\n"
]
},
{
"data": {
"text/plain": [
"AIMessage(content='Why did the scarecrow win an award? Because he was outstanding in his field!', id='run-39d9e1e8-7766-4970-b1d8-f50213fd94c5-0')"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"# The second time it is, so it goes faster\n",
"llm.invoke(\"Tell me a joke\")"
]
},
{
"cell_type": "markdown",
"id": "2950a913",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now learned how to cache model responses to save time and money.\n",
"\n",
"Next, check out the other how-to guides chat models in this section, like [how to get a model to return structured output](/docs/how_to/structured_output) or [how to create your own custom chat model](/docs/how_to/custom_chat_model)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,186 @@
{
"cells": [
{
"cell_type": "raw",
"id": "e9437c8a-d8b7-4bf6-8ff4-54068a5a266c",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 1.5\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "d0df7646-b1e1-4014-a841-6dae9b3c50d9",
"metadata": {},
"source": [
"# How to stream chat model responses\n",
"\n",
"\n",
"All [chat models](https://api.python.langchain.com/en/latest/language_models/langchain_core.language_models.chat_models.BaseChatModel.html) implement the [Runnable interface](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable), which comes with a **default** implementations of standard runnable methods (i.e. `ainvoke`, `batch`, `abatch`, `stream`, `astream`, `astream_events`).\n",
"\n",
"The **default** streaming implementation provides an`Iterator` (or `AsyncIterator` for asynchronous streaming) that yields a single value: the final output from the underlying chat model provider.\n",
"\n",
":::{.callout-tip}\n",
"\n",
"The **default** implementation does **not** provide support for token-by-token streaming, but it ensures that the the model can be swapped in for any other model as it supports the same standard interface.\n",
"\n",
":::\n",
"\n",
"The ability to stream the output token-by-token depends on whether the provider has implemented proper streaming support.\n",
"\n",
"See which [integrations support token-by-token streaming here](/docs/integrations/chat/)."
]
},
{
"cell_type": "markdown",
"id": "7a76660e-7691-48b7-a2b4-2ccdff7875c3",
"metadata": {},
"source": [
"## Sync streaming\n",
"\n",
"Below we use a `|` to help visualize the delimiter between tokens."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "975c4f32-21f6-4a71-9091-f87b56347c33",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Here| is| a| |1| |verse| song| about| gol|dfish| on| the| moon|:|\n",
"\n",
"Floating| up| in| the| star|ry| night|,|\n",
"Fins| a|-|gl|im|mer| in| the| pale| moon|light|.|\n",
"Gol|dfish| swimming|,| peaceful| an|d free|,|\n",
"Se|ren|ely| |drif|ting| across| the| lunar| sea|.|"
]
}
],
"source": [
"from langchain_anthropic.chat_models import ChatAnthropic\n",
"\n",
"chat = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
"for chunk in chat.stream(\"Write me a 1 verse song about goldfish on the moon\"):\n",
" print(chunk.content, end=\"|\", flush=True)"
]
},
{
"cell_type": "markdown",
"id": "5482d3a7-ee4f-40ba-b871-4d3f52603cd5",
"metadata": {
"tags": []
},
"source": [
"## Async Streaming"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "422f480c-df79-42e8-9bee-d0ebed31c557",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Here| is| a| |1| |verse| song| about| gol|dfish| on| the| moon|:|\n",
"\n",
"Floating| up| above| the| Earth|,|\n",
"Gol|dfish| swim| in| alien| m|irth|.|\n",
"In| their| bowl| of| lunar| dust|,|\n",
"Gl|it|tering| scales| reflect| the| trust|\n",
"Of| swimming| free| in| this| new| worl|d,|\n",
"Where| their| aqu|atic| dream|'s| unf|ur|le|d.|"
]
}
],
"source": [
"from langchain_anthropic.chat_models import ChatAnthropic\n",
"\n",
"chat = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
"async for chunk in chat.astream(\"Write me a 1 verse song about goldfish on the moon\"):\n",
" print(chunk.content, end=\"|\", flush=True)"
]
},
{
"cell_type": "markdown",
"id": "c61e1309-3b6e-42fb-820a-2e4e3e6bc074",
"metadata": {},
"source": [
"## Astream events\n",
"\n",
"Chat models also support the standard [astream events](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.astream_events) method.\n",
"\n",
"This method is useful if you're streaming output from a larger LLM application that contains multiple steps (e.g., an LLM chain composed of a prompt, llm and parser)."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "27bd1dfd-8ae2-49d6-b526-97180c81b5f4",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'event': 'on_chat_model_start', 'run_id': '08da631a-12a0-4f07-baee-fc9a175ad4ba', 'name': 'ChatAnthropic', 'tags': [], 'metadata': {}, 'data': {'input': 'Write me a 1 verse song about goldfish on the moon'}}\n",
"{'event': 'on_chat_model_stream', 'run_id': '08da631a-12a0-4f07-baee-fc9a175ad4ba', 'tags': [], 'metadata': {}, 'name': 'ChatAnthropic', 'data': {'chunk': AIMessageChunk(content='Here', id='run-08da631a-12a0-4f07-baee-fc9a175ad4ba')}}\n",
"{'event': 'on_chat_model_stream', 'run_id': '08da631a-12a0-4f07-baee-fc9a175ad4ba', 'tags': [], 'metadata': {}, 'name': 'ChatAnthropic', 'data': {'chunk': AIMessageChunk(content=\"'s\", id='run-08da631a-12a0-4f07-baee-fc9a175ad4ba')}}\n",
"{'event': 'on_chat_model_stream', 'run_id': '08da631a-12a0-4f07-baee-fc9a175ad4ba', 'tags': [], 'metadata': {}, 'name': 'ChatAnthropic', 'data': {'chunk': AIMessageChunk(content=' a', id='run-08da631a-12a0-4f07-baee-fc9a175ad4ba')}}\n",
"...Truncated\n"
]
}
],
"source": [
"from langchain_anthropic.chat_models import ChatAnthropic\n",
"\n",
"chat = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
"idx = 0\n",
"\n",
"async for event in chat.astream_events(\n",
" \"Write me a 1 verse song about goldfish on the moon\", version=\"v1\"\n",
"):\n",
" idx += 1\n",
" if idx >= 5: # Truncate the output\n",
" print(\"...Truncated\")\n",
" break\n",
" print(event)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,373 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e5715368",
"metadata": {},
"source": [
"# How to track token usage in ChatModels\n",
"\n",
"Tracking token usage to calculate cost is an important part of putting your app in production. This guide goes over how to obtain this information from your LangChain model calls.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Chat models](/docs/concepts/#chat-models)\n",
"`} />\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "1a55e87a-3291-4e7f-8e8e-4c69b0854384",
"metadata": {},
"source": [
"## Using AIMessage.response_metadata\n",
"\n",
"A number of model providers return token usage information as part of the chat generation response. When available, this is included in the [`AIMessage.response_metadata`](/docs/modules/model_io/chat/response_metadata/) field. Here's an example with OpenAI:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "467ccdeb-6b62-45e5-816e-167cd24d2586",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'token_usage': {'completion_tokens': 225,\n",
" 'prompt_tokens': 17,\n",
" 'total_tokens': 242},\n",
" 'model_name': 'gpt-4-turbo',\n",
" 'system_fingerprint': 'fp_76f018034d',\n",
" 'finish_reason': 'stop',\n",
" 'logprobs': None}"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# !pip install -qU langchain-openai\n",
"\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-4-turbo\")\n",
"msg = llm.invoke([(\"human\", \"What's the oldest known example of cuneiform\")])\n",
"msg.response_metadata"
]
},
{
"cell_type": "markdown",
"id": "9d5026e9-3ad4-41e6-9946-9f1a26f4a21f",
"metadata": {},
"source": [
"And here's an example with Anthropic:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "145404f1-e088-4824-b468-236c486a9903",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'id': 'msg_01P61rdHbapEo6h3fjpfpCQT',\n",
" 'model': 'claude-3-sonnet-20240229',\n",
" 'stop_reason': 'end_turn',\n",
" 'stop_sequence': None,\n",
" 'usage': {'input_tokens': 17, 'output_tokens': 306}}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# !pip install -qU langchain-anthropic\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"\n",
"llm = ChatAnthropic(model=\"claude-3-sonnet-20240229\")\n",
"msg = llm.invoke([(\"human\", \"What's the oldest known example of cuneiform\")])\n",
"msg.response_metadata"
]
},
{
"cell_type": "markdown",
"id": "d6845407-af25-4eed-bc3e-50925c6661e0",
"metadata": {},
"source": [
"## Using callbacks\n",
"\n",
"There are also some API-specific callback context managers that allow you to track token usage across multiple calls. It is currently only implemented for the OpenAI API and Bedrock Anthropic API.\n",
"\n",
"### OpenAI\n",
"\n",
"Let's first look at an extremely simple example of tracking token usage for a single Chat model call."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "31667d54",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tokens Used: 26\n",
"\tPrompt Tokens: 11\n",
"\tCompletion Tokens: 15\n",
"Successful Requests: 1\n",
"Total Cost (USD): $0.00056\n"
]
}
],
"source": [
"# !pip install -qU langchain-community wikipedia\n",
"\n",
"from langchain_community.callbacks.manager import get_openai_callback\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-4-turbo\", temperature=0)\n",
"\n",
"with get_openai_callback() as cb:\n",
" result = llm.invoke(\"Tell me a joke\")\n",
" print(cb)"
]
},
{
"cell_type": "markdown",
"id": "c0ab6d27",
"metadata": {},
"source": [
"Anything inside the context manager will get tracked. Here's an example of using it to track multiple calls in sequence."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "e09420f4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"52\n"
]
}
],
"source": [
"with get_openai_callback() as cb:\n",
" result = llm.invoke(\"Tell me a joke\")\n",
" result2 = llm.invoke(\"Tell me a joke\")\n",
" print(cb.total_tokens)"
]
},
{
"cell_type": "markdown",
"id": "d8186e7b",
"metadata": {},
"source": [
"If a chain or agent with multiple steps in it is used, it will track all those steps."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "5d1125c6",
"metadata": {},
"outputs": [],
"source": [
"from langchain.agents import AgentExecutor, create_tool_calling_agent, load_tools\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", \"You're a helpful assistant\"),\n",
" (\"human\", \"{input}\"),\n",
" (\"placeholder\", \"{agent_scratchpad}\"),\n",
" ]\n",
")\n",
"tools = load_tools([\"wikipedia\"])\n",
"agent = create_tool_calling_agent(llm, tools, prompt)\n",
"agent_executor = AgentExecutor(\n",
" agent=agent, tools=tools, verbose=True, stream_runnable=False\n",
")"
]
},
{
"cell_type": "markdown",
"id": "9c1ae74d-8300-4041-9ff4-66093ee592b1",
"metadata": {},
"source": [
"```{=mdx}\n",
":::note\n",
"We have to set `stream_runnable=False` for token counting to work. By default the AgentExecutor will stream the underlying agent so that you can get the most granular results when streaming events via AgentExecutor.stream_events. However, OpenAI does not return token counts when streaming model responses, so we need to turn off the underlying streaming.\n",
":::\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "2f98c536",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `wikipedia` with `Hummingbird`\n",
"\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3mPage: Hummingbird\n",
"Summary: Hummingbirds are birds native to the Americas and comprise the biological family Trochilidae. With approximately 366 species and 113 genera, they occur from Alaska to Tierra del Fuego, but most species are found in Central and South America. As of 2024, 21 hummingbird species are listed as endangered or critically endangered, with numerous species declining in population.Hummingbirds have varied specialized characteristics to enable rapid, maneuverable flight: exceptional metabolic capacity, adaptations to high altitude, sensitive visual and communication abilities, and long-distance migration in some species. Among all birds, male hummingbirds have the widest diversity of plumage color, particularly in blues, greens, and purples. Hummingbirds are the smallest mature birds, measuring 7.513 cm (35 in) in length. The smallest is the 5 cm (2.0 in) bee hummingbird, which weighs less than 2.0 g (0.07 oz), and the largest is the 23 cm (9 in) giant hummingbird, weighing 1824 grams (0.630.85 oz). Noted for long beaks, hummingbirds are specialized for feeding on flower nectar, but all species also consume small insects.\n",
"They are known as hummingbirds because of the humming sound created by their beating wings, which flap at high frequencies audible to other birds and humans. They hover at rapid wing-flapping rates, which vary from around 12 beats per second in the largest species to 80 per second in small hummingbirds.\n",
"Hummingbirds have the highest mass-specific metabolic rate of any homeothermic animal. To conserve energy when food is scarce and at night when not foraging, they can enter torpor, a state similar to hibernation, and slow their metabolic rate to 115 of its normal rate. While most hummingbirds do not migrate, the rufous hummingbird has one of the longest migrations among birds, traveling twice per year between Alaska and Mexico, a distance of about 3,900 miles (6,300 km).\n",
"Hummingbirds split from their sister group, the swifts and treeswifts, around 42 million years ago. The oldest known fossil hummingbird is Eurotrochilus, from the Rupelian Stage of Early Oligocene Europe.\n",
"\n",
"\n",
"\n",
"Page: Bee hummingbird\n",
"Summary: The bee hummingbird, zunzuncito or Helena hummingbird (Mellisuga helenae) is a species of hummingbird, native to the island of Cuba in the Caribbean. It is the smallest known bird. The bee hummingbird feeds on nectar of flowers and bugs found in Cuba.\n",
"\n",
"Page: Hummingbird cake\n",
"Summary: Hummingbird cake is a banana-pineapple spice cake originating in Jamaica and a popular dessert in the southern United States since the 1970s. Ingredients include flour, sugar, salt, vegetable oil, ripe banana, pineapple, cinnamon, pecans, vanilla extract, eggs, and leavening agent. It is often served with cream cheese frosting.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
"Invoking: `wikipedia` with `Fastest bird`\n",
"\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3mPage: Fastest animals\n",
"Summary: This is a list of the fastest animals in the world, by types of animal.\n",
"\n",
"\n",
"\n",
"Page: List of birds by flight speed\n",
"Summary: This is a list of the fastest flying birds in the world. A bird's velocity is necessarily variable; a hunting bird will reach much greater speeds while diving to catch prey than when flying horizontally. The bird that can achieve the greatest airspeed is the peregrine falcon, able to exceed 320 km/h (200 mph) in its dives. A close relative of the common swift, the white-throated needletail (Hirundapus caudacutus), is commonly reported as the fastest bird in level flight with a reported top speed of 169 km/h (105 mph). This record remains unconfirmed as the measurement methods have never been published or verified. The record for the fastest confirmed level flight by a bird is 111.5 km/h (69.3 mph) held by the common swift.\n",
"\n",
"Page: Ostrich\n",
"Summary: Ostriches are large flightless birds. They are the heaviest and largest living birds, with adult common ostriches weighing anywhere between 63.5 and 145 kilograms and laying the largest eggs of any living land animal. With the ability to run at 70 km/h (43.5 mph), they are the fastest birds on land. They are farmed worldwide, with significant industries in the Philippines and in Namibia. Ostrich leather is a lucrative commodity, and the large feathers are used as plumes for the decoration of ceremonial headgear. Ostrich eggs have been used by humans for millennia.\n",
"Ostriches are of the genus Struthio in the order Struthioniformes, part of the infra-class Palaeognathae, a diverse group of flightless birds also known as ratites that includes the emus, rheas, cassowaries, kiwis and the extinct elephant birds and moas. There are two living species of ostrich: the common ostrich, native to large areas of sub-Saharan Africa, and the Somali ostrich, native to the Horn of Africa. The common ostrich was historically native to the Arabian Peninsula, and ostriches were present across Asia as far east as China and Mongolia during the Late Pleistocene and possibly into the Holocene.\u001b[0m\u001b[32;1m\u001b[1;3m### Hummingbird's Scientific Name\n",
"The scientific name for the bee hummingbird, which is the smallest known bird and a species of hummingbird, is **Mellisuga helenae**. It is native to Cuba.\n",
"\n",
"### Fastest Bird Species\n",
"The fastest bird in terms of airspeed is the **peregrine falcon**, which can exceed speeds of 320 km/h (200 mph) during its diving flight. In level flight, the fastest confirmed speed is held by the **common swift**, which can fly at 111.5 km/h (69.3 mph).\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"Total Tokens: 1583\n",
"Prompt Tokens: 1412\n",
"Completion Tokens: 171\n",
"Total Cost (USD): $0.019250000000000003\n"
]
}
],
"source": [
"with get_openai_callback() as cb:\n",
" response = agent_executor.invoke(\n",
" {\n",
" \"input\": \"What's a hummingbird's scientific name and what's the fastest bird species?\"\n",
" }\n",
" )\n",
" print(f\"Total Tokens: {cb.total_tokens}\")\n",
" print(f\"Prompt Tokens: {cb.prompt_tokens}\")\n",
" print(f\"Completion Tokens: {cb.completion_tokens}\")\n",
" print(f\"Total Cost (USD): ${cb.total_cost}\")"
]
},
{
"cell_type": "markdown",
"id": "ebc9122b-050b-4006-b763-264b0b26d9df",
"metadata": {},
"source": [
"### Bedrock Anthropic\n",
"\n",
"The `get_bedrock_anthropic_callback` works very similarly:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "4a3eced5-2ff7-49a7-a48b-768af8658323",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tokens Used: 0\n",
"\tPrompt Tokens: 0\n",
"\tCompletion Tokens: 0\n",
"Successful Requests: 2\n",
"Total Cost (USD): $0.0\n"
]
}
],
"source": [
"# !pip install langchain-aws\n",
"from langchain_aws import ChatBedrock\n",
"from langchain_community.callbacks.manager import get_bedrock_anthropic_callback\n",
"\n",
"llm = ChatBedrock(model_id=\"anthropic.claude-v2\")\n",
"\n",
"with get_bedrock_anthropic_callback() as cb:\n",
" result = llm.invoke(\"Tell me a joke\")\n",
" result2 = llm.invoke(\"Tell me a joke\")\n",
" print(cb)"
]
},
{
"cell_type": "markdown",
"id": "33172f31",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now seen a few examples of how to track token usage for supported providers.\n",
"\n",
"Next, check out the other how-to guides chat models in this section, like [how to get a model to return structured output](/docs/how_to/structured_output) or [how to add caching to your chat models](/docs/how_to/chat_model_caching)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb40375d",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,780 @@
{
"cells": [
{
"cell_type": "raw",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 1\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to add memory to chatbots\n",
"\n",
"A key feature of chatbots is their ability to use content of previous conversation turns as context. This state management can take several forms, including:\n",
"\n",
"- Simply stuffing previous messages into a chat model prompt.\n",
"- The above, but trimming old messages to reduce the amount of distracting information the model has to deal with.\n",
"- More complex modifications like synthesizing summaries for long running conversations.\n",
"\n",
"We'll go into more detail on a few techniques below!\n",
"\n",
"## Setup\n",
"\n",
"You'll need to install a few packages, and have your OpenAI API key set as an environment variable named `OPENAI_API_KEY`:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARNING: You are using pip version 22.0.4; however, version 23.3.2 is available.\n",
"You should consider upgrading via the '/Users/jacoblee/.pyenv/versions/3.10.5/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n",
"\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%pip install --upgrade --quiet langchain langchain-openai\n",
"\n",
"# Set env var OPENAI_API_KEY or load from a .env file:\n",
"import dotenv\n",
"\n",
"dotenv.load_dotenv()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's also set up a chat model that we'll use for the below examples."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from langchain_openai import ChatOpenAI\n",
"\n",
"chat = ChatOpenAI(model=\"gpt-3.5-turbo-1106\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Message passing\n",
"\n",
"The simplest form of memory is simply passing chat history messages into a chain. Here's an example:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='I said \"J\\'adore la programmation,\" which means \"I love programming\" in French.')"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import AIMessage, HumanMessage\n",
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful assistant. Answer all questions to the best of your ability.\",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"messages\"),\n",
" ]\n",
")\n",
"\n",
"chain = prompt | chat\n",
"\n",
"chain.invoke(\n",
" {\n",
" \"messages\": [\n",
" HumanMessage(\n",
" content=\"Translate this sentence from English to French: I love programming.\"\n",
" ),\n",
" AIMessage(content=\"J'adore la programmation.\"),\n",
" HumanMessage(content=\"What did you just say?\"),\n",
" ],\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that by passing the previous conversation into a chain, it can use it as context to answer questions. This is the basic concept underpinning chatbot memory - the rest of the guide will demonstrate convenient techniques for passing or reformatting messages.\n",
"\n",
"## Chat history\n",
"\n",
"It's perfectly fine to store and pass messages directly as an array, but we can use LangChain's built-in [message history class](/docs/modules/memory/chat_messages/) to store and load messages as well. Instances of this class are responsible for storing and loading chat messages from persistent storage. LangChain integrates with many providers - you can see a [list of integrations here](/docs/integrations/memory) - but for this demo we will use an ephemeral demo class.\n",
"\n",
"Here's an example of the API:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content='Translate this sentence from English to French: I love programming.'),\n",
" AIMessage(content=\"J'adore la programmation.\")]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.memory import ChatMessageHistory\n",
"\n",
"demo_ephemeral_chat_history = ChatMessageHistory()\n",
"\n",
"demo_ephemeral_chat_history.add_user_message(\n",
" \"Translate this sentence from English to French: I love programming.\"\n",
")\n",
"\n",
"demo_ephemeral_chat_history.add_ai_message(\"J'adore la programmation.\")\n",
"\n",
"demo_ephemeral_chat_history.messages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can use it directly to store conversation turns for our chain:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='You asked me to translate the sentence \"I love programming\" from English to French.')"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"demo_ephemeral_chat_history = ChatMessageHistory()\n",
"\n",
"input1 = \"Translate this sentence from English to French: I love programming.\"\n",
"\n",
"demo_ephemeral_chat_history.add_user_message(input1)\n",
"\n",
"response = chain.invoke(\n",
" {\n",
" \"messages\": demo_ephemeral_chat_history.messages,\n",
" }\n",
")\n",
"\n",
"demo_ephemeral_chat_history.add_ai_message(response)\n",
"\n",
"input2 = \"What did I just ask you?\"\n",
"\n",
"demo_ephemeral_chat_history.add_user_message(input2)\n",
"\n",
"chain.invoke(\n",
" {\n",
" \"messages\": demo_ephemeral_chat_history.messages,\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Automatic history management\n",
"\n",
"The previous examples pass messages to the chain explicitly. This is a completely acceptable approach, but it does require external management of new messages. LangChain also includes an wrapper for LCEL chains that can handle this process automatically called `RunnableWithMessageHistory`.\n",
"\n",
"To show how it works, let's slightly modify the above prompt to take a final `input` variable that populates a `HumanMessage` template after the chat history. This means that we will expect a `chat_history` parameter that contains all messages BEFORE the current messages instead of all messages:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful assistant. Answer all questions to the best of your ability.\",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"chat_history\"),\n",
" (\"human\", \"{input}\"),\n",
" ]\n",
")\n",
"\n",
"chain = prompt | chat"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" We'll pass the latest input to the conversation here and let the `RunnableWithMessageHistory` class wrap our chain and do the work of appending that `input` variable to the chat history.\n",
" \n",
" Next, let's declare our wrapped chain:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.runnables.history import RunnableWithMessageHistory\n",
"\n",
"demo_ephemeral_chat_history_for_chain = ChatMessageHistory()\n",
"\n",
"chain_with_message_history = RunnableWithMessageHistory(\n",
" chain,\n",
" lambda session_id: demo_ephemeral_chat_history_for_chain,\n",
" input_messages_key=\"input\",\n",
" history_messages_key=\"chat_history\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This class takes a few parameters in addition to the chain that we want to wrap:\n",
"\n",
"- A factory function that returns a message history for a given session id. This allows your chain to handle multiple users at once by loading different messages for different conversations.\n",
"- An `input_messages_key` that specifies which part of the input should be tracked and stored in the chat history. In this example, we want to track the string passed in as `input`.\n",
"- A `history_messages_key` that specifies what the previous messages should be injected into the prompt as. Our prompt has a `MessagesPlaceholder` named `chat_history`, so we specify this property to match.\n",
"- (For chains with multiple outputs) an `output_messages_key` which specifies which output to store as history. This is the inverse of `input_messages_key`.\n",
"\n",
"We can invoke this new chain as normal, with an additional `configurable` field that specifies the particular `session_id` to pass to the factory function. This is unused for the demo, but in real-world chains, you'll want to return a chat history corresponding to the passed session:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='The translation of \"I love programming\" in French is \"J\\'adore la programmation.\"')"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain_with_message_history.invoke(\n",
" {\"input\": \"Translate this sentence from English to French: I love programming.\"},\n",
" {\"configurable\": {\"session_id\": \"unused\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='You just asked me to translate the sentence \"I love programming\" from English to French.')"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain_with_message_history.invoke(\n",
" {\"input\": \"What did I just ask you?\"}, {\"configurable\": {\"session_id\": \"unused\"}}\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Modifying chat history\n",
"\n",
"Modifying stored chat messages can help your chatbot handle a variety of situations. Here are some examples:\n",
"\n",
"### Trimming messages\n",
"\n",
"LLMs and chat models have limited context windows, and even if you're not directly hitting limits, you may want to limit the amount of distraction the model has to deal with. One solution is to only load and store the most recent `n` messages. Let's use an example history with some preloaded messages:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content=\"Hey there! I'm Nemo.\"),\n",
" AIMessage(content='Hello!'),\n",
" HumanMessage(content='How are you today?'),\n",
" AIMessage(content='Fine thanks!')]"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"demo_ephemeral_chat_history = ChatMessageHistory()\n",
"\n",
"demo_ephemeral_chat_history.add_user_message(\"Hey there! I'm Nemo.\")\n",
"demo_ephemeral_chat_history.add_ai_message(\"Hello!\")\n",
"demo_ephemeral_chat_history.add_user_message(\"How are you today?\")\n",
"demo_ephemeral_chat_history.add_ai_message(\"Fine thanks!\")\n",
"\n",
"demo_ephemeral_chat_history.messages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's use this message history with the `RunnableWithMessageHistory` chain we declared above:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='Your name is Nemo.')"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful assistant. Answer all questions to the best of your ability.\",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"chat_history\"),\n",
" (\"human\", \"{input}\"),\n",
" ]\n",
")\n",
"\n",
"chain = prompt | chat\n",
"\n",
"chain_with_message_history = RunnableWithMessageHistory(\n",
" chain,\n",
" lambda session_id: demo_ephemeral_chat_history,\n",
" input_messages_key=\"input\",\n",
" history_messages_key=\"chat_history\",\n",
")\n",
"\n",
"chain_with_message_history.invoke(\n",
" {\"input\": \"What's my name?\"},\n",
" {\"configurable\": {\"session_id\": \"unused\"}},\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see the chain remembers the preloaded name.\n",
"\n",
"But let's say we have a very small context window, and we want to trim the number of messages passed to the chain to only the 2 most recent ones. We can use the `clear` method to remove messages and re-add them to the history. We don't have to, but let's put this method at the front of our chain to ensure it's always called:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.runnables import RunnablePassthrough\n",
"\n",
"\n",
"def trim_messages(chain_input):\n",
" stored_messages = demo_ephemeral_chat_history.messages\n",
" if len(stored_messages) <= 2:\n",
" return False\n",
"\n",
" demo_ephemeral_chat_history.clear()\n",
"\n",
" for message in stored_messages[-2:]:\n",
" demo_ephemeral_chat_history.add_message(message)\n",
"\n",
" return True\n",
"\n",
"\n",
"chain_with_trimming = (\n",
" RunnablePassthrough.assign(messages_trimmed=trim_messages)\n",
" | chain_with_message_history\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's call this new chain and check the messages afterwards:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"P. Sherman's address is 42 Wallaby Way, Sydney.\")"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain_with_trimming.invoke(\n",
" {\"input\": \"Where does P. Sherman live?\"},\n",
" {\"configurable\": {\"session_id\": \"unused\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content=\"What's my name?\"),\n",
" AIMessage(content='Your name is Nemo.'),\n",
" HumanMessage(content='Where does P. Sherman live?'),\n",
" AIMessage(content=\"P. Sherman's address is 42 Wallaby Way, Sydney.\")]"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"demo_ephemeral_chat_history.messages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And we can see that our history has removed the two oldest messages while still adding the most recent conversation at the end. The next time the chain is called, `trim_messages` will be called again, and only the two most recent messages will be passed to the model. In this case, this means that the model will forget the name we gave it the next time we invoke it:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"I'm sorry, I don't have access to your personal information.\")"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain_with_trimming.invoke(\n",
" {\"input\": \"What is my name?\"},\n",
" {\"configurable\": {\"session_id\": \"unused\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content='Where does P. Sherman live?'),\n",
" AIMessage(content=\"P. Sherman's address is 42 Wallaby Way, Sydney.\"),\n",
" HumanMessage(content='What is my name?'),\n",
" AIMessage(content=\"I'm sorry, I don't have access to your personal information.\")]"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"demo_ephemeral_chat_history.messages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Summary memory\n",
"\n",
"We can use this same pattern in other ways too. For example, we could use an additional LLM call to generate a summary of the conversation before calling our chain. Let's recreate our chat history and chatbot chain:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content=\"Hey there! I'm Nemo.\"),\n",
" AIMessage(content='Hello!'),\n",
" HumanMessage(content='How are you today?'),\n",
" AIMessage(content='Fine thanks!')]"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"demo_ephemeral_chat_history = ChatMessageHistory()\n",
"\n",
"demo_ephemeral_chat_history.add_user_message(\"Hey there! I'm Nemo.\")\n",
"demo_ephemeral_chat_history.add_ai_message(\"Hello!\")\n",
"demo_ephemeral_chat_history.add_user_message(\"How are you today?\")\n",
"demo_ephemeral_chat_history.add_ai_message(\"Fine thanks!\")\n",
"\n",
"demo_ephemeral_chat_history.messages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We'll slightly modify the prompt to make the LLM aware that will receive a condensed summary instead of a chat history:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful assistant. Answer all questions to the best of your ability. The provided chat history includes facts about the user you are speaking with.\",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"chat_history\"),\n",
" (\"user\", \"{input}\"),\n",
" ]\n",
")\n",
"\n",
"chain = prompt | chat\n",
"\n",
"chain_with_message_history = RunnableWithMessageHistory(\n",
" chain,\n",
" lambda session_id: demo_ephemeral_chat_history,\n",
" input_messages_key=\"input\",\n",
" history_messages_key=\"chat_history\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And now, let's create a function that will distill previous interactions into a summary. We can add this one to the front of the chain too:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"def summarize_messages(chain_input):\n",
" stored_messages = demo_ephemeral_chat_history.messages\n",
" if len(stored_messages) == 0:\n",
" return False\n",
" summarization_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" MessagesPlaceholder(variable_name=\"chat_history\"),\n",
" (\n",
" \"user\",\n",
" \"Distill the above chat messages into a single summary message. Include as many specific details as you can.\",\n",
" ),\n",
" ]\n",
" )\n",
" summarization_chain = summarization_prompt | chat\n",
"\n",
" summary_message = summarization_chain.invoke({\"chat_history\": stored_messages})\n",
"\n",
" demo_ephemeral_chat_history.clear()\n",
"\n",
" demo_ephemeral_chat_history.add_message(summary_message)\n",
"\n",
" return True\n",
"\n",
"\n",
"chain_with_summarization = (\n",
" RunnablePassthrough.assign(messages_summarized=summarize_messages)\n",
" | chain_with_message_history\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's see if it remembers the name we gave it:"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='You introduced yourself as Nemo. How can I assist you today, Nemo?')"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain_with_summarization.invoke(\n",
" {\"input\": \"What did I say my name was?\"},\n",
" {\"configurable\": {\"session_id\": \"unused\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[AIMessage(content='The conversation is between Nemo and an AI. Nemo introduces himself and the AI responds with a greeting. Nemo then asks the AI how it is doing, and the AI responds that it is fine.'),\n",
" HumanMessage(content='What did I say my name was?'),\n",
" AIMessage(content='You introduced yourself as Nemo. How can I assist you today, Nemo?')]"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"demo_ephemeral_chat_history.messages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that invoking the chain again will generate another summary generated from the initial summary plus new messages and so on. You could also design a hybrid approach where a certain number of messages are retained in chat history while others are summarized."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,765 @@
{
"cells": [
{
"cell_type": "raw",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 2\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to add retrieval to chatbots\n",
"\n",
"Retrieval is a common technique chatbots use to augment their responses with data outside a chat model's training data. This section will cover how to implement retrieval in the context of chatbots, but it's worth noting that retrieval is a very subtle and deep topic - we encourage you to explore [other parts of the documentation](/docs/use_cases/question_answering/) that go into greater depth!\n",
"\n",
"## Setup\n",
"\n",
"You'll need to install a few packages, and have your OpenAI API key set as an environment variable named `OPENAI_API_KEY`:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARNING: You are using pip version 22.0.4; however, version 23.3.2 is available.\n",
"You should consider upgrading via the '/Users/jacoblee/.pyenv/versions/3.10.5/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n",
"\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%pip install -qU langchain langchain-openai langchain-chroma beautifulsoup4\n",
"\n",
"# Set env var OPENAI_API_KEY or load from a .env file:\n",
"import dotenv\n",
"\n",
"dotenv.load_dotenv()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's also set up a chat model that we'll use for the below examples."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from langchain_openai import ChatOpenAI\n",
"\n",
"chat = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", temperature=0.2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Creating a retriever\n",
"\n",
"We'll use [the LangSmith documentation](https://docs.smith.langchain.com/overview) as source material and store the content in a vectorstore for later retrieval. Note that this example will gloss over some of the specifics around parsing and storing a data source - you can see more [in-depth documentation on creating retrieval systems here](/docs/use_cases/question_answering/).\n",
"\n",
"Let's use a document loader to pull text from the docs:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import WebBaseLoader\n",
"\n",
"loader = WebBaseLoader(\"https://docs.smith.langchain.com/overview\")\n",
"data = loader.load()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we split it into smaller chunks that the LLM's context window can handle and store it in a vector database:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
"\n",
"text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)\n",
"all_splits = text_splitter.split_documents(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we embed and store those chunks in a vector database:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"from langchain_chroma import Chroma\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"vectorstore = Chroma.from_documents(documents=all_splits, embedding=OpenAIEmbeddings())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And finally, let's create a retriever from our initialized vectorstore:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Skip to main content🦜🛠 LangSmith DocsPython DocsJS/TS DocsSearchGo to AppLangSmithOverviewTracingTesting & EvaluationOrganizationsHubLangSmith CookbookOverviewOn this pageLangSmith Overview and User GuideBuilding reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.Over the past two months, we at LangChain', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='LangSmith Overview and User Guide | 🦜️🛠️ LangSmith', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='You can also quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs.Monitoring\\u200bAfter all this, your app might finally ready to go in production. LangSmith can also be used to monitor your application in much the same way that you used for debugging. You can log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise. Each run can also be', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content=\"does that affect the output?\\u200bSo you notice a bad output, and you go into LangSmith to see what's going on. You find the faulty LLM call and are now looking at the exact input. You want to try changing a word or a phrase to see what happens -- what do you do?We constantly ran into this issue. Initially, we copied the prompt to a playground of sorts. But this got annoying, so we built a playground of our own! When examining an LLM call, you can click the Open in Playground button to access this\", metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'})]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# k is the number of chunks to retrieve\n",
"retriever = vectorstore.as_retriever(k=4)\n",
"\n",
"docs = retriever.invoke(\"Can LangSmith help test my LLM applications?\")\n",
"\n",
"docs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that invoking the retriever above results in some parts of the LangSmith docs that contain information about testing that our chatbot can use as context when answering questions. And now we've got a retriever that can return related data from the LangSmith docs!\n",
"\n",
"## Document chains\n",
"\n",
"Now that we have a retriever that can return LangChain docs, let's create a chain that can use them as context to answer questions. We'll use a `create_stuff_documents_chain` helper function to \"stuff\" all of the input documents into the prompt. It will also handle formatting the docs as strings.\n",
"\n",
"In addition to a chat model, the function also expects a prompt that has a `context` variables, as well as a placeholder for chat history messages named `messages`. We'll create an appropriate prompt and pass it as shown below:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"from langchain.chains.combine_documents import create_stuff_documents_chain\n",
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"\n",
"SYSTEM_TEMPLATE = \"\"\"\n",
"Answer the user's questions based on the below context. \n",
"If the context doesn't contain any relevant information to the question, don't make something up and just say \"I don't know\":\n",
"\n",
"<context>\n",
"{context}\n",
"</context>\n",
"\"\"\"\n",
"\n",
"question_answering_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" SYSTEM_TEMPLATE,\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"messages\"),\n",
" ]\n",
")\n",
"\n",
"document_chain = create_stuff_documents_chain(chat, question_answering_prompt)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can invoke this `document_chain` by itself to answer questions. Let's use the docs we retrieved above and the same question, `how can langsmith help with testing?`:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Yes, LangSmith can help test and evaluate your LLM applications. It simplifies the initial setup, and you can use it to monitor your application, log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise.'"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import HumanMessage\n",
"\n",
"document_chain.invoke(\n",
" {\n",
" \"context\": docs,\n",
" \"messages\": [\n",
" HumanMessage(content=\"Can LangSmith help test my LLM applications?\")\n",
" ],\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looks good! For comparison, we can try it with no context docs and compare the result:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"I don't know about LangSmith's specific capabilities for testing LLM applications. It's best to reach out to LangSmith directly to inquire about their services and how they can assist with testing your LLM applications.\""
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"document_chain.invoke(\n",
" {\n",
" \"context\": [],\n",
" \"messages\": [\n",
" HumanMessage(content=\"Can LangSmith help test my LLM applications?\")\n",
" ],\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that the LLM does not return any results.\n",
"\n",
"## Retrieval chains\n",
"\n",
"Let's combine this document chain with the retriever. Here's one way this can look:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"from typing import Dict\n",
"\n",
"from langchain_core.runnables import RunnablePassthrough\n",
"\n",
"\n",
"def parse_retriever_input(params: Dict):\n",
" return params[\"messages\"][-1].content\n",
"\n",
"\n",
"retrieval_chain = RunnablePassthrough.assign(\n",
" context=parse_retriever_input | retriever,\n",
").assign(\n",
" answer=document_chain,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Given a list of input messages, we extract the content of the last message in the list and pass that to the retriever to fetch some documents. Then, we pass those documents as context to our document chain to generate a final response.\n",
"\n",
"Invoking this chain combines both steps outlined above:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content='Can LangSmith help test my LLM applications?')],\n",
" 'context': [Document(page_content='Skip to main content🦜🛠 LangSmith DocsPython DocsJS/TS DocsSearchGo to AppLangSmithOverviewTracingTesting & EvaluationOrganizationsHubLangSmith CookbookOverviewOn this pageLangSmith Overview and User GuideBuilding reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.Over the past two months, we at LangChain', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='LangSmith Overview and User Guide | 🦜️🛠️ LangSmith', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='You can also quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs.Monitoring\\u200bAfter all this, your app might finally ready to go in production. LangSmith can also be used to monitor your application in much the same way that you used for debugging. You can log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise. Each run can also be', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content=\"does that affect the output?\\u200bSo you notice a bad output, and you go into LangSmith to see what's going on. You find the faulty LLM call and are now looking at the exact input. You want to try changing a word or a phrase to see what happens -- what do you do?We constantly ran into this issue. Initially, we copied the prompt to a playground of sorts. But this got annoying, so we built a playground of our own! When examining an LLM call, you can click the Open in Playground button to access this\", metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'})],\n",
" 'answer': 'Yes, LangSmith can help test and evaluate your LLM applications. It simplifies the initial setup, and you can use it to monitor your application, log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise.'}"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retrieval_chain.invoke(\n",
" {\n",
" \"messages\": [\n",
" HumanMessage(content=\"Can LangSmith help test my LLM applications?\")\n",
" ],\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looks good!\n",
"\n",
"## Query transformation\n",
"\n",
"Our retrieval chain is capable of answering questions about LangSmith, but there's a problem - chatbots interact with users conversationally, and therefore have to deal with followup questions.\n",
"\n",
"The chain in its current form will struggle with this. Consider a followup question to our original question like `Tell me more!`. If we invoke our retriever with that query directly, we get documents irrelevant to LLM application testing:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='You can also quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs.Monitoring\\u200bAfter all this, your app might finally ready to go in production. LangSmith can also be used to monitor your application in much the same way that you used for debugging. You can log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise. Each run can also be', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='playground. Here, you can modify the prompt and re-run it to observe the resulting changes to the output - as many times as needed!Currently, this feature supports only OpenAI and Anthropic models and works for LLM and Chat Model calls. We plan to extend its functionality to more LLM types, chains, agents, and retrievers in the future.What is the exact sequence of events?\\u200bIn complicated chains and agents, it can often be hard to understand what is going on under the hood. What calls are being', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='however, there is still no complete substitute for human review to get the utmost quality and reliability from your application.', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='Skip to main content🦜🛠 LangSmith DocsPython DocsJS/TS DocsSearchGo to AppLangSmithOverviewTracingTesting & EvaluationOrganizationsHubLangSmith CookbookOverviewOn this pageLangSmith Overview and User GuideBuilding reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.Over the past two months, we at LangChain', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'})]"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retriever.invoke(\"Tell me more!\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is because the retriever has no innate concept of state, and will only pull documents most similar to the query given. To solve this, we can transform the query into a standalone query without any external references an LLM.\n",
"\n",
"Here's an example:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='\"LangSmith LLM application testing and evaluation\"')"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import AIMessage, HumanMessage\n",
"\n",
"query_transform_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" MessagesPlaceholder(variable_name=\"messages\"),\n",
" (\n",
" \"user\",\n",
" \"Given the above conversation, generate a search query to look up in order to get information relevant to the conversation. Only respond with the query, nothing else.\",\n",
" ),\n",
" ]\n",
")\n",
"\n",
"query_transformation_chain = query_transform_prompt | chat\n",
"\n",
"query_transformation_chain.invoke(\n",
" {\n",
" \"messages\": [\n",
" HumanMessage(content=\"Can LangSmith help test my LLM applications?\"),\n",
" AIMessage(\n",
" content=\"Yes, LangSmith can help test and evaluate your LLM applications. It allows you to quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs. Additionally, LangSmith can be used to monitor your application, log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise.\"\n",
" ),\n",
" HumanMessage(content=\"Tell me more!\"),\n",
" ],\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Awesome! That transformed query would pull up context documents related to LLM application testing.\n",
"\n",
"Let's add this to our retrieval chain. We can wrap our retriever as follows:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.runnables import RunnableBranch\n",
"\n",
"query_transforming_retriever_chain = RunnableBranch(\n",
" (\n",
" lambda x: len(x.get(\"messages\", [])) == 1,\n",
" # If only one message, then we just pass that message's content to retriever\n",
" (lambda x: x[\"messages\"][-1].content) | retriever,\n",
" ),\n",
" # If messages, then we pass inputs to LLM chain to transform the query, then pass to retriever\n",
" query_transform_prompt | chat | StrOutputParser() | retriever,\n",
").with_config(run_name=\"chat_retriever_chain\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then, we can use this query transformation chain to make our retrieval chain better able to handle such followup questions:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"SYSTEM_TEMPLATE = \"\"\"\n",
"Answer the user's questions based on the below context. \n",
"If the context doesn't contain any relevant information to the question, don't make something up and just say \"I don't know\":\n",
"\n",
"<context>\n",
"{context}\n",
"</context>\n",
"\"\"\"\n",
"\n",
"question_answering_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" SYSTEM_TEMPLATE,\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"messages\"),\n",
" ]\n",
")\n",
"\n",
"document_chain = create_stuff_documents_chain(chat, question_answering_prompt)\n",
"\n",
"conversational_retrieval_chain = RunnablePassthrough.assign(\n",
" context=query_transforming_retriever_chain,\n",
").assign(\n",
" answer=document_chain,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Awesome! Let's invoke this new chain with the same inputs as earlier:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content='Can LangSmith help test my LLM applications?')],\n",
" 'context': [Document(page_content='Skip to main content🦜🛠 LangSmith DocsPython DocsJS/TS DocsSearchGo to AppLangSmithOverviewTracingTesting & EvaluationOrganizationsHubLangSmith CookbookOverviewOn this pageLangSmith Overview and User GuideBuilding reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.Over the past two months, we at LangChain', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='LangSmith Overview and User Guide | 🦜️🛠️ LangSmith', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='You can also quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs.Monitoring\\u200bAfter all this, your app might finally ready to go in production. LangSmith can also be used to monitor your application in much the same way that you used for debugging. You can log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise. Each run can also be', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content=\"does that affect the output?\\u200bSo you notice a bad output, and you go into LangSmith to see what's going on. You find the faulty LLM call and are now looking at the exact input. You want to try changing a word or a phrase to see what happens -- what do you do?We constantly ran into this issue. Initially, we copied the prompt to a playground of sorts. But this got annoying, so we built a playground of our own! When examining an LLM call, you can click the Open in Playground button to access this\", metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'})],\n",
" 'answer': 'Yes, LangSmith can help test and evaluate LLM (Language Model) applications. It simplifies the initial setup, and you can use it to monitor your application, log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise.'}"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"conversational_retrieval_chain.invoke(\n",
" {\n",
" \"messages\": [\n",
" HumanMessage(content=\"Can LangSmith help test my LLM applications?\"),\n",
" ]\n",
" }\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content='Can LangSmith help test my LLM applications?'),\n",
" AIMessage(content='Yes, LangSmith can help test and evaluate your LLM applications. It allows you to quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs. Additionally, LangSmith can be used to monitor your application, log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise.'),\n",
" HumanMessage(content='Tell me more!')],\n",
" 'context': [Document(page_content='LangSmith Overview and User Guide | 🦜️🛠️ LangSmith', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='You can also quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs.Monitoring\\u200bAfter all this, your app might finally ready to go in production. LangSmith can also be used to monitor your application in much the same way that you used for debugging. You can log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise. Each run can also be', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='Skip to main content🦜🛠 LangSmith DocsPython DocsJS/TS DocsSearchGo to AppLangSmithOverviewTracingTesting & EvaluationOrganizationsHubLangSmith CookbookOverviewOn this pageLangSmith Overview and User GuideBuilding reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.Over the past two months, we at LangChain', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}),\n",
" Document(page_content='LangSmith makes it easy to manually review and annotate runs through annotation queues.These queues allow you to select any runs based on criteria like model type or automatic evaluation scores, and queue them up for human review. As a reviewer, you can then quickly step through the runs, viewing the input, output, and any existing tags before adding your own feedback.We often use this for a couple of reasons:To assess subjective qualities that automatic evaluators struggle with, like', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'})],\n",
" 'answer': 'LangSmith simplifies the initial setup for building reliable LLM applications, but it acknowledges that there is still work needed to bring the performance of prompts, chains, and agents up to the level where they are reliable enough to be used in production. It also provides the capability to manually review and annotate runs through annotation queues, allowing you to select runs based on criteria like model type or automatic evaluation scores for human review. This feature is particularly useful for assessing subjective qualities that automatic evaluators struggle with.'}"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"conversational_retrieval_chain.invoke(\n",
" {\n",
" \"messages\": [\n",
" HumanMessage(content=\"Can LangSmith help test my LLM applications?\"),\n",
" AIMessage(\n",
" content=\"Yes, LangSmith can help test and evaluate your LLM applications. It allows you to quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs. Additionally, LangSmith can be used to monitor your application, log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise.\"\n",
" ),\n",
" HumanMessage(content=\"Tell me more!\"),\n",
" ],\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can check out [this LangSmith trace](https://smith.langchain.com/public/bb329a3b-e92a-4063-ad78-43f720fbb5a2/r) to see the internal query transformation step for yourself.\n",
"\n",
"## Streaming\n",
"\n",
"Because this chain is constructed with LCEL, you can use familiar methods like `.stream()` with it:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'messages': [HumanMessage(content='Can LangSmith help test my LLM applications?'), AIMessage(content='Yes, LangSmith can help test and evaluate your LLM applications. It allows you to quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs. Additionally, LangSmith can be used to monitor your application, log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise.'), HumanMessage(content='Tell me more!')]}\n",
"{'context': [Document(page_content='LangSmith Overview and User Guide | 🦜️🛠️ LangSmith', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}), Document(page_content='You can also quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs.Monitoring\\u200bAfter all this, your app might finally ready to go in production. LangSmith can also be used to monitor your application in much the same way that you used for debugging. You can log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise. Each run can also be', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}), Document(page_content='Skip to main content🦜🛠 LangSmith DocsPython DocsJS/TS DocsSearchGo to AppLangSmithOverviewTracingTesting & EvaluationOrganizationsHubLangSmith CookbookOverviewOn this pageLangSmith Overview and User GuideBuilding reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.Over the past two months, we at LangChain', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'}), Document(page_content='LangSmith makes it easy to manually review and annotate runs through annotation queues.These queues allow you to select any runs based on criteria like model type or automatic evaluation scores, and queue them up for human review. As a reviewer, you can then quickly step through the runs, viewing the input, output, and any existing tags before adding your own feedback.We often use this for a couple of reasons:To assess subjective qualities that automatic evaluators struggle with, like', metadata={'description': 'Building reliable LLM applications can be challenging. LangChain simplifies the initial setup, but there is still work needed to bring the performance of prompts, chains and agents up the level where they are reliable enough to be used in production.', 'language': 'en', 'source': 'https://docs.smith.langchain.com/overview', 'title': 'LangSmith Overview and User Guide | 🦜️🛠️ LangSmith'})]}\n",
"{'answer': ''}\n",
"{'answer': 'Lang'}\n",
"{'answer': 'Smith'}\n",
"{'answer': ' simpl'}\n",
"{'answer': 'ifies'}\n",
"{'answer': ' the'}\n",
"{'answer': ' initial'}\n",
"{'answer': ' setup'}\n",
"{'answer': ' for'}\n",
"{'answer': ' building'}\n",
"{'answer': ' reliable'}\n",
"{'answer': ' L'}\n",
"{'answer': 'LM'}\n",
"{'answer': ' applications'}\n",
"{'answer': '.'}\n",
"{'answer': ' It'}\n",
"{'answer': ' provides'}\n",
"{'answer': ' features'}\n",
"{'answer': ' for'}\n",
"{'answer': ' manually'}\n",
"{'answer': ' reviewing'}\n",
"{'answer': ' and'}\n",
"{'answer': ' annot'}\n",
"{'answer': 'ating'}\n",
"{'answer': ' runs'}\n",
"{'answer': ' through'}\n",
"{'answer': ' annotation'}\n",
"{'answer': ' queues'}\n",
"{'answer': ','}\n",
"{'answer': ' allowing'}\n",
"{'answer': ' you'}\n",
"{'answer': ' to'}\n",
"{'answer': ' select'}\n",
"{'answer': ' runs'}\n",
"{'answer': ' based'}\n",
"{'answer': ' on'}\n",
"{'answer': ' criteria'}\n",
"{'answer': ' like'}\n",
"{'answer': ' model'}\n",
"{'answer': ' type'}\n",
"{'answer': ' or'}\n",
"{'answer': ' automatic'}\n",
"{'answer': ' evaluation'}\n",
"{'answer': ' scores'}\n",
"{'answer': ','}\n",
"{'answer': ' and'}\n",
"{'answer': ' queue'}\n",
"{'answer': ' them'}\n",
"{'answer': ' up'}\n",
"{'answer': ' for'}\n",
"{'answer': ' human'}\n",
"{'answer': ' review'}\n",
"{'answer': '.'}\n",
"{'answer': ' As'}\n",
"{'answer': ' a'}\n",
"{'answer': ' reviewer'}\n",
"{'answer': ','}\n",
"{'answer': ' you'}\n",
"{'answer': ' can'}\n",
"{'answer': ' quickly'}\n",
"{'answer': ' step'}\n",
"{'answer': ' through'}\n",
"{'answer': ' the'}\n",
"{'answer': ' runs'}\n",
"{'answer': ','}\n",
"{'answer': ' view'}\n",
"{'answer': ' the'}\n",
"{'answer': ' input'}\n",
"{'answer': ','}\n",
"{'answer': ' output'}\n",
"{'answer': ','}\n",
"{'answer': ' and'}\n",
"{'answer': ' any'}\n",
"{'answer': ' existing'}\n",
"{'answer': ' tags'}\n",
"{'answer': ' before'}\n",
"{'answer': ' adding'}\n",
"{'answer': ' your'}\n",
"{'answer': ' own'}\n",
"{'answer': ' feedback'}\n",
"{'answer': '.'}\n",
"{'answer': ' This'}\n",
"{'answer': ' can'}\n",
"{'answer': ' be'}\n",
"{'answer': ' particularly'}\n",
"{'answer': ' useful'}\n",
"{'answer': ' for'}\n",
"{'answer': ' assessing'}\n",
"{'answer': ' subjective'}\n",
"{'answer': ' qualities'}\n",
"{'answer': ' that'}\n",
"{'answer': ' automatic'}\n",
"{'answer': ' evalu'}\n",
"{'answer': 'ators'}\n",
"{'answer': ' struggle'}\n",
"{'answer': ' with'}\n",
"{'answer': '.'}\n",
"{'answer': ''}\n"
]
}
],
"source": [
"stream = conversational_retrieval_chain.stream(\n",
" {\n",
" \"messages\": [\n",
" HumanMessage(content=\"Can LangSmith help test my LLM applications?\"),\n",
" AIMessage(\n",
" content=\"Yes, LangSmith can help test and evaluate your LLM applications. It allows you to quickly edit examples and add them to datasets to expand the surface area of your evaluation sets or to fine-tune a model for improved quality or reduced costs. Additionally, LangSmith can be used to monitor your application, log all traces, visualize latency and token usage statistics, and troubleshoot specific issues as they arise.\"\n",
" ),\n",
" HumanMessage(content=\"Tell me more!\"),\n",
" ],\n",
" }\n",
")\n",
"\n",
"for chunk in stream:\n",
" print(chunk)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Further reading\n",
"\n",
"This guide only scratches the surface of retrieval techniques. For more on different ways of ingesting, preparing, and retrieving the most relevant data, check out [this section](/docs/modules/data_connection/) of the docs."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,465 @@
{
"cells": [
{
"cell_type": "raw",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 3\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to add tools to chatbots\n",
"\n",
"This section will cover how to create conversational agents: chatbots that can interact with other systems and APIs using tools.\n",
"\n",
"Before reading this guide, we recommend you read both [the chatbot quickstart](/docs/use_cases/chatbots/quickstart) in this section and be familiar with [the documentation on agents](/docs/tutorials/agents).\n",
"\n",
"## Setup\n",
"\n",
"For this guide, we'll be using an [OpenAI tools agent](/docs/modules/agents/agent_types/openai_tools) with a single tool for searching the web. The default will be powered by [Tavily](/docs/integrations/tools/tavily_search), but you can switch it out for any similar tool. The rest of this section will assume you're using Tavily.\n",
"\n",
"You'll need to [sign up for an account](https://tavily.com/) on the Tavily website, and install the following packages:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARNING: You are using pip version 22.0.4; however, version 23.3.2 is available.\n",
"You should consider upgrading via the '/Users/jacoblee/.pyenv/versions/3.10.5/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n",
"\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
]
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%pip install --upgrade --quiet langchain-openai tavily-python\n",
"\n",
"# Set env var OPENAI_API_KEY or load from a .env file:\n",
"import dotenv\n",
"\n",
"dotenv.load_dotenv()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You will also need your OpenAI key set as `OPENAI_API_KEY` and your Tavily API key set as `TAVILY_API_KEY`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Creating an agent\n",
"\n",
"Our end goal is to create an agent that can respond conversationally to user questions while looking up information as needed.\n",
"\n",
"First, let's initialize Tavily and an OpenAI chat model capable of tool calling:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"tools = [TavilySearchResults(max_results=1)]\n",
"\n",
"# Choose the LLM that will drive the agent\n",
"# Only certain models support this\n",
"chat = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", temperature=0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To make our agent conversational, we must also choose a prompt with a placeholder for our chat history. Here's an example:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"\n",
"# Adapted from https://smith.langchain.com/hub/hwchase17/openai-tools-agent\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful assistant. You may not need to use tools for every query - the user may just want to chat!\",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"messages\"),\n",
" MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
" ]\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Great! Now let's assemble our agent:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"from langchain.agents import AgentExecutor, create_openai_tools_agent\n",
"\n",
"agent = create_openai_tools_agent(chat, tools, prompt)\n",
"\n",
"agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Running the agent\n",
"\n",
"Now that we've set up our agent, let's try interacting with it! It can handle both trivial queries that require no lookup:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mHello Nemo! It's great to meet you. How can I assist you today?\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content=\"I'm Nemo!\")],\n",
" 'output': \"Hello Nemo! It's great to meet you. How can I assist you today?\"}"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import HumanMessage\n",
"\n",
"agent_executor.invoke({\"messages\": [HumanMessage(content=\"I'm Nemo!\")]})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Or, it can use of the passed search tool to get up to date information if needed:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `tavily_search_results_json` with `{'query': 'current conservation status of the Great Barrier Reef'}`\n",
"\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3m[{'url': 'https://www.barrierreef.org/news/blog/this-is-the-critical-decade-for-coral-reef-survival', 'content': \"global coral reef conservation. © 2024 Great Barrier Reef Foundation. Website by bigfish.tv #Related News · 29 January 2024 290m more baby corals to help restore and protect the Great Barrier Reef Great Barrier Reef Foundation Managing Director Anna Marsden says its not too late if we act now.The Status of Coral Reefs of the World: 2020 report is the largest analysis of global coral reef health ever undertaken. It found that 14 per cent of the world's coral has been lost since 2009. The report also noted, however, that some of these corals recovered during the 10 years to 2019.\"}]\u001b[0m\u001b[32;1m\u001b[1;3mThe current conservation status of the Great Barrier Reef is a critical concern. According to the Great Barrier Reef Foundation, the Status of Coral Reefs of the World: 2020 report found that 14% of the world's coral has been lost since 2009. However, the report also noted that some of these corals recovered during the 10 years to 2019. For more information, you can visit the following link: [Great Barrier Reef Foundation - Conservation Status](https://www.barrierreef.org/news/blog/this-is-the-critical-decade-for-coral-reef-survival)\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content='What is the current conservation status of the Great Barrier Reef?')],\n",
" 'output': \"The current conservation status of the Great Barrier Reef is a critical concern. According to the Great Barrier Reef Foundation, the Status of Coral Reefs of the World: 2020 report found that 14% of the world's coral has been lost since 2009. However, the report also noted that some of these corals recovered during the 10 years to 2019. For more information, you can visit the following link: [Great Barrier Reef Foundation - Conservation Status](https://www.barrierreef.org/news/blog/this-is-the-critical-decade-for-coral-reef-survival)\"}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_executor.invoke(\n",
" {\n",
" \"messages\": [\n",
" HumanMessage(\n",
" content=\"What is the current conservation status of the Great Barrier Reef?\"\n",
" )\n",
" ],\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conversational responses\n",
"\n",
"Because our prompt contains a placeholder for chat history messages, our agent can also take previous interactions into account and respond conversationally like a standard chatbot:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mYour name is Nemo!\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content=\"I'm Nemo!\"),\n",
" AIMessage(content='Hello Nemo! How can I assist you today?'),\n",
" HumanMessage(content='What is my name?')],\n",
" 'output': 'Your name is Nemo!'}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import AIMessage, HumanMessage\n",
"\n",
"agent_executor.invoke(\n",
" {\n",
" \"messages\": [\n",
" HumanMessage(content=\"I'm Nemo!\"),\n",
" AIMessage(content=\"Hello Nemo! How can I assist you today?\"),\n",
" HumanMessage(content=\"What is my name?\"),\n",
" ],\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If preferred, you can also wrap the agent executor in a `RunnableWithMessageHistory` class to internally manage history messages. First, we need to slightly modify the prompt to take a separate input variable so that the wrapper can parse which input value to store as history:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# Adapted from https://smith.langchain.com/hub/hwchase17/openai-tools-agent\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful assistant. You may not need to use tools for every query - the user may just want to chat!\",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"chat_history\"),\n",
" (\"human\", \"{input}\"),\n",
" MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
" ]\n",
")\n",
"\n",
"agent = create_openai_tools_agent(chat, tools, prompt)\n",
"\n",
"agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then, because our agent executor has multiple outputs, we also have to set the `output_messages_key` property when initializing the wrapper:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"from langchain.memory import ChatMessageHistory\n",
"from langchain_core.runnables.history import RunnableWithMessageHistory\n",
"\n",
"demo_ephemeral_chat_history_for_chain = ChatMessageHistory()\n",
"\n",
"conversational_agent_executor = RunnableWithMessageHistory(\n",
" agent_executor,\n",
" lambda session_id: demo_ephemeral_chat_history_for_chain,\n",
" input_messages_key=\"input\",\n",
" output_messages_key=\"output\",\n",
" history_messages_key=\"chat_history\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mHi Nemo! It's great to meet you. How can I assist you today?\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'input': \"I'm Nemo!\",\n",
" 'chat_history': [],\n",
" 'output': \"Hi Nemo! It's great to meet you. How can I assist you today?\"}"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"conversational_agent_executor.invoke(\n",
" {\n",
" \"input\": \"I'm Nemo!\",\n",
" },\n",
" {\"configurable\": {\"session_id\": \"unused\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mYour name is Nemo! How can I assist you today, Nemo?\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'input': 'What is my name?',\n",
" 'chat_history': [HumanMessage(content=\"I'm Nemo!\"),\n",
" AIMessage(content=\"Hi Nemo! It's great to meet you. How can I assist you today?\")],\n",
" 'output': 'Your name is Nemo! How can I assist you today, Nemo?'}"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"conversational_agent_executor.invoke(\n",
" {\n",
" \"input\": \"What is my name?\",\n",
" },\n",
" {\"configurable\": {\"session_id\": \"unused\"}},\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Further reading\n",
"\n",
"Other types agents can also support conversational responses too - for more, check out the [agents section](/docs/tutorials/agents).\n",
"\n",
"For more on tool usage, you can also check out [this use case section](/docs/use_cases/tool_use/)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,750 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "44b9976d",
"metadata": {},
"source": [
"# How to split code\n",
"\n",
"[RecursiveCharacterTextSplitter](https://api.python.langchain.com/en/latest/character/langchain_text_splitters.character.RecursiveCharacterTextSplitter.html) includes pre-built lists of separators that are useful for splitting text in a specific programming language.\n",
"\n",
"Supported languages are stored in the `langchain_text_splitters.Language` enum. They include:\n",
"\n",
"```\n",
"\"cpp\",\n",
"\"go\",\n",
"\"java\",\n",
"\"kotlin\",\n",
"\"js\",\n",
"\"ts\",\n",
"\"php\",\n",
"\"proto\",\n",
"\"python\",\n",
"\"rst\",\n",
"\"ruby\",\n",
"\"rust\",\n",
"\"scala\",\n",
"\"swift\",\n",
"\"markdown\",\n",
"\"latex\",\n",
"\"html\",\n",
"\"sol\",\n",
"\"csharp\",\n",
"\"cobol\",\n",
"\"c\",\n",
"\"lua\",\n",
"\"perl\",\n",
"\"haskell\"\n",
"```\n",
"\n",
"To view the list of separators for a given language, pass a value from this enum into\n",
"```python\n",
"RecursiveCharacterTextSplitter.get_separators_for_language`\n",
"```\n",
"\n",
"To instantiate a splitter that is tailored for a specific language, pass a value from the enum into\n",
"```python\n",
"RecursiveCharacterTextSplitter.from_language\n",
"```\n",
"\n",
"Below we demonstrate examples for the various languages."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e4144de-d925-4d4c-91c3-685ef8baa57c",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain-text-splitters"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "a9e37aa1",
"metadata": {},
"outputs": [],
"source": [
"from langchain_text_splitters import (\n",
" Language,\n",
" RecursiveCharacterTextSplitter,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "082807cb-dfba-4495-af12-0441f63f30e1",
"metadata": {},
"source": [
"To view the full list of supported languages:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e21a2434",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['cpp',\n",
" 'go',\n",
" 'java',\n",
" 'kotlin',\n",
" 'js',\n",
" 'ts',\n",
" 'php',\n",
" 'proto',\n",
" 'python',\n",
" 'rst',\n",
" 'ruby',\n",
" 'rust',\n",
" 'scala',\n",
" 'swift',\n",
" 'markdown',\n",
" 'latex',\n",
" 'html',\n",
" 'sol',\n",
" 'csharp',\n",
" 'cobol',\n",
" 'c',\n",
" 'lua',\n",
" 'perl',\n",
" 'haskell']"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[e.value for e in Language]"
]
},
{
"cell_type": "markdown",
"id": "56669f16-266a-4820-a7e7-d90ade9e642f",
"metadata": {},
"source": [
"You can also see the separators used for a given language:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c92fb913",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['\\nclass ', '\\ndef ', '\\n\\tdef ', '\\n\\n', '\\n', ' ', '']"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"RecursiveCharacterTextSplitter.get_separators_for_language(Language.PYTHON)"
]
},
{
"cell_type": "markdown",
"id": "dcb8931b",
"metadata": {},
"source": [
"## Python\n",
"\n",
"Here's an example using the PythonTextSplitter:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "a58512b9",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='def hello_world():\\n print(\"Hello, World!\")'),\n",
" Document(page_content='# Call the function\\nhello_world()')]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"PYTHON_CODE = \"\"\"\n",
"def hello_world():\n",
" print(\"Hello, World!\")\n",
"\n",
"# Call the function\n",
"hello_world()\n",
"\"\"\"\n",
"python_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.PYTHON, chunk_size=50, chunk_overlap=0\n",
")\n",
"python_docs = python_splitter.create_documents([PYTHON_CODE])\n",
"python_docs"
]
},
{
"cell_type": "markdown",
"id": "354f60a5",
"metadata": {},
"source": [
"## JS\n",
"Here's an example using the JS text splitter:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7db0d486",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='function helloWorld() {\\n console.log(\"Hello, World!\");\\n}'),\n",
" Document(page_content='// Call the function\\nhelloWorld();')]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"JS_CODE = \"\"\"\n",
"function helloWorld() {\n",
" console.log(\"Hello, World!\");\n",
"}\n",
"\n",
"// Call the function\n",
"helloWorld();\n",
"\"\"\"\n",
"\n",
"js_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.JS, chunk_size=60, chunk_overlap=0\n",
")\n",
"js_docs = js_splitter.create_documents([JS_CODE])\n",
"js_docs"
]
},
{
"cell_type": "markdown",
"id": "a739f545",
"metadata": {},
"source": [
"## TS\n",
"Here's an example using the TS text splitter:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "aee738a4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='function helloWorld(): void {'),\n",
" Document(page_content='console.log(\"Hello, World!\");\\n}'),\n",
" Document(page_content='// Call the function\\nhelloWorld();')]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"TS_CODE = \"\"\"\n",
"function helloWorld(): void {\n",
" console.log(\"Hello, World!\");\n",
"}\n",
"\n",
"// Call the function\n",
"helloWorld();\n",
"\"\"\"\n",
"\n",
"ts_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.TS, chunk_size=60, chunk_overlap=0\n",
")\n",
"ts_docs = ts_splitter.create_documents([TS_CODE])\n",
"ts_docs"
]
},
{
"cell_type": "markdown",
"id": "ee2361f8",
"metadata": {},
"source": [
"## Markdown\n",
"\n",
"Here's an example using the Markdown text splitter:\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "ac9295d3",
"metadata": {},
"outputs": [],
"source": [
"markdown_text = \"\"\"\n",
"# 🦜️🔗 LangChain\n",
"\n",
"⚡ Building applications with LLMs through composability ⚡\n",
"\n",
"## Quick Install\n",
"\n",
"```bash\n",
"# Hopefully this code block isn't split\n",
"pip install langchain\n",
"```\n",
"\n",
"As an open-source project in a rapidly developing field, we are extremely open to contributions.\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "3a0cb17a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='# 🦜️🔗 LangChain'),\n",
" Document(page_content='⚡ Building applications with LLMs through composability ⚡'),\n",
" Document(page_content='## Quick Install\\n\\n```bash'),\n",
" Document(page_content=\"# Hopefully this code block isn't split\"),\n",
" Document(page_content='pip install langchain'),\n",
" Document(page_content='```'),\n",
" Document(page_content='As an open-source project in a rapidly developing field, we'),\n",
" Document(page_content='are extremely open to contributions.')]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"md_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.MARKDOWN, chunk_size=60, chunk_overlap=0\n",
")\n",
"md_docs = md_splitter.create_documents([markdown_text])\n",
"md_docs"
]
},
{
"cell_type": "markdown",
"id": "7aa306f6",
"metadata": {},
"source": [
"## Latex\n",
"\n",
"Here's an example on Latex text:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "77d1049d",
"metadata": {},
"outputs": [],
"source": [
"latex_text = \"\"\"\n",
"\\documentclass{article}\n",
"\n",
"\\begin{document}\n",
"\n",
"\\maketitle\n",
"\n",
"\\section{Introduction}\n",
"Large language models (LLMs) are a type of machine learning model that can be trained on vast amounts of text data to generate human-like language. In recent years, LLMs have made significant advances in a variety of natural language processing tasks, including language translation, text generation, and sentiment analysis.\n",
"\n",
"\\subsection{History of LLMs}\n",
"The earliest LLMs were developed in the 1980s and 1990s, but they were limited by the amount of data that could be processed and the computational power available at the time. In the past decade, however, advances in hardware and software have made it possible to train LLMs on massive datasets, leading to significant improvements in performance.\n",
"\n",
"\\subsection{Applications of LLMs}\n",
"LLMs have many applications in industry, including chatbots, content creation, and virtual assistants. They can also be used in academia for research in linguistics, psychology, and computational linguistics.\n",
"\n",
"\\end{document}\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "4dbc47e1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='\\\\documentclass{article}\\n\\n\\x08egin{document}\\n\\n\\\\maketitle'),\n",
" Document(page_content='\\\\section{Introduction}'),\n",
" Document(page_content='Large language models (LLMs) are a type of machine learning'),\n",
" Document(page_content='model that can be trained on vast amounts of text data to'),\n",
" Document(page_content='generate human-like language. In recent years, LLMs have'),\n",
" Document(page_content='made significant advances in a variety of natural language'),\n",
" Document(page_content='processing tasks, including language translation, text'),\n",
" Document(page_content='generation, and sentiment analysis.'),\n",
" Document(page_content='\\\\subsection{History of LLMs}'),\n",
" Document(page_content='The earliest LLMs were developed in the 1980s and 1990s,'),\n",
" Document(page_content='but they were limited by the amount of data that could be'),\n",
" Document(page_content='processed and the computational power available at the'),\n",
" Document(page_content='time. In the past decade, however, advances in hardware and'),\n",
" Document(page_content='software have made it possible to train LLMs on massive'),\n",
" Document(page_content='datasets, leading to significant improvements in'),\n",
" Document(page_content='performance.'),\n",
" Document(page_content='\\\\subsection{Applications of LLMs}'),\n",
" Document(page_content='LLMs have many applications in industry, including'),\n",
" Document(page_content='chatbots, content creation, and virtual assistants. They'),\n",
" Document(page_content='can also be used in academia for research in linguistics,'),\n",
" Document(page_content='psychology, and computational linguistics.'),\n",
" Document(page_content='\\\\end{document}')]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"latex_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.MARKDOWN, chunk_size=60, chunk_overlap=0\n",
")\n",
"latex_docs = latex_splitter.create_documents([latex_text])\n",
"latex_docs"
]
},
{
"cell_type": "markdown",
"id": "c29adadf",
"metadata": {},
"source": [
"## HTML\n",
"\n",
"Here's an example using an HTML text splitter:\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "0fc78794",
"metadata": {},
"outputs": [],
"source": [
"html_text = \"\"\"\n",
"<!DOCTYPE html>\n",
"<html>\n",
" <head>\n",
" <title>🦜️🔗 LangChain</title>\n",
" <style>\n",
" body {\n",
" font-family: Arial, sans-serif;\n",
" }\n",
" h1 {\n",
" color: darkblue;\n",
" }\n",
" </style>\n",
" </head>\n",
" <body>\n",
" <div>\n",
" <h1>🦜️🔗 LangChain</h1>\n",
" <p>⚡ Building applications with LLMs through composability ⚡</p>\n",
" </div>\n",
" <div>\n",
" As an open-source project in a rapidly developing field, we are extremely open to contributions.\n",
" </div>\n",
" </body>\n",
"</html>\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "e3e3fca1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='<!DOCTYPE html>\\n<html>'),\n",
" Document(page_content='<head>\\n <title>🦜️🔗 LangChain</title>'),\n",
" Document(page_content='<style>\\n body {\\n font-family: Aria'),\n",
" Document(page_content='l, sans-serif;\\n }\\n h1 {'),\n",
" Document(page_content='color: darkblue;\\n }\\n </style>\\n </head'),\n",
" Document(page_content='>'),\n",
" Document(page_content='<body>'),\n",
" Document(page_content='<div>\\n <h1>🦜️🔗 LangChain</h1>'),\n",
" Document(page_content='<p>⚡ Building applications with LLMs through composability ⚡'),\n",
" Document(page_content='</p>\\n </div>'),\n",
" Document(page_content='<div>\\n As an open-source project in a rapidly dev'),\n",
" Document(page_content='eloping field, we are extremely open to contributions.'),\n",
" Document(page_content='</div>\\n </body>\\n</html>')]"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"html_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.HTML, chunk_size=60, chunk_overlap=0\n",
")\n",
"html_docs = html_splitter.create_documents([html_text])\n",
"html_docs"
]
},
{
"cell_type": "markdown",
"id": "fcaf7abf",
"metadata": {},
"source": [
"## Solidity\n",
"Here's an example using the Solidity text splitter:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "49a1df11",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='pragma solidity ^0.8.20;'),\n",
" Document(page_content='contract HelloWorld {\\n function add(uint a, uint b) pure public returns(uint) {\\n return a + b;\\n }\\n}')]"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"SOL_CODE = \"\"\"\n",
"pragma solidity ^0.8.20;\n",
"contract HelloWorld {\n",
" function add(uint a, uint b) pure public returns(uint) {\n",
" return a + b;\n",
" }\n",
"}\n",
"\"\"\"\n",
"\n",
"sol_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.SOL, chunk_size=128, chunk_overlap=0\n",
")\n",
"sol_docs = sol_splitter.create_documents([SOL_CODE])\n",
"sol_docs"
]
},
{
"cell_type": "markdown",
"id": "edd0052c",
"metadata": {},
"source": [
"## C#\n",
"Here's an example using the C# text splitter:\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "1524ae0f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='using System;'),\n",
" Document(page_content='class Program\\n{\\n static void Main()\\n {\\n int age = 30; // Change the age value as needed'),\n",
" Document(page_content='// Categorize the age without any console output\\n if (age < 18)\\n {\\n // Age is under 18'),\n",
" Document(page_content='}\\n else if (age >= 18 && age < 65)\\n {\\n // Age is an adult\\n }\\n else\\n {'),\n",
" Document(page_content='// Age is a senior citizen\\n }\\n }\\n}')]"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"C_CODE = \"\"\"\n",
"using System;\n",
"class Program\n",
"{\n",
" static void Main()\n",
" {\n",
" int age = 30; // Change the age value as needed\n",
"\n",
" // Categorize the age without any console output\n",
" if (age < 18)\n",
" {\n",
" // Age is under 18\n",
" }\n",
" else if (age >= 18 && age < 65)\n",
" {\n",
" // Age is an adult\n",
" }\n",
" else\n",
" {\n",
" // Age is a senior citizen\n",
" }\n",
" }\n",
"}\n",
"\"\"\"\n",
"c_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.CSHARP, chunk_size=128, chunk_overlap=0\n",
")\n",
"c_docs = c_splitter.create_documents([C_CODE])\n",
"c_docs"
]
},
{
"cell_type": "markdown",
"id": "af9de667-230e-4c2a-8c5f-122a28515d97",
"metadata": {},
"source": [
"## Haskell\n",
"Here's an example using the Haskell text splitter:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "688185b5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='main :: IO ()'),\n",
" Document(page_content='main = do\\n putStrLn \"Hello, World!\"\\n-- Some'),\n",
" Document(page_content='sample functions\\nadd :: Int -> Int -> Int\\nadd x y'),\n",
" Document(page_content='= x + y')]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"HASKELL_CODE = \"\"\"\n",
"main :: IO ()\n",
"main = do\n",
" putStrLn \"Hello, World!\"\n",
"-- Some sample functions\n",
"add :: Int -> Int -> Int\n",
"add x y = x + y\n",
"\"\"\"\n",
"haskell_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.HASKELL, chunk_size=50, chunk_overlap=0\n",
")\n",
"haskell_docs = haskell_splitter.create_documents([HASKELL_CODE])\n",
"haskell_docs"
]
},
{
"cell_type": "markdown",
"id": "4a11f7cd-cd85-430c-b307-5b5b5f07f8db",
"metadata": {},
"source": [
"## PHP\n",
"Here's an example using the PHP text splitter:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "90c66e7e-87a5-4a81-bece-7949aabf2369",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='<?php\\nnamespace foo;'),\n",
" Document(page_content='class Hello {'),\n",
" Document(page_content='public function __construct() { }\\n}'),\n",
" Document(page_content='function hello() {\\n echo \"Hello World!\";\\n}'),\n",
" Document(page_content='interface Human {\\n public function breath();\\n}'),\n",
" Document(page_content='trait Foo { }\\nenum Color\\n{\\n case Red;'),\n",
" Document(page_content='case Blue;\\n}')]"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"PHP_CODE = \"\"\"<?php\n",
"namespace foo;\n",
"class Hello {\n",
" public function __construct() { }\n",
"}\n",
"function hello() {\n",
" echo \"Hello World!\";\n",
"}\n",
"interface Human {\n",
" public function breath();\n",
"}\n",
"trait Foo { }\n",
"enum Color\n",
"{\n",
" case Red;\n",
" case Blue;\n",
"}\"\"\"\n",
"php_splitter = RecursiveCharacterTextSplitter.from_language(\n",
" language=Language.PHP, chunk_size=50, chunk_overlap=0\n",
")\n",
"haskell_docs = php_splitter.create_documents([PHP_CODE])\n",
"haskell_docs"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,621 @@
{
"cells": [
{
"cell_type": "raw",
"id": "9ede5870",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 7\n",
"keywords: [ConfigurableField, configurable_fields, ConfigurableAlternatives, configurable_alternatives, LCEL]\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "39eaf61b",
"metadata": {},
"source": [
"# How to configure runtime chain internals\n",
"\n",
"Sometimes you may want to experiment with, or even expose to the end user, multiple different ways of doing things within your chains.\n",
"This can include tweaking parameters such as temperature or even swapping out one model for another.\n",
"In order to make this experience as easy as possible, we have defined two methods.\n",
"\n",
"- A `configurable_fields` method. This lets you configure particular fields of a runnable.\n",
" - This is related to the [`.bind`](/docs/how_to/binding) method on runnables, but allows you to specify parameters for a given step in a chain at runtime rather than specifying them beforehand.\n",
"- A `configurable_alternatives` method. With this method, you can list out alternatives for any particular runnable that can be set during runtime, and swap them for those specified alternatives.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
"- [Chaining runnables](/docs/how_to/sequence/)\n",
"- [Binding runtime arguments](/docs/how_to/binding/)\n",
"`} />\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "f2347a11",
"metadata": {},
"source": [
"## Configurable Fields\n",
"\n",
"Let's walk through an example that configures chat model fields like temperature at runtime:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "40ed76a2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARNING: You are using pip version 22.0.4; however, version 24.0 is available.\n",
"You should consider upgrading via the '/Users/jacoblee/.pyenv/versions/3.10.5/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n",
"\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install --upgrade --quiet langchain langchain-openai\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "7ba735f4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='17', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 11, 'total_tokens': 12}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-ba26a0da-0a69-4533-ab7f-21178a73d303-0')"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.prompts import PromptTemplate\n",
"from langchain_core.runnables import ConfigurableField\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"model = ChatOpenAI(temperature=0).configurable_fields(\n",
" temperature=ConfigurableField(\n",
" id=\"llm_temperature\",\n",
" name=\"LLM Temperature\",\n",
" description=\"The temperature of the LLM\",\n",
" )\n",
")\n",
"\n",
"model.invoke(\"pick a random number\")"
]
},
{
"cell_type": "markdown",
"id": "b0f74589",
"metadata": {},
"source": [
"Above, we defined `temperature` as a [`ConfigurableField`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.utils.ConfigurableField.html#langchain_core.runnables.utils.ConfigurableField) that we can set at runtime. To do so, we use the [`with_config`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.with_config) method like this:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4f83245c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='12', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 11, 'total_tokens': 12}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-ba8422ad-be77-4cb1-ac45-ad0aae74e3d9-0')"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.with_config(configurable={\"llm_temperature\": 0.9}).invoke(\"pick a random number\")"
]
},
{
"cell_type": "markdown",
"id": "9da1fcd2",
"metadata": {},
"source": [
"Note that the passed `llm_temperature` entry in the dict has the same key as the `id` of the `ConfigurableField`.\n",
"\n",
"We can also do this to affect just one step that's part of a chain:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "e75ae678",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='27', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 14, 'total_tokens': 15}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-ecd4cadd-1b72-4f92-b9a0-15e08091f537-0')"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prompt = PromptTemplate.from_template(\"Pick a random number above {x}\")\n",
"chain = prompt | model\n",
"\n",
"chain.invoke({\"x\": 0})"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "c09fac15",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='35', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 14, 'total_tokens': 15}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-a916602b-3460-46d3-a4a8-7c926ec747c0-0')"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain.with_config(configurable={\"llm_temperature\": 0.9}).invoke({\"x\": 0})"
]
},
{
"cell_type": "markdown",
"id": "fb9637d0",
"metadata": {},
"source": [
"### With HubRunnables\n",
"\n",
"This is useful to allow for switching of prompts"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "9a9ea077",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"ChatPromptValue(messages=[HumanMessage(content=\"You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\\nQuestion: foo \\nContext: bar \\nAnswer:\")])"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.runnables.hub import HubRunnable\n",
"\n",
"prompt = HubRunnable(\"rlm/rag-prompt\").configurable_fields(\n",
" owner_repo_commit=ConfigurableField(\n",
" id=\"hub_commit\",\n",
" name=\"Hub Commit\",\n",
" description=\"The Hub commit to pull from\",\n",
" )\n",
")\n",
"\n",
"prompt.invoke({\"question\": \"foo\", \"context\": \"bar\"})"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "f33f3cf2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"ChatPromptValue(messages=[HumanMessage(content=\"[INST]<<SYS>> You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.<</SYS>> \\nQuestion: foo \\nContext: bar \\nAnswer: [/INST]\")])"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prompt.with_config(configurable={\"hub_commit\": \"rlm/rag-prompt-llama\"}).invoke(\n",
" {\"question\": \"foo\", \"context\": \"bar\"}\n",
")"
]
},
{
"cell_type": "markdown",
"id": "79d51519",
"metadata": {},
"source": [
"## Configurable Alternatives\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "ac733d35",
"metadata": {},
"source": [
"The `configurable_alternatives()` method allows us to swap out steps in a chain with an alternative. Below, we swap out one chat model for another:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "3db59f45",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARNING: You are using pip version 22.0.4; however, version 24.0 is available.\n",
"You should consider upgrading via the '/Users/jacoblee/.pyenv/versions/3.10.5/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n",
"\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install --upgrade --quiet langchain-anthropic\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"ANTHROPIC_API_KEY\"] = getpass()"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "71248a9f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"Here's a bear joke for you:\\n\\nWhy don't bears wear socks? \\nBecause they have bear feet!\\n\\nHow's that? I tried to come up with a simple, silly pun-based joke about bears. Puns and wordplay are a common way to create humorous bear jokes. Let me know if you'd like to hear another one!\", response_metadata={'id': 'msg_018edUHh5fUbWdiimhrC3dZD', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 13, 'output_tokens': 80}}, id='run-775bc58c-28d7-4e6b-a268-48fa6661f02f-0')"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.prompts import PromptTemplate\n",
"from langchain_anthropic import ChatAnthropic\n",
"from langchain_core.runnables import ConfigurableField\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatAnthropic(\n",
" model=\"claude-3-haiku-20240307\", temperature=0\n",
").configurable_alternatives(\n",
" # This gives this field an id\n",
" # When configuring the end runnable, we can then use this id to configure this field\n",
" ConfigurableField(id=\"llm\"),\n",
" # This sets a default_key.\n",
" # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used\n",
" default_key=\"anthropic\",\n",
" # This adds a new option, with name `openai` that is equal to `ChatOpenAI()`\n",
" openai=ChatOpenAI(),\n",
" # This adds a new option, with name `gpt4` that is equal to `ChatOpenAI(model=\"gpt-4\")`\n",
" gpt4=ChatOpenAI(model=\"gpt-4\"),\n",
" # You can add more configuration options here\n",
")\n",
"prompt = PromptTemplate.from_template(\"Tell me a joke about {topic}\")\n",
"chain = prompt | llm\n",
"\n",
"# By default it will call Anthropic\n",
"chain.invoke({\"topic\": \"bears\"})"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "48b45337",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"Why don't bears like fast food?\\n\\nBecause they can't catch it!\", response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 13, 'total_tokens': 28}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-7bdaa992-19c9-4f0d-9a0c-1f326bc992d4-0')"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# We can use `.with_config(configurable={\"llm\": \"openai\"})` to specify an llm to use\n",
"chain.with_config(configurable={\"llm\": \"openai\"}).invoke({\"topic\": \"bears\"})"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "42647fb7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"Here's a bear joke for you:\\n\\nWhy don't bears wear socks? \\nBecause they have bear feet!\\n\\nHow's that? I tried to come up with a simple, silly pun-based joke about bears. Puns and wordplay are a common way to create humorous bear jokes. Let me know if you'd like to hear another one!\", response_metadata={'id': 'msg_01BZvbmnEPGBtcxRWETCHkct', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 13, 'output_tokens': 80}}, id='run-59b6ee44-a1cd-41b8-a026-28ee67cdd718-0')"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# If we use the `default_key` then it uses the default\n",
"chain.with_config(configurable={\"llm\": \"anthropic\"}).invoke({\"topic\": \"bears\"})"
]
},
{
"cell_type": "markdown",
"id": "a9134559",
"metadata": {},
"source": [
"### With Prompts\n",
"\n",
"We can do a similar thing, but alternate between prompts\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "9f6a7c6c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"Here's a bear joke for you:\\n\\nWhy don't bears wear socks? \\nBecause they have bear feet!\", response_metadata={'id': 'msg_01DtM1cssjNFZYgeS3gMZ49H', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 13, 'output_tokens': 28}}, id='run-8199af7d-ea31-443d-b064-483693f2e0a1-0')"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"llm = ChatAnthropic(model=\"claude-3-haiku-20240307\", temperature=0)\n",
"prompt = PromptTemplate.from_template(\n",
" \"Tell me a joke about {topic}\"\n",
").configurable_alternatives(\n",
" # This gives this field an id\n",
" # When configuring the end runnable, we can then use this id to configure this field\n",
" ConfigurableField(id=\"prompt\"),\n",
" # This sets a default_key.\n",
" # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used\n",
" default_key=\"joke\",\n",
" # This adds a new option, with name `poem`\n",
" poem=PromptTemplate.from_template(\"Write a short poem about {topic}\"),\n",
" # You can add more configuration options here\n",
")\n",
"chain = prompt | llm\n",
"\n",
"# By default it will write a joke\n",
"chain.invoke({\"topic\": \"bears\"})"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "927297a1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"Here is a short poem about bears:\\n\\nMajestic bears, strong and true,\\nRoaming the forests, wild and free.\\nPowerful paws, fur soft and brown,\\nCommanding respect, nature's crown.\\n\\nForaging for berries, fishing streams,\\nProtecting their young, fierce and keen.\\nMighty bears, a sight to behold,\\nGuardians of the wilderness, untold.\\n\\nIn the wild they reign supreme,\\nEmbodying nature's grand theme.\\nBears, a symbol of strength and grace,\\nCaptivating all who see their face.\", response_metadata={'id': 'msg_01Wck3qPxrjURtutvtodaJFn', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 13, 'output_tokens': 134}}, id='run-69414a1e-51d7-4bec-a307-b34b7d61025e-0')"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# We can configure it write a poem\n",
"chain.with_config(configurable={\"prompt\": \"poem\"}).invoke({\"topic\": \"bears\"})"
]
},
{
"cell_type": "markdown",
"id": "0c77124e",
"metadata": {},
"source": [
"### With Prompts and LLMs\n",
"\n",
"We can also have multiple things configurable!\n",
"Here's an example doing that with both prompts and LLMs."
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "97538c23",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"In the forest deep and wide,\\nBears roam with grace and pride.\\nWith fur as dark as night,\\nThey rule the land with all their might.\\n\\nIn winter's chill, they hibernate,\\nIn spring they emerge, hungry and great.\\nWith claws sharp and eyes so keen,\\nThey hunt for food, fierce and lean.\\n\\nBut beneath their tough exterior,\\nLies a gentle heart, warm and superior.\\nThey love their cubs with all their might,\\nProtecting them through day and night.\\n\\nSo let us admire these majestic creatures,\\nIn awe of their strength and features.\\nFor in the wild, they reign supreme,\\nThe mighty bears, a timeless dream.\", response_metadata={'token_usage': {'completion_tokens': 133, 'prompt_tokens': 13, 'total_tokens': 146}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-5eec0b96-d580-49fd-ac4e-e32a0803b49b-0')"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"llm = ChatAnthropic(\n",
" model=\"claude-3-haiku-20240307\", temperature=0\n",
").configurable_alternatives(\n",
" # This gives this field an id\n",
" # When configuring the end runnable, we can then use this id to configure this field\n",
" ConfigurableField(id=\"llm\"),\n",
" # This sets a default_key.\n",
" # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used\n",
" default_key=\"anthropic\",\n",
" # This adds a new option, with name `openai` that is equal to `ChatOpenAI()`\n",
" openai=ChatOpenAI(),\n",
" # This adds a new option, with name `gpt4` that is equal to `ChatOpenAI(model=\"gpt-4\")`\n",
" gpt4=ChatOpenAI(model=\"gpt-4\"),\n",
" # You can add more configuration options here\n",
")\n",
"prompt = PromptTemplate.from_template(\n",
" \"Tell me a joke about {topic}\"\n",
").configurable_alternatives(\n",
" # This gives this field an id\n",
" # When configuring the end runnable, we can then use this id to configure this field\n",
" ConfigurableField(id=\"prompt\"),\n",
" # This sets a default_key.\n",
" # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used\n",
" default_key=\"joke\",\n",
" # This adds a new option, with name `poem`\n",
" poem=PromptTemplate.from_template(\"Write a short poem about {topic}\"),\n",
" # You can add more configuration options here\n",
")\n",
"chain = prompt | llm\n",
"\n",
"# We can configure it write a poem with OpenAI\n",
"chain.with_config(configurable={\"prompt\": \"poem\", \"llm\": \"openai\"}).invoke(\n",
" {\"topic\": \"bears\"}\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "e4ee9fbc",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they have bear feet!\", response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 13, 'total_tokens': 26}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-c1b14c9c-4988-49b8-9363-15bfd479973a-0')"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# We can always just configure only one if we want\n",
"chain.with_config(configurable={\"llm\": \"openai\"}).invoke({\"topic\": \"bears\"})"
]
},
{
"cell_type": "markdown",
"id": "02fc4841",
"metadata": {},
"source": [
"### Saving configurations\n",
"\n",
"We can also easily save configured chains as their own objects"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "5cf53202",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"Why did the bear break up with his girlfriend? \\nBecause he couldn't bear the relationship anymore!\", response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 13, 'total_tokens': 33}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-391ebd55-9137-458b-9a11-97acaff6a892-0')"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"openai_joke = chain.with_config(configurable={\"llm\": \"openai\"})\n",
"\n",
"openai_joke.invoke({\"topic\": \"bears\"})"
]
},
{
"cell_type": "markdown",
"id": "76702b0e",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You now know how to configure a chain's internal steps at runtime.\n",
"\n",
"To learn more, see the other how-to guides on runnables in this section, including:\n",
"\n",
"- Using [.bind()](/docs/how_to/binding) as a simpler way to set a runnable's runtime parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a43e3b70",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,437 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "612eac0a",
"metadata": {},
"source": [
"# How to do retrieval with contextual compression\n",
"\n",
"One challenge with retrieval is that usually you don't know the specific queries your document storage system will face when you ingest data into the system. This means that the information most relevant to a query may be buried in a document with a lot of irrelevant text. Passing that full document through your application can lead to more expensive LLM calls and poorer responses.\n",
"\n",
"Contextual compression is meant to fix this. The idea is simple: instead of immediately returning retrieved documents as-is, you can compress them using the context of the given query, so that only the relevant information is returned. “Compressing” here refers to both compressing the contents of an individual document and filtering out documents wholesale.\n",
"\n",
"To use the Contextual Compression Retriever, you'll need:\n",
"- a base retriever\n",
"- a Document Compressor\n",
"\n",
"The Contextual Compression Retriever passes queries to the base retriever, takes the initial documents and passes them through the Document Compressor. The Document Compressor takes a list of documents and shortens it by reducing the contents of documents or dropping documents altogether.\n",
"\n",
"![](https://drive.google.com/uc?id=1CtNgWODXZudxAWSRiWgSGEoTNrUFT98v)\n",
"\n",
"## Get started"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e0029369",
"metadata": {},
"outputs": [],
"source": [
"# Helper function for printing docs\n",
"\n",
"\n",
"def pretty_print_docs(docs):\n",
" print(\n",
" f\"\\n{'-' * 100}\\n\".join(\n",
" [f\"Document {i+1}:\\n\\n\" + d.page_content for i, d in enumerate(docs)]\n",
" )\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "9d2360fc",
"metadata": {},
"source": [
"## Using a vanilla vector store retriever\n",
"Let's start by initializing a simple vector store retriever and storing the 2023 State of the Union speech (in chunks). We can see that given an example question our retriever returns one or two relevant docs and a few irrelevant docs. And even the relevant docs have a lot of irrelevant information in them.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "2b0be066",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Document 1:\n",
"\n",
"Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while youre at it, pass the Disclose Act so Americans can know who is funding our elections. \n",
"\n",
"Tonight, Id like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n",
"\n",
"One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n",
"\n",
"And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nations top legal minds, who will continue Justice Breyers legacy of excellence.\n",
"----------------------------------------------------------------------------------------------------\n",
"Document 2:\n",
"\n",
"A former top litigator in private practice. A former federal public defender. And from a family of public school educators and police officers. A consensus builder. Since shes been nominated, shes received a broad range of support—from the Fraternal Order of Police to former judges appointed by Democrats and Republicans. \n",
"\n",
"And if we are to advance liberty and justice, we need to secure the Border and fix the immigration system. \n",
"\n",
"We can do both. At our border, weve installed new technology like cutting-edge scanners to better detect drug smuggling. \n",
"\n",
"Weve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \n",
"\n",
"Were putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \n",
"\n",
"Were securing commitments and supporting partners in South and Central America to host more refugees and secure their own borders.\n",
"----------------------------------------------------------------------------------------------------\n",
"Document 3:\n",
"\n",
"And for our LGBTQ+ Americans, lets finally get the bipartisan Equality Act to my desk. The onslaught of state laws targeting transgender Americans and their families is wrong. \n",
"\n",
"As I said last year, especially to our younger transgender Americans, I will always have your back as your President, so you can be yourself and reach your God-given potential. \n",
"\n",
"While it often appears that we never agree, that isnt true. I signed 80 bipartisan bills into law last year. From preventing government shutdowns to protecting Asian-Americans from still-too-common hate crimes to reforming military justice. \n",
"\n",
"And soon, well strengthen the Violence Against Women Act that I first wrote three decades ago. It is important for us to show the nation that we can come together and do big things. \n",
"\n",
"So tonight Im offering a Unity Agenda for the Nation. Four big things we can do together. \n",
"\n",
"First, beat the opioid epidemic.\n",
"----------------------------------------------------------------------------------------------------\n",
"Document 4:\n",
"\n",
"Tonight, Im announcing a crackdown on these companies overcharging American businesses and consumers. \n",
"\n",
"And as Wall Street firms take over more nursing homes, quality in those homes has gone down and costs have gone up. \n",
"\n",
"That ends on my watch. \n",
"\n",
"Medicare is going to set higher standards for nursing homes and make sure your loved ones get the care they deserve and expect. \n",
"\n",
"Well also cut costs and keep the economy going strong by giving workers a fair shot, provide more training and apprenticeships, hire them based on their skills not degrees. \n",
"\n",
"Lets pass the Paycheck Fairness Act and paid leave. \n",
"\n",
"Raise the minimum wage to $15 an hour and extend the Child Tax Credit, so no one has to raise a family in poverty. \n",
"\n",
"Lets increase Pell Grants and increase our historic support of HBCUs, and invest in what Jill—our First Lady who teaches full-time—calls Americas best-kept secret: community colleges.\n"
]
}
],
"source": [
"from langchain_community.document_loaders import TextLoader\n",
"from langchain_community.vectorstores import FAISS\n",
"from langchain_openai import OpenAIEmbeddings\n",
"from langchain_text_splitters import CharacterTextSplitter\n",
"\n",
"documents = TextLoader(\"../../state_of_the_union.txt\").load()\n",
"text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n",
"texts = text_splitter.split_documents(documents)\n",
"retriever = FAISS.from_documents(texts, OpenAIEmbeddings()).as_retriever()\n",
"\n",
"docs = retriever.get_relevant_documents(\n",
" \"What did the president say about Ketanji Brown Jackson\"\n",
")\n",
"pretty_print_docs(docs)"
]
},
{
"cell_type": "markdown",
"id": "3473c553",
"metadata": {},
"source": [
"## Adding contextual compression with an `LLMChainExtractor`\n",
"Now let's wrap our base retriever with a `ContextualCompressionRetriever`. We'll add an `LLMChainExtractor`, which will iterate over the initially returned documents and extract from each only the content that is relevant to the query.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "f08d19e6",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.\n",
" warnings.warn(\n",
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.\n",
" warnings.warn(\n",
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.\n",
" warnings.warn(\n",
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Document 1:\n",
"\n",
"I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson.\n"
]
}
],
"source": [
"from langchain.retrievers import ContextualCompressionRetriever\n",
"from langchain.retrievers.document_compressors import LLMChainExtractor\n",
"from langchain_openai import OpenAI\n",
"\n",
"llm = OpenAI(temperature=0)\n",
"compressor = LLMChainExtractor.from_llm(llm)\n",
"compression_retriever = ContextualCompressionRetriever(\n",
" base_compressor=compressor, base_retriever=retriever\n",
")\n",
"\n",
"compressed_docs = compression_retriever.get_relevant_documents(\n",
" \"What did the president say about Ketanji Jackson Brown\"\n",
")\n",
"pretty_print_docs(compressed_docs)"
]
},
{
"cell_type": "markdown",
"id": "8a97cd9b",
"metadata": {},
"source": [
"## More built-in compressors: filters\n",
"### `LLMChainFilter`\n",
"The `LLMChainFilter` is slightly simpler but more robust compressor that uses an LLM chain to decide which of the initially retrieved documents to filter out and which ones to return, without manipulating the document contents.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "6fa3ec79",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.\n",
" warnings.warn(\n",
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.\n",
" warnings.warn(\n",
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.\n",
" warnings.warn(\n",
"/Users/harrisonchase/workplace/langchain/libs/langchain/langchain/chains/llm.py:316: UserWarning: The predict_and_parse method is deprecated, instead pass an output parser directly to LLMChain.\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Document 1:\n",
"\n",
"Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while youre at it, pass the Disclose Act so Americans can know who is funding our elections. \n",
"\n",
"Tonight, Id like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n",
"\n",
"One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n",
"\n",
"And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nations top legal minds, who will continue Justice Breyers legacy of excellence.\n"
]
}
],
"source": [
"from langchain.retrievers.document_compressors import LLMChainFilter\n",
"\n",
"_filter = LLMChainFilter.from_llm(llm)\n",
"compression_retriever = ContextualCompressionRetriever(\n",
" base_compressor=_filter, base_retriever=retriever\n",
")\n",
"\n",
"compressed_docs = compression_retriever.get_relevant_documents(\n",
" \"What did the president say about Ketanji Jackson Brown\"\n",
")\n",
"pretty_print_docs(compressed_docs)"
]
},
{
"cell_type": "markdown",
"id": "7194da42",
"metadata": {},
"source": [
"### `EmbeddingsFilter`\n",
"\n",
"Making an extra LLM call over each retrieved document is expensive and slow. The `EmbeddingsFilter` provides a cheaper and faster option by embedding the documents and query and only returning those documents which have sufficiently similar embeddings to the query.\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "e84aceea",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Document 1:\n",
"\n",
"Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while youre at it, pass the Disclose Act so Americans can know who is funding our elections. \n",
"\n",
"Tonight, Id like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n",
"\n",
"One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n",
"\n",
"And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nations top legal minds, who will continue Justice Breyers legacy of excellence.\n",
"----------------------------------------------------------------------------------------------------\n",
"Document 2:\n",
"\n",
"A former top litigator in private practice. A former federal public defender. And from a family of public school educators and police officers. A consensus builder. Since shes been nominated, shes received a broad range of support—from the Fraternal Order of Police to former judges appointed by Democrats and Republicans. \n",
"\n",
"And if we are to advance liberty and justice, we need to secure the Border and fix the immigration system. \n",
"\n",
"We can do both. At our border, weve installed new technology like cutting-edge scanners to better detect drug smuggling. \n",
"\n",
"Weve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \n",
"\n",
"Were putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \n",
"\n",
"Were securing commitments and supporting partners in South and Central America to host more refugees and secure their own borders.\n",
"----------------------------------------------------------------------------------------------------\n",
"Document 3:\n",
"\n",
"And for our LGBTQ+ Americans, lets finally get the bipartisan Equality Act to my desk. The onslaught of state laws targeting transgender Americans and their families is wrong. \n",
"\n",
"As I said last year, especially to our younger transgender Americans, I will always have your back as your President, so you can be yourself and reach your God-given potential. \n",
"\n",
"While it often appears that we never agree, that isnt true. I signed 80 bipartisan bills into law last year. From preventing government shutdowns to protecting Asian-Americans from still-too-common hate crimes to reforming military justice. \n",
"\n",
"And soon, well strengthen the Violence Against Women Act that I first wrote three decades ago. It is important for us to show the nation that we can come together and do big things. \n",
"\n",
"So tonight Im offering a Unity Agenda for the Nation. Four big things we can do together. \n",
"\n",
"First, beat the opioid epidemic.\n"
]
}
],
"source": [
"from langchain.retrievers.document_compressors import EmbeddingsFilter\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"embeddings = OpenAIEmbeddings()\n",
"embeddings_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)\n",
"compression_retriever = ContextualCompressionRetriever(\n",
" base_compressor=embeddings_filter, base_retriever=retriever\n",
")\n",
"\n",
"compressed_docs = compression_retriever.get_relevant_documents(\n",
" \"What did the president say about Ketanji Jackson Brown\"\n",
")\n",
"pretty_print_docs(compressed_docs)"
]
},
{
"cell_type": "markdown",
"id": "2074462b",
"metadata": {},
"source": [
"## Stringing compressors and document transformers together\n",
"Using the `DocumentCompressorPipeline` we can also easily combine multiple compressors in sequence. Along with compressors we can add `BaseDocumentTransformer`s to our pipeline, which don't perform any contextual compression but simply perform some transformation on a set of documents. For example `TextSplitter`s can be used as document transformers to split documents into smaller pieces, and the `EmbeddingsRedundantFilter` can be used to filter out redundant documents based on embedding similarity between documents.\n",
"\n",
"Below we create a compressor pipeline by first splitting our docs into smaller chunks, then removing redundant documents, and then filtering based on relevance to the query.\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "617a1756",
"metadata": {},
"outputs": [],
"source": [
"from langchain.retrievers.document_compressors import DocumentCompressorPipeline\n",
"from langchain_community.document_transformers import EmbeddingsRedundantFilter\n",
"from langchain_text_splitters import CharacterTextSplitter\n",
"\n",
"splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=0, separator=\". \")\n",
"redundant_filter = EmbeddingsRedundantFilter(embeddings=embeddings)\n",
"relevant_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)\n",
"pipeline_compressor = DocumentCompressorPipeline(\n",
" transformers=[splitter, redundant_filter, relevant_filter]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "c715228a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Document 1:\n",
"\n",
"One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n",
"\n",
"And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson\n",
"----------------------------------------------------------------------------------------------------\n",
"Document 2:\n",
"\n",
"As I said last year, especially to our younger transgender Americans, I will always have your back as your President, so you can be yourself and reach your God-given potential. \n",
"\n",
"While it often appears that we never agree, that isnt true. I signed 80 bipartisan bills into law last year\n",
"----------------------------------------------------------------------------------------------------\n",
"Document 3:\n",
"\n",
"A former top litigator in private practice. A former federal public defender. And from a family of public school educators and police officers. A consensus builder\n",
"----------------------------------------------------------------------------------------------------\n",
"Document 4:\n",
"\n",
"Since shes been nominated, shes received a broad range of support—from the Fraternal Order of Police to former judges appointed by Democrats and Republicans. \n",
"\n",
"And if we are to advance liberty and justice, we need to secure the Border and fix the immigration system. \n",
"\n",
"We can do both\n"
]
}
],
"source": [
"compression_retriever = ContextualCompressionRetriever(\n",
" base_compressor=pipeline_compressor, base_retriever=retriever\n",
")\n",
"\n",
"compressed_docs = compression_retriever.get_relevant_documents(\n",
" \"What did the president say about Ketanji Jackson Brown\"\n",
")\n",
"pretty_print_docs(compressed_docs)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "78581dcb",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,570 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e3da9a3f-f583-4ba6-994e-0e8c1158f5eb",
"metadata": {},
"source": [
"# How to create a custom chat model class\n",
"\n",
"In this guide, we'll learn how to create a custom chat model using LangChain abstractions.\n",
"\n",
"Wrapping your LLM with the standard [`BaseChatModel`](https://api.python.langchain.com/en/latest/language_models/langchain_core.language_models.chat_models.BaseChatModel.html) interface allow you to use your LLM in existing LangChain programs with minimal code modifications!\n",
"\n",
"As an bonus, your LLM will automatically become a LangChain `Runnable` and will benefit from some optimizations out of the box (e.g., batch via a threadpool), async support, the `astream_events` API, etc.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Chat models](/docs/concepts/#chat-models)\n",
"`} />\n",
"```\n",
"\n",
"## Inputs and outputs\n",
"\n",
"First, we need to talk about **messages**, which are the inputs and outputs of chat models.\n",
"\n",
"### Messages\n",
"\n",
"Chat models take messages as inputs and return a message as output. \n",
"\n",
"LangChain has a few [built-in message types](/docs/concepts/#message-types):\n",
"\n",
"| Message Type | Description |\n",
"|-----------------------|-------------------------------------------------------------------------------------------------|\n",
"| `SystemMessage` | Used for priming AI behavior, usually passed in as the first of a sequence of input messages. |\n",
"| `HumanMessage` | Represents a message from a person interacting with the chat model. |\n",
"| `AIMessage` | Represents a message from the chat model. This can be either text or a request to invoke a tool.|\n",
"| `FunctionMessage` / `ToolMessage` | Message for passing the results of tool invocation back to the model. |\n",
"| `AIMessageChunk` / `HumanMessageChunk` / ... | Chunk variant of each type of message. |\n",
"\n",
"\n",
"::: {.callout-note}\n",
"`ToolMessage` and `FunctionMessage` closely follow OpenAI's `function` and `tool` roles.\n",
"\n",
"This is a rapidly developing field and as more models add function calling capabilities. Expect that there will be additions to this schema.\n",
":::"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "c5046e6a-8b09-4a99-b6e6-7a605aac5738",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain_core.messages import (\n",
" AIMessage,\n",
" BaseMessage,\n",
" FunctionMessage,\n",
" HumanMessage,\n",
" SystemMessage,\n",
" ToolMessage,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "53033447-8260-4f53-bd6f-b2f744e04e75",
"metadata": {},
"source": [
"### Streaming Variant\n",
"\n",
"All the chat messages have a streaming variant that contains `Chunk` in the name."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "d4656e9d-bfa1-4703-8f79-762fe6421294",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain_core.messages import (\n",
" AIMessageChunk,\n",
" FunctionMessageChunk,\n",
" HumanMessageChunk,\n",
" SystemMessageChunk,\n",
" ToolMessageChunk,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "81ebf3f4-c760-4898-b921-fdb469453d4a",
"metadata": {},
"source": [
"These chunks are used when streaming output from chat models, and they all define an additive property!"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "9c15c299-6f8a-49cf-a072-09924fd44396",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"AIMessageChunk(content='Hello World!')"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"AIMessageChunk(content=\"Hello\") + AIMessageChunk(content=\" World!\")"
]
},
{
"cell_type": "markdown",
"id": "bbfebea1",
"metadata": {},
"source": [
"## Base Chat Model\n",
"\n",
"Let's implement a chat model that echoes back the first `n` characetrs of the last message in the prompt!\n",
"\n",
"To do so, we will inherit from `BaseChatModel` and we'll need to implement the following:\n",
"\n",
"| Method/Property | Description | Required/Optional |\n",
"|------------------------------------|-------------------------------------------------------------------|--------------------|\n",
"| `_generate` | Use to generate a chat result from a prompt | Required |\n",
"| `_llm_type` (property) | Used to uniquely identify the type of the model. Used for logging.| Required |\n",
"| `_identifying_params` (property) | Represent model parameterization for tracing purposes. | Optional |\n",
"| `_stream` | Use to implement streaming. | Optional |\n",
"| `_agenerate` | Use to implement a native async method. | Optional |\n",
"| `_astream` | Use to implement async version of `_stream`. | Optional |\n",
"\n",
"\n",
":::{.callout-tip}\n",
"The `_astream` implementation uses `run_in_executor` to launch the sync `_stream` in a separate thread if `_stream` is implemented, otherwise it fallsback to use `_agenerate`.\n",
"\n",
"You can use this trick if you want to reuse the `_stream` implementation, but if you're able to implement code that's natively async that's a better solution since that code will run with less overhead.\n",
":::"
]
},
{
"cell_type": "markdown",
"id": "8e7047bd-c235-46f6-85e1-d6d7e0868eb1",
"metadata": {},
"source": [
"### Implementation"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "25ba32e5-5a6d-49f4-bb68-911827b84d61",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from typing import Any, AsyncIterator, Dict, Iterator, List, Optional\n",
"\n",
"from langchain_core.callbacks import (\n",
" AsyncCallbackManagerForLLMRun,\n",
" CallbackManagerForLLMRun,\n",
")\n",
"from langchain_core.language_models import BaseChatModel, SimpleChatModel\n",
"from langchain_core.messages import AIMessageChunk, BaseMessage, HumanMessage\n",
"from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult\n",
"from langchain_core.runnables import run_in_executor\n",
"\n",
"\n",
"class CustomChatModelAdvanced(BaseChatModel):\n",
" \"\"\"A custom chat model that echoes the first `n` characters of the input.\n",
"\n",
" When contributing an implementation to LangChain, carefully document\n",
" the model including the initialization parameters, include\n",
" an example of how to initialize the model and include any relevant\n",
" links to the underlying models documentation or API.\n",
"\n",
" Example:\n",
"\n",
" .. code-block:: python\n",
"\n",
" model = CustomChatModel(n=2)\n",
" result = model.invoke([HumanMessage(content=\"hello\")])\n",
" result = model.batch([[HumanMessage(content=\"hello\")],\n",
" [HumanMessage(content=\"world\")]])\n",
" \"\"\"\n",
"\n",
" model_name: str\n",
" \"\"\"The name of the model\"\"\"\n",
" n: int\n",
" \"\"\"The number of characters from the last message of the prompt to be echoed.\"\"\"\n",
"\n",
" def _generate(\n",
" self,\n",
" messages: List[BaseMessage],\n",
" stop: Optional[List[str]] = None,\n",
" run_manager: Optional[CallbackManagerForLLMRun] = None,\n",
" **kwargs: Any,\n",
" ) -> ChatResult:\n",
" \"\"\"Override the _generate method to implement the chat model logic.\n",
"\n",
" This can be a call to an API, a call to a local model, or any other\n",
" implementation that generates a response to the input prompt.\n",
"\n",
" Args:\n",
" messages: the prompt composed of a list of messages.\n",
" stop: a list of strings on which the model should stop generating.\n",
" If generation stops due to a stop token, the stop token itself\n",
" SHOULD BE INCLUDED as part of the output. This is not enforced\n",
" across models right now, but it's a good practice to follow since\n",
" it makes it much easier to parse the output of the model\n",
" downstream and understand why generation stopped.\n",
" run_manager: A run manager with callbacks for the LLM.\n",
" \"\"\"\n",
" # Replace this with actual logic to generate a response from a list\n",
" # of messages.\n",
" last_message = messages[-1]\n",
" tokens = last_message.content[: self.n]\n",
" message = AIMessage(\n",
" content=tokens,\n",
" additional_kwargs={}, # Used to add additional payload (e.g., function calling request)\n",
" response_metadata={ # Use for response metadata\n",
" \"time_in_seconds\": 3,\n",
" },\n",
" )\n",
" ##\n",
"\n",
" generation = ChatGeneration(message=message)\n",
" return ChatResult(generations=[generation])\n",
"\n",
" def _stream(\n",
" self,\n",
" messages: List[BaseMessage],\n",
" stop: Optional[List[str]] = None,\n",
" run_manager: Optional[CallbackManagerForLLMRun] = None,\n",
" **kwargs: Any,\n",
" ) -> Iterator[ChatGenerationChunk]:\n",
" \"\"\"Stream the output of the model.\n",
"\n",
" This method should be implemented if the model can generate output\n",
" in a streaming fashion. If the model does not support streaming,\n",
" do not implement it. In that case streaming requests will be automatically\n",
" handled by the _generate method.\n",
"\n",
" Args:\n",
" messages: the prompt composed of a list of messages.\n",
" stop: a list of strings on which the model should stop generating.\n",
" If generation stops due to a stop token, the stop token itself\n",
" SHOULD BE INCLUDED as part of the output. This is not enforced\n",
" across models right now, but it's a good practice to follow since\n",
" it makes it much easier to parse the output of the model\n",
" downstream and understand why generation stopped.\n",
" run_manager: A run manager with callbacks for the LLM.\n",
" \"\"\"\n",
" last_message = messages[-1]\n",
" tokens = last_message.content[: self.n]\n",
"\n",
" for token in tokens:\n",
" chunk = ChatGenerationChunk(message=AIMessageChunk(content=token))\n",
"\n",
" if run_manager:\n",
" # This is optional in newer versions of LangChain\n",
" # The on_llm_new_token will be called automatically\n",
" run_manager.on_llm_new_token(token, chunk=chunk)\n",
"\n",
" yield chunk\n",
"\n",
" # Let's add some other information (e.g., response metadata)\n",
" chunk = ChatGenerationChunk(\n",
" message=AIMessageChunk(content=\"\", response_metadata={\"time_in_sec\": 3})\n",
" )\n",
" if run_manager:\n",
" # This is optional in newer versions of LangChain\n",
" # The on_llm_new_token will be called automatically\n",
" run_manager.on_llm_new_token(token, chunk=chunk)\n",
" yield chunk\n",
"\n",
" @property\n",
" def _llm_type(self) -> str:\n",
" \"\"\"Get the type of language model used by this chat model.\"\"\"\n",
" return \"echoing-chat-model-advanced\"\n",
"\n",
" @property\n",
" def _identifying_params(self) -> Dict[str, Any]:\n",
" \"\"\"Return a dictionary of identifying parameters.\n",
"\n",
" This information is used by the LangChain callback system, which\n",
" is used for tracing purposes make it possible to monitor LLMs.\n",
" \"\"\"\n",
" return {\n",
" # The model name allows users to specify custom token counting\n",
" # rules in LLM monitoring applications (e.g., in LangSmith users\n",
" # can provide per token pricing for their model and monitor\n",
" # costs for the given LLM.)\n",
" \"model_name\": self.model_name,\n",
" }"
]
},
{
"cell_type": "markdown",
"id": "1e9af284-f2d3-44e2-ac6a-09b73d89ada3",
"metadata": {},
"source": [
"### Let's test it 🧪\n",
"\n",
"The chat model will implement the standard `Runnable` interface of LangChain which many of the LangChain abstractions support!"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "27689f30-dcd2-466b-ba9d-f60b7d434110",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='Meo', response_metadata={'time_in_seconds': 3}, id='run-ddb42bd6-4fdd-4bd2-8be5-e11b67d3ac29-0')"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = CustomChatModelAdvanced(n=3, model_name=\"my_custom_model\")\n",
"\n",
"model.invoke(\n",
" [\n",
" HumanMessage(content=\"hello!\"),\n",
" AIMessage(content=\"Hi there human!\"),\n",
" HumanMessage(content=\"Meow!\"),\n",
" ]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "406436df-31bf-466b-9c3d-39db9d6b6407",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='hel', response_metadata={'time_in_seconds': 3}, id='run-4d3cc912-44aa-454b-977b-ca02be06c12e-0')"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.invoke(\"hello\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "a72ffa46-6004-41ef-bbe4-56fa17a029e2",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"[AIMessage(content='hel', response_metadata={'time_in_seconds': 3}, id='run-9620e228-1912-4582-8aa1-176813afec49-0'),\n",
" AIMessage(content='goo', response_metadata={'time_in_seconds': 3}, id='run-1ce8cdf8-6f75-448e-82f7-1bb4a121df93-0')]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.batch([\"hello\", \"goodbye\"])"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "3633be2c-2ea0-42f9-a72f-3b5240690b55",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"c|a|t||"
]
}
],
"source": [
"for chunk in model.stream(\"cat\"):\n",
" print(chunk.content, end=\"|\")"
]
},
{
"cell_type": "markdown",
"id": "3f8a7c42-aec4-4116-adf3-93133d409827",
"metadata": {},
"source": [
"Please see the implementation of `_astream` in the model! If you do not implement it, then no output will stream.!"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b7d73995-eeab-48c6-a7d8-32c98ba29fc2",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"c|a|t||"
]
}
],
"source": [
"async for chunk in model.astream(\"cat\"):\n",
" print(chunk.content, end=\"|\")"
]
},
{
"cell_type": "markdown",
"id": "f80dc55b-d159-4527-9191-407a7c6d6042",
"metadata": {},
"source": [
"Let's try to use the astream events API which will also help double check that all the callbacks were implemented!"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "17840eba-8ff4-4e73-8e4f-85f16eb1c9d0",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'event': 'on_chat_model_start', 'run_id': '125a2a16-b9cd-40de-aa08-8aa9180b07d0', 'name': 'CustomChatModelAdvanced', 'tags': [], 'metadata': {}, 'data': {'input': 'cat'}}\n",
"{'event': 'on_chat_model_stream', 'run_id': '125a2a16-b9cd-40de-aa08-8aa9180b07d0', 'tags': [], 'metadata': {}, 'name': 'CustomChatModelAdvanced', 'data': {'chunk': AIMessageChunk(content='c', id='run-125a2a16-b9cd-40de-aa08-8aa9180b07d0')}}\n",
"{'event': 'on_chat_model_stream', 'run_id': '125a2a16-b9cd-40de-aa08-8aa9180b07d0', 'tags': [], 'metadata': {}, 'name': 'CustomChatModelAdvanced', 'data': {'chunk': AIMessageChunk(content='a', id='run-125a2a16-b9cd-40de-aa08-8aa9180b07d0')}}\n",
"{'event': 'on_chat_model_stream', 'run_id': '125a2a16-b9cd-40de-aa08-8aa9180b07d0', 'tags': [], 'metadata': {}, 'name': 'CustomChatModelAdvanced', 'data': {'chunk': AIMessageChunk(content='t', id='run-125a2a16-b9cd-40de-aa08-8aa9180b07d0')}}\n",
"{'event': 'on_chat_model_stream', 'run_id': '125a2a16-b9cd-40de-aa08-8aa9180b07d0', 'tags': [], 'metadata': {}, 'name': 'CustomChatModelAdvanced', 'data': {'chunk': AIMessageChunk(content='', response_metadata={'time_in_sec': 3}, id='run-125a2a16-b9cd-40de-aa08-8aa9180b07d0')}}\n",
"{'event': 'on_chat_model_end', 'name': 'CustomChatModelAdvanced', 'run_id': '125a2a16-b9cd-40de-aa08-8aa9180b07d0', 'tags': [], 'metadata': {}, 'data': {'output': AIMessageChunk(content='cat', response_metadata={'time_in_sec': 3}, id='run-125a2a16-b9cd-40de-aa08-8aa9180b07d0')}}\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/eugene/src/langchain/libs/core/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: This API is in beta and may change in the future.\n",
" warn_beta(\n"
]
}
],
"source": [
"async for event in model.astream_events(\"cat\", version=\"v1\"):\n",
" print(event)"
]
},
{
"cell_type": "markdown",
"id": "44ee559b-b1da-4851-8c97-420ab394aff9",
"metadata": {},
"source": [
"## Contributing\n",
"\n",
"We appreciate all chat model integration contributions. \n",
"\n",
"Here's a checklist to help make sure your contribution gets added to LangChain:\n",
"\n",
"Documentation:\n",
"\n",
"* The model contains doc-strings for all initialization arguments, as these will be surfaced in the [APIReference](https://api.python.langchain.com/en/stable/langchain_api_reference.html).\n",
"* The class doc-string for the model contains a link to the model API if the model is powered by a service.\n",
"\n",
"Tests:\n",
"\n",
"* [ ] Add unit or integration tests to the overridden methods. Verify that `invoke`, `ainvoke`, `batch`, `stream` work if you've over-ridden the corresponding code.\n",
"\n",
"\n",
"Streaming (if you're implementing it):\n",
"\n",
"* [ ] Implement the _stream method to get streaming working\n",
"\n",
"Stop Token Behavior:\n",
"\n",
"* [ ] Stop token should be respected\n",
"* [ ] Stop token should be INCLUDED as part of the response\n",
"\n",
"Secret API Keys:\n",
"\n",
"* [ ] If your model connects to an API it will likely accept API keys as part of its initialization. Use Pydantic's `SecretStr` type for secrets, so they don't get accidentally printed out when folks print the model.\n",
"\n",
"\n",
"Identifying Params:\n",
"\n",
"* [ ] Include a `model_name` in identifying params\n",
"\n",
"\n",
"Optimizations:\n",
"\n",
"Consider providing native async support to reduce the overhead from the model!\n",
" \n",
"* [ ] Provided a native async of `_agenerate` (used by `ainvoke`)\n",
"* [ ] Provided a native async of `_astream` (used by `astream`)\n",
"\n",
"## Next steps\n",
"\n",
"You've now learned how to create your own custom chat models.\n",
"\n",
"Next, check out the other how-to guides chat models in this section, like [how to get a model to return structured output](/docs/how_to/structured_output) or [how to track chat model token usage](/docs/how_to/chat_token_usage_tracking)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,449 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9e9b7651",
"metadata": {},
"source": [
"# How to create a custom LLM class\n",
"\n",
"This notebook goes over how to create a custom LLM wrapper, in case you want to use your own LLM or a different wrapper than one that is supported in LangChain.\n",
"\n",
"Wrapping your LLM with the standard `LLM` interface allow you to use your LLM in existing LangChain programs with minimal code modifications!\n",
"\n",
"As an bonus, your LLM will automatically become a LangChain `Runnable` and will benefit from some optimizations out of the box, async support, the `astream_events` API, etc.\n",
"\n",
"## Implementation\n",
"\n",
"There are only two required things that a custom LLM needs to implement:\n",
"\n",
"\n",
"| Method | Description |\n",
"|---------------|---------------------------------------------------------------------------|\n",
"| `_call` | Takes in a string and some optional stop words, and returns a string. Used by `invoke`. |\n",
"| `_llm_type` | A property that returns a string, used for logging purposes only. \n",
"\n",
"\n",
"\n",
"Optional implementations: \n",
"\n",
"\n",
"| Method | Description |\n",
"|----------------------|-----------------------------------------------------------------------------------------------------------|\n",
"| `_identifying_params` | Used to help with identifying the model and printing the LLM; should return a dictionary. This is a **@property**. |\n",
"| `_acall` | Provides an async native implementation of `_call`, used by `ainvoke`. |\n",
"| `_stream` | Method to stream the output token by token. |\n",
"| `_astream` | Provides an async native implementation of `_stream`; in newer LangChain versions, defaults to `_stream`. |\n",
"\n",
"\n",
"\n",
"Let's implement a simple custom LLM that just returns the first n characters of the input."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "2e9bb32f-6fd1-46ac-b32f-d175663710c0",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from typing import Any, Dict, Iterator, List, Mapping, Optional\n",
"\n",
"from langchain_core.callbacks.manager import CallbackManagerForLLMRun\n",
"from langchain_core.language_models.llms import LLM\n",
"from langchain_core.outputs import GenerationChunk\n",
"\n",
"\n",
"class CustomLLM(LLM):\n",
" \"\"\"A custom chat model that echoes the first `n` characters of the input.\n",
"\n",
" When contributing an implementation to LangChain, carefully document\n",
" the model including the initialization parameters, include\n",
" an example of how to initialize the model and include any relevant\n",
" links to the underlying models documentation or API.\n",
"\n",
" Example:\n",
"\n",
" .. code-block:: python\n",
"\n",
" model = CustomChatModel(n=2)\n",
" result = model.invoke([HumanMessage(content=\"hello\")])\n",
" result = model.batch([[HumanMessage(content=\"hello\")],\n",
" [HumanMessage(content=\"world\")]])\n",
" \"\"\"\n",
"\n",
" n: int\n",
" \"\"\"The number of characters from the last message of the prompt to be echoed.\"\"\"\n",
"\n",
" def _call(\n",
" self,\n",
" prompt: str,\n",
" stop: Optional[List[str]] = None,\n",
" run_manager: Optional[CallbackManagerForLLMRun] = None,\n",
" **kwargs: Any,\n",
" ) -> str:\n",
" \"\"\"Run the LLM on the given input.\n",
"\n",
" Override this method to implement the LLM logic.\n",
"\n",
" Args:\n",
" prompt: The prompt to generate from.\n",
" stop: Stop words to use when generating. Model output is cut off at the\n",
" first occurrence of any of the stop substrings.\n",
" If stop tokens are not supported consider raising NotImplementedError.\n",
" run_manager: Callback manager for the run.\n",
" **kwargs: Arbitrary additional keyword arguments. These are usually passed\n",
" to the model provider API call.\n",
"\n",
" Returns:\n",
" The model output as a string. Actual completions SHOULD NOT include the prompt.\n",
" \"\"\"\n",
" if stop is not None:\n",
" raise ValueError(\"stop kwargs are not permitted.\")\n",
" return prompt[: self.n]\n",
"\n",
" def _stream(\n",
" self,\n",
" prompt: str,\n",
" stop: Optional[List[str]] = None,\n",
" run_manager: Optional[CallbackManagerForLLMRun] = None,\n",
" **kwargs: Any,\n",
" ) -> Iterator[GenerationChunk]:\n",
" \"\"\"Stream the LLM on the given prompt.\n",
"\n",
" This method should be overridden by subclasses that support streaming.\n",
"\n",
" If not implemented, the default behavior of calls to stream will be to\n",
" fallback to the non-streaming version of the model and return\n",
" the output as a single chunk.\n",
"\n",
" Args:\n",
" prompt: The prompt to generate from.\n",
" stop: Stop words to use when generating. Model output is cut off at the\n",
" first occurrence of any of these substrings.\n",
" run_manager: Callback manager for the run.\n",
" **kwargs: Arbitrary additional keyword arguments. These are usually passed\n",
" to the model provider API call.\n",
"\n",
" Returns:\n",
" An iterator of GenerationChunks.\n",
" \"\"\"\n",
" for char in prompt[: self.n]:\n",
" chunk = GenerationChunk(text=char)\n",
" if run_manager:\n",
" run_manager.on_llm_new_token(chunk.text, chunk=chunk)\n",
"\n",
" yield chunk\n",
"\n",
" @property\n",
" def _identifying_params(self) -> Dict[str, Any]:\n",
" \"\"\"Return a dictionary of identifying parameters.\"\"\"\n",
" return {\n",
" # The model name allows users to specify custom token counting\n",
" # rules in LLM monitoring applications (e.g., in LangSmith users\n",
" # can provide per token pricing for their model and monitor\n",
" # costs for the given LLM.)\n",
" \"model_name\": \"CustomChatModel\",\n",
" }\n",
"\n",
" @property\n",
" def _llm_type(self) -> str:\n",
" \"\"\"Get the type of language model used by this chat model. Used for logging purposes only.\"\"\"\n",
" return \"custom\""
]
},
{
"cell_type": "markdown",
"id": "f614fb7b-e476-4d81-821b-57a2ebebe21c",
"metadata": {
"tags": []
},
"source": [
"### Let's test it 🧪"
]
},
{
"cell_type": "markdown",
"id": "e3feae15-4afc-49f4-8542-93867d4ea769",
"metadata": {
"tags": []
},
"source": [
"This LLM will implement the standard `Runnable` interface of LangChain which many of the LangChain abstractions support!"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "dfff4a95-99b2-4dba-b80d-9c3855046ef1",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1mCustomLLM\u001b[0m\n",
"Params: {'model_name': 'CustomChatModel'}\n"
]
}
],
"source": [
"llm = CustomLLM(n=5)\n",
"print(llm)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "8cd49199",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"'This '"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"llm.invoke(\"This is a foobar thing\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "511b3cb1-9c6f-49b6-9002-a2ec490632b0",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"'world'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"await llm.ainvoke(\"world\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "d9d5bec2-d60a-4ebd-a97d-ac32c98ab02f",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"['woof ', 'meow ']"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"llm.batch([\"woof woof woof\", \"meow meow meow\"])"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "fe246b29-7a93-4bef-8861-389445598c25",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"['woof ', 'meow ']"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"await llm.abatch([\"woof woof woof\", \"meow meow meow\"])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "3a67c38f-b83b-4eb9-a231-441c55ee8c82",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"h|e|l|l|o|"
]
}
],
"source": [
"async for token in llm.astream(\"hello\"):\n",
" print(token, end=\"|\", flush=True)"
]
},
{
"cell_type": "markdown",
"id": "b62c282b-3a35-4529-aac4-2c2f0916790e",
"metadata": {},
"source": [
"Let's confirm that in integrates nicely with other `LangChain` APIs."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "d5578e74-7fa8-4673-afee-7a59d442aaff",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "672ff664-8673-4832-9f4f-335253880141",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"prompt = ChatPromptTemplate.from_messages(\n",
" [(\"system\", \"you are a bot\"), (\"human\", \"{input}\")]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "c400538a-9146-4c93-9fac-293d8f9ca6bf",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"llm = CustomLLM(n=7)\n",
"chain = prompt | llm"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "080964af-3e2d-4573-85cb-0d7cc58a6f42",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'event': 'on_chain_start', 'run_id': '05f24b4f-7ea3-4fb6-8417-3aa21633462f', 'name': 'RunnableSequence', 'tags': [], 'metadata': {}, 'data': {'input': {'input': 'hello there!'}}}\n",
"{'event': 'on_prompt_start', 'name': 'ChatPromptTemplate', 'run_id': '7e996251-a926-4344-809e-c425a9846d21', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'input': {'input': 'hello there!'}}}\n",
"{'event': 'on_prompt_end', 'name': 'ChatPromptTemplate', 'run_id': '7e996251-a926-4344-809e-c425a9846d21', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'input': {'input': 'hello there!'}, 'output': ChatPromptValue(messages=[SystemMessage(content='you are a bot'), HumanMessage(content='hello there!')])}}\n",
"{'event': 'on_llm_start', 'name': 'CustomLLM', 'run_id': 'a8766beb-10f4-41de-8750-3ea7cf0ca7e2', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'input': {'prompts': ['System: you are a bot\\nHuman: hello there!']}}}\n",
"{'event': 'on_llm_stream', 'name': 'CustomLLM', 'run_id': 'a8766beb-10f4-41de-8750-3ea7cf0ca7e2', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': 'S'}}\n",
"{'event': 'on_chain_stream', 'run_id': '05f24b4f-7ea3-4fb6-8417-3aa21633462f', 'tags': [], 'metadata': {}, 'name': 'RunnableSequence', 'data': {'chunk': 'S'}}\n",
"{'event': 'on_llm_stream', 'name': 'CustomLLM', 'run_id': 'a8766beb-10f4-41de-8750-3ea7cf0ca7e2', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': 'y'}}\n",
"{'event': 'on_chain_stream', 'run_id': '05f24b4f-7ea3-4fb6-8417-3aa21633462f', 'tags': [], 'metadata': {}, 'name': 'RunnableSequence', 'data': {'chunk': 'y'}}\n"
]
}
],
"source": [
"idx = 0\n",
"async for event in chain.astream_events({\"input\": \"hello there!\"}, version=\"v1\"):\n",
" print(event)\n",
" idx += 1\n",
" if idx > 7:\n",
" # Truncate\n",
" break"
]
},
{
"cell_type": "markdown",
"id": "a85e848a-5316-4318-b770-3f8fd34f4231",
"metadata": {},
"source": [
"## Contributing\n",
"\n",
"We appreciate all chat model integration contributions. \n",
"\n",
"Here's a checklist to help make sure your contribution gets added to LangChain:\n",
"\n",
"Documentation:\n",
"\n",
"* The model contains doc-strings for all initialization arguments, as these will be surfaced in the [APIReference](https://api.python.langchain.com/en/stable/langchain_api_reference.html).\n",
"* The class doc-string for the model contains a link to the model API if the model is powered by a service.\n",
"\n",
"Tests:\n",
"\n",
"* [ ] Add unit or integration tests to the overridden methods. Verify that `invoke`, `ainvoke`, `batch`, `stream` work if you've over-ridden the corresponding code.\n",
"\n",
"Streaming (if you're implementing it):\n",
"\n",
"* [ ] Make sure to invoke the `on_llm_new_token` callback\n",
"* [ ] `on_llm_new_token` is invoked BEFORE yielding the chunk\n",
"\n",
"Stop Token Behavior:\n",
"\n",
"* [ ] Stop token should be respected\n",
"* [ ] Stop token should be INCLUDED as part of the response\n",
"\n",
"Secret API Keys:\n",
"\n",
"* [ ] If your model connects to an API it will likely accept API keys as part of its initialization. Use Pydantic's `SecretStr` type for secrets, so they don't get accidentally printed out when folks print the model."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,309 @@
{
"cells": [
{
"cell_type": "raw",
"id": "b5fc1fc7-c4c5-418f-99da-006c604a7ea6",
"metadata": {},
"source": [
"---\n",
"title: Custom Retriever\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "ff6f3c79-0848-4956-9115-54f6b2134587",
"metadata": {},
"source": [
"# How to create a custom Retriever\n",
"\n",
"## Overview\n",
"\n",
"Many LLM applications involve retrieving information from external data sources using a `Retriever`. \n",
"\n",
"A retriever is responsible for retrieving a list of relevant `Documents` to a given user `query`.\n",
"\n",
"The retrieved documents are often formatted into prompts that are fed into an LLM, allowing the LLM to use the information in the to generate an appropriate response (e.g., answering a user question based on a knowledge base).\n",
"\n",
"## Interface\n",
"\n",
"To create your own retriever, you need to extend the `BaseRetriever` class and implement the following methods:\n",
"\n",
"| Method | Description | Required/Optional |\n",
"|--------------------------------|--------------------------------------------------|-------------------|\n",
"| `_get_relevant_documents` | Get documents relevant to a query. | Required |\n",
"| `_aget_relevant_documents` | Implement to provide async native support. | Optional |\n",
"\n",
"\n",
"The logic inside of `_get_relevant_documents` can involve arbitrary calls to a database or to the web using requests.\n",
"\n",
":::{.callout-tip}\n",
"By inherting from `BaseRetriever`, your retriever automatically becomes a LangChain [Runnable](/docs/expression_language/interface) and will gain the standard `Runnable` functionality out of the box!\n",
":::\n",
"\n",
"\n",
":::{.callout-info}\n",
"You can use a `RunnableLambda` or `RunnableGenerator` to implement a retriever.\n",
"\n",
"The main benefit of implementing a retriever as a `BaseRetriever` vs. a `RunnableLambda` (a custom [runnable function](/docs/how_to/functions)) is that a `BaseRetriever` is a well\n",
"known LangChain entity so some tooling for monitoring may implement specialized behavior for retrievers. Another difference\n",
"is that a `BaseRetriever` will behave slightly differently from `RunnableLambda` in some APIs; e.g., the `start` event\n",
"in `astream_events` API will be `on_retriever_start` instead of `on_chain_start`.\n",
":::\n"
]
},
{
"cell_type": "markdown",
"id": "2be9fe82-0757-41d1-a647-15bed11fd3bf",
"metadata": {},
"source": [
"## Example\n",
"\n",
"Let's implement a toy retriever that returns all documents whose text contains the text in the user query."
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "bdf61902-2984-493b-a002-d4fced6df590",
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"\n",
"from langchain_core.callbacks import CallbackManagerForRetrieverRun\n",
"from langchain_core.documents import Document\n",
"from langchain_core.retrievers import BaseRetriever\n",
"\n",
"\n",
"class ToyRetriever(BaseRetriever):\n",
" \"\"\"A toy retriever that contains the top k documents that contain the user query.\n",
"\n",
" This retriever only implements the sync method _get_relevant_documents.\n",
"\n",
" If the retriever were to involve file access or network access, it could benefit\n",
" from a native async implementation of `_aget_relevant_documents`.\n",
"\n",
" As usual, with Runnables, there's a default async implementation that's provided\n",
" that delegates to the sync implementation running on another thread.\n",
" \"\"\"\n",
"\n",
" documents: List[Document]\n",
" \"\"\"List of documents to retrieve from.\"\"\"\n",
" k: int\n",
" \"\"\"Number of top results to return\"\"\"\n",
"\n",
" def _get_relevant_documents(\n",
" self, query: str, *, run_manager: CallbackManagerForRetrieverRun\n",
" ) -> List[Document]:\n",
" \"\"\"Sync implementations for retriever.\"\"\"\n",
" matching_documents = []\n",
" for document in documents:\n",
" if len(matching_documents) > self.k:\n",
" return matching_documents\n",
"\n",
" if query.lower() in document.page_content.lower():\n",
" matching_documents.append(document)\n",
" return matching_documents\n",
"\n",
" # Optional: Provide a more efficient native implementation by overriding\n",
" # _aget_relevant_documents\n",
" # async def _aget_relevant_documents(\n",
" # self, query: str, *, run_manager: AsyncCallbackManagerForRetrieverRun\n",
" # ) -> List[Document]:\n",
" # \"\"\"Asynchronously get documents relevant to a query.\n",
"\n",
" # Args:\n",
" # query: String to find relevant documents for\n",
" # run_manager: The callbacks handler to use\n",
"\n",
" # Returns:\n",
" # List of relevant documents\n",
" # \"\"\""
]
},
{
"cell_type": "markdown",
"id": "2eac1f28-29c1-4888-b3aa-b4fa70c73b4c",
"metadata": {},
"source": [
"## Test it 🧪"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "ea868db5-48cc-4ec2-9b0a-1ab94c32b302",
"metadata": {},
"outputs": [],
"source": [
"documents = [\n",
" Document(\n",
" page_content=\"Dogs are great companions, known for their loyalty and friendliness.\",\n",
" metadata={\"type\": \"dog\", \"trait\": \"loyalty\"},\n",
" ),\n",
" Document(\n",
" page_content=\"Cats are independent pets that often enjoy their own space.\",\n",
" metadata={\"type\": \"cat\", \"trait\": \"independence\"},\n",
" ),\n",
" Document(\n",
" page_content=\"Goldfish are popular pets for beginners, requiring relatively simple care.\",\n",
" metadata={\"type\": \"fish\", \"trait\": \"low maintenance\"},\n",
" ),\n",
" Document(\n",
" page_content=\"Parrots are intelligent birds capable of mimicking human speech.\",\n",
" metadata={\"type\": \"bird\", \"trait\": \"intelligence\"},\n",
" ),\n",
" Document(\n",
" page_content=\"Rabbits are social animals that need plenty of space to hop around.\",\n",
" metadata={\"type\": \"rabbit\", \"trait\": \"social\"},\n",
" ),\n",
"]\n",
"retriever = ToyRetriever(documents=documents, k=3)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "18be85e9-6ef0-4ee0-ae5d-a0810c38b254",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Cats are independent pets that often enjoy their own space.', metadata={'type': 'cat', 'trait': 'independence'}),\n",
" Document(page_content='Rabbits are social animals that need plenty of space to hop around.', metadata={'type': 'rabbit', 'trait': 'social'})]"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retriever.invoke(\"that\")"
]
},
{
"cell_type": "markdown",
"id": "13f76f6e-cf2b-4f67-859b-0ef8be98abbe",
"metadata": {},
"source": [
"It's a **runnable** so it'll benefit from the standard Runnable Interface! 🤩"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "3672e9fe-4365-4628-9d25-31924cfaf784",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Cats are independent pets that often enjoy their own space.', metadata={'type': 'cat', 'trait': 'independence'}),\n",
" Document(page_content='Rabbits are social animals that need plenty of space to hop around.', metadata={'type': 'rabbit', 'trait': 'social'})]"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"await retriever.ainvoke(\"that\")"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "e2c96eed-6813-421c-acf2-6554839840ee",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[[Document(page_content='Dogs are great companions, known for their loyalty and friendliness.', metadata={'type': 'dog', 'trait': 'loyalty'})],\n",
" [Document(page_content='Cats are independent pets that often enjoy their own space.', metadata={'type': 'cat', 'trait': 'independence'})]]"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retriever.batch([\"dog\", \"cat\"])"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "978b6636-bf36-42c2-969c-207718f084cf",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'event': 'on_retriever_start', 'run_id': 'f96f268d-8383-4921-b175-ca583924d9ff', 'name': 'ToyRetriever', 'tags': [], 'metadata': {}, 'data': {'input': 'bar'}}\n",
"{'event': 'on_retriever_stream', 'run_id': 'f96f268d-8383-4921-b175-ca583924d9ff', 'tags': [], 'metadata': {}, 'name': 'ToyRetriever', 'data': {'chunk': []}}\n",
"{'event': 'on_retriever_end', 'name': 'ToyRetriever', 'run_id': 'f96f268d-8383-4921-b175-ca583924d9ff', 'tags': [], 'metadata': {}, 'data': {'output': []}}\n"
]
}
],
"source": [
"async for event in retriever.astream_events(\"bar\", version=\"v1\"):\n",
" print(event)"
]
},
{
"cell_type": "markdown",
"id": "7b45c404-37bf-4370-bb7c-26556777ff46",
"metadata": {},
"source": [
"## Contributing\n",
"\n",
"We appreciate contributions of interesting retrievers!\n",
"\n",
"Here's a checklist to help make sure your contribution gets added to LangChain:\n",
"\n",
"Documentation:\n",
"\n",
"* The retriever contains doc-strings for all initialization arguments, as these will be surfaced in the [API Reference](https://api.python.langchain.com/en/stable/langchain_api_reference.html).\n",
"* The class doc-string for the model contains a link to any relevant APIs used for the retriever (e.g., if the retriever is retrieving from wikipedia, it'll be good to link to the wikipedia API!)\n",
"\n",
"Tests:\n",
"\n",
"* [ ] Add unit or integration tests to verify that `invoke` and `ainvoke` work.\n",
"\n",
"Optimizations:\n",
"\n",
"If the retriever is connecting to external data sources (e.g., an API or a file), it'll almost certainly benefit from an async native optimization!\n",
" \n",
"* [ ] Provide a native async implementation of `_aget_relevant_documents` (used by `ainvoke`)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,576 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5436020b",
"metadata": {},
"source": [
"# How to create custom Tools\n",
"\n",
"When constructing your own agent, you will need to provide it with a list of Tools that it can use. Besides the actual function that is called, the Tool consists of several components:\n",
"\n",
"- `name` (str), is required and must be unique within a set of tools provided to an agent\n",
"- `description` (str), is optional but recommended, as it is used by an agent to determine tool use\n",
"- `args_schema` (Pydantic BaseModel), is optional but recommended, can be used to provide more information (e.g., few-shot examples) or validation for expected parameters.\n",
"\n",
"\n",
"There are multiple ways to define a tool. In this guide, we will walk through how to do for two functions:\n",
"\n",
"1. A made up search function that always returns the string \"LangChain\"\n",
"2. A multiplier function that will multiply two numbers by eachother\n",
"\n",
"The biggest difference here is that the first function only requires one input, while the second one requires multiple. Many agents only work with functions that require single inputs, so it's important to know how to work with those. For the most part, defining these custom tools is the same, but there are some differences."
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "1aaba18c",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# Import things that are needed generically\n",
"from langchain.pydantic_v1 import BaseModel, Field\n",
"from langchain.tools import BaseTool, StructuredTool, tool"
]
},
{
"cell_type": "markdown",
"id": "c7326b23",
"metadata": {},
"source": [
"## @tool decorator\n",
"\n",
"This `@tool` decorator is the simplest way to define a custom tool. The decorator uses the function name as the tool name by default, but this can be overridden by passing a string as the first argument. Additionally, the decorator will use the function's docstring as the tool's description - so a docstring MUST be provided. "
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "b0ce7de8",
"metadata": {},
"outputs": [],
"source": [
"@tool\n",
"def search(query: str) -> str:\n",
" \"\"\"Look up things online.\"\"\"\n",
" return \"LangChain\""
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e889fa34",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"search\n",
"search(query: str) -> str - Look up things online.\n",
"{'query': {'title': 'Query', 'type': 'string'}}\n"
]
}
],
"source": [
"print(search.name)\n",
"print(search.description)\n",
"print(search.args)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "0b9694d9",
"metadata": {},
"outputs": [],
"source": [
"@tool\n",
"def multiply(a: int, b: int) -> int:\n",
" \"\"\"Multiply two numbers.\"\"\"\n",
" return a * b"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "d7f9395b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"multiply\n",
"multiply(a: int, b: int) -> int - Multiply two numbers.\n",
"{'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}\n"
]
}
],
"source": [
"print(multiply.name)\n",
"print(multiply.description)\n",
"print(multiply.args)"
]
},
{
"cell_type": "markdown",
"id": "98d6eee9",
"metadata": {},
"source": [
"You can also customize the tool name and JSON args by passing them into the tool decorator."
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "dbbf4b6c",
"metadata": {},
"outputs": [],
"source": [
"class SearchInput(BaseModel):\n",
" query: str = Field(description=\"should be a search query\")\n",
"\n",
"\n",
"@tool(\"search-tool\", args_schema=SearchInput, return_direct=True)\n",
"def search(query: str) -> str:\n",
" \"\"\"Look up things online.\"\"\"\n",
" return \"LangChain\""
]
},
{
"cell_type": "code",
"execution_count": 44,
"id": "5950ce32",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"search-tool\n",
"search-tool(query: str) -> str - Look up things online.\n",
"{'query': {'title': 'Query', 'description': 'should be a search query', 'type': 'string'}}\n",
"True\n"
]
}
],
"source": [
"print(search.name)\n",
"print(search.description)\n",
"print(search.args)\n",
"print(search.return_direct)"
]
},
{
"cell_type": "markdown",
"id": "9d11e80c",
"metadata": {},
"source": [
"## Subclass BaseTool\n",
"\n",
"You can also explicitly define a custom tool by subclassing the BaseTool class. This provides maximal control over the tool definition, but is a bit more work."
]
},
{
"cell_type": "code",
"execution_count": 45,
"id": "1dad8f8e",
"metadata": {},
"outputs": [],
"source": [
"from typing import Optional, Type\n",
"\n",
"from langchain.callbacks.manager import (\n",
" AsyncCallbackManagerForToolRun,\n",
" CallbackManagerForToolRun,\n",
")\n",
"\n",
"\n",
"class SearchInput(BaseModel):\n",
" query: str = Field(description=\"should be a search query\")\n",
"\n",
"\n",
"class CalculatorInput(BaseModel):\n",
" a: int = Field(description=\"first number\")\n",
" b: int = Field(description=\"second number\")\n",
"\n",
"\n",
"class CustomSearchTool(BaseTool):\n",
" name = \"custom_search\"\n",
" description = \"useful for when you need to answer questions about current events\"\n",
" args_schema: Type[BaseModel] = SearchInput\n",
"\n",
" def _run(\n",
" self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None\n",
" ) -> str:\n",
" \"\"\"Use the tool.\"\"\"\n",
" return \"LangChain\"\n",
"\n",
" async def _arun(\n",
" self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None\n",
" ) -> str:\n",
" \"\"\"Use the tool asynchronously.\"\"\"\n",
" raise NotImplementedError(\"custom_search does not support async\")\n",
"\n",
"\n",
"class CustomCalculatorTool(BaseTool):\n",
" name = \"Calculator\"\n",
" description = \"useful for when you need to answer questions about math\"\n",
" args_schema: Type[BaseModel] = CalculatorInput\n",
" return_direct: bool = True\n",
"\n",
" def _run(\n",
" self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None\n",
" ) -> str:\n",
" \"\"\"Use the tool.\"\"\"\n",
" return a * b\n",
"\n",
" async def _arun(\n",
" self,\n",
" a: int,\n",
" b: int,\n",
" run_manager: Optional[AsyncCallbackManagerForToolRun] = None,\n",
" ) -> str:\n",
" \"\"\"Use the tool asynchronously.\"\"\"\n",
" raise NotImplementedError(\"Calculator does not support async\")"
]
},
{
"cell_type": "code",
"execution_count": 46,
"id": "89933e27",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"custom_search\n",
"useful for when you need to answer questions about current events\n",
"{'query': {'title': 'Query', 'description': 'should be a search query', 'type': 'string'}}\n"
]
}
],
"source": [
"search = CustomSearchTool()\n",
"print(search.name)\n",
"print(search.description)\n",
"print(search.args)"
]
},
{
"cell_type": "code",
"execution_count": 48,
"id": "bb551c33",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Calculator\n",
"useful for when you need to answer questions about math\n",
"{'a': {'title': 'A', 'description': 'first number', 'type': 'integer'}, 'b': {'title': 'B', 'description': 'second number', 'type': 'integer'}}\n",
"True\n"
]
}
],
"source": [
"multiply = CustomCalculatorTool()\n",
"print(multiply.name)\n",
"print(multiply.description)\n",
"print(multiply.args)\n",
"print(multiply.return_direct)"
]
},
{
"cell_type": "markdown",
"id": "b63fcc3b",
"metadata": {},
"source": [
"## StructuredTool dataclass\n",
"\n",
"You can also use a `StructuredTool` dataclass. This methods is a mix between the previous two. It's more convenient than inheriting from the BaseTool class, but provides more functionality than just using a decorator."
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "56ff7670",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"def search_function(query: str):\n",
" return \"LangChain\"\n",
"\n",
"\n",
"search = StructuredTool.from_function(\n",
" func=search_function,\n",
" name=\"Search\",\n",
" description=\"useful for when you need to answer questions about current events\",\n",
" # coroutine= ... <- you can specify an async method if desired as well\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 38,
"id": "d3fd3896",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Search\n",
"Search(query: str) - useful for when you need to answer questions about current events\n",
"{'query': {'title': 'Query', 'type': 'string'}}\n"
]
}
],
"source": [
"print(search.name)\n",
"print(search.description)\n",
"print(search.args)"
]
},
{
"cell_type": "markdown",
"id": "e9b560f7",
"metadata": {},
"source": [
"You can also define a custom `args_schema` to provide more information about inputs."
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "712c1967",
"metadata": {},
"outputs": [],
"source": [
"class CalculatorInput(BaseModel):\n",
" a: int = Field(description=\"first number\")\n",
" b: int = Field(description=\"second number\")\n",
"\n",
"\n",
"def multiply(a: int, b: int) -> int:\n",
" \"\"\"Multiply two numbers.\"\"\"\n",
" return a * b\n",
"\n",
"\n",
"calculator = StructuredTool.from_function(\n",
" func=multiply,\n",
" name=\"Calculator\",\n",
" description=\"multiply numbers\",\n",
" args_schema=CalculatorInput,\n",
" return_direct=True,\n",
" # coroutine= ... <- you can specify an async method if desired as well\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "f634081e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Calculator\n",
"Calculator(a: int, b: int) -> int - multiply numbers\n",
"{'a': {'title': 'A', 'description': 'first number', 'type': 'integer'}, 'b': {'title': 'B', 'description': 'second number', 'type': 'integer'}}\n"
]
}
],
"source": [
"print(calculator.name)\n",
"print(calculator.description)\n",
"print(calculator.args)"
]
},
{
"cell_type": "markdown",
"id": "f1da459d",
"metadata": {},
"source": [
"## Handling Tool Errors \n",
"When a tool encounters an error and the exception is not caught, the agent will stop executing. If you want the agent to continue execution, you can raise a `ToolException` and set `handle_tool_error` accordingly. \n",
"\n",
"When `ToolException` is thrown, the agent will not stop working, but will handle the exception according to the `handle_tool_error` variable of the tool, and the processing result will be returned to the agent as observation, and printed in red.\n",
"\n",
"You can set `handle_tool_error` to `True`, set it a unified string value, or set it as a function. If it's set as a function, the function should take a `ToolException` as a parameter and return a `str` value.\n",
"\n",
"Please note that only raising a `ToolException` won't be effective. You need to first set the `handle_tool_error` of the tool because its default value is `False`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8bf4668",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.tools import ToolException\n",
"\n",
"\n",
"def search_tool1(s: str):\n",
" raise ToolException(\"The search tool1 is not available.\")"
]
},
{
"cell_type": "markdown",
"id": "7fb56757",
"metadata": {},
"source": [
"First, let's see what happens if we don't set `handle_tool_error` - it will error."
]
},
{
"cell_type": "code",
"execution_count": 58,
"id": "f3dfbcb0",
"metadata": {},
"outputs": [
{
"ename": "ToolException",
"evalue": "The search tool1 is not available.",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mToolException\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[58], line 7\u001b[0m\n\u001b[1;32m 1\u001b[0m search \u001b[38;5;241m=\u001b[39m StructuredTool\u001b[38;5;241m.\u001b[39mfrom_function(\n\u001b[1;32m 2\u001b[0m func\u001b[38;5;241m=\u001b[39msearch_tool1,\n\u001b[1;32m 3\u001b[0m name\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSearch_tool1\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 4\u001b[0m description\u001b[38;5;241m=\u001b[39mdescription,\n\u001b[1;32m 5\u001b[0m )\n\u001b[0;32m----> 7\u001b[0m \u001b[43msearch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtest\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/workplace/langchain/libs/core/langchain_core/tools.py:344\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, **kwargs)\u001b[0m\n\u001b[1;32m 342\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandle_tool_error:\n\u001b[1;32m 343\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_tool_error(e)\n\u001b[0;32m--> 344\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[1;32m 345\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandle_tool_error, \u001b[38;5;28mbool\u001b[39m):\n\u001b[1;32m 346\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs:\n",
"File \u001b[0;32m~/workplace/langchain/libs/core/langchain_core/tools.py:337\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, **kwargs)\u001b[0m\n\u001b[1;32m 334\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 335\u001b[0m tool_args, tool_kwargs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_to_args_and_kwargs(parsed_input)\n\u001b[1;32m 336\u001b[0m observation \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m--> 337\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_run\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mtool_args\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mtool_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 338\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_arg_supported\n\u001b[1;32m 339\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_run(\u001b[38;5;241m*\u001b[39mtool_args, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mtool_kwargs)\n\u001b[1;32m 340\u001b[0m )\n\u001b[1;32m 341\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ToolException \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 342\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandle_tool_error:\n",
"File \u001b[0;32m~/workplace/langchain/libs/core/langchain_core/tools.py:631\u001b[0m, in \u001b[0;36mStructuredTool._run\u001b[0;34m(self, run_manager, *args, **kwargs)\u001b[0m\n\u001b[1;32m 622\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc:\n\u001b[1;32m 623\u001b[0m new_argument_supported \u001b[38;5;241m=\u001b[39m signature(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc)\u001b[38;5;241m.\u001b[39mparameters\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcallbacks\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 624\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m (\n\u001b[1;32m 625\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc(\n\u001b[1;32m 626\u001b[0m \u001b[38;5;241m*\u001b[39margs,\n\u001b[1;32m 627\u001b[0m callbacks\u001b[38;5;241m=\u001b[39mrun_manager\u001b[38;5;241m.\u001b[39mget_child() \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 628\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs,\n\u001b[1;32m 629\u001b[0m )\n\u001b[1;32m 630\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_argument_supported\n\u001b[0;32m--> 631\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 632\u001b[0m )\n\u001b[1;32m 633\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mNotImplementedError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTool does not support sync\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
"Cell \u001b[0;32mIn[55], line 5\u001b[0m, in \u001b[0;36msearch_tool1\u001b[0;34m(s)\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21msearch_tool1\u001b[39m(s: \u001b[38;5;28mstr\u001b[39m):\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ToolException(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe search tool1 is not available.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
"\u001b[0;31mToolException\u001b[0m: The search tool1 is not available."
]
}
],
"source": [
"search = StructuredTool.from_function(\n",
" func=search_tool1,\n",
" name=\"Search_tool1\",\n",
" description=\"A bad tool\",\n",
")\n",
"\n",
"search.run(\"test\")"
]
},
{
"cell_type": "markdown",
"id": "d2475acd",
"metadata": {},
"source": [
"Now, let's set `handle_tool_error` to be True"
]
},
{
"cell_type": "code",
"execution_count": 59,
"id": "ab81e0f0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The search tool1 is not available.'"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"search = StructuredTool.from_function(\n",
" func=search_tool1,\n",
" name=\"Search_tool1\",\n",
" description=\"A bad tool\",\n",
" handle_tool_error=True,\n",
")\n",
"\n",
"search.run(\"test\")"
]
},
{
"cell_type": "markdown",
"id": "dafbbcbe",
"metadata": {},
"source": [
"We can also define a custom way to handle the tool error"
]
},
{
"cell_type": "code",
"execution_count": 60,
"id": "ad16fbcf",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The following errors occurred during tool execution:The search tool1 is not available.Please try another tool.'"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def _handle_error(error: ToolException) -> str:\n",
" return (\n",
" \"The following errors occurred during tool execution:\"\n",
" + error.args[0]\n",
" + \"Please try another tool.\"\n",
" )\n",
"\n",
"\n",
"search = StructuredTool.from_function(\n",
" func=search_tool1,\n",
" name=\"Search_tool1\",\n",
" description=\"A bad tool\",\n",
" handle_tool_error=_handle_error,\n",
")\n",
"\n",
"search.run(\"test\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
},
"vscode": {
"interpreter": {
"hash": "e90c8aa204a57276aa905271aff2d11799d0acb3547adabc5892e639a5e45e34"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,189 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "dfc274c4-0c24-4c5f-865a-ee7fcdaafdac",
"metadata": {},
"source": [
"# How to load CSVs\n",
"\n",
"A [comma-separated values (CSV)](https://en.wikipedia.org/wiki/Comma-separated_values) file is a delimited text file that uses a comma to separate values. Each line of the file is a data record. Each record consists of one or more fields, separated by commas.\n",
"\n",
"LangChain implements a [CSV Loader](https://api.python.langchain.com/en/latest/document_loaders/langchain_community.document_loaders.csv_loader.CSVLoader.html) that will load CSV files into a sequence of [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html#langchain_core.documents.base.Document) objects. Each row of the CSV file is translated to one document."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "64a25376-c31a-422e-845b-6538dcc68898",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='Team: Nationals\\n\"Payroll (millions)\": 81.34\\n\"Wins\": 98' metadata={'source': '../../../docs/integrations/document_loaders/example_data/mlb_teams_2012.csv', 'row': 0}\n",
"page_content='Team: Reds\\n\"Payroll (millions)\": 82.20\\n\"Wins\": 97' metadata={'source': '../../../docs/integrations/document_loaders/example_data/mlb_teams_2012.csv', 'row': 1}\n"
]
}
],
"source": [
"from langchain_community.document_loaders.csv_loader import CSVLoader\n",
"\n",
"file_path = (\n",
" \"../../../docs/integrations/document_loaders/example_data/mlb_teams_2012.csv\"\n",
")\n",
"\n",
"loader = CSVLoader(file_path=file_path)\n",
"data = loader.load()\n",
"\n",
"for record in data[:2]:\n",
" print(record)"
]
},
{
"cell_type": "markdown",
"id": "1c716f76-364d-4515-ada9-0ae7c75e61b2",
"metadata": {},
"source": [
"## Customizing the CSV parsing and loading\n",
"\n",
"`CSVLoader` will accept a `csv_args` kwarg that supports customization of arguments passed to Python's `csv.DictReader`. See the [csv module](https://docs.python.org/3/library/csv.html) documentation for more information of what csv args are supported."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "bf07fdee-d3a6-49c3-a517-bcba6819e8ea",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='MLB Team: Team\\nPayroll in millions: \"Payroll (millions)\"\\nWins: \"Wins\"' metadata={'source': '../../../docs/integrations/document_loaders/example_data/mlb_teams_2012.csv', 'row': 0}\n",
"page_content='MLB Team: Nationals\\nPayroll in millions: 81.34\\nWins: 98' metadata={'source': '../../../docs/integrations/document_loaders/example_data/mlb_teams_2012.csv', 'row': 1}\n"
]
}
],
"source": [
"loader = CSVLoader(\n",
" file_path=file_path,\n",
" csv_args={\n",
" \"delimiter\": \",\",\n",
" \"quotechar\": '\"',\n",
" \"fieldnames\": [\"MLB Team\", \"Payroll in millions\", \"Wins\"],\n",
" },\n",
")\n",
"\n",
"data = loader.load()\n",
"for record in data[:2]:\n",
" print(record)"
]
},
{
"cell_type": "markdown",
"id": "433536be-1531-43ae-920a-14fe4deef844",
"metadata": {},
"source": [
"## Specify a column to identify the document source\n",
"\n",
"The `\"source\"` key on [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html#langchain_core.documents.base.Document) metadata can be set using a column of the CSV. Use the `source_column` argument to specify a source for the document created from each row. Otherwise `file_path` will be used as the source for all documents created from the CSV file.\n",
"\n",
"This is useful when using documents loaded from CSV files for chains that answer questions using sources."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d927392c-95e6-4a82-86c2-978387ebe91a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='Team: Nationals\\n\"Payroll (millions)\": 81.34\\n\"Wins\": 98' metadata={'source': 'Nationals', 'row': 0}\n",
"page_content='Team: Reds\\n\"Payroll (millions)\": 82.20\\n\"Wins\": 97' metadata={'source': 'Reds', 'row': 1}\n"
]
}
],
"source": [
"loader = CSVLoader(file_path=file_path, source_column=\"Team\")\n",
"\n",
"data = loader.load()\n",
"for record in data[:2]:\n",
" print(record)"
]
},
{
"cell_type": "markdown",
"id": "cab6a4bd-476b-4f4c-92e0-5d1cbcd1f6bf",
"metadata": {},
"source": [
"## Load from a string\n",
"\n",
"Python's `tempfile` can be used when working with CSV strings directly."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "f3fb28b7-8ebe-4af9-9b7d-719e9a252a46",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='Team: Nationals\\n\"Payroll (millions)\": 81.34\\n\"Wins\": 98' metadata={'source': 'Nationals', 'row': 0}\n",
"page_content='Team: Reds\\n\"Payroll (millions)\": 82.20\\n\"Wins\": 97' metadata={'source': 'Reds', 'row': 1}\n"
]
}
],
"source": [
"import tempfile\n",
"from io import StringIO\n",
"\n",
"string_data = \"\"\"\n",
"\"Team\", \"Payroll (millions)\", \"Wins\"\n",
"\"Nationals\", 81.34, 98\n",
"\"Reds\", 82.20, 97\n",
"\"Yankees\", 197.96, 95\n",
"\"Giants\", 117.62, 94\n",
"\"\"\".strip()\n",
"\n",
"\n",
"with tempfile.NamedTemporaryFile(delete=False, mode=\"w+\") as temp_file:\n",
" temp_file.write(string_data)\n",
" temp_file_path = temp_file.name\n",
"\n",
"loader = CSVLoader(file_path=temp_file_path)\n",
"loader.load()\n",
"for record in data[:2]:\n",
" print(record)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,778 @@
{
"cells": [
{
"cell_type": "raw",
"id": "c5990f4f-4430-4bbb-8d25-9703d7d8e95c",
"metadata": {},
"source": [
"---\n",
"title: Custom Document Loader\n",
"sidebar_position: 10\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "4be0aa7c-aee3-4e11-b7f4-059611ab8626",
"metadata": {},
"source": [
"# How to create a custom Document Loader\n",
"\n",
"## Overview\n",
"\n",
"\n",
"Applications based on LLMs frequently entail extracting data from databases or files, like PDFs, and converting it into a format that LLMs can utilize. In LangChain, this usually involves creating Document objects, which encapsulate the extracted text (`page_content`) along with metadata—a dictionary containing details about the document, such as the author's name or the date of publication.\n",
"\n",
"`Document` objects are often formatted into prompts that are fed into an LLM, allowing the LLM to use the information in the `Document` to generate a desired response (e.g., summarizing the document).\n",
"`Documents` can be either used immediately or indexed into a vectorstore for future retrieval and use.\n",
"\n",
"The main abstractions for Document Loading are:\n",
"\n",
"\n",
"| Component | Description |\n",
"|----------------|--------------------------------|\n",
"| Document | Contains `text` and `metadata` |\n",
"| BaseLoader | Use to convert raw data into `Documents` |\n",
"| Blob | A representation of binary data that's located either in a file or in memory |\n",
"| BaseBlobParser | Logic to parse a `Blob` to yield `Document` objects |\n",
"\n",
"This guide will demonstrate how to write custom document loading and file parsing logic; specifically, we'll see how to:\n",
"\n",
"1. Create a standard document Loader by sub-classing from `BaseLoader`.\n",
"2. Create a parser using `BaseBlobParser` and use it in conjunction with `Blob` and `BlobLoaders`. This is useful primarily when working with files."
]
},
{
"cell_type": "markdown",
"id": "20dc4c18-accc-4009-805c-961f3e8dc50a",
"metadata": {},
"source": [
"## Standard Document Loader\n",
"\n",
"A document loader can be implemented by sub-classing from a `BaseLoader` which provides a standard interface for loading documents.\n",
"\n",
"### Interface \n",
"\n",
"| Method Name | Explanation |\n",
"|-------------|-------------|\n",
"| lazy_load | Used to load documents one by one **lazily**. Use for production code. |\n",
"| alazy_load | Async variant of `lazy_load` |\n",
"| load | Used to load all the documents into memory **eagerly**. Use for prototyping or interactive work. |\n",
"| aload | Used to load all the documents into memory **eagerly**. Use for prototyping or interactive work. **Added in 2024-04 to LangChain.** |\n",
"\n",
"* The `load` methods is a convenience method meant solely for prototyping work -- it just invokes `list(self.lazy_load())`.\n",
"* The `alazy_load` has a default implementation that will delegate to `lazy_load`. If you're using async, we recommend overriding the default implementation and providing a native async implementation.\n",
"\n",
"::: {.callout-important}\n",
"When implementing a document loader do **NOT** provide parameters via the `lazy_load` or `alazy_load` methods.\n",
"\n",
"All configuration is expected to be passed through the initializer (__init__). This was a design choice made by LangChain to make sure that once a document loader has been instantiated it has all the information needed to load documents.\n",
":::\n",
"\n",
"\n",
"### Implementation\n",
"\n",
"Let's create an example of a standard document loader that loads a file and creates a document from each line in the file."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "20f128c1-1a2c-43b9-9e7b-cf9b3a86d1db",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from typing import AsyncIterator, Iterator\n",
"\n",
"from langchain_core.document_loaders import BaseLoader\n",
"from langchain_core.documents import Document\n",
"\n",
"\n",
"class CustomDocumentLoader(BaseLoader):\n",
" \"\"\"An example document loader that reads a file line by line.\"\"\"\n",
"\n",
" def __init__(self, file_path: str) -> None:\n",
" \"\"\"Initialize the loader with a file path.\n",
"\n",
" Args:\n",
" file_path: The path to the file to load.\n",
" \"\"\"\n",
" self.file_path = file_path\n",
"\n",
" def lazy_load(self) -> Iterator[Document]: # <-- Does not take any arguments\n",
" \"\"\"A lazy loader that reads a file line by line.\n",
"\n",
" When you're implementing lazy load methods, you should use a generator\n",
" to yield documents one by one.\n",
" \"\"\"\n",
" with open(self.file_path, encoding=\"utf-8\") as f:\n",
" line_number = 0\n",
" for line in f:\n",
" yield Document(\n",
" page_content=line,\n",
" metadata={\"line_number\": line_number, \"source\": self.file_path},\n",
" )\n",
" line_number += 1\n",
"\n",
" # alazy_load is OPTIONAL.\n",
" # If you leave out the implementation, a default implementation which delegates to lazy_load will be used!\n",
" async def alazy_load(\n",
" self,\n",
" ) -> AsyncIterator[Document]: # <-- Does not take any arguments\n",
" \"\"\"An async lazy loader that reads a file line by line.\"\"\"\n",
" # Requires aiofiles\n",
" # Install with `pip install aiofiles`\n",
" # https://github.com/Tinche/aiofiles\n",
" import aiofiles\n",
"\n",
" async with aiofiles.open(self.file_path, encoding=\"utf-8\") as f:\n",
" line_number = 0\n",
" async for line in f:\n",
" yield Document(\n",
" page_content=line,\n",
" metadata={\"line_number\": line_number, \"source\": self.file_path},\n",
" )\n",
" line_number += 1"
]
},
{
"cell_type": "markdown",
"id": "eb845512-3d46-44fa-a4c6-ff723533abbe",
"metadata": {
"tags": []
},
"source": [
"### Test 🧪\n",
"\n",
"\n",
"To test out the document loader, we need a file with some quality content."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "b1751198-c6dd-4149-95bd-6370ce8fa06f",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"with open(\"./meow.txt\", \"w\", encoding=\"utf-8\") as f:\n",
" quality_content = \"meow meow🐱 \\n meow meow🐱 \\n meow😻😻\"\n",
" f.write(quality_content)\n",
"\n",
"loader = CustomDocumentLoader(\"./meow.txt\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "71ef1482-f9de-4852-b5a4-0938f350612e",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"<class 'langchain_core.documents.base.Document'>\n",
"page_content='meow meow🐱 \\n' metadata={'line_number': 0, 'source': './meow.txt'}\n",
"\n",
"<class 'langchain_core.documents.base.Document'>\n",
"page_content=' meow meow🐱 \\n' metadata={'line_number': 1, 'source': './meow.txt'}\n",
"\n",
"<class 'langchain_core.documents.base.Document'>\n",
"page_content=' meow😻😻' metadata={'line_number': 2, 'source': './meow.txt'}\n"
]
}
],
"source": [
"## Test out the lazy load interface\n",
"for doc in loader.lazy_load():\n",
" print()\n",
" print(type(doc))\n",
" print(doc)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "1588e78c-e81a-4d40-b36c-634242c84a6a",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"<class 'langchain_core.documents.base.Document'>\n",
"page_content='meow meow🐱 \\n' metadata={'line_number': 0, 'source': './meow.txt'}\n",
"\n",
"<class 'langchain_core.documents.base.Document'>\n",
"page_content=' meow meow🐱 \\n' metadata={'line_number': 1, 'source': './meow.txt'}\n",
"\n",
"<class 'langchain_core.documents.base.Document'>\n",
"page_content=' meow😻😻' metadata={'line_number': 2, 'source': './meow.txt'}\n"
]
}
],
"source": [
"## Test out the async implementation\n",
"async for doc in loader.alazy_load():\n",
" print()\n",
" print(type(doc))\n",
" print(doc)"
]
},
{
"cell_type": "markdown",
"id": "56cb443e-f987-4386-b4ec-975ee129adb2",
"metadata": {},
"source": [
"::: {.callout-tip}\n",
"\n",
"`load()` can be helpful in an interactive environment such as a jupyter notebook.\n",
"\n",
"Avoid using it for production code since eager loading assumes that all the content\n",
"can fit into memory, which is not always the case, especially for enterprise data.\n",
":::"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "df5ad46a-9e00-4073-8505-489fc4f3799e",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='meow meow🐱 \\n', metadata={'line_number': 0, 'source': './meow.txt'}),\n",
" Document(page_content=' meow meow🐱 \\n', metadata={'line_number': 1, 'source': './meow.txt'}),\n",
" Document(page_content=' meow😻😻', metadata={'line_number': 2, 'source': './meow.txt'})]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"loader.load()"
]
},
{
"cell_type": "markdown",
"id": "639fe87c-b65f-4bef-8fe2-d10be85589f4",
"metadata": {},
"source": [
"## Working with Files\n",
"\n",
"Many document loaders invovle parsing files. The difference between such loaders usually stems from how the file is parsed rather than how the file is loaded. For example, you can use `open` to read the binary content of either a PDF or a markdown file, but you need different parsing logic to convert that binary data into text.\n",
"\n",
"As a result, it can be helpful to decouple the parsing logic from the loading logic, which makes it easier to re-use a given parser regardless of how the data was loaded.\n",
"\n",
"### BaseBlobParser\n",
"\n",
"A `BaseBlobParser` is an interface that accepts a `blob` and outputs a list of `Document` objects. A `blob` is a representation of data that lives either in memory or in a file. LangChain python has a `Blob` primitive which is inspired by the [Blob WebAPI spec](https://developer.mozilla.org/en-US/docs/Web/API/Blob)."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "209f6a91-2f15-4cb2-9237-f79fc9493b82",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain_core.document_loaders import BaseBlobParser, Blob\n",
"\n",
"\n",
"class MyParser(BaseBlobParser):\n",
" \"\"\"A simple parser that creates a document from each line.\"\"\"\n",
"\n",
" def lazy_parse(self, blob: Blob) -> Iterator[Document]:\n",
" \"\"\"Parse a blob into a document line by line.\"\"\"\n",
" line_number = 0\n",
" with blob.as_bytes_io() as f:\n",
" for line in f:\n",
" line_number += 1\n",
" yield Document(\n",
" page_content=line,\n",
" metadata={\"line_number\": line_number, \"source\": blob.source},\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "b1275c59-06d4-458f-abd2-fcbad0bde442",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"blob = Blob.from_path(\"./meow.txt\")\n",
"parser = MyParser()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "56a3d707-2086-413b-ae82-50e92ddb27f6",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='meow meow🐱 \\n', metadata={'line_number': 1, 'source': './meow.txt'}),\n",
" Document(page_content=' meow meow🐱 \\n', metadata={'line_number': 2, 'source': './meow.txt'}),\n",
" Document(page_content=' meow😻😻', metadata={'line_number': 3, 'source': './meow.txt'})]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(parser.lazy_parse(blob))"
]
},
{
"cell_type": "markdown",
"id": "433bfb7c-7767-43bc-b71e-42413d7494a8",
"metadata": {},
"source": [
"Using the **blob** API also allows one to load content direclty from memory without having to read it from a file!"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "20d03092-ba35-47d7-b612-9d1631c261cd",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='some data from memory\\n', metadata={'line_number': 1, 'source': None}),\n",
" Document(page_content='meow', metadata={'line_number': 2, 'source': None})]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blob = Blob(data=b\"some data from memory\\nmeow\")\n",
"list(parser.lazy_parse(blob))"
]
},
{
"cell_type": "markdown",
"id": "d401c5e9-32cc-41e2-973f-c70d1cd3ba76",
"metadata": {},
"source": [
"### Blob\n",
"\n",
"Let's take a quick look through some of the Blob API."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "a9e92e0e-c8da-401c-b8c6-f0676004cf58",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"blob = Blob.from_path(\"./meow.txt\", metadata={\"foo\": \"bar\"})"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "6b559d30-8b0c-4e45-86b1-e4602d9aaa7e",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"'utf-8'"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blob.encoding"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "2f7b145a-9c6f-47f9-9487-1f4b25aff46f",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"b'meow meow\\xf0\\x9f\\x90\\xb1 \\n meow meow\\xf0\\x9f\\x90\\xb1 \\n meow\\xf0\\x9f\\x98\\xbb\\xf0\\x9f\\x98\\xbb'"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blob.as_bytes()"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "9b9482fa-c49c-42cd-a2ef-80bc93214631",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"'meow meow🐱 \\n meow meow🐱 \\n meow😻😻'"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blob.as_string()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "04cc7a81-290e-4ef8-b7e1-d885fcc59ece",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"<contextlib._GeneratorContextManager at 0x743f34324450>"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blob.as_bytes_io()"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "ec8de0ab-51d7-4e41-82c9-3ce0a6fdc2cd",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"{'foo': 'bar'}"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blob.metadata"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "19eae991-ae48-43c2-8952-7347cdb76a34",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"'./meow.txt'"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blob.source"
]
},
{
"cell_type": "markdown",
"id": "3ea67645-a367-48ce-b164-0d9f00c17370",
"metadata": {},
"source": [
"### Blob Loaders\n",
"\n",
"While a parser encapsulates the logic needed to parse binary data into documents, *blob loaders* encapsulate the logic that's necessary to load blobs from a given storage location.\n",
"\n",
"A the moment, `LangChain` only supports `FileSystemBlobLoader`.\n",
"\n",
"You can use the `FileSystemBlobLoader` to load blobs and then use the parser to parse them."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "c093becb-2e84-4329-89e3-956a3bd765e5",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain_community.document_loaders.blob_loaders import FileSystemBlobLoader\n",
"\n",
"blob_loader = FileSystemBlobLoader(path=\".\", glob=\"*.mdx\", show_progress=True)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "77739dab-2a1e-4b64-8daa-fee8aa029972",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "45e85d3f63224bb59db02a40ae2e3268",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/8 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='# Microsoft Office\\n' metadata={'line_number': 1, 'source': 'office_file.mdx'}\n",
"page_content='# Markdown\\n' metadata={'line_number': 1, 'source': 'markdown.mdx'}\n",
"page_content='# JSON\\n' metadata={'line_number': 1, 'source': 'json.mdx'}\n",
"page_content='---\\n' metadata={'line_number': 1, 'source': 'pdf.mdx'}\n",
"page_content='---\\n' metadata={'line_number': 1, 'source': 'index.mdx'}\n",
"page_content='# File Directory\\n' metadata={'line_number': 1, 'source': 'file_directory.mdx'}\n",
"page_content='# CSV\\n' metadata={'line_number': 1, 'source': 'csv.mdx'}\n",
"page_content='# HTML\\n' metadata={'line_number': 1, 'source': 'html.mdx'}\n"
]
}
],
"source": [
"parser = MyParser()\n",
"for blob in blob_loader.yield_blobs():\n",
" for doc in parser.lazy_parse(blob):\n",
" print(doc)\n",
" break"
]
},
{
"cell_type": "markdown",
"id": "f016390c-d38b-4261-946d-34eefe546df7",
"metadata": {},
"source": [
"### Generic Loader\n",
"\n",
"LangChain has a `GenericLoader` abstraction which composes a `BlobLoader` with a `BaseBlobParser`.\n",
"\n",
"`GenericLoader` is meant to provide standardized classmethods that make it easy to use existing `BlobLoader` implementations. At the moment, only the `FileSystemBlobLoader` is supported."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "1de74daf-70ee-4616-9089-d28e26b16851",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "5f1f6810a71a4909ac9fe1e8f8cb9e0a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/8 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='# Microsoft Office\\n' metadata={'line_number': 1, 'source': 'office_file.mdx'}\n",
"page_content='\\n' metadata={'line_number': 2, 'source': 'office_file.mdx'}\n",
"page_content='>[The Microsoft Office](https://www.office.com/) suite of productivity software includes Microsoft Word, Microsoft Excel, Microsoft PowerPoint, Microsoft Outlook, and Microsoft OneNote. It is available for Microsoft Windows and macOS operating systems. It is also available on Android and iOS.\\n' metadata={'line_number': 3, 'source': 'office_file.mdx'}\n",
"page_content='\\n' metadata={'line_number': 4, 'source': 'office_file.mdx'}\n",
"page_content='This covers how to load commonly used file formats including `DOCX`, `XLSX` and `PPTX` documents into a document format that we can use downstream.\\n' metadata={'line_number': 5, 'source': 'office_file.mdx'}\n",
"... output truncated for demo purposes\n"
]
}
],
"source": [
"from langchain_community.document_loaders.generic import GenericLoader\n",
"\n",
"loader = GenericLoader.from_filesystem(\n",
" path=\".\", glob=\"*.mdx\", show_progress=True, parser=MyParser()\n",
")\n",
"\n",
"for idx, doc in enumerate(loader.lazy_load()):\n",
" if idx < 5:\n",
" print(doc)\n",
"\n",
"print(\"... output truncated for demo purposes\")"
]
},
{
"cell_type": "markdown",
"id": "902048b7-ff04-46c0-97b5-935b40ff8511",
"metadata": {},
"source": [
"#### Custom Generic Loader\n",
"\n",
"If you really like creating classes, you can sub-class and create a class to encapsulate the logic together.\n",
"\n",
"You can sub-class from this class to load content using an existing loader."
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "23633102-dc44-4fed-a4e1-8159489101c8",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from typing import Any\n",
"\n",
"\n",
"class MyCustomLoader(GenericLoader):\n",
" @staticmethod\n",
" def get_parser(**kwargs: Any) -> BaseBlobParser:\n",
" \"\"\"Override this method to associate a default parser with the class.\"\"\"\n",
" return MyParser()"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "dc95be85-4a29-4c6f-a260-08afa3c95538",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "4320598ea3b44a52b1873e1c801db312",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/8 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"page_content='# Microsoft Office\\n' metadata={'line_number': 1, 'source': 'office_file.mdx'}\n",
"page_content='\\n' metadata={'line_number': 2, 'source': 'office_file.mdx'}\n",
"page_content='>[The Microsoft Office](https://www.office.com/) suite of productivity software includes Microsoft Word, Microsoft Excel, Microsoft PowerPoint, Microsoft Outlook, and Microsoft OneNote. It is available for Microsoft Windows and macOS operating systems. It is also available on Android and iOS.\\n' metadata={'line_number': 3, 'source': 'office_file.mdx'}\n",
"page_content='\\n' metadata={'line_number': 4, 'source': 'office_file.mdx'}\n",
"page_content='This covers how to load commonly used file formats including `DOCX`, `XLSX` and `PPTX` documents into a document format that we can use downstream.\\n' metadata={'line_number': 5, 'source': 'office_file.mdx'}\n",
"... output truncated for demo purposes\n"
]
}
],
"source": [
"loader = MyCustomLoader.from_filesystem(path=\".\", glob=\"*.mdx\", show_progress=True)\n",
"\n",
"for idx, doc in enumerate(loader.lazy_load()):\n",
" if idx < 5:\n",
" print(doc)\n",
"\n",
"print(\"... output truncated for demo purposes\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,392 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9122e4b9-4883-4e6e-940b-ab44a70f0951",
"metadata": {},
"source": [
"# How to load documents from a directory\n",
"\n",
"LangChain's [DirectoryLoader](https://api.python.langchain.com/en/latest/document_loaders/langchain_community.document_loaders.directory.DirectoryLoader.html) implements functionality for reading files from disk into LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html#langchain_core.documents.base.Document) objects. Here we demonstrate:\n",
"\n",
"- How to load from a filesystem, including use of wildcard patterns;\n",
"- How to use multithreading for file I/O;\n",
"- How to use custom loader classes to parse specific file types (e.g., code);\n",
"- How to handle errors, such as those due to decoding."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1c1e3796-bee8-4882-8065-6b98e48ec53a",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import DirectoryLoader"
]
},
{
"cell_type": "markdown",
"id": "e3cdb7bb-1f58-4a7a-af83-599443127834",
"metadata": {},
"source": [
"`DirectoryLoader` accepts a `loader_cls` kwarg, which defaults to [UnstructuredLoader](/docs/integrations/document_loaders/unstructured_file). [Unstructured](https://unstructured-io.github.io/unstructured/) supports parsing for a number of formats, such as PDF and HTML. Here we use it to read in a markdown (.md) file.\n",
"\n",
"We can use the `glob` parameter to control which files to load. Note that here it doesn't load the `.rst` file or the `.html` files."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "bd2fcd1f-8286-499b-b43a-0c17084ae8ee",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"20"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"loader = DirectoryLoader(\"../\", glob=\"**/*.md\")\n",
"docs = loader.load()\n",
"len(docs)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "9ff1503d-3ac0-4172-99ec-15c9a4a707d8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Security\n",
"\n",
"LangChain has a large ecosystem of integrations with various external resources like local\n"
]
}
],
"source": [
"print(docs[0].page_content[:100])"
]
},
{
"cell_type": "markdown",
"id": "b8b1cee8-626a-461a-8d33-1c56120f1cc0",
"metadata": {},
"source": [
"## Show a progress bar\n",
"\n",
"By default a progress bar will not be shown. To show a progress bar, install the `tqdm` library (e.g. `pip install tqdm`), and set the `show_progress` parameter to `True`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "cfa48224-5d02-4aa7-93c7-ce48241645d5",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 20/20 [00:00<00:00, 54.56it/s]\n"
]
}
],
"source": [
"loader = DirectoryLoader(\"../\", glob=\"**/*.md\", show_progress=True)\n",
"docs = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "5e02c922-6a4b-48e6-8c46-5015553eafbe",
"metadata": {},
"source": [
"## Use multithreading\n",
"\n",
"By default the loading happens in one thread. In order to utilize several threads set the `use_multithreading` flag to true."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "aae1c580-6d7c-409c-bfc8-3049fa8bdbf9",
"metadata": {},
"outputs": [],
"source": [
"loader = DirectoryLoader(\"../\", glob=\"**/*.md\", use_multithreading=True)\n",
"docs = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "5add3f54-f303-4006-90c9-540a90ab8c46",
"metadata": {},
"source": [
"## Change loader class\n",
"By default this uses the `UnstructuredLoader` class. To customize the loader, specify the loader class in the `loader_cls` kwarg. Below we show an example using [TextLoader](https://api.python.langchain.com/en/latest/document_loaders/langchain_community.document_loaders.text.TextLoader.html):"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "d369ee78-ea24-48cc-9f46-1f5cd4b56f48",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import TextLoader\n",
"\n",
"loader = DirectoryLoader(\"../\", glob=\"**/*.md\", loader_cls=TextLoader)\n",
"docs = loader.load()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "2863d7dd-2d56-4fef-8bfd-95c48a6b4a71",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# Security\n",
"\n",
"LangChain has a large ecosystem of integrations with various external resources like loc\n"
]
}
],
"source": [
"print(docs[0].page_content[:100])"
]
},
{
"cell_type": "markdown",
"id": "c97ed37b-38c0-4f31-9403-d3a5d5444f78",
"metadata": {},
"source": [
"Notice that while the `UnstructuredLoader` parses Markdown headers, `TextLoader` does not.\n",
"\n",
"If you need to load Python source code files, use the `PythonLoader`:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "5ef483a8-57d3-45e5-93be-37c8416c543c",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import PythonLoader\n",
"\n",
"loader = DirectoryLoader(\"../../../../../\", glob=\"**/*.py\", loader_cls=PythonLoader)"
]
},
{
"cell_type": "markdown",
"id": "61dd1428-8246-47e3-b1da-f6a3d6f05566",
"metadata": {},
"source": [
"## Auto-detect file encodings with TextLoader\n",
"\n",
"`DirectoryLoader` can help manage errors due to variations in file encodings. Below we will attempt to load in a collection of files, one of which includes non-UTF8 encodings."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "e69db7ae-0385-4129-968f-17c42c7a635c",
"metadata": {},
"outputs": [],
"source": [
"path = \"../../../../libs/langchain/tests/unit_tests/examples/\"\n",
"\n",
"loader = DirectoryLoader(path, glob=\"**/*.txt\", loader_cls=TextLoader)"
]
},
{
"cell_type": "markdown",
"id": "e3b61cf0-809b-4c97-b1a4-17c6aa4343e1",
"metadata": {},
"source": [
"### A. Default Behavior\n",
"\n",
"By default we raise an error:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "4b8f56be-122a-4c56-86a5-a70631a78ec7",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Error loading file ../../../../libs/langchain/tests/unit_tests/examples/example-non-utf8.txt\n"
]
},
{
"ename": "RuntimeError",
"evalue": "Error loading ../../../../libs/langchain/tests/unit_tests/examples/example-non-utf8.txt",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mUnicodeDecodeError\u001b[0m Traceback (most recent call last)",
"File \u001b[0;32m~/repos/langchain/libs/community/langchain_community/document_loaders/text.py:43\u001b[0m, in \u001b[0;36mTextLoader.lazy_load\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 42\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfile_path, encoding\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mencoding) \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[0;32m---> 43\u001b[0m text \u001b[38;5;241m=\u001b[39m \u001b[43mf\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 44\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mUnicodeDecodeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n",
"File \u001b[0;32m~/.pyenv/versions/3.10.4/lib/python3.10/codecs.py:322\u001b[0m, in \u001b[0;36mBufferedIncrementalDecoder.decode\u001b[0;34m(self, input, final)\u001b[0m\n\u001b[1;32m 321\u001b[0m data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbuffer \u001b[38;5;241m+\u001b[39m \u001b[38;5;28minput\u001b[39m\n\u001b[0;32m--> 322\u001b[0m (result, consumed) \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_buffer_decode\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43merrors\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfinal\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 323\u001b[0m \u001b[38;5;66;03m# keep undecoded input until the next call\u001b[39;00m\n",
"\u001b[0;31mUnicodeDecodeError\u001b[0m: 'utf-8' codec can't decode byte 0xca in position 0: invalid continuation byte",
"\nThe above exception was the direct cause of the following exception:\n",
"\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[10], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mloader\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mload\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/repos/langchain/libs/community/langchain_community/document_loaders/directory.py:117\u001b[0m, in \u001b[0;36mDirectoryLoader.load\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 115\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mload\u001b[39m(\u001b[38;5;28mself\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m List[Document]:\n\u001b[1;32m 116\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Load documents.\"\"\"\u001b[39;00m\n\u001b[0;32m--> 117\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlazy_load\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/repos/langchain/libs/community/langchain_community/document_loaders/directory.py:182\u001b[0m, in \u001b[0;36mDirectoryLoader.lazy_load\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 180\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 181\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m items:\n\u001b[0;32m--> 182\u001b[0m \u001b[38;5;28;01myield from\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_lazy_load_file(i, p, pbar)\n\u001b[1;32m 184\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m pbar:\n\u001b[1;32m 185\u001b[0m pbar\u001b[38;5;241m.\u001b[39mclose()\n",
"File \u001b[0;32m~/repos/langchain/libs/community/langchain_community/document_loaders/directory.py:220\u001b[0m, in \u001b[0;36mDirectoryLoader._lazy_load_file\u001b[0;34m(self, item, path, pbar)\u001b[0m\n\u001b[1;32m 218\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 219\u001b[0m logger\u001b[38;5;241m.\u001b[39merror(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mError loading file \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mstr\u001b[39m(item)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m--> 220\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[1;32m 221\u001b[0m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[1;32m 222\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m pbar:\n",
"File \u001b[0;32m~/repos/langchain/libs/community/langchain_community/document_loaders/directory.py:210\u001b[0m, in \u001b[0;36mDirectoryLoader._lazy_load_file\u001b[0;34m(self, item, path, pbar)\u001b[0m\n\u001b[1;32m 208\u001b[0m loader \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mloader_cls(\u001b[38;5;28mstr\u001b[39m(item), \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mloader_kwargs)\n\u001b[1;32m 209\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 210\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m subdoc \u001b[38;5;129;01min\u001b[39;00m loader\u001b[38;5;241m.\u001b[39mlazy_load():\n\u001b[1;32m 211\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m subdoc\n\u001b[1;32m 212\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mNotImplementedError\u001b[39;00m:\n",
"File \u001b[0;32m~/repos/langchain/libs/community/langchain_community/document_loaders/text.py:56\u001b[0m, in \u001b[0;36mTextLoader.lazy_load\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 54\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m---> 56\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mError loading \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfile_path\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01me\u001b[39;00m\n\u001b[1;32m 57\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 58\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mError loading \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfile_path\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01me\u001b[39;00m\n",
"\u001b[0;31mRuntimeError\u001b[0m: Error loading ../../../../libs/langchain/tests/unit_tests/examples/example-non-utf8.txt"
]
}
],
"source": [
"loader.load()"
]
},
{
"cell_type": "markdown",
"id": "48308077-2d99-4dd6-9bf1-dd1ad6c64b0f",
"metadata": {},
"source": [
"The file `example-non-utf8.txt` uses a different encoding, so the `load()` function fails with a helpful message indicating which file failed decoding.\n",
"\n",
"With the default behavior of `TextLoader` any failure to load any of the documents will fail the whole loading process and no documents are loaded.\n",
"\n",
"### B. Silent fail\n",
"\n",
"We can pass the parameter `silent_errors` to the `DirectoryLoader` to skip the files which could not be loaded and continue the load process."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "b333c652-a7ad-47f4-8be8-d27c18ef11b7",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Error loading file ../../../../libs/langchain/tests/unit_tests/examples/example-non-utf8.txt: Error loading ../../../../libs/langchain/tests/unit_tests/examples/example-non-utf8.txt\n"
]
}
],
"source": [
"loader = DirectoryLoader(\n",
" path, glob=\"**/*.txt\", loader_cls=TextLoader, silent_errors=True\n",
")\n",
"docs = loader.load()"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "b99ef682-b892-4790-8964-40185fea41a2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['../../../../libs/langchain/tests/unit_tests/examples/example-utf8.txt']"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"doc_sources = [doc.metadata[\"source\"] for doc in docs]\n",
"doc_sources"
]
},
{
"cell_type": "markdown",
"id": "da475bff-2f4f-4ea3-a058-2979042c5326",
"metadata": {},
"source": [
"### C. Auto detect encodings\n",
"\n",
"We can also ask `TextLoader` to auto detect the file encoding before failing, by passing the `autodetect_encoding` to the loader class."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "832760da-ed9f-4e68-a67c-35493bde2214",
"metadata": {},
"outputs": [],
"source": [
"text_loader_kwargs = {\"autodetect_encoding\": True}\n",
"loader = DirectoryLoader(\n",
" path, glob=\"**/*.txt\", loader_cls=TextLoader, loader_kwargs=text_loader_kwargs\n",
")\n",
"docs = loader.load()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "5c4f4dba-f84f-496e-9378-3e6858305619",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['../../../../libs/langchain/tests/unit_tests/examples/example-utf8.txt',\n",
" '../../../../libs/langchain/tests/unit_tests/examples/example-non-utf8.txt']"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"doc_sources = [doc.metadata[\"source\"] for doc in docs]\n",
"doc_sources"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,119 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0c6c50fc-15e1-4767-925a-53a37c430b9b",
"metadata": {},
"source": [
"# How to load HTML\n",
"\n",
"The HyperText Markup Language or [HTML](https://en.wikipedia.org/wiki/HTML) is the standard markup language for documents designed to be displayed in a web browser.\n",
"\n",
"This covers how to load `HTML` documents into a LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html#langchain_core.documents.base.Document) objects that we can use downstream.\n",
"\n",
"Parsing HTML files often requires specialized tools. Here we demonstrate parsing via [Unstructured](https://unstructured-io.github.io/unstructured/) and [BeautifulSoup4](https://beautiful-soup-4.readthedocs.io/en/latest/), which can be installed via pip. Head over to the integrations page to find integrations with additional services, such as [Azure AI Document Intelligence](/docs/0.2.x/integrations/document_loaders/azure_document_intelligence) or [FireCrawl](/docs/0.2.x/integrations/document_loaders/firecrawl).\n",
"\n",
"## Loading HTML with Unstructured"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "617a5e2b-1e92-4bdd-bd04-95a4d2379410",
"metadata": {},
"outputs": [],
"source": [
"%pip install \"unstructured[html]\""
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "7d167ca3-c7c7-4ef0-b509-080629f0f482",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[Document(page_content='My First Heading\\n\\nMy first paragraph.', metadata={'source': '../../../docs/integrations/document_loaders/example_data/fake-content.html'})]\n"
]
}
],
"source": [
"from langchain_community.document_loaders import UnstructuredHTMLLoader\n",
"\n",
"file_path = \"../../../docs/integrations/document_loaders/example_data/fake-content.html\"\n",
"\n",
"loader = UnstructuredHTMLLoader(file_path)\n",
"data = loader.load()\n",
"\n",
"print(data)"
]
},
{
"cell_type": "markdown",
"id": "cc85f7e8-f62e-49bc-910e-d0b151c9d651",
"metadata": {},
"source": [
"## Loading HTML with BeautifulSoup4\n",
"\n",
"We can also use `BeautifulSoup4` to load HTML documents using the `BSHTMLLoader`. This will extract the text from the HTML into `page_content`, and the page title as `title` into `metadata`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06a5e555-8e1f-44a7-b921-4dd8aedd3bca",
"metadata": {},
"outputs": [],
"source": [
"%pip install bs4"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "0a2050a8-6df6-4696-9889-ba367d6f9caa",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[Document(page_content='\\nTest Title\\n\\n\\nMy First Heading\\nMy first paragraph.\\n\\n\\n', metadata={'source': '../../../docs/integrations/document_loaders/example_data/fake-content.html', 'title': 'Test Title'})]\n"
]
}
],
"source": [
"from langchain_community.document_loaders import BSHTMLLoader\n",
"\n",
"loader = BSHTMLLoader(file_path)\n",
"data = loader.load()\n",
"\n",
"print(data)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,402 @@
# How to load JSON
[JSON (JavaScript Object Notation)](https://en.wikipedia.org/wiki/JSON) is an open standard file format and data interchange format that uses human-readable text to store and transmit data objects consisting of attributevalue pairs and arrays (or other serializable values).
[JSON Lines](https://jsonlines.org/) is a file format where each line is a valid JSON value.
LangChain implements a [JSONLoader](https://api.python.langchain.com/en/latest/document_loaders/langchain_community.document_loaders.json_loader.JSONLoader.html)
to convert JSON and JSONL data into LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html#langchain_core.documents.base.Document)
objects. It uses a specified [jq schema](https://en.wikipedia.org/wiki/Jq_(programming_language)) to parse the JSON files, allowing for the extraction of specific fields into the content
and metadata of the LangChain Document.
It uses the `jq` python package. Check out this [manual](https://stedolan.github.io/jq/manual/#Basicfilters) for a detailed documentation of the `jq` syntax.
Here we will demonstrate:
- How to load JSON and JSONL data into the content of a LangChain `Document`;
- How to load JSON and JSONL data into metadata associated with a `Document`.
```python
#!pip install jq
```
```python
from langchain_community.document_loaders import JSONLoader
```
```python
import json
from pathlib import Path
from pprint import pprint
file_path='./example_data/facebook_chat.json'
data = json.loads(Path(file_path).read_text())
```
```python
pprint(data)
```
<CodeOutputBlock lang="python">
```
{'image': {'creation_timestamp': 1675549016, 'uri': 'image_of_the_chat.jpg'},
'is_still_participant': True,
'joinable_mode': {'link': '', 'mode': 1},
'magic_words': [],
'messages': [{'content': 'Bye!',
'sender_name': 'User 2',
'timestamp_ms': 1675597571851},
{'content': 'Oh no worries! Bye',
'sender_name': 'User 1',
'timestamp_ms': 1675597435669},
{'content': 'No Im sorry it was my mistake, the blue one is not '
'for sale',
'sender_name': 'User 2',
'timestamp_ms': 1675596277579},
{'content': 'I thought you were selling the blue one!',
'sender_name': 'User 1',
'timestamp_ms': 1675595140251},
{'content': 'Im not interested in this bag. Im interested in the '
'blue one!',
'sender_name': 'User 1',
'timestamp_ms': 1675595109305},
{'content': 'Here is $129',
'sender_name': 'User 2',
'timestamp_ms': 1675595068468},
{'photos': [{'creation_timestamp': 1675595059,
'uri': 'url_of_some_picture.jpg'}],
'sender_name': 'User 2',
'timestamp_ms': 1675595060730},
{'content': 'Online is at least $100',
'sender_name': 'User 2',
'timestamp_ms': 1675595045152},
{'content': 'How much do you want?',
'sender_name': 'User 1',
'timestamp_ms': 1675594799696},
{'content': 'Goodmorning! $50 is too low.',
'sender_name': 'User 2',
'timestamp_ms': 1675577876645},
{'content': 'Hi! Im interested in your bag. Im offering $50. Let '
'me know if you are interested. Thanks!',
'sender_name': 'User 1',
'timestamp_ms': 1675549022673}],
'participants': [{'name': 'User 1'}, {'name': 'User 2'}],
'thread_path': 'inbox/User 1 and User 2 chat',
'title': 'User 1 and User 2 chat'}
```
</CodeOutputBlock>
## Using `JSONLoader`
Suppose we are interested in extracting the values under the `content` field within the `messages` key of the JSON data. This can easily be done through the `JSONLoader` as shown below.
### JSON file
```python
loader = JSONLoader(
file_path='./example_data/facebook_chat.json',
jq_schema='.messages[].content',
text_content=False)
data = loader.load()
```
```python
pprint(data)
```
<CodeOutputBlock lang="python">
```
[Document(page_content='Bye!', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 1}),
Document(page_content='Oh no worries! Bye', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 2}),
Document(page_content='No Im sorry it was my mistake, the blue one is not for sale', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 3}),
Document(page_content='I thought you were selling the blue one!', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 4}),
Document(page_content='Im not interested in this bag. Im interested in the blue one!', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 5}),
Document(page_content='Here is $129', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 6}),
Document(page_content='', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 7}),
Document(page_content='Online is at least $100', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 8}),
Document(page_content='How much do you want?', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 9}),
Document(page_content='Goodmorning! $50 is too low.', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 10}),
Document(page_content='Hi! Im interested in your bag. Im offering $50. Let me know if you are interested. Thanks!', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 11})]
```
</CodeOutputBlock>
### JSON Lines file
If you want to load documents from a JSON Lines file, you pass `json_lines=True`
and specify `jq_schema` to extract `page_content` from a single JSON object.
```python
file_path = './example_data/facebook_chat_messages.jsonl'
pprint(Path(file_path).read_text())
```
<CodeOutputBlock lang="python">
```
('{"sender_name": "User 2", "timestamp_ms": 1675597571851, "content": "Bye!"}\n'
'{"sender_name": "User 1", "timestamp_ms": 1675597435669, "content": "Oh no '
'worries! Bye"}\n'
'{"sender_name": "User 2", "timestamp_ms": 1675596277579, "content": "No Im '
'sorry it was my mistake, the blue one is not for sale"}\n')
```
</CodeOutputBlock>
```python
loader = JSONLoader(
file_path='./example_data/facebook_chat_messages.jsonl',
jq_schema='.content',
text_content=False,
json_lines=True)
data = loader.load()
```
```python
pprint(data)
```
<CodeOutputBlock lang="python">
```
[Document(page_content='Bye!', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat_messages.jsonl', 'seq_num': 1}),
Document(page_content='Oh no worries! Bye', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat_messages.jsonl', 'seq_num': 2}),
Document(page_content='No Im sorry it was my mistake, the blue one is not for sale', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat_messages.jsonl', 'seq_num': 3})]
```
</CodeOutputBlock>
Another option is set `jq_schema='.'` and provide `content_key`:
```python
loader = JSONLoader(
file_path='./example_data/facebook_chat_messages.jsonl',
jq_schema='.',
content_key='sender_name',
json_lines=True)
data = loader.load()
```
```python
pprint(data)
```
<CodeOutputBlock lang="python">
```
[Document(page_content='User 2', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat_messages.jsonl', 'seq_num': 1}),
Document(page_content='User 1', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat_messages.jsonl', 'seq_num': 2}),
Document(page_content='User 2', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat_messages.jsonl', 'seq_num': 3})]
```
</CodeOutputBlock>
### JSON file with jq schema `content_key`
To load documents from a JSON file using the content_key within the jq schema, set is_content_key_jq_parsable=True.
Ensure that content_key is compatible and can be parsed using the jq schema.
```python
file_path = './sample.json'
pprint(Path(file_path).read_text())
```
<CodeOutputBlock lang="python">
```json
{"data": [
{"attributes": {
"message": "message1",
"tags": [
"tag1"]},
"id": "1"},
{"attributes": {
"message": "message2",
"tags": [
"tag2"]},
"id": "2"}]}
```
</CodeOutputBlock>
```python
loader = JSONLoader(
file_path=file_path,
jq_schema=".data[]",
content_key=".attributes.message",
is_content_key_jq_parsable=True,
)
data = loader.load()
```
```python
pprint(data)
```
<CodeOutputBlock lang="python">
```
[Document(page_content='message1', metadata={'source': '/path/to/sample.json', 'seq_num': 1}),
Document(page_content='message2', metadata={'source': '/path/to/sample.json', 'seq_num': 2})]
```
</CodeOutputBlock>
## Extracting metadata
Generally, we want to include metadata available in the JSON file into the documents that we create from the content.
The following demonstrates how metadata can be extracted using the `JSONLoader`.
There are some key changes to be noted. In the previous example where we didn't collect the metadata, we managed to directly specify in the schema where the value for the `page_content` can be extracted from.
```
.messages[].content
```
In the current example, we have to tell the loader to iterate over the records in the `messages` field. The jq_schema then has to be:
```
.messages[]
```
This allows us to pass the records (dict) into the `metadata_func` that has to be implemented. The `metadata_func` is responsible for identifying which pieces of information in the record should be included in the metadata stored in the final `Document` object.
Additionally, we now have to explicitly specify in the loader, via the `content_key` argument, the key from the record where the value for the `page_content` needs to be extracted from.
```python
# Define the metadata extraction function.
def metadata_func(record: dict, metadata: dict) -> dict:
metadata["sender_name"] = record.get("sender_name")
metadata["timestamp_ms"] = record.get("timestamp_ms")
return metadata
loader = JSONLoader(
file_path='./example_data/facebook_chat.json',
jq_schema='.messages[]',
content_key="content",
metadata_func=metadata_func
)
data = loader.load()
```
```python
pprint(data)
```
<CodeOutputBlock lang="python">
```
[Document(page_content='Bye!', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 1, 'sender_name': 'User 2', 'timestamp_ms': 1675597571851}),
Document(page_content='Oh no worries! Bye', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 2, 'sender_name': 'User 1', 'timestamp_ms': 1675597435669}),
Document(page_content='No Im sorry it was my mistake, the blue one is not for sale', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 3, 'sender_name': 'User 2', 'timestamp_ms': 1675596277579}),
Document(page_content='I thought you were selling the blue one!', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 4, 'sender_name': 'User 1', 'timestamp_ms': 1675595140251}),
Document(page_content='Im not interested in this bag. Im interested in the blue one!', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 5, 'sender_name': 'User 1', 'timestamp_ms': 1675595109305}),
Document(page_content='Here is $129', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 6, 'sender_name': 'User 2', 'timestamp_ms': 1675595068468}),
Document(page_content='', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 7, 'sender_name': 'User 2', 'timestamp_ms': 1675595060730}),
Document(page_content='Online is at least $100', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 8, 'sender_name': 'User 2', 'timestamp_ms': 1675595045152}),
Document(page_content='How much do you want?', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 9, 'sender_name': 'User 1', 'timestamp_ms': 1675594799696}),
Document(page_content='Goodmorning! $50 is too low.', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 10, 'sender_name': 'User 2', 'timestamp_ms': 1675577876645}),
Document(page_content='Hi! Im interested in your bag. Im offering $50. Let me know if you are interested. Thanks!', metadata={'source': '/Users/avsolatorio/WBG/langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 11, 'sender_name': 'User 1', 'timestamp_ms': 1675549022673})]
```
</CodeOutputBlock>
Now, you will see that the documents contain the metadata associated with the content we extracted.
## The `metadata_func`
As shown above, the `metadata_func` accepts the default metadata generated by the `JSONLoader`. This allows full control to the user with respect to how the metadata is formatted.
For example, the default metadata contains the `source` and the `seq_num` keys. However, it is possible that the JSON data contain these keys as well. The user can then exploit the `metadata_func` to rename the default keys and use the ones from the JSON data.
The example below shows how we can modify the `source` to only contain information of the file source relative to the `langchain` directory.
```python
# Define the metadata extraction function.
def metadata_func(record: dict, metadata: dict) -> dict:
metadata["sender_name"] = record.get("sender_name")
metadata["timestamp_ms"] = record.get("timestamp_ms")
if "source" in metadata:
source = metadata["source"].split("/")
source = source[source.index("langchain"):]
metadata["source"] = "/".join(source)
return metadata
loader = JSONLoader(
file_path='./example_data/facebook_chat.json',
jq_schema='.messages[]',
content_key="content",
metadata_func=metadata_func
)
data = loader.load()
```
```python
pprint(data)
```
<CodeOutputBlock lang="python">
```
[Document(page_content='Bye!', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 1, 'sender_name': 'User 2', 'timestamp_ms': 1675597571851}),
Document(page_content='Oh no worries! Bye', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 2, 'sender_name': 'User 1', 'timestamp_ms': 1675597435669}),
Document(page_content='No Im sorry it was my mistake, the blue one is not for sale', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 3, 'sender_name': 'User 2', 'timestamp_ms': 1675596277579}),
Document(page_content='I thought you were selling the blue one!', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 4, 'sender_name': 'User 1', 'timestamp_ms': 1675595140251}),
Document(page_content='Im not interested in this bag. Im interested in the blue one!', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 5, 'sender_name': 'User 1', 'timestamp_ms': 1675595109305}),
Document(page_content='Here is $129', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 6, 'sender_name': 'User 2', 'timestamp_ms': 1675595068468}),
Document(page_content='', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 7, 'sender_name': 'User 2', 'timestamp_ms': 1675595060730}),
Document(page_content='Online is at least $100', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 8, 'sender_name': 'User 2', 'timestamp_ms': 1675595045152}),
Document(page_content='How much do you want?', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 9, 'sender_name': 'User 1', 'timestamp_ms': 1675594799696}),
Document(page_content='Goodmorning! $50 is too low.', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 10, 'sender_name': 'User 2', 'timestamp_ms': 1675577876645}),
Document(page_content='Hi! Im interested in your bag. Im offering $50. Let me know if you are interested. Thanks!', metadata={'source': 'langchain/docs/modules/indexes/document_loaders/examples/example_data/facebook_chat.json', 'seq_num': 11, 'sender_name': 'User 1', 'timestamp_ms': 1675549022673})]
```
</CodeOutputBlock>
## Common JSON structures with jq schema
The list below provides a reference to the possible `jq_schema` the user can use to extract content from the JSON data depending on the structure.
```
JSON -> [{"text": ...}, {"text": ...}, {"text": ...}]
jq_schema -> ".[].text"
JSON -> {"key": [{"text": ...}, {"text": ...}, {"text": ...}]}
jq_schema -> ".key[].text"
JSON -> ["...", "...", "..."]
jq_schema -> ".[]"
```

View File

@@ -0,0 +1,162 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d836a98a-ad14-4bed-af76-e1877f7ef8a4",
"metadata": {},
"source": [
"# How to load Markdown\n",
"\n",
"[Markdown](https://en.wikipedia.org/wiki/Markdown) is a lightweight markup language for creating formatted text using a plain-text editor.\n",
"\n",
"Here we cover how to load `Markdown` documents into LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html#langchain_core.documents.base.Document) objects that we can use downstream.\n",
"\n",
"We will cover:\n",
"\n",
"- Basic usage;\n",
"- Parsing of Markdown into elements such as titles, list items, and text.\n",
"\n",
"LangChain implements an [UnstructuredMarkdownLoader](https://api.python.langchain.com/en/latest/document_loaders/langchain_community.document_loaders.markdown.UnstructuredMarkdownLoader.html) object which requires the [Unstructured](https://unstructured-io.github.io/unstructured/) package. First we install it:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "c8b147fb-6877-4f7a-b2ee-ee971c7bc662",
"metadata": {},
"outputs": [],
"source": [
"# !pip install \"unstructured[md]\""
]
},
{
"cell_type": "markdown",
"id": "ea8c41f8-a8dc-48cc-b78d-7b3e2427a34c",
"metadata": {},
"source": [
"Basic usage will ingest a Markdown file to a single document. Here we demonstrate on LangChain's readme:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "80c50cc4-7ce9-4418-81b9-29c52c7b3627",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🦜️🔗 LangChain\n",
"\n",
"⚡ Build context-aware reasoning applications ⚡\n",
"\n",
"Looking for the JS/TS library? Check out LangChain.js.\n",
"\n",
"To help you ship LangChain apps to production faster, check out LangSmith. \n",
"LangSmith is a unified developer platform for building,\n"
]
}
],
"source": [
"from langchain_community.document_loaders import UnstructuredMarkdownLoader\n",
"from langchain_core.documents import Document\n",
"\n",
"markdown_path = \"../../../../README.md\"\n",
"loader = UnstructuredMarkdownLoader(markdown_path)\n",
"\n",
"data = loader.load()\n",
"assert len(data) == 1\n",
"assert isinstance(data[0], Document)\n",
"readme_content = data[0].page_content\n",
"print(readme_content[:250])"
]
},
{
"cell_type": "markdown",
"id": "b7560a6e-ca5d-47e1-b176-a9c40e763ff3",
"metadata": {},
"source": [
"## Retain Elements\n",
"\n",
"Under the hood, Unstructured creates different \"elements\" for different chunks of text. By default we combine those together, but you can easily keep that separation by specifying `mode=\"elements\"`."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "a986bbce-7fd3-41d1-bc47-49f9f57c7cd1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Number of documents: 65\n",
"\n",
"page_content='🦜️🔗 LangChain' metadata={'source': '../../../../README.md', 'last_modified': '2024-04-29T13:40:19', 'page_number': 1, 'languages': ['eng'], 'filetype': 'text/markdown', 'file_directory': '../../../..', 'filename': 'README.md', 'category': 'Title'}\n",
"\n",
"page_content='⚡ Build context-aware reasoning applications ⚡' metadata={'source': '../../../../README.md', 'last_modified': '2024-04-29T13:40:19', 'page_number': 1, 'languages': ['eng'], 'parent_id': 'c3223b6f7100be08a78f1e8c0c28fde1', 'filetype': 'text/markdown', 'file_directory': '../../../..', 'filename': 'README.md', 'category': 'NarrativeText'}\n",
"\n"
]
}
],
"source": [
"loader = UnstructuredMarkdownLoader(markdown_path, mode=\"elements\")\n",
"\n",
"data = loader.load()\n",
"print(f\"Number of documents: {len(data)}\\n\")\n",
"\n",
"for document in data[:2]:\n",
" print(f\"{document}\\n\")"
]
},
{
"cell_type": "markdown",
"id": "117dc6b0-9baa-44a2-9d1d-fc38ecf7a233",
"metadata": {},
"source": [
"Note that in this case we recover three distinct element types:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "75abc139-3ded-4e8e-9f21-d0c8ec40fdfc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'Title', 'NarrativeText', 'ListItem'}\n"
]
}
],
"source": [
"print(set(document.metadata[\"category\"] for document in data))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,35 @@
# How to load Microsoft Office files
The [Microsoft Office](https://www.office.com/) suite of productivity software includes Microsoft Word, Microsoft Excel, Microsoft PowerPoint, Microsoft Outlook, and Microsoft OneNote. It is available for Microsoft Windows and macOS operating systems. It is also available on Android and iOS.
This covers how to load commonly used file formats including `DOCX`, `XLSX` and `PPTX` documents into a LangChain
[Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html#langchain_core.documents.base.Document)
object that we can use downstream.
## Loading DOCX, XLSX, PPTX with AzureAIDocumentIntelligenceLoader
[Azure AI Document Intelligence](https://aka.ms/doc-intelligence) (formerly known as `Azure Form Recognizer`) is machine-learning
based service that extracts texts (including handwriting), tables, document structures (e.g., titles, section headings, etc.) and key-value-pairs from
digital or scanned PDFs, images, Office and HTML files. Document Intelligence supports `PDF`, `JPEG/JPG`, `PNG`, `BMP`, `TIFF`, `HEIF`, `DOCX`, `XLSX`, `PPTX` and `HTML`.
This [current implementation](https://aka.ms/di-langchain) of a loader using `Document Intelligence` can incorporate content page-wise and turn it into LangChain documents. The default output format is markdown, which can be easily chained with `MarkdownHeaderTextSplitter` for semantic document chunking. You can also use `mode="single"` or `mode="page"` to return pure texts in a single page or document split by page.
### Prerequisite
An Azure AI Document Intelligence resource in one of the 3 preview regions: **East US**, **West US2**, **West Europe** - follow [this document](https://learn.microsoft.com/azure/ai-services/document-intelligence/create-document-intelligence-resource?view=doc-intel-4.0.0) to create one if you don't have. You will be passing `<endpoint>` and `<key>` as parameters to the loader.
```python
%pip install --upgrade --quiet langchain langchain-community azure-ai-documentintelligence
from langchain_community.document_loaders import AzureAIDocumentIntelligenceLoader
file_path = "<filepath>"
endpoint = "<endpoint>"
key = "<key>"
loader = AzureAIDocumentIntelligenceLoader(
api_endpoint=endpoint, api_key=key, file_path=file_path, api_model="prebuilt-layout"
)
documents = loader.load()
```

View File

@@ -0,0 +1,677 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d3dd7178-8337-44f0-a468-bc1af5c0e811",
"metadata": {},
"source": [
"# How to load PDFs\n",
"\n",
"[Portable Document Format (PDF)](https://en.wikipedia.org/wiki/PDF), standardized as ISO 32000, is a file format developed by Adobe in 1992 to present documents, including text formatting and images, in a manner independent of application software, hardware, and operating systems.\n",
"\n",
"This guide covers how to load `PDF` documents into the LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html#langchain_core.documents.base.Document) format that we use downstream.\n",
"\n",
"LangChain integrates with a host of PDF parsers. Some are simple and relatively low-level; others will support OCR and image-processing, or perform advanced document layout analysis. The right choice will depend on your application. Below we enumerate the possibilities.\n",
"\n",
"## Using PyPDF\n",
"\n",
"Here we load a PDF using `pypdf` into array of documents, where each document contains the page content and metadata with `page` number."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35c08d82-8b0a-45e2-8167-73e70f88208a",
"metadata": {},
"outputs": [],
"source": [
"%pip install pypdf"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "7d8ccd0b-8415-4916-af32-0e6d30b9496b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Document(page_content='LayoutParser : A Unified Toolkit for Deep\\nLearning Based Document Image Analysis\\nZejiang Shen1( \\x00), Ruochen Zhang2, Melissa Dell3, Benjamin Charles Germain\\nLee4, Jacob Carlson3, and Weining Li5\\n1Allen Institute for AI\\nshannons@allenai.org\\n2Brown University\\nruochen zhang@brown.edu\\n3Harvard University\\n{melissadell,jacob carlson }@fas.harvard.edu\\n4University of Washington\\nbcgl@cs.washington.edu\\n5University of Waterloo\\nw422li@uwaterloo.ca\\nAbstract. Recent advances in document image analysis (DIA) have been\\nprimarily driven by the application of neural networks. Ideally, research\\noutcomes could be easily deployed in production and extended for further\\ninvestigation. However, various factors like loosely organized codebases\\nand sophisticated model configurations complicate the easy reuse of im-\\nportant innovations by a wide audience. Though there have been on-going\\nefforts to improve reusability and simplify deep learning (DL) model\\ndevelopment in disciplines like natural language processing and computer\\nvision, none of them are optimized for challenges in the domain of DIA.\\nThis represents a major gap in the existing toolkit, as DIA is central to\\nacademic research across a wide range of disciplines in the social sciences\\nand humanities. This paper introduces LayoutParser , an open-source\\nlibrary for streamlining the usage of DL in DIA research and applica-\\ntions. The core LayoutParser library comes with a set of simple and\\nintuitive interfaces for applying and customizing DL models for layout de-\\ntection, character recognition, and many other document processing tasks.\\nTo promote extensibility, LayoutParser also incorporates a community\\nplatform for sharing both pre-trained models and full document digiti-\\nzation pipelines. We demonstrate that LayoutParser is helpful for both\\nlightweight and large-scale digitization pipelines in real-word use cases.\\nThe library is publicly available at https://layout-parser.github.io .\\nKeywords: Document Image Analysis ·Deep Learning ·Layout Analysis\\n·Character Recognition ·Open Source library ·Toolkit.\\n1 Introduction\\nDeep Learning(DL)-based approaches are the state-of-the-art for a wide range of\\ndocument image analysis (DIA) tasks including document image classification [ 11,arXiv:2103.15348v2 [cs.CV] 21 Jun 2021', metadata={'source': '../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf', 'page': 0})"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_community.document_loaders import PyPDFLoader\n",
"\n",
"file_path = (\n",
" \"../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf\"\n",
")\n",
"loader = PyPDFLoader(file_path)\n",
"pages = loader.load_and_split()\n",
"\n",
"pages[0]"
]
},
{
"cell_type": "markdown",
"id": "78ce6d1d-86cc-45e3-8259-e21fbd2c7e6c",
"metadata": {},
"source": [
"An advantage of this approach is that documents can be retrieved with page numbers.\n",
"\n",
"### Vector search over PDFs\n",
"\n",
"Once we have loaded PDFs into LangChain `Document` objects, we can index them (e.g., a RAG application) in the usual way:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ba35f1c-0a85-4f2f-a56e-3a994c69180d",
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "e0eaec77-f5cf-4172-8e39-41e1520eabba",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"13: 14 Z. Shen et al.\n",
"6 Conclusion\n",
"LayoutParser provides a comprehensive toolkit for deep learning-based document\n",
"image analysis. The off-the-shelf library is easy to install, and can be used to\n",
"build flexible and accurate pipelines for processing documents with complicated\n",
"structures. It also supports hi\n",
"0: LayoutParser : A Unified Toolkit for Deep\n",
"Learning Based Document Image Analysis\n",
"Zejiang Shen1( \u0000), Ruochen Zhang2, Melissa Dell3, Benjamin Charles Germain\n",
"Lee4, Jacob Carlson3, and Weining Li5\n",
"1Allen Institute for AI\n",
"shannons@allenai.org\n",
"2Brown University\n",
"ruochen zhang@brown.edu\n",
"3Harvard University\n",
"\n"
]
}
],
"source": [
"from langchain_community.vectorstores import FAISS\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"faiss_index = FAISS.from_documents(pages, OpenAIEmbeddings())\n",
"docs = faiss_index.similarity_search(\"What is LayoutParser?\", k=2)\n",
"for doc in docs:\n",
" print(str(doc.metadata[\"page\"]) + \":\", doc.page_content[:300])"
]
},
{
"cell_type": "markdown",
"id": "9ac123ca-386f-4b06-b3a7-9205ea3d6da7",
"metadata": {},
"source": [
"### Extract text from images\n",
"\n",
"Some PDFs contain images of text-- e.g., within scanned documents, or figures. Using the `rapidocr-onnxruntime` package we can extract images as text as well:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "347f67fb-67f3-4be7-9af3-23a73cf00f71",
"metadata": {},
"outputs": [],
"source": [
"%pip install rapidocr-onnxruntime"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "babc138a-2188-49f7-a8d6-3570fa3ad802",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'LayoutParser : A Unified Toolkit for DL-Based DIA 5\\nTable 1: Current layout detection models in the LayoutParser model zoo\\nDataset Base Model1Large Model Notes\\nPubLayNet [38] F / M M Layouts of modern scientific documents\\nPRImA [3] M - Layouts of scanned modern magazines and scientific reports\\nNewspaper [17] F - Layouts of scanned US newspapers from the 20th century\\nTableBank [18] F F Table region on modern scientific and business document\\nHJDataset [31] F / M - Layouts of history Japanese documents\\n1For each dataset, we train several models of different sizes for different needs (the trade-off between accuracy\\nvs. computational cost). For “base model” and “large model”, we refer to using the ResNet 50 or ResNet 101\\nbackbones [ 13], respectively. One can train models of different architectures, like Faster R-CNN [ 28] (F) and Mask\\nR-CNN [ 12] (M). For example, an F in the Large Model column indicates it has a Faster R-CNN model trained\\nusing the ResNet 101 backbone. The platform is maintained and a number of additions will be made to the model\\nzoo in coming months.\\nlayout data structures , which are optimized for efficiency and versatility. 3) When\\nnecessary, users can employ existing or customized OCR models via the unified\\nAPI provided in the OCR module . 4)LayoutParser comes with a set of utility\\nfunctions for the visualization and storage of the layout data. 5) LayoutParser\\nis also highly customizable, via its integration with functions for layout data\\nannotation and model training . We now provide detailed descriptions for each\\ncomponent.\\n3.1 Layout Detection Models\\nInLayoutParser , a layout model takes a document image as an input and\\ngenerates a list of rectangular boxes for the target content regions. Different\\nfrom traditional methods, it relies on deep convolutional neural networks rather\\nthan manually curated rules to identify content regions. It is formulated as an\\nobject detection problem and state-of-the-art models like Faster R-CNN [ 28] and\\nMask R-CNN [ 12] are used. This yields prediction results of high accuracy and\\nmakes it possible to build a concise, generalized interface for layout detection.\\nLayoutParser , built upon Detectron2 [ 35], provides a minimal API that can\\nperform layout detection with only four lines of code in Python:\\n1import layoutparser as lp\\n2image = cv2. imread (\" image_file \") # load images\\n3model = lp. Detectron2LayoutModel (\\n4 \"lp :// PubLayNet / faster_rcnn_R_50_FPN_3x / config \")\\n5layout = model . detect ( image )\\nLayoutParser provides a wealth of pre-trained model weights using various\\ndatasets covering different languages, time periods, and document types. Due to\\ndomain shift [ 7], the prediction performance can notably drop when models are ap-\\nplied to target samples that are significantly different from the training dataset. As\\ndocument structures and layouts vary greatly in different domains, it is important\\nto select models trained on a dataset similar to the test samples. A semantic syntax\\nis used for initializing the model weights in LayoutParser , using both the dataset\\nname and model name lp://<dataset-name>/<model-architecture-name> .'"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"loader = PyPDFLoader(\"https://arxiv.org/pdf/2103.15348.pdf\", extract_images=True)\n",
"pages = loader.load()\n",
"pages[4].page_content"
]
},
{
"cell_type": "markdown",
"id": "eaf6c92e-ad2f-4157-ad35-9a2dc4dd1b66",
"metadata": {},
"source": [
"## Using PyMuPDF\n",
"\n",
"This is the fastest of the PDF parsing options, and contains detailed metadata about the PDF and its pages, as well as returns one document per page."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1be9463c-e08b-432e-be46-dc41f6d0ec28",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import PyMuPDFLoader\n",
"\n",
"loader = PyMuPDFLoader(\"example_data/layout-parser-paper.pdf\")\n",
"data = loader.load()\n",
"data[0]"
]
},
{
"cell_type": "markdown",
"id": "7839a181-f042-4b30-a31f-4ae8631fba42",
"metadata": {},
"source": [
"Additionally, you can pass along any of the options from the [PyMuPDF documentation](https://pymupdf.readthedocs.io/en/latest/app1.html#plain-text/) as keyword arguments in the `load` call, and it will be pass along to the `get_text()` call.\n",
"\n",
"## Using MathPix\n",
"\n",
"Inspired by Daniel Gross's [https://gist.github.com/danielgross/3ab4104e14faccc12b49200843adab21](https://gist.github.com/danielgross/3ab4104e14faccc12b49200843adab21)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b5f17610-2b24-43a0-908b-8144a5a79916",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import MathpixPDFLoader\n",
"\n",
"file_path = (\n",
" \"../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf\"\n",
")\n",
"loader = MathpixPDFLoader(file_path)\n",
"data = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "17c40629-09b8-42d0-a3de-3a43939c4cd8",
"metadata": {},
"source": [
"## Using Unstructured\n",
"\n",
"[Unstructured](https://unstructured-io.github.io/unstructured/) supports a common interface for working with unstructured or semi-structured file formats, such as Markdown or PDF. LangChain's [UnstructuredPDFLoader](https://api.python.langchain.com/en/latest/document_loaders/langchain_community.document_loaders.pdf.UnstructuredPDFLoader.html) integrates with Unstructured to parse PDF documents into LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html) objects."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "c6a15bd3-aaa4-49dc-935a-f18617a7dbdd",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import UnstructuredPDFLoader\n",
"\n",
"file_path = (\n",
" \"../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf\"\n",
")\n",
"loader = UnstructuredPDFLoader(file_path)\n",
"data = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "4263ba1f-4ccc-413c-9644-46a3ab3ae6fb",
"metadata": {},
"source": [
"### Retain Elements\n",
"\n",
"Under the hood, Unstructured creates different \"elements\" for different chunks of text. By default we combine those together, but you can easily keep that separation by specifying `mode=\"elements\"`."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "efd80620-0bb8-4298-ab3b-07d7ef9c0085",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Document(page_content='1 2 0 2', metadata={'source': '../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf', 'coordinates': {'points': ((16.34, 213.36), (16.34, 253.36), (36.34, 253.36), (36.34, 213.36)), 'system': 'PixelSpace', 'layout_width': 612, 'layout_height': 792}, 'file_directory': '../../../docs/integrations/document_loaders/example_data', 'filename': 'layout-parser-paper.pdf', 'languages': ['eng'], 'last_modified': '2024-03-18T13:22:22', 'page_number': 1, 'filetype': 'application/pdf', 'category': 'UncategorizedText'})"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"file_path = (\n",
" \"../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf\"\n",
")\n",
"loader = UnstructuredPDFLoader(file_path, mode=\"elements\")\n",
"\n",
"data = loader.load()\n",
"data[0]"
]
},
{
"cell_type": "markdown",
"id": "9b269d2a-2385-48a0-95c0-07202e1dff5f",
"metadata": {},
"source": [
"See the full set of element types for this particular document:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "3c40d9e8-5bf7-466d-b2bb-ce2ae08bea35",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'ListItem', 'NarrativeText', 'Title', 'UncategorizedText'}"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"set(doc.metadata[\"category\"] for doc in data)"
]
},
{
"cell_type": "markdown",
"id": "90fa9e65-6b00-456c-a0ee-23056f7dacdf",
"metadata": {},
"source": [
"### Fetching remote PDFs using Unstructured\n",
"\n",
"This covers how to load online PDFs into a document format that we can use downstream. This can be used for various online PDF sites such as https://open.umn.edu/opentextbooks/textbooks/ and https://arxiv.org/archive/\n",
"\n",
"Note: all other PDF loaders can also be used to fetch remote PDFs, but `OnlinePDFLoader` is a legacy function, and works specifically with `UnstructuredPDFLoader`."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "54737607-072e-4eb9-aac8-6615472fefc1",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import OnlinePDFLoader\n",
"\n",
"loader = OnlinePDFLoader(\"https://arxiv.org/pdf/2302.03803.pdf\")\n",
"data = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "2c7199f9-bbc5-4b03-873a-3d54c1bf4f68",
"metadata": {},
"source": [
"## Using PyPDFium2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f209821b-1fe9-402b-adf7-d472c8a24939",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import PyPDFium2Loader\n",
"\n",
"file_path = (\n",
" \"../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf\"\n",
")\n",
"loader = PyPDFium2Loader(file_path)\n",
"data = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "885a8c0e-25e4-4f3b-bb84-9db3f2c9367d",
"metadata": {},
"source": [
"## Using PDFMiner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4f465592-15be-4b8f-8f8c-0ffe207d0e4d",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import PDFMinerLoader\n",
"\n",
"file_path = (\n",
" \"../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf\"\n",
")\n",
"loader = PDFMinerLoader(file_path)\n",
"data = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "b9345c37-b0ba-4803-813c-f1c344a90a7c",
"metadata": {},
"source": [
"### Using PDFMiner to generate HTML text\n",
"\n",
"This can be helpful for chunking texts semantically into sections as the output html content can be parsed via `BeautifulSoup` to get more structured and rich information about font size, page numbers, PDF headers/footers, etc."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "2d39159e-61a5-4ac2-a6c2-3981c3aa6f4d",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import PDFMinerPDFasHTMLLoader\n",
"\n",
"file_path = (\n",
" \"../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf\"\n",
")\n",
"loader = PDFMinerPDFasHTMLLoader(file_path)\n",
"data = loader.load()[0]"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "2f18fc1e-988f-4778-ab79-4fac739bec8f",
"metadata": {},
"outputs": [],
"source": [
"from bs4 import BeautifulSoup\n",
"\n",
"soup = BeautifulSoup(data.page_content, \"html.parser\")\n",
"content = soup.find_all(\"div\")"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "0b40f5bd-631e-4444-b79e-ef55e088807e",
"metadata": {},
"outputs": [],
"source": [
"import re\n",
"\n",
"cur_fs = None\n",
"cur_text = \"\"\n",
"snippets = [] # first collect all snippets that have the same font size\n",
"for c in content:\n",
" sp = c.find(\"span\")\n",
" if not sp:\n",
" continue\n",
" st = sp.get(\"style\")\n",
" if not st:\n",
" continue\n",
" fs = re.findall(\"font-size:(\\d+)px\", st)\n",
" if not fs:\n",
" continue\n",
" fs = int(fs[0])\n",
" if not cur_fs:\n",
" cur_fs = fs\n",
" if fs == cur_fs:\n",
" cur_text += c.text\n",
" else:\n",
" snippets.append((cur_text, cur_fs))\n",
" cur_fs = fs\n",
" cur_text = c.text\n",
"snippets.append((cur_text, cur_fs))\n",
"# Note: The above logic is very straightforward. One can also add more strategies such as removing duplicate snippets (as\n",
"# headers/footers in a PDF appear on multiple pages so if we find duplicates it's safe to assume that it is redundant info)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "953b168f-4ae1-4279-b370-c21961206c0a",
"metadata": {},
"outputs": [],
"source": [
"from langchain.docstore.document import Document\n",
"\n",
"cur_idx = -1\n",
"semantic_snippets = []\n",
"# Assumption: headings have higher font size than their respective content\n",
"for s in snippets:\n",
" # if current snippet's font size > previous section's heading => it is a new heading\n",
" if (\n",
" not semantic_snippets\n",
" or s[1] > semantic_snippets[cur_idx].metadata[\"heading_font\"]\n",
" ):\n",
" metadata = {\"heading\": s[0], \"content_font\": 0, \"heading_font\": s[1]}\n",
" metadata.update(data.metadata)\n",
" semantic_snippets.append(Document(page_content=\"\", metadata=metadata))\n",
" cur_idx += 1\n",
" continue\n",
"\n",
" # if current snippet's font size <= previous section's content => content belongs to the same section (one can also create\n",
" # a tree like structure for sub sections if needed but that may require some more thinking and may be data specific)\n",
" if (\n",
" not semantic_snippets[cur_idx].metadata[\"content_font\"]\n",
" or s[1] <= semantic_snippets[cur_idx].metadata[\"content_font\"]\n",
" ):\n",
" semantic_snippets[cur_idx].page_content += s[0]\n",
" semantic_snippets[cur_idx].metadata[\"content_font\"] = max(\n",
" s[1], semantic_snippets[cur_idx].metadata[\"content_font\"]\n",
" )\n",
" continue\n",
"\n",
" # if current snippet's font size > previous section's content but less than previous section's heading than also make a new\n",
" # section (e.g. title of a PDF will have the highest font size but we don't want it to subsume all sections)\n",
" metadata = {\"heading\": s[0], \"content_font\": 0, \"heading_font\": s[1]}\n",
" metadata.update(data.metadata)\n",
" semantic_snippets.append(Document(page_content=\"\", metadata=metadata))\n",
" cur_idx += 1"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "9bf28b73-dad4-4f51-9238-4af523fa7225",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Document(page_content='Recently, various DL models and datasets have been developed for layout analysis\\ntasks. The dhSegment [22] utilizes fully convolutional networks [20] for segmen-\\ntation tasks on historical documents. Object detection-based methods like Faster\\nR-CNN [28] and Mask R-CNN [12] are used for identifying document elements [38]\\nand detecting tables [30, 26]. Most recently, Graph Neural Networks [29] have also\\nbeen used in table detection [27]. However, these models are usually implemented\\nindividually and there is no unified framework to load and use such models.\\nThere has been a surge of interest in creating open-source tools for document\\nimage processing: a search of document image analysis in Github leads to 5M\\nrelevant code pieces 6; yet most of them rely on traditional rule-based methods\\nor provide limited functionalities. The closest prior research to our work is the\\nOCR-D project7, which also tries to build a complete toolkit for DIA. However,\\nsimilar to the platform developed by Neudecker et al. [21], it is designed for\\nanalyzing historical documents, and provides no supports for recent DL models.\\nThe DocumentLayoutAnalysis project8 focuses on processing born-digital PDF\\ndocuments via analyzing the stored PDF data. Repositories like DeepLayout9\\nand Detectron2-PubLayNet10 are individual deep learning models trained on\\nlayout analysis datasets without support for the full DIA pipeline. The Document\\nAnalysis and Exploitation (DAE) platform [15] and the DeepDIVA project [2]\\naim to improve the reproducibility of DIA methods (or DL models), yet they\\nare not actively maintained. OCR engines like Tesseract [14], easyOCR11 and\\npaddleOCR12 usually do not come with comprehensive functionalities for other\\nDIA tasks like layout analysis.\\nRecent years have also seen numerous efforts to create libraries for promoting\\nreproducibility and reusability in the field of DL. Libraries like Dectectron2 [35],\\n6 The number shown is obtained by specifying the search type as code.\\n7 https://ocr-d.de/en/about\\n8 https://github.com/BobLd/DocumentLayoutAnalysis\\n9 https://github.com/leonlulu/DeepLayout\\n10 https://github.com/hpanwar08/detectron2\\n11 https://github.com/JaidedAI/EasyOCR\\n12 https://github.com/PaddlePaddle/PaddleOCR\\n4\\nZ. Shen et al.\\nFig. 1: The overall architecture of LayoutParser. For an input document image,\\nthe core LayoutParser library provides a set of off-the-shelf tools for layout\\ndetection, OCR, visualization, and storage, backed by a carefully designed layout\\ndata structure. LayoutParser also supports high level customization via efficient\\nlayout annotation and model training functions. These improve model accuracy\\non the target samples. The community platform enables the easy sharing of DIA\\nmodels and whole digitization pipelines to promote reusability and reproducibility.\\nA collection of detailed documentation, tutorials and exemplar projects make\\nLayoutParser easy to learn and use.\\nAllenNLP [8] and transformers [34] have provided the community with complete\\nDL-based support for developing and deploying models for general computer\\nvision and natural language processing problems. LayoutParser, on the other\\nhand, specializes specifically in DIA tasks. LayoutParser is also equipped with a\\ncommunity platform inspired by established model hubs such as Torch Hub [23]\\nand TensorFlow Hub [1]. It enables the sharing of pretrained models as well as\\nfull document processing pipelines that are unique to DIA tasks.\\nThere have been a variety of document data collections to facilitate the\\ndevelopment of DL models. Some examples include PRImA [3](magazine layouts),\\nPubLayNet [38](academic paper layouts), Table Bank [18](tables in academic\\npapers), Newspaper Navigator Dataset [16, 17](newspaper figure layouts) and\\nHJDataset [31](historical Japanese document layouts). A spectrum of models\\ntrained on these datasets are currently available in the LayoutParser model zoo\\nto support different use cases.\\n', metadata={'heading': '2 Related Work\\n', 'content_font': 9, 'heading_font': 11, 'source': '../../../docs/integrations/document_loaders/example_data/layout-parser-paper.pdf'})"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"semantic_snippets[4]"
]
},
{
"cell_type": "markdown",
"id": "e87d7447-c620-4f48-b4fd-8933a614e4e1",
"metadata": {},
"source": [
"## PyPDF Directory\n",
"\n",
"Load PDFs from directory"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "78e5a485-ff53-4b0c-ba5f-9f442079b529",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import PyPDFDirectoryLoader"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "51b2fe13-3755-4031-b7ce-84d9983db71c",
"metadata": {},
"outputs": [],
"source": [
"directory_path = \"../../../docs/integrations/document_loaders/example_data/\"\n",
"loader = PyPDFDirectoryLoader(\"example_data/\")\n",
"\n",
"\n",
"docs = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "78365a16-c011-4de1-8c32-873b88e7fead",
"metadata": {},
"source": [
"## Using PDFPlumber\n",
"\n",
"Like PyMuPDF, the output Documents contain detailed metadata about the PDF and its pages, and returns one document per page."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c8c1001b-48b1-4777-a34f-2fbdca5457df",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import PDFPlumberLoader\n",
"\n",
"data = loader.load()\n",
"data[0]"
]
},
{
"cell_type": "markdown",
"id": "94795ae5-161d-4d64-963c-dbcf1e60ca15",
"metadata": {},
"source": [
"## Using AmazonTextractPDFParser\n",
"\n",
"The AmazonTextractPDFLoader calls the [Amazon Textract Service](https://aws.amazon.com/textract/) to convert PDFs into a Document structure. The loader does pure OCR at the moment, with more features like layout support planned, depending on demand. Single and multi-page documents are supported with up to 3000 pages and 512 MB of size.\n",
"\n",
"For the call to be successful an AWS account is required, similar to the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) requirements.\n",
"\n",
"Besides the AWS configuration, it is very similar to the other PDF loaders, while also supporting JPEG, PNG and TIFF and non-native PDF formats."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5329e301-4bb6-4d51-aced-c9984ff6808a",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import AmazonTextractPDFLoader\n",
"\n",
"loader = AmazonTextractPDFLoader(\"example_data/alejandro_rosalez_sample-small.jpeg\")\n",
"documents = loader.load()"
]
},
{
"cell_type": "markdown",
"id": "e8291366-e2ec-4460-8e97-3fae3971986e",
"metadata": {},
"source": [
"## Using AzureAIDocumentIntelligenceLoader\n",
"\n",
"[Azure AI Document Intelligence](https://aka.ms/doc-intelligence) (formerly known as `Azure Form Recognizer`) is machine-learning \n",
"based service that extracts texts (including handwriting), tables, document structures (e.g., titles, section headings, etc.) and key-value-pairs from\n",
"digital or scanned PDFs, images, Office and HTML files. Document Intelligence supports `PDF`, `JPEG/JPG`, `PNG`, `BMP`, `TIFF`, `HEIF`, `DOCX`, `XLSX`, `PPTX` and `HTML`.\n",
"\n",
"This [current implementation](https://aka.ms/di-langchain) of a loader using `Document Intelligence` can incorporate content page-wise and turn it into LangChain documents. The default output format is markdown, which can be easily chained with `MarkdownHeaderTextSplitter` for semantic document chunking. You can also use `mode=\"single\"` or `mode=\"page\"` to return pure texts in a single page or document split by page.\n",
"\n",
"### Prerequisite\n",
"\n",
"An Azure AI Document Intelligence resource in one of the 3 preview regions: **East US**, **West US2**, **West Europe** - follow [this document](https://learn.microsoft.com/azure/ai-services/document-intelligence/create-document-intelligence-resource?view=doc-intel-4.0.0) to create one if you don't have. You will be passing `<endpoint>` and `<key>` as parameters to the loader."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12dfb5ff-ddd5-40a7-a5db-25d149d556ce",
"metadata": {},
"outputs": [],
"source": [
"%pip install --upgrade --quiet langchain langchain-community azure-ai-documentintelligence"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b06bd5d4-7093-4d12-8963-1eb41f82d21d",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders import AzureAIDocumentIntelligenceLoader\n",
"\n",
"file_path = \"<filepath>\"\n",
"endpoint = \"<endpoint>\"\n",
"key = \"<key>\"\n",
"loader = AzureAIDocumentIntelligenceLoader(\n",
" api_endpoint=endpoint, api_key=key, file_path=file_path, api_model=\"prebuilt-layout\"\n",
")\n",
"\n",
"documents = loader.load()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,128 @@
# Text embedding models
:::info
Head to [Integrations](/docs/integrations/text_embedding/) for documentation on built-in integrations with text embedding model providers.
:::
The Embeddings class is a class designed for interfacing with text embedding models. There are lots of embedding model providers (OpenAI, Cohere, Hugging Face, etc) - this class is designed to provide a standard interface for all of them.
Embeddings create a vector representation of a piece of text. This is useful because it means we can think about text in the vector space, and do things like semantic search where we look for pieces of text that are most similar in the vector space.
The base Embeddings class in LangChain provides two methods: one for embedding documents and one for embedding a query. The former, `.embed_documents`, takes as input multiple texts, while the latter, `.embed_query`, takes a single text. The reason for having these as two separate methods is that some embedding providers have different embedding methods for documents (to be searched over) vs queries (the search query itself).
`.embed_query` will return a list of floats, whereas `.embed_documents` returns a list of lists of floats.
## Get started
### Setup
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="openai" label="OpenAI" default>
To start we'll need to install the OpenAI partner package:
```bash
pip install langchain-openai
```
Accessing the API requires an API key, which you can get by creating an account and heading [here](https://platform.openai.com/account/api-keys). Once we have a key we'll want to set it as an environment variable by running:
```bash
export OPENAI_API_KEY="..."
```
If you'd prefer not to set an environment variable you can pass the key in directly via the `api_key` named parameter when initiating the OpenAI LLM class:
```python
from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings(api_key="...")
```
Otherwise you can initialize without any params:
```python
from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()
```
</TabItem>
<TabItem value="cohere" label="Cohere">
To start we'll need to install the Cohere SDK package:
```bash
pip install langchain-cohere
```
Accessing the API requires an API key, which you can get by creating an account and heading [here](https://dashboard.cohere.com/api-keys). Once we have a key we'll want to set it as an environment variable by running:
```shell
export COHERE_API_KEY="..."
```
If you'd prefer not to set an environment variable you can pass the key in directly via the `cohere_api_key` named parameter when initiating the Cohere LLM class:
```python
from langchain_cohere import CohereEmbeddings
embeddings_model = CohereEmbeddings(cohere_api_key="...")
```
Otherwise you can initialize without any params:
```python
from langchain_cohere import CohereEmbeddings
embeddings_model = CohereEmbeddings()
```
</TabItem>
</Tabs>
### `embed_documents`
#### Embed list of texts
Use `.embed_documents` to embed a list of strings, recovering a list of embeddings:
```python
embeddings = embeddings_model.embed_documents(
[
"Hi there!",
"Oh, hello!",
"What's your name?",
"My friends call me World",
"Hello World!"
]
)
len(embeddings), len(embeddings[0])
```
<CodeOutputBlock language="python">
```
(5, 1536)
```
</CodeOutputBlock>
### `embed_query`
#### Embed single query
Use `.embed_query` to embed a single piece of text (e.g., for the purpose of comparing to other embedded pieces of texts).
```python
embedded_query = embeddings_model.embed_query("What was the name mentioned in the conversation?")
embedded_query[:5]
```
<CodeOutputBlock language="python">
```
[0.0053587136790156364,
-0.0004999046213924885,
0.038883671164512634,
-0.003001077566295862,
-0.00900818221271038]
```
</CodeOutputBlock>

View File

@@ -0,0 +1,189 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to create an Ensemble Retriever\n",
"\n",
"The `EnsembleRetriever` takes a list of retrievers as input and ensemble the results of their `get_relevant_documents()` methods and rerank the results based on the [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) algorithm.\n",
"\n",
"By leveraging the strengths of different algorithms, the `EnsembleRetriever` can achieve better performance than any single algorithm. \n",
"\n",
"The most common pattern is to combine a sparse retriever (like BM25) with a dense retriever (like embedding similarity), because their strengths are complementary. It is also known as \"hybrid search\". The sparse retriever is good at finding relevant documents based on keywords, while the dense retriever is good at finding relevant documents based on semantic similarity."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"%pip install --upgrade --quiet rank_bm25 > /dev/null"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from langchain.retrievers import EnsembleRetriever\n",
"from langchain_community.retrievers import BM25Retriever\n",
"from langchain_community.vectorstores import FAISS\n",
"from langchain_openai import OpenAIEmbeddings"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"doc_list_1 = [\n",
" \"I like apples\",\n",
" \"I like oranges\",\n",
" \"Apples and oranges are fruits\",\n",
"]\n",
"\n",
"# initialize the bm25 retriever and faiss retriever\n",
"bm25_retriever = BM25Retriever.from_texts(\n",
" doc_list_1, metadatas=[{\"source\": 1}] * len(doc_list_1)\n",
")\n",
"bm25_retriever.k = 2\n",
"\n",
"doc_list_2 = [\n",
" \"You like apples\",\n",
" \"You like oranges\",\n",
"]\n",
"\n",
"embedding = OpenAIEmbeddings()\n",
"faiss_vectorstore = FAISS.from_texts(\n",
" doc_list_2, embedding, metadatas=[{\"source\": 2}] * len(doc_list_2)\n",
")\n",
"faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={\"k\": 2})\n",
"\n",
"# initialize the ensemble retriever\n",
"ensemble_retriever = EnsembleRetriever(\n",
" retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='You like apples', metadata={'source': 2}),\n",
" Document(page_content='I like apples', metadata={'source': 1}),\n",
" Document(page_content='You like oranges', metadata={'source': 2}),\n",
" Document(page_content='Apples and oranges are fruits', metadata={'source': 1})]"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"docs = ensemble_retriever.invoke(\"apples\")\n",
"docs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Runtime Configuration\n",
"\n",
"We can also configure the retrievers at runtime. In order to do this, we need to mark the fields as configurable"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.runnables import ConfigurableField"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"faiss_retriever = faiss_vectorstore.as_retriever(\n",
" search_kwargs={\"k\": 2}\n",
").configurable_fields(\n",
" search_kwargs=ConfigurableField(\n",
" id=\"search_kwargs_faiss\",\n",
" name=\"Search Kwargs\",\n",
" description=\"The search kwargs to use\",\n",
" )\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"ensemble_retriever = EnsembleRetriever(\n",
" retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"config = {\"configurable\": {\"search_kwargs_faiss\": {\"k\": 1}}}\n",
"docs = ensemble_retriever.invoke(\"apples\", config=config)\n",
"docs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice that this only returns one source from the FAISS retriever, because we pass in the relevant configuration at run time"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,277 @@
{
"cells": [
{
"cell_type": "raw",
"id": "af408f61",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 1\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "1a65e4c9",
"metadata": {},
"source": [
"# How to use example selectors\n",
"\n",
"If you have a large number of examples, you may need to select which ones to include in the prompt. The Example Selector is the class responsible for doing so.\n",
"\n",
"The base interface is defined as below:\n",
"\n",
"```python\n",
"class BaseExampleSelector(ABC):\n",
" \"\"\"Interface for selecting examples to include in prompts.\"\"\"\n",
"\n",
" @abstractmethod\n",
" def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:\n",
" \"\"\"Select which examples to use based on the inputs.\"\"\"\n",
" \n",
" @abstractmethod\n",
" def add_example(self, example: Dict[str, str]) -> Any:\n",
" \"\"\"Add new example to store.\"\"\"\n",
"```\n",
"\n",
"The only method it needs to define is a ``select_examples`` method. This takes in the input variables and then returns a list of examples. It is up to each specific implementation as to how those examples are selected.\n",
"\n",
"LangChain has a few different types of example selectors. For an overview of all these types, see the below table.\n",
"\n",
"In this guide, we will walk through creating a custom example selector."
]
},
{
"cell_type": "markdown",
"id": "638e9039",
"metadata": {},
"source": [
"## Examples\n",
"\n",
"In order to use an example selector, we need to create a list of examples. These should generally be example inputs and outputs. For this demo purpose, let's imagine we are selecting examples of how to translate English to Italian."
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "48658d53",
"metadata": {},
"outputs": [],
"source": [
"examples = [\n",
" {\"input\": \"hi\", \"output\": \"ciao\"},\n",
" {\"input\": \"bye\", \"output\": \"arrivaderci\"},\n",
" {\"input\": \"soccer\", \"output\": \"calcio\"},\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "c2830b49",
"metadata": {},
"source": [
"## Custom Example Selector\n",
"\n",
"Let's write an example selector that chooses what example to pick based on the length of the word."
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "56b740a1",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.example_selectors.base import BaseExampleSelector\n",
"\n",
"\n",
"class CustomExampleSelector(BaseExampleSelector):\n",
" def __init__(self, examples):\n",
" self.examples = examples\n",
"\n",
" def add_example(self, example):\n",
" self.examples.append(example)\n",
"\n",
" def select_examples(self, input_variables):\n",
" # This assumes knowledge that part of the input will be a 'text' key\n",
" new_word = input_variables[\"input\"]\n",
" new_word_length = len(new_word)\n",
"\n",
" # Initialize variables to store the best match and its length difference\n",
" best_match = None\n",
" smallest_diff = float(\"inf\")\n",
"\n",
" # Iterate through each example\n",
" for example in self.examples:\n",
" # Calculate the length difference with the first word of the example\n",
" current_diff = abs(len(example[\"input\"]) - new_word_length)\n",
"\n",
" # Update the best match if the current one is closer in length\n",
" if current_diff < smallest_diff:\n",
" smallest_diff = current_diff\n",
" best_match = example\n",
"\n",
" return [best_match]"
]
},
{
"cell_type": "code",
"execution_count": 38,
"id": "ce928187",
"metadata": {},
"outputs": [],
"source": [
"example_selector = CustomExampleSelector(examples)"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "37ef3149",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'input': 'bye', 'output': 'arrivaderci'}]"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"example_selector.select_examples({\"input\": \"okay\"})"
]
},
{
"cell_type": "code",
"execution_count": 40,
"id": "c5ad9f35",
"metadata": {},
"outputs": [],
"source": [
"example_selector.add_example({\"input\": \"hand\", \"output\": \"mano\"})"
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "e4127fe0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'input': 'hand', 'output': 'mano'}]"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"example_selector.select_examples({\"input\": \"okay\"})"
]
},
{
"cell_type": "markdown",
"id": "786c920c",
"metadata": {},
"source": [
"## Use in a Prompt\n",
"\n",
"We can now use this example selector in a prompt"
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "619090e2",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts.few_shot import FewShotPromptTemplate\n",
"from langchain_core.prompts.prompt import PromptTemplate\n",
"\n",
"example_prompt = PromptTemplate.from_template(\"Input: {input} -> Output: {output}\")"
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "5934c415",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Translate the following words from English to Italain:\n",
"\n",
"Input: hand -> Output: mano\n",
"\n",
"Input: word -> Output:\n"
]
}
],
"source": [
"prompt = FewShotPromptTemplate(\n",
" example_selector=example_selector,\n",
" example_prompt=example_prompt,\n",
" suffix=\"Input: {input} -> Output:\",\n",
" prefix=\"Translate the following words from English to Italain:\",\n",
" input_variables=[\"input\"],\n",
")\n",
"\n",
"print(prompt.format(input=\"word\"))"
]
},
{
"cell_type": "markdown",
"id": "e767f69d",
"metadata": {},
"source": [
"## Example Selector Types\n",
"\n",
"| Name | Description |\n",
"|------------|---------------------------------------------------------------------------------------------|\n",
"| Similarity | Uses semantic similarity between inputs and examples to decide which examples to choose. |\n",
"| MMR | Uses Max Marginal Relevance between inputs and examples to decide which examples to choose. |\n",
"| Length | Selects examples based on how many can fit within a certain length |\n",
"| Ngram | Uses ngram overlap between inputs and examples to decide which examples to choose. |"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a6e0abe",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,194 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "1036fdb2",
"metadata": {},
"source": [
"# How to select examples by length\n",
"\n",
"This example selector selects which examples to use based on length. This is useful when you are worried about constructing a prompt that will go over the length of the context window. For longer inputs, it will select fewer examples to include, while for shorter inputs it will select more."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1bd45644",
"metadata": {},
"outputs": [],
"source": [
"from langchain.prompts import FewShotPromptTemplate, PromptTemplate\n",
"from langchain.prompts.example_selector import LengthBasedExampleSelector\n",
"\n",
"# Examples of a pretend task of creating antonyms.\n",
"examples = [\n",
" {\"input\": \"happy\", \"output\": \"sad\"},\n",
" {\"input\": \"tall\", \"output\": \"short\"},\n",
" {\"input\": \"energetic\", \"output\": \"lethargic\"},\n",
" {\"input\": \"sunny\", \"output\": \"gloomy\"},\n",
" {\"input\": \"windy\", \"output\": \"calm\"},\n",
"]\n",
"\n",
"example_prompt = PromptTemplate(\n",
" input_variables=[\"input\", \"output\"],\n",
" template=\"Input: {input}\\nOutput: {output}\",\n",
")\n",
"example_selector = LengthBasedExampleSelector(\n",
" # The examples it has available to choose from.\n",
" examples=examples,\n",
" # The PromptTemplate being used to format the examples.\n",
" example_prompt=example_prompt,\n",
" # The maximum length that the formatted examples should be.\n",
" # Length is measured by the get_text_length function below.\n",
" max_length=25,\n",
" # The function used to get the length of a string, which is used\n",
" # to determine which examples to include. It is commented out because\n",
" # it is provided as a default value if none is specified.\n",
" # get_text_length: Callable[[str], int] = lambda x: len(re.split(\"\\n| \", x))\n",
")\n",
"dynamic_prompt = FewShotPromptTemplate(\n",
" # We provide an ExampleSelector instead of examples.\n",
" example_selector=example_selector,\n",
" example_prompt=example_prompt,\n",
" prefix=\"Give the antonym of every input\",\n",
" suffix=\"Input: {adjective}\\nOutput:\",\n",
" input_variables=[\"adjective\"],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f62c140b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the antonym of every input\n",
"\n",
"Input: happy\n",
"Output: sad\n",
"\n",
"Input: tall\n",
"Output: short\n",
"\n",
"Input: energetic\n",
"Output: lethargic\n",
"\n",
"Input: sunny\n",
"Output: gloomy\n",
"\n",
"Input: windy\n",
"Output: calm\n",
"\n",
"Input: big\n",
"Output:\n"
]
}
],
"source": [
"# An example with small input, so it selects all examples.\n",
"print(dynamic_prompt.format(adjective=\"big\"))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "3ca959eb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the antonym of every input\n",
"\n",
"Input: happy\n",
"Output: sad\n",
"\n",
"Input: big and huge and massive and large and gigantic and tall and much much much much much bigger than everything else\n",
"Output:\n"
]
}
],
"source": [
"# An example with long input, so it selects only one example.\n",
"long_string = \"big and huge and massive and large and gigantic and tall and much much much much much bigger than everything else\"\n",
"print(dynamic_prompt.format(adjective=long_string))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "da43f9a7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the antonym of every input\n",
"\n",
"Input: happy\n",
"Output: sad\n",
"\n",
"Input: tall\n",
"Output: short\n",
"\n",
"Input: energetic\n",
"Output: lethargic\n",
"\n",
"Input: sunny\n",
"Output: gloomy\n",
"\n",
"Input: windy\n",
"Output: calm\n",
"\n",
"Input: big\n",
"Output: small\n",
"\n",
"Input: enthusiastic\n",
"Output:\n"
]
}
],
"source": [
"# You can add an example to an example selector as well.\n",
"new_example = {\"input\": \"big\", \"output\": \"small\"}\n",
"dynamic_prompt.example_selector.add_example(new_example)\n",
"print(dynamic_prompt.format(adjective=\"enthusiastic\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be3cf8aa",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,175 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "bc35afd0",
"metadata": {},
"source": [
"# How to select examples by maximal marginal relevance (MMR)\n",
"\n",
"The `MaxMarginalRelevanceExampleSelector` selects examples based on a combination of which examples are most similar to the inputs, while also optimizing for diversity. It does this by finding the examples with the embeddings that have the greatest cosine similarity with the inputs, and then iteratively adding them while penalizing them for closeness to already selected examples.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "ac95c968",
"metadata": {},
"outputs": [],
"source": [
"from langchain.prompts import FewShotPromptTemplate, PromptTemplate\n",
"from langchain.prompts.example_selector import (\n",
" MaxMarginalRelevanceExampleSelector,\n",
" SemanticSimilarityExampleSelector,\n",
")\n",
"from langchain_community.vectorstores import FAISS\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"example_prompt = PromptTemplate(\n",
" input_variables=[\"input\", \"output\"],\n",
" template=\"Input: {input}\\nOutput: {output}\",\n",
")\n",
"\n",
"# Examples of a pretend task of creating antonyms.\n",
"examples = [\n",
" {\"input\": \"happy\", \"output\": \"sad\"},\n",
" {\"input\": \"tall\", \"output\": \"short\"},\n",
" {\"input\": \"energetic\", \"output\": \"lethargic\"},\n",
" {\"input\": \"sunny\", \"output\": \"gloomy\"},\n",
" {\"input\": \"windy\", \"output\": \"calm\"},\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "db579bea",
"metadata": {},
"outputs": [],
"source": [
"example_selector = MaxMarginalRelevanceExampleSelector.from_examples(\n",
" # The list of examples available to select from.\n",
" examples,\n",
" # The embedding class used to produce embeddings which are used to measure semantic similarity.\n",
" OpenAIEmbeddings(),\n",
" # The VectorStore class that is used to store the embeddings and do a similarity search over.\n",
" FAISS,\n",
" # The number of examples to produce.\n",
" k=2,\n",
")\n",
"mmr_prompt = FewShotPromptTemplate(\n",
" # We provide an ExampleSelector instead of examples.\n",
" example_selector=example_selector,\n",
" example_prompt=example_prompt,\n",
" prefix=\"Give the antonym of every input\",\n",
" suffix=\"Input: {adjective}\\nOutput:\",\n",
" input_variables=[\"adjective\"],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "cd76e344",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the antonym of every input\n",
"\n",
"Input: happy\n",
"Output: sad\n",
"\n",
"Input: windy\n",
"Output: calm\n",
"\n",
"Input: worried\n",
"Output:\n"
]
}
],
"source": [
"# Input is a feeling, so should select the happy/sad example as the first one\n",
"print(mmr_prompt.format(adjective=\"worried\"))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "cf82956b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the antonym of every input\n",
"\n",
"Input: happy\n",
"Output: sad\n",
"\n",
"Input: sunny\n",
"Output: gloomy\n",
"\n",
"Input: worried\n",
"Output:\n"
]
}
],
"source": [
"# Let's compare this to what we would just get if we went solely off of similarity,\n",
"# by using SemanticSimilarityExampleSelector instead of MaxMarginalRelevanceExampleSelector.\n",
"example_selector = SemanticSimilarityExampleSelector.from_examples(\n",
" # The list of examples available to select from.\n",
" examples,\n",
" # The embedding class used to produce embeddings which are used to measure semantic similarity.\n",
" OpenAIEmbeddings(),\n",
" # The VectorStore class that is used to store the embeddings and do a similarity search over.\n",
" FAISS,\n",
" # The number of examples to produce.\n",
" k=2,\n",
")\n",
"similar_prompt = FewShotPromptTemplate(\n",
" # We provide an ExampleSelector instead of examples.\n",
" example_selector=example_selector,\n",
" example_prompt=example_prompt,\n",
" prefix=\"Give the antonym of every input\",\n",
" suffix=\"Input: {adjective}\\nOutput:\",\n",
" input_variables=[\"adjective\"],\n",
")\n",
"print(similar_prompt.format(adjective=\"worried\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39f30097",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,258 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4aaeed2f",
"metadata": {},
"source": [
"# How to select examples by n-gram overlap\n",
"\n",
"The `NGramOverlapExampleSelector` selects and orders examples based on which examples are most similar to the input, according to an ngram overlap score. The ngram overlap score is a float between 0.0 and 1.0, inclusive. \n",
"\n",
"The selector allows for a threshold score to be set. Examples with an ngram overlap score less than or equal to the threshold are excluded. The threshold is set to -1.0, by default, so will not exclude any examples, only reorder them. Setting the threshold to 0.0 will exclude examples that have no ngram overlaps with the input.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "9cbc0acc",
"metadata": {},
"outputs": [],
"source": [
"from langchain.prompts import FewShotPromptTemplate, PromptTemplate\n",
"from langchain.prompts.example_selector.ngram_overlap import NGramOverlapExampleSelector\n",
"\n",
"example_prompt = PromptTemplate(\n",
" input_variables=[\"input\", \"output\"],\n",
" template=\"Input: {input}\\nOutput: {output}\",\n",
")\n",
"\n",
"# Examples of a fictional translation task.\n",
"examples = [\n",
" {\"input\": \"See Spot run.\", \"output\": \"Ver correr a Spot.\"},\n",
" {\"input\": \"My dog barks.\", \"output\": \"Mi perro ladra.\"},\n",
" {\"input\": \"Spot can run.\", \"output\": \"Spot puede correr.\"},\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "bf75e0fe",
"metadata": {},
"outputs": [],
"source": [
"example_selector = NGramOverlapExampleSelector(\n",
" # The examples it has available to choose from.\n",
" examples=examples,\n",
" # The PromptTemplate being used to format the examples.\n",
" example_prompt=example_prompt,\n",
" # The threshold, at which selector stops.\n",
" # It is set to -1.0 by default.\n",
" threshold=-1.0,\n",
" # For negative threshold:\n",
" # Selector sorts examples by ngram overlap score, and excludes none.\n",
" # For threshold greater than 1.0:\n",
" # Selector excludes all examples, and returns an empty list.\n",
" # For threshold equal to 0.0:\n",
" # Selector sorts examples by ngram overlap score,\n",
" # and excludes those with no ngram overlap with input.\n",
")\n",
"dynamic_prompt = FewShotPromptTemplate(\n",
" # We provide an ExampleSelector instead of examples.\n",
" example_selector=example_selector,\n",
" example_prompt=example_prompt,\n",
" prefix=\"Give the Spanish translation of every input\",\n",
" suffix=\"Input: {sentence}\\nOutput:\",\n",
" input_variables=[\"sentence\"],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "83fb218a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the Spanish translation of every input\n",
"\n",
"Input: Spot can run.\n",
"Output: Spot puede correr.\n",
"\n",
"Input: See Spot run.\n",
"Output: Ver correr a Spot.\n",
"\n",
"Input: My dog barks.\n",
"Output: Mi perro ladra.\n",
"\n",
"Input: Spot can run fast.\n",
"Output:\n"
]
}
],
"source": [
"# An example input with large ngram overlap with \"Spot can run.\"\n",
"# and no overlap with \"My dog barks.\"\n",
"print(dynamic_prompt.format(sentence=\"Spot can run fast.\"))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "485f5307",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the Spanish translation of every input\n",
"\n",
"Input: Spot can run.\n",
"Output: Spot puede correr.\n",
"\n",
"Input: See Spot run.\n",
"Output: Ver correr a Spot.\n",
"\n",
"Input: Spot plays fetch.\n",
"Output: Spot juega a buscar.\n",
"\n",
"Input: My dog barks.\n",
"Output: Mi perro ladra.\n",
"\n",
"Input: Spot can run fast.\n",
"Output:\n"
]
}
],
"source": [
"# You can add examples to NGramOverlapExampleSelector as well.\n",
"new_example = {\"input\": \"Spot plays fetch.\", \"output\": \"Spot juega a buscar.\"}\n",
"\n",
"example_selector.add_example(new_example)\n",
"print(dynamic_prompt.format(sentence=\"Spot can run fast.\"))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "606ce697",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the Spanish translation of every input\n",
"\n",
"Input: Spot can run.\n",
"Output: Spot puede correr.\n",
"\n",
"Input: See Spot run.\n",
"Output: Ver correr a Spot.\n",
"\n",
"Input: Spot plays fetch.\n",
"Output: Spot juega a buscar.\n",
"\n",
"Input: Spot can run fast.\n",
"Output:\n"
]
}
],
"source": [
"# You can set a threshold at which examples are excluded.\n",
"# For example, setting threshold equal to 0.0\n",
"# excludes examples with no ngram overlaps with input.\n",
"# Since \"My dog barks.\" has no ngram overlaps with \"Spot can run fast.\"\n",
"# it is excluded.\n",
"example_selector.threshold = 0.0\n",
"print(dynamic_prompt.format(sentence=\"Spot can run fast.\"))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "7f8d72f7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the Spanish translation of every input\n",
"\n",
"Input: Spot can run.\n",
"Output: Spot puede correr.\n",
"\n",
"Input: Spot plays fetch.\n",
"Output: Spot juega a buscar.\n",
"\n",
"Input: Spot can play fetch.\n",
"Output:\n"
]
}
],
"source": [
"# Setting small nonzero threshold\n",
"example_selector.threshold = 0.09\n",
"print(dynamic_prompt.format(sentence=\"Spot can play fetch.\"))"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "09633aa8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the Spanish translation of every input\n",
"\n",
"Input: Spot can play fetch.\n",
"Output:\n"
]
}
],
"source": [
"# Setting threshold greater than 1.0\n",
"example_selector.threshold = 1.0 + 1e-9\n",
"print(dynamic_prompt.format(sentence=\"Spot can play fetch.\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39f30097",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,175 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8c1e7149",
"metadata": {},
"source": [
"# How to select examples by similarity\n",
"\n",
"This object selects examples based on similarity to the inputs. It does this by finding the examples with the embeddings that have the greatest cosine similarity with the inputs.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "abc30764",
"metadata": {},
"outputs": [],
"source": [
"from langchain.prompts import FewShotPromptTemplate, PromptTemplate\n",
"from langchain.prompts.example_selector import SemanticSimilarityExampleSelector\n",
"from langchain_chroma import Chroma\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"example_prompt = PromptTemplate(\n",
" input_variables=[\"input\", \"output\"],\n",
" template=\"Input: {input}\\nOutput: {output}\",\n",
")\n",
"\n",
"# Examples of a pretend task of creating antonyms.\n",
"examples = [\n",
" {\"input\": \"happy\", \"output\": \"sad\"},\n",
" {\"input\": \"tall\", \"output\": \"short\"},\n",
" {\"input\": \"energetic\", \"output\": \"lethargic\"},\n",
" {\"input\": \"sunny\", \"output\": \"gloomy\"},\n",
" {\"input\": \"windy\", \"output\": \"calm\"},\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "8a37fc84",
"metadata": {},
"outputs": [],
"source": [
"example_selector = SemanticSimilarityExampleSelector.from_examples(\n",
" # The list of examples available to select from.\n",
" examples,\n",
" # The embedding class used to produce embeddings which are used to measure semantic similarity.\n",
" OpenAIEmbeddings(),\n",
" # The VectorStore class that is used to store the embeddings and do a similarity search over.\n",
" Chroma,\n",
" # The number of examples to produce.\n",
" k=1,\n",
")\n",
"similar_prompt = FewShotPromptTemplate(\n",
" # We provide an ExampleSelector instead of examples.\n",
" example_selector=example_selector,\n",
" example_prompt=example_prompt,\n",
" prefix=\"Give the antonym of every input\",\n",
" suffix=\"Input: {adjective}\\nOutput:\",\n",
" input_variables=[\"adjective\"],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "eabd2020",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the antonym of every input\n",
"\n",
"Input: happy\n",
"Output: sad\n",
"\n",
"Input: worried\n",
"Output:\n"
]
}
],
"source": [
"# Input is a feeling, so should select the happy/sad example\n",
"print(similar_prompt.format(adjective=\"worried\"))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "c02225a8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the antonym of every input\n",
"\n",
"Input: tall\n",
"Output: short\n",
"\n",
"Input: large\n",
"Output:\n"
]
}
],
"source": [
"# Input is a measurement, so should select the tall/short example\n",
"print(similar_prompt.format(adjective=\"large\"))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "09836c64",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Give the antonym of every input\n",
"\n",
"Input: enthusiastic\n",
"Output: apathetic\n",
"\n",
"Input: passionate\n",
"Output:\n"
]
}
],
"source": [
"# You can add new examples to the SemanticSimilarityExampleSelector as well\n",
"similar_prompt.example_selector.add_example(\n",
" {\"input\": \"enthusiastic\", \"output\": \"apathetic\"}\n",
")\n",
"print(similar_prompt.format(adjective=\"passionate\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "92e2c85f",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,484 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "70403d4f-50c1-43f8-a7ea-a211167649a5",
"metadata": {},
"source": [
"# How to use reference examples when doing extraction\n",
"\n",
"The quality of extractions can often be improved by providing reference examples to the LLM.\n",
"\n",
"Data extraction attempts to generate structured representations of information found in text and other unstructured or semi-structured formats. [Tool-calling](/docs/concepts#functiontool-calling) LLM features are often used in this context. This guide demonstrates how to build few-shot examples of tool calls to help steer the behavior of extraction and similar applications.\n",
"\n",
":::{.callout-tip}\n",
"While this guide focuses how to use examples with a tool calling model, this technique is generally applicable, and will work\n",
"also with JSON more or prompt based techniques.\n",
":::\n",
"\n",
"LangChain implements a [tool-call attribute](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessage.html#langchain_core.messages.ai.AIMessage.tool_calls) on messages from LLMs that include tool calls. See our [how-to guide on tool calling](/docs/how_to/tool_calling) for more detail. To build reference examples for data extraction, we build a chat history containing a sequence of: \n",
"\n",
"- [HumanMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.human.HumanMessage.html) containing example inputs;\n",
"- [AIMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessage.html) containing example tool calls;\n",
"- [ToolMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.tool.ToolMessage.html) containing example tool outputs.\n",
"\n",
"LangChain adopts this convention for structuring tool calls into conversation across LLM model providers.\n",
"\n",
"First we build a prompt template that includes a placeholder for these messages:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "89579144-bcb3-490a-8036-86a0a6bcd56b",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"\n",
"# Define a custom prompt to provide instructions and any additional context.\n",
"# 1) You can add examples into the prompt template to improve extraction quality\n",
"# 2) Introduce additional parameters to take context into account (e.g., include metadata\n",
"# about the document from which the text was extracted.)\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are an expert extraction algorithm. \"\n",
" \"Only extract relevant information from the text. \"\n",
" \"If you do not know the value of an attribute asked \"\n",
" \"to extract, return null for the attribute's value.\",\n",
" ),\n",
" # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓\n",
" MessagesPlaceholder(\"examples\"), # <-- EXAMPLES!\n",
" # ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑\n",
" (\"human\", \"{text}\"),\n",
" ]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2484008c-ba1a-42a5-87a1-628a900de7fd",
"metadata": {},
"source": [
"Test out the template:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "610c3025-ea63-4cd7-88bd-c8cbcb4d8a3f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"ChatPromptValue(messages=[SystemMessage(content=\"You are an expert extraction algorithm. Only extract relevant information from the text. If you do not know the value of an attribute asked to extract, return null for the attribute's value.\"), HumanMessage(content='testing 1 2 3'), HumanMessage(content='this is some text')])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import (\n",
" HumanMessage,\n",
")\n",
"\n",
"prompt.invoke(\n",
" {\"text\": \"this is some text\", \"examples\": [HumanMessage(content=\"testing 1 2 3\")]}\n",
")"
]
},
{
"cell_type": "markdown",
"id": "368abd80-0cf0-41a7-8224-acf90dd6830d",
"metadata": {},
"source": [
"## Define the schema\n",
"\n",
"Let's re-use the person schema from the [extraction tutorial](/docs/tutorials/extraction)."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "d875a49a-d2cb-4b9e-b5bf-41073bc3905c",
"metadata": {},
"outputs": [],
"source": [
"from typing import List, Optional\n",
"\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"\n",
"class Person(BaseModel):\n",
" \"\"\"Information about a person.\"\"\"\n",
"\n",
" # ^ Doc-string for the entity Person.\n",
" # This doc-string is sent to the LLM as the description of the schema Person,\n",
" # and it can help to improve extraction results.\n",
"\n",
" # Note that:\n",
" # 1. Each field is an `optional` -- this allows the model to decline to extract it!\n",
" # 2. Each field has a `description` -- this description is used by the LLM.\n",
" # Having a good description can help improve extraction results.\n",
" name: Optional[str] = Field(..., description=\"The name of the person\")\n",
" hair_color: Optional[str] = Field(\n",
" ..., description=\"The color of the peron's eyes if known\"\n",
" )\n",
" height_in_meters: Optional[str] = Field(..., description=\"Height in METERs\")\n",
"\n",
"\n",
"class Data(BaseModel):\n",
" \"\"\"Extracted data about people.\"\"\"\n",
"\n",
" # Creates a model so that we can extract multiple entities.\n",
" people: List[Person]"
]
},
{
"cell_type": "markdown",
"id": "96c42162-e4f6-4461-88fd-c76f5aab7e32",
"metadata": {},
"source": [
"## Define reference examples\n",
"\n",
"Examples can be defined as a list of input-output pairs. \n",
"\n",
"Each example contains an example `input` text and an example `output` showing what should be extracted from the text.\n",
"\n",
":::{.callout-important}\n",
"This is a bit in the weeds, so feel free to skip.\n",
"\n",
"The format of the example needs to match the API used (e.g., tool calling or JSON mode etc.).\n",
"\n",
"Here, the formatted examples will match the format expected for the tool calling API since that's what we're using.\n",
":::"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "08356810-77ce-4e68-99d9-faa0326f2cee",
"metadata": {},
"outputs": [],
"source": [
"import uuid\n",
"from typing import Dict, List, TypedDict\n",
"\n",
"from langchain_core.messages import (\n",
" AIMessage,\n",
" BaseMessage,\n",
" HumanMessage,\n",
" SystemMessage,\n",
" ToolMessage,\n",
")\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"\n",
"\n",
"class Example(TypedDict):\n",
" \"\"\"A representation of an example consisting of text input and expected tool calls.\n",
"\n",
" For extraction, the tool calls are represented as instances of pydantic model.\n",
" \"\"\"\n",
"\n",
" input: str # This is the example text\n",
" tool_calls: List[BaseModel] # Instances of pydantic model that should be extracted\n",
"\n",
"\n",
"def tool_example_to_messages(example: Example) -> List[BaseMessage]:\n",
" \"\"\"Convert an example into a list of messages that can be fed into an LLM.\n",
"\n",
" This code is an adapter that converts our example to a list of messages\n",
" that can be fed into a chat model.\n",
"\n",
" The list of messages per example corresponds to:\n",
"\n",
" 1) HumanMessage: contains the content from which content should be extracted.\n",
" 2) AIMessage: contains the extracted information from the model\n",
" 3) ToolMessage: contains confirmation to the model that the model requested a tool correctly.\n",
"\n",
" The ToolMessage is required because some of the chat models are hyper-optimized for agents\n",
" rather than for an extraction use case.\n",
" \"\"\"\n",
" messages: List[BaseMessage] = [HumanMessage(content=example[\"input\"])]\n",
" tool_calls = []\n",
" for tool_call in example[\"tool_calls\"]:\n",
" tool_calls.append(\n",
" {\n",
" \"id\": str(uuid.uuid4()),\n",
" \"args\": tool_call.dict(),\n",
" # The name of the function right now corresponds\n",
" # to the name of the pydantic model\n",
" # This is implicit in the API right now,\n",
" # and will be improved over time.\n",
" \"name\": tool_call.__class__.__name__,\n",
" },\n",
" )\n",
" messages.append(AIMessage(content=\"\", tool_calls=tool_calls))\n",
" tool_outputs = example.get(\"tool_outputs\") or [\n",
" \"You have correctly called this tool.\"\n",
" ] * len(tool_calls)\n",
" for output, tool_call in zip(tool_outputs, tool_calls):\n",
" messages.append(ToolMessage(content=output, tool_call_id=tool_call[\"id\"]))\n",
" return messages"
]
},
{
"cell_type": "markdown",
"id": "463aa282-51c4-42bf-9463-6ca3b2c08de6",
"metadata": {},
"source": [
"Next let's define our examples and then convert them into message format."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7f59a745-5c81-4011-a4c5-a33ec1eca7ef",
"metadata": {},
"outputs": [],
"source": [
"examples = [\n",
" (\n",
" \"The ocean is vast and blue. It's more than 20,000 feet deep. There are many fish in it.\",\n",
" Person(name=None, height_in_meters=None, hair_color=None),\n",
" ),\n",
" (\n",
" \"Fiona traveled far from France to Spain.\",\n",
" Person(name=\"Fiona\", height_in_meters=None, hair_color=None),\n",
" ),\n",
"]\n",
"\n",
"\n",
"messages = []\n",
"\n",
"for text, tool_call in examples:\n",
" messages.extend(\n",
" tool_example_to_messages({\"input\": text, \"tool_calls\": [tool_call]})\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "6fdbda30-e7e3-46b5-a54a-1769c580af93",
"metadata": {},
"source": [
"Let's test out the prompt"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "976bb7b8-09c4-4a3e-80df-49a483705c08",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"system: content=\"You are an expert extraction algorithm. Only extract relevant information from the text. If you do not know the value of an attribute asked to extract, return null for the attribute's value.\"\n",
"human: content=\"The ocean is vast and blue. It's more than 20,000 feet deep. There are many fish in it.\"\n",
"ai: content='' tool_calls=[{'name': 'Person', 'args': {'name': None, 'hair_color': None, 'height_in_meters': None}, 'id': 'b843ba77-4c9c-48ef-92a4-54e534f24521'}]\n",
"tool: content='You have correctly called this tool.' tool_call_id='b843ba77-4c9c-48ef-92a4-54e534f24521'\n",
"human: content='Fiona traveled far from France to Spain.'\n",
"ai: content='' tool_calls=[{'name': 'Person', 'args': {'name': 'Fiona', 'hair_color': None, 'height_in_meters': None}, 'id': '46f00d6b-50e5-4482-9406-b07bb10340f6'}]\n",
"tool: content='You have correctly called this tool.' tool_call_id='46f00d6b-50e5-4482-9406-b07bb10340f6'\n",
"human: content='this is some text'\n"
]
}
],
"source": [
"example_prompt = prompt.invoke({\"text\": \"this is some text\", \"examples\": messages})\n",
"\n",
"for message in example_prompt.messages:\n",
" print(f\"{message.type}: {message}\")"
]
},
{
"cell_type": "markdown",
"id": "47b0bbef-bc6b-4535-a8e2-5c84f09d5637",
"metadata": {},
"source": [
"## Create an extractor\n",
"\n",
"Let's select an LLM. Because we are using tool-calling, we will need a model that supports a tool-calling feature. See [this table](/docs/integrations/chat) for available LLMs.\n",
"\n",
"```{=mdx}\n",
"import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
"\n",
"<ChatModelTabs\n",
" customVarName=\"llm\"\n",
" openaiParams={`model=\"gpt-4-0125-preview\", temperature=0`}\n",
"/>\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "df2e1ee1-69e8-4c4d-b349-95f2e320317b",
"metadata": {},
"outputs": [],
"source": [
"# | output: false\n",
"# | echo: false\n",
"\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-4-0125-preview\", temperature=0)"
]
},
{
"cell_type": "markdown",
"id": "ef21e8cb-c4df-4e12-9be7-37ac9d291d42",
"metadata": {},
"source": [
"Following the [extraction tutorial](/docs/tutorials/extraction), we use the `.with_structured_output` method to structure model outputs according to the desired schema:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "dbfea43d-769b-42e9-a76f-ce722f7d6f93",
"metadata": {},
"outputs": [],
"source": [
"runnable = prompt | llm.with_structured_output(\n",
" schema=Data,\n",
" method=\"function_calling\",\n",
" include_raw=False,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "58a8139e-f201-4b8e-baf0-16a83e5fa987",
"metadata": {},
"source": [
"## Without examples 😿\n",
"\n",
"Notice that even capable models can fail with a **very simple** test case!"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "66545cab-af2a-40a4-9dc9-b4110458b7d3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"people=[Person(name='earth', hair_color='null', height_in_meters='null')]\n",
"people=[Person(name='earth', hair_color='null', height_in_meters='null')]\n",
"people=[]\n",
"people=[Person(name='earth', hair_color='null', height_in_meters='null')]\n",
"people=[]\n"
]
}
],
"source": [
"for _ in range(5):\n",
" text = \"The solar system is large, but earth has only 1 moon.\"\n",
" print(runnable.invoke({\"text\": text, \"examples\": []}))"
]
},
{
"cell_type": "markdown",
"id": "09840f17-ab26-4ea2-8a39-c747103804ec",
"metadata": {},
"source": [
"## With examples 😻\n",
"\n",
"Reference examples helps to fix the failure!"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "1c09d805-ec16-4123-aef9-6a5b59499b5c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"people=[]\n",
"people=[]\n",
"people=[]\n",
"people=[]\n",
"people=[]\n"
]
}
],
"source": [
"for _ in range(5):\n",
" text = \"The solar system is large, but earth has only 1 moon.\"\n",
" print(runnable.invoke({\"text\": text, \"examples\": messages}))"
]
},
{
"cell_type": "markdown",
"id": "3855cad5-dfee-4b42-ad35-b28d4d98902e",
"metadata": {},
"source": [
"Note that we can see the few-shot examples as tool-calls in the [Langsmith trace](https://smith.langchain.com/public/4c436bc2-a1ce-440b-82f5-093947542e40/r).\n",
"\n",
"And we retain performance on a positive sample:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "a9b7a762-1b75-4f9f-b9d9-6732dd05802c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Data(people=[Person(name='Harrison', hair_color='black', height_in_meters=None)])"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"runnable.invoke(\n",
" {\n",
" \"text\": \"My name is Harrison. My hair is black.\",\n",
" \"examples\": messages,\n",
" }\n",
")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,424 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9e161a8a-fcf0-4d55-933e-da271ce28d7e",
"metadata": {},
"source": [
"# How to handle long text when doing extraction\n",
"\n",
"When working with files, like PDFs, you're likely to encounter text that exceeds your language model's context window. To process this text, consider these strategies:\n",
"\n",
"1. **Change LLM** Choose a different LLM that supports a larger context window.\n",
"2. **Brute Force** Chunk the document, and extract content from each chunk.\n",
"3. **RAG** Chunk the document, index the chunks, and only extract content from a subset of chunks that look \"relevant\".\n",
"\n",
"Keep in mind that these strategies have different trade off and the best strategy likely depends on the application that you're designing!\n",
"\n",
"This guide demonstrates how to implement strategies 2 and 3."
]
},
{
"cell_type": "markdown",
"id": "57969139-ad0a-487e-97d8-cb30e2af9742",
"metadata": {},
"source": [
"## Set up\n",
"\n",
"We need some example data! Let's download an article about [cars from wikipedia](https://en.wikipedia.org/wiki/Car) and load it as a LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "84460db2-36e1-4037-bfa6-2a11883c2ba5",
"metadata": {},
"outputs": [],
"source": [
"import re\n",
"\n",
"import requests\n",
"from langchain_community.document_loaders import BSHTMLLoader\n",
"\n",
"# Download the content\n",
"response = requests.get(\"https://en.wikipedia.org/wiki/Car\")\n",
"# Write it to a file\n",
"with open(\"car.html\", \"w\", encoding=\"utf-8\") as f:\n",
" f.write(response.text)\n",
"# Load it with an HTML parser\n",
"loader = BSHTMLLoader(\"car.html\")\n",
"document = loader.load()[0]\n",
"# Clean up code\n",
"# Replace consecutive new lines with a single new line\n",
"document.page_content = re.sub(\"\\n\\n+\", \"\\n\", document.page_content)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "fcb6917b-123d-4630-a0ce-ed8b293d482d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"79174\n"
]
}
],
"source": [
"print(len(document.page_content))"
]
},
{
"cell_type": "markdown",
"id": "af3ffb8d-587a-4370-886a-e56e617bcb9c",
"metadata": {},
"source": [
"## Define the schema\n",
"\n",
"Following the [extraction tutorial](/docs/tutorials/extraction), we will use Pydantic to define the schema of information we wish to extract. In this case, we will extract a list of \"key developments\" (e.g., important historical events) that include a year and description.\n",
"\n",
"Note that we also include an `evidence` key and instruct the model to provide in verbatim the relevant sentences of text from the article. This allows us to compare the extraction results to (the model's reconstruction of) text from the original document."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a3b288ed-87a6-4af0-aac8-20921dc370d4",
"metadata": {},
"outputs": [],
"source": [
"from typing import List, Optional\n",
"\n",
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"\n",
"\n",
"class KeyDevelopment(BaseModel):\n",
" \"\"\"Information about a development in the history of cars.\"\"\"\n",
"\n",
" year: int = Field(\n",
" ..., description=\"The year when there was an important historic development.\"\n",
" )\n",
" description: str = Field(\n",
" ..., description=\"What happened in this year? What was the development?\"\n",
" )\n",
" evidence: str = Field(\n",
" ...,\n",
" description=\"Repeat in verbatim the sentence(s) from which the year and description information were extracted\",\n",
" )\n",
"\n",
"\n",
"class ExtractionData(BaseModel):\n",
" \"\"\"Extracted information about key developments in the history of cars.\"\"\"\n",
"\n",
" key_developments: List[KeyDevelopment]\n",
"\n",
"\n",
"# Define a custom prompt to provide instructions and any additional context.\n",
"# 1) You can add examples into the prompt template to improve extraction quality\n",
"# 2) Introduce additional parameters to take context into account (e.g., include metadata\n",
"# about the document from which the text was extracted.)\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are an expert at identifying key historic development in text. \"\n",
" \"Only extract important historic developments. Extract nothing if no important information can be found in the text.\",\n",
" ),\n",
" (\"human\", \"{text}\"),\n",
" ]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "3909e22e-8a00-4f3d-bbf2-4762a0558af3",
"metadata": {},
"source": [
"## Create an extractor\n",
"\n",
"Let's select an LLM. Because we are using tool-calling, we will need a model that supports a tool-calling feature. See [this table](/docs/integrations/chat) for available LLMs.\n",
"\n",
"```{=mdx}\n",
"import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
"\n",
"<ChatModelTabs\n",
" customVarName=\"llm\"\n",
" openaiParams={`model=\"gpt-4-0125-preview\", temperature=0`}\n",
"/>\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "109f4f05-d0ff-431d-93d9-8f5aa34979a6",
"metadata": {},
"outputs": [],
"source": [
"# | output: false\n",
"# | echo: false\n",
"\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-4-0125-preview\", temperature=0)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "aa4ae224-6d3d-4fe2-b210-7db19a9fe580",
"metadata": {},
"outputs": [],
"source": [
"extractor = prompt | llm.with_structured_output(\n",
" schema=ExtractionData,\n",
" include_raw=False,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "13aebafb-26b5-42b2-ae8e-9c05cd56e5c5",
"metadata": {},
"source": [
"## Brute force approach\n",
"\n",
"Split the documents into chunks such that each chunk fits into the context window of the LLMs."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "27b8a373-14b3-45ea-8bf5-9749122ad927",
"metadata": {},
"outputs": [],
"source": [
"from langchain_text_splitters import TokenTextSplitter\n",
"\n",
"text_splitter = TokenTextSplitter(\n",
" # Controls the size of each chunk\n",
" chunk_size=2000,\n",
" # Controls overlap between chunks\n",
" chunk_overlap=20,\n",
")\n",
"\n",
"texts = text_splitter.split_text(document.page_content)"
]
},
{
"cell_type": "markdown",
"id": "5b43d7e0-3c85-4d97-86c7-e8c984b60b0a",
"metadata": {},
"source": [
"Use [batch](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html) functionality to run the extraction in **parallel** across each chunk! \n",
"\n",
":::{.callout-tip}\n",
"You can often use .batch() to parallelize the extractions! `.batch` uses a threadpool under the hood to help you parallelize workloads.\n",
"\n",
"If your model is exposed via an API, this will likely speed up your extraction flow!\n",
":::"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "6ba766b5-8d6c-48e6-8d69-f391a66b65d2",
"metadata": {},
"outputs": [],
"source": [
"# Limit just to the first 3 chunks\n",
"# so the code can be re-run quickly\n",
"first_few = texts[:3]\n",
"\n",
"extractions = extractor.batch(\n",
" [{\"text\": text} for text in first_few],\n",
" {\"max_concurrency\": 5}, # limit the concurrency by passing max concurrency!\n",
")"
]
},
{
"cell_type": "markdown",
"id": "67da8904-e927-406b-a439-2a16f6087ccf",
"metadata": {},
"source": [
"### Merge results\n",
"\n",
"After extracting data from across the chunks, we'll want to merge the extractions together."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "c3f77470-ce6c-477f-8957-650913218632",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[KeyDevelopment(year=1966, description='The Toyota Corolla began production, becoming the best-selling series of automobile in history.', evidence='The Toyota Corolla, which has been in production since 1966, is the best-selling series of automobile in history.'),\n",
" KeyDevelopment(year=1769, description='Nicolas-Joseph Cugnot built the first steam-powered road vehicle.', evidence='The French inventor Nicolas-Joseph Cugnot built the first steam-powered road vehicle in 1769.'),\n",
" KeyDevelopment(year=1808, description='François Isaac de Rivaz designed and constructed the first internal combustion-powered automobile.', evidence='the Swiss inventor François Isaac de Rivaz designed and constructed the first internal combustion-powered automobile in 1808.'),\n",
" KeyDevelopment(year=1886, description='Carl Benz patented his Benz Patent-Motorwagen, inventing the modern car.', evidence='The modern car—a practical, marketable automobile for everyday use—was invented in 1886, when the German inventor Carl Benz patented his Benz Patent-Motorwagen.'),\n",
" KeyDevelopment(year=1908, description='Ford Model T, one of the first cars affordable by the masses, began production.', evidence='One of the first cars affordable by the masses was the Ford Model T, begun in 1908, an American car manufactured by the Ford Motor Company.'),\n",
" KeyDevelopment(year=1888, description=\"Bertha Benz undertook the first road trip by car to prove the road-worthiness of her husband's invention.\", evidence=\"In August 1888, Bertha Benz, the wife of Carl Benz, undertook the first road trip by car, to prove the road-worthiness of her husband's invention.\"),\n",
" KeyDevelopment(year=1896, description='Benz designed and patented the first internal-combustion flat engine, called boxermotor.', evidence='In 1896, Benz designed and patented the first internal-combustion flat engine, called boxermotor.'),\n",
" KeyDevelopment(year=1897, description='Nesselsdorfer Wagenbau produced the Präsident automobil, one of the first factory-made cars in the world.', evidence='The first motor car in central Europe and one of the first factory-made cars in the world, was produced by Czech company Nesselsdorfer Wagenbau (later renamed to Tatra) in 1897, the Präsident automobil.'),\n",
" KeyDevelopment(year=1890, description='Daimler Motoren Gesellschaft (DMG) was founded by Daimler and Maybach in Cannstatt.', evidence='Daimler and Maybach founded Daimler Motoren Gesellschaft (DMG) in Cannstatt in 1890.'),\n",
" KeyDevelopment(year=1891, description='Auguste Doriot and Louis Rigoulot completed the longest trip by a petrol-driven vehicle with a Daimler powered Peugeot Type 3.', evidence='In 1891, Auguste Doriot and his Peugeot colleague Louis Rigoulot completed the longest trip by a petrol-driven vehicle when their self-designed and built Daimler powered Peugeot Type 3 completed 2,100 kilometres (1,300 mi) from Valentigney to Paris and Brest and back again.')]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"key_developments = []\n",
"\n",
"for extraction in extractions:\n",
" key_developments.extend(extraction.key_developments)\n",
"\n",
"key_developments[:10]"
]
},
{
"cell_type": "markdown",
"id": "48afd4a7-abcd-48b4-8ff1-6ca485f529e3",
"metadata": {},
"source": [
"## RAG based approach\n",
"\n",
"Another simple idea is to chunk up the text, but instead of extracting information from every chunk, just focus on the the most relevant chunks.\n",
"\n",
":::{.callout-caution}\n",
"It can be difficult to identify which chunks are relevant.\n",
"\n",
"For example, in the `car` article we're using here, most of the article contains key development information. So by using\n",
"**RAG**, we'll likely be throwing out a lot of relevant information.\n",
"\n",
"We suggest experimenting with your use case and determining whether this approach works or not.\n",
":::\n",
"\n",
"To implement the RAG based approach: \n",
"\n",
"1. Chunk up your document(s) and index them (e.g., in a vectorstore);\n",
"2. Prepend the `extractor` chain with a retrieval step using the vectorstore.\n",
"\n",
"Here's a simple example that relies on the `FAISS` vectorstore."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "aaf37c82-625b-4fa1-8e88-73303f08ac16",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.vectorstores import FAISS\n",
"from langchain_core.documents import Document\n",
"from langchain_core.runnables import RunnableLambda\n",
"from langchain_openai import OpenAIEmbeddings\n",
"from langchain_text_splitters import CharacterTextSplitter\n",
"\n",
"texts = text_splitter.split_text(document.page_content)\n",
"vectorstore = FAISS.from_texts(texts, embedding=OpenAIEmbeddings())\n",
"\n",
"retriever = vectorstore.as_retriever(\n",
" search_kwargs={\"k\": 1}\n",
") # Only extract from first document"
]
},
{
"cell_type": "markdown",
"id": "013ecad9-f80f-477c-b954-494b46a02a07",
"metadata": {},
"source": [
"In this case the RAG extractor is only looking at the top document."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "47aad00b-7013-4f7f-a1b0-02ef269093bf",
"metadata": {},
"outputs": [],
"source": [
"rag_extractor = {\n",
" \"text\": retriever | (lambda docs: docs[0].page_content) # fetch content of top doc\n",
"} | extractor"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "68f2de01-0cd8-456e-a959-db236189d41b",
"metadata": {},
"outputs": [],
"source": [
"results = rag_extractor.invoke(\"Key developments associated with cars\")"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "1788e2d6-77bb-417f-827c-eb96c035164e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"year=1869 description='Mary Ward became one of the first documented car fatalities in Parsonstown, Ireland.' evidence='Mary Ward became one of the first documented car fatalities in 1869 in Parsonstown, Ireland,'\n",
"year=1899 description=\"Henry Bliss one of the US's first pedestrian car casualties in New York City.\" evidence=\"Henry Bliss one of the US's first pedestrian car casualties in 1899 in New York City.\"\n",
"year=2030 description='All fossil fuel vehicles will be banned in Amsterdam.' evidence='all fossil fuel vehicles will be banned in Amsterdam from 2030.'\n"
]
}
],
"source": [
"for key_development in results.key_developments:\n",
" print(key_development)"
]
},
{
"cell_type": "markdown",
"id": "cf36e626-cf5d-4324-ba29-9bd602be9b97",
"metadata": {},
"source": [
"## Common issues\n",
"\n",
"Different methods have their own pros and cons related to cost, speed, and accuracy.\n",
"\n",
"Watch out for these issues:\n",
"\n",
"* Chunking content means that the LLM can fail to extract information if the information is spread across multiple chunks.\n",
"* Large chunk overlap may cause the same information to be extracted twice, so be prepared to de-duplicate!\n",
"* LLMs can make up data. If looking for a single fact across a large text and using a brute force approach, you may end up getting more made up data."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,357 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ea37db49-d389-4291-be73-885d06c1fb7e",
"metadata": {},
"source": [
"# How to use prompting alone (no tool calling) to do extraction\n",
"\n",
"Tool calling features are not required for generating structured output from LLMs. LLMs that are able to follow prompt instructions well can be tasked with outputting information in a given format.\n",
"\n",
"This approach relies on designing good prompts and then parsing the output of the LLMs to make them extract information well.\n",
"\n",
"To extract data without tool-calling features: \n",
"\n",
"1. Instruct the LLM to generate text following an expected format (e.g., JSON with a certain schema);\n",
"2. Use [output parsers](/docs/concepts#output-parsers) to structure the model response into a desired Python object.\n",
"\n",
"First we select a LLM:\n",
"\n",
"```{=mdx}\n",
"import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
"\n",
"<ChatModelTabs customVarName=\"model\" />\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "25487939-8713-4ec7-b774-e4a761ac8298",
"metadata": {},
"outputs": [],
"source": [
"# | output: false\n",
"# | echo: false\n",
"\n",
"from langchain_anthropic.chat_models import ChatAnthropic\n",
"\n",
"model = ChatAnthropic(model_name=\"claude-3-sonnet-20240229\", temperature=0)"
]
},
{
"cell_type": "markdown",
"id": "3e412374-3beb-4bbf-966b-400c1f66a258",
"metadata": {},
"source": [
":::{.callout-tip}\n",
"This tutorial is meant to be simple, but generally should really include reference examples to squeeze out performance!\n",
":::"
]
},
{
"cell_type": "markdown",
"id": "abc1a945-0f80-4953-add4-cd572b6f2a51",
"metadata": {},
"source": [
"## Using PydanticOutputParser\n",
"\n",
"The following example uses the built-in `PydanticOutputParser` to parse the output of a chat model."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "497eb023-c043-443d-ac62-2d4ea85fe1b0",
"metadata": {},
"outputs": [],
"source": [
"from typing import List, Optional\n",
"\n",
"from langchain.output_parsers import PydanticOutputParser\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.pydantic_v1 import BaseModel, Field, validator\n",
"\n",
"\n",
"class Person(BaseModel):\n",
" \"\"\"Information about a person.\"\"\"\n",
"\n",
" name: str = Field(..., description=\"The name of the person\")\n",
" height_in_meters: float = Field(\n",
" ..., description=\"The height of the person expressed in meters.\"\n",
" )\n",
"\n",
"\n",
"class People(BaseModel):\n",
" \"\"\"Identifying information about all people in a text.\"\"\"\n",
"\n",
" people: List[Person]\n",
"\n",
"\n",
"# Set up a parser\n",
"parser = PydanticOutputParser(pydantic_object=People)\n",
"\n",
"# Prompt\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"Answer the user query. Wrap the output in `json` tags\\n{format_instructions}\",\n",
" ),\n",
" (\"human\", \"{query}\"),\n",
" ]\n",
").partial(format_instructions=parser.get_format_instructions())"
]
},
{
"cell_type": "markdown",
"id": "c31aa2c8-05a9-4a12-80c5-ea1250dea0ae",
"metadata": {},
"source": [
"Let's take a look at what information is sent to the model"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "20b99ffb-a114-49a9-a7be-154c525f8ada",
"metadata": {},
"outputs": [],
"source": [
"query = \"Anna is 23 years old and she is 6 feet tall\""
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4f3a66ce-de19-4571-9e54-67504ae3fba7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"System: Answer the user query. Wrap the output in `json` tags\n",
"The output should be formatted as a JSON instance that conforms to the JSON schema below.\n",
"\n",
"As an example, for the schema {\"properties\": {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\n",
"the object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n",
"\n",
"Here is the output schema:\n",
"```\n",
"{\"description\": \"Identifying information about all people in a text.\", \"properties\": {\"people\": {\"title\": \"People\", \"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/Person\"}}}, \"required\": [\"people\"], \"definitions\": {\"Person\": {\"title\": \"Person\", \"description\": \"Information about a person.\", \"type\": \"object\", \"properties\": {\"name\": {\"title\": \"Name\", \"description\": \"The name of the person\", \"type\": \"string\"}, \"height_in_meters\": {\"title\": \"Height In Meters\", \"description\": \"The height of the person expressed in meters.\", \"type\": \"number\"}}, \"required\": [\"name\", \"height_in_meters\"]}}}\n",
"```\n",
"Human: Anna is 23 years old and she is 6 feet tall\n"
]
}
],
"source": [
"print(prompt.format_prompt(query=query).to_string())"
]
},
{
"cell_type": "markdown",
"id": "6f1048e0-1bfd-49f9-b697-74389a5ce69c",
"metadata": {},
"source": [
"Having defined our prompt, we simply chain together the prompt, model and output parser:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7e0041eb-37dc-4384-9fe3-6dd8c356371e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"People(people=[Person(name='Anna', height_in_meters=1.83)])"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain = prompt | model | parser\n",
"chain.invoke({\"query\": query})"
]
},
{
"cell_type": "markdown",
"id": "dd492fe4-110a-4b83-a191-79fffbc1055a",
"metadata": {},
"source": [
"Check out the associated [Langsmith trace](https://smith.langchain.com/public/92ed52a3-92b9-45af-a663-0a9c00e5e396/r).\n",
"\n",
"Note that the schema shows up in two places: \n",
"\n",
"1. In the prompt, via `parser.get_format_instructions()`;\n",
"2. In the chain, to receive the formatted output and structure it into a Python object (in this case, the Pydantic object `People`)."
]
},
{
"cell_type": "markdown",
"id": "815b3b87-3bc6-4b56-835e-c6b6703cef5d",
"metadata": {},
"source": [
"## Custom Parsing\n",
"\n",
"If desired, it's easy to create a custom prompt and parser with `LangChain` and `LCEL`.\n",
"\n",
"To create a custom parser, define a function to parse the output from the model (typically an [AIMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessage.html)) into an object of your choice.\n",
"\n",
"See below for a simple implementation of a JSON parser."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "b1f11912-c1bb-4a2a-a482-79bf3996961f",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import re\n",
"from typing import List, Optional\n",
"\n",
"from langchain_anthropic.chat_models import ChatAnthropic\n",
"from langchain_core.messages import AIMessage\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.pydantic_v1 import BaseModel, Field, validator\n",
"\n",
"\n",
"class Person(BaseModel):\n",
" \"\"\"Information about a person.\"\"\"\n",
"\n",
" name: str = Field(..., description=\"The name of the person\")\n",
" height_in_meters: float = Field(\n",
" ..., description=\"The height of the person expressed in meters.\"\n",
" )\n",
"\n",
"\n",
"class People(BaseModel):\n",
" \"\"\"Identifying information about all people in a text.\"\"\"\n",
"\n",
" people: List[Person]\n",
"\n",
"\n",
"# Prompt\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"Answer the user query. Output your answer as JSON that \"\n",
" \"matches the given schema: ```json\\n{schema}\\n```. \"\n",
" \"Make sure to wrap the answer in ```json and ``` tags\",\n",
" ),\n",
" (\"human\", \"{query}\"),\n",
" ]\n",
").partial(schema=People.schema())\n",
"\n",
"\n",
"# Custom parser\n",
"def extract_json(message: AIMessage) -> List[dict]:\n",
" \"\"\"Extracts JSON content from a string where JSON is embedded between ```json and ``` tags.\n",
"\n",
" Parameters:\n",
" text (str): The text containing the JSON content.\n",
"\n",
" Returns:\n",
" list: A list of extracted JSON strings.\n",
" \"\"\"\n",
" text = message.content\n",
" # Define the regular expression pattern to match JSON blocks\n",
" pattern = r\"```json(.*?)```\"\n",
"\n",
" # Find all non-overlapping matches of the pattern in the string\n",
" matches = re.findall(pattern, text, re.DOTALL)\n",
"\n",
" # Return the list of matched JSON strings, stripping any leading or trailing whitespace\n",
" try:\n",
" return [json.loads(match.strip()) for match in matches]\n",
" except Exception:\n",
" raise ValueError(f\"Failed to parse: {message}\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "9260d5e8-3b6c-4639-9f3b-fb2f90239e4b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"System: Answer the user query. Output your answer as JSON that matches the given schema: ```json\n",
"{'title': 'People', 'description': 'Identifying information about all people in a text.', 'type': 'object', 'properties': {'people': {'title': 'People', 'type': 'array', 'items': {'$ref': '#/definitions/Person'}}}, 'required': ['people'], 'definitions': {'Person': {'title': 'Person', 'description': 'Information about a person.', 'type': 'object', 'properties': {'name': {'title': 'Name', 'description': 'The name of the person', 'type': 'string'}, 'height_in_meters': {'title': 'Height In Meters', 'description': 'The height of the person expressed in meters.', 'type': 'number'}}, 'required': ['name', 'height_in_meters']}}}\n",
"```. Make sure to wrap the answer in ```json and ``` tags\n",
"Human: Anna is 23 years old and she is 6 feet tall\n"
]
}
],
"source": [
"query = \"Anna is 23 years old and she is 6 feet tall\"\n",
"print(prompt.format_prompt(query=query).to_string())"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "c523301d-ae0e-45e3-b195-7fd28c67a5c4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'people': [{'name': 'Anna', 'height_in_meters': 1.83}]}]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain = prompt | model | extract_json\n",
"chain.invoke({\"query\": query})"
]
},
{
"cell_type": "markdown",
"id": "d3601bde",
"metadata": {},
"source": [
"## Other Libraries\n",
"\n",
"If you're looking at extracting using a parsing approach, check out the [Kor](https://eyurtsev.github.io/kor/) library. It's written by one of the `LangChain` maintainers and it\n",
"helps to craft a prompt that takes examples into account, allows controlling formats (e.g., JSON or CSV) and expresses the schema in TypeScript. It seems to work pretty!"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,455 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "19c9cbd6",
"metadata": {},
"source": [
"# Fallbacks\n",
"\n",
"When working with language models, you may often encounter issues from the underlying APIs, whether these be rate limiting or downtime. Therefore, as you go to move your LLM applications into production it becomes more and more important to safeguard against these. That's why we've introduced the concept of fallbacks. \n",
"\n",
"A **fallback** is an alternative plan that may be used in an emergency.\n",
"\n",
"Crucially, fallbacks can be applied not only on the LLM level but on the whole runnable level. This is important because often times different models require different prompts. So if your call to OpenAI fails, you don't just want to send the same prompt to Anthropic - you probably want to use a different prompt template and send a different version there."
]
},
{
"cell_type": "markdown",
"id": "a6bb9ba9",
"metadata": {},
"source": [
"## Fallback for LLM API Errors\n",
"\n",
"This is maybe the most common use case for fallbacks. A request to an LLM API can fail for a variety of reasons - the API could be down, you could have hit rate limits, any number of things. Therefore, using fallbacks can help protect against these types of things.\n",
"\n",
"IMPORTANT: By default, a lot of the LLM wrappers catch errors and retry. You will most likely want to turn those off when working with fallbacks. Otherwise the first wrapper will keep on retrying and not failing."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a449a2e",
"metadata": {},
"outputs": [],
"source": [
"%pip install --upgrade --quiet langchain langchain-openai"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "d3e893bf",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.chat_models import ChatAnthropic\n",
"from langchain_openai import ChatOpenAI"
]
},
{
"cell_type": "markdown",
"id": "4847c82d",
"metadata": {},
"source": [
"First, let's mock out what happens if we hit a RateLimitError from OpenAI"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "dfdd8bf5",
"metadata": {},
"outputs": [],
"source": [
"from unittest.mock import patch\n",
"\n",
"import httpx\n",
"from openai import RateLimitError\n",
"\n",
"request = httpx.Request(\"GET\", \"/\")\n",
"response = httpx.Response(200, request=request)\n",
"error = RateLimitError(\"rate limit\", response=response, body=\"\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e6fdffc1",
"metadata": {},
"outputs": [],
"source": [
"# Note that we set max_retries = 0 to avoid retrying on RateLimits, etc\n",
"openai_llm = ChatOpenAI(max_retries=0)\n",
"anthropic_llm = ChatAnthropic()\n",
"llm = openai_llm.with_fallbacks([anthropic_llm])"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "584461ab",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hit error\n"
]
}
],
"source": [
"# Let's use just the OpenAI LLm first, to show that we run into an error\n",
"with patch(\"openai.resources.chat.completions.Completions.create\", side_effect=error):\n",
" try:\n",
" print(openai_llm.invoke(\"Why did the chicken cross the road?\"))\n",
" except RateLimitError:\n",
" print(\"Hit error\")"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "4fc1e673",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"content=' I don\\'t actually know why the chicken crossed the road, but here are some possible humorous answers:\\n\\n- To get to the other side!\\n\\n- It was too chicken to just stand there. \\n\\n- It wanted a change of scenery.\\n\\n- It wanted to show the possum it could be done.\\n\\n- It was on its way to a poultry farmers\\' convention.\\n\\nThe joke plays on the double meaning of \"the other side\" - literally crossing the road to the other side, or the \"other side\" meaning the afterlife. So it\\'s an anti-joke, with a silly or unexpected pun as the answer.' additional_kwargs={} example=False\n"
]
}
],
"source": [
"# Now let's try with fallbacks to Anthropic\n",
"with patch(\"openai.resources.chat.completions.Completions.create\", side_effect=error):\n",
" try:\n",
" print(llm.invoke(\"Why did the chicken cross the road?\"))\n",
" except RateLimitError:\n",
" print(\"Hit error\")"
]
},
{
"cell_type": "markdown",
"id": "f00bea25",
"metadata": {},
"source": [
"We can use our \"LLM with Fallbacks\" as we would a normal LLM."
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "4f8eaaa0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"content=\" I don't actually know why the kangaroo crossed the road, but I can take a guess! Here are some possible reasons:\\n\\n- To get to the other side (the classic joke answer!)\\n\\n- It was trying to find some food or water \\n\\n- It was trying to find a mate during mating season\\n\\n- It was fleeing from a predator or perceived threat\\n\\n- It was disoriented and crossed accidentally \\n\\n- It was following a herd of other kangaroos who were crossing\\n\\n- It wanted a change of scenery or environment \\n\\n- It was trying to reach a new habitat or territory\\n\\nThe real reason is unknown without more context, but hopefully one of those potential explanations does the joke justice! Let me know if you have any other animal jokes I can try to decipher.\" additional_kwargs={} example=False\n"
]
}
],
"source": [
"from langchain_core.prompts import ChatPromptTemplate\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You're a nice assistant who always includes a compliment in your response\",\n",
" ),\n",
" (\"human\", \"Why did the {animal} cross the road\"),\n",
" ]\n",
")\n",
"chain = prompt | llm\n",
"with patch(\"openai.resources.chat.completions.Completions.create\", side_effect=error):\n",
" try:\n",
" print(chain.invoke({\"animal\": \"kangaroo\"}))\n",
" except RateLimitError:\n",
" print(\"Hit error\")"
]
},
{
"cell_type": "markdown",
"id": "8d62241b",
"metadata": {},
"source": [
"## Fallback for Sequences\n",
"\n",
"We can also create fallbacks for sequences, that are sequences themselves. Here we do that with two different models: ChatOpenAI and then normal OpenAI (which does not use a chat model). Because OpenAI is NOT a chat model, you likely want a different prompt."
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "6d0b8056",
"metadata": {},
"outputs": [],
"source": [
"# First let's create a chain with a ChatModel\n",
"# We add in a string output parser here so the outputs between the two are the same type\n",
"from langchain_core.output_parsers import StrOutputParser\n",
"\n",
"chat_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You're a nice assistant who always includes a compliment in your response\",\n",
" ),\n",
" (\"human\", \"Why did the {animal} cross the road\"),\n",
" ]\n",
")\n",
"# Here we're going to use a bad model name to easily create a chain that will error\n",
"chat_model = ChatOpenAI(model=\"gpt-fake\")\n",
"bad_chain = chat_prompt | chat_model | StrOutputParser()"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "8d1fc2a5",
"metadata": {},
"outputs": [],
"source": [
"# Now lets create a chain with the normal OpenAI model\n",
"from langchain_core.prompts import PromptTemplate\n",
"from langchain_openai import OpenAI\n",
"\n",
"prompt_template = \"\"\"Instructions: You should always include a compliment in your response.\n",
"\n",
"Question: Why did the {animal} cross the road?\"\"\"\n",
"prompt = PromptTemplate.from_template(prompt_template)\n",
"llm = OpenAI()\n",
"good_chain = prompt | llm"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "283bfa44",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'\\n\\nAnswer: The turtle crossed the road to get to the other side, and I have to say he had some impressive determination.'"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# We can now create a final chain which combines the two\n",
"chain = bad_chain.with_fallbacks([good_chain])\n",
"chain.invoke({\"animal\": \"turtle\"})"
]
},
{
"cell_type": "markdown",
"id": "ec4685b4",
"metadata": {},
"source": [
"## Fallback for Long Inputs\n",
"\n",
"One of the big limiting factors of LLMs is their context window. Usually, you can count and track the length of prompts before sending them to an LLM, but in situations where that is hard/complicated, you can fallback to a model with a longer context length."
]
},
{
"cell_type": "code",
"execution_count": 34,
"id": "564b84c9",
"metadata": {},
"outputs": [],
"source": [
"short_llm = ChatOpenAI()\n",
"long_llm = ChatOpenAI(model=\"gpt-3.5-turbo-16k\")\n",
"llm = short_llm.with_fallbacks([long_llm])"
]
},
{
"cell_type": "code",
"execution_count": 38,
"id": "5e27a775",
"metadata": {},
"outputs": [],
"source": [
"inputs = \"What is the next number: \" + \", \".join([\"one\", \"two\"] * 3000)"
]
},
{
"cell_type": "code",
"execution_count": 40,
"id": "0a502731",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"This model's maximum context length is 4097 tokens. However, your messages resulted in 12012 tokens. Please reduce the length of the messages.\n"
]
}
],
"source": [
"try:\n",
" print(short_llm.invoke(inputs))\n",
"except Exception as e:\n",
" print(e)"
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "d91ba5d7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"content='The next number in the sequence is two.' additional_kwargs={} example=False\n"
]
}
],
"source": [
"try:\n",
" print(llm.invoke(inputs))\n",
"except Exception as e:\n",
" print(e)"
]
},
{
"cell_type": "markdown",
"id": "2a6735df",
"metadata": {},
"source": [
"## Fallback to Better Model\n",
"\n",
"Often times we ask models to output format in a specific format (like JSON). Models like GPT-3.5 can do this okay, but sometimes struggle. This naturally points to fallbacks - we can try with GPT-3.5 (faster, cheaper), but then if parsing fails we can use GPT-4."
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "867a3793",
"metadata": {},
"outputs": [],
"source": [
"from langchain.output_parsers import DatetimeOutputParser"
]
},
{
"cell_type": "code",
"execution_count": 67,
"id": "b8d9959d",
"metadata": {},
"outputs": [],
"source": [
"prompt = ChatPromptTemplate.from_template(\n",
" \"what time was {event} (in %Y-%m-%dT%H:%M:%S.%fZ format - only return this value)\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 75,
"id": "98087a76",
"metadata": {},
"outputs": [],
"source": [
"# In this case we are going to do the fallbacks on the LLM + output parser level\n",
"# Because the error will get raised in the OutputParser\n",
"openai_35 = ChatOpenAI() | DatetimeOutputParser()\n",
"openai_4 = ChatOpenAI(model=\"gpt-4\") | DatetimeOutputParser()"
]
},
{
"cell_type": "code",
"execution_count": 77,
"id": "17ec9e8f",
"metadata": {},
"outputs": [],
"source": [
"only_35 = prompt | openai_35\n",
"fallback_4 = prompt | openai_35.with_fallbacks([openai_4])"
]
},
{
"cell_type": "code",
"execution_count": 80,
"id": "7e536f0b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Error: Could not parse datetime string: The Super Bowl in 1994 took place on January 30th at 3:30 PM local time. Converting this to the specified format (%Y-%m-%dT%H:%M:%S.%fZ) results in: 1994-01-30T15:30:00.000Z\n"
]
}
],
"source": [
"try:\n",
" print(only_35.invoke({\"event\": \"the superbowl in 1994\"}))\n",
"except Exception as e:\n",
" print(f\"Error: {e}\")"
]
},
{
"cell_type": "code",
"execution_count": 81,
"id": "01355c5e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1994-01-30 15:30:00\n"
]
}
],
"source": [
"try:\n",
" print(fallback_4.invoke({\"event\": \"the superbowl in 1994\"}))\n",
"except Exception as e:\n",
" print(f\"Error: {e}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c537f9d0",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,398 @@
{
"cells": [
{
"cell_type": "raw",
"id": "94c3ad61",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 3\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "b91e03f1",
"metadata": {},
"source": [
"# How to use few shot examples\n",
"\n",
"In this guide, we'll learn how to create a simple prompt template that provides the model with example inputs and outputs when generating. Providing the LLM with a few such examples is called few-shotting, and is a simple yet powerful way to guide generation and in some cases drastically improve model performance.\n",
"\n",
"A few-shot prompt template can be constructed from either a set of examples, or from an [Example Selector](https://api.python.langchain.com/en/latest/example_selectors/langchain_core.example_selectors.base.BaseExampleSelector.html) class responsible for choosing a subset of examples from the defined set.\n",
"\n",
"This guide will cover few-shotting with string prompt templates. For a guide on few-shotting with chat messages for chat models, see [here](/docs/how_to/few_shot_examples_chat/).\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Prompt templates](/docs/concepts/#prompt-templates)\n",
"- [Example selectors](/docs/concepts/#example-selectors)\n",
"- [LLMs](/docs/concepts/#llms)\n",
"- [Vectorstores](/docs/concepts/#vectorstores)\n",
"`} />\n",
"```\n",
"\n",
"## Create a formatter for the few-shot examples\n",
"\n",
"Configure a formatter that will format the few-shot examples into a string. This formatter should be a `PromptTemplate` object."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "4e70bce2",
"metadata": {},
"outputs": [],
"source": [
"from langchain.prompts.prompt import PromptTemplate\n",
"\n",
"example_prompt = PromptTemplate.from_template(\"Question: {question}\\n{answer}\")"
]
},
{
"cell_type": "markdown",
"id": "50846ad4",
"metadata": {},
"source": [
"## Creating the example set\n",
"\n",
"Next, we'll create a list of few-shot examples. Each example should be a dictionary representing an example input to the formatter prompt we defined above."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "a44be840",
"metadata": {},
"outputs": [],
"source": [
"examples = [\n",
" {\n",
" \"question\": \"Who lived longer, Muhammad Ali or Alan Turing?\",\n",
" \"answer\": \"\"\"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: How old was Muhammad Ali when he died?\n",
"Intermediate answer: Muhammad Ali was 74 years old when he died.\n",
"Follow up: How old was Alan Turing when he died?\n",
"Intermediate answer: Alan Turing was 41 years old when he died.\n",
"So the final answer is: Muhammad Ali\n",
"\"\"\",\n",
" },\n",
" {\n",
" \"question\": \"When was the founder of craigslist born?\",\n",
" \"answer\": \"\"\"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: Who was the founder of craigslist?\n",
"Intermediate answer: Craigslist was founded by Craig Newmark.\n",
"Follow up: When was Craig Newmark born?\n",
"Intermediate answer: Craig Newmark was born on December 6, 1952.\n",
"So the final answer is: December 6, 1952\n",
"\"\"\",\n",
" },\n",
" {\n",
" \"question\": \"Who was the maternal grandfather of George Washington?\",\n",
" \"answer\": \"\"\"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: Who was the mother of George Washington?\n",
"Intermediate answer: The mother of George Washington was Mary Ball Washington.\n",
"Follow up: Who was the father of Mary Ball Washington?\n",
"Intermediate answer: The father of Mary Ball Washington was Joseph Ball.\n",
"So the final answer is: Joseph Ball\n",
"\"\"\",\n",
" },\n",
" {\n",
" \"question\": \"Are both the directors of Jaws and Casino Royale from the same country?\",\n",
" \"answer\": \"\"\"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: Who is the director of Jaws?\n",
"Intermediate Answer: The director of Jaws is Steven Spielberg.\n",
"Follow up: Where is Steven Spielberg from?\n",
"Intermediate Answer: The United States.\n",
"Follow up: Who is the director of Casino Royale?\n",
"Intermediate Answer: The director of Casino Royale is Martin Campbell.\n",
"Follow up: Where is Martin Campbell from?\n",
"Intermediate Answer: New Zealand.\n",
"So the final answer is: No\n",
"\"\"\",\n",
" },\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "3d1ec9d5",
"metadata": {},
"source": [
"Let's test the formatting prompt with one of our examples:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "8c6e48ad",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Question: Who lived longer, Muhammad Ali or Alan Turing?\n",
"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: How old was Muhammad Ali when he died?\n",
"Intermediate answer: Muhammad Ali was 74 years old when he died.\n",
"Follow up: How old was Alan Turing when he died?\n",
"Intermediate answer: Alan Turing was 41 years old when he died.\n",
"So the final answer is: Muhammad Ali\n",
"\n"
]
}
],
"source": [
"print(example_prompt.invoke(examples[0]).to_string())"
]
},
{
"cell_type": "markdown",
"id": "dad66af1",
"metadata": {},
"source": [
"### Pass the examples and formatter to `FewShotPromptTemplate`\n",
"\n",
"Finally, create a [`FewShotPromptTemplate`](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.few_shot.FewShotPromptTemplate.html) object. This object takes in the few-shot examples and the formatter for the few-shot examples. When this `FewShotPromptTemplate` is formatted, it formats the passed examples using the `example_prompt`, then and adds them to the final prompt before `suffix`:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "e76fa1ba",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Question: Who lived longer, Muhammad Ali or Alan Turing?\n",
"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: How old was Muhammad Ali when he died?\n",
"Intermediate answer: Muhammad Ali was 74 years old when he died.\n",
"Follow up: How old was Alan Turing when he died?\n",
"Intermediate answer: Alan Turing was 41 years old when he died.\n",
"So the final answer is: Muhammad Ali\n",
"\n",
"\n",
"Question: When was the founder of craigslist born?\n",
"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: Who was the founder of craigslist?\n",
"Intermediate answer: Craigslist was founded by Craig Newmark.\n",
"Follow up: When was Craig Newmark born?\n",
"Intermediate answer: Craig Newmark was born on December 6, 1952.\n",
"So the final answer is: December 6, 1952\n",
"\n",
"\n",
"Question: Who was the maternal grandfather of George Washington?\n",
"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: Who was the mother of George Washington?\n",
"Intermediate answer: The mother of George Washington was Mary Ball Washington.\n",
"Follow up: Who was the father of Mary Ball Washington?\n",
"Intermediate answer: The father of Mary Ball Washington was Joseph Ball.\n",
"So the final answer is: Joseph Ball\n",
"\n",
"\n",
"Question: Are both the directors of Jaws and Casino Royale from the same country?\n",
"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: Who is the director of Jaws?\n",
"Intermediate Answer: The director of Jaws is Steven Spielberg.\n",
"Follow up: Where is Steven Spielberg from?\n",
"Intermediate Answer: The United States.\n",
"Follow up: Who is the director of Casino Royale?\n",
"Intermediate Answer: The director of Casino Royale is Martin Campbell.\n",
"Follow up: Where is Martin Campbell from?\n",
"Intermediate Answer: New Zealand.\n",
"So the final answer is: No\n",
"\n",
"\n",
"Question: Who was the father of Mary Ball Washington?\n"
]
}
],
"source": [
"from langchain.prompts.few_shot import FewShotPromptTemplate\n",
"\n",
"prompt = FewShotPromptTemplate(\n",
" examples=examples,\n",
" example_prompt=example_prompt,\n",
" suffix=\"Question: {input}\",\n",
" input_variables=[\"input\"],\n",
")\n",
"\n",
"print(\n",
" prompt.invoke({\"input\": \"Who was the father of Mary Ball Washington?\"}).to_string()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "59c6f332",
"metadata": {},
"source": [
"By providing the model with examples like this, we can guide the model to a better response."
]
},
{
"cell_type": "markdown",
"id": "bbe1f843",
"metadata": {},
"source": [
"## Using an example selector\n",
"\n",
"We will reuse the example set and the formatter from the previous section. However, instead of feeding the examples directly into the `FewShotPromptTemplate` object, we will feed them into an implementation of `ExampleSelector` called [`SemanticSimilarityExampleSelector`](https://api.python.langchain.com/en/latest/example_selectors/langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector.html) instance. This class selects few-shot examples from the initial set based on their similarity to the input. It uses an embedding model to compute the similarity between the input and the few-shot examples, as well as a vector store to perform the nearest neighbor search.\n",
"\n",
"To show what it looks like, let's initialize an instance and call it in isolation:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "80c5ac5c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Examples most similar to the input: Who was the father of Mary Ball Washington?\n",
"\n",
"\n",
"answer: \n",
"Are follow up questions needed here: Yes.\n",
"Follow up: Who was the mother of George Washington?\n",
"Intermediate answer: The mother of George Washington was Mary Ball Washington.\n",
"Follow up: Who was the father of Mary Ball Washington?\n",
"Intermediate answer: The father of Mary Ball Washington was Joseph Ball.\n",
"So the final answer is: Joseph Ball\n",
"\n",
"question: Who was the maternal grandfather of George Washington?\n"
]
}
],
"source": [
"from langchain.prompts.example_selector import SemanticSimilarityExampleSelector\n",
"from langchain_chroma import Chroma\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"example_selector = SemanticSimilarityExampleSelector.from_examples(\n",
" # This is the list of examples available to select from.\n",
" examples,\n",
" # This is the embedding class used to produce embeddings which are used to measure semantic similarity.\n",
" OpenAIEmbeddings(),\n",
" # This is the VectorStore class that is used to store the embeddings and do a similarity search over.\n",
" Chroma,\n",
" # This is the number of examples to produce.\n",
" k=1,\n",
")\n",
"\n",
"# Select the most similar example to the input.\n",
"question = \"Who was the father of Mary Ball Washington?\"\n",
"selected_examples = example_selector.select_examples({\"question\": question})\n",
"print(f\"Examples most similar to the input: {question}\")\n",
"for example in selected_examples:\n",
" print(\"\\n\")\n",
" for k, v in example.items():\n",
" print(f\"{k}: {v}\")"
]
},
{
"cell_type": "markdown",
"id": "89ac47fe",
"metadata": {},
"source": [
"Now, let's create a `FewShotPromptTemplate` object. This object takes in the example selector and the formatter prompt for the few-shot examples."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "de69a214",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Question: Who was the maternal grandfather of George Washington?\n",
"\n",
"Are follow up questions needed here: Yes.\n",
"Follow up: Who was the mother of George Washington?\n",
"Intermediate answer: The mother of George Washington was Mary Ball Washington.\n",
"Follow up: Who was the father of Mary Ball Washington?\n",
"Intermediate answer: The father of Mary Ball Washington was Joseph Ball.\n",
"So the final answer is: Joseph Ball\n",
"\n",
"\n",
"Question: Who was the father of Mary Ball Washington?\n"
]
}
],
"source": [
"prompt = FewShotPromptTemplate(\n",
" example_selector=example_selector,\n",
" example_prompt=example_prompt,\n",
" suffix=\"Question: {input}\",\n",
" input_variables=[\"input\"],\n",
")\n",
"\n",
"print(\n",
" prompt.invoke({\"input\": \"Who was the father of Mary Ball Washington?\"}).to_string()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "1b460794",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now learned how to add few-shot examples to your prompts.\n",
"\n",
"Next, check out the other how-to guides on prompt templates in this section, the related how-to guide on [few shotting with chat models](/docs/how_to/few_shot_examples_chat), or the other [example selector how-to guides](/docs/how_to/example_selectors/)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf06d2a6",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,443 @@
{
"cells": [
{
"cell_type": "raw",
"id": "beba2e0e",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 2\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "bb0735c0",
"metadata": {},
"source": [
"# How to use few shot examples in chat models\n",
"\n",
"This guide covers how to prompt a chat model with example inputs and outputs. Providing the model with a few such examples is called few-shotting, and is a simple yet powerful way to guide generation and in some cases drastically improve model performance.\n",
"\n",
"There does not appear to be solid consensus on how best to do few-shot prompting, and the optimal prompt compilation will likely vary by model. Because of this, we provide few-shot prompt templates like the [FewShotChatMessagePromptTemplate](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.few_shot.FewShotChatMessagePromptTemplate.html?highlight=fewshot#langchain_core.prompts.few_shot.FewShotChatMessagePromptTemplate) as a flexible starting point, and you can modify or replace them as you see fit.\n",
"\n",
"The goal of few-shot prompt templates are to dynamically select examples based on an input, and then format the examples in a final prompt to provide for the model.\n",
"\n",
"**Note:** The following code examples are for chat models only, since `FewShotChatMessagePromptTemplates` are designed to output formatted [chat messages](/docs/concepts/#message-types) rather than pure strings. For similar few-shot prompt examples for pure string templates compatible with completion models (LLMs), see the [few-shot prompt templates](/docs/how_to/few_shot_examples/) guide.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Prompt templates](/docs/concepts/#prompt-templates)\n",
"- [Example selectors](/docs/concepts/#example-selectors)\n",
"- [Chat models](/docs/concepts/#chat-model)\n",
"- [Vectorstores](/docs/concepts/#vectorstores)\n",
"`} />\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "d716f2de-cc29-4823-9360-a808c7bfdb86",
"metadata": {
"tags": []
},
"source": [
"## Fixed Examples\n",
"\n",
"The most basic (and common) few-shot prompting technique is to use fixed prompt examples. This way you can select a chain, evaluate it, and avoid worrying about additional moving parts in production.\n",
"\n",
"The basic components of the template are:\n",
"- `examples`: A list of dictionary examples to include in the final prompt.\n",
"- `example_prompt`: converts each example into 1 or more messages through its [`format_messages`](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.chat.ChatPromptTemplate.html?highlight=format_messages#langchain_core.prompts.chat.ChatPromptTemplate.format_messages) method. A common example would be to convert each example into one human message and one AI message response, or a human message followed by a function call message.\n",
"\n",
"Below is a simple demonstration. First, define the examples you'd like to include:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5b79e400",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARNING: You are using pip version 22.0.4; however, version 24.0 is available.\n",
"You should consider upgrading via the '/Users/jacoblee/.pyenv/versions/3.10.5/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n",
"\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install -qU langchain langchain-openai langchain-chroma\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "0fc5a02a-6249-4e92-95c3-30fff9671e8b",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain.prompts import (\n",
" ChatPromptTemplate,\n",
" FewShotChatMessagePromptTemplate,\n",
")\n",
"\n",
"examples = [\n",
" {\"input\": \"2+2\", \"output\": \"4\"},\n",
" {\"input\": \"2+3\", \"output\": \"5\"},\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "e8710ecc-2aa0-4172-a74c-250f6bc3d9e2",
"metadata": {},
"source": [
"Next, assemble them into the few-shot prompt template."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "65e72ad1-9060-47d0-91a1-bc130c8b98ac",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[HumanMessage(content='2+2'), AIMessage(content='4'), HumanMessage(content='2+3'), AIMessage(content='5')]\n"
]
}
],
"source": [
"# This is a prompt template used to format each individual example.\n",
"example_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"human\", \"{input}\"),\n",
" (\"ai\", \"{output}\"),\n",
" ]\n",
")\n",
"few_shot_prompt = FewShotChatMessagePromptTemplate(\n",
" example_prompt=example_prompt,\n",
" examples=examples,\n",
")\n",
"\n",
"print(few_shot_prompt.invoke({}).to_messages())"
]
},
{
"cell_type": "markdown",
"id": "5490bd59-b28f-46a4-bbdf-0191802dd3c5",
"metadata": {},
"source": [
"Finally, we assemble the final prompt as shown below, passing `few_shot_prompt` directly into the `from_messages` factory method, and use it with a model:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "9f86d6d9-50de-41b6-b6c7-0f9980cc0187",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"final_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", \"You are a wondrous wizard of math.\"),\n",
" few_shot_prompt,\n",
" (\"human\", \"{input}\"),\n",
" ]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "97d443b1-6fae-4b36-bede-3ff7306288a3",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='A triangle does not have a square. The square of a number is the result of multiplying the number by itself.', response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 52, 'total_tokens': 75}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-3456c4ef-7b4d-4adb-9e02-8079de82a47a-0')"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_openai import ChatOpenAI\n",
"\n",
"chain = final_prompt | ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0.0)\n",
"\n",
"chain.invoke({\"input\": \"What's the square of a triangle?\"})"
]
},
{
"cell_type": "markdown",
"id": "70ab7114-f07f-46be-8874-3705a25aba5f",
"metadata": {},
"source": [
"## Dynamic few-shot prompting\n",
"\n",
"Sometimes you may want to select only a few examples from your overall set to show based on the input. For this, you can replace the `examples` passed into `FewShotChatMessagePromptTemplate` with an `example_selector`. The other components remain the same as above! Our dynamic few-shot prompt template would look like:\n",
"\n",
"- `example_selector`: responsible for selecting few-shot examples (and the order in which they are returned) for a given input. These implement the [BaseExampleSelector](https://api.python.langchain.com/en/latest/example_selectors/langchain_core.example_selectors.base.BaseExampleSelector.html?highlight=baseexampleselector#langchain_core.example_selectors.base.BaseExampleSelector) interface. A common example is the vectorstore-backed [SemanticSimilarityExampleSelector](https://api.python.langchain.com/en/latest/example_selectors/langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector.html?highlight=semanticsimilarityexampleselector#langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector)\n",
"- `example_prompt`: convert each example into 1 or more messages through its [`format_messages`](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.chat.ChatPromptTemplate.html?highlight=chatprompttemplate#langchain_core.prompts.chat.ChatPromptTemplate.format_messages) method. A common example would be to convert each example into one human message and one AI message response, or a human message followed by a function call message.\n",
"\n",
"These once again can be composed with other messages and chat templates to assemble your final prompt.\n",
"\n",
"Let's walk through an example with the `SemanticSimilarityExampleSelector`. Since this implementation uses a vectorstore to select examples based on semantic similarity, we will want to first populate the store. Since the basic idea here is that we want to search for and return examples most similar to the text input, we embed the `values` of our prompt examples rather than considering the keys:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "ad66f06a-66fd-4fcc-8166-5d0e3c801e57",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain.prompts import SemanticSimilarityExampleSelector\n",
"from langchain_chroma import Chroma\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"examples = [\n",
" {\"input\": \"2+2\", \"output\": \"4\"},\n",
" {\"input\": \"2+3\", \"output\": \"5\"},\n",
" {\"input\": \"2+4\", \"output\": \"6\"},\n",
" {\"input\": \"What did the cow say to the moon?\", \"output\": \"nothing at all\"},\n",
" {\n",
" \"input\": \"Write me a poem about the moon\",\n",
" \"output\": \"One for the moon, and one for me, who are we to talk about the moon?\",\n",
" },\n",
"]\n",
"\n",
"to_vectorize = [\" \".join(example.values()) for example in examples]\n",
"embeddings = OpenAIEmbeddings()\n",
"vectorstore = Chroma.from_texts(to_vectorize, embeddings, metadatas=examples)"
]
},
{
"cell_type": "markdown",
"id": "2f7e384a-2031-432b-951c-7ea8cf9262f1",
"metadata": {},
"source": [
"### Create the `example_selector`\n",
"\n",
"With a vectorstore created, we can create the `example_selector`. Here we will call it in isolation, and set `k` on it to only fetch the two example closest to the input."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "7790303a-f722-452e-8921-b14bdf20bdff",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"[{'input': 'What did the cow say to the moon?', 'output': 'nothing at all'},\n",
" {'input': '2+4', 'output': '6'}]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"example_selector = SemanticSimilarityExampleSelector(\n",
" vectorstore=vectorstore,\n",
" k=2,\n",
")\n",
"\n",
"# The prompt template will load examples by passing the input do the `select_examples` method\n",
"example_selector.select_examples({\"input\": \"horse\"})"
]
},
{
"cell_type": "markdown",
"id": "cc77c40f-3f58-40a2-b757-a2a2ea43f24a",
"metadata": {},
"source": [
"### Create prompt template\n",
"\n",
"We now assemble the prompt template, using the `example_selector` created above."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "253c255e-41d7-45f6-9d88-c7a0ced4b1bd",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[HumanMessage(content='2+3'), AIMessage(content='5'), HumanMessage(content='2+2'), AIMessage(content='4')]\n"
]
}
],
"source": [
"from langchain.prompts import (\n",
" ChatPromptTemplate,\n",
" FewShotChatMessagePromptTemplate,\n",
")\n",
"\n",
"# Define the few-shot prompt.\n",
"few_shot_prompt = FewShotChatMessagePromptTemplate(\n",
" # The input variables select the values to pass to the example_selector\n",
" input_variables=[\"input\"],\n",
" example_selector=example_selector,\n",
" # Define how each example will be formatted.\n",
" # In this case, each example will become 2 messages:\n",
" # 1 human, and 1 AI\n",
" example_prompt=ChatPromptTemplate.from_messages(\n",
" [(\"human\", \"{input}\"), (\"ai\", \"{output}\")]\n",
" ),\n",
")\n",
"\n",
"print(few_shot_prompt.invoke(input=\"What's 3+3?\").to_messages())"
]
},
{
"cell_type": "markdown",
"id": "339cae7d-0eb0-44a6-852f-0267c5ff72b3",
"metadata": {},
"source": [
"And we can pass this few-shot chat message prompt template into another chat prompt template:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "e731cb45-f0ea-422c-be37-42af2a6cb2c4",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"messages=[HumanMessage(content='2+3'), AIMessage(content='5'), HumanMessage(content='2+2'), AIMessage(content='4')]\n"
]
}
],
"source": [
"final_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", \"You are a wondrous wizard of math.\"),\n",
" few_shot_prompt,\n",
" (\"human\", \"{input}\"),\n",
" ]\n",
")\n",
"\n",
"print(few_shot_prompt.invoke(input=\"What's 3+3?\"))"
]
},
{
"cell_type": "markdown",
"id": "2408ea69-1880-4ef5-a0fa-ffa8d2026aa9",
"metadata": {},
"source": [
"### Use with an chat model\n",
"\n",
"Finally, you can connect your model to the few-shot prompt."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "0568cbc6-5354-47f1-ab4d-dfcc616cf583",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='6', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 51, 'total_tokens': 52}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-6bcbe158-a8e3-4a85-a754-1ba274a9f147-0')"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain = final_prompt | ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0.0)\n",
"\n",
"chain.invoke({\"input\": \"What's 3+3?\"})"
]
},
{
"cell_type": "markdown",
"id": "c87fad3c",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now learned how to add few-shot examples to your chat prompts.\n",
"\n",
"Next, check out the other how-to guides on prompt templates in this section, the related how-to guide on [few shotting with text completion models](/docs/how_to/few_shot_examples), or the other [example selector how-to guides](/docs/how_to/example_selectors/)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "46e26b53",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,706 @@
{
"cells": [
{
"cell_type": "raw",
"id": "a413ade7-48f0-4d43-a1f3-d87f550a8018",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 2\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "50d59b14-c434-4359-be8e-4a21378e762f",
"metadata": {},
"source": [
"# How to do tool/function calling\n",
"\n",
"```{=mdx}\n",
":::info\n",
"We use the term tool calling interchangeably with function calling. Although\n",
"function calling is sometimes meant to refer to invocations of a single function,\n",
"we treat all models as though they can return multiple tool or function calls in \n",
"each message.\n",
":::\n",
"```\n",
"\n",
"Tool calling allows a model to respond to a given prompt by generating output that \n",
"matches a user-defined schema. While the name implies that the model is performing \n",
"some action, this is actually not the case! The model is coming up with the \n",
"arguments to a tool, and actually running the tool (or not) is up to the user - \n",
"for example, if you want to [extract output matching some schema](/docs/tutorials/extraction) \n",
"from unstructured text, you could give the model an \"extraction\" tool that takes \n",
"parameters matching the desired schema, then treat the generated output as your final \n",
"result.\n",
"\n",
"A tool call includes a name, arguments dict, and an optional identifier. The \n",
"arguments dict is structured `{argument_name: argument_value}`.\n",
"\n",
"Many LLM providers, including [Anthropic](https://www.anthropic.com/), \n",
"[Cohere](https://cohere.com/), [Google](https://cloud.google.com/vertex-ai), \n",
"[Mistral](https://mistral.ai/), [OpenAI](https://openai.com/), and others, \n",
"support variants of a tool calling feature. These features typically allow requests \n",
"to the LLM to include available tools and their schemas, and for responses to include \n",
"calls to these tools. For instance, given a search engine tool, an LLM might handle a \n",
"query by first issuing a call to the search engine. The system calling the LLM can \n",
"receive the tool call, execute it, and return the output to the LLM to inform its \n",
"response. LangChain includes a suite of [built-in tools](/docs/integrations/tools/) \n",
"and supports several methods for defining your own [custom tools](/docs/how_to/custom_tools). \n",
"Tool-calling is extremely useful for building [tool-using chains and agents](/docs/use_cases/tool_use), \n",
"and for getting structured outputs from models more generally.\n",
"\n",
"Providers adopt different conventions for formatting tool schemas and tool calls. \n",
"For instance, Anthropic returns tool calls as parsed structures within a larger content block:\n",
"```python\n",
"[\n",
" {\n",
" \"text\": \"<thinking>\\nI should use a tool.\\n</thinking>\",\n",
" \"type\": \"text\"\n",
" },\n",
" {\n",
" \"id\": \"id_value\",\n",
" \"input\": {\"arg_name\": \"arg_value\"},\n",
" \"name\": \"tool_name\",\n",
" \"type\": \"tool_use\"\n",
" }\n",
"]\n",
"```\n",
"whereas OpenAI separates tool calls into a distinct parameter, with arguments as JSON strings:\n",
"```python\n",
"{\n",
" \"tool_calls\": [\n",
" {\n",
" \"id\": \"id_value\",\n",
" \"function\": {\n",
" \"arguments\": '{\"arg_name\": \"arg_value\"}',\n",
" \"name\": \"tool_name\"\n",
" },\n",
" \"type\": \"function\"\n",
" }\n",
" ]\n",
"}\n",
"```\n",
"LangChain implements standard interfaces for defining tools, passing them to LLMs, \n",
"and representing tool calls.\n",
"\n",
"## Passing tools to LLMs\n",
"\n",
"Chat models supporting tool calling features implement a `.bind_tools` method, which \n",
"receives a list of LangChain [tool objects](https://api.python.langchain.com/en/latest/tools/langchain_core.tools.BaseTool.html#langchain_core.tools.BaseTool) \n",
"and binds them to the chat model in its expected format. Subsequent invocations of the \n",
"chat model will include tool schemas in its calls to the LLM.\n",
"\n",
"For example, we can define the schema for custom tools using the `@tool` decorator \n",
"on Python functions:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "841dca72-1b57-4a42-8e22-da4835c4cfe0",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.tools import tool\n",
"\n",
"\n",
"@tool\n",
"def add(a: int, b: int) -> int:\n",
" \"\"\"Adds a and b.\"\"\"\n",
" return a + b\n",
"\n",
"\n",
"@tool\n",
"def multiply(a: int, b: int) -> int:\n",
" \"\"\"Multiplies a and b.\"\"\"\n",
" return a * b\n",
"\n",
"\n",
"tools = [add, multiply]"
]
},
{
"cell_type": "markdown",
"id": "48058b7d-048d-48e6-a272-3931ad7ad146",
"metadata": {},
"source": [
"Or below, we define the schema using Pydantic:\n"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "fca56328-85e4-4839-97b7-b5dc55920602",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"\n",
"\n",
"# Note that the docstrings here are crucial, as they will be passed along\n",
"# to the model along with the class name.\n",
"class Add(BaseModel):\n",
" \"\"\"Add two integers together.\"\"\"\n",
"\n",
" a: int = Field(..., description=\"First integer\")\n",
" b: int = Field(..., description=\"Second integer\")\n",
"\n",
"\n",
"class Multiply(BaseModel):\n",
" \"\"\"Multiply two integers together.\"\"\"\n",
"\n",
" a: int = Field(..., description=\"First integer\")\n",
" b: int = Field(..., description=\"Second integer\")\n",
"\n",
"\n",
"tools = [Add, Multiply]"
]
},
{
"cell_type": "markdown",
"id": "ead9068d-11f6-42f3-a508-3c1830189947",
"metadata": {},
"source": [
"We can bind them to chat models as follows:\n",
"\n",
"```{=mdx}\n",
"import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
"\n",
"<ChatModelTabs\n",
" customVarName=\"llm\"\n",
" fireworksParams={`model=\"accounts/fireworks/models/firefunction-v1\", temperature=0`}\n",
"/>\n",
"```\n",
"\n",
"We can use the `bind_tools()` method to handle converting\n",
"`Multiply` to a \"tool\" and binding it to the model (i.e.,\n",
"passing it in each time the model is invoked)."
]
},
{
"cell_type": "code",
"execution_count": 67,
"id": "44eb8327-a03d-4c7c-945e-30f13f455346",
"metadata": {},
"outputs": [],
"source": [
"# | echo: false\n",
"# | output: false\n",
"\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)"
]
},
{
"cell_type": "code",
"execution_count": 68,
"id": "af2a83ac-e43f-43ce-b107-9ed8376bfb75",
"metadata": {},
"outputs": [],
"source": [
"llm_with_tools = llm.bind_tools(tools)"
]
},
{
"cell_type": "markdown",
"id": "16208230-f64f-4935-9aa1-280a91f34ba3",
"metadata": {},
"source": [
"## Tool calls\n",
"\n",
"If tool calls are included in a LLM response, they are attached to the corresponding \n",
"[message](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessage.html#langchain_core.messages.ai.AIMessage) \n",
"or [message chunk](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessageChunk.html#langchain_core.messages.ai.AIMessageChunk) \n",
"as a list of [tool call](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.tool.ToolCall.html#langchain_core.messages.tool.ToolCall) \n",
"objects in the `.tool_calls` attribute. A `ToolCall` is a typed dict that includes a \n",
"tool name, dict of argument values, and (optionally) an identifier. Messages with no \n",
"tool calls default to an empty list for this attribute.\n",
"\n",
"Example:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "1640a4b4-c201-4b23-b257-738d854fb9fd",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'name': 'Multiply',\n",
" 'args': {'a': 3, 'b': 12},\n",
" 'id': 'call_1Tdp5wUXbYQzpkBoagGXqUTo'},\n",
" {'name': 'Add',\n",
" 'args': {'a': 11, 'b': 49},\n",
" 'id': 'call_k9v09vYioS3X0Qg35zESuUKI'}]"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"query = \"What is 3 * 12? Also, what is 11 + 49?\"\n",
"\n",
"llm_with_tools.invoke(query).tool_calls"
]
},
{
"cell_type": "markdown",
"id": "ac3ff0fe-5119-46b8-a578-530245bff23f",
"metadata": {},
"source": [
"The `.tool_calls` attribute should contain valid tool calls. Note that on occasion, \n",
"model providers may output malformed tool calls (e.g., arguments that are not \n",
"valid JSON). When parsing fails in these cases, instances \n",
"of [InvalidToolCall](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.tool.InvalidToolCall.html#langchain_core.messages.tool.InvalidToolCall) \n",
"are populated in the `.invalid_tool_calls` attribute. An `InvalidToolCall` can have \n",
"a name, string arguments, identifier, and error message.\n",
"\n",
"If desired, [output parsers](/docs/modules/model_io/output_parsers) can further \n",
"process the output. For example, we can convert back to the original Pydantic class:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "ca15fcad-74fe-4109-a1b1-346c3eefe238",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Multiply(a=3, b=12), Add(a=11, b=49)]"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.output_parsers.openai_tools import PydanticToolsParser\n",
"\n",
"chain = llm_with_tools | PydanticToolsParser(tools=[Multiply, Add])\n",
"chain.invoke(query)"
]
},
{
"cell_type": "markdown",
"id": "0ba3505d-f405-43ba-93c4-7fbd84f6464b",
"metadata": {},
"source": [
"### Streaming\n",
"\n",
"When tools are called in a streaming context, \n",
"[message chunks](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessageChunk.html#langchain_core.messages.ai.AIMessageChunk) \n",
"will be populated with [tool call chunk](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.tool.ToolCallChunk.html#langchain_core.messages.tool.ToolCallChunk) \n",
"objects in a list via the `.tool_call_chunks` attribute. A `ToolCallChunk` includes \n",
"optional string fields for the tool `name`, `args`, and `id`, and includes an optional \n",
"integer field `index` that can be used to join chunks together. Fields are optional \n",
"because portions of a tool call may be streamed across different chunks (e.g., a chunk \n",
"that includes a substring of the arguments may have null values for the tool name and id).\n",
"\n",
"Because message chunks inherit from their parent message class, an \n",
"[AIMessageChunk](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessageChunk.html#langchain_core.messages.ai.AIMessageChunk) \n",
"with tool call chunks will also include `.tool_calls` and `.invalid_tool_calls` fields. \n",
"These fields are parsed best-effort from the message's tool call chunks.\n",
"\n",
"Note that not all providers currently support streaming for tool calls.\n",
"\n",
"Example:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "4f54a0de-74c7-4f2d-86c5-660aed23840d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[]\n",
"[{'name': 'Multiply', 'args': '', 'id': 'call_d39MsxKM5cmeGJOoYKdGBgzc', 'index': 0}]\n",
"[{'name': None, 'args': '{\"a\"', 'id': None, 'index': 0}]\n",
"[{'name': None, 'args': ': 3, ', 'id': None, 'index': 0}]\n",
"[{'name': None, 'args': '\"b\": 1', 'id': None, 'index': 0}]\n",
"[{'name': None, 'args': '2}', 'id': None, 'index': 0}]\n",
"[{'name': 'Add', 'args': '', 'id': 'call_QJpdxD9AehKbdXzMHxgDMMhs', 'index': 1}]\n",
"[{'name': None, 'args': '{\"a\"', 'id': None, 'index': 1}]\n",
"[{'name': None, 'args': ': 11,', 'id': None, 'index': 1}]\n",
"[{'name': None, 'args': ' \"b\": ', 'id': None, 'index': 1}]\n",
"[{'name': None, 'args': '49}', 'id': None, 'index': 1}]\n",
"[]\n"
]
}
],
"source": [
"async for chunk in llm_with_tools.astream(query):\n",
" print(chunk.tool_call_chunks)"
]
},
{
"cell_type": "markdown",
"id": "55046320-3466-4ec1-a1f8-336234ba9019",
"metadata": {},
"source": [
"Note that adding message chunks will merge their corresponding tool call chunks. This is the principle by which LangChain's various [tool output parsers](/docs/modules/model_io/output_parsers/types/openai_tools/) support streaming.\n",
"\n",
"For example, below we accumulate tool call chunks:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "0a944af0-eedd-43c8-8ff3-f4301f129d9b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[]\n",
"[{'name': 'Multiply', 'args': '', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}]\n",
"[{'name': 'Multiply', 'args': '{\"a\"', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, ', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 1', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}, {'name': 'Add', 'args': '', 'id': 'call_tYHYdEV2YBvzDcSCiFCExNvw', 'index': 1}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}, {'name': 'Add', 'args': '{\"a\"', 'id': 'call_tYHYdEV2YBvzDcSCiFCExNvw', 'index': 1}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}, {'name': 'Add', 'args': '{\"a\": 11,', 'id': 'call_tYHYdEV2YBvzDcSCiFCExNvw', 'index': 1}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}, {'name': 'Add', 'args': '{\"a\": 11, \"b\": ', 'id': 'call_tYHYdEV2YBvzDcSCiFCExNvw', 'index': 1}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}, {'name': 'Add', 'args': '{\"a\": 11, \"b\": 49}', 'id': 'call_tYHYdEV2YBvzDcSCiFCExNvw', 'index': 1}]\n",
"[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_erKtz8z3e681cmxYKbRof0NS', 'index': 0}, {'name': 'Add', 'args': '{\"a\": 11, \"b\": 49}', 'id': 'call_tYHYdEV2YBvzDcSCiFCExNvw', 'index': 1}]\n"
]
}
],
"source": [
"first = True\n",
"async for chunk in llm_with_tools.astream(query):\n",
" if first:\n",
" gathered = chunk\n",
" first = False\n",
" else:\n",
" gathered = gathered + chunk\n",
"\n",
" print(gathered.tool_call_chunks)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "db4e3e3a-3553-44dc-bd31-149c0981a06a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'str'>\n"
]
}
],
"source": [
"print(type(gathered.tool_call_chunks[0][\"args\"]))"
]
},
{
"cell_type": "markdown",
"id": "95e92826-6e55-4684-9498-556f357f73ac",
"metadata": {},
"source": [
"And below we accumulate tool calls to demonstrate partial parsing:"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "e9402bde-d4b5-4564-a99e-f88c9b46b28a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[]\n",
"[]\n",
"[{'name': 'Multiply', 'args': {}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3, 'b': 1}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}, {'name': 'Add', 'args': {}, 'id': 'call_UjSHJKROSAw2BDc8cp9cSv4i'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}, {'name': 'Add', 'args': {'a': 11}, 'id': 'call_UjSHJKROSAw2BDc8cp9cSv4i'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}, {'name': 'Add', 'args': {'a': 11}, 'id': 'call_UjSHJKROSAw2BDc8cp9cSv4i'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_UjSHJKROSAw2BDc8cp9cSv4i'}]\n",
"[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_BXqUtt6jYCwR1DguqpS2ehP0'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_UjSHJKROSAw2BDc8cp9cSv4i'}]\n"
]
}
],
"source": [
"first = True\n",
"async for chunk in llm_with_tools.astream(query):\n",
" if first:\n",
" gathered = chunk\n",
" first = False\n",
" else:\n",
" gathered = gathered + chunk\n",
"\n",
" print(gathered.tool_calls)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "8c2f21cc-0c6d-416a-871f-e854621c96e2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'dict'>\n"
]
}
],
"source": [
"print(type(gathered.tool_calls[0][\"args\"]))"
]
},
{
"cell_type": "markdown",
"id": "97a0c977-0c3c-4011-b49b-db98c609d0ce",
"metadata": {},
"source": [
"## Passing tool outputs to model\n",
"\n",
"If we're using the model-generated tool invocations to actually call tools and want to pass the tool results back to the model, we can do so using `ToolMessage`s."
]
},
{
"cell_type": "code",
"execution_count": 117,
"id": "48049192-be28-42ab-9a44-d897924e67cd",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content='What is 3 * 12? Also, what is 11 + 49?'),\n",
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_K5DsWEmgt6D08EI9AFu9NaL1', 'function': {'arguments': '{\"a\": 3, \"b\": 12}', 'name': 'Multiply'}, 'type': 'function'}, {'id': 'call_qywVrsplg0ZMv7LHYYMjyG81', 'function': {'arguments': '{\"a\": 11, \"b\": 49}', 'name': 'Add'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 50, 'prompt_tokens': 105, 'total_tokens': 155}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-1a0b8cdd-9221-4d94-b2ed-5701f67ce9fe-0', tool_calls=[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_K5DsWEmgt6D08EI9AFu9NaL1'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_qywVrsplg0ZMv7LHYYMjyG81'}]),\n",
" ToolMessage(content='36', tool_call_id='call_K5DsWEmgt6D08EI9AFu9NaL1'),\n",
" ToolMessage(content='60', tool_call_id='call_qywVrsplg0ZMv7LHYYMjyG81')]"
]
},
"execution_count": 117,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import HumanMessage, ToolMessage\n",
"\n",
"messages = [HumanMessage(query)]\n",
"ai_msg = llm_with_tools.invoke(messages)\n",
"messages.append(ai_msg)\n",
"for tool_call in ai_msg.tool_calls:\n",
" selected_tool = {\"add\": add, \"multiply\": multiply}[tool_call[\"name\"].lower()]\n",
" tool_output = selected_tool.invoke(tool_call[\"args\"])\n",
" messages.append(ToolMessage(tool_output, tool_call_id=tool_call[\"id\"]))\n",
"messages"
]
},
{
"cell_type": "code",
"execution_count": 118,
"id": "611e0f36-d736-48d1-bca1-1cec51d223f3",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='3 * 12 is 36 and 11 + 49 is 60.', response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 171, 'total_tokens': 189}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None}, id='run-a6c8093c-b16a-4c92-8308-7c9ac998118c-0')"
]
},
"execution_count": 118,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"llm_with_tools.invoke(messages)"
]
},
{
"cell_type": "markdown",
"id": "a5937498-d6fe-400a-b192-ef35c314168e",
"metadata": {},
"source": [
"## Few-shot prompting\n",
"\n",
"For more complex tool use it's very useful to add few-shot examples to the prompt. We can do this by adding `AIMessage`s with `ToolCall`s and corresponding `ToolMessage`s to our prompt.\n",
"\n",
"For example, even with some special instructions our model can get tripped up by order of operations:"
]
},
{
"cell_type": "code",
"execution_count": 112,
"id": "5ef2e7c3-0925-49da-ab8f-e42c4fa40f29",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'name': 'Multiply',\n",
" 'args': {'a': 119, 'b': 8},\n",
" 'id': 'call_Dl3FXRVkQCFW4sUNYOe4rFr7'},\n",
" {'name': 'Add',\n",
" 'args': {'a': 952, 'b': -20},\n",
" 'id': 'call_n03l4hmka7VZTCiP387Wud2C'}]"
]
},
"execution_count": 112,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"llm_with_tools.invoke(\n",
" \"Whats 119 times 8 minus 20. Don't do any math yourself, only use tools for math. Respect order of operations\"\n",
").tool_calls"
]
},
{
"cell_type": "markdown",
"id": "a5249069-b5f8-40ac-ae74-30d67c4e9168",
"metadata": {},
"source": [
"The model shouldn't be trying to add anything yet, since it technically can't know the results of 119 * 8 yet.\n",
"\n",
"By adding a prompt with some examples we can correct this behavior:"
]
},
{
"cell_type": "code",
"execution_count": 107,
"id": "7b2e8b19-270f-4e1a-8be7-7aad704c1cf4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'name': 'Multiply',\n",
" 'args': {'a': 119, 'b': 8},\n",
" 'id': 'call_MoSgwzIhPxhclfygkYaKIsGZ'}]"
]
},
"execution_count": 107,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import AIMessage\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.runnables import RunnablePassthrough\n",
"\n",
"examples = [\n",
" HumanMessage(\n",
" \"What's the product of 317253 and 128472 plus four\", name=\"example_user\"\n",
" ),\n",
" AIMessage(\n",
" \"\",\n",
" name=\"example_assistant\",\n",
" tool_calls=[\n",
" {\"name\": \"Multiply\", \"args\": {\"x\": 317253, \"y\": 128472}, \"id\": \"1\"}\n",
" ],\n",
" ),\n",
" ToolMessage(\"16505054784\", tool_call_id=\"1\"),\n",
" AIMessage(\n",
" \"\",\n",
" name=\"example_assistant\",\n",
" tool_calls=[{\"name\": \"Add\", \"args\": {\"x\": 16505054784, \"y\": 4}, \"id\": \"2\"}],\n",
" ),\n",
" ToolMessage(\"16505054788\", tool_call_id=\"2\"),\n",
" AIMessage(\n",
" \"The product of 317253 and 128472 plus four is 16505054788\",\n",
" name=\"example_assistant\",\n",
" ),\n",
"]\n",
"\n",
"system = \"\"\"You are bad at math but are an expert at using a calculator. \n",
"\n",
"Use past tool usage as an example of how to correctly use the tools.\"\"\"\n",
"few_shot_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", system),\n",
" *examples,\n",
" (\"human\", \"{query}\"),\n",
" ]\n",
")\n",
"\n",
"chain = {\"query\": RunnablePassthrough()} | few_shot_prompt | llm_with_tools\n",
"chain.invoke(\"Whats 119 times 8 minus 20\").tool_calls"
]
},
{
"cell_type": "markdown",
"id": "19160e3e-3eb5-4e9a-ae56-74a2dce0af32",
"metadata": {},
"source": [
"Seems like we get the correct output this time.\n",
"\n",
"Here's what the [LangSmith trace](https://smith.langchain.com/public/f70550a1-585f-4c9d-a643-13148ab1616f/r) looks like."
]
},
{
"cell_type": "markdown",
"id": "020cfd3b-0838-49d0-96bb-7cd919921833",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"- **Output parsing**: See [OpenAI Tools output\n",
" parsers](/docs/modules/model_io/output_parsers/types/openai_tools/)\n",
" and [OpenAI Functions output\n",
" parsers](/docs/modules/model_io/output_parsers/types/openai_functions/)\n",
" to learn about extracting the function calling API responses into\n",
" various formats.\n",
"- **Structured output chains**: [Some models have constructors](/docs/how_to/structured_output) that\n",
" handle creating a structured output chain for you.\n",
"- **Tool use**: See how to construct chains and agents that\n",
" call the invoked tools in [these\n",
" guides](/docs/use_cases/tool_use/)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,534 @@
{
"cells": [
{
"cell_type": "raw",
"id": "ce0e08fd",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 3\n",
"keywords: [RunnableLambda, LCEL]\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "fbc4bf6e",
"metadata": {},
"source": [
"# How to run custom functions\n",
"\n",
"You can use arbitrary functions as [Runnables](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable). This is useful for formatting or when you need functionality not provided by other LangChain components, and custom functions used as Runnables are called [`RunnableLambdas`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html).\n",
"\n",
"Note that all inputs to these functions need to be a SINGLE argument. If you have a function that accepts multiple arguments, you should write a wrapper that accepts a single dict input and unpacks it into multiple argument.\n",
"\n",
"This guide will cover:\n",
"\n",
"- How to explicitly create a runnable from a custom function using the `RunnableLambda` constructor and the convenience `@chain` decorator\n",
"- Coercion of custom functions into runnables when used in chains\n",
"- How to accept and use run metadata in your custom function\n",
"- How to stream with custom functions by having them return generators\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
"- [Chaining runnables](/docs/how_to/sequence/)\n",
"`} />\n",
"```\n",
"\n",
"## Using the constructor\n",
"\n",
"Below, we explicitly wrap our custom logic using the `RunnableLambda` constructor:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5c34d2af",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain langchain_openai\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "6bb221b3",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='3 + 9 equals 12.', response_metadata={'token_usage': {'completion_tokens': 8, 'prompt_tokens': 14, 'total_tokens': 22}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-73728de3-e483-49e3-ad54-51bd9570e71a-0')"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from operator import itemgetter\n",
"\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.runnables import RunnableLambda\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"\n",
"def length_function(text):\n",
" return len(text)\n",
"\n",
"\n",
"def _multiple_length_function(text1, text2):\n",
" return len(text1) * len(text2)\n",
"\n",
"\n",
"def multiple_length_function(_dict):\n",
" return _multiple_length_function(_dict[\"text1\"], _dict[\"text2\"])\n",
"\n",
"\n",
"model = ChatOpenAI()\n",
"\n",
"prompt = ChatPromptTemplate.from_template(\"what is {a} + {b}\")\n",
"\n",
"chain1 = prompt | model\n",
"\n",
"chain = (\n",
" {\n",
" \"a\": itemgetter(\"foo\") | RunnableLambda(length_function),\n",
" \"b\": {\"text1\": itemgetter(\"foo\"), \"text2\": itemgetter(\"bar\")}\n",
" | RunnableLambda(multiple_length_function),\n",
" }\n",
" | prompt\n",
" | model\n",
")\n",
"\n",
"chain.invoke({\"foo\": \"bar\", \"bar\": \"gah\"})"
]
},
{
"cell_type": "markdown",
"id": "b7926002",
"metadata": {},
"source": [
"## The convenience `@chain` decorator\n",
"\n",
"You can also turn an arbitrary function into a chain by adding a `@chain` decorator. This is functionaly equivalent to wrapping the function in a `RunnableLambda` constructor as shown above. Here's an example:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "3142a516",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The subject of the joke is the bear and his girlfriend.'"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.runnables import chain\n",
"\n",
"prompt1 = ChatPromptTemplate.from_template(\"Tell me a joke about {topic}\")\n",
"prompt2 = ChatPromptTemplate.from_template(\"What is the subject of this joke: {joke}\")\n",
"\n",
"\n",
"@chain\n",
"def custom_chain(text):\n",
" prompt_val1 = prompt1.invoke({\"topic\": text})\n",
" output1 = ChatOpenAI().invoke(prompt_val1)\n",
" parsed_output1 = StrOutputParser().invoke(output1)\n",
" chain2 = prompt2 | ChatOpenAI() | StrOutputParser()\n",
" return chain2.invoke({\"joke\": parsed_output1})\n",
"\n",
"\n",
"custom_chain.invoke(\"bears\")"
]
},
{
"cell_type": "markdown",
"id": "4728ddd9-914d-42ce-ae9b-72c9ce8ec940",
"metadata": {},
"source": [
"Above, the `@chain` decorator is used to convert `custom_chain` into a runnable, which we invoke with the `.invoke()` method.\n",
"\n",
"If you are using a tracing with [LangSmith](/docs/langsmith/), you should see a `custom_chain` trace in there, with the calls to OpenAI nested underneath.\n",
"\n",
"## Automatic coercion in chains\n",
"\n",
"When using custom functions in chains with the pipe operator (`|`), you can omit the `RunnableLambda` or `@chain` constructor and rely on coercion. Here's a simple example with a function that takes the output from the model and returns the first five letters of it:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "5ab39a87",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Once '"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prompt = ChatPromptTemplate.from_template(\"tell me a story about {topic}\")\n",
"\n",
"model = ChatOpenAI()\n",
"\n",
"chain_with_coerced_function = prompt | model | (lambda x: x.content[:5])\n",
"\n",
"chain_with_coerced_function.invoke({\"topic\": \"bears\"})"
]
},
{
"cell_type": "markdown",
"id": "c9a481d1",
"metadata": {},
"source": [
"Note that we didn't need to wrap the custom function `(lambda x: x.content[:5])` in a `RunnableLambda` constructor because the `model` on the left of the pipe operator is already a Runnable. The custom function is **coerced** into a runnable. See [this section](/docs/how_to/sequence/#coercion) for more information.\n",
"\n",
"## Passing run metadata\n",
"\n",
"Runnable lambdas can optionally accept a [RunnableConfig](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig) parameter, which they can use to pass callbacks, tags, and other configuration information to nested runs."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ff0daf0c-49dd-4d21-9772-e5fa133c5f36",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'foo': 'bar'}\n",
"Tokens Used: 62\n",
"\tPrompt Tokens: 56\n",
"\tCompletion Tokens: 6\n",
"Successful Requests: 1\n",
"Total Cost (USD): $9.6e-05\n"
]
}
],
"source": [
"import json\n",
"\n",
"from langchain_core.runnables import RunnableConfig\n",
"\n",
"\n",
"def parse_or_fix(text: str, config: RunnableConfig):\n",
" fixing_chain = (\n",
" ChatPromptTemplate.from_template(\n",
" \"Fix the following text:\\n\\n```text\\n{input}\\n```\\nError: {error}\"\n",
" \" Don't narrate, just respond with the fixed data.\"\n",
" )\n",
" | model\n",
" | StrOutputParser()\n",
" )\n",
" for _ in range(3):\n",
" try:\n",
" return json.loads(text)\n",
" except Exception as e:\n",
" text = fixing_chain.invoke({\"input\": text, \"error\": e}, config)\n",
" return \"Failed to parse\"\n",
"\n",
"\n",
"from langchain_community.callbacks import get_openai_callback\n",
"\n",
"with get_openai_callback() as cb:\n",
" output = RunnableLambda(parse_or_fix).invoke(\n",
" \"{foo: bar}\", {\"tags\": [\"my-tag\"], \"callbacks\": [cb]}\n",
" )\n",
" print(output)\n",
" print(cb)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "1a5e709e-9d75-48c7-bb9c-503251990505",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'foo': 'bar'}\n",
"Tokens Used: 62\n",
"\tPrompt Tokens: 56\n",
"\tCompletion Tokens: 6\n",
"Successful Requests: 1\n",
"Total Cost (USD): $9.6e-05\n"
]
}
],
"source": [
"from langchain_community.callbacks import get_openai_callback\n",
"\n",
"with get_openai_callback() as cb:\n",
" output = RunnableLambda(parse_or_fix).invoke(\n",
" \"{foo: bar}\", {\"tags\": [\"my-tag\"], \"callbacks\": [cb]}\n",
" )\n",
" print(output)\n",
" print(cb)"
]
},
{
"cell_type": "markdown",
"id": "922b48bd",
"metadata": {},
"source": [
"# Streaming\n",
"\n",
"You can use generator functions (ie. functions that use the `yield` keyword, and behave like iterators) in a chain.\n",
"\n",
"The signature of these generators should be `Iterator[Input] -> Iterator[Output]`. Or for async generators: `AsyncIterator[Input] -> AsyncIterator[Output]`.\n",
"\n",
"These are useful for:\n",
"- implementing a custom output parser\n",
"- modifying the output of a previous step, while preserving streaming capabilities\n",
"\n",
"Here's an example of a custom output parser for comma-separated lists. First, we create a chain that generates such a list as text:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "29f55c38",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"lion, tiger, wolf, gorilla, panda"
]
}
],
"source": [
"from typing import Iterator, List\n",
"\n",
"prompt = ChatPromptTemplate.from_template(\n",
" \"Write a comma-separated list of 5 animals similar to: {animal}. Do not include numbers\"\n",
")\n",
"\n",
"str_chain = prompt | model | StrOutputParser()\n",
"\n",
"for chunk in str_chain.stream({\"animal\": \"bear\"}):\n",
" print(chunk, end=\"\", flush=True)"
]
},
{
"cell_type": "markdown",
"id": "46345323",
"metadata": {},
"source": [
"Next, we define a custom function that will aggregate the currently streamed output and yield it when the model generates the next comma in the list:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "f08b8a5b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['lion']\n",
"['tiger']\n",
"['wolf']\n",
"['gorilla']\n",
"['raccoon']\n"
]
}
],
"source": [
"# This is a custom parser that splits an iterator of llm tokens\n",
"# into a list of strings separated by commas\n",
"def split_into_list(input: Iterator[str]) -> Iterator[List[str]]:\n",
" # hold partial input until we get a comma\n",
" buffer = \"\"\n",
" for chunk in input:\n",
" # add current chunk to buffer\n",
" buffer += chunk\n",
" # while there are commas in the buffer\n",
" while \",\" in buffer:\n",
" # split buffer on comma\n",
" comma_index = buffer.index(\",\")\n",
" # yield everything before the comma\n",
" yield [buffer[:comma_index].strip()]\n",
" # save the rest for the next iteration\n",
" buffer = buffer[comma_index + 1 :]\n",
" # yield the last chunk\n",
" yield [buffer.strip()]\n",
"\n",
"\n",
"list_chain = str_chain | split_into_list\n",
"\n",
"for chunk in list_chain.stream({\"animal\": \"bear\"}):\n",
" print(chunk, flush=True)"
]
},
{
"cell_type": "markdown",
"id": "0a5adb69",
"metadata": {},
"source": [
"Invoking it gives a full array of values:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "9ea4ddc6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['lion', 'tiger', 'wolf', 'gorilla', 'raccoon']"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list_chain.invoke({\"animal\": \"bear\"})"
]
},
{
"cell_type": "markdown",
"id": "96e320ed",
"metadata": {},
"source": [
"## Async version\n",
"\n",
"If you are working in an `async` environment, here is an `async` version of the above example:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "569dbbef",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['lion']\n",
"['tiger']\n",
"['wolf']\n",
"['gorilla']\n",
"['panda']\n"
]
}
],
"source": [
"from typing import AsyncIterator\n",
"\n",
"\n",
"async def asplit_into_list(\n",
" input: AsyncIterator[str],\n",
") -> AsyncIterator[List[str]]: # async def\n",
" buffer = \"\"\n",
" async for (\n",
" chunk\n",
" ) in input: # `input` is a `async_generator` object, so use `async for`\n",
" buffer += chunk\n",
" while \",\" in buffer:\n",
" comma_index = buffer.index(\",\")\n",
" yield [buffer[:comma_index].strip()]\n",
" buffer = buffer[comma_index + 1 :]\n",
" yield [buffer.strip()]\n",
"\n",
"\n",
"list_chain = str_chain | asplit_into_list\n",
"\n",
"async for chunk in list_chain.astream({\"animal\": \"bear\"}):\n",
" print(chunk, flush=True)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "3a650482",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['lion', 'tiger', 'wolf', 'gorilla', 'panda']"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"await list_chain.ainvoke({\"animal\": \"bear\"})"
]
},
{
"cell_type": "markdown",
"id": "3306ac3b",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"Now you've learned a few different ways to use custom logic within your chains, and how to implement streaming.\n",
"\n",
"To learn more, see the other how-to guides on runnables in this section."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,261 @@
{
"cells": [
{
"cell_type": "raw",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 4\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to construct knowledge graphs\n",
"\n",
"In this guide we'll go over the basic ways of constructing a knowledge graph based on unstructured text. The constructured graph can then be used as knowledge base in a RAG application.\n",
"\n",
"## ⚠️ Security note ⚠️\n",
"\n",
"Constructing knowledge graphs requires executing write access to the database. There are inherent risks in doing this. Make sure that you verify and validate data before importing it. For more on general security best practices, [see here](/docs/security).\n",
"\n",
"\n",
"## Architecture\n",
"\n",
"At a high-level, the steps of constructing a knowledge are from text are:\n",
"\n",
"1. **Extracting structured information from text**: Model is used to extract structured graph information from text.\n",
"2. **Storing into graph database**: Storing the extracted structured graph information into a graph database enables downstream RAG applications\n",
"\n",
"## Setup\n",
"\n",
"First, get required packages and set environment variables.\n",
"In this example, we will be using Neo4j graph database."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Note: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install --upgrade --quiet langchain langchain-community langchain-openai langchain-experimental neo4j"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We default to OpenAI models in this guide."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" ········\n"
]
}
],
"source": [
"import getpass\n",
"import os\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n",
"\n",
"# Uncomment the below to use LangSmith. Not required.\n",
"# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n",
"# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we need to define Neo4j credentials and connection.\n",
"Follow [these installation steps](https://neo4j.com/docs/operations-manual/current/installation/) to set up a Neo4j database."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"from langchain_community.graphs import Neo4jGraph\n",
"\n",
"os.environ[\"NEO4J_URI\"] = \"bolt://localhost:7687\"\n",
"os.environ[\"NEO4J_USERNAME\"] = \"neo4j\"\n",
"os.environ[\"NEO4J_PASSWORD\"] = \"password\"\n",
"\n",
"graph = Neo4jGraph()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## LLM Graph Transformer\n",
"\n",
"Extracting graph data from text enables the transformation of unstructured information into structured formats, facilitating deeper insights and more efficient navigation through complex relationships and patterns. The `LLMGraphTransformer` converts text documents into structured graph documents by leveraging a LLM to parse and categorize entities and their relationships. The selection of the LLM model significantly influences the output by determining the accuracy and nuance of the extracted graph data.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"from langchain_experimental.graph_transformers import LLMGraphTransformer\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(temperature=0, model_name=\"gpt-4-0125-preview\")\n",
"\n",
"llm_transformer = LLMGraphTransformer(llm=llm)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can pass in example text and examine the results."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Nodes:[Node(id='Marie Curie', type='Person'), Node(id='Polish', type='Nationality'), Node(id='French', type='Nationality'), Node(id='Physicist', type='Occupation'), Node(id='Chemist', type='Occupation'), Node(id='Radioactivity', type='Field'), Node(id='Nobel Prize', type='Award'), Node(id='Pierre Curie', type='Person'), Node(id='University Of Paris', type='Organization')]\n",
"Relationships:[Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='Polish', type='Nationality'), type='NATIONALITY'), Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='French', type='Nationality'), type='NATIONALITY'), Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='Physicist', type='Occupation'), type='OCCUPATION'), Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='Chemist', type='Occupation'), type='OCCUPATION'), Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='Radioactivity', type='Field'), type='RESEARCH_FIELD'), Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='Nobel Prize', type='Award'), type='AWARD_WINNER'), Relationship(source=Node(id='Pierre Curie', type='Person'), target=Node(id='Nobel Prize', type='Award'), type='AWARD_WINNER'), Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='University Of Paris', type='Organization'), type='PROFESSOR')]\n"
]
}
],
"source": [
"from langchain_core.documents import Document\n",
"\n",
"text = \"\"\"\n",
"Marie Curie, was a Polish and naturalised-French physicist and chemist who conducted pioneering research on radioactivity.\n",
"She was the first woman to win a Nobel Prize, the first person to win a Nobel Prize twice, and the only person to win a Nobel Prize in two scientific fields.\n",
"Her husband, Pierre Curie, was a co-winner of her first Nobel Prize, making them the first-ever married couple to win the Nobel Prize and launching the Curie family legacy of five Nobel Prizes.\n",
"She was, in 1906, the first woman to become a professor at the University of Paris.\n",
"\"\"\"\n",
"documents = [Document(page_content=text)]\n",
"graph_documents = llm_transformer.convert_to_graph_documents(documents)\n",
"print(f\"Nodes:{graph_documents[0].nodes}\")\n",
"print(f\"Relationships:{graph_documents[0].relationships}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Examine the following image to better grasp the structure of the generated knowledge graph. \n",
"\n",
"![graph_construction1.png](/static/img/graph_construction1.png)\n",
"\n",
"Note that the graph construction process is non-deterministic since we are using LLM. Therefore, you might get slightly different results on each execution.\n",
"\n",
"Additionally, you have the flexibility to define specific types of nodes and relationships for extraction according to your requirements."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Nodes:[Node(id='Marie Curie', type='Person'), Node(id='Polish', type='Country'), Node(id='French', type='Country'), Node(id='Pierre Curie', type='Person'), Node(id='University Of Paris', type='Organization')]\n",
"Relationships:[Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='Polish', type='Country'), type='NATIONALITY'), Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='French', type='Country'), type='NATIONALITY'), Relationship(source=Node(id='Pierre Curie', type='Person'), target=Node(id='Marie Curie', type='Person'), type='SPOUSE'), Relationship(source=Node(id='Marie Curie', type='Person'), target=Node(id='University Of Paris', type='Organization'), type='WORKED_AT')]\n"
]
}
],
"source": [
"llm_transformer_filtered = LLMGraphTransformer(\n",
" llm=llm,\n",
" allowed_nodes=[\"Person\", \"Country\", \"Organization\"],\n",
" allowed_relationships=[\"NATIONALITY\", \"LOCATED_IN\", \"WORKED_AT\", \"SPOUSE\"],\n",
")\n",
"graph_documents_filtered = llm_transformer_filtered.convert_to_graph_documents(\n",
" documents\n",
")\n",
"print(f\"Nodes:{graph_documents_filtered[0].nodes}\")\n",
"print(f\"Relationships:{graph_documents_filtered[0].relationships}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For a better understanding of the generated graph, we can again visualize it.\n",
"\n",
"![graph_construction2.png](/static/img/graph_construction2.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Storing to graph database\n",
"\n",
"The generated graph documents can be stored to a graph database using the `add_graph_documents` method."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"graph.add_graph_documents(graph_documents_filtered)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,474 @@
{
"cells": [
{
"cell_type": "raw",
"id": "5e61b0f2-15b9-4241-9ab5-ff0f3f732232",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 1\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "846ef4f4-ee38-4a42-a7d3-1a23826e4830",
"metadata": {},
"source": [
"# How to map values to a graph database\n",
"\n",
"In this guide we'll go over strategies to improve graph database query generation by mapping values from user inputs to database.\n",
"When using the built-in graph chains, the LLM is aware of the graph schema, but has no information about the values of properties stored in the database.\n",
"Therefore, we can introduce a new step in graph database QA system to accurately map values.\n",
"\n",
"## Setup\n",
"\n",
"First, get required packages and set environment variables:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "18294435-182d-48da-bcab-5b8945b6d9cf",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Note: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install --upgrade --quiet langchain langchain-community langchain-openai neo4j"
]
},
{
"cell_type": "markdown",
"id": "d86dd771-4001-4a34-8680-22e9b50e1e88",
"metadata": {},
"source": [
"We default to OpenAI models in this guide, but you can swap them out for the model provider of your choice."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "9346f8e9-78bf-4667-b3d3-72807a73b718",
"metadata": {},
"outputs": [
{
"name": "stdin",
"output_type": "stream",
"text": [
" ········\n"
]
}
],
"source": [
"import getpass\n",
"import os\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n",
"\n",
"# Uncomment the below to use LangSmith. Not required.\n",
"# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n",
"# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\""
]
},
{
"cell_type": "markdown",
"id": "271c8a23-e51c-4ead-a76e-cf21107db47e",
"metadata": {},
"source": [
"Next, we need to define Neo4j credentials.\n",
"Follow [these installation steps](https://neo4j.com/docs/operations-manual/current/installation/) to set up a Neo4j database."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "a2a3bb65-05c7-4daf-bac2-b25ae7fe2751",
"metadata": {},
"outputs": [],
"source": [
"os.environ[\"NEO4J_URI\"] = \"bolt://localhost:7687\"\n",
"os.environ[\"NEO4J_USERNAME\"] = \"neo4j\"\n",
"os.environ[\"NEO4J_PASSWORD\"] = \"password\""
]
},
{
"cell_type": "markdown",
"id": "50fa4510-29b7-49b6-8496-5e86f694e81f",
"metadata": {},
"source": [
"The below example will create a connection with a Neo4j database and will populate it with example data about movies and their actors."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4ee9ef7a-eef9-4289-b9fd-8fbc31041688",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_community.graphs import Neo4jGraph\n",
"\n",
"graph = Neo4jGraph()\n",
"\n",
"# Import movie information\n",
"\n",
"movies_query = \"\"\"\n",
"LOAD CSV WITH HEADERS FROM \n",
"'https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/movies/movies_small.csv'\n",
"AS row\n",
"MERGE (m:Movie {id:row.movieId})\n",
"SET m.released = date(row.released),\n",
" m.title = row.title,\n",
" m.imdbRating = toFloat(row.imdbRating)\n",
"FOREACH (director in split(row.director, '|') | \n",
" MERGE (p:Person {name:trim(director)})\n",
" MERGE (p)-[:DIRECTED]->(m))\n",
"FOREACH (actor in split(row.actors, '|') | \n",
" MERGE (p:Person {name:trim(actor)})\n",
" MERGE (p)-[:ACTED_IN]->(m))\n",
"FOREACH (genre in split(row.genres, '|') | \n",
" MERGE (g:Genre {name:trim(genre)})\n",
" MERGE (m)-[:IN_GENRE]->(g))\n",
"\"\"\"\n",
"\n",
"graph.query(movies_query)"
]
},
{
"cell_type": "markdown",
"id": "0cb0ea30-ca55-4f35-aad6-beb57453de66",
"metadata": {},
"source": [
"## Detecting entities in the user input\n",
"We have to extract the types of entities/values we want to map to a graph database. In this example, we are dealing with a movie graph, so we can map movies and people to the database."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e1a19424-6046-40c2-81d1-f3b88193a293",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/tomazbratanic/anaconda3/lib/python3.11/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `create_structured_output_chain` was deprecated in LangChain 0.1.1 and will be removed in 0.2.0. Use create_structured_output_runnable instead.\n",
" warn_deprecated(\n"
]
}
],
"source": [
"from typing import List, Optional\n",
"\n",
"from langchain.chains.openai_functions import create_structured_output_chain\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n",
"\n",
"\n",
"class Entities(BaseModel):\n",
" \"\"\"Identifying information about entities.\"\"\"\n",
"\n",
" names: List[str] = Field(\n",
" ...,\n",
" description=\"All the person or movies appearing in the text\",\n",
" )\n",
"\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are extracting person and movies from the text.\",\n",
" ),\n",
" (\n",
" \"human\",\n",
" \"Use the given format to extract information from the following \"\n",
" \"input: {question}\",\n",
" ),\n",
" ]\n",
")\n",
"\n",
"\n",
"entity_chain = create_structured_output_chain(Entities, llm, prompt)"
]
},
{
"cell_type": "markdown",
"id": "9c14084c-37a7-4a9c-a026-74e12961c781",
"metadata": {},
"source": [
"We can test the entity extraction chain."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "bbfe0d8f-982e-46e6-88fb-8a4f0d850b07",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'question': 'Who played in Casino movie?',\n",
" 'function': Entities(names=['Casino'])}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"entities = entity_chain.invoke({\"question\": \"Who played in Casino movie?\"})\n",
"entities"
]
},
{
"cell_type": "markdown",
"id": "a8afbf13-05d0-4383-8050-f88b8c2f6fab",
"metadata": {},
"source": [
"We will utilize a simple `CONTAINS` clause to match entities to database. In practice, you might want to use a fuzzy search or a fulltext index to allow for minor misspellings."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "6f92929f-74fb-4db2-b7e1-eb1e9d386a67",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Casino maps to Casino Movie in database\\n'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"match_query = \"\"\"MATCH (p:Person|Movie)\n",
"WHERE p.name CONTAINS $value OR p.title CONTAINS $value\n",
"RETURN coalesce(p.name, p.title) AS result, labels(p)[0] AS type\n",
"LIMIT 1\n",
"\"\"\"\n",
"\n",
"\n",
"def map_to_database(values):\n",
" result = \"\"\n",
" for entity in values.names:\n",
" response = graph.query(match_query, {\"value\": entity})\n",
" try:\n",
" result += f\"{entity} maps to {response[0]['result']} {response[0]['type']} in database\\n\"\n",
" except IndexError:\n",
" pass\n",
" return result\n",
"\n",
"\n",
"map_to_database(entities[\"function\"])"
]
},
{
"cell_type": "markdown",
"id": "f66c6756-6efb-4b1e-9b5d-87ed914a5212",
"metadata": {},
"source": [
"## Custom Cypher generating chain\n",
"\n",
"We need to define a custom Cypher prompt that takes the entity mapping information along with the schema and the user question to construct a Cypher statement.\n",
"We will be using the LangChain expression language to accomplish that."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8ef3e21d-f1c2-45e2-9511-4920d1cf6e7e",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.runnables import RunnablePassthrough\n",
"\n",
"# Generate Cypher statement based on natural language input\n",
"cypher_template = \"\"\"Based on the Neo4j graph schema below, write a Cypher query that would answer the user's question:\n",
"{schema}\n",
"Entities in the question map to the following database values:\n",
"{entities_list}\n",
"Question: {question}\n",
"Cypher query:\"\"\" # noqa: E501\n",
"\n",
"cypher_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"Given an input question, convert it to a Cypher query. No pre-amble.\",\n",
" ),\n",
" (\"human\", cypher_template),\n",
" ]\n",
")\n",
"\n",
"cypher_response = (\n",
" RunnablePassthrough.assign(names=entity_chain)\n",
" | RunnablePassthrough.assign(\n",
" entities_list=lambda x: map_to_database(x[\"names\"][\"function\"]),\n",
" schema=lambda _: graph.get_schema,\n",
" )\n",
" | cypher_prompt\n",
" | llm.bind(stop=[\"\\nCypherResult:\"])\n",
" | StrOutputParser()\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "1f0011e3-9660-4975-af2a-486b1bc3b954",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'MATCH (:Movie {title: \"Casino\"})<-[:ACTED_IN]-(actor)\\nRETURN actor.name'"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cypher = cypher_response.invoke({\"question\": \"Who played in Casino movie?\"})\n",
"cypher"
]
},
{
"cell_type": "markdown",
"id": "38095678-611f-4847-a4de-e51ef7ef727c",
"metadata": {},
"source": [
"## Generating answers based on database results\n",
"\n",
"Now that we have a chain that generates the Cypher statement, we need to execute the Cypher statement against the database and send the database results back to an LLM to generate the final answer.\n",
"Again, we will be using LCEL."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "d1fa97c0-1c9c-41d3-9ee1-5f1905d17434",
"metadata": {},
"outputs": [],
"source": [
"from langchain.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema\n",
"\n",
"# Cypher validation tool for relationship directions\n",
"corrector_schema = [\n",
" Schema(el[\"start\"], el[\"type\"], el[\"end\"])\n",
" for el in graph.structured_schema.get(\"relationships\")\n",
"]\n",
"cypher_validation = CypherQueryCorrector(corrector_schema)\n",
"\n",
"# Generate natural language response based on database results\n",
"response_template = \"\"\"Based on the the question, Cypher query, and Cypher response, write a natural language response:\n",
"Question: {question}\n",
"Cypher query: {query}\n",
"Cypher Response: {response}\"\"\" # noqa: E501\n",
"\n",
"response_prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"Given an input question and Cypher response, convert it to a natural\"\n",
" \" language answer. No pre-amble.\",\n",
" ),\n",
" (\"human\", response_template),\n",
" ]\n",
")\n",
"\n",
"chain = (\n",
" RunnablePassthrough.assign(query=cypher_response)\n",
" | RunnablePassthrough.assign(\n",
" response=lambda x: graph.query(cypher_validation(x[\"query\"])),\n",
" )\n",
" | response_prompt\n",
" | llm\n",
" | StrOutputParser()\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "918146e5-7918-46d2-a774-53f9547d8fcb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Joe Pesci, Robert De Niro, Sharon Stone, and James Woods played in the movie \"Casino\".'"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain.invoke({\"question\": \"Who played in Casino movie?\"})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c7ba75cd-8399-4e54-a6f8-8a411f159f56",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,540 @@
{
"cells": [
{
"cell_type": "raw",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 2\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to best prompt for Graph-RAG\n",
"\n",
"In this guide we'll go over prompting strategies to improve graph database query generation. We'll largely focus on methods for getting relevant database-specific information in your prompt.\n",
"\n",
"## Setup\n",
"\n",
"First, get required packages and set environment variables:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Note: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install --upgrade --quiet langchain langchain-community langchain-openai neo4j"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We default to OpenAI models in this guide, but you can swap them out for the model provider of your choice."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdin",
"output_type": "stream",
"text": [
" ········\n"
]
}
],
"source": [
"import getpass\n",
"import os\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n",
"\n",
"# Uncomment the below to use LangSmith. Not required.\n",
"# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n",
"# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we need to define Neo4j credentials.\n",
"Follow [these installation steps](https://neo4j.com/docs/operations-manual/current/installation/) to set up a Neo4j database."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"os.environ[\"NEO4J_URI\"] = \"bolt://localhost:7687\"\n",
"os.environ[\"NEO4J_USERNAME\"] = \"neo4j\"\n",
"os.environ[\"NEO4J_PASSWORD\"] = \"password\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The below example will create a connection with a Neo4j database and will populate it with example data about movies and their actors."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_community.graphs import Neo4jGraph\n",
"\n",
"graph = Neo4jGraph()\n",
"\n",
"# Import movie information\n",
"\n",
"movies_query = \"\"\"\n",
"LOAD CSV WITH HEADERS FROM \n",
"'https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/movies/movies_small.csv'\n",
"AS row\n",
"MERGE (m:Movie {id:row.movieId})\n",
"SET m.released = date(row.released),\n",
" m.title = row.title,\n",
" m.imdbRating = toFloat(row.imdbRating)\n",
"FOREACH (director in split(row.director, '|') | \n",
" MERGE (p:Person {name:trim(director)})\n",
" MERGE (p)-[:DIRECTED]->(m))\n",
"FOREACH (actor in split(row.actors, '|') | \n",
" MERGE (p:Person {name:trim(actor)})\n",
" MERGE (p)-[:ACTED_IN]->(m))\n",
"FOREACH (genre in split(row.genres, '|') | \n",
" MERGE (g:Genre {name:trim(genre)})\n",
" MERGE (m)-[:IN_GENRE]->(g))\n",
"\"\"\"\n",
"\n",
"graph.query(movies_query)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Filtering graph schema\n",
"\n",
"At times, you may need to focus on a specific subset of the graph schema while generating Cypher statements.\n",
"Let's say we are dealing with the following graph schema:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Node properties are the following:\n",
"Movie {imdbRating: FLOAT, id: STRING, released: DATE, title: STRING},Person {name: STRING},Genre {name: STRING}\n",
"Relationship properties are the following:\n",
"\n",
"The relationships are the following:\n",
"(:Movie)-[:IN_GENRE]->(:Genre),(:Person)-[:DIRECTED]->(:Movie),(:Person)-[:ACTED_IN]->(:Movie)\n"
]
}
],
"source": [
"graph.refresh_schema()\n",
"print(graph.schema)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's say we want to exclude the _Genre_ node from the schema representation we pass to an LLM.\n",
"We can achieve that using the `exclude` parameter of the GraphCypherQAChain chain."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"from langchain.chains import GraphCypherQAChain\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n",
"chain = GraphCypherQAChain.from_llm(\n",
" graph=graph, llm=llm, exclude_types=[\"Genre\"], verbose=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Node properties are the following:\n",
"Movie {imdbRating: FLOAT, id: STRING, released: DATE, title: STRING},Person {name: STRING}\n",
"Relationship properties are the following:\n",
"\n",
"The relationships are the following:\n",
"(:Person)-[:DIRECTED]->(:Movie),(:Person)-[:ACTED_IN]->(:Movie)\n"
]
}
],
"source": [
"print(chain.graph_schema)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Few-shot examples\n",
"\n",
"Including examples of natural language questions being converted to valid Cypher queries against our database in the prompt will often improve model performance, especially for complex queries.\n",
"\n",
"Let's say we have the following examples:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"examples = [\n",
" {\n",
" \"question\": \"How many artists are there?\",\n",
" \"query\": \"MATCH (a:Person)-[:ACTED_IN]->(:Movie) RETURN count(DISTINCT a)\",\n",
" },\n",
" {\n",
" \"question\": \"Which actors played in the movie Casino?\",\n",
" \"query\": \"MATCH (m:Movie {{title: 'Casino'}})<-[:ACTED_IN]-(a) RETURN a.name\",\n",
" },\n",
" {\n",
" \"question\": \"How many movies has Tom Hanks acted in?\",\n",
" \"query\": \"MATCH (a:Person {{name: 'Tom Hanks'}})-[:ACTED_IN]->(m:Movie) RETURN count(m)\",\n",
" },\n",
" {\n",
" \"question\": \"List all the genres of the movie Schindler's List\",\n",
" \"query\": \"MATCH (m:Movie {{title: 'Schindler\\\\'s List'}})-[:IN_GENRE]->(g:Genre) RETURN g.name\",\n",
" },\n",
" {\n",
" \"question\": \"Which actors have worked in movies from both the comedy and action genres?\",\n",
" \"query\": \"MATCH (a:Person)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g1:Genre), (a)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g2:Genre) WHERE g1.name = 'Comedy' AND g2.name = 'Action' RETURN DISTINCT a.name\",\n",
" },\n",
" {\n",
" \"question\": \"Which directors have made movies with at least three different actors named 'John'?\",\n",
" \"query\": \"MATCH (d:Person)-[:DIRECTED]->(m:Movie)<-[:ACTED_IN]-(a:Person) WHERE a.name STARTS WITH 'John' WITH d, COUNT(DISTINCT a) AS JohnsCount WHERE JohnsCount >= 3 RETURN d.name\",\n",
" },\n",
" {\n",
" \"question\": \"Identify movies where directors also played a role in the film.\",\n",
" \"query\": \"MATCH (p:Person)-[:DIRECTED]->(m:Movie), (p)-[:ACTED_IN]->(m) RETURN m.title, p.name\",\n",
" },\n",
" {\n",
" \"question\": \"Find the actor with the highest number of movies in the database.\",\n",
" \"query\": \"MATCH (a:Actor)-[:ACTED_IN]->(m:Movie) RETURN a.name, COUNT(m) AS movieCount ORDER BY movieCount DESC LIMIT 1\",\n",
" },\n",
"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can create a few-shot prompt with them like so:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate\n",
"\n",
"example_prompt = PromptTemplate.from_template(\n",
" \"User input: {question}\\nCypher query: {query}\"\n",
")\n",
"prompt = FewShotPromptTemplate(\n",
" examples=examples[:5],\n",
" example_prompt=example_prompt,\n",
" prefix=\"You are a Neo4j expert. Given an input question, create a syntactically correct Cypher query to run.\\n\\nHere is the schema information\\n{schema}.\\n\\nBelow are a number of examples of questions and their corresponding Cypher queries.\",\n",
" suffix=\"User input: {question}\\nCypher query: \",\n",
" input_variables=[\"question\", \"schema\"],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"You are a Neo4j expert. Given an input question, create a syntactically correct Cypher query to run.\n",
"\n",
"Here is the schema information\n",
"foo.\n",
"\n",
"Below are a number of examples of questions and their corresponding Cypher queries.\n",
"\n",
"User input: How many artists are there?\n",
"Cypher query: MATCH (a:Person)-[:ACTED_IN]->(:Movie) RETURN count(DISTINCT a)\n",
"\n",
"User input: Which actors played in the movie Casino?\n",
"Cypher query: MATCH (m:Movie {title: 'Casino'})<-[:ACTED_IN]-(a) RETURN a.name\n",
"\n",
"User input: How many movies has Tom Hanks acted in?\n",
"Cypher query: MATCH (a:Person {name: 'Tom Hanks'})-[:ACTED_IN]->(m:Movie) RETURN count(m)\n",
"\n",
"User input: List all the genres of the movie Schindler's List\n",
"Cypher query: MATCH (m:Movie {title: 'Schindler\\'s List'})-[:IN_GENRE]->(g:Genre) RETURN g.name\n",
"\n",
"User input: Which actors have worked in movies from both the comedy and action genres?\n",
"Cypher query: MATCH (a:Person)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g1:Genre), (a)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g2:Genre) WHERE g1.name = 'Comedy' AND g2.name = 'Action' RETURN DISTINCT a.name\n",
"\n",
"User input: How many artists are there?\n",
"Cypher query: \n"
]
}
],
"source": [
"print(prompt.format(question=\"How many artists are there?\", schema=\"foo\"))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Dynamic few-shot examples\n",
"\n",
"If we have enough examples, we may want to only include the most relevant ones in the prompt, either because they don't fit in the model's context window or because the long tail of examples distracts the model. And specifically, given any input we want to include the examples most relevant to that input.\n",
"\n",
"We can do just this using an ExampleSelector. In this case we'll use a [SemanticSimilarityExampleSelector](https://api.python.langchain.com/en/latest/example_selectors/langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector.html), which will store the examples in the vector database of our choosing. At runtime it will perform a similarity search between the input and our examples, and return the most semantically similar ones: "
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.vectorstores import Neo4jVector\n",
"from langchain_core.example_selectors import SemanticSimilarityExampleSelector\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"example_selector = SemanticSimilarityExampleSelector.from_examples(\n",
" examples,\n",
" OpenAIEmbeddings(),\n",
" Neo4jVector,\n",
" k=5,\n",
" input_keys=[\"question\"],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'query': 'MATCH (a:Person)-[:ACTED_IN]->(:Movie) RETURN count(DISTINCT a)',\n",
" 'question': 'How many artists are there?'},\n",
" {'query': \"MATCH (a:Person {{name: 'Tom Hanks'}})-[:ACTED_IN]->(m:Movie) RETURN count(m)\",\n",
" 'question': 'How many movies has Tom Hanks acted in?'},\n",
" {'query': \"MATCH (a:Person)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g1:Genre), (a)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g2:Genre) WHERE g1.name = 'Comedy' AND g2.name = 'Action' RETURN DISTINCT a.name\",\n",
" 'question': 'Which actors have worked in movies from both the comedy and action genres?'},\n",
" {'query': \"MATCH (d:Person)-[:DIRECTED]->(m:Movie)<-[:ACTED_IN]-(a:Person) WHERE a.name STARTS WITH 'John' WITH d, COUNT(DISTINCT a) AS JohnsCount WHERE JohnsCount >= 3 RETURN d.name\",\n",
" 'question': \"Which directors have made movies with at least three different actors named 'John'?\"},\n",
" {'query': 'MATCH (a:Actor)-[:ACTED_IN]->(m:Movie) RETURN a.name, COUNT(m) AS movieCount ORDER BY movieCount DESC LIMIT 1',\n",
" 'question': 'Find the actor with the highest number of movies in the database.'}]"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"example_selector.select_examples({\"question\": \"how many artists are there?\"})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To use it, we can pass the ExampleSelector directly in to our FewShotPromptTemplate:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"prompt = FewShotPromptTemplate(\n",
" example_selector=example_selector,\n",
" example_prompt=example_prompt,\n",
" prefix=\"You are a Neo4j expert. Given an input question, create a syntactically correct Cypher query to run.\\n\\nHere is the schema information\\n{schema}.\\n\\nBelow are a number of examples of questions and their corresponding Cypher queries.\",\n",
" suffix=\"User input: {question}\\nCypher query: \",\n",
" input_variables=[\"question\", \"schema\"],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"You are a Neo4j expert. Given an input question, create a syntactically correct Cypher query to run.\n",
"\n",
"Here is the schema information\n",
"foo.\n",
"\n",
"Below are a number of examples of questions and their corresponding Cypher queries.\n",
"\n",
"User input: How many artists are there?\n",
"Cypher query: MATCH (a:Person)-[:ACTED_IN]->(:Movie) RETURN count(DISTINCT a)\n",
"\n",
"User input: How many movies has Tom Hanks acted in?\n",
"Cypher query: MATCH (a:Person {name: 'Tom Hanks'})-[:ACTED_IN]->(m:Movie) RETURN count(m)\n",
"\n",
"User input: Which actors have worked in movies from both the comedy and action genres?\n",
"Cypher query: MATCH (a:Person)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g1:Genre), (a)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g2:Genre) WHERE g1.name = 'Comedy' AND g2.name = 'Action' RETURN DISTINCT a.name\n",
"\n",
"User input: Which directors have made movies with at least three different actors named 'John'?\n",
"Cypher query: MATCH (d:Person)-[:DIRECTED]->(m:Movie)<-[:ACTED_IN]-(a:Person) WHERE a.name STARTS WITH 'John' WITH d, COUNT(DISTINCT a) AS JohnsCount WHERE JohnsCount >= 3 RETURN d.name\n",
"\n",
"User input: Find the actor with the highest number of movies in the database.\n",
"Cypher query: MATCH (a:Actor)-[:ACTED_IN]->(m:Movie) RETURN a.name, COUNT(m) AS movieCount ORDER BY movieCount DESC LIMIT 1\n",
"\n",
"User input: how many artists are there?\n",
"Cypher query: \n"
]
}
],
"source": [
"print(prompt.format(question=\"how many artists are there?\", schema=\"foo\"))"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n",
"chain = GraphCypherQAChain.from_llm(\n",
" graph=graph, llm=llm, cypher_prompt=prompt, verbose=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new GraphCypherQAChain chain...\u001b[0m\n",
"Generated Cypher:\n",
"\u001b[32;1m\u001b[1;3mMATCH (a:Person)-[:ACTED_IN]->(:Movie) RETURN count(DISTINCT a)\u001b[0m\n",
"Full Context:\n",
"\u001b[32;1m\u001b[1;3m[{'count(DISTINCT a)': 967}]\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'query': 'How many actors are in the graph?',\n",
" 'result': 'There are 967 actors in the graph.'}"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain.invoke(\"How many actors are in the graph?\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,415 @@
{
"cells": [
{
"cell_type": "raw",
"id": "19cc5b11-3822-454b-afb3-7bebd7f17b5c",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 1\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "2e17a273-bcfc-433f-8d42-2ba9533feeb8",
"metadata": {},
"source": [
"# How to add a semantic layer over graph database\n",
"\n",
"You can use database queries to retrieve information from a graph database like Neo4j.\n",
"One option is to use LLMs to generate Cypher statements.\n",
"While that option provides excellent flexibility, the solution could be brittle and not consistently generating precise Cypher statements.\n",
"Instead of generating Cypher statements, we can implement Cypher templates as tools in a semantic layer that an LLM agent can interact with.\n",
"\n",
"![graph_semantic.png](/static/img/graph_semantic.png)\n",
"\n",
"## Setup\n",
"\n",
"First, get required packages and set environment variables:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "ffdd48f6-bd05-4e5c-b846-d41183398a55",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Note: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install --upgrade --quiet langchain langchain-community langchain-openai neo4j"
]
},
{
"cell_type": "markdown",
"id": "4575b174-01e6-4061-aebf-f81e718de777",
"metadata": {},
"source": [
"We default to OpenAI models in this guide, but you can swap them out for the model provider of your choice."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "eb11c4a8-c00c-4c2d-9309-74a6acfff91c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" ········\n"
]
}
],
"source": [
"import getpass\n",
"import os\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n",
"\n",
"# Uncomment the below to use LangSmith. Not required.\n",
"# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n",
"# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\""
]
},
{
"cell_type": "markdown",
"id": "76bb62ba-0060-41a2-a7b9-1f9c1faf571a",
"metadata": {},
"source": [
"Next, we need to define Neo4j credentials.\n",
"Follow [these installation steps](https://neo4j.com/docs/operations-manual/current/installation/) to set up a Neo4j database."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "ef59a3af-31a8-4ad8-8eb9-132aca66956e",
"metadata": {},
"outputs": [],
"source": [
"os.environ[\"NEO4J_URI\"] = \"bolt://localhost:7687\"\n",
"os.environ[\"NEO4J_USERNAME\"] = \"neo4j\"\n",
"os.environ[\"NEO4J_PASSWORD\"] = \"password\""
]
},
{
"cell_type": "markdown",
"id": "1e8fbc2c-b8e8-4c53-8fce-243cf99d3c1c",
"metadata": {},
"source": [
"The below example will create a connection with a Neo4j database and will populate it with example data about movies and their actors."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "c84b1449-6fcd-4140-b591-cb45e8dce207",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_community.graphs import Neo4jGraph\n",
"\n",
"graph = Neo4jGraph()\n",
"\n",
"# Import movie information\n",
"\n",
"movies_query = \"\"\"\n",
"LOAD CSV WITH HEADERS FROM \n",
"'https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/movies/movies_small.csv'\n",
"AS row\n",
"MERGE (m:Movie {id:row.movieId})\n",
"SET m.released = date(row.released),\n",
" m.title = row.title,\n",
" m.imdbRating = toFloat(row.imdbRating)\n",
"FOREACH (director in split(row.director, '|') | \n",
" MERGE (p:Person {name:trim(director)})\n",
" MERGE (p)-[:DIRECTED]->(m))\n",
"FOREACH (actor in split(row.actors, '|') | \n",
" MERGE (p:Person {name:trim(actor)})\n",
" MERGE (p)-[:ACTED_IN]->(m))\n",
"FOREACH (genre in split(row.genres, '|') | \n",
" MERGE (g:Genre {name:trim(genre)})\n",
" MERGE (m)-[:IN_GENRE]->(g))\n",
"\"\"\"\n",
"\n",
"graph.query(movies_query)"
]
},
{
"cell_type": "markdown",
"id": "403b9acd-aa0d-4157-b9de-6ec426835c43",
"metadata": {},
"source": [
"## Custom tools with Cypher templates\n",
"\n",
"A semantic layer consists of various tools exposed to an LLM that it can use to interact with a knowledge graph.\n",
"They can be of various complexity. You can think of each tool in a semantic layer as a function.\n",
"\n",
"The function we will implement is to retrieve information about movies or their cast."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "d1dc1c8c-f343-4024-924b-a8a86cf5f1af",
"metadata": {},
"outputs": [],
"source": [
"from typing import Optional, Type\n",
"\n",
"from langchain.callbacks.manager import (\n",
" AsyncCallbackManagerForToolRun,\n",
" CallbackManagerForToolRun,\n",
")\n",
"\n",
"# Import things that are needed generically\n",
"from langchain.pydantic_v1 import BaseModel, Field\n",
"from langchain.tools import BaseTool\n",
"\n",
"description_query = \"\"\"\n",
"MATCH (m:Movie|Person)\n",
"WHERE m.title CONTAINS $candidate OR m.name CONTAINS $candidate\n",
"MATCH (m)-[r:ACTED_IN|HAS_GENRE]-(t)\n",
"WITH m, type(r) as type, collect(coalesce(t.name, t.title)) as names\n",
"WITH m, type+\": \"+reduce(s=\"\", n IN names | s + n + \", \") as types\n",
"WITH m, collect(types) as contexts\n",
"WITH m, \"type:\" + labels(m)[0] + \"\\ntitle: \"+ coalesce(m.title, m.name) \n",
" + \"\\nyear: \"+coalesce(m.released,\"\") +\"\\n\" +\n",
" reduce(s=\"\", c in contexts | s + substring(c, 0, size(c)-2) +\"\\n\") as context\n",
"RETURN context LIMIT 1\n",
"\"\"\"\n",
"\n",
"\n",
"def get_information(entity: str) -> str:\n",
" try:\n",
" data = graph.query(description_query, params={\"candidate\": entity})\n",
" return data[0][\"context\"]\n",
" except IndexError:\n",
" return \"No information was found\""
]
},
{
"cell_type": "markdown",
"id": "bdecc24b-8065-4755-98cc-9c6d093d4897",
"metadata": {},
"source": [
"You can observe that we have defined the Cypher statement used to retrieve information.\n",
"Therefore, we can avoid generating Cypher statements and use the LLM agent to only populate the input parameters.\n",
"To provide additional information to an LLM agent about when to use the tool and their input parameters, we wrap the function as a tool."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "f4cde772-0d05-475d-a2f0-b53e1669bd13",
"metadata": {},
"outputs": [],
"source": [
"from typing import Optional, Type\n",
"\n",
"from langchain.callbacks.manager import (\n",
" AsyncCallbackManagerForToolRun,\n",
" CallbackManagerForToolRun,\n",
")\n",
"\n",
"# Import things that are needed generically\n",
"from langchain.pydantic_v1 import BaseModel, Field\n",
"from langchain.tools import BaseTool\n",
"\n",
"\n",
"class InformationInput(BaseModel):\n",
" entity: str = Field(description=\"movie or a person mentioned in the question\")\n",
"\n",
"\n",
"class InformationTool(BaseTool):\n",
" name = \"Information\"\n",
" description = (\n",
" \"useful for when you need to answer questions about various actors or movies\"\n",
" )\n",
" args_schema: Type[BaseModel] = InformationInput\n",
"\n",
" def _run(\n",
" self,\n",
" entity: str,\n",
" run_manager: Optional[CallbackManagerForToolRun] = None,\n",
" ) -> str:\n",
" \"\"\"Use the tool.\"\"\"\n",
" return get_information(entity)\n",
"\n",
" async def _arun(\n",
" self,\n",
" entity: str,\n",
" run_manager: Optional[AsyncCallbackManagerForToolRun] = None,\n",
" ) -> str:\n",
" \"\"\"Use the tool asynchronously.\"\"\"\n",
" return get_information(entity)"
]
},
{
"cell_type": "markdown",
"id": "ff4820aa-2b57-4558-901f-6d984b326738",
"metadata": {},
"source": [
"## OpenAI Agent\n",
"\n",
"LangChain expression language makes it very convenient to define an agent to interact with a graph database over the semantic layer."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "6e959ac2-537d-4358-a43b-e3a47f68e1d6",
"metadata": {},
"outputs": [],
"source": [
"from typing import List, Tuple\n",
"\n",
"from langchain.agents import AgentExecutor\n",
"from langchain.agents.format_scratchpad import format_to_openai_function_messages\n",
"from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser\n",
"from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"from langchain_core.messages import AIMessage, HumanMessage\n",
"from langchain_core.utils.function_calling import convert_to_openai_function\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n",
"tools = [InformationTool()]\n",
"\n",
"llm_with_tools = llm.bind(functions=[convert_to_openai_function(t) for t in tools])\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful assistant that finds information about movies \"\n",
" \" and recommends them. If tools require follow up questions, \"\n",
" \"make sure to ask the user for clarification. Make sure to include any \"\n",
" \"available options that need to be clarified in the follow up questions \"\n",
" \"Do only the things the user specifically requested. \",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"chat_history\"),\n",
" (\"user\", \"{input}\"),\n",
" MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
" ]\n",
")\n",
"\n",
"\n",
"def _format_chat_history(chat_history: List[Tuple[str, str]]):\n",
" buffer = []\n",
" for human, ai in chat_history:\n",
" buffer.append(HumanMessage(content=human))\n",
" buffer.append(AIMessage(content=ai))\n",
" return buffer\n",
"\n",
"\n",
"agent = (\n",
" {\n",
" \"input\": lambda x: x[\"input\"],\n",
" \"chat_history\": lambda x: _format_chat_history(x[\"chat_history\"])\n",
" if x.get(\"chat_history\")\n",
" else [],\n",
" \"agent_scratchpad\": lambda x: format_to_openai_function_messages(\n",
" x[\"intermediate_steps\"]\n",
" ),\n",
" }\n",
" | prompt\n",
" | llm_with_tools\n",
" | OpenAIFunctionsAgentOutputParser()\n",
")\n",
"\n",
"agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "b0459833-fe84-4ebc-9823-a3a3ffd929e9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `Information` with `{'entity': 'Casino'}`\n",
"\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3mtype:Movie\n",
"title: Casino\n",
"year: 1995-11-22\n",
"ACTED_IN: Joe Pesci, Robert De Niro, Sharon Stone, James Woods\n",
"\u001b[0m\u001b[32;1m\u001b[1;3mThe movie \"Casino\" starred Joe Pesci, Robert De Niro, Sharon Stone, and James Woods.\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'input': 'Who played in Casino?',\n",
" 'output': 'The movie \"Casino\" starred Joe Pesci, Robert De Niro, Sharon Stone, and James Woods.'}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_executor.invoke({\"input\": \"Who played in Casino?\"})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2759973-de8a-4624-8930-c90a21d6caa3",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,253 @@
---
sidebar_position: 0
sidebar_class_name: hidden
---
# How-to Guides
Here youll find short answers to “How do I….?” types of questions.
These how-to guides dont cover topics in depth youll find that material in the [Tutorials](/docs/tutorials) and the [API Reference](https://api.python.langchain.com/en/latest/).
However, these guides will help you quickly accomplish common tasks.
## Core Functionality
This covers functionality that is core to using LangChain
- [How to return structured data from an LLM](/docs/how_to/structured_output/)
- [How to use a chat model to call tools](/docs/how_to/tool_calling/)
- [How to stream](/docs/how_to/streaming)
- [How to debug your LLM apps](/docs/how_to/debugging/)
## LangChain Expression Language (LCEL)
LangChain Expression Language a way to create arbitrary custom chains.
- [How to combine multiple runnables into a chain](/docs/how_to/sequence)
- [How to invoke runnables in parallel](/docs/how_to/parallel/)
- [How to attach runtime arguments to a runnable](/docs/how_to/binding/)
- [How to run custom functions](/docs/how_to/functions)
- [How to pass through arguments from one step to the next](/docs/how_to/passthrough)
- [How to add values to a chain's state](/docs/how_to/assign)
- [How to configure a chain at runtime](/docs/how_to/configure)
- [How to add message history](/docs/how_to/message_history)
- [How to route execution within a chain](/docs/how_to/routing)
- [How to inspect your runnables](/docs/how_to/inspect)
- [How to add fallbacks](/docs/how_to/fallbacks)
## Components
These are the core building blocks you can use when building applications.
### Prompt Templates
Prompt Templates are responsible for formatting user input into a format that can be passed to a language model.
- [How to use few shot examples](/docs/how_to/few_shot_examples)
- [How to use few shot examples in chat models](/docs/how_to/few_shot_examples_chat/)
- [How to partially format prompt templates](/docs/how_to/prompts_partial)
- [How to compose prompts together](/docs/how_to/prompts_composition)
### Example Selectors
Example Selectors are responsible for selecting the correct few shot examples to pass to the prompt.
- [How to use example selectors](/docs/how_to/example_selectors)
- [How to select examples by length](/docs/how_to/example_selectors_length_based)
- [How to select examples by semantic similarity](/docs/how_to/example_selectors_similarity)
- [How to select examples by semantic ngram overlap](/docs/how_to/example_selectors_ngram)
- [How to select examples by maximal marginal relevance](/docs/how_to/example_selectors_mmr)
### Chat Models
Chat Models are newer forms of language models that take messages in and output a message.
- [How to do function/tool calling](/docs/how_to/tool_calling)
- [How to get models to return structured output](/docs/how_to/structured_output)
- [How to cache model responses](/docs/how_to/chat_model_caching)
- [How to get log probabilities from model calls](/docs/how_to/logprobs)
- [How to create a custom chat model class](/docs/how_to/custom_chat_model)
- [How to stream a response back](/docs/how_to/chat_streaming)
- [How to track token usage](/docs/how_to/chat_token_usage_tracking)
### LLMs
What LangChain calls LLMs are older forms of language models that take a string in and output a string.
- [How to cache model responses](/docs/how_to/llm_caching)
- [How to create a custom LLM class](/docs/how_to/custom_llm)
- [How to stream a response back](/docs/how_to/streaming_llm)
- [How to track token usage](/docs/how_to/llm_token_usage_tracking)
### Output Parsers
Output Parsers are responsible for taking the output of an LLM and parsing into more structured format.
- [How to use output parsers to parse an LLM response into structured format](/docs/how_to/output_parser_structured)
- [How to parse JSON output](/docs/how_to/output_parser_json)
- [How to parse XML output](/docs/how_to/output_parser_xml)
- [How to parse YAML output](/docs/how_to/output_parser_yaml)
- [How to retry when output parsing errors occur](/docs/how_to/output_parser_retry)
- [How to try to fix errors in output parsing](/docs/how_to/output_parser_fixing)
- [How to write a custom output parser class](/docs/how_to/output_parser_custom)
### Document Loaders
Document Loaders are responsible for loading documents from a variety of sources.
- [How to load CSV data](/docs/how_to/document_loader_csv)
- [How to load data from a directory](/docs/how_to/document_loader_directory)
- [How to load HTML data](/docs/how_to/document_loader_html)
- [How to load JSON data](/docs/how_to/document_loader_json)
- [How to load Markdown data](/docs/how_to/document_loader_markdown)
- [How to load Microsoft Office data](/docs/how_to/document_loader_office_file)
- [How to load PDF files](/docs/how_to/document_loader_pdf)
- [How to write a custom document loader](/docs/how_to/document_loader_custom)
### Text Splitters
Text Splitters take a document and split into chunks that can be used for retrieval.
- [How to recursively split text](/docs/how_to/recursive_text_splitter)
- [How to split by HTML headers](/docs/how_to/HTML_header_metadata_splitter)
- [How to split by HTML sections](/docs/how_to/HTML_section_aware_splitter)
- [How to split by character](/docs/how_to/character_text_splitter)
- [How to split code](/docs/how_to/code_splitter)
- [How to split Markdown by headers](/docs/how_to/markdown_header_metadata_splitter)
- [How to recursively split JSON](/docs/how_to/recursive_json_splitter)
- [How to split text into semantic chunks](/docs/how_to/semantic-chunker)
- [How to split by tokens](/docs/how_to/split_by_token)
### Embedding Models
Embedding Models take a piece of text and create a numerical representation of it.
- [How to embed text data](/docs/how_to/embed_text)
- [How to cache embedding results](/docs/how_to/caching_embeddings)
### Vector Stores
Vector Stores are databases that can efficiently store and retrieve embeddings.
- [How to use a vector store to retrieve data](/docs/how_to/vectorstores)
### Retrievers
Retrievers are responsible for taking a query and returning relevant documents.
- [How use a vector store to retrieve data](/docs/how_to/vectorstore_retriever)
- [How to generate multiple queries to retrieve data for](/docs/how_to/MultiQueryRetriever)
- [How to use contextual compression to compress the data retrieved](/docs/how_to/contextual_compression)
- [How to write a custom retriever class](/docs/how_to/custom_retriever)
- [How to combine the results from multiple retrievers](/docs/how_to/ensemble_retriever)
- [How to reorder retrieved results to put most relevant documents not in the middle](/docs/how_to/long_context_reorder)
- [How to generate multiple embeddings per document](/docs/how_to/multi_vector)
- [How to retrieve the whole document for a chunk](/docs/how_to/parent_document_retriever)
- [How to generate metadata filters](/docs/how_to/self_query)
- [How to create a time-weighted retriever](/docs/how_to/time_weighted_vectorstore)
### Indexing
Indexing is the process of keeping your vectorstore in-sync with the underlying data source.
- [How to reindex data to keep your vectorstore in-sync with the underlying data source](/docs/how_to/indexing)
### Tools
LangChain Tools contain a description of the tool (to pass to the language model) as well as the implementation of the function to call).
- [How to use LangChain tools](/docs/how_to/tools)
- [How to use a chat model to call tools](/docs/how_to/tool_calling/)
- [How to use LangChain toolkits](/docs/how_to/toolkits)
- [How to define a custom tool](/docs/how_to/custom_tools)
- [How to convert LangChain tools to OpenAI functions](/docs/how_to/tools_as_openai_functions)
- [How to use tools without function calling](/docs/how_to/tools_prompting)
- [How to let the LLM choose between multiple tools](/docs/how_to/tools_multiple)
- [How to add a human in the loop to tool usage](/docs/how_to/tools_human)
- [How to do parallel tool use](/docs/how_to/tools_parallel)
- [How to handle errors when calling tools](/docs/how_to/tools_error)
### Agents
:::note
For in depth how-to guides for agents, please check out [LangGraph](https://github.com/langchain-ai/langgraph) documentation.
:::
- [How to use legacy LangChain Agents (AgentExecutor)](/docs/how_to/agent_executor)
- [How to migrate from legacy LangChain agents to LangGraph](/docs/how_to/migrate_agent)
### Custom
All of LangChain components can easily be extended to support your own versions.
- [How to create a custom chat model class](/docs/how_to/custom_chat_model)
- [How to create a custom LLM class](/docs/how_to/custom_llm)
- [How to write a custom retriever class](/docs/how_to/custom_retriever)
- [How to write a custom document loader](/docs/how_to/document_loader_custom)
- [How to write a custom output parser class](/docs/how_to/output_parser_custom)
- [How to define a custom tool](/docs/how_to/custom_tools)
## Use Cases
These guides cover use-case specific details.
### Q&A with RAG
Retrieval Augmented Generation (RAG) is a way to connect LLMs to external sources of data.
- [How to add chat history](/docs/how_to/qa_chat_history_how_to/)
- [How to stream](/docs/how_to/qa_streaming/)
- [How to return sources](/docs/how_to/qa_sources/)
- [How to return citations](/docs/how_to/qa_citations/)
- [How to do per-user retrieval](/docs/how_to/qa_per_user/)
### Extraction
Extraction is when you use LLMs to extract structured information from unstructured text.
- [How to use reference examples](/docs/how_to/extraction_examples/)
- [How to handle long text](/docs/how_to/extraction_long_text/)
- [How to do extraction without using function calling](/docs/how_to/extraction_parse)
### Chatbots
Chatbots involve using an LLM to have a conversation.
- [How to manage memory](/docs/how_to/chatbots_memory)
- [How to do retrieval](/docs/how_to/chatbots_retrieval)
- [How to use tools](/docs/how_to/chatbots_tools)
### Query Analysis
Query Analysis is the task of using an LLM to generate a query to send to a retriever.
- [How to add examples to the prompt](/docs/how_to/query_few_shot)
- [How to handle cases where no queries are generated](/docs/how_to/query_no_queries)
- [How to handle multiple queries](/docs/how_to/query_multiple_queries)
- [How to handle multiple retrievers](/docs/how_to/query_multiple_retrievers)
- [How to construct filters](/docs/how_to/query_constructing_filters)
- [How to deal with high cardinality categorical variables](/docs/how_to/query_high_cardinality)
### Q&A over SQL + CSV
You can use LLMs to do question answering over tabular data.
- [How to use prompting to improve results](/docs/how_to/sql_prompting)
- [How to do query validation](/docs/how_to/sql_query_checking)
- [How to deal with large databases](/docs/how_to/sql_large_db)
- [How to deal with CSV files](/docs/how_to/sql_csv)
### Q&A over Graph Databases
You can use an LLM to do question answering over graph databases.
- [How to map values to a database](/docs/how_to/graph_mapping)
- [How to add a semantic layer over the database](/docs/how_to/graph_semantic)
- [How to improve results with prompting](/docs/how_to/graph_prompting)
- [How to construct knowledge graphs](/docs/how_to/graph_constructing)

View File

@@ -0,0 +1,914 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0fe57ac5-31c5-4dbb-b96c-78dead32e1bd",
"metadata": {},
"source": [
"# How to use the LangChain indexing API\n",
"\n",
"Here, we will look at a basic indexing workflow using the LangChain indexing API. \n",
"\n",
"The indexing API lets you load and keep in sync documents from any source into a vector store. Specifically, it helps:\n",
"\n",
"* Avoid writing duplicated content into the vector store\n",
"* Avoid re-writing unchanged content\n",
"* Avoid re-computing embeddings over unchanged content\n",
"\n",
"All of which should save you time and money, as well as improve your vector search results.\n",
"\n",
"Crucially, the indexing API will work even with documents that have gone through several \n",
"transformation steps (e.g., via text chunking) with respect to the original source documents.\n",
"\n",
"## How it works\n",
"\n",
"LangChain indexing makes use of a record manager (`RecordManager`) that keeps track of document writes into the vector store.\n",
"\n",
"When indexing content, hashes are computed for each document, and the following information is stored in the record manager: \n",
"\n",
"- the document hash (hash of both page content and metadata)\n",
"- write time\n",
"- the source id -- each document should include information in its metadata to allow us to determine the ultimate source of this document\n",
"\n",
"## Deletion modes\n",
"\n",
"When indexing documents into a vector store, it's possible that some existing documents in the vector store should be deleted. In certain situations you may want to remove any existing documents that are derived from the same sources as the new documents being indexed. In others you may want to delete all existing documents wholesale. The indexing API deletion modes let you pick the behavior you want:\n",
"\n",
"| Cleanup Mode | De-Duplicates Content | Parallelizable | Cleans Up Deleted Source Docs | Cleans Up Mutations of Source Docs and/or Derived Docs | Clean Up Timing |\n",
"|-------------|-----------------------|---------------|----------------------------------|----------------------------------------------------|---------------------|\n",
"| None | ✅ | ✅ | ❌ | ❌ | - |\n",
"| Incremental | ✅ | ✅ | ❌ | ✅ | Continuously |\n",
"| Full | ✅ | ❌ | ✅ | ✅ | At end of indexing |\n",
"\n",
"\n",
"`None` does not do any automatic clean up, allowing the user to manually do clean up of old content. \n",
"\n",
"`incremental` and `full` offer the following automated clean up:\n",
"\n",
"* If the content of the source document or derived documents has **changed**, both `incremental` or `full` modes will clean up (delete) previous versions of the content.\n",
"* If the source document has been **deleted** (meaning it is not included in the documents currently being indexed), the `full` cleanup mode will delete it from the vector store correctly, but the `incremental` mode will not.\n",
"\n",
"When content is mutated (e.g., the source PDF file was revised) there will be a period of time during indexing when both the new and old versions may be returned to the user. This happens after the new content was written, but before the old version was deleted.\n",
"\n",
"* `incremental` indexing minimizes this period of time as it is able to do clean up continuously, as it writes.\n",
"* `full` mode does the clean up after all batches have been written.\n",
"\n",
"## Requirements\n",
"\n",
"1. Do not use with a store that has been pre-populated with content independently of the indexing API, as the record manager will not know that records have been inserted previously.\n",
"2. Only works with LangChain `vectorstore`'s that support:\n",
" * document addition by id (`add_documents` method with `ids` argument)\n",
" * delete by id (`delete` method with `ids` argument)\n",
"\n",
"Compatible Vectorstores: `AnalyticDB`, `AstraDB`, `AwaDB`, `Bagel`, `Cassandra`, `Chroma`, `CouchbaseVectorStore`, `DashVector`, `DatabricksVectorSearch`, `DeepLake`, `Dingo`, `ElasticVectorSearch`, `ElasticsearchStore`, `FAISS`, `HanaDB`, `Milvus`, `MyScale`, `OpenSearchVectorSearch`, `PGVector`, `Pinecone`, `Qdrant`, `Redis`, `Rockset`, `ScaNN`, `SupabaseVectorStore`, `SurrealDBStore`, `TimescaleVector`, `Vald`, `VDMS`, `Vearch`, `VespaStore`, `Weaviate`, `ZepVectorStore`, `TencentVectorDB`, `OpenSearchVectorSearch`.\n",
" \n",
"## Caution\n",
"\n",
"The record manager relies on a time-based mechanism to determine what content can be cleaned up (when using `full` or `incremental` cleanup modes).\n",
"\n",
"If two tasks run back-to-back, and the first task finishes before the clock time changes, then the second task may not be able to clean up content.\n",
"\n",
"This is unlikely to be an issue in actual settings for the following reasons:\n",
"\n",
"1. The RecordManager uses higher resolution timestamps.\n",
"2. The data would need to change between the first and the second tasks runs, which becomes unlikely if the time interval between the tasks is small.\n",
"3. Indexing tasks typically take more than a few ms."
]
},
{
"cell_type": "markdown",
"id": "ec2109b4-cbcc-44eb-9dac-3f7345f971dc",
"metadata": {},
"source": [
"## Quickstart"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "15f7263e-c82e-4914-874f-9699ea4de93e",
"metadata": {},
"outputs": [],
"source": [
"from langchain.indexes import SQLRecordManager, index\n",
"from langchain_core.documents import Document\n",
"from langchain_elasticsearch import ElasticsearchStore\n",
"from langchain_openai import OpenAIEmbeddings"
]
},
{
"cell_type": "markdown",
"id": "f81201ab-d997-433c-9f18-ceea70e61cbd",
"metadata": {},
"source": [
"Initialize a vector store and set up the embeddings:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4ffc9659-91c0-41e0-ae4b-f7ff0d97292d",
"metadata": {},
"outputs": [],
"source": [
"collection_name = \"test_index\"\n",
"\n",
"embedding = OpenAIEmbeddings()\n",
"\n",
"vectorstore = ElasticsearchStore(\n",
" es_url=\"http://localhost:9200\", index_name=\"test_index\", embedding=embedding\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b9b7564f-2334-428b-b513-13045a08b56c",
"metadata": {},
"source": [
"Initialize a record manager with an appropriate namespace.\n",
"\n",
"**Suggestion:** Use a namespace that takes into account both the vector store and the collection name in the vector store; e.g., 'redis/my_docs', 'chromadb/my_docs' or 'postgres/my_docs'."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "498cc80e-c339-49ee-893b-b18d06346ef8",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"namespace = f\"elasticsearch/{collection_name}\"\n",
"record_manager = SQLRecordManager(\n",
" namespace, db_url=\"sqlite:///record_manager_cache.sql\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "835c2c19-68ec-4086-9066-f7ba40877fd5",
"metadata": {},
"source": [
"Create a schema before using the record manager."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a4be2da3-3a5c-468a-a824-560157290f7f",
"metadata": {},
"outputs": [],
"source": [
"record_manager.create_schema()"
]
},
{
"cell_type": "markdown",
"id": "7f07c6bd-6ada-4b17-a8c5-fe5e4a5278fd",
"metadata": {},
"source": [
"Let's index some test documents:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "bbfdf314-14f9-4799-8fb6-d42de4d51287",
"metadata": {},
"outputs": [],
"source": [
"doc1 = Document(page_content=\"kitty\", metadata={\"source\": \"kitty.txt\"})\n",
"doc2 = Document(page_content=\"doggy\", metadata={\"source\": \"doggy.txt\"})"
]
},
{
"cell_type": "markdown",
"id": "c7d572be-a913-4511-ab64-2864a252458a",
"metadata": {},
"source": [
"Indexing into an empty vector store:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "67d2a5c8-f2bd-489a-b58e-2c7ba7fefe6f",
"metadata": {},
"outputs": [],
"source": [
"def _clear():\n",
" \"\"\"Hacky helper method to clear content. See the `full` mode section to to understand why it works.\"\"\"\n",
" index([], record_manager, vectorstore, cleanup=\"full\", source_id_key=\"source\")"
]
},
{
"cell_type": "markdown",
"id": "e5e92e76-f23f-4a61-8a2d-f16baf288700",
"metadata": {},
"source": [
"### ``None`` deletion mode\n",
"\n",
"This mode does not do automatic clean up of old versions of content; however, it still takes care of content de-duplication."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e2288cee-1738-4054-af72-23b5c5be8840",
"metadata": {},
"outputs": [],
"source": [
"_clear()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "b253483b-5be0-4151-b732-ca93db4457b1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 1, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(\n",
" [doc1, doc1, doc1, doc1, doc1],\n",
" record_manager,\n",
" vectorstore,\n",
" cleanup=None,\n",
" source_id_key=\"source\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "7abaf351-bf5a-4d9e-95cd-4e3ecbfc1a84",
"metadata": {},
"outputs": [],
"source": [
"_clear()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "55b6873c-5907-4fa6-84ca-df6cdf1810f0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index([doc1, doc2], record_manager, vectorstore, cleanup=None, source_id_key=\"source\")"
]
},
{
"cell_type": "markdown",
"id": "7be3e55a-5fe9-4f40-beff-577c2aa5e76a",
"metadata": {},
"source": [
"Second time around all content will be skipped:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "59d74ca1-2e3d-4b4c-ad88-a4907aa20081",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 0, 'num_updated': 0, 'num_skipped': 2, 'num_deleted': 0}"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index([doc1, doc2], record_manager, vectorstore, cleanup=None, source_id_key=\"source\")"
]
},
{
"cell_type": "markdown",
"id": "237a809e-575d-4f02-870e-5906a3643f30",
"metadata": {},
"source": [
"### ``\"incremental\"`` deletion mode"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "6bc91073-0ab4-465a-9302-e7f4bbd2285c",
"metadata": {},
"outputs": [],
"source": [
"_clear()"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "4a551091-6d46-4cdd-9af9-8672e5866a0a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(\n",
" [doc1, doc2],\n",
" record_manager,\n",
" vectorstore,\n",
" cleanup=\"incremental\",\n",
" source_id_key=\"source\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d0604ab8-318c-4706-959b-3907af438630",
"metadata": {},
"source": [
"Indexing again should result in both documents getting **skipped** -- also skipping the embedding operation!"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "81785863-391b-4578-a6f6-63b3e5285488",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 0, 'num_updated': 0, 'num_skipped': 2, 'num_deleted': 0}"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(\n",
" [doc1, doc2],\n",
" record_manager,\n",
" vectorstore,\n",
" cleanup=\"incremental\",\n",
" source_id_key=\"source\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b205c1ba-f069-4a4e-af93-dc98afd5c9e6",
"metadata": {},
"source": [
"If we provide no documents with incremental indexing mode, nothing will change."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "1f73ca85-7478-48ab-976c-17b00beec7bd",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 0, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index([], record_manager, vectorstore, cleanup=\"incremental\", source_id_key=\"source\")"
]
},
{
"cell_type": "markdown",
"id": "b8c4ac96-8d60-4ade-8a94-e76ccb536442",
"metadata": {},
"source": [
"If we mutate a document, the new version will be written and all old versions sharing the same source will be deleted."
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "27d05bcb-d96d-42eb-88a8-54b33d6cfcdc",
"metadata": {},
"outputs": [],
"source": [
"changed_doc_2 = Document(page_content=\"puppy\", metadata={\"source\": \"doggy.txt\"})"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "3809e379-5962-4267-add9-b10f43e24c66",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 1, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 1}"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(\n",
" [changed_doc_2],\n",
" record_manager,\n",
" vectorstore,\n",
" cleanup=\"incremental\",\n",
" source_id_key=\"source\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "8bc75b9c-784a-4eb6-b5d6-688e3fbd4658",
"metadata": {},
"source": [
"### ``\"full\"`` deletion mode\n",
"\n",
"In `full` mode the user should pass the `full` universe of content that should be indexed into the indexing function.\n",
"\n",
"Any documents that are not passed into the indexing function and are present in the vectorstore will be deleted!\n",
"\n",
"This behavior is useful to handle deletions of source documents."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "38a14a3d-11c7-43e2-b7f1-08e487961bb5",
"metadata": {},
"outputs": [],
"source": [
"_clear()"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "46b5d7b6-ce91-47d2-a9d0-f390e77d847f",
"metadata": {},
"outputs": [],
"source": [
"all_docs = [doc1, doc2]"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "06954765-6155-40a0-b95e-33ef87754c8d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(all_docs, record_manager, vectorstore, cleanup=\"full\", source_id_key=\"source\")"
]
},
{
"cell_type": "markdown",
"id": "887c45c6-4363-4389-ac56-9cdad682b4c8",
"metadata": {},
"source": [
"Say someone deleted the first doc:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "35270e4e-9b03-4486-95de-e819ca5e469f",
"metadata": {},
"outputs": [],
"source": [
"del all_docs[0]"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "7d835a6a-f468-4d79-9a3d-47db187edbb8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='doggy', metadata={'source': 'doggy.txt'})]"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"all_docs"
]
},
{
"cell_type": "markdown",
"id": "d940bcb4-cf6d-4c21-a565-e7f53f6dacf1",
"metadata": {},
"source": [
"Using full mode will clean up the deleted content as well."
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "1b660eae-3bed-434d-a6f5-2aec96e5f0d6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 0, 'num_updated': 0, 'num_skipped': 1, 'num_deleted': 1}"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(all_docs, record_manager, vectorstore, cleanup=\"full\", source_id_key=\"source\")"
]
},
{
"cell_type": "markdown",
"id": "1a7ecdc9-df3c-4601-b2f3-50fdffc6e5f9",
"metadata": {},
"source": [
"## Source "
]
},
{
"cell_type": "markdown",
"id": "4002a4ac-02dd-4599-9b23-9b59f54237c8",
"metadata": {},
"source": [
"The metadata attribute contains a field called `source`. This source should be pointing at the *ultimate* provenance associated with the given document.\n",
"\n",
"For example, if these documents are representing chunks of some parent document, the `source` for both documents should be the same and reference the parent document.\n",
"\n",
"In general, `source` should always be specified. Only use a `None`, if you **never** intend to use `incremental` mode, and for some reason can't specify the `source` field correctly."
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "184d3051-7fd1-4db2-a1d5-218ac0e1e641",
"metadata": {},
"outputs": [],
"source": [
"from langchain_text_splitters import CharacterTextSplitter"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "11318248-ad2a-4ef0-bd9b-9d4dab97caba",
"metadata": {},
"outputs": [],
"source": [
"doc1 = Document(\n",
" page_content=\"kitty kitty kitty kitty kitty\", metadata={\"source\": \"kitty.txt\"}\n",
")\n",
"doc2 = Document(page_content=\"doggy doggy the doggy\", metadata={\"source\": \"doggy.txt\"})"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "2cbf0902-d17b-44c9-8983-e8d0e831f909",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='kitty kit', metadata={'source': 'kitty.txt'}),\n",
" Document(page_content='tty kitty ki', metadata={'source': 'kitty.txt'}),\n",
" Document(page_content='tty kitty', metadata={'source': 'kitty.txt'}),\n",
" Document(page_content='doggy doggy', metadata={'source': 'doggy.txt'}),\n",
" Document(page_content='the doggy', metadata={'source': 'doggy.txt'})]"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"new_docs = CharacterTextSplitter(\n",
" separator=\"t\", keep_separator=True, chunk_size=12, chunk_overlap=2\n",
").split_documents([doc1, doc2])\n",
"new_docs"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "0f9d9bc2-ea85-48ab-b4a2-351c8708b1d4",
"metadata": {},
"outputs": [],
"source": [
"_clear()"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "58781d81-f273-4aeb-8df6-540236826d00",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 5, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(\n",
" new_docs,\n",
" record_manager,\n",
" vectorstore,\n",
" cleanup=\"incremental\",\n",
" source_id_key=\"source\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "11b81cb6-5f04-499b-b125-1abb22d353bf",
"metadata": {},
"outputs": [],
"source": [
"changed_doggy_docs = [\n",
" Document(page_content=\"woof woof\", metadata={\"source\": \"doggy.txt\"}),\n",
" Document(page_content=\"woof woof woof\", metadata={\"source\": \"doggy.txt\"}),\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "ab1c0915-3f9e-42ac-bdb5-3017935c6e7f",
"metadata": {},
"source": [
"This should delete the old versions of documents associated with `doggy.txt` source and replace them with the new versions."
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "fec71cb5-6757-4b92-a306-62509f6e867d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 2}"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(\n",
" changed_doggy_docs,\n",
" record_manager,\n",
" vectorstore,\n",
" cleanup=\"incremental\",\n",
" source_id_key=\"source\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "876f5ab6-4b25-423e-8cff-f5a7a014395b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='woof woof', metadata={'source': 'doggy.txt'}),\n",
" Document(page_content='woof woof woof', metadata={'source': 'doggy.txt'}),\n",
" Document(page_content='tty kitty', metadata={'source': 'kitty.txt'}),\n",
" Document(page_content='tty kitty ki', metadata={'source': 'kitty.txt'}),\n",
" Document(page_content='kitty kit', metadata={'source': 'kitty.txt'})]"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vectorstore.similarity_search(\"dog\", k=30)"
]
},
{
"cell_type": "markdown",
"id": "c0af4d24-d735-4e5d-ad9b-a2e8b281f9f1",
"metadata": {},
"source": [
"## Using with loaders\n",
"\n",
"Indexing can accept either an iterable of documents or else any loader.\n",
"\n",
"**Attention:** The loader **must** set source keys correctly."
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "08b68357-27c0-4f07-a51d-61c986aeb359",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.document_loaders.base import BaseLoader\n",
"\n",
"\n",
"class MyCustomLoader(BaseLoader):\n",
" def lazy_load(self):\n",
" text_splitter = CharacterTextSplitter(\n",
" separator=\"t\", keep_separator=True, chunk_size=12, chunk_overlap=2\n",
" )\n",
" docs = [\n",
" Document(page_content=\"woof woof\", metadata={\"source\": \"doggy.txt\"}),\n",
" Document(page_content=\"woof woof woof\", metadata={\"source\": \"doggy.txt\"}),\n",
" ]\n",
" yield from text_splitter.split_documents(docs)\n",
"\n",
" def load(self):\n",
" return list(self.lazy_load())"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "5dae8e11-c0d6-4fc6-aa0e-68f8d92b5087",
"metadata": {},
"outputs": [],
"source": [
"_clear()"
]
},
{
"cell_type": "code",
"execution_count": 34,
"id": "d8d72f76-6d6e-4a7c-8fea-9bdec05af05b",
"metadata": {},
"outputs": [],
"source": [
"loader = MyCustomLoader()"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "945c45cc-5a8d-4bd7-9f36-4ebd4a50e08b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='woof woof', metadata={'source': 'doggy.txt'}),\n",
" Document(page_content='woof woof woof', metadata={'source': 'doggy.txt'})]"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"loader.load()"
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "dcb1ba71-db49-4140-ab4a-c5d64fc2578a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'num_added': 2, 'num_updated': 0, 'num_skipped': 0, 'num_deleted': 0}"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index(loader, record_manager, vectorstore, cleanup=\"full\", source_id_key=\"source\")"
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "441159c1-dd84-48d7-8599-37a65c9fb589",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='woof woof', metadata={'source': 'doggy.txt'}),\n",
" Document(page_content='woof woof woof', metadata={'source': 'doggy.txt'})]"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vectorstore.similarity_search(\"dog\", k=30)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,230 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8c5eb99a",
"metadata": {},
"source": [
"# How to inspect your runnables\n",
"\n",
"Once you create a runnable with [LangChain Expression Language](/docs/concepts/#langchain-expression-language), you may often want to inspect it to get a better sense for what is going on. This notebook covers some methods for doing so.\n",
"\n",
"This guide shows some ways you can programmatically introspect the internal steps of chains. If you are instead interested in debugging issues in your chain, see [this section](/docs/how_to/debugging) instead.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
"- [Chaining runnables](/docs/how_to/sequence/)\n",
"`} />\n",
"```\n",
"\n",
"First, let's create an example chain. We will create one that does retrieval:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d816e954",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain langchain-openai faiss-cpu tiktoken"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "139228c2",
"metadata": {},
"outputs": [],
"source": [
"from langchain.prompts import ChatPromptTemplate\n",
"from langchain_community.vectorstores import FAISS\n",
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.runnables import RunnablePassthrough\n",
"from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n",
"\n",
"vectorstore = FAISS.from_texts(\n",
" [\"harrison worked at kensho\"], embedding=OpenAIEmbeddings()\n",
")\n",
"retriever = vectorstore.as_retriever()\n",
"\n",
"template = \"\"\"Answer the question based only on the following context:\n",
"{context}\n",
"\n",
"Question: {question}\n",
"\"\"\"\n",
"prompt = ChatPromptTemplate.from_template(template)\n",
"\n",
"model = ChatOpenAI()\n",
"\n",
"chain = (\n",
" {\"context\": retriever, \"question\": RunnablePassthrough()}\n",
" | prompt\n",
" | model\n",
" | StrOutputParser()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "849e3c42",
"metadata": {},
"source": [
"## Get a graph\n",
"\n",
"You can use the `get_graph()` method to get a graph representation of the runnable:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2448b6c2",
"metadata": {},
"outputs": [],
"source": [
"chain.get_graph()"
]
},
{
"cell_type": "markdown",
"id": "065b02fb",
"metadata": {},
"source": [
"## Print a graph\n",
"\n",
"While that is not super legible, you can use the `print_ascii()` method to show that graph in a way that's easier to understand:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "d5ab1515",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" +---------------------------------+ \n",
" | Parallel<context,question>Input | \n",
" +---------------------------------+ \n",
" ** ** \n",
" *** *** \n",
" ** ** \n",
"+----------------------+ +-------------+ \n",
"| VectorStoreRetriever | | Passthrough | \n",
"+----------------------+ +-------------+ \n",
" ** ** \n",
" *** *** \n",
" ** ** \n",
" +----------------------------------+ \n",
" | Parallel<context,question>Output | \n",
" +----------------------------------+ \n",
" * \n",
" * \n",
" * \n",
" +--------------------+ \n",
" | ChatPromptTemplate | \n",
" +--------------------+ \n",
" * \n",
" * \n",
" * \n",
" +------------+ \n",
" | ChatOpenAI | \n",
" +------------+ \n",
" * \n",
" * \n",
" * \n",
" +-----------------+ \n",
" | StrOutputParser | \n",
" +-----------------+ \n",
" * \n",
" * \n",
" * \n",
" +-----------------------+ \n",
" | StrOutputParserOutput | \n",
" +-----------------------+ \n"
]
}
],
"source": [
"chain.get_graph().print_ascii()"
]
},
{
"cell_type": "markdown",
"id": "2babf851",
"metadata": {},
"source": [
"## Get the prompts\n",
"\n",
"You may want to see just the prompts that are used in a chain with the `get_prompts()` method:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "34b2118d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[ChatPromptTemplate(input_variables=['context', 'question'], messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], template='Answer the question based only on the following context:\\n{context}\\n\\nQuestion: {question}\\n'))])]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain.get_prompts()"
]
},
{
"cell_type": "markdown",
"id": "c5a74bd5",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now learned how to introspect your composed LCEL chains.\n",
"\n",
"Next, check out the other how-to guides on runnables in this section, or the related how-to guide on [debugging your chains](/docs/how_to/debugging)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ed965769",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,219 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b843b5c4",
"metadata": {},
"source": [
"# How to cache LLM responses\n",
"\n",
"LangChain provides an optional caching layer for LLMs. This is useful for two reasons:\n",
"\n",
"It can save you money by reducing the number of API calls you make to the LLM provider, if you're often requesting the same completion multiple times.\n",
"It can speed up your application by reducing the number of API calls you make to the LLM provider.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "0aa6d335",
"metadata": {},
"outputs": [],
"source": [
"from langchain.globals import set_llm_cache\n",
"from langchain_openai import OpenAI\n",
"\n",
"# To make the caching really obvious, lets use a slower model.\n",
"llm = OpenAI(model_name=\"gpt-3.5-turbo-instruct\", n=2, best_of=2)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "f168ff0d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 13.7 ms, sys: 6.54 ms, total: 20.2 ms\n",
"Wall time: 330 ms\n"
]
},
{
"data": {
"text/plain": [
"\"\\n\\nWhy couldn't the bicycle stand up by itself? Because it was two-tired!\""
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"from langchain.cache import InMemoryCache\n",
"\n",
"set_llm_cache(InMemoryCache())\n",
"\n",
"# The first time, it is not yet in cache, so it should take longer\n",
"llm.predict(\"Tell me a joke\")"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "ce7620fb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 436 µs, sys: 921 µs, total: 1.36 ms\n",
"Wall time: 1.36 ms\n"
]
},
{
"data": {
"text/plain": [
"\"\\n\\nWhy couldn't the bicycle stand up by itself? Because it was two-tired!\""
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"# The second time it is, so it goes faster\n",
"llm.predict(\"Tell me a joke\")"
]
},
{
"cell_type": "markdown",
"id": "4ab452f4",
"metadata": {},
"source": [
"## SQLite Cache"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "2e65de83",
"metadata": {},
"outputs": [],
"source": [
"!rm .langchain.db"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "0be83715",
"metadata": {},
"outputs": [],
"source": [
"# We can do the same thing with a SQLite cache\n",
"from langchain.cache import SQLiteCache\n",
"\n",
"set_llm_cache(SQLiteCache(database_path=\".langchain.db\"))"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "9b427ce7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 29.3 ms, sys: 17.3 ms, total: 46.7 ms\n",
"Wall time: 364 ms\n"
]
},
{
"data": {
"text/plain": [
"'\\n\\nWhy did the tomato turn red?\\n\\nBecause it saw the salad dressing!'"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"# The first time, it is not yet in cache, so it should take longer\n",
"llm.predict(\"Tell me a joke\")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "87f52611",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 4.58 ms, sys: 2.23 ms, total: 6.8 ms\n",
"Wall time: 4.68 ms\n"
]
},
{
"data": {
"text/plain": [
"'\\n\\nWhy did the tomato turn red?\\n\\nBecause it saw the salad dressing!'"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"# The second time it is, so it goes faster\n",
"llm.predict(\"Tell me a joke\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a9bb158",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,191 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e5715368",
"metadata": {},
"source": [
"# How to track token usage for LLMs\n",
"\n",
"This notebook goes over how to track your token usage for specific calls. It is currently only implemented for the OpenAI API.\n",
"\n",
"Let's first look at an extremely simple example of tracking token usage for a single LLM call."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "9455db35",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.callbacks import get_openai_callback\n",
"from langchain_openai import OpenAI"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "d1c55cc9",
"metadata": {},
"outputs": [],
"source": [
"llm = OpenAI(model_name=\"gpt-3.5-turbo-instruct\", n=2, best_of=2)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "31667d54",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tokens Used: 37\n",
"\tPrompt Tokens: 4\n",
"\tCompletion Tokens: 33\n",
"Successful Requests: 1\n",
"Total Cost (USD): $7.2e-05\n"
]
}
],
"source": [
"with get_openai_callback() as cb:\n",
" result = llm.invoke(\"Tell me a joke\")\n",
" print(cb)"
]
},
{
"cell_type": "markdown",
"id": "c0ab6d27",
"metadata": {},
"source": [
"Anything inside the context manager will get tracked. Here's an example of using it to track multiple calls in sequence."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "e09420f4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"72\n"
]
}
],
"source": [
"with get_openai_callback() as cb:\n",
" result = llm.invoke(\"Tell me a joke\")\n",
" result2 = llm.invoke(\"Tell me a joke\")\n",
" print(cb.total_tokens)"
]
},
{
"cell_type": "markdown",
"id": "d8186e7b",
"metadata": {},
"source": [
"If a chain or agent with multiple steps in it is used, it will track all those steps."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "5d1125c6",
"metadata": {},
"outputs": [],
"source": [
"from langchain.agents import AgentType, initialize_agent, load_tools\n",
"from langchain_openai import OpenAI\n",
"\n",
"llm = OpenAI(temperature=0)\n",
"tools = load_tools([\"serpapi\", \"llm-math\"], llm=llm)\n",
"agent = initialize_agent(\n",
" tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "2f98c536",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m I need to find out who Olivia Wilde's boyfriend is and then calculate his age raised to the 0.23 power.\n",
"Action: Search\n",
"Action Input: \"Olivia Wilde boyfriend\"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3m[\"Olivia Wilde and Harry Styles took fans by surprise with their whirlwind romance, which began when they met on the set of Don't Worry Darling.\", 'Olivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.', 'Olivia Wilde and Harry Styles were spotted early on in their relationship walking around London. (. Image ...', \"Looks like Olivia Wilde and Jason Sudeikis are starting 2023 on good terms. Amid their highly publicized custody battle and the actress' ...\", 'The two started dating after Wilde split up with actor Jason Sudeikisin 2020. However, their relationship came to an end last November.', \"Olivia Wilde and Harry Styles started dating during the filming of Don't Worry Darling. While the movie got a lot of backlash because of the ...\", \"Here's what we know so far about Harry Styles and Olivia Wilde's relationship.\", 'Olivia and the Grammy winner kept their romance out of the spotlight as their relationship began just two months after her split from ex-fiancé ...', \"Harry Styles and Olivia Wilde first met on the set of Don't Worry Darling and stepped out as a couple in January 2021. Relive all their biggest relationship ...\"]\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m Harry Styles is Olivia Wilde's boyfriend.\n",
"Action: Search\n",
"Action Input: \"Harry Styles age\"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3m29 years\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to calculate 29 raised to the 0.23 power.\n",
"Action: Calculator\n",
"Action Input: 29^0.23\u001b[0m\n",
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 2.169459462491557\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n",
"Final Answer: Harry Styles is Olivia Wilde's boyfriend and his current age raised to the 0.23 power is 2.169459462491557.\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"Total Tokens: 2205\n",
"Prompt Tokens: 2053\n",
"Completion Tokens: 152\n",
"Total Cost (USD): $0.0441\n"
]
}
],
"source": [
"with get_openai_callback() as cb:\n",
" response = agent.run(\n",
" \"Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?\"\n",
" )\n",
" print(f\"Total Tokens: {cb.total_tokens}\")\n",
" print(f\"Prompt Tokens: {cb.prompt_tokens}\")\n",
" print(f\"Completion Tokens: {cb.completion_tokens}\")\n",
" print(f\"Total Cost (USD): ${cb.total_cost}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "80ca77a3",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,178 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "78b45321-7740-4399-b2ad-459811131de3",
"metadata": {},
"source": [
"# How to get log probabilities from model calls\n",
"\n",
"Certain chat models can be configured to return token-level log probabilities representing the likelihood of a given token. This guide walks through how to get this information in LangChain.\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Chat models](/docs/concepts/#chat-models)\n",
"`} />\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "7f5016bf-2a7b-4140-9b80-8c35c7e5c0d5",
"metadata": {},
"source": [
"## OpenAI\n",
"\n",
"Install the LangChain x OpenAI package and set your API key"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe5143fe-84d3-4a91-bae8-629807bbe2cb",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain-openai"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "fd1a2bff-7ac8-46cb-ab95-72c616b45f2c",
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()"
]
},
{
"cell_type": "markdown",
"id": "f88ffa0d-f4a7-482c-88de-cbec501a79b1",
"metadata": {},
"source": [
"For the OpenAI API to return log probabilities we need to configure the `logprobs=True` param. Then, the logprobs are included on each output [`AIMessage`](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessage.html) as part of the `response_metadata`:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d1bf0a9a-e402-4931-ab53-32899f8e0326",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'token': 'I', 'bytes': [73], 'logprob': -0.26341408, 'top_logprobs': []},\n",
" {'token': \"'m\",\n",
" 'bytes': [39, 109],\n",
" 'logprob': -0.48584133,\n",
" 'top_logprobs': []},\n",
" {'token': ' just',\n",
" 'bytes': [32, 106, 117, 115, 116],\n",
" 'logprob': -0.23484154,\n",
" 'top_logprobs': []},\n",
" {'token': ' a',\n",
" 'bytes': [32, 97],\n",
" 'logprob': -0.0018291725,\n",
" 'top_logprobs': []},\n",
" {'token': ' computer',\n",
" 'bytes': [32, 99, 111, 109, 112, 117, 116, 101, 114],\n",
" 'logprob': -0.052299336,\n",
" 'top_logprobs': []}]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\").bind(logprobs=True)\n",
"\n",
"msg = llm.invoke((\"human\", \"how are you today\"))\n",
"\n",
"msg.response_metadata[\"logprobs\"][\"content\"][:5]"
]
},
{
"cell_type": "markdown",
"id": "d1ee1c29-d27e-4353-8c3c-2ed7e7f95ff5",
"metadata": {},
"source": [
"And are part of streamed Message chunks as well:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4bfaf309-3b23-43b7-b333-01fc4848992d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[]\n",
"[{'token': 'I', 'bytes': [73], 'logprob': -0.26593843, 'top_logprobs': []}]\n",
"[{'token': 'I', 'bytes': [73], 'logprob': -0.26593843, 'top_logprobs': []}, {'token': \"'m\", 'bytes': [39, 109], 'logprob': -0.3238896, 'top_logprobs': []}]\n",
"[{'token': 'I', 'bytes': [73], 'logprob': -0.26593843, 'top_logprobs': []}, {'token': \"'m\", 'bytes': [39, 109], 'logprob': -0.3238896, 'top_logprobs': []}, {'token': ' just', 'bytes': [32, 106, 117, 115, 116], 'logprob': -0.23778509, 'top_logprobs': []}]\n",
"[{'token': 'I', 'bytes': [73], 'logprob': -0.26593843, 'top_logprobs': []}, {'token': \"'m\", 'bytes': [39, 109], 'logprob': -0.3238896, 'top_logprobs': []}, {'token': ' just', 'bytes': [32, 106, 117, 115, 116], 'logprob': -0.23778509, 'top_logprobs': []}, {'token': ' a', 'bytes': [32, 97], 'logprob': -0.0022134194, 'top_logprobs': []}]\n"
]
}
],
"source": [
"ct = 0\n",
"full = None\n",
"for chunk in llm.stream((\"human\", \"how are you today\")):\n",
" if ct < 5:\n",
" full = chunk if full is None else full + chunk\n",
" if \"logprobs\" in full.response_metadata:\n",
" print(full.response_metadata[\"logprobs\"][\"content\"])\n",
" else:\n",
" break\n",
" ct += 1"
]
},
{
"cell_type": "markdown",
"id": "19766435",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now learned how to get logprobs from OpenAI models in LangChain.\n",
"\n",
"Next, check out the other how-to guides chat models in this section, like [how to get a model to return structured output](/docs/how_to/structured_output) or [how to track token usage](/docs/how_to/chat_token_usage_tracking)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,203 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fc0db1bc",
"metadata": {},
"source": [
"# How to reorder retrieved results to put most relevant documents not in the middle\n",
"\n",
"No matter the architecture of your model, there is a substantial performance degradation when you include 10+ retrieved documents.\n",
"In brief: When models must access relevant information in the middle of long contexts, they tend to ignore the provided documents.\n",
"See: https://arxiv.org/abs/2307.03172\n",
"\n",
"To avoid this issue you can re-order documents after retrieval to avoid performance degradation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74d1ebe8",
"metadata": {},
"outputs": [],
"source": [
"%pip install --upgrade --quiet sentence-transformers langchain-chroma langchain langchain-openai > /dev/null"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "49cbcd8e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='This is a document about the Boston Celtics'),\n",
" Document(page_content='The Celtics are my favourite team.'),\n",
" Document(page_content='L. Kornet is one of the best Celtics players.'),\n",
" Document(page_content='The Boston Celtics won the game by 20 points'),\n",
" Document(page_content='Larry Bird was an iconic NBA player.'),\n",
" Document(page_content='Elden Ring is one of the best games in the last 15 years.'),\n",
" Document(page_content='Basquetball is a great sport.'),\n",
" Document(page_content='I simply love going to the movies'),\n",
" Document(page_content='Fly me to the moon is one of my favourite songs.'),\n",
" Document(page_content='This is just a random text.')]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.chains import LLMChain, StuffDocumentsChain\n",
"from langchain.prompts import PromptTemplate\n",
"from langchain_chroma import Chroma\n",
"from langchain_community.document_transformers import (\n",
" LongContextReorder,\n",
")\n",
"from langchain_community.embeddings import HuggingFaceEmbeddings\n",
"from langchain_openai import OpenAI\n",
"\n",
"# Get embeddings.\n",
"embeddings = HuggingFaceEmbeddings(model_name=\"all-MiniLM-L6-v2\")\n",
"\n",
"texts = [\n",
" \"Basquetball is a great sport.\",\n",
" \"Fly me to the moon is one of my favourite songs.\",\n",
" \"The Celtics are my favourite team.\",\n",
" \"This is a document about the Boston Celtics\",\n",
" \"I simply love going to the movies\",\n",
" \"The Boston Celtics won the game by 20 points\",\n",
" \"This is just a random text.\",\n",
" \"Elden Ring is one of the best games in the last 15 years.\",\n",
" \"L. Kornet is one of the best Celtics players.\",\n",
" \"Larry Bird was an iconic NBA player.\",\n",
"]\n",
"\n",
"# Create a retriever\n",
"retriever = Chroma.from_texts(texts, embedding=embeddings).as_retriever(\n",
" search_kwargs={\"k\": 10}\n",
")\n",
"query = \"What can you tell me about the Celtics?\"\n",
"\n",
"# Get relevant documents ordered by relevance score\n",
"docs = retriever.get_relevant_documents(query)\n",
"docs"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "34fb9d6e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='The Celtics are my favourite team.'),\n",
" Document(page_content='The Boston Celtics won the game by 20 points'),\n",
" Document(page_content='Elden Ring is one of the best games in the last 15 years.'),\n",
" Document(page_content='I simply love going to the movies'),\n",
" Document(page_content='This is just a random text.'),\n",
" Document(page_content='Fly me to the moon is one of my favourite songs.'),\n",
" Document(page_content='Basquetball is a great sport.'),\n",
" Document(page_content='Larry Bird was an iconic NBA player.'),\n",
" Document(page_content='L. Kornet is one of the best Celtics players.'),\n",
" Document(page_content='This is a document about the Boston Celtics')]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Reorder the documents:\n",
"# Less relevant document will be at the middle of the list and more\n",
"# relevant elements at beginning / end.\n",
"reordering = LongContextReorder()\n",
"reordered_docs = reordering.transform_documents(docs)\n",
"\n",
"# Confirm that the 4 relevant documents are at beginning and end.\n",
"reordered_docs"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ceccab87",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'\\n\\nThe Celtics are referenced in four of the nine text extracts. They are mentioned as the favorite team of the author, the winner of a basketball game, a team with one of the best players, and a team with a specific player. Additionally, the last extract states that the document is about the Boston Celtics. This suggests that the Celtics are a basketball team, possibly from Boston, that is well-known and has had successful players and games in the past. '"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# We prepare and run a custom Stuff chain with reordered docs as context.\n",
"\n",
"# Override prompts\n",
"document_prompt = PromptTemplate(\n",
" input_variables=[\"page_content\"], template=\"{page_content}\"\n",
")\n",
"document_variable_name = \"context\"\n",
"llm = OpenAI()\n",
"stuff_prompt_override = \"\"\"Given this text extracts:\n",
"-----\n",
"{context}\n",
"-----\n",
"Please answer the following question:\n",
"{query}\"\"\"\n",
"prompt = PromptTemplate(\n",
" template=stuff_prompt_override, input_variables=[\"context\", \"query\"]\n",
")\n",
"\n",
"# Instantiate the chain\n",
"llm_chain = LLMChain(llm=llm, prompt=prompt)\n",
"chain = StuffDocumentsChain(\n",
" llm_chain=llm_chain,\n",
" document_prompt=document_prompt,\n",
" document_variable_name=document_variable_name,\n",
")\n",
"chain.run(input_documents=reordered_docs, query=query)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4696a97",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,287 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "70e9b619",
"metadata": {},
"source": [
"# How to split Markdown by Headers\n",
"\n",
"### Motivation\n",
"\n",
"Many chat or Q+A applications involve chunking input documents prior to embedding and vector storage.\n",
"\n",
"[These notes](https://www.pinecone.io/learn/chunking-strategies/) from Pinecone provide some useful tips:\n",
"\n",
"```\n",
"When a full paragraph or document is embedded, the embedding process considers both the overall context and the relationships between the sentences and phrases within the text. This can result in a more comprehensive vector representation that captures the broader meaning and themes of the text.\n",
"```\n",
" \n",
"As mentioned, chunking often aims to keep text with common context together. With this in mind, we might want to specifically honor the structure of the document itself. For example, a markdown file is organized by headers. Creating chunks within specific header groups is an intuitive idea. To address this challenge, we can use [MarkdownHeaderTextSplitter](https://api.python.langchain.com/en/latest/markdown/langchain_text_splitters.markdown.MarkdownHeaderTextSplitter.html). This will split a markdown file by a specified set of headers. \n",
"\n",
"For example, if we want to split this markdown:\n",
"```\n",
"md = '# Foo\\n\\n ## Bar\\n\\nHi this is Jim \\nHi this is Joe\\n\\n ## Baz\\n\\n Hi this is Molly' \n",
"```\n",
" \n",
"We can specify the headers to split on:\n",
"```\n",
"[(\"#\", \"Header 1\"),(\"##\", \"Header 2\")]\n",
"```\n",
"\n",
"And content is grouped or split by common headers:\n",
"```\n",
"{'content': 'Hi this is Jim \\nHi this is Joe', 'metadata': {'Header 1': 'Foo', 'Header 2': 'Bar'}}\n",
"{'content': 'Hi this is Molly', 'metadata': {'Header 1': 'Foo', 'Header 2': 'Baz'}}\n",
"```\n",
"\n",
"Let's have a look at some examples below.\n",
"\n",
"### Basic usage:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0cd11819-4d4e-4fc1-aa85-faf69d24db89",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain-text-splitters"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "ceb3c1fb",
"metadata": {
"ExecuteTime": {
"end_time": "2023-09-25T19:12:27.243781300Z",
"start_time": "2023-09-25T19:12:24.943559400Z"
}
},
"outputs": [],
"source": [
"from langchain_text_splitters import MarkdownHeaderTextSplitter"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2ae3649b",
"metadata": {
"ExecuteTime": {
"end_time": "2023-09-25T19:12:31.917013600Z",
"start_time": "2023-09-25T19:12:31.905694500Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Hi this is Jim \\nHi this is Joe', metadata={'Header 1': 'Foo', 'Header 2': 'Bar'}),\n",
" Document(page_content='Hi this is Lance', metadata={'Header 1': 'Foo', 'Header 2': 'Bar', 'Header 3': 'Boo'}),\n",
" Document(page_content='Hi this is Molly', metadata={'Header 1': 'Foo', 'Header 2': 'Baz'})]"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"markdown_document = \"# Foo\\n\\n ## Bar\\n\\nHi this is Jim\\n\\nHi this is Joe\\n\\n ### Boo \\n\\n Hi this is Lance \\n\\n ## Baz\\n\\n Hi this is Molly\"\n",
"\n",
"headers_to_split_on = [\n",
" (\"#\", \"Header 1\"),\n",
" (\"##\", \"Header 2\"),\n",
" (\"###\", \"Header 3\"),\n",
"]\n",
"\n",
"markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on)\n",
"md_header_splits = markdown_splitter.split_text(markdown_document)\n",
"md_header_splits"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "aac1738c",
"metadata": {
"ExecuteTime": {
"end_time": "2023-09-25T19:12:35.672077100Z",
"start_time": "2023-09-25T19:12:35.666731400Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"langchain_core.documents.base.Document"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(md_header_splits[0])"
]
},
{
"cell_type": "markdown",
"id": "102aad57-7bef-42d3-ab4e-b50d6dc11718",
"metadata": {},
"source": [
"By default, `MarkdownHeaderTextSplitter` strips headers being split on from the output chunk's content. This can be disabled by setting `strip_headers = False`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "9fce45ba-a4be-4a69-ad27-f5ff195c4fd7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='# Foo \\n## Bar \\nHi this is Jim \\nHi this is Joe', metadata={'Header 1': 'Foo', 'Header 2': 'Bar'}),\n",
" Document(page_content='### Boo \\nHi this is Lance', metadata={'Header 1': 'Foo', 'Header 2': 'Bar', 'Header 3': 'Boo'}),\n",
" Document(page_content='## Baz \\nHi this is Molly', metadata={'Header 1': 'Foo', 'Header 2': 'Baz'})]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on, strip_headers=False)\n",
"md_header_splits = markdown_splitter.split_text(markdown_document)\n",
"md_header_splits"
]
},
{
"cell_type": "markdown",
"id": "aa67e0cc-d721-4536-9c7a-9fa3a7a69cbe",
"metadata": {},
"source": [
"### How to return Markdown lines as separate documents\n",
"\n",
"By default, `MarkdownHeaderTextSplitter` aggregates lines based on the headers specified in `headers_to_split_on`. We can disable this by specifying `return_each_line`:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "940bb609-c9c3-4593-ac2d-d825c80ceb44",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Hi this is Jim', metadata={'Header 1': 'Foo', 'Header 2': 'Bar'}),\n",
" Document(page_content='Hi this is Joe', metadata={'Header 1': 'Foo', 'Header 2': 'Bar'}),\n",
" Document(page_content='Hi this is Lance', metadata={'Header 1': 'Foo', 'Header 2': 'Bar', 'Header 3': 'Boo'}),\n",
" Document(page_content='Hi this is Molly', metadata={'Header 1': 'Foo', 'Header 2': 'Baz'})]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"markdown_splitter = MarkdownHeaderTextSplitter(\n",
" headers_to_split_on,\n",
" return_each_line=True,\n",
")\n",
"md_header_splits = markdown_splitter.split_text(markdown_document)\n",
"md_header_splits"
]
},
{
"cell_type": "markdown",
"id": "9bd8977a",
"metadata": {},
"source": [
"Note that here header information is retained in the `metadata` for each document.\n",
"\n",
"### How to constrain chunk size:\n",
"\n",
"Within each markdown group we can then apply any text splitter we want, such as `RecursiveCharacterTextSplitter`, which allows for further control of the chunk size."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "6f1f62bf-2653-4361-9bb0-964d86cb14db",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='# Intro \\n## History \\nMarkdown[9] is a lightweight markup language for creating formatted text using a plain-text editor. John Gruber created Markdown in 2004 as a markup language that is appealing to human readers in its source code form.[9]', metadata={'Header 1': 'Intro', 'Header 2': 'History'}),\n",
" Document(page_content='Markdown is widely used in blogging, instant messaging, online forums, collaborative software, documentation pages, and readme files.', metadata={'Header 1': 'Intro', 'Header 2': 'History'}),\n",
" Document(page_content='## Rise and divergence \\nAs Markdown popularity grew rapidly, many Markdown implementations appeared, driven mostly by the need for \\nadditional features such as tables, footnotes, definition lists,[note 1] and Markdown inside HTML blocks.', metadata={'Header 1': 'Intro', 'Header 2': 'Rise and divergence'}),\n",
" Document(page_content='#### Standardization \\nFrom 2012, a group of people, including Jeff Atwood and John MacFarlane, launched what Atwood characterised as a standardisation effort.', metadata={'Header 1': 'Intro', 'Header 2': 'Rise and divergence'}),\n",
" Document(page_content='## Implementations \\nImplementations of Markdown are available for over a dozen programming languages.', metadata={'Header 1': 'Intro', 'Header 2': 'Implementations'})]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"markdown_document = \"# Intro \\n\\n ## History \\n\\n Markdown[9] is a lightweight markup language for creating formatted text using a plain-text editor. John Gruber created Markdown in 2004 as a markup language that is appealing to human readers in its source code form.[9] \\n\\n Markdown is widely used in blogging, instant messaging, online forums, collaborative software, documentation pages, and readme files. \\n\\n ## Rise and divergence \\n\\n As Markdown popularity grew rapidly, many Markdown implementations appeared, driven mostly by the need for \\n\\n additional features such as tables, footnotes, definition lists,[note 1] and Markdown inside HTML blocks. \\n\\n #### Standardization \\n\\n From 2012, a group of people, including Jeff Atwood and John MacFarlane, launched what Atwood characterised as a standardisation effort. \\n\\n ## Implementations \\n\\n Implementations of Markdown are available for over a dozen programming languages.\"\n",
"\n",
"headers_to_split_on = [\n",
" (\"#\", \"Header 1\"),\n",
" (\"##\", \"Header 2\"),\n",
"]\n",
"\n",
"# MD splits\n",
"markdown_splitter = MarkdownHeaderTextSplitter(\n",
" headers_to_split_on=headers_to_split_on, strip_headers=False\n",
")\n",
"md_header_splits = markdown_splitter.split_text(markdown_document)\n",
"\n",
"# Char-level splits\n",
"from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
"\n",
"chunk_size = 250\n",
"chunk_overlap = 30\n",
"text_splitter = RecursiveCharacterTextSplitter(\n",
" chunk_size=chunk_size, chunk_overlap=chunk_overlap\n",
")\n",
"\n",
"# Split\n",
"splits = text_splitter.split_documents(md_header_splits)\n",
"splits"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,675 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "6a4becbd-238e-4c1d-a02d-08e61fbc3763",
"metadata": {},
"source": [
"# How to add message history\n",
"\n",
"Passing conversation state into and out a chain is vital when building a chatbot. The [`RunnableWithMessageHistory`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html#langchain_core.runnables.history.RunnableWithMessageHistory) class lets us add message history to certain types of chains. It wraps another Runnable and manages the chat message history for it.\n",
"\n",
"Specifically, it can be used for any Runnable that takes as input one of:\n",
"\n",
"* a sequence of [`BaseMessages`](/docs/concepts/#message-types)\n",
"* a dict with a key that takes a sequence of `BaseMessages`\n",
"* a dict with a key that takes the latest message(s) as a string or sequence of `BaseMessages`, and a separate key that takes historical messages\n",
"\n",
"And returns as output one of\n",
"\n",
"* a string that can be treated as the contents of an `AIMessage`\n",
"* a sequence of `BaseMessage`\n",
"* a dict with a key that contains a sequence of `BaseMessage`\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
"- [Chaining runnables](/docs/how_to/sequence/)\n",
"- [Configuring chain parameters at runtime](/docs/how_to/configure)\n",
"- [Prompt templates](/docs/concepts/#prompt-templates)\n",
"- [Chat Messages](/docs/concepts/#message-types)\n",
"`} />\n",
"```\n",
"\n",
"Let's take a look at some examples to see how it works. First we construct a runnable (which here accepts a dict as input and returns a message as output):\n",
"\n",
"```{=mdx}\n",
"import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
"\n",
"<ChatModelTabs\n",
" customVarName=\"llm\"\n",
"/>\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "6489f585",
"metadata": {},
"outputs": [],
"source": [
"# | output: false\n",
"# | echo: false\n",
"\n",
"%pip install -qU langchain langchain_anthropic\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"\n",
"os.environ[\"ANTHROPIC_API_KEY\"] = getpass()\n",
"\n",
"model = ChatAnthropic(model=\"claude-3-haiku-20240307\", temperature=0)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2ed413b4-33a1-48ee-89b0-2d4917ec101a",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"from langchain_openai.chat_models import ChatOpenAI\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You're an assistant who's good at {ability}. Respond in 20 words or fewer\",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"history\"),\n",
" (\"human\", \"{input}\"),\n",
" ]\n",
")\n",
"runnable = prompt | model"
]
},
{
"cell_type": "markdown",
"id": "9fd175e1-c7b8-4929-a57e-3331865fe7aa",
"metadata": {},
"source": [
"To manage the message history, we will need:\n",
"1. This runnable;\n",
"2. A callable that returns an instance of `BaseChatMessageHistory`.\n",
"\n",
"Check out the [memory integrations](https://integrations.langchain.com/memory) page for implementations of chat message histories using Redis and other providers. Here we demonstrate using an in-memory `ChatMessageHistory` as well as more persistent storage using `RedisChatMessageHistory`."
]
},
{
"cell_type": "markdown",
"id": "3d83adad-9672-496d-9f25-5747e7b8c8bb",
"metadata": {},
"source": [
"## In-memory\n",
"\n",
"Below we show a simple example in which the chat history lives in memory, in this case via a global Python dict.\n",
"\n",
"We construct a callable `get_session_history` that references this dict to return an instance of `ChatMessageHistory`. The arguments to the callable can be specified by passing a configuration to the `RunnableWithMessageHistory` at runtime. By default, the configuration parameter is expected to be a single string `session_id`. This can be adjusted via the `history_factory_config` kwarg.\n",
"\n",
"Using the single-parameter default:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "54348d02-d8ee-440c-bbf9-41bc0fbbc46c",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.chat_message_histories import ChatMessageHistory\n",
"from langchain_core.chat_history import BaseChatMessageHistory\n",
"from langchain_core.runnables.history import RunnableWithMessageHistory\n",
"\n",
"store = {}\n",
"\n",
"\n",
"def get_session_history(session_id: str) -> BaseChatMessageHistory:\n",
" if session_id not in store:\n",
" store[session_id] = ChatMessageHistory()\n",
" return store[session_id]\n",
"\n",
"\n",
"with_message_history = RunnableWithMessageHistory(\n",
" runnable,\n",
" get_session_history,\n",
" input_messages_key=\"input\",\n",
" history_messages_key=\"history\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "01acb505-3fd3-4ab4-9f04-5ea07e81542e",
"metadata": {},
"source": [
"Note that we've specified `input_messages_key` (the key to be treated as the latest input message) and `history_messages_key` (the key to add historical messages to).\n",
"\n",
"When invoking this new runnable, we specify the corresponding chat history via a configuration parameter:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "01384412-f08e-4634-9edb-3f46f475b582",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='Cosine is a trigonometric function that represents the ratio of the adjacent side to the hypotenuse of a right triangle.', response_metadata={'id': 'msg_017rAM9qrBTSdJ5i1rwhB7bT', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 32, 'output_tokens': 31}}, id='run-65e94a5e-a804-40de-ba88-d01b6cd06864-0')"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"with_message_history.invoke(\n",
" {\"ability\": \"math\", \"input\": \"What does cosine mean?\"},\n",
" config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "954688a2-9a3f-47ee-a9e8-fa0c83e69477",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='Cosine is a trigonometric function that represents the ratio of the adjacent side to the hypotenuse of a right triangle.', response_metadata={'id': 'msg_017hK1Q63ganeQZ9wdeqruLP', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 68, 'output_tokens': 31}}, id='run-a42177ef-b04a-4968-8606-446fb465b943-0')"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Remembers\n",
"with_message_history.invoke(\n",
" {\"ability\": \"math\", \"input\": \"What?\"},\n",
" config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "39350d7c-2641-4744-bc2a-fd6a57c4ea90",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content=\"I'm an AI assistant skilled in mathematics. How can I help you with a math-related task?\", response_metadata={'id': 'msg_01AYwfQ6SH5qz8ZQMW3nYtGU', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 28, 'output_tokens': 24}}, id='run-c57d93e3-305f-4c0e-bdb9-ef82f5b49f61-0')"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# New session_id --> does not remember.\n",
"with_message_history.invoke(\n",
" {\"ability\": \"math\", \"input\": \"What?\"},\n",
" config={\"configurable\": {\"session_id\": \"def234\"}},\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d29497be-3366-408d-bbb9-d4a8bf4ef37c",
"metadata": {},
"source": [
"The configuration parameters by which we track message histories can be customized by passing in a list of ``ConfigurableFieldSpec`` objects to the ``history_factory_config`` parameter. Below, we use two parameters: a `user_id` and `conversation_id`."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "1c89daee-deff-4fdf-86a3-178f7d8ef536",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='Hello! How can I assist you with math today?', response_metadata={'id': 'msg_01UdhnwghuSE7oRM57STFhHL', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 27, 'output_tokens': 14}}, id='run-3d53f67a-4ea7-4d78-8e67-37db43d4af5d-0')"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.runnables import ConfigurableFieldSpec\n",
"\n",
"store = {}\n",
"\n",
"\n",
"def get_session_history(user_id: str, conversation_id: str) -> BaseChatMessageHistory:\n",
" if (user_id, conversation_id) not in store:\n",
" store[(user_id, conversation_id)] = ChatMessageHistory()\n",
" return store[(user_id, conversation_id)]\n",
"\n",
"\n",
"with_message_history = RunnableWithMessageHistory(\n",
" runnable,\n",
" get_session_history,\n",
" input_messages_key=\"input\",\n",
" history_messages_key=\"history\",\n",
" history_factory_config=[\n",
" ConfigurableFieldSpec(\n",
" id=\"user_id\",\n",
" annotation=str,\n",
" name=\"User ID\",\n",
" description=\"Unique identifier for the user.\",\n",
" default=\"\",\n",
" is_shared=True,\n",
" ),\n",
" ConfigurableFieldSpec(\n",
" id=\"conversation_id\",\n",
" annotation=str,\n",
" name=\"Conversation ID\",\n",
" description=\"Unique identifier for the conversation.\",\n",
" default=\"\",\n",
" is_shared=True,\n",
" ),\n",
" ],\n",
")\n",
"\n",
"with_message_history.invoke(\n",
" {\"ability\": \"math\", \"input\": \"Hello\"},\n",
" config={\"configurable\": {\"user_id\": \"123\", \"conversation_id\": \"1\"}},\n",
")"
]
},
{
"cell_type": "markdown",
"id": "18f1a459-3f88-4ee6-8542-76a907070dd6",
"metadata": {},
"source": [
"### Examples with runnables of different signatures\n",
"\n",
"The above runnable takes a dict as input and returns a BaseMessage. Below we show some alternatives."
]
},
{
"cell_type": "markdown",
"id": "48eae1bf-b59d-4a61-8e62-b6dbf667e866",
"metadata": {},
"source": [
"#### Messages input, dict output"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "17733d4f-3a32-4055-9d44-5d58b9446a26",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'output_message': AIMessage(content='Simone de Beauvoir was a prominent French existentialist philosopher who had some key beliefs about free will:\\n\\n1. Radical Freedom: De Beauvoir believed that humans have radical freedom - the ability to choose and define themselves through their actions. She rejected determinism and believed that we are not simply products of our biology, upbringing, or social circumstances.\\n\\n2. Ambiguity of the Human Condition: However, de Beauvoir also recognized the ambiguity of the human condition. While we have radical freedom, we are also situated beings constrained by our facticity (our given circumstances and limitations). This creates a tension and anguish in the human experience.\\n\\n3. Responsibility and Bad Faith: With this radical freedom comes great responsibility. De Beauvoir criticized \"bad faith\" - the tendency of people to deny their freedom and responsibility by making excuses or hiding behind social roles and norms.\\n\\n4. Ethical Engagement: For de Beauvoir, true freedom and authenticity required ethical engagement with the world and with others. We must take responsibility for our choices and their impact on others.\\n\\nOverall, de Beauvoir saw free will as a core aspect of the human condition, but one that is fraught with difficulty and ambiguity. Her philosophy emphasized the importance of owning our freedom and using it to ethically shape our lives and world.', response_metadata={'id': 'msg_01A78LdxxsCm6uR8vcAdMQBt', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 20, 'output_tokens': 293}}, id='run-9447a229-5d17-4b20-a48b-7507b78b225a-0')}"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import HumanMessage\n",
"from langchain_core.runnables import RunnableParallel\n",
"\n",
"chain = RunnableParallel({\"output_message\": model})\n",
"\n",
"\n",
"def get_session_history(session_id: str) -> BaseChatMessageHistory:\n",
" if session_id not in store:\n",
" store[session_id] = ChatMessageHistory()\n",
" return store[session_id]\n",
"\n",
"\n",
"with_message_history = RunnableWithMessageHistory(\n",
" chain,\n",
" get_session_history,\n",
" output_messages_key=\"output_message\",\n",
")\n",
"\n",
"with_message_history.invoke(\n",
" [HumanMessage(content=\"What did Simone de Beauvoir believe about free will\")],\n",
" config={\"configurable\": {\"session_id\": \"baz\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "efb57ef5-91f9-426b-84b9-b77f071a9dd7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'output_message': AIMessage(content=\"Simone de Beauvoir's views on free will were quite similar, but not identical, to those of her long-time partner Jean-Paul Sartre, another prominent existentialist philosopher.\\n\\nKey similarities:\\n\\n1. Radical Freedom: Both de Beauvoir and Sartre believed that humans have radical, unconditioned freedom to choose and define themselves.\\n\\n2. Rejection of Determinism: They both rejected deterministic views that see humans as products of their circumstances or biology.\\n\\n3. Emphasis on Responsibility: They agreed that with radical freedom comes great responsibility for one's choices and their consequences.\\n\\nKey differences:\\n\\n1. Ambiguity of the Human Condition: While Sartre emphasized the pure, unconditioned nature of human freedom, de Beauvoir recognized the ambiguity of the human condition - our freedom is constrained by our facticity (circumstances).\\n\\n2. Ethical Engagement: De Beauvoir placed more emphasis on the importance of ethical engagement with the world and others, whereas Sartre's focus was more on the individual's freedom.\\n\\n3. Gendered Perspectives: As a woman, de Beauvoir's perspective was more attuned to issues of gender and the lived experience of women, which shaped her views on freedom and ethics.\\n\\nSo in summary, while Sartre and de Beauvoir shared a core existentialist philosophy centered on radical human freedom, de Beauvoir's thought incorporated a greater recognition of the ambiguity and ethical dimensions of the human condition. This reflected her distinct feminist and phenomenological approach.\", response_metadata={'id': 'msg_01U6X3KNPufVg3zFvnx24eKq', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 324, 'output_tokens': 338}}, id='run-c4a984bd-33c6-4e26-a4d1-d58b666d065c-0')}"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"with_message_history.invoke(\n",
" [HumanMessage(content=\"How did this compare to Sartre\")],\n",
" config={\"configurable\": {\"session_id\": \"baz\"}},\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a39eac5f-a9d8-4729-be06-5e7faf0c424d",
"metadata": {},
"source": [
"#### Messages input, messages output"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "e45bcd95-e31f-4a9a-967a-78f96e8da881",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"RunnableWithMessageHistory(bound=RunnableBinding(bound=RunnableBinding(bound=RunnableLambda(_enter_history), config={'run_name': 'load_history'})\n",
"| RunnableBinding(bound=ChatAnthropic(model='claude-3-haiku-20240307', temperature=0.0, anthropic_api_url='https://api.anthropic.com', anthropic_api_key=SecretStr('**********'), _client=<anthropic.Anthropic object at 0x1077ff5b0>, _async_client=<anthropic.AsyncAnthropic object at 0x1321c71f0>), config_factories=[<function Runnable.with_listeners.<locals>.<lambda> at 0x1473dd000>]), config={'run_name': 'RunnableWithMessageHistory'}), get_session_history=<function get_session_history at 0x1374c7be0>, history_factory_config=[ConfigurableFieldSpec(id='session_id', annotation=<class 'str'>, name='Session ID', description='Unique identifier for a session.', default='', is_shared=True, dependencies=None)])"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"RunnableWithMessageHistory(\n",
" model,\n",
" get_session_history,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "04daa921-a2d1-40f9-8cd1-ae4e9a4163a7",
"metadata": {},
"source": [
"#### Dict with single key for all messages input, messages output"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "27157f15-9fb0-4167-9870-f4d7f234b3cb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"RunnableWithMessageHistory(bound=RunnableBinding(bound=RunnableBinding(bound=RunnableAssign(mapper={\n",
" input_messages: RunnableBinding(bound=RunnableLambda(_enter_history), config={'run_name': 'load_history'})\n",
"}), config={'run_name': 'insert_history'})\n",
"| RunnableBinding(bound=RunnableLambda(itemgetter('input_messages'))\n",
" | ChatAnthropic(model='claude-3-haiku-20240307', temperature=0.0, anthropic_api_url='https://api.anthropic.com', anthropic_api_key=SecretStr('**********'), _client=<anthropic.Anthropic object at 0x1077ff5b0>, _async_client=<anthropic.AsyncAnthropic object at 0x1321c71f0>), config_factories=[<function Runnable.with_listeners.<locals>.<lambda> at 0x1473df6d0>]), config={'run_name': 'RunnableWithMessageHistory'}), get_session_history=<function get_session_history at 0x1374c7be0>, input_messages_key='input_messages', history_factory_config=[ConfigurableFieldSpec(id='session_id', annotation=<class 'str'>, name='Session ID', description='Unique identifier for a session.', default='', is_shared=True, dependencies=None)])"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from operator import itemgetter\n",
"\n",
"RunnableWithMessageHistory(\n",
" itemgetter(\"input_messages\") | model,\n",
" get_session_history,\n",
" input_messages_key=\"input_messages\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "418ca7af-9ed9-478c-8bca-cba0de2ca61e",
"metadata": {},
"source": [
"## Persistent storage"
]
},
{
"cell_type": "markdown",
"id": "76799a13-d99a-4c4f-91f2-db699e40b8df",
"metadata": {},
"source": [
"In many cases it is preferable to persist conversation histories. `RunnableWithMessageHistory` is agnostic as to how the `get_session_history` callable retrieves its chat message histories. See [here](https://github.com/langchain-ai/langserve/blob/main/examples/chat_with_persistence_and_user/server.py) for an example using a local filesystem. Below we demonstrate how one could use Redis. Check out the [memory integrations](https://integrations.langchain.com/memory) page for implementations of chat message histories using other providers."
]
},
{
"cell_type": "markdown",
"id": "6bca45e5-35d9-4603-9ca9-6ac0ce0e35cd",
"metadata": {},
"source": [
"### Setup\n",
"\n",
"We'll need to install Redis if it's not installed already:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "477d04b3-c2b6-4ba5-962f-492c0d625cd5",
"metadata": {},
"outputs": [],
"source": [
"%pip install --upgrade --quiet redis"
]
},
{
"cell_type": "markdown",
"id": "6a0ec9e0-7b1c-4c6f-b570-e61d520b47c6",
"metadata": {},
"source": [
"Start a local Redis Stack server if we don't have an existing Redis deployment to connect to:\n",
"```bash\n",
"docker run -d -p 6379:6379 -p 8001:8001 redis/redis-stack:latest\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cd6a250e-17fe-4368-a39d-1fe6b2cbde68",
"metadata": {},
"outputs": [],
"source": [
"REDIS_URL = \"redis://localhost:6379/0\""
]
},
{
"cell_type": "markdown",
"id": "36f43b87-655c-4f64-aa7b-bd8c1955d8e5",
"metadata": {},
"source": [
"### [LangSmith](/docs/langsmith)\n",
"\n",
"LangSmith is especially useful for something like message history injection, where it can be hard to otherwise understand what the inputs are to various parts of the chain.\n",
"\n",
"Note that LangSmith is not needed, but it is helpful.\n",
"If you do want to use LangSmith, after you sign up at the link above, make sure to uncoment the below and set your environment variables to start logging traces:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2afc1556-8da1-4499-ba11-983b66c58b18",
"metadata": {},
"outputs": [],
"source": [
"# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
"# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()"
]
},
{
"cell_type": "markdown",
"id": "f9d81796-ce61-484c-89e2-6c567d5e54ef",
"metadata": {},
"source": [
"Updating the message history implementation just requires us to define a new callable, this time returning an instance of `RedisChatMessageHistory`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ca7c64d8-e138-4ef8-9734-f82076c47d80",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.chat_message_histories import RedisChatMessageHistory\n",
"\n",
"\n",
"def get_message_history(session_id: str) -> RedisChatMessageHistory:\n",
" return RedisChatMessageHistory(session_id, url=REDIS_URL)\n",
"\n",
"\n",
"with_message_history = RunnableWithMessageHistory(\n",
" runnable,\n",
" get_message_history,\n",
" input_messages_key=\"input\",\n",
" history_messages_key=\"history\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "37eefdec-9901-4650-b64c-d3c097ed5f4d",
"metadata": {},
"source": [
"We can invoke as before:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a85bcc22-ca4c-4ad5-9440-f94be7318f3e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='Cosine is a trigonometric function that represents the ratio of the adjacent side to the hypotenuse in a right triangle.')"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"with_message_history.invoke(\n",
" {\"ability\": \"math\", \"input\": \"What does cosine mean?\"},\n",
" config={\"configurable\": {\"session_id\": \"foobar\"}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab29abd3-751f-41ce-a1b0-53f6b565e79d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='The inverse of cosine is the arccosine function, denoted as acos or cos^-1, which gives the angle corresponding to a given cosine value.')"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"with_message_history.invoke(\n",
" {\"ability\": \"math\", \"input\": \"What's its inverse\"},\n",
" config={\"configurable\": {\"session_id\": \"foobar\"}},\n",
")"
]
},
{
"cell_type": "markdown",
"id": "da3d1feb-b4bb-4624-961c-7db2e1180df7",
"metadata": {},
"source": [
":::{.callout-tip}\n",
"\n",
"[Langsmith trace](https://smith.langchain.com/public/bd73e122-6ec1-48b2-82df-e6483dc9cb63/r)\n",
"\n",
":::"
]
},
{
"cell_type": "markdown",
"id": "61d5115e-64a1-4ad5-b676-8afd4ef6093e",
"metadata": {},
"source": [
"Looking at the Langsmith trace for the second call, we can see that when constructing the prompt, a \"history\" variable has been injected which is a list of two messages (our first input and first output)."
]
},
{
"cell_type": "markdown",
"id": "fd510b68",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You have now learned one way to manage message history for a runnable.\n",
"\n",
"To learn more, see the other how-to guides on runnables in this section."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,631 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "457cdc67-1893-4653-8b0c-b185a5947e74",
"metadata": {},
"source": [
"# How to migrate from legacy LangChain agents to LangGraph\n",
"\n",
"Here we focus on how to move from legacy LangChain agents to LangGraph agents.\n",
"LangChain agents (the AgentExecutor in particular) have multiple configuration parameters.\n",
"In this notebook we will show how those parameters map to the LangGraph `chat_agent_executor`."
]
},
{
"cell_type": "markdown",
"id": "8e50635c-1671-46e6-be65-ce95f8167c2f",
"metadata": {},
"source": [
"## Basic Usage\n",
"\n",
"First, let's define a model and tool."
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "1e425fea-2796-4b99-bee6-9a6ffe73f756",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.tools import tool\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"model = ChatOpenAI()\n",
"\n",
"\n",
"@tool\n",
"def magic_function(input: int) -> int:\n",
" \"\"\"Applies a magic function to an input.\"\"\"\n",
" return input + 2\n",
"\n",
"\n",
"tools = [magic_function]\n",
"\n",
"\n",
"query = \"what is the value of magic_function(3)?\""
]
},
{
"cell_type": "markdown",
"id": "af002033-fe51-4d14-b47c-3e9b483c8395",
"metadata": {},
"source": [
"For AgentExecutor, we define a prompt with a placeholder for the agent's scratchpad. The agent can be invoked as follows:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "03ea357c-9c36-4464-b2cc-27bd150e1554",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'what is the value of magic_function(3)?',\n",
" 'output': 'The value of `magic_function(3)` is 5.'}"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.agents import AgentExecutor, create_tool_calling_agent\n",
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", \"You are a helpful assistant\"),\n",
" (\"human\", \"{input}\"),\n",
" MessagesPlaceholder(\"agent_scratchpad\"),\n",
" ]\n",
")\n",
"\n",
"\n",
"agent = create_tool_calling_agent(model, tools, prompt)\n",
"agent_executor = AgentExecutor(agent=agent, tools=tools)\n",
"\n",
"agent_executor.invoke({\"input\": query})"
]
},
{
"cell_type": "markdown",
"id": "94205f3b-fd2b-4fd7-af69-0a3fc313dc88",
"metadata": {},
"source": [
"LangGraph's `chat_agent_executor` manages a state that is defined by a list of messages. It will continue to process the list until there are no tool calls in the agent's output. To kick it off, we input a list of messages. The output will contain the entire state of the graph-- in this case, the conversation history.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "53a3737a-d167-4255-89bf-20ac37f89a3e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'what is the value of magic_function(3)?',\n",
" 'output': 'The value of the magic function with input 3 is 5.'}"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langgraph.prebuilt import chat_agent_executor\n",
"\n",
"app = chat_agent_executor.create_tool_calling_executor(model, tools)\n",
"\n",
"\n",
"messages = app.invoke({\"messages\": [(\"human\", query)]})\n",
"{\n",
" \"input\": query,\n",
" \"output\": messages[\"messages\"][-1].content,\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "74ecebe3-512e-409c-a661-bdd5b0a2b782",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'Pardon?',\n",
" 'output': 'The value of the magic function with input 3 is 5.'}"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"message_history = messages[\"messages\"]\n",
"\n",
"new_query = \"Pardon?\"\n",
"\n",
"messages = app.invoke({\"messages\": message_history + [(\"human\", new_query)]})\n",
"{\n",
" \"input\": new_query,\n",
" \"output\": messages[\"messages\"][-1].content,\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "f4466a4d-e55e-4ece-bee8-2269a0b5677b",
"metadata": {},
"source": [
"## Prompt Templates\n",
"\n",
"With legacy LangChain agents you have to pass in a prompt template. You can use this to control the agent.\n",
"\n",
"With LangGraph `chat_agent_executor`, by default there is no prompt. You can achieve similar control over the agent in a few ways:\n",
"\n",
"1. Pass in a system message as input\n",
"2. Initialize the agent with a system message\n",
"3. Initialize the agent with a function to transform messages before passing to the model.\n",
"\n",
"Let's take a look at all of these below. We will pass in custom instructions to get the agent to respond in Spanish.\n",
"\n",
"First up, using AgentExecutor:"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "a9a11ccd-75e2-4c11-844d-a34870b0ff91",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'what is the value of magic_function(3)?',\n",
" 'output': 'El valor de `magic_function(3)` es 5.'}"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", \"You are a helpful assistant. Respond only in Spanish.\"),\n",
" (\"human\", \"{input}\"),\n",
" MessagesPlaceholder(\"agent_scratchpad\"),\n",
" ]\n",
")\n",
"\n",
"\n",
"agent = create_tool_calling_agent(model, tools, prompt)\n",
"agent_executor = AgentExecutor(agent=agent, tools=tools)\n",
"\n",
"agent_executor.invoke({\"input\": query})"
]
},
{
"cell_type": "markdown",
"id": "bd5f5500-5ae4-4000-a9fd-8c5a2cc6404d",
"metadata": {},
"source": [
"Now, let's pass a custom system message to `chat_agent_executor`. This can either be a string or a LangChain SystemMessage."
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "a9486805-676a-4d19-a5c4-08b41b172989",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'what is the value of magic_function(3)?',\n",
" 'output': 'El valor de magic_function(3) es 5.'}"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import SystemMessage\n",
"\n",
"system_message = \"Respond only in Spanish\"\n",
"# This could also be a SystemMessage object\n",
"# system_message = SystemMessage(content=\"Respond only in Spanish\")\n",
"\n",
"app = chat_agent_executor.create_tool_calling_executor(\n",
" model, tools, messages_modifier=system_message\n",
")\n",
"\n",
"\n",
"messages = app.invoke({\"messages\": [(\"human\", query)]})\n",
"{\n",
" \"input\": query,\n",
" \"output\": messages[\"messages\"][-1].content,\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "fc6059fd-0df7-4b6f-a84c-b5874e983638",
"metadata": {},
"source": [
"We can also pass in an arbitrary function. This function should take in a list of messages and output a list of messages.\n",
"We can do all types of arbitrary formatting of messages here. In this cases, let's just add a SystemMessage to the start of the list of messages."
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "d369ab45-0c82-45f4-9d3e-8efb8dd47e2c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input': 'what is the value of magic_function(3)?',\n",
" 'output': 'El valor de magic_function(3) es 5.'}"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def _modify_messages(messages):\n",
" return [SystemMessage(content=\"Respond only in spanish\")] + messages\n",
"\n",
"\n",
"app = chat_agent_executor.create_tool_calling_executor(\n",
" model, tools, messages_modifier=_modify_messages\n",
")\n",
"\n",
"\n",
"messages = app.invoke({\"messages\": [(\"human\", query)]})\n",
"{\n",
" \"input\": query,\n",
" \"output\": messages[\"messages\"][-1].content,\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "6898ccbc-42b1-4373-954a-2c7b3849fbb0",
"metadata": {},
"source": [
"## `return_intermediate_steps`\n",
"\n",
"Setting this parameter on AgentExecutor allows users to access intermediate_steps, which pairs agent actions (e.g., tool invocations) with their outcomes.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4eff44bc-a620-4c8a-97b1-268692a842bb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[(ToolAgentAction(tool='magic_function', tool_input={'input': 3}, log=\"\\nInvoking: `magic_function` with `{'input': 3}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_qckwqZI7p2LGYhMnQI5r6qsL', 'function': {'arguments': '{\"input\":3}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-0602a2dd-c4d9-4050-b851-3e2b838c6773', tool_calls=[{'name': 'magic_function', 'args': {'input': 3}, 'id': 'call_qckwqZI7p2LGYhMnQI5r6qsL'}], tool_call_chunks=[{'name': 'magic_function', 'args': '{\"input\":3}', 'id': 'call_qckwqZI7p2LGYhMnQI5r6qsL', 'index': 0}])], tool_call_id='call_qckwqZI7p2LGYhMnQI5r6qsL'), 5)]\n"
]
}
],
"source": [
"agent_executor = AgentExecutor(agent=agent, tools=tools, return_intermediate_steps=True)\n",
"result = agent_executor.invoke({\"input\": query})\n",
"print(result[\"intermediate_steps\"])"
]
},
{
"cell_type": "markdown",
"id": "594f7567-302f-4fa8-85bb-025ac8322162",
"metadata": {},
"source": [
"By default the `chat_agent_executor` in LangGraph appends all messages to the central state. Therefore, it is easy to see any intermediate steps by just looking at the full state."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "4f4364ea-dffe-4d25-bdce-ef7d0020b880",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content='what is the value of magic_function(3)?', id='408451ee-d65b-498b-abf1-788aaadfbeff'),\n",
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_eF7WussX7KgpGdoJFj6cWTxR', 'function': {'arguments': '{\"input\":3}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 65, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-a07e5d11-9319-4e27-85fb-253b75c5d7c3-0', tool_calls=[{'name': 'magic_function', 'args': {'input': 3}, 'id': 'call_eF7WussX7KgpGdoJFj6cWTxR'}]),\n",
" ToolMessage(content='5', name='magic_function', id='35045a27-a301-474b-b321-5f93da671fb1', tool_call_id='call_eF7WussX7KgpGdoJFj6cWTxR'),\n",
" AIMessage(content='The value of magic_function(3) is 5.', response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 88, 'total_tokens': 101}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, id='run-18a36a26-2477-4fc6-be51-7a675a6e10e8-0')]}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langgraph.prebuilt import chat_agent_executor\n",
"\n",
"app = chat_agent_executor.create_tool_calling_executor(model, tools)\n",
"\n",
"\n",
"messages = app.invoke({\"messages\": [(\"human\", query)]})\n",
"\n",
"messages"
]
},
{
"cell_type": "markdown",
"id": "45b528e5-57e1-450e-8d91-513eab53b543",
"metadata": {},
"source": [
"## `max_iterations`\n",
"\n",
"`AgentExecutor` implements a `max_iterations` parameter, whereas this is controlled via `recursion_limit` in LangGraph.\n",
"\n",
"Note that in AgentExecutor, an \"iteration\" includes a full turn of tool invocation and execution. In LangGraph, each step contributes to the recursion limit, so we will need to multiply by two (and add one) to get equivalent results.\n",
"\n",
"If the recursion limit is reached, LangGraph raises a specific exception type, that we can catch and manage similarly to AgentExecutor."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "16f189a7-fc78-4cb5-aa16-a94ca06401a6",
"metadata": {},
"outputs": [],
"source": [
"@tool\n",
"def magic_function(input: str) -> str:\n",
" \"\"\"Applies a magic function to an input.\"\"\"\n",
" return \"Sorry, there was an error. Please try again.\"\n",
"\n",
"\n",
"tools = [magic_function]"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "c96aefd7-6f6e-4670-aca6-1ac3d4e7871f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `magic_function` with `{'input': '3'}`\n",
"\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3mSorry, there was an error. Please try again.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
"Invoking: `magic_function` with `{'input': '3'}`\n",
"responded: I encountered an error while trying to determine the value of the magic function for the input \"3\". Let me try again.\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3mSorry, there was an error. Please try again.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
"Invoking: `magic_function` with `{'input': '3'}`\n",
"responded: I apologize for the inconvenience. It seems there is still an error in calculating the value of the magic function for the input \"3\". Let me attempt to resolve the issue by trying a different approach.\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3mSorry, there was an error. Please try again.\u001b[0m\u001b[32;1m\u001b[1;3m\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'input': 'what is the value of magic_function(3)?',\n",
" 'output': 'Agent stopped due to max iterations.'}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent = create_tool_calling_agent(model, tools, prompt)\n",
"agent_executor = AgentExecutor(\n",
" agent=agent,\n",
" tools=tools,\n",
" verbose=True,\n",
" max_iterations=3,\n",
")\n",
"\n",
"agent_executor.invoke({\"input\": query})"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b974a91f-6ae8-4644-83d9-73666258a6db",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_VkrswGIkIUKJQyVF0AvMaU3p', 'function': {'arguments': '{\"input\":\"3\"}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 65, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2dd5504b-9386-4b35-aed1-a2a267f883fd-0', tool_calls=[{'name': 'magic_function', 'args': {'input': '3'}, 'id': 'call_VkrswGIkIUKJQyVF0AvMaU3p'}])]}}\n",
"------\n",
"{'action': {'messages': [ToolMessage(content='Sorry, there was an error. Please try again.', name='magic_function', id='85d7e845-f4ef-40a6-828d-c48c93b02b97', tool_call_id='call_VkrswGIkIUKJQyVF0AvMaU3p')]}}\n",
"------\n",
"{'agent': {'messages': [AIMessage(content='It seems there was an error when trying to calculate the value of the magic function for the input 3. Let me try again.', additional_kwargs={'tool_calls': [{'id': 'call_i5ZWsDhQvzgKs2bCroMB4JSL', 'function': {'arguments': '{\"input\":\"3\"}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 42, 'prompt_tokens': 98, 'total_tokens': 140}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-6224c33b-0d3a-4925-9050-cb2a844dfe62-0', tool_calls=[{'name': 'magic_function', 'args': {'input': '3'}, 'id': 'call_i5ZWsDhQvzgKs2bCroMB4JSL'}])]}}\n",
"------\n",
"{'action': {'messages': [ToolMessage(content='Sorry, there was an error. Please try again.', name='magic_function', id='f846363c-b143-402c-949d-40d84b19d979', tool_call_id='call_i5ZWsDhQvzgKs2bCroMB4JSL')]}}\n",
"------\n",
"{'agent': {'messages': [AIMessage(content='Unfortunately, there seems to be an issue with calculating the value of the magic function for the input 3. Let me attempt to resolve this issue by using a different approach.', additional_kwargs={'tool_calls': [{'id': 'call_I26nZWbe4iVnagUh4GVePwig', 'function': {'arguments': '{\"input\": \"3\"}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 65, 'prompt_tokens': 162, 'total_tokens': 227}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-0512509d-201e-4fbb-ac96-fdd68400810a-0', tool_calls=[{'name': 'magic_function', 'args': {'input': '3'}, 'id': 'call_I26nZWbe4iVnagUh4GVePwig'}])]}}\n",
"------\n",
"{'action': {'messages': [ToolMessage(content='Sorry, there was an error. Please try again.', name='magic_function', id='fb19299f-de26-4659-9507-4bf4fb53bff4', tool_call_id='call_I26nZWbe4iVnagUh4GVePwig')]}}\n",
"------\n",
"{'input': 'what is the value of magic_function(3)?', 'output': 'Agent stopped due to max iterations.'}\n"
]
}
],
"source": [
"from langgraph.pregel import GraphRecursionError\n",
"\n",
"RECURSION_LIMIT = 2 * 3 + 1\n",
"\n",
"app = chat_agent_executor.create_tool_calling_executor(model, tools)\n",
"\n",
"try:\n",
" for chunk in app.stream(\n",
" {\"messages\": [(\"human\", query)]}, {\"recursion_limit\": RECURSION_LIMIT}\n",
" ):\n",
" print(chunk)\n",
" print(\"------\")\n",
"except GraphRecursionError:\n",
" print({\"input\": query, \"output\": \"Agent stopped due to max iterations.\"})"
]
},
{
"cell_type": "markdown",
"id": "3a527158-ada5-4774-a98b-8272c6b6b2c0",
"metadata": {},
"source": [
"## `max_execution_time`\n",
"\n",
"`AgentExecutor` implements a `max_execution_time` parameter, allowing users to abort a run that exceeds a total time limit."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "4b8498fc-a7af-4164-a401-d8714f082306",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `magic_function` with `{'input': '3'}`\n",
"\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3mSorry, there was an error. Please try again.\u001b[0m\u001b[32;1m\u001b[1;3m\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'input': 'what is the value of magic_function(3)?',\n",
" 'output': 'Agent stopped due to max iterations.'}"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import time\n",
"\n",
"\n",
"@tool\n",
"def magic_function(input: str) -> str:\n",
" \"\"\"Applies a magic function to an input.\"\"\"\n",
" time.sleep(2.5)\n",
" return \"Sorry, there was an error. Please try again.\"\n",
"\n",
"\n",
"tools = [magic_function]\n",
"\n",
"agent = create_tool_calling_agent(model, tools, prompt)\n",
"agent_executor = AgentExecutor(\n",
" agent=agent,\n",
" tools=tools,\n",
" max_execution_time=2,\n",
" verbose=True,\n",
")\n",
"\n",
"agent_executor.invoke({\"input\": query})"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "a2b29113-e6be-4f91-aa4c-5c63dea3e423",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_lp2tuTmBpulORJr4FJp9za4E', 'function': {'arguments': '{\"input\":\"3\"}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 65, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-4070a5d8-c2ea-46f3-a3a2-dfcd2ebdadc2-0', tool_calls=[{'name': 'magic_function', 'args': {'input': '3'}, 'id': 'call_lp2tuTmBpulORJr4FJp9za4E'}])]}}\n",
"------\n",
"{'input': 'what is the value of magic_function(3)?', 'output': 'Agent stopped due to max iterations.'}\n"
]
}
],
"source": [
"app = chat_agent_executor.create_tool_calling_executor(model, tools)\n",
"# Set the max timeout for each step here\n",
"app.step_timeout = 2\n",
"\n",
"try:\n",
" for chunk in app.stream({\"messages\": [(\"human\", query)]}):\n",
" print(chunk)\n",
" print(\"------\")\n",
"except TimeoutError:\n",
" print({\"input\": query, \"output\": \"Agent stopped due to max iterations.\"})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e9eb55f4-a321-4bac-b52d-9e43b411cf92",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,617 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d9172545",
"metadata": {},
"source": [
"# How to use the MultiVector Retriever\n",
"\n",
"It can often be beneficial to store multiple vectors per document. There are multiple use cases where this is beneficial. LangChain has a base `MultiVectorRetriever` which makes querying this type of setup easy. A lot of the complexity lies in how to create the multiple vectors per document. This notebook covers some of the common ways to create those vectors and use the `MultiVectorRetriever`.\n",
"\n",
"The methods to create multiple vectors per document include:\n",
"\n",
"- Smaller chunks: split a document into smaller chunks, and embed those (this is ParentDocumentRetriever).\n",
"- Summary: create a summary for each document, embed that along with (or instead of) the document.\n",
"- Hypothetical questions: create hypothetical questions that each document would be appropriate to answer, embed those along with (or instead of) the document.\n",
"\n",
"\n",
"Note that this also enables another method of adding embeddings - manually. This is great because you can explicitly add questions or queries that should lead to a document being recovered, giving you more control."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "eed469be",
"metadata": {},
"outputs": [],
"source": [
"from langchain.retrievers.multi_vector import MultiVectorRetriever"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "18c1421a",
"metadata": {},
"outputs": [],
"source": [
"from langchain.storage import InMemoryByteStore\n",
"from langchain_chroma import Chroma\n",
"from langchain_community.document_loaders import TextLoader\n",
"from langchain_openai import OpenAIEmbeddings\n",
"from langchain_text_splitters import RecursiveCharacterTextSplitter"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "6d869496",
"metadata": {},
"outputs": [],
"source": [
"loaders = [\n",
" TextLoader(\"../../paul_graham_essay.txt\"),\n",
" TextLoader(\"../../state_of_the_union.txt\"),\n",
"]\n",
"docs = []\n",
"for loader in loaders:\n",
" docs.extend(loader.load())\n",
"text_splitter = RecursiveCharacterTextSplitter(chunk_size=10000)\n",
"docs = text_splitter.split_documents(docs)"
]
},
{
"cell_type": "markdown",
"id": "fa17beda",
"metadata": {},
"source": [
"## Smaller chunks\n",
"\n",
"Often times it can be useful to retrieve larger chunks of information, but embed smaller chunks. This allows for embeddings to capture the semantic meaning as closely as possible, but for as much context as possible to be passed downstream. Note that this is what the `ParentDocumentRetriever` does. Here we show what is going on under the hood."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "0e7b6b45",
"metadata": {},
"outputs": [],
"source": [
"# The vectorstore to use to index the child chunks\n",
"vectorstore = Chroma(\n",
" collection_name=\"full_documents\", embedding_function=OpenAIEmbeddings()\n",
")\n",
"# The storage layer for the parent documents\n",
"store = InMemoryByteStore()\n",
"id_key = \"doc_id\"\n",
"# The retriever (empty to start)\n",
"retriever = MultiVectorRetriever(\n",
" vectorstore=vectorstore,\n",
" byte_store=store,\n",
" id_key=id_key,\n",
")\n",
"import uuid\n",
"\n",
"doc_ids = [str(uuid.uuid4()) for _ in docs]"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "72a36491",
"metadata": {},
"outputs": [],
"source": [
"# The splitter to use to create smaller chunks\n",
"child_text_splitter = RecursiveCharacterTextSplitter(chunk_size=400)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "5d23247d",
"metadata": {},
"outputs": [],
"source": [
"sub_docs = []\n",
"for i, doc in enumerate(docs):\n",
" _id = doc_ids[i]\n",
" _sub_docs = child_text_splitter.split_documents([doc])\n",
" for _doc in _sub_docs:\n",
" _doc.metadata[id_key] = _id\n",
" sub_docs.extend(_sub_docs)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "92ed5861",
"metadata": {},
"outputs": [],
"source": [
"retriever.vectorstore.add_documents(sub_docs)\n",
"retriever.docstore.mset(list(zip(doc_ids, docs)))"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8afed60c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Document(page_content='Tonight, Id like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \\n\\nOne of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court.', metadata={'doc_id': '2fd77862-9ed5-4fad-bf76-e487b747b333', 'source': '../../state_of_the_union.txt'})"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Vectorstore alone retrieves the small chunks\n",
"retriever.vectorstore.similarity_search(\"justice breyer\")[0]"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "3c9017f1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"9875"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Retriever returns larger chunks\n",
"len(retriever.get_relevant_documents(\"justice breyer\")[0].page_content)"
]
},
{
"cell_type": "markdown",
"id": "cdef8339-f9fa-4b3b-955f-ad9dbdf2734f",
"metadata": {},
"source": [
"The default search type the retriever performs on the vector database is a similarity search. LangChain Vector Stores also support searching via [Max Marginal Relevance](https://api.python.langchain.com/en/latest/vectorstores/langchain_core.vectorstores.VectorStore.html#langchain_core.vectorstores.VectorStore.max_marginal_relevance_search) so if you want this instead you can just set the `search_type` property as follows:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "36739460-a737-4a8e-b70f-50bf8c8eaae7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"9875"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.retrievers.multi_vector import SearchType\n",
"\n",
"retriever.search_type = SearchType.mmr\n",
"\n",
"len(retriever.get_relevant_documents(\"justice breyer\")[0].page_content)"
]
},
{
"cell_type": "markdown",
"id": "d6a7ae0d",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"Oftentimes a summary may be able to distill more accurately what a chunk is about, leading to better retrieval. Here we show how to create summaries, and then embed those."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "1433dff4",
"metadata": {},
"outputs": [],
"source": [
"import uuid\n",
"\n",
"from langchain_core.documents import Document\n",
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_openai import ChatOpenAI"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "35b30390",
"metadata": {},
"outputs": [],
"source": [
"chain = (\n",
" {\"doc\": lambda x: x.page_content}\n",
" | ChatPromptTemplate.from_template(\"Summarize the following document:\\n\\n{doc}\")\n",
" | ChatOpenAI(max_retries=0)\n",
" | StrOutputParser()\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "41a2a738",
"metadata": {},
"outputs": [],
"source": [
"summaries = chain.batch(docs, {\"max_concurrency\": 5})"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "7ac5e4b1",
"metadata": {},
"outputs": [],
"source": [
"# The vectorstore to use to index the child chunks\n",
"vectorstore = Chroma(collection_name=\"summaries\", embedding_function=OpenAIEmbeddings())\n",
"# The storage layer for the parent documents\n",
"store = InMemoryByteStore()\n",
"id_key = \"doc_id\"\n",
"# The retriever (empty to start)\n",
"retriever = MultiVectorRetriever(\n",
" vectorstore=vectorstore,\n",
" byte_store=store,\n",
" id_key=id_key,\n",
")\n",
"doc_ids = [str(uuid.uuid4()) for _ in docs]"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "0d93309f",
"metadata": {},
"outputs": [],
"source": [
"summary_docs = [\n",
" Document(page_content=s, metadata={id_key: doc_ids[i]})\n",
" for i, s in enumerate(summaries)\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "6d5edf0d",
"metadata": {},
"outputs": [],
"source": [
"retriever.vectorstore.add_documents(summary_docs)\n",
"retriever.docstore.mset(list(zip(doc_ids, docs)))"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "862ae920",
"metadata": {},
"outputs": [],
"source": [
"# # We can also add the original chunks to the vectorstore if we so want\n",
"# for i, doc in enumerate(docs):\n",
"# doc.metadata[id_key] = doc_ids[i]\n",
"# retriever.vectorstore.add_documents(docs)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "299232d6",
"metadata": {},
"outputs": [],
"source": [
"sub_docs = vectorstore.similarity_search(\"justice breyer\")"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "10e404c0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Document(page_content=\"The document is a speech given by President Biden addressing various issues and outlining his agenda for the nation. He highlights the importance of nominating a Supreme Court justice and introduces his nominee, Judge Ketanji Brown Jackson. He emphasizes the need to secure the border and reform the immigration system, including providing a pathway to citizenship for Dreamers and essential workers. The President also discusses the protection of women's rights, including access to healthcare and the right to choose. He calls for the passage of the Equality Act to protect LGBTQ+ rights. Additionally, President Biden discusses the need to address the opioid epidemic, improve mental health services, support veterans, and fight against cancer. He expresses optimism for the future of America and the strength of the American people.\", metadata={'doc_id': '56345bff-3ead-418c-a4ff-dff203f77474'})"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sub_docs[0]"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "e4cce5c2",
"metadata": {},
"outputs": [],
"source": [
"retrieved_docs = retriever.get_relevant_documents(\"justice breyer\")"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "c8570dbb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"9194"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(retrieved_docs[0].page_content)"
]
},
{
"cell_type": "markdown",
"id": "097a5396",
"metadata": {},
"source": [
"## Hypothetical Queries\n",
"\n",
"An LLM can also be used to generate a list of hypothetical questions that could be asked of a particular document. These questions can then be embedded"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "5219b085",
"metadata": {},
"outputs": [],
"source": [
"functions = [\n",
" {\n",
" \"name\": \"hypothetical_questions\",\n",
" \"description\": \"Generate hypothetical questions\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"questions\": {\n",
" \"type\": \"array\",\n",
" \"items\": {\"type\": \"string\"},\n",
" },\n",
" },\n",
" \"required\": [\"questions\"],\n",
" },\n",
" }\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "523deb92",
"metadata": {},
"outputs": [],
"source": [
"from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser\n",
"\n",
"chain = (\n",
" {\"doc\": lambda x: x.page_content}\n",
" # Only asking for 3 hypothetical questions, but this could be adjusted\n",
" | ChatPromptTemplate.from_template(\n",
" \"Generate a list of exactly 3 hypothetical questions that the below document could be used to answer:\\n\\n{doc}\"\n",
" )\n",
" | ChatOpenAI(max_retries=0, model=\"gpt-4\").bind(\n",
" functions=functions, function_call={\"name\": \"hypothetical_questions\"}\n",
" )\n",
" | JsonKeyOutputFunctionsParser(key_name=\"questions\")\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "11d30554",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[\"What was the author's first experience with programming like?\",\n",
" 'Why did the author switch their focus from AI to Lisp during their graduate studies?',\n",
" 'What led the author to contemplate a career in art instead of computer science?']"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain.invoke(docs[0])"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "3eb2e48c",
"metadata": {},
"outputs": [],
"source": [
"hypothetical_questions = chain.batch(docs, {\"max_concurrency\": 5})"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "b2cd6e75",
"metadata": {},
"outputs": [],
"source": [
"# The vectorstore to use to index the child chunks\n",
"vectorstore = Chroma(\n",
" collection_name=\"hypo-questions\", embedding_function=OpenAIEmbeddings()\n",
")\n",
"# The storage layer for the parent documents\n",
"store = InMemoryByteStore()\n",
"id_key = \"doc_id\"\n",
"# The retriever (empty to start)\n",
"retriever = MultiVectorRetriever(\n",
" vectorstore=vectorstore,\n",
" byte_store=store,\n",
" id_key=id_key,\n",
")\n",
"doc_ids = [str(uuid.uuid4()) for _ in docs]"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "18831b3b",
"metadata": {},
"outputs": [],
"source": [
"question_docs = []\n",
"for i, question_list in enumerate(hypothetical_questions):\n",
" question_docs.extend(\n",
" [Document(page_content=s, metadata={id_key: doc_ids[i]}) for s in question_list]\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "224b24c5",
"metadata": {},
"outputs": [],
"source": [
"retriever.vectorstore.add_documents(question_docs)\n",
"retriever.docstore.mset(list(zip(doc_ids, docs)))"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "7b442b90",
"metadata": {},
"outputs": [],
"source": [
"sub_docs = vectorstore.similarity_search(\"justice breyer\")"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "089b5ad0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Who has been nominated to serve on the United States Supreme Court?', metadata={'doc_id': '0b3a349e-c936-4e77-9c40-0a39fc3e07f0'}),\n",
" Document(page_content=\"What was the context and content of Robert Morris' advice to the document's author in 2010?\", metadata={'doc_id': 'b2b2cdca-988a-4af1-ba47-46170770bc8c'}),\n",
" Document(page_content='How did personal circumstances influence the decision to pass on the leadership of Y Combinator?', metadata={'doc_id': 'b2b2cdca-988a-4af1-ba47-46170770bc8c'}),\n",
" Document(page_content='What were the reasons for the author leaving Yahoo in the summer of 1999?', metadata={'doc_id': 'ce4f4981-ca60-4f56-86f0-89466de62325'})]"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sub_docs"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "7594b24e",
"metadata": {},
"outputs": [],
"source": [
"retrieved_docs = retriever.get_relevant_documents(\"justice breyer\")"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "4c120c65",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"9194"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(retrieved_docs[0].page_content)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "005072b8",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,580 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "80f15d95-00d8-4c38-a291-07ff2233b4fd",
"metadata": {},
"source": [
"# How to create a custom Output Parser\n",
"\n",
"In some situations you may want to implement a custom parser to structure the model output into a custom format.\n",
"\n",
"There are two ways to implement a custom parser:\n",
"\n",
"1. Using `RunnableLambda` or `RunnableGenerator` in LCEL -- we strongly recommend this for most use cases\n",
"2. By inherting from one of the base classes for out parsing -- this is the hard way of doing things\n",
"\n",
"The difference between the two approaches are mostly superficial and are mainly in terms of which callbacks are triggered (e.g., `on_chain_start` vs. `on_parser_start`), and how a runnable lambda vs. a parser might be visualized in a tracing platform like LangSmith."
]
},
{
"cell_type": "markdown",
"id": "c651cc26-28cb-45d1-9969-d88deff8b819",
"metadata": {},
"source": [
"## Runnable Lambdas and Generators\n",
"\n",
"The recommended way to parse is using **runnable lambdas** and **runnable generators**!\n",
"\n",
"Here, we will make a simple parse that inverts the case of the output from the model.\n",
"\n",
"For example, if the model outputs: \"Meow\", the parser will produce \"mEOW\"."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "6cd7cc21-ec51-4e22-82d0-32c4401f5adc",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'hELLO!'"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from typing import Iterable\n",
"\n",
"from langchain_anthropic.chat_models import ChatAnthropic\n",
"from langchain_core.messages import AIMessage, AIMessageChunk\n",
"\n",
"model = ChatAnthropic(model_name=\"claude-2.1\")\n",
"\n",
"\n",
"def parse(ai_message: AIMessage) -> str:\n",
" \"\"\"Parse the AI message.\"\"\"\n",
" return ai_message.content.swapcase()\n",
"\n",
"\n",
"chain = model | parse\n",
"chain.invoke(\"hello\")"
]
},
{
"cell_type": "markdown",
"id": "eed8baf2-f4c2-44c1-b47d-e9f560af6202",
"metadata": {},
"source": [
":::{.callout-tip}\n",
"\n",
"LCEL automatically upgrades the function `parse` to `RunnableLambda(parse)` when composed using a `|` syntax.\n",
"\n",
"If you don't like that you can manually import `RunnableLambda` and then run`parse = RunnableLambda(parse)`.\n",
":::"
]
},
{
"cell_type": "markdown",
"id": "896f52ce-91e2-4c7c-bd62-1f901002ade2",
"metadata": {},
"source": [
"Does streaming work?"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4e35389a-caa5-4c0d-9d95-48648d0b8d4f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"i'M cLAUDE, AN ai ASSISTANT CREATED BY aNTHROPIC TO BE HELPFUL, HARMLESS, AND HONEST.|"
]
}
],
"source": [
"for chunk in chain.stream(\"tell me about yourself in one sentence\"):\n",
" print(chunk, end=\"|\", flush=True)"
]
},
{
"cell_type": "markdown",
"id": "11c486bb-b2d4-461b-8fd8-19b9e0472129",
"metadata": {},
"source": [
"No, it doesn't because the parser aggregates the input before parsing the output.\n",
"\n",
"If we want to implement a streaming parser, we can have the parser accept an iterable over the input instead and yield\n",
"the results as they're available."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "930aa59e-82d0-447c-b711-b416d92a08b7",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.runnables import RunnableGenerator\n",
"\n",
"\n",
"def streaming_parse(chunks: Iterable[AIMessageChunk]) -> Iterable[str]:\n",
" for chunk in chunks:\n",
" yield chunk.content.swapcase()\n",
"\n",
"\n",
"streaming_parse = RunnableGenerator(streaming_parse)"
]
},
{
"cell_type": "markdown",
"id": "62192808-c7e1-4b3a-85f4-b7901de7c0b8",
"metadata": {},
"source": [
":::{.callout-important}\n",
"\n",
"Please wrap the streaming parser in `RunnableGenerator` as we may stop automatically upgrading it with the `|` syntax.\n",
":::"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "c054d4da-66f3-4f11-8137-0734bb3de06c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'hELLO!'"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain = model | streaming_parse\n",
"chain.invoke(\"hello\")"
]
},
{
"cell_type": "markdown",
"id": "1d344ff2-5c93-49a9-af00-03856d2cbfdb",
"metadata": {},
"source": [
"Let's confirm that streaming works!"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "26d746ae-9c5a-4cda-a535-33f555e2e04a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"i|'M| cLAUDE|,| AN| ai| ASSISTANT| CREATED| BY| aN|THROP|IC| TO| BE| HELPFUL|,| HARMLESS|,| AND| HONEST|.|"
]
}
],
"source": [
"for chunk in chain.stream(\"tell me about yourself in one sentence\"):\n",
" print(chunk, end=\"|\", flush=True)"
]
},
{
"cell_type": "markdown",
"id": "24067447-8a5a-4d6b-86a3-4b9cc4b4369b",
"metadata": {},
"source": [
"## Inherting from Parsing Base Classes"
]
},
{
"cell_type": "markdown",
"id": "9713f547-b2e4-48eb-807f-a0f6f6d0e7e0",
"metadata": {},
"source": [
"Another approach to implement a parser is by inherting from `BaseOutputParser`, `BaseGenerationOutputParser` or another one of the base parsers depending on what you need to do.\n",
"\n",
"In general, we **do not** recommend this approach for most use cases as it results in more code to write without significant benefits.\n",
"\n",
"The simplest kind of output parser extends the `BaseOutputParser` class and must implement the following methods:\n",
"\n",
"* `parse`: takes the string output from the model and parses it\n",
"* (optional) `_type`: identifies the name of the parser.\n",
"\n",
"When the output from the chat model or LLM is malformed, the can throw an `OutputParserException` to indicate that parsing fails because of bad input. Using this exception allows code that utilizes the parser to handle the exceptions in a consistent manner.\n",
"\n",
":::{.callout-tip} Parsers are Runnables! 🏃\n",
"\n",
"Because `BaseOutputParser` implements the `Runnable` interface, any custom parser you will create this way will become valid LangChain Runnables and will benefit from automatic async support, batch interface, logging support etc.\n",
":::\n"
]
},
{
"cell_type": "markdown",
"id": "1e0f9c59-b5bd-4ed0-a187-ae514c203e80",
"metadata": {},
"source": [
"### Simple Parser"
]
},
{
"cell_type": "markdown",
"id": "3a96a846-1296-4d92-8e76-e29e583dee22",
"metadata": {},
"source": [
"Here's a simple parser that can parse a **string** representation of a booealn (e.g., `YES` or `NO`) and convert it into the corresponding `boolean` type."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "733a0c4f-471a-4161-ad3e-804f63053e6f",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.exceptions import OutputParserException\n",
"from langchain_core.output_parsers import BaseOutputParser\n",
"\n",
"\n",
"# The [bool] desribes a parameterization of a generic.\n",
"# It's basically indicating what the return type of parse is\n",
"# in this case the return type is either True or False\n",
"class BooleanOutputParser(BaseOutputParser[bool]):\n",
" \"\"\"Custom boolean parser.\"\"\"\n",
"\n",
" true_val: str = \"YES\"\n",
" false_val: str = \"NO\"\n",
"\n",
" def parse(self, text: str) -> bool:\n",
" cleaned_text = text.strip().upper()\n",
" if cleaned_text not in (self.true_val.upper(), self.false_val.upper()):\n",
" raise OutputParserException(\n",
" f\"BooleanOutputParser expected output value to either be \"\n",
" f\"{self.true_val} or {self.false_val} (case-insensitive). \"\n",
" f\"Received {cleaned_text}.\"\n",
" )\n",
" return cleaned_text == self.true_val.upper()\n",
"\n",
" @property\n",
" def _type(self) -> str:\n",
" return \"boolean_output_parser\""
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "101e54f0-12f1-4734-a80d-98e6f62644b2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"parser = BooleanOutputParser()\n",
"parser.invoke(\"YES\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "39ed9d84-16a1-4612-a1f7-13269b9f48e8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Triggered an exception of type: <class 'langchain_core.exceptions.OutputParserException'>\n"
]
}
],
"source": [
"try:\n",
" parser.invoke(\"MEOW\")\n",
"except Exception as e:\n",
" print(f\"Triggered an exception of type: {type(e)}\")"
]
},
{
"cell_type": "markdown",
"id": "c27da11a-2c64-4108-9a8a-38008d6041fc",
"metadata": {},
"source": [
"Let's test changing the parameterization"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2e94c0f4-f6c1-401b-8cee-2572a80846cb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"parser = BooleanOutputParser(true_val=\"OKAY\")\n",
"parser.invoke(\"OKAY\")"
]
},
{
"cell_type": "markdown",
"id": "dac313d5-20c8-44a9-bfe9-c2b5020172e2",
"metadata": {},
"source": [
"Let's confirm that other LCEL methods are present"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "97fb540f-83b2-46fd-a741-b200235f8f9e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[True, False]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"parser.batch([\"OKAY\", \"NO\"])"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "60cbdb2f-5538-4e74-ba03-53bc1bc4bb2f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[True, False]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"await parser.abatch([\"OKAY\", \"NO\"])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "6520dff0-259c-48e4-be69-829fb3275ac2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='OKAY')"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_anthropic.chat_models import ChatAnthropic\n",
"\n",
"anthropic = ChatAnthropic(model_name=\"claude-2.1\")\n",
"anthropic.invoke(\"say OKAY or NO\")"
]
},
{
"cell_type": "markdown",
"id": "12dc079e-c451-496c-953c-cba55ef26de8",
"metadata": {},
"source": [
"Let's test that our parser works!"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "bb177c14-b1f5-474f-a1c8-5b32ae242259",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain = anthropic | parser\n",
"chain.invoke(\"say OKAY or NO\")"
]
},
{
"cell_type": "markdown",
"id": "18f83192-37e8-43f5-ab29-9568b1279f1b",
"metadata": {},
"source": [
":::{.callout-note}\n",
"The parser will work with either the output from an LLM (a string) or the output from a chat model (an `AIMessage`)!\n",
":::"
]
},
{
"cell_type": "markdown",
"id": "9ed063d3-3159-4f5b-8362-710956fc50bd",
"metadata": {},
"source": [
"### Parsing Raw Model Outputs\n",
"\n",
"Sometimes there is additional metadata on the model output that is important besides the raw text. One example of this is tool calling, where arguments intended to be passed to called functions are returned in a separate property. If you need this finer-grained control, you can instead subclass the `BaseGenerationOutputParser` class. \n",
"\n",
"This class requires a single method `parse_result`. This method takes raw model output (e.g., list of `Generation` or `ChatGeneration`) and returns the parsed output.\n",
"\n",
"Supporting both `Generation` and `ChatGeneration` allows the parser to work with both regular LLMs as well as with Chat Models."
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "0fd1f936-e77d-4602-921c-52a37e589e90",
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"\n",
"from langchain_core.exceptions import OutputParserException\n",
"from langchain_core.messages import AIMessage\n",
"from langchain_core.output_parsers import BaseGenerationOutputParser\n",
"from langchain_core.outputs import ChatGeneration, Generation\n",
"\n",
"\n",
"class StrInvertCase(BaseGenerationOutputParser[str]):\n",
" \"\"\"An example parser that inverts the case of the characters in the message.\n",
"\n",
" This is an example parse shown just for demonstration purposes and to keep\n",
" the example as simple as possible.\n",
" \"\"\"\n",
"\n",
" def parse_result(self, result: List[Generation], *, partial: bool = False) -> str:\n",
" \"\"\"Parse a list of model Generations into a specific format.\n",
"\n",
" Args:\n",
" result: A list of Generations to be parsed. The Generations are assumed\n",
" to be different candidate outputs for a single model input.\n",
" Many parsers assume that only a single generation is passed it in.\n",
" We will assert for that\n",
" partial: Whether to allow partial results. This is used for parsers\n",
" that support streaming\n",
" \"\"\"\n",
" if len(result) != 1:\n",
" raise NotImplementedError(\n",
" \"This output parser can only be used with a single generation.\"\n",
" )\n",
" generation = result[0]\n",
" if not isinstance(generation, ChatGeneration):\n",
" # Say that this one only works with chat generations\n",
" raise OutputParserException(\n",
" \"This output parser can only be used with a chat generation.\"\n",
" )\n",
" return generation.message.content.swapcase()\n",
"\n",
"\n",
"chain = anthropic | StrInvertCase()"
]
},
{
"cell_type": "markdown",
"id": "accab8a3-6b0e-4ad0-89e6-1824ca20c726",
"metadata": {},
"source": [
"Let's the new parser! It should be inverting the output from the model."
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "568fae19-b09c-484f-8775-1c9a60aabdf4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'hELLO! mY NAME IS cLAUDE.'"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain.invoke(\"Tell me a short sentence about yourself\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,167 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0fee7096",
"metadata": {},
"source": [
"# How to use the output-fixing parser\n",
"\n",
"This output parser wraps another output parser, and in the event that the first one fails it calls out to another LLM to fix any errors.\n",
"\n",
"But we can do other things besides throw errors. Specifically, we can pass the misformatted output, along with the formatted instructions, to the model and ask it to fix it.\n",
"\n",
"For this example, we'll use the above Pydantic output parser. Here's what happens if we pass it a result that does not comply with the schema:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "9bad594d",
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"\n",
"from langchain.output_parsers import PydanticOutputParser\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"from langchain_openai import ChatOpenAI"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "15283e0b",
"metadata": {},
"outputs": [],
"source": [
"class Actor(BaseModel):\n",
" name: str = Field(description=\"name of an actor\")\n",
" film_names: List[str] = Field(description=\"list of names of films they starred in\")\n",
"\n",
"\n",
"actor_query = \"Generate the filmography for a random actor.\"\n",
"\n",
"parser = PydanticOutputParser(pydantic_object=Actor)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "072d2d4c",
"metadata": {},
"outputs": [],
"source": [
"misformatted = \"{'name': 'Tom Hanks', 'film_names': ['Forrest Gump']}\""
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4cbb35b3",
"metadata": {},
"outputs": [
{
"ename": "OutputParserException",
"evalue": "Failed to parse Actor from completion {'name': 'Tom Hanks', 'film_names': ['Forrest Gump']}. Got: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mJSONDecodeError\u001b[0m Traceback (most recent call last)",
"File \u001b[0;32m~/workplace/langchain/libs/langchain/langchain/output_parsers/pydantic.py:29\u001b[0m, in \u001b[0;36mPydanticOutputParser.parse\u001b[0;34m(self, text)\u001b[0m\n\u001b[1;32m 28\u001b[0m json_str \u001b[38;5;241m=\u001b[39m match\u001b[38;5;241m.\u001b[39mgroup()\n\u001b[0;32m---> 29\u001b[0m json_object \u001b[38;5;241m=\u001b[39m \u001b[43mjson\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mloads\u001b[49m\u001b[43m(\u001b[49m\u001b[43mjson_str\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstrict\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 30\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpydantic_object\u001b[38;5;241m.\u001b[39mparse_obj(json_object)\n",
"File \u001b[0;32m~/.pyenv/versions/3.10.1/lib/python3.10/json/__init__.py:359\u001b[0m, in \u001b[0;36mloads\u001b[0;34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001b[0m\n\u001b[1;32m 358\u001b[0m kw[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mparse_constant\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m=\u001b[39m parse_constant\n\u001b[0;32m--> 359\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mcls\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkw\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdecode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/.pyenv/versions/3.10.1/lib/python3.10/json/decoder.py:337\u001b[0m, in \u001b[0;36mJSONDecoder.decode\u001b[0;34m(self, s, _w)\u001b[0m\n\u001b[1;32m 333\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001b[39;00m\n\u001b[1;32m 334\u001b[0m \u001b[38;5;124;03mcontaining a JSON document).\u001b[39;00m\n\u001b[1;32m 335\u001b[0m \n\u001b[1;32m 336\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m--> 337\u001b[0m obj, end \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mraw_decode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m_w\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mend\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 338\u001b[0m end \u001b[38;5;241m=\u001b[39m _w(s, end)\u001b[38;5;241m.\u001b[39mend()\n",
"File \u001b[0;32m~/.pyenv/versions/3.10.1/lib/python3.10/json/decoder.py:353\u001b[0m, in \u001b[0;36mJSONDecoder.raw_decode\u001b[0;34m(self, s, idx)\u001b[0m\n\u001b[1;32m 352\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 353\u001b[0m obj, end \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mscan_once\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 354\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n",
"\u001b[0;31mJSONDecodeError\u001b[0m: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
"\nDuring handling of the above exception, another exception occurred:\n",
"\u001b[0;31mOutputParserException\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mparser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparse\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmisformatted\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/workplace/langchain/libs/langchain/langchain/output_parsers/pydantic.py:35\u001b[0m, in \u001b[0;36mPydanticOutputParser.parse\u001b[0;34m(self, text)\u001b[0m\n\u001b[1;32m 33\u001b[0m name \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpydantic_object\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\n\u001b[1;32m 34\u001b[0m msg \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFailed to parse \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m from completion \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtext\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m. Got: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00me\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m---> 35\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m OutputParserException(msg, llm_output\u001b[38;5;241m=\u001b[39mtext)\n",
"\u001b[0;31mOutputParserException\u001b[0m: Failed to parse Actor from completion {'name': 'Tom Hanks', 'film_names': ['Forrest Gump']}. Got: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"
]
}
],
"source": [
"parser.parse(misformatted)"
]
},
{
"cell_type": "markdown",
"id": "723c559d",
"metadata": {},
"source": [
"Now we can construct and use a `OutputFixingParser`. This output parser takes as an argument another output parser but also an LLM with which to try to correct any formatting mistakes."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4aaccbf1",
"metadata": {},
"outputs": [],
"source": [
"from langchain.output_parsers import OutputFixingParser\n",
"\n",
"new_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "8031c22d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Actor(name='Tom Hanks', film_names=['Forrest Gump'])"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"new_parser.parse(misformatted)"
]
},
{
"cell_type": "markdown",
"id": "84498e02",
"metadata": {},
"source": [
"Find out api documentation for [OutputFixingParser](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.fix.OutputFixingParser.html#langchain.output_parsers.fix.OutputFixingParser)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bc7af2a0",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,263 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "72b1b316",
"metadata": {},
"source": [
"# How to parse JSON output\n",
"\n",
"While some model providers support [built-in ways to return structured output](/docs/how_to/structured_output), not all do. We can use an output parser to help users to specify an arbitrary JSON schema via the prompt, query a model for outputs that conform to that schema, and finally parse that schema as JSON.\n",
"\n",
":::{.callout-note}\n",
"Keep in mind that large language models are leaky abstractions! You'll have to use an LLM with sufficient capacity to generate well-formed JSON.\n",
":::\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Chat models](/docs/concepts/#chat-models)\n",
"- [Output parsers](/docs/concepts/#output-parsers)\n",
"- [Prompt templates](/docs/concepts/#prompt-templates)\n",
"- [Structured output](/docs/how_to/structured_output)\n",
"- [Chaining runnables together](/docs/how_to/sequence/)\n",
"`}/>\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "ae909b7a",
"metadata": {},
"source": [
"The [`JsonOutputParser`](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.json.JsonOutputParser.html) is one built-in option for prompting for and then parsing JSON output. While it is similar in functionality to the [`PydanticOutputParser`](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.pydantic.PydanticOutputParser.html), it also supports streaming back partial JSON objects.\n",
"\n",
"Here's an example of how it can be used alongside [Pydantic](https://docs.pydantic.dev/) to conveniently declare the expected schema:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd9d9110",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain langchain-openai\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4ccf45a3",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'setup': \"Why couldn't the bicycle stand up by itself?\",\n",
" 'punchline': 'Because it was two tired!'}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.output_parsers import JsonOutputParser\n",
"from langchain_core.prompts import PromptTemplate\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"model = ChatOpenAI(temperature=0)\n",
"\n",
"\n",
"# Define your desired data structure.\n",
"class Joke(BaseModel):\n",
" setup: str = Field(description=\"question to set up a joke\")\n",
" punchline: str = Field(description=\"answer to resolve the joke\")\n",
"\n",
"\n",
"# And a query intented to prompt a language model to populate the data structure.\n",
"joke_query = \"Tell me a joke.\"\n",
"\n",
"# Set up a parser + inject instructions into the prompt template.\n",
"parser = JsonOutputParser(pydantic_object=Joke)\n",
"\n",
"prompt = PromptTemplate(\n",
" template=\"Answer the user query.\\n{format_instructions}\\n{query}\\n\",\n",
" input_variables=[\"query\"],\n",
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
")\n",
"\n",
"chain = prompt | model | parser\n",
"\n",
"chain.invoke({\"query\": joke_query})"
]
},
{
"cell_type": "markdown",
"id": "51ffa2e3",
"metadata": {},
"source": [
"Note that we are passing `format_instructions` from the parser directly into the prompt. You can and should experiment with adding your own formatting hints in the other parts of your prompt to either augment or replace the default instructions:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "72de9c82",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The output should be formatted as a JSON instance that conforms to the JSON schema below.\\n\\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\\nthe object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\\n\\nHere is the output schema:\\n```\\n{\"properties\": {\"setup\": {\"title\": \"Setup\", \"description\": \"question to set up a joke\", \"type\": \"string\"}, \"punchline\": {\"title\": \"Punchline\", \"description\": \"answer to resolve the joke\", \"type\": \"string\"}}, \"required\": [\"setup\", \"punchline\"]}\\n```'"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"parser.get_format_instructions()"
]
},
{
"cell_type": "markdown",
"id": "37d801be",
"metadata": {},
"source": [
"## Streaming\n",
"\n",
"As mentioned above, a key difference between the `JsonOutputParser` and the `PydanticOutputParser` is that the `JsonOutputParser` output parser supports streaming partial chunks. Here's what that looks like:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "0309256d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{}\n",
"{'setup': ''}\n",
"{'setup': 'Why'}\n",
"{'setup': 'Why couldn'}\n",
"{'setup': \"Why couldn't\"}\n",
"{'setup': \"Why couldn't the\"}\n",
"{'setup': \"Why couldn't the bicycle\"}\n",
"{'setup': \"Why couldn't the bicycle stand\"}\n",
"{'setup': \"Why couldn't the bicycle stand up\"}\n",
"{'setup': \"Why couldn't the bicycle stand up by\"}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself\"}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself?\"}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself?\", 'punchline': ''}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself?\", 'punchline': 'Because'}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself?\", 'punchline': 'Because it'}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself?\", 'punchline': 'Because it was'}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself?\", 'punchline': 'Because it was two'}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself?\", 'punchline': 'Because it was two tired'}\n",
"{'setup': \"Why couldn't the bicycle stand up by itself?\", 'punchline': 'Because it was two tired!'}\n"
]
}
],
"source": [
"for s in chain.stream({\"query\": joke_query}):\n",
" print(s)"
]
},
{
"cell_type": "markdown",
"id": "344bd968",
"metadata": {},
"source": [
"## Without Pydantic\n",
"\n",
"You can also use the `JsonOutputParser` without Pydantic. This will prompt the model to return JSON, but doesn't provide specifics about what the schema should be."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "dd3806d1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'response': \"Sure! Here's a joke for you: Why couldn't the bicycle stand up by itself? Because it was two tired!\"}"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joke_query = \"Tell me a joke.\"\n",
"\n",
"parser = JsonOutputParser()\n",
"\n",
"prompt = PromptTemplate(\n",
" template=\"Answer the user query.\\n{format_instructions}\\n{query}\\n\",\n",
" input_variables=[\"query\"],\n",
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
")\n",
"\n",
"chain = prompt | model | parser\n",
"\n",
"chain.invoke({\"query\": joke_query})"
]
},
{
"cell_type": "markdown",
"id": "1eefe12b",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now learned one way to prompt a model to return structured JSON. Next, check out the [broader guide on obtaining structured output](/docs/how_to/structured_output) for other techniques."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4d12261",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,284 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4d6c0c86",
"metadata": {},
"source": [
"# How to retry when a parsing error occurs\n",
"\n",
"While in some cases it is possible to fix any parsing mistakes by only looking at the output, in other cases it isn't. An example of this is when the output is not just in the incorrect format, but is partially complete. Consider the below example."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "f28526bd",
"metadata": {},
"outputs": [],
"source": [
"from langchain.output_parsers import (\n",
" OutputFixingParser,\n",
" PydanticOutputParser,\n",
")\n",
"from langchain.prompts import (\n",
" PromptTemplate,\n",
")\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"from langchain_openai import ChatOpenAI, OpenAI"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "67c5e1ac",
"metadata": {},
"outputs": [],
"source": [
"template = \"\"\"Based on the user question, provide an Action and Action Input for what step should be taken.\n",
"{format_instructions}\n",
"Question: {query}\n",
"Response:\"\"\"\n",
"\n",
"\n",
"class Action(BaseModel):\n",
" action: str = Field(description=\"action to take\")\n",
" action_input: str = Field(description=\"input to the action\")\n",
"\n",
"\n",
"parser = PydanticOutputParser(pydantic_object=Action)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "007aa87f",
"metadata": {},
"outputs": [],
"source": [
"prompt = PromptTemplate(\n",
" template=\"Answer the user query.\\n{format_instructions}\\n{query}\\n\",\n",
" input_variables=[\"query\"],\n",
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "10d207ff",
"metadata": {},
"outputs": [],
"source": [
"prompt_value = prompt.format_prompt(query=\"who is leo di caprios gf?\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "68622837",
"metadata": {},
"outputs": [],
"source": [
"bad_response = '{\"action\": \"search\"}'"
]
},
{
"cell_type": "markdown",
"id": "25631465",
"metadata": {},
"source": [
"If we try to parse this response as is, we will get an error:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "894967c1",
"metadata": {},
"outputs": [
{
"ename": "OutputParserException",
"evalue": "Failed to parse Action from completion {\"action\": \"search\"}. Got: 1 validation error for Action\naction_input\n field required (type=value_error.missing)",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)",
"File \u001b[0;32m~/workplace/langchain/libs/langchain/langchain/output_parsers/pydantic.py:30\u001b[0m, in \u001b[0;36mPydanticOutputParser.parse\u001b[0;34m(self, text)\u001b[0m\n\u001b[1;32m 29\u001b[0m json_object \u001b[38;5;241m=\u001b[39m json\u001b[38;5;241m.\u001b[39mloads(json_str, strict\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[0;32m---> 30\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpydantic_object\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparse_obj\u001b[49m\u001b[43m(\u001b[49m\u001b[43mjson_object\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 32\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (json\u001b[38;5;241m.\u001b[39mJSONDecodeError, ValidationError) \u001b[38;5;28;01mas\u001b[39;00m e:\n",
"File \u001b[0;32m~/.pyenv/versions/3.10.1/envs/langchain/lib/python3.10/site-packages/pydantic/main.py:526\u001b[0m, in \u001b[0;36mpydantic.main.BaseModel.parse_obj\u001b[0;34m()\u001b[0m\n",
"File \u001b[0;32m~/.pyenv/versions/3.10.1/envs/langchain/lib/python3.10/site-packages/pydantic/main.py:341\u001b[0m, in \u001b[0;36mpydantic.main.BaseModel.__init__\u001b[0;34m()\u001b[0m\n",
"\u001b[0;31mValidationError\u001b[0m: 1 validation error for Action\naction_input\n field required (type=value_error.missing)",
"\nDuring handling of the above exception, another exception occurred:\n",
"\u001b[0;31mOutputParserException\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[6], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mparser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparse\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbad_response\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/workplace/langchain/libs/langchain/langchain/output_parsers/pydantic.py:35\u001b[0m, in \u001b[0;36mPydanticOutputParser.parse\u001b[0;34m(self, text)\u001b[0m\n\u001b[1;32m 33\u001b[0m name \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpydantic_object\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\n\u001b[1;32m 34\u001b[0m msg \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFailed to parse \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m from completion \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtext\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m. Got: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00me\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m---> 35\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m OutputParserException(msg, llm_output\u001b[38;5;241m=\u001b[39mtext)\n",
"\u001b[0;31mOutputParserException\u001b[0m: Failed to parse Action from completion {\"action\": \"search\"}. Got: 1 validation error for Action\naction_input\n field required (type=value_error.missing)"
]
}
],
"source": [
"parser.parse(bad_response)"
]
},
{
"cell_type": "markdown",
"id": "f6b64696",
"metadata": {},
"source": [
"If we try to use the `OutputFixingParser` to fix this error, it will be confused - namely, it doesn't know what to actually put for action input."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "78b2b40d",
"metadata": {},
"outputs": [],
"source": [
"fix_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "4fe1301d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Action(action='search', action_input='input')"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fix_parser.parse(bad_response)"
]
},
{
"cell_type": "markdown",
"id": "9bd9ea7d",
"metadata": {},
"source": [
"Instead, we can use the RetryOutputParser, which passes in the prompt (as well as the original output) to try again to get a better response."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "7e8a8a28",
"metadata": {},
"outputs": [],
"source": [
"from langchain.output_parsers import RetryOutputParser"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "5c86e141",
"metadata": {},
"outputs": [],
"source": [
"retry_parser = RetryOutputParser.from_llm(parser=parser, llm=OpenAI(temperature=0))"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "9c04f731",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Action(action='search', action_input='leo di caprio girlfriend')"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retry_parser.parse_with_prompt(bad_response, prompt_value)"
]
},
{
"cell_type": "markdown",
"id": "16827256-5801-4388-b6fa-608991e29961",
"metadata": {},
"source": [
"We can also add the RetryOutputParser easily with a custom chain which transform the raw LLM/ChatModel output into a more workable format."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "7eaff2fb-56d3-481c-99a1-a968a49d0654",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Action(action='search', action_input='leo di caprio girlfriend')\n"
]
}
],
"source": [
"from langchain_core.runnables import RunnableLambda, RunnableParallel\n",
"\n",
"completion_chain = prompt | OpenAI(temperature=0)\n",
"\n",
"main_chain = RunnableParallel(\n",
" completion=completion_chain, prompt_value=prompt\n",
") | RunnableLambda(lambda x: retry_parser.parse_with_prompt(**x))\n",
"\n",
"\n",
"main_chain.invoke({\"query\": \"who is leo di caprios gf?\"})"
]
},
{
"cell_type": "markdown",
"id": "e3a2513a",
"metadata": {},
"source": [
"Find out api documentation for [RetryOutputParser](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.retry.RetryOutputParser.html#langchain.output_parsers.retry.RetryOutputParser)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a2f94fd8",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,261 @@
{
"cells": [
{
"cell_type": "raw",
"id": "38831021-76ed-48b3-9f62-d1241a68b6ad",
"metadata": {},
"source": [
"---\n",
"sidebar_position: 3\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "a745f98b-c495-44f6-a882-757c38992d76",
"metadata": {},
"source": [
"# How to use output parsers to parse an LLM response into structured format\n",
"\n",
"Language models output text. But there are times where you want to get more structured information than just text back. While some model providers support [built-in ways to return structured output](/docs/how_to/structured_output), not all do.\n",
"\n",
"Output parsers are classes that help structure language model responses. There are two main methods an output parser must implement:\n",
"\n",
"- \"Get format instructions\": A method which returns a string containing instructions for how the output of a language model should be formatted.\n",
"- \"Parse\": A method which takes in a string (assumed to be the response from a language model) and parses it into some structure.\n",
"\n",
"And then one optional one:\n",
"\n",
"- \"Parse with prompt\": A method which takes in a string (assumed to be the response from a language model) and a prompt (assumed to be the prompt that generated such a response) and parses it into some structure. The prompt is largely provided in the event the OutputParser wants to retry or fix the output in some way, and needs information from the prompt to do so.\n",
"\n",
"## Get started\n",
"\n",
"Below we go over the main type of output parser, the `PydanticOutputParser`."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "1594b2bf-2a6f-47bb-9a81-38930f8e606b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.output_parsers import PydanticOutputParser\n",
"from langchain_core.prompts import PromptTemplate\n",
"from langchain_core.pydantic_v1 import BaseModel, Field, validator\n",
"from langchain_openai import OpenAI\n",
"\n",
"model = OpenAI(model_name=\"gpt-3.5-turbo-instruct\", temperature=0.0)\n",
"\n",
"\n",
"# Define your desired data structure.\n",
"class Joke(BaseModel):\n",
" setup: str = Field(description=\"question to set up a joke\")\n",
" punchline: str = Field(description=\"answer to resolve the joke\")\n",
"\n",
" # You can add custom validation logic easily with Pydantic.\n",
" @validator(\"setup\")\n",
" def question_ends_with_question_mark(cls, field):\n",
" if field[-1] != \"?\":\n",
" raise ValueError(\"Badly formed question!\")\n",
" return field\n",
"\n",
"\n",
"# Set up a parser + inject instructions into the prompt template.\n",
"parser = PydanticOutputParser(pydantic_object=Joke)\n",
"\n",
"prompt = PromptTemplate(\n",
" template=\"Answer the user query.\\n{format_instructions}\\n{query}\\n\",\n",
" input_variables=[\"query\"],\n",
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
")\n",
"\n",
"# And a query intended to prompt a language model to populate the data structure.\n",
"prompt_and_model = prompt | model\n",
"output = prompt_and_model.invoke({\"query\": \"Tell me a joke.\"})\n",
"parser.invoke(output)"
]
},
{
"cell_type": "markdown",
"id": "75976cd6-78e2-458b-821f-3ddf3683466b",
"metadata": {},
"source": [
"## LCEL\n",
"\n",
"Output parsers implement the [Runnable interface](/docs/expression_language/interface), the basic building block of the [LangChain Expression Language (LCEL)](/docs/expression_language/). This means they support `invoke`, `ainvoke`, `stream`, `astream`, `batch`, `abatch`, `astream_log` calls.\n",
"\n",
"Output parsers accept a string or `BaseMessage` as input and can return an arbitrary type."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "34f7ff0c-8443-4eb9-8704-b4f821811d93",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"parser.invoke(output)"
]
},
{
"cell_type": "markdown",
"id": "bdebf4a5-57a8-4632-bd17-56723d431cf1",
"metadata": {},
"source": [
"Instead of manually invoking the parser, we also could've just added it to our `Runnable` sequence:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "51f7fff5-e9bd-49a1-b5ab-b9ff281b93cb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain = prompt | model | parser\n",
"chain.invoke({\"query\": \"Tell me a joke.\"})"
]
},
{
"cell_type": "markdown",
"id": "d88590a0-f36b-4ad5-8a56-d300971a6440",
"metadata": {},
"source": [
"While all parsers support the streaming interface, only certain parsers can stream through partially parsed objects, since this is highly dependent on the output type. Parsers which cannot construct partial objects will simply yield the fully parsed output.\n",
"\n",
"The `SimpleJsonOutputParser` for example can stream through partial outputs:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "d7ecfe4d-dae8-4452-98ea-e48bdc498788",
"metadata": {},
"outputs": [],
"source": [
"from langchain.output_parsers.json import SimpleJsonOutputParser\n",
"\n",
"json_prompt = PromptTemplate.from_template(\n",
" \"Return a JSON object with an `answer` key that answers the following question: {question}\"\n",
")\n",
"json_parser = SimpleJsonOutputParser()\n",
"json_chain = json_prompt | model | json_parser"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "cc2b999e-47aa-41f4-ba6a-13b20a204576",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{},\n",
" {'answer': ''},\n",
" {'answer': 'Ant'},\n",
" {'answer': 'Anton'},\n",
" {'answer': 'Antonie'},\n",
" {'answer': 'Antonie van'},\n",
" {'answer': 'Antonie van Lee'},\n",
" {'answer': 'Antonie van Leeu'},\n",
" {'answer': 'Antonie van Leeuwen'},\n",
" {'answer': 'Antonie van Leeuwenho'},\n",
" {'answer': 'Antonie van Leeuwenhoek'}]"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(json_chain.stream({\"question\": \"Who invented the microscope?\"}))"
]
},
{
"cell_type": "markdown",
"id": "3ca23082-c602-4ee8-af8c-a185b1f42bd1",
"metadata": {},
"source": [
"While the PydanticOutputParser cannot:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "07420e8f-e144-42aa-93ac-de890b6222f5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')]"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(chain.stream({\"query\": \"Tell me a joke.\"}))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,282 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "181b5b6d",
"metadata": {},
"source": [
"# How to parse XML output\n",
"\n",
"LLMs from different providers often have different strengths depending on the specific data they are trianed on. This also means that some may be \"better\" and more reliable at generating output in formats other than JSON.\n",
"\n",
"This guide shows you how to use the [`XMLOutputParser`](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.xml.XMLOutputParser.html) to prompt models for XML output, then and parse that output into a usable format.\n",
"\n",
":::{.callout-note}\n",
"Keep in mind that large language models are leaky abstractions! You'll have to use an LLM with sufficient capacity to generate well-formed XML.\n",
":::\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Chat models](/docs/concepts/#chat-models)\n",
"- [Output parsers](/docs/concepts/#output-parsers)\n",
"- [Structured output](/docs/how_to/structured_output)\n",
"- [Chaining runnables together](/docs/how_to/sequence/)\n",
"`}/>\n",
"```\n",
"\n",
"In the following examples, we use Anthropic's Claude-2 model (https://docs.anthropic.com/claude/docs), which is one such model that is optimized for XML tags."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aee0c52e",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain langchain-anthropic\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"ANTHROPIC_API_KEY\"] = getpass()"
]
},
{
"cell_type": "markdown",
"id": "da312f86-0d2a-4aef-a09d-1e72bd0ea9b1",
"metadata": {},
"source": [
"Let's start with a simple request to the model."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "b03785af-69fc-40a1-a1be-c04ed6fade70",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Here is the shortened filmography for Tom Hanks, with movies enclosed in XML tags:\n",
"\n",
"<movie>Splash</movie>\n",
"<movie>Big</movie>\n",
"<movie>A League of Their Own</movie>\n",
"<movie>Sleepless in Seattle</movie>\n",
"<movie>Forrest Gump</movie>\n",
"<movie>Toy Story</movie>\n",
"<movie>Apollo 13</movie>\n",
"<movie>Saving Private Ryan</movie>\n",
"<movie>Cast Away</movie>\n",
"<movie>The Da Vinci Code</movie>\n"
]
}
],
"source": [
"from langchain_anthropic import ChatAnthropic\n",
"from langchain_core.output_parsers import XMLOutputParser\n",
"from langchain_core.prompts import PromptTemplate\n",
"\n",
"model = ChatAnthropic(model=\"claude-2.1\", max_tokens_to_sample=512, temperature=0.1)\n",
"\n",
"actor_query = \"Generate the shortened filmography for Tom Hanks.\"\n",
"\n",
"output = model.invoke(\n",
" f\"\"\"{actor_query}\n",
"Please enclose the movies in <movie></movie> tags\"\"\"\n",
")\n",
"\n",
"print(output.content)"
]
},
{
"cell_type": "markdown",
"id": "4db65781-3d54-4ba6-ae26-5b4ead47a4c8",
"metadata": {},
"source": [
"This actually worked pretty well! But it would be nice to parse that XML into a more easily usable format. We can use the `XMLOutputParser` to both add default format instructions to the prompt and parse outputted XML into a dict:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "6917e057",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The output should be formatted as a XML file.\\n1. Output should conform to the tags below. \\n2. If tags are not given, make them on your own.\\n3. Remember to always open and close all the tags.\\n\\nAs an example, for the tags [\"foo\", \"bar\", \"baz\"]:\\n1. String \"<foo>\\n <bar>\\n <baz></baz>\\n </bar>\\n</foo>\" is a well-formatted instance of the schema. \\n2. String \"<foo>\\n <bar>\\n </foo>\" is a badly-formatted instance.\\n3. String \"<foo>\\n <tag>\\n </tag>\\n</foo>\" is a badly-formatted instance.\\n\\nHere are the output tags:\\n```\\nNone\\n```'"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"parser = XMLOutputParser()\n",
"\n",
"# We will add these instructions to the prompt below\n",
"parser.get_format_instructions()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "87ba8d11",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'filmography': [{'movie': [{'title': 'Big'}, {'year': '1988'}]}, {'movie': [{'title': 'Forrest Gump'}, {'year': '1994'}]}, {'movie': [{'title': 'Toy Story'}, {'year': '1995'}]}, {'movie': [{'title': 'Saving Private Ryan'}, {'year': '1998'}]}, {'movie': [{'title': 'Cast Away'}, {'year': '2000'}]}]}\n"
]
}
],
"source": [
"prompt = PromptTemplate(\n",
" template=\"\"\"{query}\\n{format_instructions}\"\"\",\n",
" input_variables=[\"query\"],\n",
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
")\n",
"\n",
"chain = prompt | model | parser\n",
"\n",
"output = chain.invoke({\"query\": actor_query})\n",
"print(output)"
]
},
{
"cell_type": "markdown",
"id": "327f5479-77e0-4549-8393-2cd7a286d491",
"metadata": {},
"source": [
"We can also add some tags to tailor the output to our needs. You can and should experiment with adding your own formatting hints in the other parts of your prompt to either augment or replace the default instructions:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "4af50494",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The output should be formatted as a XML file.\\n1. Output should conform to the tags below. \\n2. If tags are not given, make them on your own.\\n3. Remember to always open and close all the tags.\\n\\nAs an example, for the tags [\"foo\", \"bar\", \"baz\"]:\\n1. String \"<foo>\\n <bar>\\n <baz></baz>\\n </bar>\\n</foo>\" is a well-formatted instance of the schema. \\n2. String \"<foo>\\n <bar>\\n </foo>\" is a badly-formatted instance.\\n3. String \"<foo>\\n <tag>\\n </tag>\\n</foo>\" is a badly-formatted instance.\\n\\nHere are the output tags:\\n```\\n[\\'movies\\', \\'actor\\', \\'film\\', \\'name\\', \\'genre\\']\\n```'"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"parser = XMLOutputParser(tags=[\"movies\", \"actor\", \"film\", \"name\", \"genre\"])\n",
"\n",
"# We will add these instructions to the prompt below\n",
"parser.get_format_instructions()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "b722a235",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'movies': [{'actor': [{'name': 'Tom Hanks'}, {'film': [{'name': 'Forrest Gump'}, {'genre': 'Drama'}]}, {'film': [{'name': 'Cast Away'}, {'genre': 'Adventure'}]}, {'film': [{'name': 'Saving Private Ryan'}, {'genre': 'War'}]}]}]}\n"
]
}
],
"source": [
"prompt = PromptTemplate(\n",
" template=\"\"\"{query}\\n{format_instructions}\"\"\",\n",
" input_variables=[\"query\"],\n",
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
")\n",
"\n",
"\n",
"chain = prompt | model | parser\n",
"\n",
"output = chain.invoke({\"query\": actor_query})\n",
"\n",
"print(output)"
]
},
{
"cell_type": "markdown",
"id": "61ab269a",
"metadata": {},
"source": [
"This output parser also supports streaming of partial chunks. Here's an example:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "808a5df5-b11e-42a0-bd7a-6b95ca0c3eba",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'movies': [{'actor': [{'name': 'Tom Hanks'}]}]}\n",
"{'movies': [{'actor': [{'film': [{'name': 'Forrest Gump'}]}]}]}\n",
"{'movies': [{'actor': [{'film': [{'genre': 'Drama'}]}]}]}\n",
"{'movies': [{'actor': [{'film': [{'name': 'Cast Away'}]}]}]}\n",
"{'movies': [{'actor': [{'film': [{'genre': 'Adventure'}]}]}]}\n",
"{'movies': [{'actor': [{'film': [{'name': 'Saving Private Ryan'}]}]}]}\n",
"{'movies': [{'actor': [{'film': [{'genre': 'War'}]}]}]}\n"
]
}
],
"source": [
"for s in chain.stream({\"query\": actor_query}):\n",
" print(s)"
]
},
{
"cell_type": "markdown",
"id": "6902fe6f",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now learned how to prompt a model to return XML. Next, check out the [broader guide on obtaining structured output](/docs/how_to/structured_output) for other related techniques."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,173 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "72b1b316",
"metadata": {},
"source": [
"# How to parse YAML output\n",
"\n",
"LLMs from different providers often have different strengths depending on the specific data they are trianed on. This also means that some may be \"better\" and more reliable at generating output in formats other than JSON.\n",
"\n",
"This output parser allows users to specify an arbitrary schema and query LLMs for outputs that conform to that schema, using YAML to format their response.\n",
"\n",
":::{.callout-note}\n",
"Keep in mind that large language models are leaky abstractions! You'll have to use an LLM with sufficient capacity to generate well-formed YAML.\n",
":::\n",
"\n",
"```{=mdx}\n",
"import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n",
"\n",
"<PrerequisiteLinks content={`\n",
"- [Chat models](/docs/concepts/#chat-models)\n",
"- [Output parsers](/docs/concepts/#output-parsers)\n",
"- [Structured output](/docs/how_to/structured_output)\n",
"- [Chaining runnables together](/docs/how_to/sequence/)\n",
"`}/>\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f142c8ca",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain langchain-openai\n",
"\n",
"import os\n",
"from getpass import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass()"
]
},
{
"cell_type": "markdown",
"id": "cc479f3a",
"metadata": {},
"source": [
"We use [Pydantic](https://docs.pydantic.dev) with the [`YamlOutputParser`](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.yaml.YamlOutputParser.html#langchain.output_parsers.yaml.YamlOutputParser) to declare our data model and give the model more context as to what type of YAML it should generate:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4ccf45a3",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Joke(setup=\"Why couldn't the bicycle find its way home?\", punchline='Because it lost its bearings!')"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.output_parsers import YamlOutputParser\n",
"from langchain_core.prompts import PromptTemplate\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"\n",
"# Define your desired data structure.\n",
"class Joke(BaseModel):\n",
" setup: str = Field(description=\"question to set up a joke\")\n",
" punchline: str = Field(description=\"answer to resolve the joke\")\n",
"\n",
"\n",
"model = ChatOpenAI(temperature=0)\n",
"\n",
"# And a query intented to prompt a language model to populate the data structure.\n",
"joke_query = \"Tell me a joke.\"\n",
"\n",
"# Set up a parser + inject instructions into the prompt template.\n",
"parser = YamlOutputParser(pydantic_object=Joke)\n",
"\n",
"prompt = PromptTemplate(\n",
" template=\"Answer the user query.\\n{format_instructions}\\n{query}\\n\",\n",
" input_variables=[\"query\"],\n",
" partial_variables={\"format_instructions\": parser.get_format_instructions()},\n",
")\n",
"\n",
"chain = prompt | model | parser\n",
"\n",
"chain.invoke({\"query\": joke_query})"
]
},
{
"cell_type": "markdown",
"id": "25e2254a",
"metadata": {},
"source": [
"The parser will automatically parse the output YAML and create a Pydantic model with the data. We can see the parser's `format_instructions`, which get added to the prompt:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "a4d12261",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The output should be formatted as a YAML instance that conforms to the given JSON schema below.\\n\\n# Examples\\n## Schema\\n```\\n{\"title\": \"Players\", \"description\": \"A list of players\", \"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/Player\"}, \"definitions\": {\"Player\": {\"title\": \"Player\", \"type\": \"object\", \"properties\": {\"name\": {\"title\": \"Name\", \"description\": \"Player name\", \"type\": \"string\"}, \"avg\": {\"title\": \"Avg\", \"description\": \"Batting average\", \"type\": \"number\"}}, \"required\": [\"name\", \"avg\"]}}}\\n```\\n## Well formatted instance\\n```\\n- name: John Doe\\n avg: 0.3\\n- name: Jane Maxfield\\n avg: 1.4\\n```\\n\\n## Schema\\n```\\n{\"properties\": {\"habit\": { \"description\": \"A common daily habit\", \"type\": \"string\" }, \"sustainable_alternative\": { \"description\": \"An environmentally friendly alternative to the habit\", \"type\": \"string\"}}, \"required\": [\"habit\", \"sustainable_alternative\"]}\\n```\\n## Well formatted instance\\n```\\nhabit: Using disposable water bottles for daily hydration.\\nsustainable_alternative: Switch to a reusable water bottle to reduce plastic waste and decrease your environmental footprint.\\n``` \\n\\nPlease follow the standard YAML formatting conventions with an indent of 2 spaces and make sure that the data types adhere strictly to the following JSON schema: \\n```\\n{\"properties\": {\"setup\": {\"title\": \"Setup\", \"description\": \"question to set up a joke\", \"type\": \"string\"}, \"punchline\": {\"title\": \"Punchline\", \"description\": \"answer to resolve the joke\", \"type\": \"string\"}}, \"required\": [\"setup\", \"punchline\"]}\\n```\\n\\nMake sure to always enclose the YAML output in triple backticks (```). Please do not add anything other than valid YAML output!'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"parser.get_format_instructions()"
]
},
{
"cell_type": "markdown",
"id": "b31ac9ce",
"metadata": {},
"source": [
"You can and should experiment with adding your own formatting hints in the other parts of your prompt to either augment or replace the default instructions.\n",
"\n",
"## Next steps\n",
"\n",
"You've now learned how to prompt a model to return XML. Next, check out the [broader guide on obtaining structured output](/docs/how_to/structured_output) for other related techniques."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "666ba894",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

Some files were not shown because too many files have changed in this diff Show More