diff --git a/docs/.local_build.sh b/docs/.local_build.sh index cde92e265f664..62b41ca2989f3 100755 --- a/docs/.local_build.sh +++ b/docs/.local_build.sh @@ -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 diff --git a/docs/docs/expression_language/get_started.ipynb b/docs/docs/expression_language/get_started.ipynb index f3f55a36fe51d..853e96c58bd23 100644 --- a/docs/docs/expression_language/get_started.ipynb +++ b/docs/docs/expression_language/get_started.ipynb @@ -529,7 +529,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.10.1" } }, "nbformat": 4, diff --git a/docs/docs/expression_language/primitives/parallel.ipynb b/docs/docs/expression_language/primitives/parallel.ipynb index 8e3f636fd702b..b06250dcc8ec7 100644 --- a/docs/docs/expression_language/primitives/parallel.ipynb +++ b/docs/docs/expression_language/primitives/parallel.ipynb @@ -302,7 +302,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.6" + "version": "3.10.1" } }, "nbformat": 4, diff --git a/docs/docs/expression_language/primitives/passthrough.ipynb b/docs/docs/expression_language/primitives/passthrough.ipynb index b21d04317ac30..86c231c247def 100644 --- a/docs/docs/expression_language/primitives/passthrough.ipynb +++ b/docs/docs/expression_language/primitives/passthrough.ipynb @@ -153,7 +153,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.6" + "version": "3.10.1" } }, "nbformat": 4, diff --git a/docs/docs/expression_language/primitives/sequence.ipynb b/docs/docs/expression_language/primitives/sequence.ipynb index 8aec2b496ceba..9aebcd439b6d7 100644 --- a/docs/docs/expression_language/primitives/sequence.ipynb +++ b/docs/docs/expression_language/primitives/sequence.ipynb @@ -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, diff --git a/docs/docs/get_started/quickstart.mdx b/docs/docs/get_started/quickstart.mdx index 87b5a10013214..45cf58bf19aa0 100644 --- a/docs/docs/get_started/quickstart.mdx +++ b/docs/docs/get_started/quickstart.mdx @@ -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 diff --git a/docs/docs/modules/model_io/chat/function_calling.ipynb b/docs/docs/modules/model_io/chat/function_calling.ipynb index 92f66b429efae..c3b1c38bf105d 100644 --- a/docs/docs/modules/model_io/chat/function_calling.ipynb +++ b/docs/docs/modules/model_io/chat/function_calling.ipynb @@ -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, diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 6be04e64da16b..c9bd546a0034c 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -20,8 +20,8 @@ const config = { // For GitHub pages deployment, it is often '//' 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" }, ] diff --git a/docs/package.json b/docs/package.json index e3c6f85a0ee88..e4a98aa730226 100644 --- a/docs/package.json +++ b/docs/package.json @@ -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", diff --git a/docs/scripts/resolve_versioned_links_in_markdown.py b/docs/scripts/resolve_versioned_links_in_markdown.py new file mode 100644 index 0000000000000..ccb919f1e03ee --- /dev/null +++ b/docs/scripts/resolve_versioned_links_in_markdown.py @@ -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]) diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index d486a725b5e8e..5aa1ae857d1a0 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -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); diff --git a/docs/src/theme/ChatModelTabs.js b/docs/src/theme/ChatModelTabs.js index 60e3d18dc4da1..e1218acbb2435 100644 --- a/docs/src/theme/ChatModelTabs.js +++ b/docs/src/theme/ChatModelTabs.js @@ -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 ( - <> -
Install dependencies
- {`pip install -qU ${packageName}`} -
Set environment variables
- {apiKeyText} - - ); -} - /** * @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) { {tabItems .filter((tabItem) => !tabItem.shouldHide) - .map((tabItem) => ( - - - {tabItem.text} - - ))} + .map((tabItem) => { + const apiKeyText = `import getpass +import os + +os.environ["${tabItem.apiKeyName}"] = getpass.getpass()`; + return ( + + {`pip install -qU ${tabItem.packageName}`} + {apiKeyText + "\n\n" + tabItem.text} + + ); + }) + } ); } diff --git a/docs/src/theme/DocVersionBanner/index.js b/docs/src/theme/DocVersionBanner/index.js new file mode 100644 index 0000000000000..6a18eafebff4b --- /dev/null +++ b/docs/src/theme/DocVersionBanner/index.js @@ -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 ( + {versionMetadata.label}, + }}> + { + 'This is unreleased documentation for {siteTitle}\'s {versionLabel} version.' + } + + ); +} +function UnmaintainedVersionLabel({siteTitle, versionMetadata}) { + return ( + {versionMetadata.label}, + }}> + { + 'This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.' + } + + ); +} +const BannerLabelComponents = { + unreleased: UnreleasedVersionLabel, + unmaintained: UnmaintainedVersionLabel, +}; +function BannerLabel(props) { + const BannerLabelComponent = + BannerLabelComponents[props.versionMetadata.banner]; + return ; +} +function LatestVersionSuggestionLabel({versionLabel, to, onClick}) { + return ( + + + + this version + + + + ), + }}> + { + 'For the current stable version, see {latestVersionLink} ({versionLabel}).' + } + + ); +} +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 ( +
+
+ +
+
+ savePreferredVersionName(latestVersionSuggestion.name)} + /> +
+
+ ); +} + +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 ( +
+
+ {versionMetadata.label}, + }}> + { + 'This is a stable version of documentation for {siteTitle}\'s version {versionLabel}.' + } + +
+
+ {versionMetadata.label}, + latestVersionLink: ( + + savePreferredVersionName("0.2.x")}> + + this experimental version + + + + ), + }}> + { + 'You can also check out {latestVersionLink} for an updated experience.' + } + +
+
+ ); +} + +export default function DocVersionBanner({className}) { + const versionMetadata = useDocsVersion(); + if (versionMetadata.banner) { + return ( + + ); + } else if (versionMetadata.isLast) { + // Uncomment when we are ready to direct people to new build + // return ( + // + // ); + return null; + } + return null; +} diff --git a/docs/src/theme/PrerequisiteLinks.js b/docs/src/theme/PrerequisiteLinks.js new file mode 100644 index 0000000000000..30c71a4b8499f --- /dev/null +++ b/docs/src/theme/PrerequisiteLinks.js @@ -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 ( + +
+ This guide will assume familiarity with the following concepts: +
+
+ + ); +} diff --git a/docs/vercel_build.sh b/docs/vercel_build.sh index f570319b46dfe..97142f62826d0 100755 --- a/docs/vercel_build.sh +++ b/docs/vercel_build.sh @@ -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 \ No newline at end of file +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 diff --git a/docs/versioned_docs/version-0.2.x/.gitignore b/docs/versioned_docs/version-0.2.x/.gitignore new file mode 100644 index 0000000000000..25a6e30a4b775 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/.gitignore @@ -0,0 +1,7 @@ +.yarn/ + +node_modules/ + +.docusaurus +.cache-loader +docs/api \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/_templates/integration.mdx b/docs/versioned_docs/version-0.2.x/_templates/integration.mdx new file mode 100644 index 0000000000000..5e686ad3fc122 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/_templates/integration.mdx @@ -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 +``` diff --git a/docs/versioned_docs/version-0.2.x/additional_resources/dependents.mdx b/docs/versioned_docs/version-0.2.x/additional_resources/dependents.mdx new file mode 100644 index 0000000000000..a09df5027ecdc --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/additional_resources/dependents.mdx @@ -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` diff --git a/docs/versioned_docs/version-0.2.x/additional_resources/tutorials.mdx b/docs/versioned_docs/version-0.2.x/additional_resources/tutorials.mdx new file mode 100644 index 0000000000000..9bc9dc53c7177 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/additional_resources/tutorials.mdx @@ -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) + +--------------------- + + diff --git a/docs/versioned_docs/version-0.2.x/additional_resources/youtube.mdx b/docs/versioned_docs/version-0.2.x/additional_resources/youtube.mdx new file mode 100644 index 0000000000000..1fde4c30208c7 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/additional_resources/youtube.mdx @@ -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 Business’s 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] diff --git a/docs/versioned_docs/version-0.2.x/changelog/core.mdx b/docs/versioned_docs/version-0.2.x/changelog/core.mdx new file mode 100644 index 0000000000000..9c43d501fcbaf --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/changelog/core.mdx @@ -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. diff --git a/docs/versioned_docs/version-0.2.x/changelog/langchain.mdx b/docs/versioned_docs/version-0.2.x/changelog/langchain.mdx new file mode 100644 index 0000000000000..bffcce729a953 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/changelog/langchain.mdx @@ -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 | \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/concepts.mdx b/docs/versioned_docs/version-0.2.x/concepts.mdx new file mode 100644 index 0000000000000..0e3051fcc3b0e --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/concepts.mdx @@ -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. + + + +### `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 (we’ve 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. We’re 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 it’s 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 it’s 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) diff --git a/docs/versioned_docs/version-0.2.x/contributing/code.mdx b/docs/versioned_docs/version-0.2.x/contributing/code.mdx new file mode 100644 index 0000000000000..7825831763563 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/code.mdx @@ -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. diff --git a/docs/versioned_docs/version-0.2.x/contributing/documentation/_category_.yml b/docs/versioned_docs/version-0.2.x/contributing/documentation/_category_.yml new file mode 100644 index 0000000000000..7a89d5111677a --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/documentation/_category_.yml @@ -0,0 +1,2 @@ +label: 'Documentation' +position: 3 \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/contributing/documentation/style_guide.mdx b/docs/versioned_docs/version-0.2.x/contributing/documentation/style_guide.mdx new file mode 100644 index 0000000000000..e8da9425955d5 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/documentation/style_guide.mdx @@ -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. diff --git a/docs/versioned_docs/version-0.2.x/contributing/documentation/technical_logistics.mdx b/docs/versioned_docs/version-0.2.x/contributing/documentation/technical_logistics.mdx new file mode 100644 index 0000000000000..4dbb0204df1a9 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/documentation/technical_logistics.mdx @@ -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). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/contributing/faq.mdx b/docs/versioned_docs/version-0.2.x/contributing/faq.mdx new file mode 100644 index 0000000000000..e0e81564a4992 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/faq.mdx @@ -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. diff --git a/docs/versioned_docs/version-0.2.x/contributing/index.mdx b/docs/versioned_docs/version-0.2.x/contributing/index.mdx new file mode 100644 index 0000000000000..95783cae45c39 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/index.mdx @@ -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. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/contributing/integrations.mdx b/docs/versioned_docs/version-0.2.x/contributing/integrations.mdx new file mode 100644 index 0000000000000..bffbacefd8078 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/integrations.mdx @@ -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.", + 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. diff --git a/docs/versioned_docs/version-0.2.x/contributing/repo_structure.mdx b/docs/versioned_docs/version-0.2.x/contributing/repo_structure.mdx new file mode 100644 index 0000000000000..fc055e3d0a1a8 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/repo_structure.mdx @@ -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. diff --git a/docs/versioned_docs/version-0.2.x/contributing/testing.mdx b/docs/versioned_docs/version-0.2.x/contributing/testing.mdx new file mode 100644 index 0000000000000..5dd0799234204 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/contributing/testing.mdx @@ -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 +``` diff --git a/docs/versioned_docs/version-0.2.x/how_to/.langchain.db b/docs/versioned_docs/version-0.2.x/how_to/.langchain.db new file mode 100644 index 0000000000000..32537d1b69326 Binary files /dev/null and b/docs/versioned_docs/version-0.2.x/how_to/.langchain.db differ diff --git a/docs/versioned_docs/version-0.2.x/how_to/HTML_header_metadata_splitter.ipynb b/docs/versioned_docs/version-0.2.x/how_to/HTML_header_metadata_splitter.ipynb new file mode 100644 index 0000000000000..1538de9c1cc03 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/HTML_header_metadata_splitter.ipynb @@ -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", + "\n", + "\n", + "\n", + "
\n", + "

Foo

\n", + "

Some intro text about Foo.

\n", + "
\n", + "

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.

\n", + "
\n", + "
\n", + "

Baz

\n", + "

Some text about Baz

\n", + "
\n", + "
\n", + "

Some concluding text about Foo

\n", + "
\n", + "\n", + "\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()\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 Berry’s 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ödel’s 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ödel’s 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ödel’s discovery was told to Hao Wang very much after the fact; but in Gödel’s 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ödel’s 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ödel’s publication of that theorem.', metadata={'Header 1': 'Kurt Gödel', 'Header 2': '2. Gödel’s 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ödel’s 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ödel’s notation.', metadata={'Header 1': 'Kurt Gödel', 'Header 2': '2. Gödel’s 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\"*—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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/HTML_section_aware_splitter.ipynb b/docs/versioned_docs/version-0.2.x/how_to/HTML_section_aware_splitter.ipynb new file mode 100644 index 0000000000000..217172d78c48a --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/HTML_section_aware_splitter.ipynb @@ -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", + " \n", + " \n", + " \n", + "
\n", + "

Foo

\n", + "

Some intro text about Foo.

\n", + "
\n", + "

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.

\n", + "
\n", + "
\n", + "

Baz

\n", + "

Some text about Baz

\n", + "
\n", + "
\n", + "

Some concluding text about Foo

\n", + "
\n", + " \n", + " \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", + " \n", + " \n", + " \n", + "
\n", + "

Foo

\n", + "

Some intro text about Foo.

\n", + "
\n", + "

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.

\n", + "
\n", + "
\n", + "

Baz

\n", + "

Some text about Baz

\n", + "
\n", + "
\n", + "

Some concluding text about Foo

\n", + "
\n", + " \n", + " \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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/MultiQueryRetriever.ipynb b/docs/versioned_docs/version-0.2.x/how_to/MultiQueryRetriever.ipynb new file mode 100644 index 0000000000000..053f98dc8a4ad --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/MultiQueryRetriever.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/agent_executor.ipynb b/docs/versioned_docs/version-0.2.x/how_to/agent_executor.ipynb new file mode 100644 index 0000000000000..8cce9e772598b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/agent_executor.ipynb @@ -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", + "\n", + " \n", + " pip install langchain\n", + " \n", + " \n", + " conda install langchain -c conda-forge\n", + " \n", + "\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", + "\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\": \"\"}},\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\": \"\"}},\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/assign.ipynb b/docs/versioned_docs/version-0.2.x/how_to/assign.ipynb new file mode 100644 index 0000000000000..c63c1530b9707 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/assign.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/binding.ipynb b/docs/versioned_docs/version-0.2.x/how_to/binding.ipynb new file mode 100644 index 0000000000000..2dfbdc062c02c --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/binding.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/caching_embeddings.ipynb b/docs/versioned_docs/version-0.2.x/how_to/caching_embeddings.ipynb new file mode 100644 index 0000000000000..f9d6ad59dc978 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/caching_embeddings.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/character_text_splitter.ipynb b/docs/versioned_docs/version-0.2.x/how_to/character_text_splitter.ipynb new file mode 100644 index 0000000000000..9ba265c0b0fe7 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/character_text_splitter.ipynb @@ -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, Russia’s 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, Russia’s 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, Russia’s 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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/chat_model_caching.ipynb b/docs/versioned_docs/version-0.2.x/how_to/chat_model_caching.ipynb new file mode 100644 index 0000000000000..25190ee4374b3 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/chat_model_caching.ipynb @@ -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", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "289b31de", + "metadata": {}, + "source": [ + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\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": [ + "# \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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/chat_streaming.ipynb b/docs/versioned_docs/version-0.2.x/how_to/chat_streaming.ipynb new file mode 100644 index 0000000000000..72d6f3c7af407 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/chat_streaming.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/chat_token_usage_tracking.ipynb b/docs/versioned_docs/version-0.2.x/how_to/chat_token_usage_tracking.ipynb new file mode 100644 index 0000000000000..de4a65236c422 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/chat_token_usage_tracking.ipynb @@ -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", + "\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.5–13 cm (3–5 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 18–24 grams (0.63–0.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 1⁄15 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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/chatbots_memory.ipynb b/docs/versioned_docs/version-0.2.x/how_to/chatbots_memory.ipynb new file mode 100644 index 0000000000000..958047a278e17 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/chatbots_memory.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/chatbots_retrieval.ipynb b/docs/versioned_docs/version-0.2.x/how_to/chatbots_retrieval.ipynb new file mode 100644 index 0000000000000..3eed67278dd8f --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/chatbots_retrieval.ipynb @@ -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", + "\n", + "{context}\n", + "\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", + "\n", + "{context}\n", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/chatbots_tools.ipynb b/docs/versioned_docs/version-0.2.x/how_to/chatbots_tools.ipynb new file mode 100644 index 0000000000000..f70e21e0fb8e9 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/chatbots_tools.ipynb @@ -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 it’s 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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/code_splitter.ipynb b/docs/versioned_docs/version-0.2.x/how_to/code_splitter.ipynb new file mode 100644 index 0000000000000..231ce86c9b4b4 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/code_splitter.ipynb @@ -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", + "\n", + "\n", + " \n", + " 🦜️🔗 LangChain\n", + " \n", + " \n", + " \n", + "
\n", + "

🦜️🔗 LangChain

\n", + "

⚡ Building applications with LLMs through composability ⚡

\n", + "
\n", + "
\n", + " As an open-source project in a rapidly developing field, we are extremely open to contributions.\n", + "
\n", + " \n", + "\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "e3e3fca1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='\\n'),\n", + " Document(page_content='\\n 🦜️🔗 LangChain'),\n", + " Document(page_content='\\n = 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='\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]<> 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: [/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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/contextual_compression.ipynb b/docs/versioned_docs/version-0.2.x/how_to/contextual_compression.ipynb new file mode 100644 index 0000000000000..7f00e920b12a1 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/contextual_compression.ipynb @@ -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 you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n", + "\n", + "Tonight, I’d 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 nation’s top legal minds, who will continue Justice Breyer’s 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 she’s been nominated, she’s 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, we’ve installed new technology like cutting-edge scanners to better detect drug smuggling. \n", + "\n", + "We’ve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \n", + "\n", + "We’re putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \n", + "\n", + "We’re 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, let’s 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 isn’t 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, we’ll 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 I’m 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, I’m 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", + "We’ll 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", + "Let’s 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", + "Let’s increase Pell Grants and increase our historic support of HBCUs, and invest in what Jill—our First Lady who teaches full-time—calls America’s 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 you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n", + "\n", + "Tonight, I’d 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 nation’s top legal minds, who will continue Justice Breyer’s 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 you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n", + "\n", + "Tonight, I’d 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 nation’s top legal minds, who will continue Justice Breyer’s 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 she’s been nominated, she’s 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, we’ve installed new technology like cutting-edge scanners to better detect drug smuggling. \n", + "\n", + "We’ve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \n", + "\n", + "We’re putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \n", + "\n", + "We’re 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, let’s 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 isn’t 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, we’ll 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 I’m 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 isn’t 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 she’s been nominated, she’s 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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/custom_chat_model.ipynb b/docs/versioned_docs/version-0.2.x/how_to/custom_chat_model.ipynb new file mode 100644 index 0000000000000..d3b3759322b1b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/custom_chat_model.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/custom_llm.ipynb b/docs/versioned_docs/version-0.2.x/how_to/custom_llm.ipynb new file mode 100644 index 0000000000000..c9797c4423c13 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/custom_llm.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/custom_retriever.ipynb b/docs/versioned_docs/version-0.2.x/how_to/custom_retriever.ipynb new file mode 100644 index 0000000000000..c97795a721c16 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/custom_retriever.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/custom_tools.ipynb b/docs/versioned_docs/version-0.2.x/how_to/custom_tools.ipynb new file mode 100644 index 0000000000000..3d724c3723520 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/custom_tools.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/debugging.ipynb b/docs/versioned_docs/version-0.2.x/how_to/debugging.ipynb new file mode 100644 index 0000000000000..9fa56f530f6fb --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/debugging.ipynb @@ -0,0 +1,548 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to debug your LLM apps\n", + "\n", + "Like building any type of software, at some point you'll need to debug when building with LLMs. A model call will fail, or model output will be misformatted, or there will be some nested model calls and it won't be clear where along the way an incorrect output was created.\n", + "\n", + "There are three main methods for debugging:\n", + "\n", + "- Verbose Mode: This adds print statements for \"important\" events in your chain.\n", + "- Debug Mode: This add logging statements for ALL events in your chain.\n", + "- LangSmith Tracing: This logs events to [LangSmith](/docs/langsmith/) to allow for visualization there.\n", + "\n", + "| | Verbose Mode | Debug Mode | LangSmith Tracing |\n", + "|------------------------|--------------|------------|-------------------|\n", + "| Free | ✅ | ✅ | ✅ |\n", + "| UI | ❌ | ❌ | ✅ |\n", + "| Persisted | ❌ | ❌ | ✅ |\n", + "| See all events | ❌ | ✅ | ✅ |\n", + "| See \"important\" events | ✅ | ❌ | ✅ |\n", + "| Runs Locally | ✅ | ✅ | ❌ |\n", + "\n", + "\n", + "## Tracing\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", + "\n", + "Let's suppose we have an agent, and want to visualize the actions it takes and tool outputs it receives. Without any debugging, here's what we see:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-4-turbo\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'input': 'Who directed the 2023 film Oppenheimer and what is their age in days?',\n", + " 'output': 'The 2023 film \"Oppenheimer\" was directed by Christopher Nolan.\\n\\nTo calculate Christopher Nolan\\'s age in days, we first need his birthdate, which is July 30, 1970. Let\\'s calculate his age in days from his birthdate to today\\'s date, December 7, 2023.\\n\\n1. Calculate the total number of days from July 30, 1970, to December 7, 2023.\\n2. Nolan was born on July 30, 1970. From July 30, 1970, to July 30, 2023, is 53 years.\\n3. From July 30, 2023, to December 7, 2023, is 130 days.\\n\\nNow, calculate the total days:\\n- 53 years = 53 x 365 = 19,345 days\\n- Adding leap years from 1970 to 2023: There are 13 leap years (1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020). So, add 13 days.\\n- Total days from years and leap years = 19,345 + 13 = 19,358 days\\n- Add the days from July 30, 2023, to December 7, 2023 = 130 days\\n\\nTotal age in days = 19,358 + 130 = 19,488 days\\n\\nChristopher Nolan is 19,488 days old as of December 7, 2023.'}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.agents import AgentExecutor, create_tool_calling_agent\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "tools = [TavilySearchResults(max_results=1)]\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You are a helpful assistant.\",\n", + " ),\n", + " (\"placeholder\", \"{chat_history}\"),\n", + " (\"human\", \"{input}\"),\n", + " (\"placeholder\", \"{agent_scratchpad}\"),\n", + " ]\n", + ")\n", + "\n", + "# Construct the Tools agent\n", + "agent = create_tool_calling_agent(llm, tools, prompt)\n", + "\n", + "# Create an agent executor by passing in the agent and tools\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools)\n", + "agent_executor.invoke(\n", + " {\"input\": \"Who directed the 2023 film Oppenheimer and what is their age in days?\"}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We don't get much output, but since we set up LangSmith we can easily see what happened under the hood:\n", + "\n", + "https://smith.langchain.com/public/a89ff88f-9ddc-4757-a395-3a1b365655bf/r" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `set_debug` and `set_verbose`\n", + "\n", + "If you're prototyping in Jupyter Notebooks or running Python scripts, it can be helpful to print out the intermediate steps of a chain run.\n", + "\n", + "There are a number of ways to enable printing at varying degrees of verbosity.\n", + "\n", + "Note: These still work even with LangSmith enabled, so you can have both turned on and running at the same time\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `set_verbose(True)`\n", + "\n", + "Setting the `verbose` flag will print out inputs and outputs in a slightly more readable format and will skip logging certain raw outputs (like the token usage stats for an LLM call) so that you can focus on application logic." + ] + }, + { + "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': 'director of the 2023 film Oppenheimer'}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[{'url': 'https://m.imdb.com/title/tt15398776/', 'content': 'Oppenheimer: Directed by Christopher Nolan. With Cillian Murphy, Emily Blunt, Robert Downey Jr., Alden Ehrenreich. The story of American scientist J. Robert Oppenheimer and his role in the development of the atomic bomb.'}]\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `tavily_search_results_json` with `{'query': 'birth date of Christopher Nolan'}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[{'url': 'https://m.imdb.com/name/nm0634240/bio/', 'content': 'Christopher Nolan. Writer: Tenet. Best known for his cerebral, often nonlinear, storytelling, acclaimed Academy Award winner writer/director/producer Sir Christopher Nolan CBE was born in London, England. Over the course of more than 25 years of filmmaking, Nolan has gone from low-budget independent films to working on some of the biggest blockbusters ever made and became one of the most ...'}]\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `tavily_search_results_json` with `{'query': 'Christopher Nolan birth date'}`\n", + "responded: The 2023 film **Oppenheimer** was directed by **Christopher Nolan**.\n", + "\n", + "To calculate Christopher Nolan's age in days, I need his exact birth date. Let me find that information for you.\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[{'url': 'https://m.imdb.com/name/nm0634240/bio/', 'content': 'Christopher Nolan. Writer: Tenet. Best known for his cerebral, often nonlinear, storytelling, acclaimed Academy Award winner writer/director/producer Sir Christopher Nolan CBE was born in London, England. Over the course of more than 25 years of filmmaking, Nolan has gone from low-budget independent films to working on some of the biggest blockbusters ever made and became one of the most ...'}]\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `tavily_search_results_json` with `{'query': 'Christopher Nolan date of birth'}`\n", + "responded: It appears that I need to refine my search to get the exact birth date of Christopher Nolan. Let me try again to find that specific information.\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[{'url': 'https://m.imdb.com/name/nm0634240/bio/', 'content': 'Christopher Nolan. Writer: Tenet. Best known for his cerebral, often nonlinear, storytelling, acclaimed Academy Award winner writer/director/producer Sir Christopher Nolan CBE was born in London, England. Over the course of more than 25 years of filmmaking, Nolan has gone from low-budget independent films to working on some of the biggest blockbusters ever made and became one of the most ...'}]\u001b[0m\u001b[32;1m\u001b[1;3mI am currently unable to retrieve the exact birth date of Christopher Nolan from the sources available. However, it is widely known that he was born on July 30, 1970. Using this date, I can calculate his age in days as of today.\n", + "\n", + "Let's calculate:\n", + "\n", + "- Christopher Nolan's birth date: July 30, 1970.\n", + "- Today's date: December 7, 2023.\n", + "\n", + "The number of days between these two dates can be calculated as follows:\n", + "\n", + "1. From July 30, 1970, to July 30, 2023, is 53 years.\n", + "2. From July 30, 2023, to December 7, 2023, is 130 days.\n", + "\n", + "Calculating the total days for 53 years (considering leap years):\n", + "- 53 years × 365 days/year = 19,345 days\n", + "- Adding leap years (1972, 1976, ..., 2020, 2024 - 13 leap years): 13 days\n", + "\n", + "Total days from birth until July 30, 2023: 19,345 + 13 = 19,358 days\n", + "Adding the days from July 30, 2023, to December 7, 2023: 130 days\n", + "\n", + "Total age in days as of December 7, 2023: 19,358 + 130 = 19,488 days.\n", + "\n", + "Therefore, Christopher Nolan is 19,488 days old as of December 7, 2023.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'Who directed the 2023 film Oppenheimer and what is their age in days?',\n", + " 'output': \"I am currently unable to retrieve the exact birth date of Christopher Nolan from the sources available. However, it is widely known that he was born on July 30, 1970. Using this date, I can calculate his age in days as of today.\\n\\nLet's calculate:\\n\\n- Christopher Nolan's birth date: July 30, 1970.\\n- Today's date: December 7, 2023.\\n\\nThe number of days between these two dates can be calculated as follows:\\n\\n1. From July 30, 1970, to July 30, 2023, is 53 years.\\n2. From July 30, 2023, to December 7, 2023, is 130 days.\\n\\nCalculating the total days for 53 years (considering leap years):\\n- 53 years × 365 days/year = 19,345 days\\n- Adding leap years (1972, 1976, ..., 2020, 2024 - 13 leap years): 13 days\\n\\nTotal days from birth until July 30, 2023: 19,345 + 13 = 19,358 days\\nAdding the days from July 30, 2023, to December 7, 2023: 130 days\\n\\nTotal age in days as of December 7, 2023: 19,358 + 130 = 19,488 days.\\n\\nTherefore, Christopher Nolan is 19,488 days old as of December 7, 2023.\"}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.globals import set_verbose\n", + "\n", + "set_verbose(True)\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools)\n", + "agent_executor.invoke(\n", + " {\"input\": \"Who directed the 2023 film Oppenheimer and what is their age in days?\"}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `set_debug(True)`\n", + "\n", + "Setting the global `debug` flag will cause all LangChain components with callback support (chains, models, agents, tools, retrievers) to print the inputs they receive and outputs they generate. This is the most verbose setting and will fully log raw inputs and outputs." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"Who directed the 2023 film Oppenheimer and what is their age in days?\"\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"\"\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 3:chain:RunnableAssign] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"\"\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 3:chain:RunnableAssign > 4:chain:RunnableParallel] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"\"\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 3:chain:RunnableAssign > 4:chain:RunnableParallel > 5:chain:RunnableLambda] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"\"\n", + "}\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 3:chain:RunnableAssign > 4:chain:RunnableParallel > 5:chain:RunnableLambda] [1ms] Exiting Chain run with output:\n", + "\u001b[0m{\n", + " \"output\": []\n", + "}\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 3:chain:RunnableAssign > 4:chain:RunnableParallel] [2ms] Exiting Chain run with output:\n", + "\u001b[0m{\n", + " \"agent_scratchpad\": []\n", + "}\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 3:chain:RunnableAssign] [5ms] Exiting Chain run with output:\n", + "\u001b[0m{\n", + " \"input\": \"Who directed the 2023 film Oppenheimer and what is their age in days?\",\n", + " \"intermediate_steps\": [],\n", + " \"agent_scratchpad\": []\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 6:prompt:ChatPromptTemplate] Entering Prompt run with input:\n", + "\u001b[0m{\n", + " \"input\": \"Who directed the 2023 film Oppenheimer and what is their age in days?\",\n", + " \"intermediate_steps\": [],\n", + " \"agent_scratchpad\": []\n", + "}\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 6:prompt:ChatPromptTemplate] [1ms] Exiting Prompt run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[32;1m\u001b[1;3m[llm/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 7:llm:ChatOpenAI] Entering LLM run with input:\n", + "\u001b[0m{\n", + " \"prompts\": [\n", + " \"System: You are a helpful assistant.\\nHuman: Who directed the 2023 film Oppenheimer and what is their age in days?\"\n", + " ]\n", + "}\n", + "\u001b[36;1m\u001b[1;3m[llm/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 7:llm:ChatOpenAI] [3.17s] Exiting LLM run with output:\n", + "\u001b[0m{\n", + " \"generations\": [\n", + " [\n", + " {\n", + " \"text\": \"\",\n", + " \"generation_info\": {\n", + " \"finish_reason\": \"tool_calls\"\n", + " },\n", + " \"type\": \"ChatGenerationChunk\",\n", + " \"message\": {\n", + " \"lc\": 1,\n", + " \"type\": \"constructor\",\n", + " \"id\": [\n", + " \"langchain\",\n", + " \"schema\",\n", + " \"messages\",\n", + " \"AIMessageChunk\"\n", + " ],\n", + " \"kwargs\": {\n", + " \"content\": \"\",\n", + " \"example\": false,\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"index\": 0,\n", + " \"id\": \"call_fnfq6GjSQED4iF6lo4rxkUup\",\n", + " \"function\": {\n", + " \"arguments\": \"{\\\"query\\\": \\\"director of the 2023 film Oppenheimer\\\"}\",\n", + " \"name\": \"tavily_search_results_json\"\n", + " },\n", + " \"type\": \"function\"\n", + " },\n", + " {\n", + " \"index\": 1,\n", + " \"id\": \"call_mwhVi6pk49f4OIo5rOWrr4TD\",\n", + " \"function\": {\n", + " \"arguments\": \"{\\\"query\\\": \\\"birth date of Christopher Nolan\\\"}\",\n", + " \"name\": \"tavily_search_results_json\"\n", + " },\n", + " \"type\": \"function\"\n", + " }\n", + " ]\n", + " },\n", + " \"tool_call_chunks\": [\n", + " {\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"args\": \"{\\\"query\\\": \\\"director of the 2023 film Oppenheimer\\\"}\",\n", + " \"id\": \"call_fnfq6GjSQED4iF6lo4rxkUup\",\n", + " \"index\": 0\n", + " },\n", + " {\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"args\": \"{\\\"query\\\": \\\"birth date of Christopher Nolan\\\"}\",\n", + " \"id\": \"call_mwhVi6pk49f4OIo5rOWrr4TD\",\n", + " \"index\": 1\n", + " }\n", + " ],\n", + " \"response_metadata\": {\n", + " \"finish_reason\": \"tool_calls\"\n", + " },\n", + " \"id\": \"run-6e160323-15f9-491d-aadf-b5d337e9e2a1\",\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"args\": {\n", + " \"query\": \"director of the 2023 film Oppenheimer\"\n", + " },\n", + " \"id\": \"call_fnfq6GjSQED4iF6lo4rxkUup\"\n", + " },\n", + " {\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"args\": {\n", + " \"query\": \"birth date of Christopher Nolan\"\n", + " },\n", + " \"id\": \"call_mwhVi6pk49f4OIo5rOWrr4TD\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": []\n", + " }\n", + " }\n", + " }\n", + " ]\n", + " ],\n", + " \"llm_output\": null,\n", + " \"run\": null\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 8:parser:ToolsAgentOutputParser] Entering Parser run with input:\n", + "\u001b[0m[inputs]\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence > 8:parser:ToolsAgentOutputParser] [1ms] Exiting Parser run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 2:chain:RunnableSequence] [3.18s] Exiting Chain run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[32;1m\u001b[1;3m[tool/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 9:tool:tavily_search_results_json] Entering Tool run with input:\n", + "\u001b[0m\"{'query': 'director of the 2023 film Oppenheimer'}\"\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Error in ConsoleCallbackHandler.on_tool_end callback: AttributeError(\"'list' object has no attribute 'strip'\")\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\u001b[1;3m[tool/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 10:tool:tavily_search_results_json] Entering Tool run with input:\n", + "\u001b[0m\"{'query': 'birth date of Christopher Nolan'}\"\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Error in ConsoleCallbackHandler.on_tool_end callback: AttributeError(\"'list' object has no attribute 'strip'\")\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"\"\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 12:chain:RunnableAssign] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"\"\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 12:chain:RunnableAssign > 13:chain:RunnableParallel] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"\"\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 12:chain:RunnableAssign > 13:chain:RunnableParallel > 14:chain:RunnableLambda] Entering Chain run with input:\n", + "\u001b[0m{\n", + " \"input\": \"\"\n", + "}\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 12:chain:RunnableAssign > 13:chain:RunnableParallel > 14:chain:RunnableLambda] [1ms] Exiting Chain run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 12:chain:RunnableAssign > 13:chain:RunnableParallel] [4ms] Exiting Chain run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 12:chain:RunnableAssign] [8ms] Exiting Chain run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 15:prompt:ChatPromptTemplate] Entering Prompt run with input:\n", + "\u001b[0m[inputs]\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 15:prompt:ChatPromptTemplate] [1ms] Exiting Prompt run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[32;1m\u001b[1;3m[llm/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 16:llm:ChatOpenAI] Entering LLM run with input:\n", + "\u001b[0m{\n", + " \"prompts\": [\n", + " \"System: You are a helpful assistant.\\nHuman: Who directed the 2023 film Oppenheimer and what is their age in days?\\nAI: \\nTool: [{\\\"url\\\": \\\"https://m.imdb.com/title/tt15398776/fullcredits/\\\", \\\"content\\\": \\\"Oppenheimer (2023) cast and crew credits, including actors, actresses, directors, writers and more. Menu. ... director of photography: behind-the-scenes Jason Gary ... best boy grip ... film loader Luc Poullain ... aerial coordinator\\\"}]\\nTool: [{\\\"url\\\": \\\"https://en.wikipedia.org/wiki/Christopher_Nolan\\\", \\\"content\\\": \\\"In early 2003, Nolan approached Warner Bros. with the idea of making a new Batman film, based on the character's origin story.[58] Nolan was fascinated by the notion of grounding it in a more realistic world than a comic-book fantasy.[59] He relied heavily on traditional stunts and miniature effects during filming, with minimal use of computer-generated imagery (CGI).[60] Batman Begins (2005), the biggest project Nolan had undertaken to that point,[61] was released to critical acclaim and commercial success.[62][63] Starring Christian Bale as Bruce Wayne / Batman—along with Michael Caine, Gary Oldman, Morgan Freeman and Liam Neeson—Batman Begins revived the franchise.[64][65] Batman Begins was 2005's ninth-highest-grossing film and was praised for its psychological depth and contemporary relevance;[63][66] it is cited as one of the most influential films of the 2000s.[67] Film author Ian Nathan wrote that within five years of his career, Nolan \\\\\\\"[went] from unknown to indie darling to gaining creative control over one of the biggest properties in Hollywood, and (perhaps unwittingly) fomenting the genre that would redefine the entire industry\\\\\\\".[68]\\\\nNolan directed, co-wrote and produced The Prestige (2006), an adaptation of the Christopher Priest novel about two rival 19th-century magicians.[69] He directed, wrote and edited the short film Larceny (1996),[19] which was filmed over a weekend in black and white with limited equipment and a small cast and crew.[12][20] Funded by Nolan and shot with the UCL Union Film society's equipment, it appeared at the Cambridge Film Festival in 1996 and is considered one of UCL's best shorts.[21] For unknown reasons, the film has since been removed from public view.[19] Nolan filmed a third short, Doodlebug (1997), about a man seemingly chasing an insect with his shoe, only to discover that it is a miniature of himself.[14][22] Nolan and Thomas first attempted to make a feature in the mid-1990s with Larry Mahoney, which they scrapped.[23] During this period in his career, Nolan had little to no success getting his projects off the ground, facing several rejections; he added, \\\\\\\"[T]here's a very limited pool of finance in the UK. Philosophy professor David Kyle Johnson wrote that \\\\\\\"Inception became a classic almost as soon as it was projected on silver screens\\\\\\\", praising its exploration of philosophical ideas, including leap of faith and allegory of the cave.[97] The film grossed over $836 million worldwide.[98] Nominated for eight Academy Awards—including Best Picture and Best Original Screenplay—it won Best Cinematography, Best Sound Mixing, Best Sound Editing and Best Visual Effects.[99] Nolan was nominated for a BAFTA Award and a Golden Globe Award for Best Director, among other accolades.[40]\\\\nAround the release of The Dark Knight Rises (2012), Nolan's third and final Batman film, Joseph Bevan of the British Film Institute wrote a profile on him: \\\\\\\"In the space of just over a decade, Christopher Nolan has shot from promising British indie director to undisputed master of a new brand of intelligent escapism. He further wrote that Nolan's body of work reflect \\\\\\\"a heterogeneity of conditions of products\\\\\\\" extending from low-budget films to lucrative blockbusters, \\\\\\\"a wide range of genres and settings\\\\\\\" and \\\\\\\"a diversity of styles that trumpet his versatility\\\\\\\".[193]\\\\nDavid Bordwell, a film theorist, wrote that Nolan has been able to blend his \\\\\\\"experimental impulses\\\\\\\" with the demands of mainstream entertainment, describing his oeuvre as \\\\\\\"experiments with cinematic time by means of techniques of subjective viewpoint and crosscutting\\\\\\\".[194] Nolan's use of practical, in-camera effects, miniatures and models, as well as shooting on celluloid film, has been highly influential in early 21st century cinema.[195][196] IndieWire wrote in 2019 that, Nolan \\\\\\\"kept a viable alternate model of big-budget filmmaking alive\\\\\\\", in an era where blockbuster filmmaking has become \\\\\\\"a largely computer-generated art form\\\\\\\".[196] Initially reluctant to make a sequel, he agreed after Warner Bros. repeatedly insisted.[78] Nolan wanted to expand on the noir quality of the first film by broadening the canvas and taking on \\\\\\\"the dynamic of a story of the city, a large crime story ... where you're looking at the police, the justice system, the vigilante, the poor people, the rich people, the criminals\\\\\\\".[79] Continuing to minimalise the use of CGI, Nolan employed high-resolution IMAX cameras, making it the first major motion picture to use this technology.[80][81]\\\"}]\"\n", + " ]\n", + "}\n", + "\u001b[36;1m\u001b[1;3m[llm/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 16:llm:ChatOpenAI] [20.22s] Exiting LLM run with output:\n", + "\u001b[0m{\n", + " \"generations\": [\n", + " [\n", + " {\n", + " \"text\": \"The 2023 film \\\"Oppenheimer\\\" was directed by Christopher Nolan.\\n\\nTo calculate Christopher Nolan's age in days, we first need his birth date, which is July 30, 1970. Let's calculate his age in days from his birth date to today's date, December 7, 2023.\\n\\n1. Calculate the total number of days from July 30, 1970, to December 7, 2023.\\n2. Christopher Nolan was born on July 30, 1970. From July 30, 1970, to July 30, 2023, is 53 years.\\n3. From July 30, 2023, to December 7, 2023, is 130 days.\\n\\nNow, calculate the total days for 53 years:\\n- Each year has 365 days, so 53 years × 365 days/year = 19,345 days.\\n- Adding the leap years from 1970 to 2023: 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, and 2024 (up to February). This gives us 14 leap years.\\n- Total days from leap years: 14 days.\\n\\nAdding all together:\\n- Total days = 19,345 days (from years) + 14 days (from leap years) + 130 days (from July 30, 2023, to December 7, 2023) = 19,489 days.\\n\\nTherefore, as of December 7, 2023, Christopher Nolan is 19,489 days old.\",\n", + " \"generation_info\": {\n", + " \"finish_reason\": \"stop\"\n", + " },\n", + " \"type\": \"ChatGenerationChunk\",\n", + " \"message\": {\n", + " \"lc\": 1,\n", + " \"type\": \"constructor\",\n", + " \"id\": [\n", + " \"langchain\",\n", + " \"schema\",\n", + " \"messages\",\n", + " \"AIMessageChunk\"\n", + " ],\n", + " \"kwargs\": {\n", + " \"content\": \"The 2023 film \\\"Oppenheimer\\\" was directed by Christopher Nolan.\\n\\nTo calculate Christopher Nolan's age in days, we first need his birth date, which is July 30, 1970. Let's calculate his age in days from his birth date to today's date, December 7, 2023.\\n\\n1. Calculate the total number of days from July 30, 1970, to December 7, 2023.\\n2. Christopher Nolan was born on July 30, 1970. From July 30, 1970, to July 30, 2023, is 53 years.\\n3. From July 30, 2023, to December 7, 2023, is 130 days.\\n\\nNow, calculate the total days for 53 years:\\n- Each year has 365 days, so 53 years × 365 days/year = 19,345 days.\\n- Adding the leap years from 1970 to 2023: 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, and 2024 (up to February). This gives us 14 leap years.\\n- Total days from leap years: 14 days.\\n\\nAdding all together:\\n- Total days = 19,345 days (from years) + 14 days (from leap years) + 130 days (from July 30, 2023, to December 7, 2023) = 19,489 days.\\n\\nTherefore, as of December 7, 2023, Christopher Nolan is 19,489 days old.\",\n", + " \"example\": false,\n", + " \"additional_kwargs\": {},\n", + " \"tool_call_chunks\": [],\n", + " \"response_metadata\": {\n", + " \"finish_reason\": \"stop\"\n", + " },\n", + " \"id\": \"run-1c08a44f-db70-4836-935b-417caaf422a5\",\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": []\n", + " }\n", + " }\n", + " }\n", + " ]\n", + " ],\n", + " \"llm_output\": null,\n", + " \"run\": null\n", + "}\n", + "\u001b[32;1m\u001b[1;3m[chain/start]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 17:parser:ToolsAgentOutputParser] Entering Parser run with input:\n", + "\u001b[0m[inputs]\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence > 17:parser:ToolsAgentOutputParser] [2ms] Exiting Parser run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor > 11:chain:RunnableSequence] [20.27s] Exiting Chain run with output:\n", + "\u001b[0m[outputs]\n", + "\u001b[36;1m\u001b[1;3m[chain/end]\u001b[0m \u001b[1m[1:chain:AgentExecutor] [26.37s] Exiting Chain run with output:\n", + "\u001b[0m{\n", + " \"output\": \"The 2023 film \\\"Oppenheimer\\\" was directed by Christopher Nolan.\\n\\nTo calculate Christopher Nolan's age in days, we first need his birth date, which is July 30, 1970. Let's calculate his age in days from his birth date to today's date, December 7, 2023.\\n\\n1. Calculate the total number of days from July 30, 1970, to December 7, 2023.\\n2. Christopher Nolan was born on July 30, 1970. From July 30, 1970, to July 30, 2023, is 53 years.\\n3. From July 30, 2023, to December 7, 2023, is 130 days.\\n\\nNow, calculate the total days for 53 years:\\n- Each year has 365 days, so 53 years × 365 days/year = 19,345 days.\\n- Adding the leap years from 1970 to 2023: 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, and 2024 (up to February). This gives us 14 leap years.\\n- Total days from leap years: 14 days.\\n\\nAdding all together:\\n- Total days = 19,345 days (from years) + 14 days (from leap years) + 130 days (from July 30, 2023, to December 7, 2023) = 19,489 days.\\n\\nTherefore, as of December 7, 2023, Christopher Nolan is 19,489 days old.\"\n", + "}\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'Who directed the 2023 film Oppenheimer and what is their age in days?',\n", + " 'output': 'The 2023 film \"Oppenheimer\" was directed by Christopher Nolan.\\n\\nTo calculate Christopher Nolan\\'s age in days, we first need his birth date, which is July 30, 1970. Let\\'s calculate his age in days from his birth date to today\\'s date, December 7, 2023.\\n\\n1. Calculate the total number of days from July 30, 1970, to December 7, 2023.\\n2. Christopher Nolan was born on July 30, 1970. From July 30, 1970, to July 30, 2023, is 53 years.\\n3. From July 30, 2023, to December 7, 2023, is 130 days.\\n\\nNow, calculate the total days for 53 years:\\n- Each year has 365 days, so 53 years × 365 days/year = 19,345 days.\\n- Adding the leap years from 1970 to 2023: 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, and 2024 (up to February). This gives us 14 leap years.\\n- Total days from leap years: 14 days.\\n\\nAdding all together:\\n- Total days = 19,345 days (from years) + 14 days (from leap years) + 130 days (from July 30, 2023, to December 7, 2023) = 19,489 days.\\n\\nTherefore, as of December 7, 2023, Christopher Nolan is 19,489 days old.'}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.globals import set_debug\n", + "\n", + "set_debug(True)\n", + "set_verbose(False)\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools)\n", + "\n", + "agent_executor.invoke(\n", + " {\"input\": \"Who directed the 2023 film Oppenheimer and what is their age in days?\"}\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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/document_loader_csv.ipynb b/docs/versioned_docs/version-0.2.x/how_to/document_loader_csv.ipynb new file mode 100644 index 0000000000000..1de70b6aafe95 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/document_loader_csv.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/document_loader_custom.ipynb b/docs/versioned_docs/version-0.2.x/how_to/document_loader_custom.ipynb new file mode 100644 index 0000000000000..169787580bb3a --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/document_loader_custom.ipynb @@ -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", + "\n", + "page_content='meow meow🐱 \\n' metadata={'line_number': 0, 'source': './meow.txt'}\n", + "\n", + "\n", + "page_content=' meow meow🐱 \\n' metadata={'line_number': 1, 'source': './meow.txt'}\n", + "\n", + "\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", + "\n", + "page_content='meow meow🐱 \\n' metadata={'line_number': 0, 'source': './meow.txt'}\n", + "\n", + "\n", + "page_content=' meow meow🐱 \\n' metadata={'line_number': 1, 'source': './meow.txt'}\n", + "\n", + "\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": [ + "" + ] + }, + "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[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[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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/document_loader_directory.ipynb b/docs/versioned_docs/version-0.2.x/how_to/document_loader_directory.ipynb new file mode 100644 index 0000000000000..27ce35a640fee --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/document_loader_directory.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/document_loader_html.ipynb b/docs/versioned_docs/version-0.2.x/how_to/document_loader_html.ipynb new file mode 100644 index 0000000000000..5fd26865009ff --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/document_loader_html.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/document_loader_json.mdx b/docs/versioned_docs/version-0.2.x/how_to/document_loader_json.mdx new file mode 100644 index 0000000000000..101e1891eef4a --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/document_loader_json.mdx @@ -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 attribute–value 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) +``` + + + +``` + {'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'} +``` + + + + +## 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) +``` + + + +``` + [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})] +``` + + + + +### 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()) +``` + + + +``` + ('{"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') +``` + + + + +```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) +``` + + + +``` + [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})] +``` + + + + +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) +``` + + + +``` + [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})] +``` + + + +### 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()) +``` + + + +```json + {"data": [ + {"attributes": { + "message": "message1", + "tags": [ + "tag1"]}, + "id": "1"}, + {"attributes": { + "message": "message2", + "tags": [ + "tag2"]}, + "id": "2"}]} +``` + + + + +```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) +``` + + + +``` + [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})] +``` + + + +## 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) +``` + + + +``` + [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})] +``` + + + +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) +``` + + + +``` + [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})] +``` + + + +## 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 -> ".[]" +``` diff --git a/docs/versioned_docs/version-0.2.x/how_to/document_loader_markdown.ipynb b/docs/versioned_docs/version-0.2.x/how_to/document_loader_markdown.ipynb new file mode 100644 index 0000000000000..ac5688c97ccf8 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/document_loader_markdown.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/document_loader_office_file.mdx b/docs/versioned_docs/version-0.2.x/how_to/document_loader_office_file.mdx new file mode 100644 index 0000000000000..f29d495366b78 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/document_loader_office_file.mdx @@ -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 `` and `` 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 = "" +endpoint = "" +key = "" +loader = AzureAIDocumentIntelligenceLoader( + api_endpoint=endpoint, api_key=key, file_path=file_path, api_model="prebuilt-layout" +) + +documents = loader.load() +``` diff --git a/docs/versioned_docs/version-0.2.x/how_to/document_loader_pdf.ipynb b/docs/versioned_docs/version-0.2.x/how_to/document_loader_pdf.ipynb new file mode 100644 index 0000000000000..8ca24a5ea1131 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/document_loader_pdf.ipynb @@ -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:/// .'" + ] + }, + "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 `` and `` 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 = \"\"\n", + "endpoint = \"\"\n", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/embed_text.mdx b/docs/versioned_docs/version-0.2.x/how_to/embed_text.mdx new file mode 100644 index 0000000000000..1be7b054d1eb2 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/embed_text.mdx @@ -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'; + + + +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() +``` + + + + +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() +``` + + + + +### `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]) +``` + + + +``` +(5, 1536) +``` + + + +### `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] +``` + + + +``` +[0.0053587136790156364, + -0.0004999046213924885, + 0.038883671164512634, + -0.003001077566295862, + -0.00900818221271038] +``` + + diff --git a/docs/versioned_docs/version-0.2.x/how_to/ensemble_retriever.ipynb b/docs/versioned_docs/version-0.2.x/how_to/ensemble_retriever.ipynb new file mode 100644 index 0000000000000..015c3146e579f --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/ensemble_retriever.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/example_selectors.ipynb b/docs/versioned_docs/version-0.2.x/how_to/example_selectors.ipynb new file mode 100644 index 0000000000000..170ac2b54f7cd --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/example_selectors.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/example_selectors_length_based.ipynb b/docs/versioned_docs/version-0.2.x/how_to/example_selectors_length_based.ipynb new file mode 100644 index 0000000000000..420f581833144 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/example_selectors_length_based.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/example_selectors_mmr.ipynb b/docs/versioned_docs/version-0.2.x/how_to/example_selectors_mmr.ipynb new file mode 100644 index 0000000000000..702623241ca0b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/example_selectors_mmr.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/example_selectors_ngram.ipynb b/docs/versioned_docs/version-0.2.x/how_to/example_selectors_ngram.ipynb new file mode 100644 index 0000000000000..e50e5254faedc --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/example_selectors_ngram.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/example_selectors_similarity.ipynb b/docs/versioned_docs/version-0.2.x/how_to/example_selectors_similarity.ipynb new file mode 100644 index 0000000000000..701f9bfc97491 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/example_selectors_similarity.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/extraction_examples.ipynb b/docs/versioned_docs/version-0.2.x/how_to/extraction_examples.ipynb new file mode 100644 index 0000000000000..56deda9c6355b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/extraction_examples.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/extraction_long_text.ipynb b/docs/versioned_docs/version-0.2.x/how_to/extraction_long_text.ipynb new file mode 100644 index 0000000000000..b224253ae7e6e --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/extraction_long_text.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/extraction_parse.ipynb b/docs/versioned_docs/version-0.2.x/how_to/extraction_parse.ipynb new file mode 100644 index 0000000000000..7fdc5e6120e63 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/extraction_parse.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/fallbacks.ipynb b/docs/versioned_docs/version-0.2.x/how_to/fallbacks.ipynb new file mode 100644 index 0000000000000..0c29961c6ed6e --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/fallbacks.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/few_shot_examples.ipynb b/docs/versioned_docs/version-0.2.x/how_to/few_shot_examples.ipynb new file mode 100644 index 0000000000000..2dd0ac0e9b2a2 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/few_shot_examples.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/few_shot_examples_chat.ipynb b/docs/versioned_docs/version-0.2.x/how_to/few_shot_examples_chat.ipynb new file mode 100644 index 0000000000000..393470a818c4f --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/few_shot_examples_chat.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/function_calling.ipynb b/docs/versioned_docs/version-0.2.x/how_to/function_calling.ipynb new file mode 100644 index 0000000000000..f93942bf991ef --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/function_calling.ipynb @@ -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\": \"\\nI should use a tool.\\n\",\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", + "\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": [ + "\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": [ + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/functions.ipynb b/docs/versioned_docs/version-0.2.x/how_to/functions.ipynb new file mode 100644 index 0000000000000..2c1a2681a0700 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/functions.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/graph_constructing.ipynb b/docs/versioned_docs/version-0.2.x/how_to/graph_constructing.ipynb new file mode 100644 index 0000000000000..b48f32a4b613f --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/graph_constructing.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/graph_mapping.ipynb b/docs/versioned_docs/version-0.2.x/how_to/graph_mapping.ipynb new file mode 100644 index 0000000000000..5d0c3d2629c1f --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/graph_mapping.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/graph_prompting.ipynb b/docs/versioned_docs/version-0.2.x/how_to/graph_prompting.ipynb new file mode 100644 index 0000000000000..eba7cc4f18c7e --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/graph_prompting.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/graph_semantic.ipynb b/docs/versioned_docs/version-0.2.x/how_to/graph_semantic.ipynb new file mode 100644 index 0000000000000..28b1562184521 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/graph_semantic.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/index.mdx b/docs/versioned_docs/version-0.2.x/how_to/index.mdx new file mode 100644 index 0000000000000..c8390ab2938eb --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/index.mdx @@ -0,0 +1,253 @@ +--- +sidebar_position: 0 +sidebar_class_name: hidden +--- + +# How-to Guides + +Here you’ll find short answers to “How do I….?” types of questions. +These how-to guides don’t cover topics in depth – you’ll 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) diff --git a/docs/versioned_docs/version-0.2.x/how_to/indexing.ipynb b/docs/versioned_docs/version-0.2.x/how_to/indexing.ipynb new file mode 100644 index 0000000000000..1a17d51645187 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/indexing.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/inspect.ipynb b/docs/versioned_docs/version-0.2.x/how_to/inspect.ipynb new file mode 100644 index 0000000000000..91916292fe39c --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/inspect.ipynb @@ -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", + "\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", + " | ParallelInput | \n", + " +---------------------------------+ \n", + " ** ** \n", + " *** *** \n", + " ** ** \n", + "+----------------------+ +-------------+ \n", + "| VectorStoreRetriever | | Passthrough | \n", + "+----------------------+ +-------------+ \n", + " ** ** \n", + " *** *** \n", + " ** ** \n", + " +----------------------------------+ \n", + " | ParallelOutput | \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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/llm_caching.ipynb b/docs/versioned_docs/version-0.2.x/how_to/llm_caching.ipynb new file mode 100644 index 0000000000000..074d1ee2d04fd --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/llm_caching.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/llm_token_usage_tracking.ipynb b/docs/versioned_docs/version-0.2.x/how_to/llm_token_usage_tracking.ipynb new file mode 100644 index 0000000000000..69ab3f26d05c5 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/llm_token_usage_tracking.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/logprobs.ipynb b/docs/versioned_docs/version-0.2.x/how_to/logprobs.ipynb new file mode 100644 index 0000000000000..a62b5df57dcfc --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/logprobs.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/long_context_reorder.ipynb b/docs/versioned_docs/version-0.2.x/how_to/long_context_reorder.ipynb new file mode 100644 index 0000000000000..4658bc246db6b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/long_context_reorder.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/markdown_header_metadata_splitter.ipynb b/docs/versioned_docs/version-0.2.x/how_to/markdown_header_metadata_splitter.ipynb new file mode 100644 index 0000000000000..24ae5e421f6d1 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/markdown_header_metadata_splitter.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/message_history.ipynb b/docs/versioned_docs/version-0.2.x/how_to/message_history.ipynb new file mode 100644 index 0000000000000..6169d8eb9e346 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/message_history.ipynb @@ -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", + "\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", + "\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=, _async_client=), config_factories=[. at 0x1473dd000>]), config={'run_name': 'RunnableWithMessageHistory'}), get_session_history=, history_factory_config=[ConfigurableFieldSpec(id='session_id', annotation=, 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=, _async_client=), config_factories=[. at 0x1473df6d0>]), config={'run_name': 'RunnableWithMessageHistory'}), get_session_history=, input_messages_key='input_messages', history_factory_config=[ConfigurableFieldSpec(id='session_id', annotation=, 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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/migrate_agent.ipynb b/docs/versioned_docs/version-0.2.x/how_to/migrate_agent.ipynb new file mode 100644 index 0000000000000..c517f415c0c31 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/migrate_agent.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/multi_vector.ipynb b/docs/versioned_docs/version-0.2.x/how_to/multi_vector.ipynb new file mode 100644 index 0000000000000..566aae27da0a0 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/multi_vector.ipynb @@ -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, I’d 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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/output_parser_custom.ipynb b/docs/versioned_docs/version-0.2.x/how_to/output_parser_custom.ipynb new file mode 100644 index 0000000000000..d5f6af6bb0d65 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/output_parser_custom.ipynb @@ -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: \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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/output_parser_fixing.ipynb b/docs/versioned_docs/version-0.2.x/how_to/output_parser_fixing.ipynb new file mode 100644 index 0000000000000..ce6c75bd7d77e --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/output_parser_fixing.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/output_parser_json.ipynb b/docs/versioned_docs/version-0.2.x/how_to/output_parser_json.ipynb new file mode 100644 index 0000000000000..034e66467f4a1 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/output_parser_json.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/output_parser_retry.ipynb b/docs/versioned_docs/version-0.2.x/how_to/output_parser_retry.ipynb new file mode 100644 index 0000000000000..86f39e02e9c94 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/output_parser_retry.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/output_parser_structured.ipynb b/docs/versioned_docs/version-0.2.x/how_to/output_parser_structured.ipynb new file mode 100644 index 0000000000000..f1bf239fb92c4 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/output_parser_structured.ipynb @@ -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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/output_parser_xml.ipynb b/docs/versioned_docs/version-0.2.x/how_to/output_parser_xml.ipynb new file mode 100644 index 0000000000000..ef57c04723e6b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/output_parser_xml.ipynb @@ -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", + "\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", + "Splash\n", + "Big\n", + "A League of Their Own\n", + "Sleepless in Seattle\n", + "Forrest Gump\n", + "Toy Story\n", + "Apollo 13\n", + "Saving Private Ryan\n", + "Cast Away\n", + "The Da Vinci Code\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 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 \"\\n \\n \\n \\n\" is a well-formatted instance of the schema. \\n2. String \"\\n \\n \" is a badly-formatted instance.\\n3. String \"\\n \\n \\n\" 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 \"\\n \\n \\n \\n\" is a well-formatted instance of the schema. \\n2. String \"\\n \\n \" is a badly-formatted instance.\\n3. String \"\\n \\n \\n\" 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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/output_parser_yaml.ipynb b/docs/versioned_docs/version-0.2.x/how_to/output_parser_yaml.ipynb new file mode 100644 index 0000000000000..34b766719d48c --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/output_parser_yaml.ipynb @@ -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", + "\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/parallel.ipynb b/docs/versioned_docs/version-0.2.x/how_to/parallel.ipynb new file mode 100644 index 0000000000000..fa9f58fa55c37 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/parallel.ipynb @@ -0,0 +1,362 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "e2596041-9b76-4e74-836f-e6235086bbf0", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 1\n", + "keywords: [RunnableParallel, RunnableMap, LCEL]\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "b022ab74-794d-4c54-ad47-ff9549ddb9d2", + "metadata": {}, + "source": [ + "# How to invoke runnables in parallel\n", + "\n", + "The [`RunnableParallel`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableParallel.html) primitive is essentially a dict whose values are runnables (or things that can be coerced to runnables, like functions). It runs all of its values in parallel, and each value is called with the overall input of the `RunnableParallel`. The final return value is a dict with the results of each value under its appropriate key.\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```\n", + "\n", + "## Formatting with `RunnableParallels`\n", + "\n", + "`RunnableParallels` are useful for parallelizing operations, but can also be useful for manipulating the output of one Runnable to match the input format of the next Runnable in a sequence. You can use them to split or fork the chain so that multiple components can process the input in parallel. Later, other components can join or merge the results to synthesize a final response. This type of chain creates a computation graph that looks like the following:\n", + "\n", + "```text\n", + " Input\n", + " / \\\n", + " / \\\n", + " Branch1 Branch2\n", + " \\ /\n", + " \\ /\n", + " Combine\n", + "```\n", + "\n", + "Below, the input to prompt is expected to be a map with keys `\"context\"` and `\"question\"`. The user input is just the question. So we need to get the context using our retriever and passthrough the user input under the `\"question\"` key.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2627ffd7", + "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": "267d1460-53c1-4fdb-b2c3-b6a1eb7fccff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Harrison worked at Kensho.'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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", + "\n", + "# The prompt expects input with keys for \"context\" and \"question\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", + "\n", + "model = ChatOpenAI()\n", + "\n", + "retrieval_chain = (\n", + " {\"context\": retriever, \"question\": RunnablePassthrough()}\n", + " | prompt\n", + " | model\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "retrieval_chain.invoke(\"where did harrison work?\")" + ] + }, + { + "cell_type": "markdown", + "id": "392cd4c4-e7ed-4ab8-934d-f7a4eca55ee1", + "metadata": {}, + "source": [ + "::: {.callout-tip}\n", + "Note that when composing a RunnableParallel with another Runnable we don't even need to wrap our dictionary in the RunnableParallel class — the type conversion is handled for us. In the context of a chain, these are equivalent:\n", + ":::\n", + "\n", + "```\n", + "{\"context\": retriever, \"question\": RunnablePassthrough()}\n", + "```\n", + "\n", + "```\n", + "RunnableParallel({\"context\": retriever, \"question\": RunnablePassthrough()})\n", + "```\n", + "\n", + "```\n", + "RunnableParallel(context=retriever, question=RunnablePassthrough())\n", + "```\n", + "\n", + "See the section on [coercion for more](/docs/how_to/sequence/#coercion)." + ] + }, + { + "cell_type": "markdown", + "id": "7c1b8baa-3a80-44f0-bb79-d22f79815d3d", + "metadata": {}, + "source": [ + "## Using itemgetter as shorthand\n", + "\n", + "Note that you can use Python's `itemgetter` as shorthand to extract data from the map when combining with `RunnableParallel`. You can find more information about itemgetter in the [Python Documentation](https://docs.python.org/3/library/operator.html#operator.itemgetter). \n", + "\n", + "In the example below, we use itemgetter to extract specific keys from the map:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "84fc49e1-2daf-4700-ae33-a0a6ed47d5f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Harrison ha lavorato a Kensho.'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from operator import itemgetter\n", + "\n", + "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", + "\n", + "template = \"\"\"Answer the question based only on the following context:\n", + "{context}\n", + "\n", + "Question: {question}\n", + "\n", + "Answer in the following language: {language}\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", + "\n", + "chain = (\n", + " {\n", + " \"context\": itemgetter(\"question\") | retriever,\n", + " \"question\": itemgetter(\"question\"),\n", + " \"language\": itemgetter(\"language\"),\n", + " }\n", + " | prompt\n", + " | model\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "chain.invoke({\"question\": \"where did harrison work\", \"language\": \"italian\"})" + ] + }, + { + "cell_type": "markdown", + "id": "bc2f9847-39aa-4fe4-9049-3a8969bc4bce", + "metadata": {}, + "source": [ + "## Parallelize steps\n", + "\n", + "RunnableParallels make it easy to execute multiple Runnables in parallel, and to return the output of these Runnables as a map." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "31f18442-f837-463f-bef4-8729368f5f8b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'joke': AIMessage(content=\"Why don't bears like fast food? Because 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_d9767fc5b9', 'finish_reason': 'stop', 'logprobs': None}, id='run-fe024170-c251-4b7a-bfd4-64a3737c67f2-0'),\n", + " 'poem': AIMessage(content='In the quiet of the forest, the bear roams free\\nMajestic and wild, a sight to see.', response_metadata={'token_usage': {'completion_tokens': 24, 'prompt_tokens': 15, 'total_tokens': 39}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-2707913e-a743-4101-b6ec-840df4568a76-0')}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnableParallel\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI()\n", + "joke_chain = ChatPromptTemplate.from_template(\"tell me a joke about {topic}\") | model\n", + "poem_chain = (\n", + " ChatPromptTemplate.from_template(\"write a 2-line poem about {topic}\") | model\n", + ")\n", + "\n", + "map_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)\n", + "\n", + "map_chain.invoke({\"topic\": \"bear\"})" + ] + }, + { + "cell_type": "markdown", + "id": "833da249-c0d4-4e5b-b3f8-cab549f0f7e1", + "metadata": {}, + "source": [ + "## Parallelism\n", + "\n", + "RunnableParallel are also useful for running independent processes in parallel, since each Runnable in the map is executed in parallel. For example, we can see our earlier `joke_chain`, `poem_chain` and `map_chain` all have about the same runtime, even though `map_chain` executes both of the other two." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "38e47834-45af-4281-991f-86f150001510", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "610 ms ± 64 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "joke_chain.invoke({\"topic\": \"bear\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d0cd40de-b37e-41fa-a2f6-8aaa49f368d6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "599 ms ± 73.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "poem_chain.invoke({\"topic\": \"bear\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "799894e1-8e18-4a73-b466-f6aea6af3920", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "643 ms ± 77.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "map_chain.invoke({\"topic\": \"bear\"})" + ] + }, + { + "cell_type": "markdown", + "id": "7d4492e1", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "You now know some ways to format and parallelize chain steps with `RunnableParallel`.\n", + "\n", + "To learn more, see the other how-to guides on runnables in this section." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4af8bebd", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/parent_document_retriever.ipynb b/docs/versioned_docs/version-0.2.x/how_to/parent_document_retriever.ipynb new file mode 100644 index 0000000000000..ed72050de02ff --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/parent_document_retriever.ipynb @@ -0,0 +1,432 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "34883374", + "metadata": {}, + "source": [ + "# How to use the Parent Document Retriever\n", + "\n", + "When splitting documents for retrieval, there are often conflicting desires:\n", + "\n", + "1. You may want to have small documents, so that their embeddings can most\n", + " accurately reflect their meaning. If too long, then the embeddings can\n", + " lose meaning.\n", + "2. You want to have long enough documents that the context of each chunk is\n", + " retained.\n", + "\n", + "The `ParentDocumentRetriever` strikes that balance by splitting and storing\n", + "small chunks of data. During retrieval, it first fetches the small chunks\n", + "but then looks up the parent ids for those chunks and returns those larger\n", + "documents.\n", + "\n", + "Note that \"parent document\" refers to the document that a small chunk\n", + "originated from. This can either be the whole raw document OR a larger\n", + "chunk." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8b6e74b2", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.retrievers import ParentDocumentRetriever" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1d17af96", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.storage import InMemoryStore\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": "604ff981", + "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())" + ] + }, + { + "cell_type": "markdown", + "id": "d3943f72", + "metadata": {}, + "source": [ + "## Retrieving full documents\n", + "\n", + "In this mode, we want to retrieve the full documents. Therefore, we only specify a child splitter." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1a8b2e5f", + "metadata": {}, + "outputs": [], + "source": [ + "# This text splitter is used to create the child documents\n", + "child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)\n", + "# 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 = InMemoryStore()\n", + "retriever = ParentDocumentRetriever(\n", + " vectorstore=vectorstore,\n", + " docstore=store,\n", + " child_splitter=child_splitter,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2b107935", + "metadata": {}, + "outputs": [], + "source": [ + "retriever.add_documents(docs, ids=None)" + ] + }, + { + "cell_type": "markdown", + "id": "d05b97b7", + "metadata": {}, + "source": [ + "This should yield two keys, because we added two documents." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "30e3812b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['cfdf4af7-51f2-4ea3-8166-5be208efa040',\n", + " 'bf213c21-cc66-4208-8a72-733d030187e6']" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(store.yield_keys())" + ] + }, + { + "cell_type": "markdown", + "id": "f895d62b", + "metadata": {}, + "source": [ + "Let's now call the vector store search functionality - we should see that it returns small chunks (since we're storing the small chunks)." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b261c02c", + "metadata": {}, + "outputs": [], + "source": [ + "sub_docs = vectorstore.similarity_search(\"justice breyer\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5108222f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tonight, I’d 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" + ] + } + ], + "source": [ + "print(sub_docs[0].page_content)" + ] + }, + { + "cell_type": "markdown", + "id": "bda8ed5a", + "metadata": {}, + "source": [ + "Let's now retrieve from the overall retriever. This should return large documents - since it returns the documents where the smaller chunks are located." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "419a91c4", + "metadata": {}, + "outputs": [], + "source": [ + "retrieved_docs = retriever.get_relevant_documents(\"justice breyer\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cf10d250", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "38540" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(retrieved_docs[0].page_content)" + ] + }, + { + "cell_type": "markdown", + "id": "14f813a5", + "metadata": {}, + "source": [ + "## Retrieving larger chunks\n", + "\n", + "Sometimes, the full documents can be too big to want to retrieve them as is. In that case, what we really want to do is to first split the raw documents into larger chunks, and then split it into smaller chunks. We then index the smaller chunks, but on retrieval we retrieve the larger chunks (but still not the full documents)." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b6f9a4f0", + "metadata": {}, + "outputs": [], + "source": [ + "# This text splitter is used to create the parent documents\n", + "parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)\n", + "# This text splitter is used to create the child documents\n", + "# It should create documents smaller than the parent\n", + "child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)\n", + "# The vectorstore to use to index the child chunks\n", + "vectorstore = Chroma(\n", + " collection_name=\"split_parents\", embedding_function=OpenAIEmbeddings()\n", + ")\n", + "# The storage layer for the parent documents\n", + "store = InMemoryStore()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "19478ff3", + "metadata": {}, + "outputs": [], + "source": [ + "retriever = ParentDocumentRetriever(\n", + " vectorstore=vectorstore,\n", + " docstore=store,\n", + " child_splitter=child_splitter,\n", + " parent_splitter=parent_splitter,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "fe16e620", + "metadata": {}, + "outputs": [], + "source": [ + "retriever.add_documents(docs)" + ] + }, + { + "cell_type": "markdown", + "id": "64ad3c8c", + "metadata": {}, + "source": [ + "We can see that there are much more than two documents now - these are the larger chunks." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "24d81886", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "66" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(list(store.yield_keys()))" + ] + }, + { + "cell_type": "markdown", + "id": "baaef673", + "metadata": {}, + "source": [ + "Let's make sure the underlying vector store still retrieves the small chunks." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "b1c859de", + "metadata": {}, + "outputs": [], + "source": [ + "sub_docs = vectorstore.similarity_search(\"justice breyer\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6fffa2eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tonight, I’d 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" + ] + } + ], + "source": [ + "print(sub_docs[0].page_content)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "3a3202df", + "metadata": {}, + "outputs": [], + "source": [ + "retrieved_docs = retriever.get_relevant_documents(\"justice breyer\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "684fdb2c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1849" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(retrieved_docs[0].page_content)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "9f17f662", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "In state after state, new laws have been passed, not only to suppress the vote, but to subvert entire elections. \n", + "\n", + "We cannot let this happen. \n", + "\n", + "Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n", + "\n", + "Tonight, I’d 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 nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence. \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 she’s been nominated, she’s 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, we’ve installed new technology like cutting-edge scanners to better detect drug smuggling. \n", + "\n", + "We’ve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \n", + "\n", + "We’re putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \n", + "\n", + "We’re securing commitments and supporting partners in South and Central America to host more refugees and secure their own borders.\n" + ] + } + ], + "source": [ + "print(retrieved_docs[0].page_content)" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/passthrough.ipynb b/docs/versioned_docs/version-0.2.x/how_to/passthrough.ipynb new file mode 100644 index 0000000000000..50dea1eb2887e --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/passthrough.ipynb @@ -0,0 +1,182 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "d35de667-0352-4bfb-a890-cebe7f676fe7", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 5\n", + "keywords: [RunnablePassthrough, LCEL]\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "b022ab74-794d-4c54-ad47-ff9549ddb9d2", + "metadata": {}, + "source": [ + "# How to pass through arguments from one step to the next\n", + "\n", + "When composing chains with several steps, sometimes you will want to pass data from previous steps unchanged for use as input to a later step. The [`RunnablePassthrough`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.passthrough.RunnablePassthrough.html) class allows you to do just this, and is typically is used in conjuction with a [RunnableParallel](/docs/how_to/parallel/) to pass data through to a later step in your constructed chains.\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```\n", + "\n", + "See the example below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e169b952", + "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": "03988b8d-d54c-4492-8707-1594372cf093", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'passed': {'num': 1}, 'modified': 2}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.runnables import RunnableParallel, RunnablePassthrough\n", + "\n", + "runnable = RunnableParallel(\n", + " passed=RunnablePassthrough(),\n", + " modified=lambda x: x[\"num\"] + 1,\n", + ")\n", + "\n", + "runnable.invoke({\"num\": 1})" + ] + }, + { + "cell_type": "markdown", + "id": "702c7acc-cd31-4037-9489-647df192fd7c", + "metadata": {}, + "source": [ + "As seen above, `passed` key was called with `RunnablePassthrough()` and so it simply passed on `{'num': 1}`. \n", + "\n", + "We also set a second key in the map with `modified`. This uses a lambda to set a single value adding 1 to the num, which resulted in `modified` key with the value of `2`." + ] + }, + { + "cell_type": "markdown", + "id": "15187a3b-d666-4b9b-a258-672fc51fe0e2", + "metadata": {}, + "source": [ + "## Retrieval Example\n", + "\n", + "In the example below, we see a more real-world use case where we use `RunnablePassthrough` along with `RunnableParallel` in a chain to properly format inputs to a prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "267d1460-53c1-4fdb-b2c3-b6a1eb7fccff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Harrison worked at Kensho.'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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", + "retrieval_chain = (\n", + " {\"context\": retriever, \"question\": RunnablePassthrough()}\n", + " | prompt\n", + " | model\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "retrieval_chain.invoke(\"where did harrison work?\")" + ] + }, + { + "cell_type": "markdown", + "id": "392cd4c4-e7ed-4ab8-934d-f7a4eca55ee1", + "metadata": {}, + "source": [ + "Here the input to prompt is expected to be a map with keys \"context\" and \"question\". The user input is just the question. So we need to get the context using our retriever and passthrough the user input under the \"question\" key. The `RunnablePassthrough` allows us to pass on the user's question to the prompt and model. \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." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/prompts_composition.ipynb b/docs/versioned_docs/version-0.2.x/how_to/prompts_composition.ipynb new file mode 100644 index 0000000000000..138b30e9d44c2 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/prompts_composition.ipynb @@ -0,0 +1,314 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "02a1c8fb", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 5\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "4de4e022", + "metadata": {}, + "source": [ + "# How to compose prompts together\n", + "\n", + "LangChain provides a user friendly interface for composing different parts of prompts together. You can do this with either string prompts or chat prompts. Constructing prompts this way allows for easy reuse of components.\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "c3190650", + "metadata": {}, + "source": [ + "## String prompt composition\n", + "\n", + "When working with string prompts, each template is joined together. You can work with either prompts directly or strings (the first element in the list needs to be a prompt)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "69b17f05", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PromptTemplate(input_variables=['language', 'topic'], template='Tell me a joke about {topic}, make it funny\\n\\nand in {language}')" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.prompts import PromptTemplate\n", + "\n", + "prompt = (\n", + " PromptTemplate.from_template(\"Tell me a joke about {topic}\")\n", + " + \", make it funny\"\n", + " + \"\\n\\nand in {language}\"\n", + ")\n", + "\n", + "prompt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dbba24ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Tell me a joke about sports, make it funny\\n\\nand in spanish'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prompt.format(topic=\"sports\", language=\"spanish\")" + ] + }, + { + "cell_type": "markdown", + "id": "4e4f6a8a", + "metadata": {}, + "source": [ + "## Chat prompt composition" + ] + }, + { + "cell_type": "markdown", + "id": "8554bae5", + "metadata": {}, + "source": [ + "A chat prompt is made up a of a list of messages. Similarly to the above example, we can concatenate chat prompt templates. Each new element is a new message in the final prompt.\n", + "\n", + "First, let's initialize the a [`ChatPromptTemplate`](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.chat.ChatPromptTemplate.html) with a [`SystemMessage`](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.system.SystemMessage.html)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cab8dd65", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import AIMessage, HumanMessage, SystemMessage\n", + "\n", + "prompt = SystemMessage(content=\"You are a nice pirate\")" + ] + }, + { + "cell_type": "markdown", + "id": "30656ef8", + "metadata": {}, + "source": [ + "You can then easily create a pipeline combining it with other messages *or* message templates.\n", + "Use a `Message` when there is no variables to be formatted, use a `MessageTemplate` when there are variables to be formatted. You can also use just a string (note: this will automatically get inferred as a [`HumanMessagePromptTemplate`](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.chat.HumanMessagePromptTemplate.html).)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a2ddd0a1", + "metadata": {}, + "outputs": [], + "source": [ + "new_prompt = (\n", + " prompt + HumanMessage(content=\"hi\") + AIMessage(content=\"what?\") + \"{input}\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "72294e1b", + "metadata": {}, + "source": [ + "Under the hood, this creates an instance of the ChatPromptTemplate class, so you can use it just as you did before!" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "297932de", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[SystemMessage(content='You are a nice pirate'),\n", + " HumanMessage(content='hi'),\n", + " AIMessage(content='what?'),\n", + " HumanMessage(content='i said hi')]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_prompt.format_messages(input=\"i said hi\")" + ] + }, + { + "cell_type": "markdown", + "id": "0e1d47e3-b05a-4aef-a58c-3057fa628c1c", + "metadata": {}, + "source": [ + "## Using PipelinePrompt" + ] + }, + { + "cell_type": "markdown", + "id": "0a5892f9-e4d8-4b7c-b6a5-4651539b9734", + "metadata": {}, + "source": [ + "LangChain includes a class called [`PipelinePromptTemplate`](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.pipeline.PipelinePromptTemplate.html), which can be useful when you want to reuse parts of prompts. A PipelinePrompt consists of two main parts:\n", + "\n", + "- Final prompt: The final prompt that is returned\n", + "- Pipeline prompts: A list of tuples, consisting of a string name and a prompt template. Each prompt template will be formatted and then passed to future prompt templates as a variable with the same name." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4face631-74d7-49ca-93b1-1e6e66fa58e2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['person', 'example_a', 'example_q', 'input']" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.prompts import PipelinePromptTemplate, PromptTemplate\n", + "\n", + "full_template = \"\"\"{introduction}\n", + "\n", + "{example}\n", + "\n", + "{start}\"\"\"\n", + "full_prompt = PromptTemplate.from_template(full_template)\n", + "\n", + "introduction_template = \"\"\"You are impersonating {person}.\"\"\"\n", + "introduction_prompt = PromptTemplate.from_template(introduction_template)\n", + "\n", + "example_template = \"\"\"Here's an example of an interaction:\n", + "\n", + "Q: {example_q}\n", + "A: {example_a}\"\"\"\n", + "example_prompt = PromptTemplate.from_template(example_template)\n", + "\n", + "start_template = \"\"\"Now, do this for real!\n", + "\n", + "Q: {input}\n", + "A:\"\"\"\n", + "start_prompt = PromptTemplate.from_template(start_template)\n", + "\n", + "input_prompts = [\n", + " (\"introduction\", introduction_prompt),\n", + " (\"example\", example_prompt),\n", + " (\"start\", start_prompt),\n", + "]\n", + "pipeline_prompt = PipelinePromptTemplate(\n", + " final_prompt=full_prompt, pipeline_prompts=input_prompts\n", + ")\n", + "\n", + "pipeline_prompt.input_variables" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c6cabb16-ea30-4de0-8548-dcce84df8421", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are impersonating Elon Musk.\n", + "\n", + "Here's an example of an interaction:\n", + "\n", + "Q: What's your favorite car?\n", + "A: Tesla\n", + "\n", + "Now, do this for real!\n", + "\n", + "Q: What's your favorite social media site?\n", + "A:\n" + ] + } + ], + "source": [ + "print(\n", + " pipeline_prompt.format(\n", + " person=\"Elon Musk\",\n", + " example_q=\"What's your favorite car?\",\n", + " example_a=\"Tesla\",\n", + " input=\"What's your favorite social media site?\",\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "96922030", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "You've now learned how to compose prompts together.\n", + "\n", + "Next, check out the other how-to guides on prompt templates in this section, like [adding few-shot examples to your prompt templates](/docs/how_to/few_shot_examples_chat)." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/prompts_partial.ipynb b/docs/versioned_docs/version-0.2.x/how_to/prompts_partial.ipynb new file mode 100644 index 0000000000000..aca86389ac565 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/prompts_partial.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "45e0127d", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 4\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "d8ca736e", + "metadata": {}, + "source": [ + "# How to partially format prompt templates\n", + "\n", + "Like partially binding arguments to a function, it can make sense to \"partial\" a prompt template - e.g. pass in a subset of the required values, as to create a new prompt template which expects only the remaining subset of values.\n", + "\n", + "LangChain supports this in two ways:\n", + "\n", + "1. Partial formatting with string values.\n", + "2. Partial formatting with functions that return string values.\n", + "\n", + "In the examples below, we go over the motivations for both use cases as well as how to do it in LangChain.\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```\n", + "\n", + "## Partial with strings\n", + "\n", + "One common use case for wanting to partial a prompt template is if you get access to some of the variables in a prompt before others. For example, suppose you have a prompt template that requires two variables, `foo` and `baz`. If you get the `foo` value early on in your chain, but the `baz` value later, it can be inconvenient to pass both variables all the way through the chain. Instead, you can partial the prompt template with the `foo` value, and then pass the partialed prompt template along and just use that. Below is an example of doing this:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5f1942bd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foobaz\n" + ] + } + ], + "source": [ + "from langchain_core.prompts import PromptTemplate\n", + "\n", + "prompt = PromptTemplate.from_template(\"{foo}{bar}\")\n", + "partial_prompt = prompt.partial(foo=\"foo\")\n", + "print(partial_prompt.format(bar=\"baz\"))" + ] + }, + { + "cell_type": "markdown", + "id": "79af4cea", + "metadata": {}, + "source": [ + "You can also just initialize the prompt with the partialed variables.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "572fa26f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foobaz\n" + ] + } + ], + "source": [ + "prompt = PromptTemplate(\n", + " template=\"{foo}{bar}\", input_variables=[\"bar\"], partial_variables={\"foo\": \"foo\"}\n", + ")\n", + "print(prompt.format(bar=\"baz\"))" + ] + }, + { + "cell_type": "markdown", + "id": "ab12d50d", + "metadata": {}, + "source": [ + "## Partial with functions\n", + "\n", + "The other common use is to partial with a function. The use case for this is when you have a variable you know that you always want to fetch in a common way. A prime example of this is with date or time. Imagine you have a prompt which you always want to have the current date. You can't hard code it in the prompt, and passing it along with the other input variables is inconvenient. In this case, it's handy to be able to partial the prompt with a function that always returns the current date.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c538703a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tell me a funny joke about the day 04/21/2024, 19:43:57\n" + ] + } + ], + "source": [ + "from datetime import datetime\n", + "\n", + "\n", + "def _get_datetime():\n", + " now = datetime.now()\n", + " return now.strftime(\"%m/%d/%Y, %H:%M:%S\")\n", + "\n", + "\n", + "prompt = PromptTemplate(\n", + " template=\"Tell me a {adjective} joke about the day {date}\",\n", + " input_variables=[\"adjective\", \"date\"],\n", + ")\n", + "partial_prompt = prompt.partial(date=_get_datetime)\n", + "print(partial_prompt.format(adjective=\"funny\"))" + ] + }, + { + "cell_type": "markdown", + "id": "da80290e", + "metadata": {}, + "source": [ + "You can also just initialize the prompt with the partialed variables, which often makes more sense in this workflow.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f86fce6d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tell me a funny joke about the day 04/21/2024, 19:43:57\n" + ] + } + ], + "source": [ + "prompt = PromptTemplate(\n", + " template=\"Tell me a {adjective} joke about the day {date}\",\n", + " input_variables=[\"adjective\"],\n", + " partial_variables={\"date\": _get_datetime},\n", + ")\n", + "print(prompt.format(adjective=\"funny\"))" + ] + }, + { + "cell_type": "markdown", + "id": "3895b210", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "You've now learned how to partially apply variables to your prompt templates.\n", + "\n", + "Next, check out the other how-to guides on prompt templates in this section, like [adding few-shot examples to your prompt templates](/docs/how_to/few_shot_examples_chat)." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/qa_chat_history_how_to.ipynb b/docs/versioned_docs/version-0.2.x/how_to/qa_chat_history_how_to.ipynb new file mode 100644 index 0000000000000..a8a7c168e071d --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/qa_chat_history_how_to.ipynb @@ -0,0 +1,949 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "86fc5bb2-017f-434e-8cd6-53ab214a5604", + "metadata": {}, + "source": [ + "# How to add chat history\n", + "\n", + "In many Q&A applications we want to allow the user to have a back-and-forth conversation, meaning the application needs some sort of \"memory\" of past questions and answers, and some logic for incorporating those into its current thinking.\n", + "\n", + "In this guide we focus on **adding logic for incorporating historical messages.**\n", + "\n", + "This is largely a condensed version of the [Conversational RAG tutorial](/docs/tutorials/qa_chat_history).\n", + "\n", + "We will cover two approaches:\n", + "1. [Chains](/docs/how_to/qa_chat_history_how_to#chains), in which we always execute a retrieval step;\n", + "2. [Agents](/docs/how_to/qa_chat_history_how_to#agents), in which we give an LLM discretion over whether and how to execute a retrieval step (or multiple steps).\n", + "\n", + "For the external knowledge source, we will use the same [LLM Powered Autonomous Agents](https://lilianweng.github.io/posts/2023-06-23-agent/) blog post by Lilian Weng from the [RAG tutorial](/docs/tutorials/rag)." + ] + }, + { + "cell_type": "markdown", + "id": "487d8d79-5ee9-4aa4-9fdf-cd5f4303e099", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "### Dependencies\n", + "\n", + "We'll use OpenAI embeddings and a Chroma vector store in this walkthrough, but everything shown here works with any [Embeddings](/docs/concepts#embedding-models), and [VectorStore](/docs/concepts#vectorstores) or [Retriever](/docs/concepts#retrievers). \n", + "\n", + "We'll use the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ede7fdc0-ef31-483d-bd67-32e4b5c5d527", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchainhub langchain-chroma bs4" + ] + }, + { + "cell_type": "markdown", + "id": "51ef48de-70b6-4f43-8e0b-ab9b84c9c02a", + "metadata": {}, + "source": [ + "We need to set environment variable `OPENAI_API_KEY`, which can be done directly or loaded from a `.env` file like so:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143787ca-d8e6-4dc9-8281-4374f4d71720", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# import dotenv\n", + "\n", + "# dotenv.load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "id": "1665e740-ce01-4f09-b9ed-516db0bd326f", + "metadata": {}, + "source": [ + "### LangSmith\n", + "\n", + "Many of the applications you build with LangChain will contain multiple steps with multiple invocations of LLM calls. 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. The best way to do this is with [LangSmith](https://smith.langchain.com).\n", + "\n", + "Note that LangSmith is not needed, but it is helpful. If you do want to use LangSmith, after you sign up at the link above, make sure to set your environment variables to start logging traces:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "07411adb-3722-4f65-ab7f-8f6f57663d11", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "fa6ba684-26cf-4860-904e-a4d51380c134", + "metadata": {}, + "source": [ + "## Chains {#chains}\n", + "\n", + "In a conversational RAG application, queries issued to the retriever should be informed by the context of the conversation. LangChain provides a [create_history_aware_retriever](https://api.python.langchain.com/en/latest/chains/langchain.chains.history_aware_retriever.create_history_aware_retriever.html) constructor to simplify this. It constructs a chain that accepts keys `input` and `chat_history` as input, and has the same output schema as a retriever. `create_history_aware_retriever` requires as inputs: \n", + "\n", + "1. LLM;\n", + "2. Retriever;\n", + "3. Prompt.\n", + "\n", + "First we obtain these objects:\n", + "\n", + "### LLM\n", + "\n", + "We can use any supported chat model:" + ] + }, + { + "cell_type": "markdown", + "id": "646840fb-5212-48ea-8bc7-ec7be5ec727e", + "metadata": {}, + "source": [ + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cb58f273-2111-4a9b-8932-9b64c95030c8", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)" + ] + }, + { + "cell_type": "markdown", + "id": "6bb76a36-15b1-4589-8a3d-18c6f5fdb7e0", + "metadata": {}, + "source": [ + "### Retriever" + ] + }, + { + "cell_type": "markdown", + "id": "15f8ad59-19de-42e3-85a8-3ba95ee0bd43", + "metadata": {}, + "source": [ + "For the retriever, we will use [WebBaseLoader](https://api.python.langchain.com/en/latest/document_loaders/langchain_community.document_loaders.web_base.WebBaseLoader.html) to load the content of a web page. Here we instantiate a `Chroma` vectorstore and then use its [.as_retriever](https://api.python.langchain.com/en/latest/vectorstores/langchain_core.vectorstores.VectorStore.html#langchain_core.vectorstores.VectorStore.as_retriever) method to build a retriever that can be incorporated into [LCEL](/docs/concepts/#langchain-expression-language) chains." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "820244ae-74b4-4593-b392-822979dd91b8", + "metadata": {}, + "outputs": [], + "source": [ + "import bs4\n", + "from langchain import hub\n", + "from langchain.chains import create_retrieval_chain\n", + "from langchain.chains.combine_documents import create_stuff_documents_chain\n", + "from langchain_chroma import Chroma\n", + "from langchain_community.document_loaders import WebBaseLoader\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 OpenAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "loader = WebBaseLoader(\n", + " web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n", + " bs_kwargs=dict(\n", + " parse_only=bs4.SoupStrainer(\n", + " class_=(\"post-content\", \"post-title\", \"post-header\")\n", + " )\n", + " ),\n", + ")\n", + "docs = loader.load()\n", + "\n", + "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n", + "splits = text_splitter.split_documents(docs)\n", + "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n", + "retriever = vectorstore.as_retriever()" + ] + }, + { + "cell_type": "markdown", + "id": "776ae958-cbdc-4471-8669-c6087436f0b5", + "metadata": {}, + "source": [ + "### Prompt\n", + "\n", + "We'll use a prompt that includes a `MessagesPlaceholder` variable under the name \"chat_history\". This allows us to pass in a list of Messages to the prompt using the \"chat_history\" input key, and these messages will be inserted after the system message and before the human message containing the latest question." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2b685428-8b82-4af1-be4f-7232c5d55b73", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains import create_history_aware_retriever\n", + "from langchain_core.prompts import MessagesPlaceholder\n", + "\n", + "contextualize_q_system_prompt = (\n", + " \"Given a chat history and the latest user question \"\n", + " \"which might reference context in the chat history, \"\n", + " \"formulate a standalone question which can be understood \"\n", + " \"without the chat history. Do NOT answer the question, \"\n", + " \"just reformulate it if needed and otherwise return it as is.\"\n", + ")\n", + "\n", + "contextualize_q_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", contextualize_q_system_prompt),\n", + " MessagesPlaceholder(\"chat_history\"),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "9d2a692e-a019-4515-9625-5b0530c3c9af", + "metadata": {}, + "source": [ + "### Assembling the chain\n", + "\n", + "We can then instantiate the history-aware retriever:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4c4b1695-6217-4ee8-abaf-7cc26366d988", + "metadata": {}, + "outputs": [], + "source": [ + "history_aware_retriever = create_history_aware_retriever(\n", + " llm, retriever, contextualize_q_prompt\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "42a47168-4a1f-4e39-bd2d-d5b03609a243", + "metadata": {}, + "source": [ + "This chain prepends a rephrasing of the input query to our retriever, so that the retrieval incorporates the context of the conversation.\n", + "\n", + "Now we can build our full QA chain.\n", + "\n", + "As in the [RAG tutorial](/docs/tutorials/rag), we will use [create_stuff_documents_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.combine_documents.stuff.create_stuff_documents_chain.html) to generate a `question_answer_chain`, with input keys `context`, `chat_history`, and `input`-- it accepts the retrieved context alongside the conversation history and query to generate an answer.\n", + "\n", + "We build our final `rag_chain` with [create_retrieval_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.retrieval.create_retrieval_chain.html). This chain applies the `history_aware_retriever` and `question_answer_chain` in sequence, retaining intermediate outputs such as the retrieved context for convenience. It has input keys `input` and `chat_history`, and includes `input`, `chat_history`, `context`, and `answer` in its output." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "afef4385-f571-4874-8f52-3d475642f579", + "metadata": {}, + "outputs": [], + "source": [ + "system_prompt = (\n", + " \"You are an assistant for question-answering tasks. \"\n", + " \"Use the following pieces of retrieved context to answer \"\n", + " \"the question. If you don't know the answer, say that you \"\n", + " \"don't know. Use three sentences maximum and keep the \"\n", + " \"answer concise.\"\n", + " \"\\n\\n\"\n", + " \"{context}\"\n", + ")\n", + "qa_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " MessagesPlaceholder(\"chat_history\"),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)\n", + "\n", + "rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)" + ] + }, + { + "cell_type": "markdown", + "id": "53a662c2-f38b-45f9-95c4-66de15637614", + "metadata": {}, + "source": [ + "### Adding chat history\n", + "\n", + "To manage the chat history, we will need:\n", + "\n", + "1. An object for storing the chat history;\n", + "2. An object that wraps our chain and manages updates to the chat history.\n", + "\n", + "For these we will use [BaseChatMessageHistory](https://api.python.langchain.com/en/latest/chat_history/langchain_core.chat_history.BaseChatMessageHistory.html) and [RunnableWithMessageHistory](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html). The latter is a wrapper for an LCEL chain and a `BaseChatMessageHistory` that handles injecting chat history into inputs and updating it after each invocation.\n", + "\n", + "For a detailed walkthrough of how to use these classes together to create a stateful conversational chain, head to the [How to add message history (memory)](/docs/how_to/message_history/) LCEL how-to guide.\n", + "\n", + "Below, we implement a simple example of the second option, in which chat histories are stored in a simple dict. LangChain manages memory integrations with [Redis](/docs/integrations/memory/redis_chat_message_history/) and other technologies to provide for more robust persistence.\n", + "\n", + "Instances of `RunnableWithMessageHistory` manage the chat history for you. They accept a config with a key (`\"session_id\"` by default) that specifies what conversation history to fetch and prepend to the input, and append the output to the same conversation history. Below is an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9c3fb176-8d6a-4dc7-8408-6a22c5f7cc72", + "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", + "conversational_rag_chain = RunnableWithMessageHistory(\n", + " rag_chain,\n", + " get_session_history,\n", + " input_messages_key=\"input\",\n", + " history_messages_key=\"chat_history\",\n", + " output_messages_key=\"answer\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1046c92f-21b3-4214-907d-92878d8cba23", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Task decomposition involves breaking down a complex task into smaller and simpler steps to make it more manageable and easier to accomplish. This process can be done using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide the model in thinking step by step or exploring multiple reasoning possibilities at each step. Task decomposition can be facilitated by providing simple prompts to a language model, task-specific instructions, or human inputs.'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conversational_rag_chain.invoke(\n", + " {\"input\": \"What is Task Decomposition?\"},\n", + " config={\n", + " \"configurable\": {\"session_id\": \"abc123\"}\n", + " }, # constructs a key \"abc123\" in `store`.\n", + ")[\"answer\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0e89c75f-7ad7-4331-a2fe-57579eb8f840", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Task decomposition can be achieved through various methods, including using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide the model in breaking down complex tasks into smaller steps. Common ways of task decomposition include providing simple prompts to a language model, task-specific instructions tailored to the specific task at hand, or incorporating human inputs to guide the decomposition process effectively.'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conversational_rag_chain.invoke(\n", + " {\"input\": \"What are common ways of doing it?\"},\n", + " config={\"configurable\": {\"session_id\": \"abc123\"}},\n", + ")[\"answer\"]" + ] + }, + { + "cell_type": "markdown", + "id": "3ab59258-84bc-4904-880e-2ebfebbca563", + "metadata": {}, + "source": [ + "The conversation history can be inspected in the `store` dict:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7686b874-3a85-499f-82b5-28a85c4c768c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User: What is Task Decomposition?\n", + "\n", + "AI: Task decomposition involves breaking down a complex task into smaller and simpler steps to make it more manageable and easier to accomplish. This process can be done using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide the model in thinking step by step or exploring multiple reasoning possibilities at each step. Task decomposition can be facilitated by providing simple prompts to a language model, task-specific instructions, or human inputs.\n", + "\n", + "User: What are common ways of doing it?\n", + "\n", + "AI: Task decomposition can be achieved through various methods, including using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide the model in breaking down complex tasks into smaller steps. Common ways of task decomposition include providing simple prompts to a language model, task-specific instructions tailored to the specific task at hand, or incorporating human inputs to guide the decomposition process effectively.\n", + "\n" + ] + } + ], + "source": [ + "from langchain_core.messages import AIMessage\n", + "\n", + "for message in store[\"abc123\"].messages:\n", + " if isinstance(message, AIMessage):\n", + " prefix = \"AI\"\n", + " else:\n", + " prefix = \"User\"\n", + "\n", + " print(f\"{prefix}: {message.content}\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "0ab1ded4-76d9-453f-9b9b-db9a4560c737", + "metadata": {}, + "source": [ + "### Tying it together" + ] + }, + { + "cell_type": "markdown", + "id": "8a08a5ea-df5b-4547-93c6-2a3940dd5c3e", + "metadata": {}, + "source": [ + "![](/static/img/conversational_retrieval_chain.png)\n", + "\n", + "For convenience, we tie together all of the necessary steps in a single code cell:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "71c32048-1a41-465f-a9e2-c4affc332fd9", + "metadata": {}, + "outputs": [], + "source": [ + "import bs4\n", + "from langchain.chains import create_history_aware_retriever, create_retrieval_chain\n", + "from langchain.chains.combine_documents import create_stuff_documents_chain\n", + "from langchain_chroma import Chroma\n", + "from langchain_community.chat_message_histories import ChatMessageHistory\n", + "from langchain_community.document_loaders import WebBaseLoader\n", + "from langchain_core.chat_history import BaseChatMessageHistory\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "\n", + "\n", + "### Construct retriever ###\n", + "loader = WebBaseLoader(\n", + " web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n", + " bs_kwargs=dict(\n", + " parse_only=bs4.SoupStrainer(\n", + " class_=(\"post-content\", \"post-title\", \"post-header\")\n", + " )\n", + " ),\n", + ")\n", + "docs = loader.load()\n", + "\n", + "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n", + "splits = text_splitter.split_documents(docs)\n", + "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "\n", + "### Contextualize question ###\n", + "contextualize_q_system_prompt = (\n", + " \"Given a chat history and the latest user question \"\n", + " \"which might reference context in the chat history, \"\n", + " \"formulate a standalone question which can be understood \"\n", + " \"without the chat history. Do NOT answer the question, \"\n", + " \"just reformulate it if needed and otherwise return it as is.\"\n", + ")\n", + "contextualize_q_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", contextualize_q_system_prompt),\n", + " MessagesPlaceholder(\"chat_history\"),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "history_aware_retriever = create_history_aware_retriever(\n", + " llm, retriever, contextualize_q_prompt\n", + ")\n", + "\n", + "\n", + "### Answer question ###\n", + "system_prompt = (\n", + " \"You are an assistant for question-answering tasks. \"\n", + " \"Use the following pieces of retrieved context to answer \"\n", + " \"the question. If you don't know the answer, say that you \"\n", + " \"don't know. Use three sentences maximum and keep the \"\n", + " \"answer concise.\"\n", + " \"\\n\\n\"\n", + " \"{context}\"\n", + ")\n", + "qa_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " MessagesPlaceholder(\"chat_history\"),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)\n", + "\n", + "rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)\n", + "\n", + "\n", + "### Statefully manage chat history ###\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", + "conversational_rag_chain = RunnableWithMessageHistory(\n", + " rag_chain,\n", + " get_session_history,\n", + " input_messages_key=\"input\",\n", + " history_messages_key=\"chat_history\",\n", + " output_messages_key=\"answer\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6d0a7a73-d151-47d9-9e99-b4f3291c0322", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Task decomposition involves breaking down a complex task into smaller and simpler steps to make it more manageable. This process helps agents or models tackle difficult tasks by dividing them into more easily achievable subgoals. Task decomposition can be done through techniques like Chain of Thought or Tree of Thoughts, which guide the model in thinking step by step or exploring multiple reasoning possibilities at each step.'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conversational_rag_chain.invoke(\n", + " {\"input\": \"What is Task Decomposition?\"},\n", + " config={\n", + " \"configurable\": {\"session_id\": \"abc123\"}\n", + " }, # constructs a key \"abc123\" in `store`.\n", + ")[\"answer\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "17021822-896a-4513-a17d-1d20b1c5381c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Common ways of task decomposition include using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide models in breaking down complex tasks into smaller steps. This can be achieved through simple prompting with LLMs, task-specific instructions, or human inputs to help the model understand and navigate the task effectively. Task decomposition aims to enhance model performance on complex tasks by utilizing more test-time computation and shedding light on the model's thinking process.\"" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conversational_rag_chain.invoke(\n", + " {\"input\": \"What are common ways of doing it?\"},\n", + " config={\"configurable\": {\"session_id\": \"abc123\"}},\n", + ")[\"answer\"]" + ] + }, + { + "cell_type": "markdown", + "id": "861da8ed-d890-4fdc-a3bf-30433db61e0d", + "metadata": {}, + "source": [ + "## Agents {#agents}\n", + "\n", + "Agents leverage the reasoning capabilities of LLMs to make decisions during execution. Using agents allow you to offload some discretion over the retrieval process. Although their behavior is less predictable than chains, they offer some advantages in this context:\n", + "- Agents generate the input to the retriever directly, without necessarily needing us to explicitly build in contextualization, as we did above;\n", + "- Agents can execute multiple retrieval steps in service of a query, or refrain from executing a retrieval step altogether (e.g., in response to a generic greeting from a user).\n", + "\n", + "### Retrieval tool\n", + "\n", + "Agents can access \"tools\" and manage their execution. In this case, we will convert our retriever into a LangChain tool to be wielded by the agent:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "809cc747-2135-40a2-8e73-e4556343ee64", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.tools.retriever import create_retriever_tool\n", + "\n", + "tool = create_retriever_tool(\n", + " retriever,\n", + " \"blog_post_retriever\",\n", + " \"Searches and returns excerpts from the Autonomous Agents blog post.\",\n", + ")\n", + "tools = [tool]" + ] + }, + { + "cell_type": "markdown", + "id": "f77e0217-28be-4b8b-b4c4-9cc4ed5ec201", + "metadata": {}, + "source": [ + "### Agent constructor\n", + "\n", + "Now that we have defined the tools and the LLM, we can create the agent. We will be using [LangGraph](/docs/concepts/#langgraph) to construct the agent. \n", + "Currently we are using a high level interface to construct the agent, but the nice thing about LangGraph is that this high-level interface is backed by a low-level, highly controllable API in case you want to modify the agent logic." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1726d151-4653-4c72-a187-a14840add526", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt import chat_agent_executor\n", + "\n", + "agent_executor = chat_agent_executor.create_tool_calling_executor(llm, tools)" + ] + }, + { + "cell_type": "markdown", + "id": "6d5152ca-1c3b-4f58-bb28-f31c0be7ba66", + "metadata": {}, + "source": [ + "We can now try it out. Note that so far it is not stateful (we still need to add in memory)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "52ae46d9-43f7-481b-96d5-df750be3ad65", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_wxRrUmNbaNny8wh9JIb5uCRB', 'function': {'arguments': '{\"query\":\"Task Decomposition\"}', 'name': 'blog_post_retriever'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 68, 'total_tokens': 87}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-57ee0d12-6142-4957-a002-cce0093efe07-0', tool_calls=[{'name': 'blog_post_retriever', 'args': {'query': 'Task Decomposition'}, 'id': 'call_wxRrUmNbaNny8wh9JIb5uCRB'}])]}}\n", + "----\n", + "{'action': {'messages': [ToolMessage(content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.\\n\\n(3) Task execution: Expert models execute on the specific tasks and log results.\\nInstruction:\\n\\nWith the input and the inference results, the AI assistant needs to describe the process and results. The previous stages can be formed as - User Input: {{ User Input }}, Task Planning: {{ Tasks }}, Model Selection: {{ Model Assignment }}, Task Execution: {{ Predictions }}. You must first answer the user\\'s request in a straightforward manner. Then describe the task process and show your analysis and model inference results to the user in the first person. If inference results contain a file path, must tell the user the complete file path.\\n\\nFig. 11. Illustration of how HuggingGPT works. (Image source: Shen et al. 2023)\\nThe system comprises of 4 stages:\\n(1) Task planning: LLM works as the brain and parses the user requests into multiple tasks. There are four attributes associated with each task: task type, ID, dependencies, and arguments. They use few-shot examples to guide LLM to do task parsing and planning.\\nInstruction:', name='blog_post_retriever', id='9c3a17f7-653c-47fa-b4e4-fa3d8d24c85d', tool_call_id='call_wxRrUmNbaNny8wh9JIb5uCRB')]}}\n", + "----\n", + "{'agent': {'messages': [AIMessage(content='Task decomposition is a technique used to break down complex tasks into smaller and simpler steps. This approach helps agents in planning and executing tasks more effectively. One common method for task decomposition is the Chain of Thought (CoT) technique, where models are instructed to think step by step to decompose hard tasks into manageable steps. Another extension of CoT is the Tree of Thoughts, which explores multiple reasoning possibilities at each step by creating a tree structure of thought steps.\\n\\nTask decomposition can be achieved through various methods, such as using language models with simple prompting, task-specific instructions, or human inputs. By breaking down tasks into smaller components, agents can better plan and execute tasks efficiently.\\n\\nIf you would like more detailed information or examples on task decomposition, feel free to ask!', response_metadata={'token_usage': {'completion_tokens': 154, 'prompt_tokens': 588, 'total_tokens': 742}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, id='run-8991fa20-c527-4f9e-a058-fc6264fe6259-0')]}}\n", + "----\n" + ] + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "\n", + "query = \"What is Task Decomposition?\"\n", + "\n", + "for s in agent_executor.stream(\n", + " {\"messages\": [HumanMessage(content=query)]},\n", + "):\n", + " print(s)\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "1df703b1-aad6-48fb-b6fa-703e32ea88b9", + "metadata": {}, + "source": [ + "LangGraph comes with built in persistence, so we don't need to use ChatMessageHistory! Rather, we can pass in a checkpointer to our LangGraph agent directly.\n", + "\n", + "Distinct conversations are managed by specifying a key for a conversation thread in the config dict, as shown below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "837a401e-9757-4d0e-a0da-24fa097d887e", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.checkpoint.sqlite import SqliteSaver\n", + "\n", + "memory = SqliteSaver.from_conn_string(\":memory:\")\n", + "\n", + "agent_executor = chat_agent_executor.create_tool_calling_executor(\n", + " llm, tools, checkpointer=memory\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "02026f78-338e-4d18-9f05-131e1dd59197", + "metadata": {}, + "source": [ + "This is all we need to construct a conversational RAG agent.\n", + "\n", + "Let's observe its behavior. Note that if we input a query that does not require a retrieval step, the agent does not execute one:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "d6d70833-b958-4cd7-9e27-29c1c08bb1b8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'agent': {'messages': [AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 67, 'total_tokens': 78}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, id='run-1451e59b-b135-4776-985d-4759338ffee5-0')]}}\n", + "----\n" + ] + } + ], + "source": [ + "config = {\"configurable\": {\"thread_id\": \"abc123\"}}\n", + "\n", + "for s in agent_executor.stream(\n", + " {\"messages\": [HumanMessage(content=\"Hi! I'm bob\")]}, config=config\n", + "):\n", + " print(s)\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "a7928865-3dd6-4d36-abc6-2a30de770d09", + "metadata": {}, + "source": [ + "Further, if we input a query that does require a retrieval step, the agent generates the input to the tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "e2c570ae-dd91-402c-8693-ae746de63b16", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ab2x4iUPSWDAHS5txL7PspSK', 'function': {'arguments': '{\"query\":\"Task Decomposition\"}', 'name': 'blog_post_retriever'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 91, 'total_tokens': 110}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-f76b5813-b41c-4d0d-9ed2-667b988d885e-0', tool_calls=[{'name': 'blog_post_retriever', 'args': {'query': 'Task Decomposition'}, 'id': 'call_ab2x4iUPSWDAHS5txL7PspSK'}])]}}\n", + "----\n", + "{'action': {'messages': [ToolMessage(content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.\\n\\n(3) Task execution: Expert models execute on the specific tasks and log results.\\nInstruction:\\n\\nWith the input and the inference results, the AI assistant needs to describe the process and results. The previous stages can be formed as - User Input: {{ User Input }}, Task Planning: {{ Tasks }}, Model Selection: {{ Model Assignment }}, Task Execution: {{ Predictions }}. You must first answer the user\\'s request in a straightforward manner. Then describe the task process and show your analysis and model inference results to the user in the first person. If inference results contain a file path, must tell the user the complete file path.\\n\\nFig. 11. Illustration of how HuggingGPT works. (Image source: Shen et al. 2023)\\nThe system comprises of 4 stages:\\n(1) Task planning: LLM works as the brain and parses the user requests into multiple tasks. There are four attributes associated with each task: task type, ID, dependencies, and arguments. They use few-shot examples to guide LLM to do task parsing and planning.\\nInstruction:', name='blog_post_retriever', id='e0895fa5-5d41-4be0-98db-10a83d42fc2f', tool_call_id='call_ab2x4iUPSWDAHS5txL7PspSK')]}}\n", + "----\n", + "{'agent': {'messages': [AIMessage(content='Task decomposition is a technique used in complex tasks where the task is broken down into smaller and simpler steps. This approach helps in managing and solving difficult tasks by dividing them into more manageable components. One common method for task decomposition is the Chain of Thought (CoT) technique, which prompts the model to think step by step and decompose hard tasks into smaller steps. Another extension of CoT is the Tree of Thoughts, which explores multiple reasoning possibilities at each step by creating a tree structure of thought steps.\\n\\nTask decomposition can be achieved through various methods, such as using language models with simple prompting, task-specific instructions, or human inputs. By breaking down tasks into smaller components, agents can better plan and execute complex tasks effectively.\\n\\nIf you would like more detailed information or examples related to task decomposition, feel free to ask!', response_metadata={'token_usage': {'completion_tokens': 165, 'prompt_tokens': 611, 'total_tokens': 776}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, id='run-13296566-8577-4d65-982b-a39718988ca3-0')]}}\n", + "----\n" + ] + } + ], + "source": [ + "query = \"What is Task Decomposition?\"\n", + "\n", + "for s in agent_executor.stream(\n", + " {\"messages\": [HumanMessage(content=query)]}, config=config\n", + "):\n", + " print(s)\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "26eaae33-3c4e-49fc-9fc6-db8967e25579", + "metadata": {}, + "source": [ + "Above, instead of inserting our query verbatim into the tool, the agent stripped unnecessary words like \"what\" and \"is\".\n", + "\n", + "This same principle allows the agent to use the context of the conversation when necessary:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "570d8c68-136e-4ba5-969a-03ba195f6118", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_KvoiamnLfGEzMeEMlV3u0TJ7', 'function': {'arguments': '{\"query\":\"common ways of task decomposition\"}', 'name': 'blog_post_retriever'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 930, 'total_tokens': 951}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-dd842071-6dbd-4b68-8657-892eaca58638-0', tool_calls=[{'name': 'blog_post_retriever', 'args': {'query': 'common ways of task decomposition'}, 'id': 'call_KvoiamnLfGEzMeEMlV3u0TJ7'}])]}}\n", + "----\n", + "{'action': {'messages': [ToolMessage(content='Tree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.\\n\\nFig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nResources:\\n1. Internet access for searches and information gathering.\\n2. Long Term memory management.\\n3. GPT-3.5 powered Agents for delegation of simple tasks.\\n4. File output.\\n\\nPerformance Evaluation:\\n1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.\\n2. Constructively self-criticize your big-picture behavior constantly.\\n3. Reflect on past decisions and strategies to refine your approach.\\n4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.\\n\\n(3) Task execution: Expert models execute on the specific tasks and log results.\\nInstruction:\\n\\nWith the input and the inference results, the AI assistant needs to describe the process and results. The previous stages can be formed as - User Input: {{ User Input }}, Task Planning: {{ Tasks }}, Model Selection: {{ Model Assignment }}, Task Execution: {{ Predictions }}. You must first answer the user\\'s request in a straightforward manner. Then describe the task process and show your analysis and model inference results to the user in the first person. If inference results contain a file path, must tell the user the complete file path.', name='blog_post_retriever', id='c749bb8e-c8e0-4fa3-bc11-3e2e0651880b', tool_call_id='call_KvoiamnLfGEzMeEMlV3u0TJ7')]}}\n", + "----\n", + "{'agent': {'messages': [AIMessage(content='According to the blog post, common ways of task decomposition include:\\n\\n1. Using language models with simple prompting like \"Steps for XYZ\" or \"What are the subgoals for achieving XYZ?\"\\n2. Utilizing task-specific instructions, for example, using \"Write a story outline\" for writing a novel.\\n3. Involving human inputs in the task decomposition process.\\n\\nThese methods help in breaking down complex tasks into smaller and more manageable steps, facilitating better planning and execution of the overall task.', response_metadata={'token_usage': {'completion_tokens': 100, 'prompt_tokens': 1475, 'total_tokens': 1575}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, id='run-98b765b3-f1a6-4c9a-ad0f-2db7950b900f-0')]}}\n", + "----\n" + ] + } + ], + "source": [ + "query = \"What according to the blog post are common ways of doing it? redo the search\"\n", + "\n", + "for s in agent_executor.stream(\n", + " {\"messages\": [HumanMessage(content=query)]}, config=config\n", + "):\n", + " print(s)\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "f2724616-c106-4e15-a61a-3077c535f692", + "metadata": {}, + "source": [ + "Note that the agent was able to infer that \"it\" in our query refers to \"task decomposition\", and generated a reasonable search query as a result-- in this case, \"common ways of task decomposition\"." + ] + }, + { + "cell_type": "markdown", + "id": "1cf87847-23bb-4672-b41c-12ad9cf81ed4", + "metadata": {}, + "source": [ + "### Tying it together\n", + "\n", + "For convenience, we tie together all of the necessary steps in a single code cell:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "b1d2b4d4-e604-497d-873d-d345b808578e", + "metadata": {}, + "outputs": [], + "source": [ + "import bs4\n", + "from langchain.agents import AgentExecutor, create_tool_calling_agent\n", + "from langchain.tools.retriever import create_retriever_tool\n", + "from langchain_chroma import Chroma\n", + "from langchain_community.chat_message_histories import ChatMessageHistory\n", + "from langchain_community.document_loaders import WebBaseLoader\n", + "from langchain_core.chat_history import BaseChatMessageHistory\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "from langgraph.checkpoint.sqlite import SqliteSaver\n", + "\n", + "memory = SqliteSaver.from_conn_string(\":memory:\")\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "\n", + "\n", + "### Construct retriever ###\n", + "loader = WebBaseLoader(\n", + " web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n", + " bs_kwargs=dict(\n", + " parse_only=bs4.SoupStrainer(\n", + " class_=(\"post-content\", \"post-title\", \"post-header\")\n", + " )\n", + " ),\n", + ")\n", + "docs = loader.load()\n", + "\n", + "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n", + "splits = text_splitter.split_documents(docs)\n", + "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "\n", + "### Build retriever tool ###\n", + "tool = create_retriever_tool(\n", + " retriever,\n", + " \"blog_post_retriever\",\n", + " \"Searches and returns excerpts from the Autonomous Agents blog post.\",\n", + ")\n", + "tools = [tool]\n", + "\n", + "\n", + "agent_executor = chat_agent_executor.create_tool_calling_executor(\n", + " llm, tools, checkpointer=memory\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cd6bf4f4-74f4-419d-9e26-f0ed83cf05fa", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "We've covered the steps to build a basic conversational Q&A application:\n", + "\n", + "- We used chains to build a predictable application that generates search queries for each user input;\n", + "- We used agents to build an application that \"decides\" when and how to generate search queries.\n", + "\n", + "To explore different types of retrievers and retrieval strategies, visit the [retrievers](/docs/how_to#retrievers) section of the how-to guides.\n", + "\n", + "For a detailed walkthrough of LangChain's conversation memory abstractions, visit the [How to add message history (memory)](/docs/how_to/message_history) LCEL page.\n", + "\n", + "To learn more about agents, head to the [Agents Modules](/docs/tutorials/agents)." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/qa_citations.ipynb b/docs/versioned_docs/version-0.2.x/how_to/qa_citations.ipynb new file mode 100644 index 0000000000000..78b306f105b22 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/qa_citations.ipynb @@ -0,0 +1,1064 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1b79ff35-50a3-40cd-86d9-703f1f8cd2c5", + "metadata": {}, + "source": [ + "# How to get a RAG application to add citations\n", + "\n", + "This guide reviews methods to get a model to cite which parts of the source documents it referenced in generating its response.\n", + "\n", + "We will cover five methods:\n", + "\n", + "1. Using tool-calling to cite document IDs;\n", + "2. Using tool-calling to cite documents IDs and provide text snippets;\n", + "3. Direct prompting;\n", + "4. Retrieval post-processing (i.e., compressing the retrieved context to make it more relevant);\n", + "5. Generation post-processing (i.e., issuing a second LLM call to annotate a generated answer with citations).\n", + "\n", + "We generally suggest using the first item of the list that works for your use-case. That is, if your model supports tool-calling, try methods 1 or 2; otherwise, or if those fail, advance down the list.\n", + "\n", + "Let's first create a simple RAG chain. To start we'll just retrieve from Wikipedia using the [WikipediaRetriever](https://api.python.langchain.com/en/latest/retrievers/langchain_community.retrievers.wikipedia.WikipediaRetriever.html)." + ] + }, + { + "cell_type": "markdown", + "id": "8a70c423-f61f-4230-b70a-d3605b31afab", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First we'll need to install some dependencies and set environment vars for the models we'll be using." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f1d26ded-e8d5-4f80-86b9-26d464869175", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -qU langchain langchain-openai langchain-anthropic langchain-community wikipedia" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8732a85a-dd1a-483c-8da7-a81251276aa1", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "os.environ[\"ANTHROPIC_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Uncomment if you want to log to LangSmith\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "30a4401f-7feb-4bd9-9409-77c3859c4292", + "metadata": {}, + "source": [ + "Let's first select a LLM:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dd00165d-0b32-466d-8f75-ec26326a9e36", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4e17c3f6-8ce6-4767-b615-50a57c84c7b0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m System Message \u001b[0m================================\n", + "\n", + "You're a helpful AI assistant. Given a user question and some Wikipedia article snippets, answer the user question. If none of the articles answer the question, just say you don't know.\n", + "\n", + "Here are the Wikipedia articles: \u001b[33;1m\u001b[1;3m{context}\u001b[0m\n", + "\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{input}\u001b[0m\n" + ] + } + ], + "source": [ + "from langchain_community.retrievers import WikipediaRetriever\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "system_prompt = (\n", + " \"You're a helpful AI assistant. Given a user question \"\n", + " \"and some Wikipedia article snippets, answer the user \"\n", + " \"question. If none of the articles answer the question, \"\n", + " \"just say you don't know.\"\n", + " \"\\n\\nHere are the Wikipedia articles: \"\n", + " \"{context}\"\n", + ")\n", + "\n", + "retriever = WikipediaRetriever(top_k_results=6, doc_content_chars_max=2000)\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "prompt.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "c89e2045-9244-43e6-bf3f-59af22658529", + "metadata": {}, + "source": [ + "Now that we've got a model, retriver and prompt, let's chain them all together. We'll need to add some logic for formatting our retrieved Documents to a string that can be passed to our prompt. Following the how-to guide on [adding citations](/docs/how_to/qa_citations) to a RAG application, we'll make it so our chain returns both the answer and the retrieved Documents." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4cd55e1c-a6b7-44b7-9dde-5f42abe714ea", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "\n", + "from langchain_core.documents import Document\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "\n", + "def format_docs(docs: List[Document]):\n", + " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", + "\n", + "\n", + "rag_chain_from_docs = (\n", + " RunnablePassthrough.assign(context=(lambda x: format_docs(x[\"context\"])))\n", + " | prompt\n", + " | llm\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "retrieve_docs = (lambda x: x[\"input\"]) | retriever\n", + "\n", + "chain = RunnablePassthrough.assign(context=retrieve_docs).assign(\n", + " answer=rag_chain_from_docs\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "42b28717-d34c-42de-b923-155ac60529a2", + "metadata": {}, + "outputs": [], + "source": [ + "result = chain.invoke({\"input\": \"How fast are cheetahs?\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8b20cf8e-dccd-45d1-aef0-25f1ad1aca6d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dict_keys(['input', 'context', 'answer'])\n" + ] + } + ], + "source": [ + "print(result.keys())" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ae5ed9a7-c72a-480d-80c6-0a6bd38b9941", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "page_content='The cheetah (Acinonyx jubatus) is a large cat and the fastest land animal. It has a tawny to creamy white or pale buff fur that is marked with evenly spaced, solid black spots. The head is small and rounded, with a short snout and black tear-like facial streaks. It reaches 67–94 cm (26–37 in) at the shoulder, and the head-and-body length is between 1.1 and 1.5 m (3 ft 7 in and 4 ft 11 in). Adults weigh between 21 and 72 kg (46 and 159 lb). The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail.\\nThe cheetah was first described in the late 18th century. Four subspecies are recognised today that are native to Africa and central Iran. An African subspecies was introduced to India in 2022. It is now distributed mainly in small, fragmented populations in northwestern, eastern and southern Africa and central Iran. It lives in a variety of habitats such as savannahs in the Serengeti, arid mountain ranges in the Sahara, and hilly desert terrain.\\nThe cheetah lives in three main social groups: females and their cubs, male \"coalitions\", and solitary males. While females lead a nomadic life searching for prey in large home ranges, males are more sedentary and instead establish much smaller territories in areas with plentiful prey and access to females. The cheetah is active during the day, with peaks during dawn and dusk. It feeds on small- to medium-sized prey, mostly weighing under 40 kg (88 lb), and prefers medium-sized ungulates such as impala, springbok and Thomson\\'s gazelles. The cheetah typically stalks its prey within 60–100 m (200–330 ft) before charging towards it, trips it during the chase and bites its throat to suffocate it to death. It breeds throughout the year. After a gestation of nearly three months, females give birth to a litter of three or four cubs. Cheetah cubs are highly vulnerable to predation by other large carnivores. They are weaned a' metadata={'title': 'Cheetah', 'summary': 'The cheetah (Acinonyx jubatus) is a large cat and the fastest land animal. It has a tawny to creamy white or pale buff fur that is marked with evenly spaced, solid black spots. The head is small and rounded, with a short snout and black tear-like facial streaks. It reaches 67–94 cm (26–37 in) at the shoulder, and the head-and-body length is between 1.1 and 1.5 m (3 ft 7 in and 4 ft 11 in). Adults weigh between 21 and 72 kg (46 and 159 lb). The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail.\\nThe cheetah was first described in the late 18th century. Four subspecies are recognised today that are native to Africa and central Iran. An African subspecies was introduced to India in 2022. It is now distributed mainly in small, fragmented populations in northwestern, eastern and southern Africa and central Iran. It lives in a variety of habitats such as savannahs in the Serengeti, arid mountain ranges in the Sahara, and hilly desert terrain.\\nThe cheetah lives in three main social groups: females and their cubs, male \"coalitions\", and solitary males. While females lead a nomadic life searching for prey in large home ranges, males are more sedentary and instead establish much smaller territories in areas with plentiful prey and access to females. The cheetah is active during the day, with peaks during dawn and dusk. It feeds on small- to medium-sized prey, mostly weighing under 40 kg (88 lb), and prefers medium-sized ungulates such as impala, springbok and Thomson\\'s gazelles. The cheetah typically stalks its prey within 60–100 m (200–330 ft) before charging towards it, trips it during the chase and bites its throat to suffocate it to death. It breeds throughout the year. After a gestation of nearly three months, females give birth to a litter of three or four cubs. Cheetah cubs are highly vulnerable to predation by other large carnivores. They are weaned at around four months and are independent by around 20 months of age.\\nThe cheetah is threatened by habitat loss, conflict with humans, poaching and high susceptibility to diseases. In 2016, the global cheetah population was estimated at 7,100 individuals in the wild; it is listed as Vulnerable on the IUCN Red List. It has been widely depicted in art, literature, advertising, and animation. It was tamed in ancient Egypt and trained for hunting ungulates in the Arabian Peninsula and India. It has been kept in zoos since the early 19th century.', 'source': 'https://en.wikipedia.org/wiki/Cheetah'}\n" + ] + } + ], + "source": [ + "print(result[\"context\"][0])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "31f20897-0a7a-44e8-aeac-75d54f6e3789", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cheetahs are capable of running at speeds of 93 to 104 km/h (58 to 65 mph). They have evolved specialized adaptations for speed, including a light build, long thin legs, and a long tail.\n" + ] + } + ], + "source": [ + "print(result[\"answer\"])" + ] + }, + { + "cell_type": "markdown", + "id": "0f1f9a49-8f3f-44dd-98df-0218b5fb93a6", + "metadata": {}, + "source": [ + "LangSmith trace: https://smith.langchain.com/public/0472c5d1-49dc-4c1c-8100-61910067d7ed/r" + ] + }, + { + "cell_type": "markdown", + "id": "a7619ba1-33bd-48bf-8637-be409c94037f", + "metadata": {}, + "source": [ + "## Function-calling\n", + "\n", + "If your LLM of choice implements a [tool-calling](/docs/concepts#functiontool-calling) feature, you can use it to make the model specify which of the provided documents it's referencing when generating its answer. LangChain tool-calling models implement a `.with_structured_output` method which will force generation adhering to a desired schema (see for example [here](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI.with_structured_output)).\n", + "\n", + "### Cite documents\n", + "\n", + "To cite documents using an identifier, we format the identifiers into the prompt, then use `.with_structured_output` to coerce the LLM to reference these identifiers in its output.\n", + "\n", + "First we define a schema for the output. The `.with_structured_output` supports multiple formats, including JSON schema and Pydantic. Here we will use Pydantic:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0af2c3a1-870c-428e-95da-0c2fd04d5616", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class CitedAnswer(BaseModel):\n", + " \"\"\"Answer the user question based only on the given sources, and cite the sources used.\"\"\"\n", + "\n", + " answer: str = Field(\n", + " ...,\n", + " description=\"The answer to the user question, which is based only on the given sources.\",\n", + " )\n", + " citations: List[int] = Field(\n", + " ...,\n", + " description=\"The integer IDs of the SPECIFIC sources which justify the answer.\",\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "68b95186-faf5-46f1-8715-ebbc38207d5d", + "metadata": {}, + "source": [ + "Let's see what the model output is like when we pass in our functions and a user input:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "2e2b7a87-3642-4ed8-9445-684daa93b0d7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CitedAnswer(answer='Brian\\'s height is 5\\'11\".', citations=[1, 3])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "structured_llm = llm.with_structured_output(CitedAnswer)\n", + "\n", + "example_q = \"\"\"What Brian's height?\n", + "\n", + "Source: 1\n", + "Information: Suzy is 6'2\"\n", + "\n", + "Source: 2\n", + "Information: Jeremiah is blonde\n", + "\n", + "Source: 3\n", + "Information: Brian is 3 inches shorter than Suzy\"\"\"\n", + "result = structured_llm.invoke(example_q)\n", + "\n", + "result" + ] + }, + { + "cell_type": "markdown", + "id": "7b847b53-987e-4d3a-9621-77e613d49cfd", + "metadata": {}, + "source": [ + "Or as a dict:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3ee49bbd-567f-41cc-8798-d5aad0fe1cea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'answer': 'Brian\\'s height is 5\\'11\".', 'citations': [1, 3]}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result.dict()" + ] + }, + { + "cell_type": "markdown", + "id": "bb8bbbb5-2afc-401f-a140-648c3d2c4522", + "metadata": {}, + "source": [ + "Now we structure the source identifiers into the prompt to replicate with our chain. We will make three changes:\n", + "\n", + "1. Update the prompt to include source identifiers;\n", + "2. Use the `structured_llm` (i.e., `llm.with_structured_output(CitedAnswer));\n", + "3. Remove the `StrOutputParser`, to retain the Pydantic object in the output." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3cb835f3-3cf5-4144-bf6b-24558b9faf31", + "metadata": {}, + "outputs": [], + "source": [ + "def format_docs_with_id(docs: List[Document]) -> str:\n", + " formatted = [\n", + " f\"Source ID: {i}\\nArticle Title: {doc.metadata['title']}\\nArticle Snippet: {doc.page_content}\"\n", + " for i, doc in enumerate(docs)\n", + " ]\n", + " return \"\\n\\n\" + \"\\n\\n\".join(formatted)\n", + "\n", + "\n", + "rag_chain_from_docs = (\n", + " RunnablePassthrough.assign(context=(lambda x: format_docs_with_id(x[\"context\"])))\n", + " | prompt\n", + " | structured_llm\n", + ")\n", + "\n", + "retrieve_docs = (lambda x: x[\"input\"]) | retriever\n", + "\n", + "chain = RunnablePassthrough.assign(context=retrieve_docs).assign(\n", + " answer=rag_chain_from_docs\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "3e259b2f-5147-4c3c-9c26-b4eb8143e5f0", + "metadata": {}, + "outputs": [], + "source": [ + "result = chain.invoke({\"input\": \"How fast are cheetahs?\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2d8d2a01-608d-479f-85f1-eb8d14b11bc2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "answer='Cheetahs can run at speeds of 93 to 104 km/h (58 to 65 mph). They are known as the fastest land animals.' citations=[0]\n" + ] + } + ], + "source": [ + "print(result[\"answer\"])" + ] + }, + { + "cell_type": "markdown", + "id": "da8341f5-a48a-4c07-8445-a313e20c36a2", + "metadata": {}, + "source": [ + "We can inspect the document at index 0, which the model cited:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "02d19f2b-2e15-492f-b44b-577990d15a86", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "page_content='The cheetah (Acinonyx jubatus) is a large cat and the fastest land animal. It has a tawny to creamy white or pale buff fur that is marked with evenly spaced, solid black spots. The head is small and rounded, with a short snout and black tear-like facial streaks. It reaches 67–94 cm (26–37 in) at the shoulder, and the head-and-body length is between 1.1 and 1.5 m (3 ft 7 in and 4 ft 11 in). Adults weigh between 21 and 72 kg (46 and 159 lb). The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail.\\nThe cheetah was first described in the late 18th century. Four subspecies are recognised today that are native to Africa and central Iran. An African subspecies was introduced to India in 2022. It is now distributed mainly in small, fragmented populations in northwestern, eastern and southern Africa and central Iran. It lives in a variety of habitats such as savannahs in the Serengeti, arid mountain ranges in the Sahara, and hilly desert terrain.\\nThe cheetah lives in three main social groups: females and their cubs, male \"coalitions\", and solitary males. While females lead a nomadic life searching for prey in large home ranges, males are more sedentary and instead establish much smaller territories in areas with plentiful prey and access to females. The cheetah is active during the day, with peaks during dawn and dusk. It feeds on small- to medium-sized prey, mostly weighing under 40 kg (88 lb), and prefers medium-sized ungulates such as impala, springbok and Thomson\\'s gazelles. The cheetah typically stalks its prey within 60–100 m (200–330 ft) before charging towards it, trips it during the chase and bites its throat to suffocate it to death. It breeds throughout the year. After a gestation of nearly three months, females give birth to a litter of three or four cubs. Cheetah cubs are highly vulnerable to predation by other large carnivores. They are weaned a' metadata={'title': 'Cheetah', 'summary': 'The cheetah (Acinonyx jubatus) is a large cat and the fastest land animal. It has a tawny to creamy white or pale buff fur that is marked with evenly spaced, solid black spots. The head is small and rounded, with a short snout and black tear-like facial streaks. It reaches 67–94 cm (26–37 in) at the shoulder, and the head-and-body length is between 1.1 and 1.5 m (3 ft 7 in and 4 ft 11 in). Adults weigh between 21 and 72 kg (46 and 159 lb). The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail.\\nThe cheetah was first described in the late 18th century. Four subspecies are recognised today that are native to Africa and central Iran. An African subspecies was introduced to India in 2022. It is now distributed mainly in small, fragmented populations in northwestern, eastern and southern Africa and central Iran. It lives in a variety of habitats such as savannahs in the Serengeti, arid mountain ranges in the Sahara, and hilly desert terrain.\\nThe cheetah lives in three main social groups: females and their cubs, male \"coalitions\", and solitary males. While females lead a nomadic life searching for prey in large home ranges, males are more sedentary and instead establish much smaller territories in areas with plentiful prey and access to females. The cheetah is active during the day, with peaks during dawn and dusk. It feeds on small- to medium-sized prey, mostly weighing under 40 kg (88 lb), and prefers medium-sized ungulates such as impala, springbok and Thomson\\'s gazelles. The cheetah typically stalks its prey within 60–100 m (200–330 ft) before charging towards it, trips it during the chase and bites its throat to suffocate it to death. It breeds throughout the year. After a gestation of nearly three months, females give birth to a litter of three or four cubs. Cheetah cubs are highly vulnerable to predation by other large carnivores. They are weaned at around four months and are independent by around 20 months of age.\\nThe cheetah is threatened by habitat loss, conflict with humans, poaching and high susceptibility to diseases. In 2016, the global cheetah population was estimated at 7,100 individuals in the wild; it is listed as Vulnerable on the IUCN Red List. It has been widely depicted in art, literature, advertising, and animation. It was tamed in ancient Egypt and trained for hunting ungulates in the Arabian Peninsula and India. It has been kept in zoos since the early 19th century.', 'source': 'https://en.wikipedia.org/wiki/Cheetah'}\n" + ] + } + ], + "source": [ + "print(result[\"context\"][0])" + ] + }, + { + "cell_type": "markdown", + "id": "94f2898a-ef4d-423a-b002-910fef7a65c9", + "metadata": {}, + "source": [ + "LangSmith trace: https://smith.langchain.com/public/aff39dc7-3e09-4d64-8083-87026d975534/r" + ] + }, + { + "cell_type": "markdown", + "id": "fdbd1407-8a5b-4c35-aa2b-9d26424edb93", + "metadata": {}, + "source": [ + "### Cite snippets\n", + "\n", + "To return text spans (perhaps in addition to source identifiers), we can use the same approach. The only change will be to build a more complex output schema, here using Pydantic, that includes a \"quote\" alongside a source identifier.\n", + "\n", + "*Aside: Note that if we break up our documents so that we have many documents with only a sentence or two instead of a few long documents, citing documents becomes roughly equivalent to citing snippets, and may be easier for the model because the model just needs to return an identifier for each snippet instead of the actual text. Probably worth trying both approaches and evaluating.*" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "fbf708aa-e8ac-4dea-bb57-82229597e2e0", + "metadata": {}, + "outputs": [], + "source": [ + "class Citation(BaseModel):\n", + " source_id: int = Field(\n", + " ...,\n", + " description=\"The integer ID of a SPECIFIC source which justifies the answer.\",\n", + " )\n", + " quote: str = Field(\n", + " ...,\n", + " description=\"The VERBATIM quote from the specified source that justifies the answer.\",\n", + " )\n", + "\n", + "\n", + "class QuotedAnswer(BaseModel):\n", + " \"\"\"Answer the user question based only on the given sources, and cite the sources used.\"\"\"\n", + "\n", + " answer: str = Field(\n", + " ...,\n", + " description=\"The answer to the user question, which is based only on the given sources.\",\n", + " )\n", + " citations: List[Citation] = Field(\n", + " ..., description=\"Citations from the given sources that justify the answer.\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "beabab7b-7b6b-4eef-b874-e92d1ed8707c", + "metadata": {}, + "outputs": [], + "source": [ + "rag_chain_from_docs = (\n", + " RunnablePassthrough.assign(context=(lambda x: format_docs_with_id(x[\"context\"])))\n", + " | prompt\n", + " | llm.with_structured_output(QuotedAnswer)\n", + ")\n", + "\n", + "retrieve_docs = (lambda x: x[\"input\"]) | retriever\n", + "\n", + "chain = RunnablePassthrough.assign(context=retrieve_docs).assign(\n", + " answer=rag_chain_from_docs\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "9709ee6d-416f-4bd3-89c6-23667b9f3cca", + "metadata": {}, + "outputs": [], + "source": [ + "result = chain.invoke({\"input\": \"How fast are cheetahs?\"})" + ] + }, + { + "cell_type": "markdown", + "id": "b42ba8c6-4214-49f5-b920-f0e028f301c2", + "metadata": {}, + "source": [ + "Here we see that the model has extracted a relevant snippet of text from source 0:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "56b01963-8680-4782-9c3f-384c197f0c2d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "QuotedAnswer(answer='Cheetahs can run at speeds of 93 to 104 km/h (58 to 65 mph).', citations=[Citation(source_id=0, quote='The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail.')])" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result[\"answer\"]" + ] + }, + { + "cell_type": "markdown", + "id": "28676cf1-4a2e-44d2-8b2f-36303a12a371", + "metadata": {}, + "source": [ + "LangSmith trace: https://smith.langchain.com/public/0f638cc9-8409-4a53-9010-86ac28144129/r" + ] + }, + { + "cell_type": "markdown", + "id": "fb2d90a4-0370-4598-9f4b-e8e9a554346e", + "metadata": {}, + "source": [ + "## Direct prompting\n", + "\n", + "Many models don't support function-calling. We can achieve similar results with direct prompting. Let's try instructing a model to generate structured XML for its output:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "4e95bd8a-2f15-4e20-a1d9-225974b8d598", + "metadata": {}, + "outputs": [], + "source": [ + "xml_system = \"\"\"You're a helpful AI assistant. Given a user question and some Wikipedia article snippets, \\\n", + "answer the user question and provide citations. If none of the articles answer the question, just say you don't know.\n", + "\n", + "Remember, you must return both an answer and citations. A citation consists of a VERBATIM quote that \\\n", + "justifies the answer and the ID of the quote article. Return a citation for every quote across all articles \\\n", + "that justify the answer. Use the following format for your final output:\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " ...\n", + " \n", + "\n", + "\n", + "Here are the Wikipedia articles:{context}\"\"\"\n", + "xml_prompt = ChatPromptTemplate.from_messages(\n", + " [(\"system\", xml_system), (\"human\", \"{input}\")]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2d3bd0f7-e249-4bc6-bd46-6fb74ebf0118", + "metadata": {}, + "source": [ + "We now make similar small updates to our chain:\n", + "\n", + "1. We update the formatting function to wrap the retrieved context in XML tags;\n", + "2. We do not use `.with_structured_output` (e.g., because it does not exist for a model);\n", + "3. We use [XMLOutputParser](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.xml.XMLOutputParser.html) in place of `StrOutputParser` to parse the answer into a dict." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "5861ca8c-63b7-4918-bdc6-fe4e53fe03ca", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers import XMLOutputParser\n", + "\n", + "\n", + "def format_docs_xml(docs: List[Document]) -> str:\n", + " formatted = []\n", + " for i, doc in enumerate(docs):\n", + " doc_str = f\"\"\"\\\n", + " \n", + " {doc.metadata['title']}\n", + " {doc.page_content}\n", + " \"\"\"\n", + " formatted.append(doc_str)\n", + " return \"\\n\\n\" + \"\\n\".join(formatted) + \"\"\n", + "\n", + "\n", + "rag_chain_from_docs = (\n", + " RunnablePassthrough.assign(context=(lambda x: format_docs_xml(x[\"context\"])))\n", + " | xml_prompt\n", + " | llm\n", + " | XMLOutputParser()\n", + ")\n", + "\n", + "retrieve_docs = (lambda x: x[\"input\"]) | retriever\n", + "\n", + "chain = RunnablePassthrough.assign(context=retrieve_docs).assign(\n", + " answer=rag_chain_from_docs\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "f1edb401-6027-4112-82ec-25736e8ebabd", + "metadata": {}, + "outputs": [], + "source": [ + "result = chain.invoke({\"input\": \"How fast are cheetahs?\"})" + ] + }, + { + "cell_type": "markdown", + "id": "e5264571-48c2-492d-a750-640f9fff3e71", + "metadata": {}, + "source": [ + "Note that citations are again structured into the answer:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "a2b4bdc9-92dd-434c-b61c-11ec44c92905", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'cited_answer': [{'answer': 'Cheetahs are capable of running at 93 to 104 km/h (58 to 65 mph).'},\n", + " {'citations': [{'citation': [{'source_id': '0'},\n", + " {'quote': 'The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail.'}]}]}]}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result[\"answer\"]" + ] + }, + { + "cell_type": "markdown", + "id": "940db8d5-8f43-44dd-9738-04fc7464baac", + "metadata": {}, + "source": [ + "LangSmith trace: https://smith.langchain.com/public/a3636c70-39c6-4c8f-bc83-1c7a174c237e/r" + ] + }, + { + "cell_type": "markdown", + "id": "9d4180b0-5d29-4bfa-85be-2a6161a872c4", + "metadata": {}, + "source": [ + "## Retrieval post-processing\n", + "\n", + "Another approach is to post-process our retrieved documents to compress the content, so that the source content is already minimal enough that we don't need the model to cite specific sources or spans. For example, we could break up each document into a sentence or two, embed those and keep only the most relevant ones. LangChain has some built-in components for this. Here we'll use a [RecursiveCharacterTextSplitter](https://api.python.langchain.com/en/latest/text_splitter/langchain_text_splitters.RecursiveCharacterTextSplitter.html#langchain_text_splitters.RecursiveCharacterTextSplitter), which creates chunks of a sepacified size by splitting on separator substrings, and an [EmbeddingsFilter](https://api.python.langchain.com/en/latest/retrievers/langchain.retrievers.document_compressors.embeddings_filter.EmbeddingsFilter.html#langchain.retrievers.document_compressors.embeddings_filter.EmbeddingsFilter), which keeps only the texts with the most relevant embeddings.\n", + "\n", + "This approach effectively swaps our original retriever with an updated one that compresses the documents. To start, we build the retriever:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "9b14f817-4454-47b2-9eb0-2b8783a8c252", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Adults weigh between 21 and 72 kg (46 and 159 lb). The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail\n", + "\n", + "\n", + "\n", + "The cheetah (Acinonyx jubatus) is a large cat and the fastest land animal. It has a tawny to creamy white or pale buff fur that is marked with evenly spaced, solid black spots. The head is small and rounded, with a short snout and black tear-like facial streaks. It reaches 67–94 cm (26–37 in) at the shoulder, and the head-and-body length is between 1.1 and 1.5 m (3 ft 7 in and 4 ft 11 in)\n", + "\n", + "\n", + "\n", + "2 mph), or 171 body lengths per second. The cheetah, the fastest land mammal, scores at only 16 body lengths per second, while Anna's hummingbird has the highest known length-specific velocity attained by any vertebrate\n", + "\n", + "\n", + "\n", + "It feeds on small- to medium-sized prey, mostly weighing under 40 kg (88 lb), and prefers medium-sized ungulates such as impala, springbok and Thomson's gazelles. The cheetah typically stalks its prey within 60–100 m (200–330 ft) before charging towards it, trips it during the chase and bites its throat to suffocate it to death. It breeds throughout the year\n", + "\n", + "\n", + "\n", + "The cheetah was first described in the late 18th century. Four subspecies are recognised today that are native to Africa and central Iran. An African subspecies was introduced to India in 2022. It is now distributed mainly in small, fragmented populations in northwestern, eastern and southern Africa and central Iran\n", + "\n", + "\n", + "\n", + "The cheetah lives in three main social groups: females and their cubs, male \"coalitions\", and solitary males. While females lead a nomadic life searching for prey in large home ranges, males are more sedentary and instead establish much smaller territories in areas with plentiful prey and access to females. The cheetah is active during the day, with peaks during dawn and dusk\n", + "\n", + "\n", + "\n", + "The Southeast African cheetah (Acinonyx jubatus jubatus) is the nominate cheetah subspecies native to East and Southern Africa. The Southern African cheetah lives mainly in the lowland areas and deserts of the Kalahari, the savannahs of Okavango Delta, and the grasslands of the Transvaal region in South Africa. In Namibia, cheetahs are mostly found in farmlands\n", + "\n", + "\n", + "\n", + "Subpopulations have been called \"South African cheetah\" and \"Namibian cheetah.\"\n", + "\n", + "\n", + "\n", + "In India, four cheetahs of the subspecies are living in Kuno National Park in Madhya Pradesh after having been introduced there\n", + "\n", + "\n", + "\n", + "Acinonyx jubatus velox proposed in 1913 by Edmund Heller on basis of a cheetah that was shot by Kermit Roosevelt in June 1909 in the Kenyan highlands.\n", + "Acinonyx rex proposed in 1927 by Reginald Innes Pocock on basis of a specimen from the Umvukwe Range in Rhodesia.\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "from langchain.retrievers.document_compressors import EmbeddingsFilter\n", + "from langchain_core.runnables import RunnableParallel\n", + "from langchain_openai import OpenAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "splitter = RecursiveCharacterTextSplitter(\n", + " chunk_size=400,\n", + " chunk_overlap=0,\n", + " separators=[\"\\n\\n\", \"\\n\", \".\", \" \"],\n", + " keep_separator=False,\n", + ")\n", + "compressor = EmbeddingsFilter(embeddings=OpenAIEmbeddings(), k=10)\n", + "\n", + "\n", + "def split_and_filter(input) -> List[Document]:\n", + " docs = input[\"docs\"]\n", + " question = input[\"question\"]\n", + " split_docs = splitter.split_documents(docs)\n", + " stateful_docs = compressor.compress_documents(split_docs, question)\n", + " return [stateful_doc for stateful_doc in stateful_docs]\n", + "\n", + "\n", + "new_retriever = (\n", + " RunnableParallel(question=RunnablePassthrough(), docs=retriever) | split_and_filter\n", + ")\n", + "docs = new_retriever.invoke(\"How fast are cheetahs?\")\n", + "for doc in docs:\n", + " print(doc.page_content)\n", + " print(\"\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "984bc1e1-76fb-4d84-baa9-5fa5abca9da4", + "metadata": {}, + "source": [ + "Next, we assemble it into our chain as before:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "fa2adb01-5d8f-484c-8216-bae35717db0d", + "metadata": {}, + "outputs": [], + "source": [ + "rag_chain_from_docs = (\n", + " RunnablePassthrough.assign(context=(lambda x: format_docs(x[\"context\"])))\n", + " | prompt\n", + " | llm\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "chain = RunnablePassthrough.assign(\n", + " context=(lambda x: x[\"input\"]) | new_retriever\n", + ").assign(answer=rag_chain_from_docs)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "1a5b72f8-135b-4604-8777-59f2ef682323", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cheetahs are capable of running at speeds between 93 to 104 km/h (58 to 65 mph), making them the fastest land animals.\n" + ] + } + ], + "source": [ + "result = chain.invoke({\"input\": \"How fast are cheetahs?\"})\n", + "\n", + "print(result[\"answer\"])" + ] + }, + { + "cell_type": "markdown", + "id": "d9ac43ab-db4f-458a-9b5a-fd3e116229bd", + "metadata": {}, + "source": [ + "Note that the document content is now compressed, although the document objects retain the original content in a \"summary\" key in their metadata. These summaries are not passed to the model; only the condensed content is." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "80625506-8764-4adf-a467-33f465d0f51f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Adults weigh between 21 and 72 kg (46 and 159 lb). The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail'" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result[\"context\"][0].page_content # passed to model" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "672c5691-5d54-4271-9d97-93571eebda91", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'The cheetah (Acinonyx jubatus) is a large cat and the fastest land animal. It has a tawny to creamy white or pale buff fur that is marked with evenly spaced, solid black spots. The head is small and rounded, with a short snout and black tear-like facial streaks. It reaches 67–94 cm (26–37 in) at the shoulder, and the head-and-body length is between 1.1 and 1.5 m (3 ft 7 in and 4 ft 11 in). Adults weigh between 21 and 72 kg (46 and 159 lb). The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail.\\nThe cheetah was first described in the late 18th century. Four subspecies are recognised today that are native to Africa and central Iran. An African subspecies was introduced to India in 2022. It is now distributed mainly in small, fragmented populations in northwestern, eastern and southern Africa and central Iran. It lives in a variety of habitats such as savannahs in the Serengeti, arid mountain ranges in the Sahara, and hilly desert terrain.\\nThe cheetah lives in three main social groups: females and their cubs, male \"coalitions\", and solitary males. While females lead a nomadic life searching for prey in large home ranges, males are more sedentary and instead establish much smaller territories in areas with plentiful prey and access to females. The cheetah is active during the day, with peaks during dawn and dusk. It feeds on small- to medium-sized prey, mostly weighing under 40 kg (88 lb), and prefers medium-sized ungulates such as impala, springbok and Thomson\\'s gazelles. The cheetah typically stalks its prey within 60–100 m (200–330 ft) before charging towards it, trips it during the chase and bites its throat to suffocate it to death. It breeds throughout the year. After a gestation of nearly three months, females give birth to a litter of three or four cubs. Cheetah cubs are highly vulnerable to predation by other large carnivores. They are weaned at around four months and are independent by around 20 months of age.\\nThe cheetah is threatened by habitat loss, conflict with humans, poaching and high susceptibility to diseases. In 2016, the global cheetah population was estimated at 7,100 individuals in the wild; it is listed as Vulnerable on the IUCN Red List. It has been widely depicted in art, literature, advertising, and animation. It was tamed in ancient Egypt and trained for hunting ungulates in the Arabian Peninsula and India. It has been kept in zoos since the early 19th century.'" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result[\"context\"][0].metadata[\"summary\"] # original document" + ] + }, + { + "cell_type": "markdown", + "id": "88ab8fd6-f6b4-4ba5-b022-f10cca983490", + "metadata": {}, + "source": [ + "LangSmith trace: https://smith.langchain.com/public/a61304fa-e5a5-4c64-a268-b0aef1130d53/r" + ] + }, + { + "cell_type": "markdown", + "id": "445722dc-2ecb-45a4-9d4d-c172d0a2fa7d", + "metadata": {}, + "source": [ + "## Generation post-processing\n", + "\n", + "Another approach is to post-process our model generation. In this example we'll first generate just an answer, and then we'll ask the model to annotate it's own answer with citations. The downside of this approach is of course that it is slower and more expensive, because two model calls need to be made.\n", + "\n", + "Let's apply this to our initial chain." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "daff5cb9-7639-4d30-b6e7-d795736a2b58", + "metadata": {}, + "outputs": [], + "source": [ + "class Citation(BaseModel):\n", + " source_id: int = Field(\n", + " ...,\n", + " description=\"The integer ID of a SPECIFIC source which justifies the answer.\",\n", + " )\n", + " quote: str = Field(\n", + " ...,\n", + " description=\"The VERBATIM quote from the specified source that justifies the answer.\",\n", + " )\n", + "\n", + "\n", + "class AnnotatedAnswer(BaseModel):\n", + " \"\"\"Annotate the answer to the user question with quote citations that justify the answer.\"\"\"\n", + "\n", + " citations: List[Citation] = Field(\n", + " ..., description=\"Citations from the given sources that justify the answer.\"\n", + " )\n", + "\n", + "\n", + "structured_llm = llm.with_structured_output(AnnotatedAnswer)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "6f505eb9-db02-4c49-add3-1e469844d7ca", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import MessagesPlaceholder\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " (\"human\", \"{question}\"),\n", + " MessagesPlaceholder(\"chat_history\", optional=True),\n", + " ]\n", + ")\n", + "answer = prompt | llm\n", + "annotation_chain = prompt | structured_llm\n", + "\n", + "chain = (\n", + " RunnableParallel(\n", + " question=RunnablePassthrough(), docs=(lambda x: x[\"input\"]) | retriever\n", + " )\n", + " .assign(context=format)\n", + " .assign(ai_message=answer)\n", + " .assign(\n", + " chat_history=(lambda x: [x[\"ai_message\"]]),\n", + " answer=(lambda x: x[\"ai_message\"].content),\n", + " )\n", + " .assign(annotations=annotation_chain)\n", + " .pick([\"answer\", \"docs\", \"annotations\"])\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "eb11c422-09b3-4d5a-87eb-3bad2e73cf6c", + "metadata": {}, + "outputs": [], + "source": [ + "result = chain.invoke({\"input\": \"How fast are cheetahs?\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "5b8bbc02-f753-4abc-87ec-211aac3dc3d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cheetahs are capable of running at speeds between 93 to 104 km/h (58 to 65 mph). Their specialized adaptations for speed, such as a light build, long thin legs, and a long tail, allow them to be the fastest land animals.\n" + ] + } + ], + "source": [ + "print(result[\"answer\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "c7882b76-db21-40ee-bb31-ff438880adf6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AnnotatedAnswer(citations=[Citation(source_id=0, quote='The cheetah is capable of running at 93 to 104 km/h (58 to 65 mph); it has evolved specialized adaptations for speed, including a light build, long thin legs and a long tail.')])" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result[\"annotations\"]" + ] + }, + { + "cell_type": "markdown", + "id": "803c6155-48af-40db-b4b0-1ecc5328e99b", + "metadata": {}, + "source": [ + "LangSmith trace: https://smith.langchain.com/public/bf5e8856-193b-4ff2-af8d-c0f4fbd1d9cb/r" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/qa_per_user.ipynb b/docs/versioned_docs/version-0.2.x/how_to/qa_per_user.ipynb new file mode 100644 index 0000000000000..bf949670701e5 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/qa_per_user.ipynb @@ -0,0 +1,319 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "14d3fd06", + "metadata": {}, + "source": [ + "# How to do per-user retrieval\n", + "\n", + "This guide demonstrates how to configure runtime properties of a retrieval chain. An example application is to limit the documents available to a retriever based on the user.\n", + "\n", + "When building a retrieval app, you often have to build it with multiple users in mind. This means that you may be storing data not just for one user, but for many different users, and they should not be able to see eachother's data. This means that you need to be able to configure your retrieval chain to only retrieve certain information. This generally involves two steps.\n", + "\n", + "**Step 1: Make sure the retriever you are using supports multiple users**\n", + "\n", + "At the moment, there is no unified flag or filter for this in LangChain. Rather, each vectorstore and retriever may have their own, and may be called different things (namespaces, multi-tenancy, etc). For vectorstores, this is generally exposed as a keyword argument that is passed in during `similarity_search`. By reading the documentation or source code, figure out whether the retriever you are using supports multiple users, and, if so, how to use it.\n", + "\n", + "Note: adding documentation and/or support for multiple users for retrievers that do not support it (or document it) is a GREAT way to contribute to LangChain\n", + "\n", + "**Step 2: Add that parameter as a configurable field for the chain**\n", + "\n", + "This will let you easily call the chain and configure any relevant flags at runtime. See [this documentation](/docs/how_to/configure) for more information on configuration.\n", + "\n", + "Now, at runtime you can call this chain with configurable field.\n", + "\n", + "## Code Example\n", + "\n", + "Let's see a concrete example of what this looks like in code. We will use Pinecone for this example.\n", + "\n", + "To configure Pinecone, set the following environment variable:\n", + "\n", + "- `PINECONE_API_KEY`: Your Pinecone API key" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7345de3c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ce15571e-4e2f-44c9-98df-7e83f6f63095']" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_openai import OpenAIEmbeddings\n", + "from langchain_pinecone import PineconeVectorStore\n", + "\n", + "embeddings = OpenAIEmbeddings()\n", + "vectorstore = PineconeVectorStore(index_name=\"test-example\", embedding=embeddings)\n", + "\n", + "vectorstore.add_texts([\"i worked at kensho\"], namespace=\"harrison\")\n", + "vectorstore.add_texts([\"i worked at facebook\"], namespace=\"ankush\")" + ] + }, + { + "cell_type": "markdown", + "id": "39c11920", + "metadata": {}, + "source": [ + "The pinecone kwarg for `namespace` can be used to separate documents" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3c2a39fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='i worked at facebook')]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This will only get documents for Ankush\n", + "vectorstore.as_retriever(search_kwargs={\"namespace\": \"ankush\"}).get_relevant_documents(\n", + " \"where did i work?\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "56393baa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='i worked at kensho')]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This will only get documents for Harrison\n", + "vectorstore.as_retriever(\n", + " search_kwargs={\"namespace\": \"harrison\"}\n", + ").get_relevant_documents(\"where did i work?\")" + ] + }, + { + "cell_type": "markdown", + "id": "88ae97ed", + "metadata": {}, + "source": [ + "We can now create the chain that we will use to do question-answering over.\n", + "\n", + "Let's first select a LLM.\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68162d05", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI()" + ] + }, + { + "cell_type": "markdown", + "id": "b6778ffa", + "metadata": {}, + "source": [ + "This is basic question-answering chain set up." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "44a865f6", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import (\n", + " ConfigurableField,\n", + " RunnablePassthrough,\n", + ")\n", + "\n", + "template = \"\"\"Answer the question based only on the following context:\n", + "{context}\n", + "Question: {question}\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", + "\n", + "retriever = vectorstore.as_retriever()" + ] + }, + { + "cell_type": "markdown", + "id": "72125166", + "metadata": {}, + "source": [ + "Here we mark the retriever as having a configurable field. All vectorstore retrievers have `search_kwargs` as a field. This is just a dictionary, with vectorstore specific fields.\n", + "\n", + "This will let us pass in a value for `search_kwargs` when invoking the chain." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "babbadff", + "metadata": {}, + "outputs": [], + "source": [ + "configurable_retriever = retriever.configurable_fields(\n", + " search_kwargs=ConfigurableField(\n", + " id=\"search_kwargs\",\n", + " name=\"Search Kwargs\",\n", + " description=\"The search kwargs to use\",\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2d481b70", + "metadata": {}, + "source": [ + "We can now create the chain using our configurable retriever" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "210b0446", + "metadata": {}, + "outputs": [], + "source": [ + "chain = (\n", + " {\"context\": configurable_retriever, \"question\": RunnablePassthrough()}\n", + " | prompt\n", + " | llm\n", + " | StrOutputParser()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "7f6458c3", + "metadata": {}, + "source": [ + "We can now invoke the chain with configurable options. `search_kwargs` is the id of the configurable field. The value is the search kwargs to use for Pinecone" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "a38037b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'The user worked at Kensho.'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\n", + " \"where did the user work?\",\n", + " config={\"configurable\": {\"search_kwargs\": {\"namespace\": \"harrison\"}}},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "0ff4f5f2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'The user worked at Facebook.'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\n", + " \"where did the user work?\",\n", + " config={\"configurable\": {\"search_kwargs\": {\"namespace\": \"ankush\"}}},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "7fb27b941602401d91542211134fc71a", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "For more vectorstore implementations for multi-user, please refer to specific pages, such as [Milvus](/docs/integrations/vectorstores/milvus)." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/qa_sources.ipynb b/docs/versioned_docs/version-0.2.x/how_to/qa_sources.ipynb new file mode 100644 index 0000000000000..047c0084f3caf --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/qa_sources.ipynb @@ -0,0 +1,332 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4ef893cf-eac1-45e6-9eb6-72e9ca043200", + "metadata": {}, + "source": [ + "# How to get your RAG application to return sources\n", + "\n", + "Often in Q&A applications it's important to show users the sources that were used to generate the answer. The simplest way to do this is for the chain to return the Documents that were retrieved in each generation.\n", + "\n", + "We'll work off of the Q&A app we built over the [LLM Powered Autonomous Agents](https://lilianweng.github.io/posts/2023-06-23-agent/) blog post by Lilian Weng in the [RAG tutorial](/docs/tutorials/rag).\n", + "\n", + "We will cover two approaches:\n", + "\n", + "1. Using the built-in [create_retrieval_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.retrieval.create_retrieval_chain.html), which returns sources by default;\n", + "2. Using a simple [LCEL](/docs/concepts#langchain-expression-language) implementation, to show the operating principle." + ] + }, + { + "cell_type": "markdown", + "id": "487d8d79-5ee9-4aa4-9fdf-cd5f4303e099", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "### Dependencies\n", + "\n", + "We'll use OpenAI embeddings and a Chroma vector store in this walkthrough, but everything shown here works with any [Embeddings](/docs/concepts#embedding-models), [VectorStore](/docs/concepts#vectorstores) or [Retriever](/docs/concepts#retrievers). \n", + "\n", + "We'll use the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "28d272cd-4e31-40aa-bbb4-0be0a1f49a14", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchainhub langchain-openai langchain-chroma bs4" + ] + }, + { + "cell_type": "markdown", + "id": "51ef48de-70b6-4f43-8e0b-ab9b84c9c02a", + "metadata": {}, + "source": [ + "We need to set environment variable `OPENAI_API_KEY`, which can be done directly or loaded from a `.env` file like so:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143787ca-d8e6-4dc9-8281-4374f4d71720", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# import dotenv\n", + "\n", + "# dotenv.load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "id": "1665e740-ce01-4f09-b9ed-516db0bd326f", + "metadata": {}, + "source": [ + "### LangSmith\n", + "\n", + "Many of the applications you build with LangChain will contain multiple steps with multiple invocations of LLM calls. 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. The best way to do this is with [LangSmith](https://smith.langchain.com).\n", + "\n", + "Note that LangSmith is not needed, but it is helpful. If you do want to use LangSmith, after you sign up at the link above, make sure to set your environment variables to start logging traces:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07411adb-3722-4f65-ab7f-8f6f57663d11", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "fa6ba684-26cf-4860-904e-a4d51380c134", + "metadata": {}, + "source": [ + "## Using `create_retrieval_chain`\n", + "\n", + "Let's first select a LLM:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5e7513b0-81e5-4477-8007-101e523f271c", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI()" + ] + }, + { + "cell_type": "markdown", + "id": "6b1bdfd7-8acf-4655-834d-ba7463a80fef", + "metadata": {}, + "source": [ + "Here is Q&A app with sources we built over the [LLM Powered Autonomous Agents](https://lilianweng.github.io/posts/2023-06-23-agent/) blog post by Lilian Weng in the [RAG tutorial](/docs/tutorials/rag):" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "820244ae-74b4-4593-b392-822979dd91b8", + "metadata": {}, + "outputs": [], + "source": [ + "import bs4\n", + "from langchain.chains import create_retrieval_chain\n", + "from langchain.chains.combine_documents import create_stuff_documents_chain\n", + "from langchain_chroma import Chroma\n", + "from langchain_community.document_loaders import WebBaseLoader\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_openai import OpenAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "# 1. Load, chunk and index the contents of the blog to create a retriever.\n", + "loader = WebBaseLoader(\n", + " web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n", + " bs_kwargs=dict(\n", + " parse_only=bs4.SoupStrainer(\n", + " class_=(\"post-content\", \"post-title\", \"post-header\")\n", + " )\n", + " ),\n", + ")\n", + "docs = loader.load()\n", + "\n", + "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n", + "splits = text_splitter.split_documents(docs)\n", + "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "\n", + "# 2. Incorporate the retriever into a question-answering chain.\n", + "system_prompt = (\n", + " \"You are an assistant for question-answering tasks. \"\n", + " \"Use the following pieces of retrieved context to answer \"\n", + " \"the question. If you don't know the answer, say that you \"\n", + " \"don't know. Use three sentences maximum and keep the \"\n", + " \"answer concise.\"\n", + " \"\\n\\n\"\n", + " \"{context}\"\n", + ")\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "\n", + "question_answer_chain = create_stuff_documents_chain(llm, prompt)\n", + "rag_chain = create_retrieval_chain(retriever, question_answer_chain)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0d3b0f36-7b56-49c0-8e40-a1aa9ebcbf24", + "metadata": {}, + "outputs": [], + "source": [ + "result = rag_chain.invoke({\"input\": \"What is Task Decomposition?\"})" + ] + }, + { + "cell_type": "markdown", + "id": "a8d9ac25-38bb-4ce7-ade9-b02a05ce3b27", + "metadata": {}, + "source": [ + "Note that `result` is a dict with keys `\"input\"`, `\"context\"`, and `\"answer\"`:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "29462727-01bc-42e7-82ed-9a0dc04b5774", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'input': 'What is Task Decomposition?',\n", + " 'context': [Document(page_content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}),\n", + " Document(page_content='Tree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}),\n", + " Document(page_content='Resources:\\n1. Internet access for searches and information gathering.\\n2. Long Term memory management.\\n3. GPT-3.5 powered Agents for delegation of simple tasks.\\n4. File output.\\n\\nPerformance Evaluation:\\n1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.\\n2. Constructively self-criticize your big-picture behavior constantly.\\n3. Reflect on past decisions and strategies to refine your approach.\\n4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}),\n", + " Document(page_content=\"(3) Task execution: Expert models execute on the specific tasks and log results.\\nInstruction:\\n\\nWith the input and the inference results, the AI assistant needs to describe the process and results. The previous stages can be formed as - User Input: {{ User Input }}, Task Planning: {{ Tasks }}, Model Selection: {{ Model Assignment }}, Task Execution: {{ Predictions }}. You must first answer the user's request in a straightforward manner. Then describe the task process and show your analysis and model inference results to the user in the first person. If inference results contain a file path, must tell the user the complete file path.\", metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'})],\n", + " 'answer': 'Task decomposition involves breaking down a complex task into smaller and simpler steps. This process helps agents or models handle challenging tasks by dividing them into more manageable subtasks. Techniques like Chain of Thought and Tree of Thoughts are used to decompose tasks into multiple steps for better problem-solving.'}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result" + ] + }, + { + "cell_type": "markdown", + "id": "00b19e47-3e70-4a79-b458-bef55adb7517", + "metadata": {}, + "source": [ + "Here, `\"context\"` contains the sources that the LLM used in generating the response in `\"answer\"`." + ] + }, + { + "cell_type": "markdown", + "id": "1c2f99b5-80b4-4178-bf30-c1c0a152638f", + "metadata": {}, + "source": [ + "## Custom LCEL implementation\n", + "\n", + "Below we construct a chain similar to those built by `create_retrieval_chain`. It works by building up a dict: \n", + "\n", + "1. Starting with a dict with the input query, add the retrieved docs in the `\"context\"` key;\n", + "2. Feed both the query and context into a RAG chain and add the result to the dict." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "22ea137c-1a7a-44dd-ac73-281213979957", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'input': 'What is Task Decomposition',\n", + " 'context': [Document(page_content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}),\n", + " Document(page_content='Tree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}),\n", + " Document(page_content='The AI assistant can parse user input to several tasks: [{\"task\": task, \"id\", task_id, \"dep\": dependency_task_ids, \"args\": {\"text\": text, \"image\": URL, \"audio\": URL, \"video\": URL}}]. The \"dep\" field denotes the id of the previous task which generates a new resource that the current task relies on. A special tag \"-task_id\" refers to the generated text image, audio and video in the dependency task with id as task_id. The task MUST be selected from the following options: {{ Available Task List }}. There is a logical relationship between tasks, please note their order. If the user input can\\'t be parsed, you need to reply empty JSON. Here are several cases for your reference: {{ Demonstrations }}. The chat history is recorded as {{ Chat History }}. From this chat history, you can find the path of the user-mentioned resources for your task planning.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}),\n", + " Document(page_content='Fig. 11. Illustration of how HuggingGPT works. (Image source: Shen et al. 2023)\\nThe system comprises of 4 stages:\\n(1) Task planning: LLM works as the brain and parses the user requests into multiple tasks. There are four attributes associated with each task: task type, ID, dependencies, and arguments. They use few-shot examples to guide LLM to do task parsing and planning.\\nInstruction:', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'})],\n", + " 'answer': 'Task decomposition involves breaking down complex tasks into smaller and simpler steps to make them more manageable for autonomous agents or models. This process can be achieved by techniques like Chain of Thought (CoT) or Tree of Thoughts, which guide the model to think step by step or explore multiple reasoning possibilities at each step. Task decomposition can be done through simple prompting with language models, task-specific instructions, or human inputs.'}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "\n", + "def format_docs(docs):\n", + " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", + "\n", + "\n", + "rag_chain_from_docs = (\n", + " RunnablePassthrough.assign(context=(lambda x: format_docs(x[\"context\"])))\n", + " | prompt\n", + " | llm\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "retrieve_docs = (lambda x: x[\"input\"]) | retriever\n", + "\n", + "chain = RunnablePassthrough.assign(context=retrieve_docs).assign(\n", + " answer=rag_chain_from_docs\n", + ")\n", + "\n", + "chain.invoke({\"input\": \"What is Task Decomposition\"})" + ] + }, + { + "cell_type": "markdown", + "id": "b437da5d-ca09-4d15-9be2-c35e5a1ace77", + "metadata": {}, + "source": [ + ":::{.callout-tip}\n", + "\n", + "Check out the [LangSmith trace](https://smith.langchain.com/public/0cb42685-e29e-4280-a503-bef2014d7ba2/r)\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/qa_streaming.ipynb b/docs/versioned_docs/version-0.2.x/how_to/qa_streaming.ipynb new file mode 100644 index 0000000000000..003204058dc09 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/qa_streaming.ipynb @@ -0,0 +1,552 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4ef893cf-eac1-45e6-9eb6-72e9ca043200", + "metadata": {}, + "source": [ + "# How to stream results from your RAG application\n", + "\n", + "This guide explains how to stream results from a RAG application. It covers streaming tokens from the final output as well as intermediate steps of a chain (e.g., from query re-writing).\n", + "\n", + "We'll work off of the Q&A app with sources we built over the [LLM Powered Autonomous Agents](https://lilianweng.github.io/posts/2023-06-23-agent/) blog post by Lilian Weng in the [RAG tutorial](/docs/tutorials/rag)." + ] + }, + { + "cell_type": "markdown", + "id": "487d8d79-5ee9-4aa4-9fdf-cd5f4303e099", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "### Dependencies\n", + "\n", + "We'll use OpenAI embeddings and a Chroma vector store in this walkthrough, but everything shown here works with any [Embeddings](/docs/concepts#embedding-models), [VectorStore](/docs/concepts#vectorstores) or [Retriever](/docs/concepts#retrievers). \n", + "\n", + "We'll use the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "28d272cd-4e31-40aa-bbb4-0be0a1f49a14", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchainhub langchain-openai langchain-chroma bs4" + ] + }, + { + "cell_type": "markdown", + "id": "51ef48de-70b6-4f43-8e0b-ab9b84c9c02a", + "metadata": {}, + "source": [ + "We need to set environment variable `OPENAI_API_KEY`, which can be done directly or loaded from a `.env` file like so:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143787ca-d8e6-4dc9-8281-4374f4d71720", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# import dotenv\n", + "\n", + "# dotenv.load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "id": "1665e740-ce01-4f09-b9ed-516db0bd326f", + "metadata": {}, + "source": [ + "### LangSmith\n", + "\n", + "Many of the applications you build with LangChain will contain multiple steps with multiple invocations of LLM calls. 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. The best way to do this is with [LangSmith](https://smith.langchain.com).\n", + "\n", + "Note that LangSmith is not needed, but it is helpful. If you do want to use LangSmith, after you sign up at the link above, make sure to set your environment variables to start logging traces:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07411adb-3722-4f65-ab7f-8f6f57663d11", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "e2a72ca8-f8c8-4c0e-929a-223946c63f12", + "metadata": {}, + "source": [ + "## RAG chain\n", + "\n", + "Let's first select a LLM:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "accc4c35-e17c-4bf0-8a11-cd9e53436a3d", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI()" + ] + }, + { + "cell_type": "markdown", + "id": "fa6ba684-26cf-4860-904e-a4d51380c134", + "metadata": {}, + "source": [ + "Here is Q&A app with sources we built over the [LLM Powered Autonomous Agents](https://lilianweng.github.io/posts/2023-06-23-agent/) blog post by Lilian Weng in the [RAG tutorial](/docs/tutorials/rag):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "820244ae-74b4-4593-b392-822979dd91b8", + "metadata": {}, + "outputs": [], + "source": [ + "import bs4\n", + "from langchain.chains import create_retrieval_chain\n", + "from langchain.chains.combine_documents import create_stuff_documents_chain\n", + "from langchain_chroma import Chroma\n", + "from langchain_community.document_loaders import WebBaseLoader\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_openai import OpenAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "# 1. Load, chunk and index the contents of the blog to create a retriever.\n", + "loader = WebBaseLoader(\n", + " web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n", + " bs_kwargs=dict(\n", + " parse_only=bs4.SoupStrainer(\n", + " class_=(\"post-content\", \"post-title\", \"post-header\")\n", + " )\n", + " ),\n", + ")\n", + "docs = loader.load()\n", + "\n", + "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n", + "splits = text_splitter.split_documents(docs)\n", + "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "\n", + "# 2. Incorporate the retriever into a question-answering chain.\n", + "system_prompt = (\n", + " \"You are an assistant for question-answering tasks. \"\n", + " \"Use the following pieces of retrieved context to answer \"\n", + " \"the question. If you don't know the answer, say that you \"\n", + " \"don't know. Use three sentences maximum and keep the \"\n", + " \"answer concise.\"\n", + " \"\\n\\n\"\n", + " \"{context}\"\n", + ")\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "\n", + "question_answer_chain = create_stuff_documents_chain(llm, prompt)\n", + "rag_chain = create_retrieval_chain(retriever, question_answer_chain)" + ] + }, + { + "cell_type": "markdown", + "id": "1c2f99b5-80b4-4178-bf30-c1c0a152638f", + "metadata": {}, + "source": [ + "## Streaming final outputs\n", + "\n", + "The chain constructed by `create_retrieval_chain` returns a dict with keys `\"input\"`, `\"context\"`, and `\"answer\"`. The `.stream` method will by default stream each key in a sequence.\n", + "\n", + "Note that here only the `\"answer\"` key is streamed token-by-token, as the other components-- such as retrieval-- do not support token-level streaming." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ded41680-b749-4e2a-9daa-b1165d74783b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'input': 'What is Task Decomposition?'}\n", + "{'context': [Document(page_content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}), Document(page_content='Tree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}), Document(page_content='Resources:\\n1. Internet access for searches and information gathering.\\n2. Long Term memory management.\\n3. GPT-3.5 powered Agents for delegation of simple tasks.\\n4. File output.\\n\\nPerformance Evaluation:\\n1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.\\n2. Constructively self-criticize your big-picture behavior constantly.\\n3. Reflect on past decisions and strategies to refine your approach.\\n4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}), Document(page_content=\"(3) Task execution: Expert models execute on the specific tasks and log results.\\nInstruction:\\n\\nWith the input and the inference results, the AI assistant needs to describe the process and results. The previous stages can be formed as - User Input: {{ User Input }}, Task Planning: {{ Tasks }}, Model Selection: {{ Model Assignment }}, Task Execution: {{ Predictions }}. You must first answer the user's request in a straightforward manner. Then describe the task process and show your analysis and model inference results to the user in the first person. If inference results contain a file path, must tell the user the complete file path.\", metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'})]}\n", + "{'answer': ''}\n", + "{'answer': 'Task'}\n", + "{'answer': ' decomposition'}\n", + "{'answer': ' involves'}\n", + "{'answer': ' breaking'}\n", + "{'answer': ' down'}\n", + "{'answer': ' complex'}\n", + "{'answer': ' tasks'}\n", + "{'answer': ' into'}\n", + "{'answer': ' smaller'}\n", + "{'answer': ' and'}\n", + "{'answer': ' simpler'}\n", + "{'answer': ' steps'}\n", + "{'answer': ' to'}\n", + "{'answer': ' make'}\n", + "{'answer': ' them'}\n", + "{'answer': ' more'}\n", + "{'answer': ' manageable'}\n", + "{'answer': '.'}\n", + "{'answer': ' This'}\n", + "{'answer': ' process'}\n", + "{'answer': ' can'}\n", + "{'answer': ' be'}\n", + "{'answer': ' facilitated'}\n", + "{'answer': ' by'}\n", + "{'answer': ' techniques'}\n", + "{'answer': ' like'}\n", + "{'answer': ' Chain'}\n", + "{'answer': ' of'}\n", + "{'answer': ' Thought'}\n", + "{'answer': ' ('}\n", + "{'answer': 'Co'}\n", + "{'answer': 'T'}\n", + "{'answer': ')'}\n", + "{'answer': ' and'}\n", + "{'answer': ' Tree'}\n", + "{'answer': ' of'}\n", + "{'answer': ' Thoughts'}\n", + "{'answer': ','}\n", + "{'answer': ' which'}\n", + "{'answer': ' help'}\n", + "{'answer': ' agents'}\n", + "{'answer': ' plan'}\n", + "{'answer': ' and'}\n", + "{'answer': ' execute'}\n", + "{'answer': ' tasks'}\n", + "{'answer': ' effectively'}\n", + "{'answer': ' by'}\n", + "{'answer': ' dividing'}\n", + "{'answer': ' them'}\n", + "{'answer': ' into'}\n", + "{'answer': ' sub'}\n", + "{'answer': 'goals'}\n", + "{'answer': ' or'}\n", + "{'answer': ' multiple'}\n", + "{'answer': ' reasoning'}\n", + "{'answer': ' possibilities'}\n", + "{'answer': '.'}\n", + "{'answer': ' Task'}\n", + "{'answer': ' decomposition'}\n", + "{'answer': ' can'}\n", + "{'answer': ' be'}\n", + "{'answer': ' initiated'}\n", + "{'answer': ' through'}\n", + "{'answer': ' simple'}\n", + "{'answer': ' prompts'}\n", + "{'answer': ','}\n", + "{'answer': ' task'}\n", + "{'answer': '-specific'}\n", + "{'answer': ' instructions'}\n", + "{'answer': ','}\n", + "{'answer': ' or'}\n", + "{'answer': ' human'}\n", + "{'answer': ' inputs'}\n", + "{'answer': ' to'}\n", + "{'answer': ' guide'}\n", + "{'answer': ' the'}\n", + "{'answer': ' agent'}\n", + "{'answer': ' in'}\n", + "{'answer': ' achieving'}\n", + "{'answer': ' its'}\n", + "{'answer': ' goals'}\n", + "{'answer': ' efficiently'}\n", + "{'answer': '.'}\n", + "{'answer': ''}\n" + ] + } + ], + "source": [ + "for chunk in rag_chain.stream({\"input\": \"What is Task Decomposition?\"}):\n", + " print(chunk)" + ] + }, + { + "cell_type": "markdown", + "id": "72380afa-965d-4715-aac4-6049cce56313", + "metadata": {}, + "source": [ + "We are free to process chunks as they are streamed out. If we just want to stream the answer tokens, for example, we can select chunks with the corresponding key:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "738eb33e-6ccd-4b26-b563-beef216fb113", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Task| decomposition| is| a| technique| used| to| break| down| complex| tasks| into| smaller| and| more| manageable| steps|.| This| process| helps| agents| or| models| handle| intricate| tasks| by| dividing| them| into| simpler| sub|tasks|.| By| decom|posing| tasks|,| the| model| can| effectively| plan| and| execute| each| step| towards| achieving| the| overall| goal|.|" + ] + } + ], + "source": [ + "for chunk in rag_chain.stream({\"input\": \"What is Task Decomposition?\"}):\n", + " if answer_chunk := chunk.get(\"answer\"):\n", + " print(f\"{answer_chunk}|\", end=\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "8b2d224d-2a82-418b-b562-01ea210b86ef", + "metadata": {}, + "source": [ + "More simply, we can use the [.pick](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.pick) method to select only the desired key:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "16c20971-a6fd-4b57-83cd-7b2b453f97c9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "|Task| decomposition| involves| breaking| down| complex| tasks| into| smaller| and| simpler| steps| to| make| them| more| manageable| for| an| agent| or| model| to| handle|.| This| process| helps| in| planning| and| executing| tasks| efficiently| by| dividing| them| into| a| series| of| sub|goals| or| actions|.| Task| decomposition| can| be| achieved| through| techniques| like| Chain| of| Thought| (|Co|T|)| or| Tree| of| Thoughts|,| which| enhance| model| performance| on| intricate| tasks| by| guiding| them| through| step|-by|-step| thinking| processes|.||" + ] + } + ], + "source": [ + "chain = rag_chain.pick(\"answer\")\n", + "\n", + "for chunk in chain.stream({\"input\": \"What is Task Decomposition?\"}):\n", + " print(f\"{chunk}|\", end=\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "fdee7ae6-4a81-46ab-8efd-d2310b596f8c", + "metadata": {}, + "source": [ + "## Streaming intermediate steps\n", + "\n", + "Suppose we want to stream not only the final outputs of the chain, but also some intermediate steps. As an example let's take our [Conversational RAG](/docs/tutorials/qa_chat_history) chain. Here we reformulate the user question before passing it to the retriever. This reformulated question is not returned as part of the final output. We could modify our chain to return the new question, but for demonstration purposes we'll leave it as is." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f4d7714e-bdca-419d-a6c6-7c1a70a69297", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains import create_history_aware_retriever\n", + "from langchain_core.prompts import MessagesPlaceholder\n", + "\n", + "### Contextualize question ###\n", + "contextualize_q_system_prompt = (\n", + " \"Given a chat history and the latest user question \"\n", + " \"which might reference context in the chat history, \"\n", + " \"formulate a standalone question which can be understood \"\n", + " \"without the chat history. Do NOT answer the question, \"\n", + " \"just reformulate it if needed and otherwise return it as is.\"\n", + ")\n", + "contextualize_q_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", contextualize_q_system_prompt),\n", + " MessagesPlaceholder(\"chat_history\"),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "contextualize_q_llm = llm.with_config(tags=[\"contextualize_q_llm\"])\n", + "history_aware_retriever = create_history_aware_retriever(\n", + " contextualize_q_llm, retriever, contextualize_q_prompt\n", + ")\n", + "\n", + "\n", + "### Answer question ###\n", + "system_prompt = (\n", + " \"You are an assistant for question-answering tasks. \"\n", + " \"Use the following pieces of retrieved context to answer \"\n", + " \"the question. If you don't know the answer, say that you \"\n", + " \"don't know. Use three sentences maximum and keep the \"\n", + " \"answer concise.\"\n", + " \"\\n\\n\"\n", + " \"{context}\"\n", + ")\n", + "qa_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " MessagesPlaceholder(\"chat_history\"),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)\n", + "\n", + "rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)" + ] + }, + { + "cell_type": "markdown", + "id": "ad306179-b6f0-4ade-9ec5-06e04fbb8d69", + "metadata": {}, + "source": [ + "Note that above we use `.with_config` to assign a tag to the LLM that is used for the question re-phrasing step. This is not necessary but will make it more convenient to stream output from that specific step.\n", + "\n", + "To demonstrate, we will pass in an artificial message history:\n", + "```\n", + "Human: What is task decomposition?\n", + "\n", + "AI: Task decomposition involves breaking up a complex task into smaller and simpler steps.\n", + "```\n", + "We then ask a follow up question: \"What are some common ways of doing it?\" Leading into the retrieval step, our `history_aware_retriever` will rephrase this question using the conversation's context to ensure that the retrieval is meaningful.\n", + "\n", + "To stream intermediate output, we recommend use of the async `.astream_events` method. This method will stream output from all \"events\" in the chain, and can be quite verbose. We can filter using tags, event types, and other criteria, as we do here.\n", + "\n", + "Below we show a typical `.astream_events` loop, where we pass in the chain input and emit desired results. See the [API reference](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.astream_events) and [streaming guide](/docs/how_to/streaming) for more detail." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "3ef2af40-e6ce-42a3-ad6a-ee405ad7f8ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "|What| are| some| typical| methods| used| for| task| decomposition|?||" + ] + } + ], + "source": [ + "first_question = \"What is task decomposition?\"\n", + "first_answer = (\n", + " \"Task decomposition involves breaking up \"\n", + " \"a complex task into smaller and simpler \"\n", + " \"steps.\"\n", + ")\n", + "follow_up_question = \"What are some common ways of doing it?\"\n", + "\n", + "chat_history = [\n", + " (\"human\", first_question),\n", + " (\"ai\", first_answer),\n", + "]\n", + "\n", + "\n", + "async for event in rag_chain.astream_events(\n", + " {\n", + " \"input\": follow_up_question,\n", + " \"chat_history\": chat_history,\n", + " },\n", + " version=\"v1\",\n", + "):\n", + " if (\n", + " event[\"event\"] == \"on_chat_model_stream\"\n", + " and \"contextualize_q_llm\" in event[\"tags\"]\n", + " ):\n", + " ai_message_chunk = event[\"data\"][\"chunk\"]\n", + " print(f\"{ai_message_chunk.content}|\", end=\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "7da5dd1b-634c-4dd7-8235-69adec21d195", + "metadata": {}, + "source": [ + "Here we recover, token-by-token, the query that is passed into the retriever given our question \"What are some common ways of doing it?\"\n", + "\n", + "If we wanted to get our retrieved docs, we could filter on name \"Retriever\":" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "987ef6be-8c4e-4257-828a-a3b4fb4ccc99", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'event': 'on_retriever_start', 'name': 'Retriever', 'run_id': '6834097c-07fe-42f5-a566-a4780af4d1d0', 'tags': ['seq:step:4', 'Chroma', 'OpenAIEmbeddings'], 'metadata': {}, 'data': {'input': {'query': 'What are some typical methods used for task decomposition?'}}}\n", + "\n", + "{'event': 'on_retriever_end', 'name': 'Retriever', 'run_id': '6834097c-07fe-42f5-a566-a4780af4d1d0', 'tags': ['seq:step:4', 'Chroma', 'OpenAIEmbeddings'], 'metadata': {}, 'data': {'input': {'query': 'What are some typical methods used for task decomposition?'}, 'output': {'documents': [Document(page_content='Tree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}), Document(page_content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}), Document(page_content='Resources:\\n1. Internet access for searches and information gathering.\\n2. Long Term memory management.\\n3. GPT-3.5 powered Agents for delegation of simple tasks.\\n4. File output.\\n\\nPerformance Evaluation:\\n1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.\\n2. Constructively self-criticize your big-picture behavior constantly.\\n3. Reflect on past decisions and strategies to refine your approach.\\n4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}), Document(page_content='Fig. 9. Comparison of MIPS algorithms, measured in recall@10. (Image source: Google Blog, 2020)\\nCheck more MIPS algorithms and performance comparison in ann-benchmarks.com.\\nComponent Three: Tool Use#\\nTool use is a remarkable and distinguishing characteristic of human beings. We create, modify and utilize external objects to do things that go beyond our physical and cognitive limits. Equipping LLMs with external tools can significantly extend the model capabilities.', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'})]}}}\n", + "\n" + ] + } + ], + "source": [ + "async for event in rag_chain.astream_events(\n", + " {\n", + " \"input\": follow_up_question,\n", + " \"chat_history\": chat_history,\n", + " },\n", + " version=\"v1\",\n", + "):\n", + " if event[\"name\"] == \"Retriever\":\n", + " print(event)\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "c5470a79-258a-4108-8ceb-dfe8180160ca", + "metadata": {}, + "source": [ + "For more on how to stream intermediate steps check out the [streaming guide](/docs/how_to/streaming)." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/query_constructing_filters.ipynb b/docs/versioned_docs/version-0.2.x/how_to/query_constructing_filters.ipynb new file mode 100644 index 0000000000000..a55f229678e45 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/query_constructing_filters.ipynb @@ -0,0 +1,190 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "df7d42b9-58a6-434c-a2d7-0b61142f6d3e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 6\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "f2195672-0cab-4967-ba8a-c6544635547d", + "metadata": {}, + "source": [ + "# How to construct filters for query analysis\n", + "\n", + "We may want to do query analysis to extract filters to pass into retrievers. One way we ask the LLM to represent these filters is as a Pydantic model. There is then the issue of converting that Pydantic model into a filter that can be passed into a retriever. \n", + "\n", + "This can be done manually, but LangChain also provides some \"Translators\" that are able to translate from a common syntax into filters specific to each retriever. Here, we will cover how to use those translators." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "8ca446a0", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Optional\n", + "\n", + "from langchain.chains.query_constructor.ir import (\n", + " Comparator,\n", + " Comparison,\n", + " Operation,\n", + " Operator,\n", + " StructuredQuery,\n", + ")\n", + "from langchain.retrievers.self_query.chroma import ChromaTranslator\n", + "from langchain.retrievers.self_query.elasticsearch import ElasticsearchTranslator\n", + "from langchain_core.pydantic_v1 import BaseModel" + ] + }, + { + "cell_type": "markdown", + "id": "bc1302ff", + "metadata": {}, + "source": [ + "In this example, `year` and `author` are both attributes to filter on." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "64055006", + "metadata": {}, + "outputs": [], + "source": [ + "class Search(BaseModel):\n", + " query: str\n", + " start_year: Optional[int]\n", + " author: Optional[str]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "44eb6d98", + "metadata": {}, + "outputs": [], + "source": [ + "search_query = Search(query=\"RAG\", start_year=2022, author=\"LangChain\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "e8ba6705", + "metadata": {}, + "outputs": [], + "source": [ + "def construct_comparisons(query: Search):\n", + " comparisons = []\n", + " if query.start_year is not None:\n", + " comparisons.append(\n", + " Comparison(\n", + " comparator=Comparator.GT,\n", + " attribute=\"start_year\",\n", + " value=query.start_year,\n", + " )\n", + " )\n", + " if query.author is not None:\n", + " comparisons.append(\n", + " Comparison(\n", + " comparator=Comparator.EQ,\n", + " attribute=\"author\",\n", + " value=query.author,\n", + " )\n", + " )\n", + " return comparisons" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6a79c9da", + "metadata": {}, + "outputs": [], + "source": [ + "comparisons = construct_comparisons(search_query)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "2d0e9689", + "metadata": {}, + "outputs": [], + "source": [ + "_filter = Operation(operator=Operator.AND, arguments=comparisons)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "e4c0b2ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'bool': {'must': [{'range': {'metadata.start_year': {'gt': 2022}}},\n", + " {'term': {'metadata.author.keyword': 'LangChain'}}]}}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ElasticsearchTranslator().visit_operation(_filter)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d75455ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'$and': [{'start_year': {'$gt': 2022}}, {'author': {'$eq': 'LangChain'}}]}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ChromaTranslator().visit_operation(_filter)" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/query_few_shot.ipynb b/docs/versioned_docs/version-0.2.x/how_to/query_few_shot.ipynb new file mode 100644 index 0000000000000..f9e50ab9b90a9 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/query_few_shot.ipynb @@ -0,0 +1,385 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "df7d42b9-58a6-434c-a2d7-0b61142f6d3e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 2\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "f2195672-0cab-4967-ba8a-c6544635547d", + "metadata": {}, + "source": [ + "# How to add examples to the prompt for query analysis\n", + "\n", + "As our query analysis becomes more complex, the LLM may struggle to understand how exactly it should respond in certain scenarios. In order to improve performance here, we can add examples to the prompt to guide the LLM.\n", + "\n", + "Let's take a look at how we can add examples for the LangChain YouTube video query analyzer we built in the [Quickstart](/docs/use_cases/query_analysis/quickstart)." + ] + }, + { + "cell_type": "markdown", + "id": "a4079b57-4369-49c9-b2ad-c809b5408d7e", + "metadata": {}, + "source": [ + "## Setup\n", + "#### Install dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e168ef5c-e54e-49a6-8552-5502854a6f01", + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -qU langchain-core langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "79d66a45-a05c-4d22-b011-b1cdbdfc8f9c", + "metadata": {}, + "source": [ + "#### Set environment variables\n", + "\n", + "We'll use OpenAI in this example:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "40e2979e-a818-4b96-ac25-039336f94319", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Optional, uncomment to trace runs with LangSmith. Sign up here: https://smith.langchain.com.\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "57396e23-c192-4d97-846b-5eacea4d6b8d", + "metadata": {}, + "source": [ + "## Query schema\n", + "\n", + "We'll define a query schema that we want our model to output. To make our query analysis a bit more interesting, we'll add a `sub_queries` field that contains more narrow questions derived from the top level question." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "0b51dd76-820d-41a4-98c8-893f6fe0d1ea", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Optional\n", + "\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "sub_queries_description = \"\"\"\\\n", + "If the original question contains multiple distinct sub-questions, \\\n", + "or if there are more generic questions that would be helpful to answer in \\\n", + "order to answer the original question, write a list of all relevant sub-questions. \\\n", + "Make sure this list is comprehensive and covers all parts of the original question. \\\n", + "It's ok if there's redundancy in the sub-questions. \\\n", + "Make sure the sub-questions are as narrowly focused as possible.\"\"\"\n", + "\n", + "\n", + "class Search(BaseModel):\n", + " \"\"\"Search over a database of tutorial videos about a software library.\"\"\"\n", + "\n", + " query: str = Field(\n", + " ...,\n", + " description=\"Primary similarity search query applied to video transcripts.\",\n", + " )\n", + " sub_queries: List[str] = Field(\n", + " default_factory=list, description=sub_queries_description\n", + " )\n", + " publish_year: Optional[int] = Field(None, description=\"Year video was published\")" + ] + }, + { + "cell_type": "markdown", + "id": "f8b08c52-1ce9-4d8b-a779-cbe8efde51d1", + "metadata": {}, + "source": [ + "## Query generation" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "783c03c3-8c72-4f88-9cf4-5829ce6745d6", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "system = \"\"\"You are an expert at converting user questions into database queries. \\\n", + "You have access to a database of tutorial videos about a software library for building LLM-powered applications. \\\n", + "Given a question, return a list of database queries optimized to retrieve the most relevant results.\n", + "\n", + "If there are acronyms or words you are not familiar with, do not try to rephrase them.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " MessagesPlaceholder(\"examples\", optional=True),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", + "structured_llm = llm.with_structured_output(Search)\n", + "query_analyzer = {\"question\": RunnablePassthrough()} | prompt | structured_llm" + ] + }, + { + "cell_type": "markdown", + "id": "f403517a-b8e3-44ac-b0a6-02f8305635a2", + "metadata": {}, + "source": [ + "Let's try out our query analyzer without any examples in the prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "0bcfce06-6f0c-4f9d-a1fc-dc29342d2aae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='web voyager vs reflection agents', sub_queries=['difference between web voyager and reflection agents', 'do web voyager and reflection agents use langgraph'], publish_year=None)" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\n", + " \"what's the difference between web voyager and reflection agents? do both use langgraph?\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "00962b08-899c-465c-9a41-6459b207e0f2", + "metadata": {}, + "source": [ + "## Adding examples and tuning the prompt\n", + "\n", + "This works pretty well, but we probably want it to decompose the question even further to separate the queries about Web Voyager and Reflection Agents.\n", + "\n", + "To tune our query generation results, we can add some examples of inputs questions and gold standard output queries to our prompt." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "15b4923d-a08e-452d-8889-9a09a57d1095", + "metadata": {}, + "outputs": [], + "source": [ + "examples = []" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "da5330e6-827a-40e5-982b-b23b6286b758", + "metadata": {}, + "outputs": [], + "source": [ + "question = \"What's chat langchain, is it a langchain template?\"\n", + "query = Search(\n", + " query=\"What is chat langchain and is it a langchain template?\",\n", + " sub_queries=[\"What is chat langchain\", \"What is a langchain template\"],\n", + ")\n", + "examples.append({\"input\": question, \"tool_calls\": [query]})" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "580e857a-27df-4ecf-a19c-458dc9244ec8", + "metadata": {}, + "outputs": [], + "source": [ + "question = \"How to build multi-agent system and stream intermediate steps from it\"\n", + "query = Search(\n", + " query=\"How to build multi-agent system and stream intermediate steps from it\",\n", + " sub_queries=[\n", + " \"How to build multi-agent system\",\n", + " \"How to stream intermediate steps from multi-agent system\",\n", + " \"How to stream intermediate steps\",\n", + " ],\n", + ")\n", + "\n", + "examples.append({\"input\": question, \"tool_calls\": [query]})" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "fa63310d-69e3-4701-825c-fbb01f8a5a16", + "metadata": {}, + "outputs": [], + "source": [ + "question = \"LangChain agents vs LangGraph?\"\n", + "query = Search(\n", + " query=\"What's the difference between LangChain agents and LangGraph? How do you deploy them?\",\n", + " sub_queries=[\n", + " \"What are LangChain agents\",\n", + " \"What is LangGraph\",\n", + " \"How do you deploy LangChain agents\",\n", + " \"How do you deploy LangGraph\",\n", + " ],\n", + ")\n", + "examples.append({\"input\": question, \"tool_calls\": [query]})" + ] + }, + { + "cell_type": "markdown", + "id": "bd21389c-f862-44e6-9d51-92db10979525", + "metadata": {}, + "source": [ + "Now we need to update our prompt template and chain so that the examples are included in each prompt. Since we're working with OpenAI function-calling, we'll need to do a bit of extra structuring to send example inputs and outputs to the model. We'll create a `tool_example_to_messages` helper function to handle this for us:" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "68b03709-9a60-4acf-b96c-cafe1056c6f3", + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "from typing import Dict\n", + "\n", + "from langchain_core.messages import (\n", + " AIMessage,\n", + " BaseMessage,\n", + " HumanMessage,\n", + " SystemMessage,\n", + " ToolMessage,\n", + ")\n", + "\n", + "\n", + "def tool_example_to_messages(example: Dict) -> List[BaseMessage]:\n", + " messages: List[BaseMessage] = [HumanMessage(content=example[\"input\"])]\n", + " openai_tool_calls = []\n", + " for tool_call in example[\"tool_calls\"]:\n", + " openai_tool_calls.append(\n", + " {\n", + " \"id\": str(uuid.uuid4()),\n", + " \"type\": \"function\",\n", + " \"function\": {\n", + " \"name\": tool_call.__class__.__name__,\n", + " \"arguments\": tool_call.json(),\n", + " },\n", + " }\n", + " )\n", + " messages.append(\n", + " AIMessage(content=\"\", additional_kwargs={\"tool_calls\": openai_tool_calls})\n", + " )\n", + " tool_outputs = example.get(\"tool_outputs\") or [\n", + " \"You have correctly called this tool.\"\n", + " ] * len(openai_tool_calls)\n", + " for output, tool_call in zip(tool_outputs, openai_tool_calls):\n", + " messages.append(ToolMessage(content=output, tool_call_id=tool_call[\"id\"]))\n", + " return messages\n", + "\n", + "\n", + "example_msgs = [msg for ex in examples for msg in tool_example_to_messages(ex)]" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "d9bf9f87-3e6b-4fc2-957b-949b077fab54", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import MessagesPlaceholder\n", + "\n", + "query_analyzer_with_examples = (\n", + " {\"question\": RunnablePassthrough()}\n", + " | prompt.partial(examples=example_msgs)\n", + " | structured_llm\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "e565ccb0-3530-4782-b56b-d1f6d0a8e559", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='Difference between web voyager and reflection agents, do they both use LangGraph?', sub_queries=['What is Web Voyager', 'What are Reflection agents', 'Do Web Voyager and Reflection agents use LangGraph'], publish_year=None)" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer_with_examples.invoke(\n", + " \"what's the difference between web voyager and reflection agents? do both use langgraph?\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e5ea49ff-be53-4072-8c25-08682bb31a19", + "metadata": {}, + "source": [ + "Thanks to our examples we get a slightly more decomposed search query. With some more prompt engineering and tuning of our examples we could improve query generation even more.\n", + "\n", + "You can see that the examples are passed to the model as messages in the [LangSmith trace](https://smith.langchain.com/public/aeaaafce-d2b1-4943-9a61-bc954e8fc6f2/r)." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/query_high_cardinality.ipynb b/docs/versioned_docs/version-0.2.x/how_to/query_high_cardinality.ipynb new file mode 100644 index 0000000000000..75c00dbc4ce15 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/query_high_cardinality.ipynb @@ -0,0 +1,585 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "df7d42b9-58a6-434c-a2d7-0b61142f6d3e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 7\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "f2195672-0cab-4967-ba8a-c6544635547d", + "metadata": {}, + "source": [ + "# How deal with high cardinality categoricals when doing query analysis\n", + "\n", + "You may want to do query analysis to create a filter on a categorical column. One of the difficulties here is that you usually need to specify the EXACT categorical value. The issue is you need to make sure the LLM generates that categorical value exactly. This can be done relatively easy with prompting when there are only a few values that are valid. When there are a high number of valid values then it becomes more difficult, as those values may not fit in the LLM context, or (if they do) there may be too many for the LLM to properly attend to.\n", + "\n", + "In this notebook we take a look at how to approach this." + ] + }, + { + "cell_type": "markdown", + "id": "a4079b57-4369-49c9-b2ad-c809b5408d7e", + "metadata": {}, + "source": [ + "## Setup\n", + "#### Install dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e168ef5c-e54e-49a6-8552-5502854a6f01", + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -qU langchain langchain-community langchain-openai faker langchain-chroma" + ] + }, + { + "cell_type": "markdown", + "id": "79d66a45-a05c-4d22-b011-b1cdbdfc8f9c", + "metadata": {}, + "source": [ + "#### Set environment variables\n", + "\n", + "We'll use OpenAI in this example:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "40e2979e-a818-4b96-ac25-039336f94319", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Optional, uncomment to trace runs with LangSmith. Sign up here: https://smith.langchain.com.\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "d8d47f4b", + "metadata": {}, + "source": [ + "#### Set up data\n", + "\n", + "We will generate a bunch of fake names" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e5ba65c2", + "metadata": {}, + "outputs": [], + "source": [ + "from faker import Faker\n", + "\n", + "fake = Faker()\n", + "\n", + "names = [fake.name() for _ in range(10000)]" + ] + }, + { + "cell_type": "markdown", + "id": "41133694", + "metadata": {}, + "source": [ + "Let's look at some of the names" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c901ea97", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Hayley Gonzalez'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b0d42ae2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Jesse Knight'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names[567]" + ] + }, + { + "cell_type": "markdown", + "id": "1725883d", + "metadata": {}, + "source": [ + "## Query Analysis\n", + "\n", + "We can now set up a baseline query analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0ae69afc", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.pydantic_v1 import BaseModel, Field" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6c9485ce", + "metadata": {}, + "outputs": [], + "source": [ + "class Search(BaseModel):\n", + " query: str\n", + " author: str" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "aebd704a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/harrisonchase/workplace/langchain/libs/core/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: The function `with_structured_output` is in beta. It is actively being worked on, so the API may change.\n", + " warn_beta(\n" + ] + } + ], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "system = \"\"\"Generate a relevant search query for a library system\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", + "structured_llm = llm.with_structured_output(Search)\n", + "query_analyzer = {\"question\": RunnablePassthrough()} | prompt | structured_llm" + ] + }, + { + "cell_type": "markdown", + "id": "41709a2e", + "metadata": {}, + "source": [ + "We can see that if we spell the name exactly correctly, it knows how to handle it" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "cc0d344b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='books about aliens', author='Jesse Knight')" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\"what are books about aliens by Jesse Knight\")" + ] + }, + { + "cell_type": "markdown", + "id": "a1b57eab", + "metadata": {}, + "source": [ + "The issue is that the values you want to filter on may NOT be spelled exactly correctly" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "82b6b2ad", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='books about aliens', author='Jess Knight')" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\"what are books about aliens by jess knight\")" + ] + }, + { + "cell_type": "markdown", + "id": "0b60b7c2", + "metadata": {}, + "source": [ + "### Add in all values\n", + "\n", + "One way around this is to add ALL possible values to the prompt. That will generally guide the query in the right direction" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "98788a94", + "metadata": {}, + "outputs": [], + "source": [ + "system = \"\"\"Generate a relevant search query for a library system.\n", + "\n", + "`author` attribute MUST be one of:\n", + "\n", + "{authors}\n", + "\n", + "Do NOT hallucinate author name!\"\"\"\n", + "base_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "prompt = base_prompt.partial(authors=\", \".join(names))" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "e65412f5", + "metadata": {}, + "outputs": [], + "source": [ + "query_analyzer_all = {\"question\": RunnablePassthrough()} | prompt | structured_llm" + ] + }, + { + "cell_type": "markdown", + "id": "e639285a", + "metadata": {}, + "source": [ + "However... if the list of categoricals is long enough, it may error!" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "696b000f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Error code: 400 - {'error': {'message': \"This model's maximum context length is 16385 tokens. However, your messages resulted in 33885 tokens (33855 in the messages, 30 in the functions). Please reduce the length of the messages or functions.\", 'type': 'invalid_request_error', 'param': 'messages', 'code': 'context_length_exceeded'}}\n" + ] + } + ], + "source": [ + "try:\n", + " res = query_analyzer_all.invoke(\"what are books about aliens by jess knight\")\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "id": "1d5d7891", + "metadata": {}, + "source": [ + "We can try to use a longer context window... but with so much information in there, it is not garunteed to pick it up reliably" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "0f0d0757", + "metadata": {}, + "outputs": [], + "source": [ + "llm_long = ChatOpenAI(model=\"gpt-4-turbo-preview\", temperature=0)\n", + "structured_llm_long = llm_long.with_structured_output(Search)\n", + "query_analyzer_all = {\"question\": RunnablePassthrough()} | prompt | structured_llm_long" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "03e5b7b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='aliens', author='Kevin Knight')" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer_all.invoke(\"what are books about aliens by jess knight\")" + ] + }, + { + "cell_type": "markdown", + "id": "73ecf52b", + "metadata": {}, + "source": [ + "### Find and all relevant values\n", + "\n", + "Instead, what we can do is create an index over the relevant values and then query that for the N most relevant values," + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "32b19e07", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_chroma import Chroma\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n", + "vectorstore = Chroma.from_texts(names, embeddings, collection_name=\"author_names\")" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "774cb7b0", + "metadata": {}, + "outputs": [], + "source": [ + "def select_names(question):\n", + " _docs = vectorstore.similarity_search(question, k=10)\n", + " _names = [d.page_content for d in _docs]\n", + " return \", \".join(_names)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "1173159c", + "metadata": {}, + "outputs": [], + "source": [ + "create_prompt = {\n", + " \"question\": RunnablePassthrough(),\n", + " \"authors\": select_names,\n", + "} | base_prompt" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "0a892607", + "metadata": {}, + "outputs": [], + "source": [ + "query_analyzer_select = create_prompt | structured_llm" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "8195d7cd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ChatPromptValue(messages=[SystemMessage(content='Generate a relevant search query for a library system.\\n\\n`author` attribute MUST be one of:\\n\\nJesse Knight, Kelly Knight, Scott Knight, Richard Knight, Andrew Knight, Katherine Knight, Erica Knight, Ashley Knight, Becky Knight, Kevin Knight\\n\\nDo NOT hallucinate author name!'), HumanMessage(content='what are books by jess knight')])" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "create_prompt.invoke(\"what are books by jess knight\")" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "d3228b4e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='books about aliens', author='Jesse Knight')" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer_select.invoke(\"what are books about aliens by jess knight\")" + ] + }, + { + "cell_type": "markdown", + "id": "46ef88bb", + "metadata": {}, + "source": [ + "### Replace after selection\n", + "\n", + "Another method is to let the LLM fill in whatever value, but then convert that value to a valid value.\n", + "This can actually be done with the Pydantic class itself!" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "a2e8b434", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.pydantic_v1 import validator\n", + "\n", + "\n", + "class Search(BaseModel):\n", + " query: str\n", + " author: str\n", + "\n", + " @validator(\"author\")\n", + " def double(cls, v: str) -> str:\n", + " return vectorstore.similarity_search(v, k=1)[0].page_content" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "919c0601", + "metadata": {}, + "outputs": [], + "source": [ + "system = \"\"\"Generate a relevant search query for a library system\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "corrective_structure_llm = llm.with_structured_output(Search)\n", + "corrective_query_analyzer = (\n", + " {\"question\": RunnablePassthrough()} | prompt | corrective_structure_llm\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "6c4f3e9a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='books about aliens', author='Jesse Knight')" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "corrective_query_analyzer.invoke(\"what are books about aliens by jes knight\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a309cb11", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO: show trigram similarity" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/query_multiple_queries.ipynb b/docs/versioned_docs/version-0.2.x/how_to/query_multiple_queries.ipynb new file mode 100644 index 0000000000000..0dcaa5bfbe7f6 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/query_multiple_queries.ipynb @@ -0,0 +1,329 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "df7d42b9-58a6-434c-a2d7-0b61142f6d3e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 4\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "f2195672-0cab-4967-ba8a-c6544635547d", + "metadata": {}, + "source": [ + "# How to handle multiple queries when doing query analysis\n", + "\n", + "Sometimes, a query analysis technique may allow for multiple queries to be generated. In these cases, we need to remember to run all queries and then to combine the results. We will show a simple example (using mock data) of how to do that." + ] + }, + { + "cell_type": "markdown", + "id": "a4079b57-4369-49c9-b2ad-c809b5408d7e", + "metadata": {}, + "source": [ + "## Setup\n", + "#### Install dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e168ef5c-e54e-49a6-8552-5502854a6f01", + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -qU langchain langchain-community langchain-openai langchain-chroma" + ] + }, + { + "cell_type": "markdown", + "id": "79d66a45-a05c-4d22-b011-b1cdbdfc8f9c", + "metadata": {}, + "source": [ + "#### Set environment variables\n", + "\n", + "We'll use OpenAI in this example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40e2979e-a818-4b96-ac25-039336f94319", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Optional, uncomment to trace runs with LangSmith. Sign up here: https://smith.langchain.com.\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "c20b48b8-16d7-4089-bc17-f2d240b3935a", + "metadata": {}, + "source": [ + "### Create Index\n", + "\n", + "We will create a vectorstore over fake information." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1f621694", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_chroma import Chroma\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "texts = [\"Harrison worked at Kensho\", \"Ankush worked at Facebook\"]\n", + "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n", + "vectorstore = Chroma.from_texts(\n", + " texts,\n", + " embeddings,\n", + ")\n", + "retriever = vectorstore.as_retriever(search_kwargs={\"k\": 1})" + ] + }, + { + "cell_type": "markdown", + "id": "57396e23-c192-4d97-846b-5eacea4d6b8d", + "metadata": {}, + "source": [ + "## Query analysis\n", + "\n", + "We will use function calling to structure the output. We will let it return multiple queries." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0b51dd76-820d-41a4-98c8-893f6fe0d1ea", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Optional\n", + "\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class Search(BaseModel):\n", + " \"\"\"Search over a database of job records.\"\"\"\n", + "\n", + " queries: List[str] = Field(\n", + " ...,\n", + " description=\"Distinct queries to search for\",\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "783c03c3-8c72-4f88-9cf4-5829ce6745d6", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/harrisonchase/workplace/langchain/libs/core/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: The function `with_structured_output` is in beta. It is actively being worked on, so the API may change.\n", + " warn_beta(\n" + ] + } + ], + "source": [ + "from langchain_core.output_parsers.openai_tools import PydanticToolsParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "output_parser = PydanticToolsParser(tools=[Search])\n", + "\n", + "system = \"\"\"You have the ability to issue search queries to get information to help answer user information.\n", + "\n", + "If you need to look up two distinct pieces of information, you are allowed to do that!\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", + "structured_llm = llm.with_structured_output(Search)\n", + "query_analyzer = {\"question\": RunnablePassthrough()} | prompt | structured_llm" + ] + }, + { + "cell_type": "markdown", + "id": "b9564078", + "metadata": {}, + "source": [ + "We can see that this allows for creating multiple queries" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "bc1d3863", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(queries=['Harrison work location'])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\"where did Harrison Work\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "af62af17-4f90-4dbd-a8b4-dfff51f1db95", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(queries=['Harrison work place', 'Ankush work place'])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\"where did Harrison and ankush Work\")" + ] + }, + { + "cell_type": "markdown", + "id": "c7c65b2f-7881-45fc-a47b-a4eaaf48245f", + "metadata": {}, + "source": [ + "## Retrieval with query analysis\n", + "\n", + "So how would we include this in a chain? One thing that will make this a lot easier is if we call our retriever asyncronously - this will let us loop over the queries and not get blocked on the response time." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "1e047d87", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.runnables import chain" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "8dac7866", + "metadata": {}, + "outputs": [], + "source": [ + "@chain\n", + "async def custom_chain(question):\n", + " response = await query_analyzer.ainvoke(question)\n", + " docs = []\n", + " for query in response.queries:\n", + " new_docs = await retriever.ainvoke(query)\n", + " docs.extend(new_docs)\n", + " # You probably want to think about reranking or deduplicating documents here\n", + " # But that is a separate topic\n", + " return docs" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "232ad8a7-7990-4066-9228-d35a555f7293", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='Harrison worked at Kensho')]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await custom_chain.ainvoke(\"where did Harrison Work\")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "28e14ba5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='Harrison worked at Kensho'),\n", + " Document(page_content='Ankush worked at Facebook')]" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await custom_chain.ainvoke(\"where did Harrison and ankush Work\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88de5a36", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/query_multiple_retrievers.ipynb b/docs/versioned_docs/version-0.2.x/how_to/query_multiple_retrievers.ipynb new file mode 100644 index 0000000000000..f653d328b8a74 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/query_multiple_retrievers.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "df7d42b9-58a6-434c-a2d7-0b61142f6d3e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 5\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "f2195672-0cab-4967-ba8a-c6544635547d", + "metadata": {}, + "source": [ + "# How to handle multiple retrievers when doing query analysis\n", + "\n", + "Sometimes, a query analysis technique may allow for selection of which retriever to use. To use this, you will need to add some logic to select the retriever to do. We will show a simple example (using mock data) of how to do that." + ] + }, + { + "cell_type": "markdown", + "id": "a4079b57-4369-49c9-b2ad-c809b5408d7e", + "metadata": {}, + "source": [ + "## Setup\n", + "#### Install dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e168ef5c-e54e-49a6-8552-5502854a6f01", + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -qU langchain langchain-community langchain-openai langchain-chroma" + ] + }, + { + "cell_type": "markdown", + "id": "79d66a45-a05c-4d22-b011-b1cdbdfc8f9c", + "metadata": {}, + "source": [ + "#### Set environment variables\n", + "\n", + "We'll use OpenAI in this example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40e2979e-a818-4b96-ac25-039336f94319", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Optional, uncomment to trace runs with LangSmith. Sign up here: https://smith.langchain.com.\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "c20b48b8-16d7-4089-bc17-f2d240b3935a", + "metadata": {}, + "source": [ + "### Create Index\n", + "\n", + "We will create a vectorstore over fake information." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "1f621694", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_chroma import Chroma\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "texts = [\"Harrison worked at Kensho\"]\n", + "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n", + "vectorstore = Chroma.from_texts(texts, embeddings, collection_name=\"harrison\")\n", + "retriever_harrison = vectorstore.as_retriever(search_kwargs={\"k\": 1})\n", + "\n", + "texts = [\"Ankush worked at Facebook\"]\n", + "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n", + "vectorstore = Chroma.from_texts(texts, embeddings, collection_name=\"ankush\")\n", + "retriever_ankush = vectorstore.as_retriever(search_kwargs={\"k\": 1})" + ] + }, + { + "cell_type": "markdown", + "id": "57396e23-c192-4d97-846b-5eacea4d6b8d", + "metadata": {}, + "source": [ + "## Query analysis\n", + "\n", + "We will use function calling to structure the output. We will let it return multiple queries." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "0b51dd76-820d-41a4-98c8-893f6fe0d1ea", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Optional\n", + "\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class Search(BaseModel):\n", + " \"\"\"Search for information about a person.\"\"\"\n", + "\n", + " query: str = Field(\n", + " ...,\n", + " description=\"Query to look up\",\n", + " )\n", + " person: str = Field(\n", + " ...,\n", + " description=\"Person to look things up for. Should be `HARRISON` or `ANKUSH`.\",\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "783c03c3-8c72-4f88-9cf4-5829ce6745d6", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers.openai_tools import PydanticToolsParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "output_parser = PydanticToolsParser(tools=[Search])\n", + "\n", + "system = \"\"\"You have the ability to issue search queries to get information to help answer user information.\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", + "structured_llm = llm.with_structured_output(Search)\n", + "query_analyzer = {\"question\": RunnablePassthrough()} | prompt | structured_llm" + ] + }, + { + "cell_type": "markdown", + "id": "b9564078", + "metadata": {}, + "source": [ + "We can see that this allows for routing between retrievers" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "bc1d3863", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='workplace', person='HARRISON')" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\"where did Harrison Work\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "af62af17-4f90-4dbd-a8b4-dfff51f1db95", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Search(query='workplace', person='ANKUSH')" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\"where did ankush Work\")" + ] + }, + { + "cell_type": "markdown", + "id": "c7c65b2f-7881-45fc-a47b-a4eaaf48245f", + "metadata": {}, + "source": [ + "## Retrieval with query analysis\n", + "\n", + "So how would we include this in a chain? We just need some simple logic to select the retriever and pass in the search query" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1e047d87", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.runnables import chain" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "4ec0c7fe", + "metadata": {}, + "outputs": [], + "source": [ + "retrievers = {\n", + " \"HARRISON\": retriever_harrison,\n", + " \"ANKUSH\": retriever_ankush,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "8dac7866", + "metadata": {}, + "outputs": [], + "source": [ + "@chain\n", + "def custom_chain(question):\n", + " response = query_analyzer.invoke(question)\n", + " retriever = retrievers[response.person]\n", + " return retriever.invoke(response.query)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "232ad8a7-7990-4066-9228-d35a555f7293", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='Harrison worked at Kensho')]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "custom_chain.invoke(\"where did Harrison Work\")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "28e14ba5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='Ankush worked at Facebook')]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "custom_chain.invoke(\"where did ankush Work\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33338d4f", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/query_no_queries.ipynb b/docs/versioned_docs/version-0.2.x/how_to/query_no_queries.ipynb new file mode 100644 index 0000000000000..4aee699c9473b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/query_no_queries.ipynb @@ -0,0 +1,328 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "df7d42b9-58a6-434c-a2d7-0b61142f6d3e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 3\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "f2195672-0cab-4967-ba8a-c6544635547d", + "metadata": {}, + "source": [ + "# How to handle cases where no queries are generated\n", + "\n", + "Sometimes, a query analysis technique may allow for any number of queries to be generated - including no queries! In this case, our overall chain will need to inspect the result of the query analysis before deciding whether to call the retriever or not.\n", + "\n", + "We will use mock data for this example." + ] + }, + { + "cell_type": "markdown", + "id": "a4079b57-4369-49c9-b2ad-c809b5408d7e", + "metadata": {}, + "source": [ + "## Setup\n", + "#### Install dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e168ef5c-e54e-49a6-8552-5502854a6f01", + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -qU langchain langchain-community langchain-openai langchain-chroma" + ] + }, + { + "cell_type": "markdown", + "id": "79d66a45-a05c-4d22-b011-b1cdbdfc8f9c", + "metadata": {}, + "source": [ + "#### Set environment variables\n", + "\n", + "We'll use OpenAI in this example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40e2979e-a818-4b96-ac25-039336f94319", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# Optional, uncomment to trace runs with LangSmith. Sign up here: https://smith.langchain.com.\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "c20b48b8-16d7-4089-bc17-f2d240b3935a", + "metadata": {}, + "source": [ + "### Create Index\n", + "\n", + "We will create a vectorstore over fake information." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1f621694", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_chroma import Chroma\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "texts = [\"Harrison worked at Kensho\"]\n", + "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n", + "vectorstore = Chroma.from_texts(\n", + " texts,\n", + " embeddings,\n", + ")\n", + "retriever = vectorstore.as_retriever()" + ] + }, + { + "cell_type": "markdown", + "id": "57396e23-c192-4d97-846b-5eacea4d6b8d", + "metadata": {}, + "source": [ + "## Query analysis\n", + "\n", + "We will use function calling to structure the output. However, we will configure the LLM such that is doesn't NEED to call the function representing a search query (should it decide not to). We will also then use a prompt to do query analysis that explicitly lays when it should and shouldn't make a search." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0b51dd76-820d-41a4-98c8-893f6fe0d1ea", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Optional\n", + "\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class Search(BaseModel):\n", + " \"\"\"Search over a database of job records.\"\"\"\n", + "\n", + " query: str = Field(\n", + " ...,\n", + " description=\"Similarity search query applied to job record.\",\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "783c03c3-8c72-4f88-9cf4-5829ce6745d6", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "system = \"\"\"You have the ability to issue search queries to get information to help answer user information.\n", + "\n", + "You do not NEED to look things up. If you don't need to, then just respond normally.\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " (\"human\", \"{question}\"),\n", + " ]\n", + ")\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", + "structured_llm = llm.bind_tools([Search])\n", + "query_analyzer = {\"question\": RunnablePassthrough()} | prompt | structured_llm" + ] + }, + { + "cell_type": "markdown", + "id": "b9564078", + "metadata": {}, + "source": [ + "We can see that by invoking this we get an message that sometimes - but not always - returns a tool call." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "bc1d3863", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ZnoVX4j9Mn8wgChaORyd1cvq', 'function': {'arguments': '{\"query\":\"Harrison\"}', 'name': 'Search'}, 'type': 'function'}]})" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\"where did Harrison Work\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "af62af17-4f90-4dbd-a8b4-dfff51f1db95", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='Hello! How can I assist you today?')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_analyzer.invoke(\"hi!\")" + ] + }, + { + "cell_type": "markdown", + "id": "c7c65b2f-7881-45fc-a47b-a4eaaf48245f", + "metadata": {}, + "source": [ + "## Retrieval with query analysis\n", + "\n", + "So how would we include this in a chain? Let's look at an example below." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "1e047d87", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers.openai_tools import PydanticToolsParser\n", + "from langchain_core.runnables import chain\n", + "\n", + "output_parser = PydanticToolsParser(tools=[Search])" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8dac7866", + "metadata": {}, + "outputs": [], + "source": [ + "@chain\n", + "def custom_chain(question):\n", + " response = query_analyzer.invoke(question)\n", + " if \"tool_calls\" in response.additional_kwargs:\n", + " query = output_parser.invoke(response)\n", + " docs = retriever.invoke(query[0].query)\n", + " # Could add more logic - like another LLM call - here\n", + " return docs\n", + " else:\n", + " return response" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "232ad8a7-7990-4066-9228-d35a555f7293", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Number of requested results 4 is greater than number of elements in index 1, updating n_results = 1\n" + ] + }, + { + "data": { + "text/plain": [ + "[Document(page_content='Harrison worked at Kensho')]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "custom_chain.invoke(\"where did Harrison Work\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "28e14ba5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='Hello! How can I assist you today?')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "custom_chain.invoke(\"hi!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33338d4f", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/recursive_json_splitter.ipynb b/docs/versioned_docs/version-0.2.x/how_to/recursive_json_splitter.ipynb new file mode 100644 index 0000000000000..f9f8350ffeec8 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/recursive_json_splitter.ipynb @@ -0,0 +1,315 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a678d550", + "metadata": {}, + "source": [ + "# How to split JSON data\n", + "\n", + "This json splitter splits json data while allowing control over chunk sizes. It traverses json data depth first and builds smaller json chunks. It attempts to keep nested json objects whole but will split them if needed to keep chunks between a min_chunk_size and the max_chunk_size.\n", + "\n", + "If the value is not a nested json, but rather a very large string the string will not be split. If you need a hard cap on the chunk size consider composing this with a Recursive Text splitter on those chunks. There is an optional pre-processing step to split lists, by first converting them to json (dict) and then splitting them as such.\n", + "\n", + "1. How the text is split: json value.\n", + "2. How the chunk size is measured: by number of characters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f335e05-e5ae-44cc-899d-749aa9031a58", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -qU langchain-text-splitters" + ] + }, + { + "cell_type": "markdown", + "id": "a2b3fe87-d230-4cbd-b3ae-01559c5351a3", + "metadata": {}, + "source": [ + "First we load some json data:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3390ae1d", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "import requests\n", + "\n", + "# This is a large nested json object and will be loaded as a python dict\n", + "json_data = requests.get(\"https://api.smith.langchain.com/openapi.json\").json()" + ] + }, + { + "cell_type": "markdown", + "id": "3cdc725d-f4b8-4725-9084-cb395d8ef48b", + "metadata": {}, + "source": [ + "## Basic usage\n", + "\n", + "Specify `max_chunk_size` to constrain chunk sizes:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7bfe2c1e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_text_splitters import RecursiveJsonSplitter\n", + "\n", + "splitter = RecursiveJsonSplitter(max_chunk_size=300)" + ] + }, + { + "cell_type": "markdown", + "id": "e03b79fb-b1c6-4324-a409-86cd3e40cb92", + "metadata": {}, + "source": [ + "To obtain json chunks, use the `.split_json` method:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "69250bc6-c0f5-40d0-b8ba-7a349236bfd2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'openapi': '3.1.0', 'info': {'title': 'LangSmith', 'version': '0.1.0'}, 'servers': [{'url': 'https://api.smith.langchain.com', 'description': 'LangSmith API endpoint.'}]}\n", + "{'paths': {'/api/v1/sessions/{session_id}': {'get': {'tags': ['tracer-sessions'], 'summary': 'Read Tracer Session', 'description': 'Get a specific session.', 'operationId': 'read_tracer_session_api_v1_sessions__session_id__get'}}}}\n", + "{'paths': {'/api/v1/sessions/{session_id}': {'get': {'security': [{'API Key': []}, {'Tenant ID': []}, {'Bearer Auth': []}]}}}}\n" + ] + } + ], + "source": [ + "# Recursively split json data - If you need to access/manipulate the smaller json chunks\n", + "json_chunks = splitter.split_json(json_data=json_data)\n", + "\n", + "for chunk in json_chunks[:3]:\n", + " print(chunk)" + ] + }, + { + "cell_type": "markdown", + "id": "3f05bc21-227e-4d2c-af51-16d69ad3cd7b", + "metadata": {}, + "source": [ + "To obtain LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html) objects, use the `.create_documents` method:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0839f4f0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "page_content='{\"openapi\": \"3.1.0\", \"info\": {\"title\": \"LangSmith\", \"version\": \"0.1.0\"}, \"servers\": [{\"url\": \"https://api.smith.langchain.com\", \"description\": \"LangSmith API endpoint.\"}]}'\n", + "page_content='{\"paths\": {\"/api/v1/sessions/{session_id}\": {\"get\": {\"tags\": [\"tracer-sessions\"], \"summary\": \"Read Tracer Session\", \"description\": \"Get a specific session.\", \"operationId\": \"read_tracer_session_api_v1_sessions__session_id__get\"}}}}'\n", + "page_content='{\"paths\": {\"/api/v1/sessions/{session_id}\": {\"get\": {\"security\": [{\"API Key\": []}, {\"Tenant ID\": []}, {\"Bearer Auth\": []}]}}}}'\n" + ] + } + ], + "source": [ + "# The splitter can also output documents\n", + "docs = splitter.create_documents(texts=[json_data])\n", + "\n", + "for doc in docs[:3]:\n", + " print(doc)" + ] + }, + { + "cell_type": "markdown", + "id": "677c3dd0-afc7-488a-a58d-b7943814f85d", + "metadata": {}, + "source": [ + "Or use `.split_text` to obtain string content directly:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "fa0a4d66-b470-404e-918b-6728df3b88b0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"openapi\": \"3.1.0\", \"info\": {\"title\": \"LangSmith\", \"version\": \"0.1.0\"}, \"servers\": [{\"url\": \"https://api.smith.langchain.com\", \"description\": \"LangSmith API endpoint.\"}]}\n", + "{\"paths\": {\"/api/v1/sessions/{session_id}\": {\"get\": {\"tags\": [\"tracer-sessions\"], \"summary\": \"Read Tracer Session\", \"description\": \"Get a specific session.\", \"operationId\": \"read_tracer_session_api_v1_sessions__session_id__get\"}}}}\n" + ] + } + ], + "source": [ + "texts = splitter.split_text(json_data=json_data)\n", + "\n", + "print(texts[0])\n", + "print(texts[1])" + ] + }, + { + "cell_type": "markdown", + "id": "7070bf45-b885-4949-b8e0-7d1ea5205d2a", + "metadata": {}, + "source": [ + "## How to manage chunk sizes from list content\n", + "\n", + "Note that one of the chunks in this example is larger than the specified `max_chunk_size` of 300. Reviewing one of these chunks that was bigger we see there is a list object there:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "86ef3195-375b-4db2-9804-f3fa5a249417", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[171, 231, 126, 469, 210, 213, 237, 271, 191, 232]\n", + "\n", + "{\"paths\": {\"/api/v1/sessions/{session_id}\": {\"get\": {\"parameters\": [{\"name\": \"session_id\", \"in\": \"path\", \"required\": true, \"schema\": {\"type\": \"string\", \"format\": \"uuid\", \"title\": \"Session Id\"}}, {\"name\": \"include_stats\", \"in\": \"query\", \"required\": false, \"schema\": {\"type\": \"boolean\", \"default\": false, \"title\": \"Include Stats\"}}, {\"name\": \"accept\", \"in\": \"header\", \"required\": false, \"schema\": {\"anyOf\": [{\"type\": \"string\"}, {\"type\": \"null\"}], \"title\": \"Accept\"}}]}}}}\n" + ] + } + ], + "source": [ + "print([len(text) for text in texts][:10])\n", + "print()\n", + "print(texts[3])" + ] + }, + { + "cell_type": "markdown", + "id": "ddc98a1d-05df-48ab-8d17-6e4ee0d9d0cb", + "metadata": {}, + "source": [ + "The json splitter by default does not split lists.\n", + "\n", + "Specify `convert_lists=True` to preprocess the json, converting list content to dicts with `index:item` as `key:val` pairs:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "992477c2", + "metadata": {}, + "outputs": [], + "source": [ + "texts = splitter.split_text(json_data=json_data, convert_lists=True)" + ] + }, + { + "cell_type": "markdown", + "id": "912c20c2-8d05-47a6-bc03-f5c866761dff", + "metadata": {}, + "source": [ + "Let's look at the size of the chunks. Now they are all under the max" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7abd43f6-78ab-4a73-853a-a777ab268efc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[176, 236, 141, 203, 212, 221, 210, 213, 242, 291]\n" + ] + } + ], + "source": [ + "print([len(text) for text in texts][:10])" + ] + }, + { + "cell_type": "markdown", + "id": "3e5753bf-cede-4751-a1c0-c42aca56b88a", + "metadata": {}, + "source": [ + "The list has been converted to a dict, but retains all the needed contextual information even if split into many chunks:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d2c2773e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"paths\": {\"/api/v1/sessions/{session_id}\": {\"get\": {\"tags\": {\"0\": \"tracer-sessions\"}, \"summary\": \"Read Tracer Session\", \"description\": \"Get a specific session.\", \"operationId\": \"read_tracer_session_api_v1_sessions__session_id__get\"}}}}\n" + ] + } + ], + "source": [ + "print(texts[1])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8963b01a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Document(page_content='{\"paths\": {\"/api/v1/sessions/{session_id}\": {\"get\": {\"tags\": [\"tracer-sessions\"], \"summary\": \"Read Tracer Session\", \"description\": \"Get a specific session.\", \"operationId\": \"read_tracer_session_api_v1_sessions__session_id__get\"}}}}')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We can also look at the documents\n", + "docs[1]" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/recursive_text_splitter.ipynb b/docs/versioned_docs/version-0.2.x/how_to/recursive_text_splitter.ipynb new file mode 100644 index 0000000000000..fa0f6bb98f63e --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/recursive_text_splitter.ipynb @@ -0,0 +1,161 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a678d550", + "metadata": {}, + "source": [ + "# How to recursively split text by characters\n", + "\n", + "This text splitter is the recommended one for generic text. It is parameterized by a list of characters. It tries to split on them in order until the chunks are small enough. The default list is `[\"\\n\\n\", \"\\n\", \" \", \"\"]`. This has the effect of trying to keep all paragraphs (and then sentences, and then words) together as long as possible, as those would generically seem to be the strongest semantically related pieces of text.\n", + "\n", + "1. How the text is split: by list of characters.\n", + "2. How the chunk size is measured: by number of characters.\n", + "\n", + "Below we show example usage.\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": "9c16167c-1e56-4e11-9b8b-60f93044498e", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -qU langchain-text-splitters" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3390ae1d", + "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'\n", + "page_content='of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans.'\n" + ] + } + ], + "source": [ + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "# Load 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 = RecursiveCharacterTextSplitter(\n", + " # Set a really small chunk size, just to show.\n", + " chunk_size=100,\n", + " chunk_overlap=20,\n", + " length_function=len,\n", + " is_separator_regex=False,\n", + ")\n", + "texts = text_splitter.create_documents([state_of_the_union])\n", + "print(texts[0])\n", + "print(texts[1])" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0839f4f0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and',\n", + " 'of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans.']" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "text_splitter.split_text(state_of_the_union)[:2]" + ] + }, + { + "cell_type": "markdown", + "id": "60336622-b9d0-4172-816a-6cd1bb9ec481", + "metadata": {}, + "source": [ + "Let's go through the parameters set above for `RecursiveCharacterTextSplitter`:\n", + "- `chunk_size`: The maximum size of a chunk, where size is determined by the `length_function`.\n", + "- `chunk_overlap`: Target overlap between chunks. Overlapping chunks helps to mitigate loss of information when context is divided between chunks.\n", + "- `length_function`: Function determining the chunk size.\n", + "- `is_separator_regex`: Whether the separator list (defaulting to `[\"\\n\\n\", \"\\n\", \" \", \"\"]`) should be interpreted as regex." + ] + }, + { + "cell_type": "markdown", + "id": "2b74939c", + "metadata": {}, + "source": [ + "## Splitting text from languages without word boundaries\n", + "\n", + "Some writing systems do not have [word boundaries](https://en.wikipedia.org/wiki/Category:Writing_systems_without_word_boundaries), for example Chinese, Japanese, and Thai. Splitting text with the default separator list of `[\"\\n\\n\", \"\\n\", \" \", \"\"]` can cause words to be split between chunks. To keep words together, you can override the list of separators to include additional punctuation:\n", + "\n", + "* Add ASCII full-stop \"`.`\", [Unicode fullwidth](https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)) full stop \"`.`\" (used in Chinese text), and [ideographic full stop](https://en.wikipedia.org/wiki/CJK_Symbols_and_Punctuation) \"`。`\" (used in Japanese and Chinese)\n", + "* Add [Zero-width space](https://en.wikipedia.org/wiki/Zero-width_space) used in Thai, Myanmar, Kmer, and Japanese.\n", + "* Add ASCII comma \"`,`\", Unicode fullwidth comma \"`,`\", and Unicode ideographic comma \"`、`\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d48a8ef", + "metadata": {}, + "outputs": [], + "source": [ + "text_splitter = RecursiveCharacterTextSplitter(\n", + " separators=[\n", + " \"\\n\\n\",\n", + " \"\\n\",\n", + " \" \",\n", + " \".\",\n", + " \",\",\n", + " \"\\u200B\", # Zero-width space\n", + " \"\\uff0c\", # Fullwidth comma\n", + " \"\\u3001\", # Ideographic comma\n", + " \"\\uff0e\", # Fullwidth full stop\n", + " \"\\u3002\", # Ideographic full stop\n", + " \"\",\n", + " ],\n", + " # Existing args\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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/routing.ipynb b/docs/versioned_docs/version-0.2.x/how_to/routing.ipynb new file mode 100644 index 0000000000000..8fd0776edfdce --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/routing.ipynb @@ -0,0 +1,482 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "9e45e81c-e16e-4c6c-b6a3-2362e5193827", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 3\n", + "keywords: [RunnableBranch, LCEL]\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "4b47436a", + "metadata": {}, + "source": [ + "# How to route execution within a chain\n", + "\n", + "Routing allows you to create non-deterministic chains where the output of a previous step defines the next step. Routing can help provide structure and consistency around interactions with models by allowing you to define states and use information related to those states as context to model calls.\n", + "\n", + "There are two ways to perform routing:\n", + "\n", + "1. Conditionally return runnables from a [`RunnableLambda`](/docs/how_to/functions) (recommended)\n", + "2. Using a `RunnableBranch` (legacy)\n", + "\n", + "We'll illustrate both methods using a two step sequence where the first step classifies an input question as being about `LangChain`, `Anthropic`, or `Other`, then routes to a corresponding prompt chain.\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "c1c6edac", + "metadata": {}, + "source": [ + "## Example Setup\n", + "First, let's create a chain that will identify incoming questions as being about `LangChain`, `Anthropic`, or `Other`:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8a8a1967", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Anthropic'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_anthropic import ChatAnthropic\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import PromptTemplate\n", + "\n", + "chain = (\n", + " PromptTemplate.from_template(\n", + " \"\"\"Given the user question below, classify it as either being about `LangChain`, `Anthropic`, or `Other`.\n", + "\n", + "Do not respond with more than one word.\n", + "\n", + "\n", + "{question}\n", + "\n", + "\n", + "Classification:\"\"\"\n", + " )\n", + " | ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "chain.invoke({\"question\": \"how do I call Anthropic?\"})" + ] + }, + { + "cell_type": "markdown", + "id": "7655555f", + "metadata": {}, + "source": [ + "Now, let's create three sub chains:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "89d7722d", + "metadata": {}, + "outputs": [], + "source": [ + "langchain_chain = PromptTemplate.from_template(\n", + " \"\"\"You are an expert in langchain. \\\n", + "Always answer questions starting with \"As Harrison Chase told me\". \\\n", + "Respond to the following question:\n", + "\n", + "Question: {question}\n", + "Answer:\"\"\"\n", + ") | ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n", + "anthropic_chain = PromptTemplate.from_template(\n", + " \"\"\"You are an expert in anthropic. \\\n", + "Always answer questions starting with \"As Dario Amodei told me\". \\\n", + "Respond to the following question:\n", + "\n", + "Question: {question}\n", + "Answer:\"\"\"\n", + ") | ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n", + "general_chain = PromptTemplate.from_template(\n", + " \"\"\"Respond to the following question:\n", + "\n", + "Question: {question}\n", + "Answer:\"\"\"\n", + ") | ChatAnthropic(model_name=\"claude-3-haiku-20240307\")" + ] + }, + { + "cell_type": "markdown", + "id": "6d8d042c", + "metadata": {}, + "source": [ + "## Using a custom function (Recommended)\n", + "\n", + "You can also use a custom function to route between different outputs. Here's an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "687492da", + "metadata": {}, + "outputs": [], + "source": [ + "def route(info):\n", + " if \"anthropic\" in info[\"topic\"].lower():\n", + " return anthropic_chain\n", + " elif \"langchain\" in info[\"topic\"].lower():\n", + " return langchain_chain\n", + " else:\n", + " return general_chain" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "02a33c86", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.runnables import RunnableLambda\n", + "\n", + "full_chain = {\"topic\": chain, \"question\": lambda x: x[\"question\"]} | RunnableLambda(\n", + " route\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c2e977a4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content=\"As Dario Amodei told me, to use Anthropic, you can start by exploring the company's website and learning about their mission, values, and the different services and products they offer. Anthropic is focused on developing safe and ethical AI systems, so they have a strong emphasis on transparency and responsible AI development. \\n\\nDepending on your specific needs, you can look into Anthropic's AI research and development services, which cover areas like natural language processing, computer vision, and reinforcement learning. They also offer consulting and advisory services to help organizations navigate the challenges and opportunities of AI integration.\\n\\nAdditionally, Anthropic has released some open-source AI models and tools that you can explore and experiment with. These can be a great way to get hands-on experience with Anthropic's approach to AI development.\\n\\nOverall, Anthropic aims to be a reliable and trustworthy partner in the AI space, so I'd encourage you to reach out to them directly to discuss how they can best support your specific requirements.\", response_metadata={'id': 'msg_01CtLFgFSwvTaJomrihE87Ra', 'content': [ContentBlock(text=\"As Dario Amodei told me, to use Anthropic, you can start by exploring the company's website and learning about their mission, values, and the different services and products they offer. Anthropic is focused on developing safe and ethical AI systems, so they have a strong emphasis on transparency and responsible AI development. \\n\\nDepending on your specific needs, you can look into Anthropic's AI research and development services, which cover areas like natural language processing, computer vision, and reinforcement learning. They also offer consulting and advisory services to help organizations navigate the challenges and opportunities of AI integration.\\n\\nAdditionally, Anthropic has released some open-source AI models and tools that you can explore and experiment with. These can be a great way to get hands-on experience with Anthropic's approach to AI development.\\n\\nOverall, Anthropic aims to be a reliable and trustworthy partner in the AI space, so I'd encourage you to reach out to them directly to discuss how they can best support your specific requirements.\", type='text')], 'model': 'claude-3-haiku-20240307', 'role': 'assistant', 'stop_reason': 'end_turn', 'stop_sequence': None, 'type': 'message', 'usage': Usage(input_tokens=53, output_tokens=219)})" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "full_chain.invoke({\"question\": \"how do I use Anthropic?\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "48913dc6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content=\"As Harrison Chase told me, using LangChain involves a few key steps:\\n\\n1. **Set up your environment**: Install the necessary Python packages, including the LangChain library itself, as well as any other dependencies your application might require, such as language models or other integrations.\\n\\n2. **Understand the core concepts**: LangChain revolves around a few core concepts, like Agents, Chains, and Tools. Familiarize yourself with these concepts and how they work together to build powerful language-based applications.\\n\\n3. **Identify your use case**: Determine what kind of task or application you want to build using LangChain, such as a chatbot, a question-answering system, or a document summarization tool.\\n\\n4. **Choose the appropriate components**: Based on your use case, select the right LangChain components, such as agents, chains, and tools, to build your application.\\n\\n5. **Integrate with language models**: LangChain is designed to work seamlessly with various language models, such as OpenAI's GPT-3 or Anthropic's models. Connect your chosen language model to your LangChain application.\\n\\n6. **Implement your application logic**: Use LangChain's building blocks to implement the specific functionality of your application, such as prompting the language model, processing the response, and integrating with other services or data sources.\\n\\n7. **Test and iterate**: Thoroughly test your application, gather feedback, and iterate on your design and implementation to improve its performance and user experience.\\n\\nAs Harrison Chase emphasized, LangChain provides a flexible and powerful framework for building language-based applications, making it easier to leverage the capabilities of modern language models. By following these steps, you can get started with LangChain and create innovative solutions tailored to your specific needs.\", response_metadata={'id': 'msg_01H3UXAAHG4TwxJLpxwuuVU7', 'content': [ContentBlock(text=\"As Harrison Chase told me, using LangChain involves a few key steps:\\n\\n1. **Set up your environment**: Install the necessary Python packages, including the LangChain library itself, as well as any other dependencies your application might require, such as language models or other integrations.\\n\\n2. **Understand the core concepts**: LangChain revolves around a few core concepts, like Agents, Chains, and Tools. Familiarize yourself with these concepts and how they work together to build powerful language-based applications.\\n\\n3. **Identify your use case**: Determine what kind of task or application you want to build using LangChain, such as a chatbot, a question-answering system, or a document summarization tool.\\n\\n4. **Choose the appropriate components**: Based on your use case, select the right LangChain components, such as agents, chains, and tools, to build your application.\\n\\n5. **Integrate with language models**: LangChain is designed to work seamlessly with various language models, such as OpenAI's GPT-3 or Anthropic's models. Connect your chosen language model to your LangChain application.\\n\\n6. **Implement your application logic**: Use LangChain's building blocks to implement the specific functionality of your application, such as prompting the language model, processing the response, and integrating with other services or data sources.\\n\\n7. **Test and iterate**: Thoroughly test your application, gather feedback, and iterate on your design and implementation to improve its performance and user experience.\\n\\nAs Harrison Chase emphasized, LangChain provides a flexible and powerful framework for building language-based applications, making it easier to leverage the capabilities of modern language models. By following these steps, you can get started with LangChain and create innovative solutions tailored to your specific needs.\", type='text')], 'model': 'claude-3-haiku-20240307', 'role': 'assistant', 'stop_reason': 'end_turn', 'stop_sequence': None, 'type': 'message', 'usage': Usage(input_tokens=50, output_tokens=400)})" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "full_chain.invoke({\"question\": \"how do I use LangChain?\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a14d0dca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='4', response_metadata={'id': 'msg_01UAKP81jTZu9fyiyFYhsbHc', 'content': [ContentBlock(text='4', type='text')], 'model': 'claude-3-haiku-20240307', 'role': 'assistant', 'stop_reason': 'end_turn', 'stop_sequence': None, 'type': 'message', 'usage': Usage(input_tokens=28, output_tokens=5)})" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "full_chain.invoke({\"question\": \"whats 2 + 2\"})" + ] + }, + { + "cell_type": "markdown", + "id": "5147b827", + "metadata": {}, + "source": [ + "## Using a RunnableBranch\n", + "\n", + "A `RunnableBranch` is a special type of runnable that allows you to define a set of conditions and runnables to execute based on the input. It does **not** offer anything that you can't achieve in a custom function as described above, so we recommend using a custom function instead.\n", + "\n", + "A `RunnableBranch` is initialized with a list of (condition, runnable) pairs and a default runnable. It selects which branch by passing each condition the input it's invoked with. It selects the first condition to evaluate to True, and runs the corresponding runnable to that condition with the input. \n", + "\n", + "If no provided conditions match, it runs the default runnable.\n", + "\n", + "Here's an example of what it looks like in action:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "2a101418", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content=\"As Dario Amodei told me, to use Anthropic, you should first familiarize yourself with our mission and principles. Anthropic is committed to developing safe and beneficial artificial intelligence that can help solve important problems facing humanity. \\n\\nTo get started, I recommend exploring the resources on our website, which cover our research, products, and approach to AI development. You can also reach out to our team to learn more about how Anthropic's technology and services can support your specific needs.\\n\\nThe key is to engage with us in a way that aligns with our values of transparency, ethical AI, and a commitment to the wellbeing of humanity. We're here to collaborate and help you harness the power of advanced AI responsibly.\", response_metadata={'id': 'msg_0187BVnpniPDJnVvwf3M1LdY', 'content': [ContentBlock(text=\"As Dario Amodei told me, to use Anthropic, you should first familiarize yourself with our mission and principles. Anthropic is committed to developing safe and beneficial artificial intelligence that can help solve important problems facing humanity. \\n\\nTo get started, I recommend exploring the resources on our website, which cover our research, products, and approach to AI development. You can also reach out to our team to learn more about how Anthropic's technology and services can support your specific needs.\\n\\nThe key is to engage with us in a way that aligns with our values of transparency, ethical AI, and a commitment to the wellbeing of humanity. We're here to collaborate and help you harness the power of advanced AI responsibly.\", type='text')], 'model': 'claude-3-haiku-20240307', 'role': 'assistant', 'stop_reason': 'end_turn', 'stop_sequence': None, 'type': 'message', 'usage': Usage(input_tokens=53, output_tokens=160)})" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.runnables import RunnableBranch\n", + "\n", + "branch = RunnableBranch(\n", + " (lambda x: \"anthropic\" in x[\"topic\"].lower(), anthropic_chain),\n", + " (lambda x: \"langchain\" in x[\"topic\"].lower(), langchain_chain),\n", + " general_chain,\n", + ")\n", + "full_chain = {\"topic\": chain, \"question\": lambda x: x[\"question\"]} | branch\n", + "full_chain.invoke({\"question\": \"how do I use Anthropic?\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8d8caf9b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content=\"As Harrison Chase told me, using LangChain involves several key steps. First, you'll need to install the LangChain library and import the necessary modules. Then, you'll want to define your language model, any data sources you plan to use, and the specific tasks you want to accomplish, such as question answering, text generation, or agent-based reasoning. \\n\\nLangChain provides a flexible framework for building applications that leverage large language models. It includes abstractions for things like retrievers, prompts, and chains, which allow you to compose different components together to create powerful workflows. \\n\\nThe documentation on the LangChain website is excellent and covers many common use cases in detail. I'd recommend starting there to get a solid understanding of the core concepts and how to apply them to your specific needs. And of course, feel free to reach out if you have any other questions - I'm always happy to share more insights from my conversations with Harrison.\", response_metadata={'id': 'msg_01T1naS99wGPkEAP4LME8iAv', 'content': [ContentBlock(text=\"As Harrison Chase told me, using LangChain involves several key steps. First, you'll need to install the LangChain library and import the necessary modules. Then, you'll want to define your language model, any data sources you plan to use, and the specific tasks you want to accomplish, such as question answering, text generation, or agent-based reasoning. \\n\\nLangChain provides a flexible framework for building applications that leverage large language models. It includes abstractions for things like retrievers, prompts, and chains, which allow you to compose different components together to create powerful workflows. \\n\\nThe documentation on the LangChain website is excellent and covers many common use cases in detail. I'd recommend starting there to get a solid understanding of the core concepts and how to apply them to your specific needs. And of course, feel free to reach out if you have any other questions - I'm always happy to share more insights from my conversations with Harrison.\", type='text')], 'model': 'claude-3-haiku-20240307', 'role': 'assistant', 'stop_reason': 'end_turn', 'stop_sequence': None, 'type': 'message', 'usage': Usage(input_tokens=50, output_tokens=205)})" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "full_chain.invoke({\"question\": \"how do I use LangChain?\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "26159af7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='4', response_metadata={'id': 'msg_01T6T3TS6hRCtU8JayN93QEi', 'content': [ContentBlock(text='4', type='text')], 'model': 'claude-3-haiku-20240307', 'role': 'assistant', 'stop_reason': 'end_turn', 'stop_sequence': None, 'type': 'message', 'usage': Usage(input_tokens=28, output_tokens=5)})" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "full_chain.invoke({\"question\": \"whats 2 + 2\"})" + ] + }, + { + "cell_type": "markdown", + "id": "fa0f589d", + "metadata": {}, + "source": [ + "# Routing by semantic similarity\n", + "\n", + "One especially useful technique is to use embeddings to route a query to the most relevant prompt. Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a23457d7", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.utils.math import cosine_similarity\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import PromptTemplate\n", + "from langchain_core.runnables import RunnableLambda, RunnablePassthrough\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "physics_template = \"\"\"You are a very smart physics professor. \\\n", + "You are great at answering questions about physics in a concise and easy to understand manner. \\\n", + "When you don't know the answer to a question you admit that you don't know.\n", + "\n", + "Here is a question:\n", + "{query}\"\"\"\n", + "\n", + "math_template = \"\"\"You are a very good mathematician. You are great at answering math questions. \\\n", + "You are so good because you are able to break down hard problems into their component parts, \\\n", + "answer the component parts, and then put them together to answer the broader question.\n", + "\n", + "Here is a question:\n", + "{query}\"\"\"\n", + "\n", + "embeddings = OpenAIEmbeddings()\n", + "prompt_templates = [physics_template, math_template]\n", + "prompt_embeddings = embeddings.embed_documents(prompt_templates)\n", + "\n", + "\n", + "def prompt_router(input):\n", + " query_embedding = embeddings.embed_query(input[\"query\"])\n", + " similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]\n", + " most_similar = prompt_templates[similarity.argmax()]\n", + " print(\"Using MATH\" if most_similar == math_template else \"Using PHYSICS\")\n", + " return PromptTemplate.from_template(most_similar)\n", + "\n", + "\n", + "chain = (\n", + " {\"query\": RunnablePassthrough()}\n", + " | RunnableLambda(prompt_router)\n", + " | ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n", + " | StrOutputParser()\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "664bb851", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using PHYSICS\n", + "As a physics professor, I would be happy to provide a concise and easy-to-understand explanation of what a black hole is.\n", + "\n", + "A black hole is an incredibly dense region of space-time where the gravitational pull is so strong that nothing, not even light, can escape from it. This means that if you were to get too close to a black hole, you would be pulled in and crushed by the intense gravitational forces.\n", + "\n", + "The formation of a black hole occurs when a massive star, much larger than our Sun, reaches the end of its life and collapses in on itself. This collapse causes the matter to become extremely dense, and the gravitational force becomes so strong that it creates a point of no return, known as the event horizon.\n", + "\n", + "Beyond the event horizon, the laws of physics as we know them break down, and the intense gravitational forces create a singularity, which is a point of infinite density and curvature in space-time.\n", + "\n", + "Black holes are fascinating and mysterious objects, and there is still much to be learned about their properties and behavior. If I were unsure about any specific details or aspects of black holes, I would readily admit that I do not have a complete understanding and would encourage further research and investigation.\n" + ] + } + ], + "source": [ + "print(chain.invoke(\"What's a black hole\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "df34e469", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using MATH\n", + "A path integral is a powerful mathematical concept in physics, particularly in the field of quantum mechanics. It was developed by the renowned physicist Richard Feynman as an alternative formulation of quantum mechanics.\n", + "\n", + "In a path integral, instead of considering a single, definite path that a particle might take from one point to another, as in classical mechanics, the particle is considered to take all possible paths simultaneously. Each path is assigned a complex-valued weight, and the total probability amplitude for the particle to go from one point to another is calculated by summing (integrating) over all possible paths.\n", + "\n", + "The key ideas behind the path integral formulation are:\n", + "\n", + "1. Superposition principle: In quantum mechanics, particles can exist in a superposition of multiple states or paths simultaneously.\n", + "\n", + "2. Probability amplitude: The probability amplitude for a particle to go from one point to another is calculated by summing the complex-valued weights of all possible paths.\n", + "\n", + "3. Weighting of paths: Each path is assigned a weight based on the action (the time integral of the Lagrangian) along that path. Paths with lower action have a greater weight.\n", + "\n", + "4. Feynman's approach: Feynman developed the path integral formulation as an alternative to the traditional wave function approach in quantum mechanics, providing a more intuitive and conceptual understanding of quantum phenomena.\n", + "\n", + "The path integral approach is particularly useful in quantum field theory, where it provides a powerful framework for calculating transition probabilities and understanding the behavior of quantum systems. It has also found applications in various areas of physics, such as condensed matter, statistical mechanics, and even in finance (the path integral approach to option pricing).\n", + "\n", + "The mathematical construction of the path integral involves the use of advanced concepts from functional analysis and measure theory, making it a powerful and sophisticated tool in the physicist's arsenal.\n" + ] + } + ], + "source": [ + "print(chain.invoke(\"What's a path integral\"))" + ] + }, + { + "cell_type": "markdown", + "id": "ff40bcb3", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "You've now learned how to add routing to your composed LCEL chains.\n", + "\n", + "Next, check out the other how-to guides on runnables in this section." + ] + }, + { + "cell_type": "markdown", + "id": "927b7498", + "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": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/self_query.ipynb b/docs/versioned_docs/version-0.2.x/how_to/self_query.ipynb new file mode 100644 index 0000000000000..b344da270ec32 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/self_query.ipynb @@ -0,0 +1,569 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c0bc3390-4bed-49d3-96ce-072badb4110b", + "metadata": {}, + "source": [ + "# How to do \"self-querying\" retrieval\n", + "\n", + ":::info\n", + "\n", + "Head to [Integrations](/docs/integrations/retrievers/self_query) for documentation on vector stores with built-in support for self-querying.\n", + "\n", + ":::\n", + "\n", + "A self-querying retriever is one that, as the name suggests, has the ability to query itself. Specifically, given any natural language query, the retriever uses a query-constructing LLM chain to write a structured query and then applies that structured query to its underlying VectorStore. This allows the retriever to not only use the user-input query for semantic similarity comparison with the contents of stored documents but to also extract filters from the user query on the metadata of stored documents and to execute those filters.\n", + "\n", + "![](/static/img/self_querying.jpg)\n", + "\n", + "## Get started\n", + "For demonstration purposes we'll use a `Chroma` vector store. We've created a small demo set of documents that contain summaries of movies.\n", + "\n", + "**Note:** The self-query retriever requires you to have `lark` package installed." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e1486ca4-9785-4107-90bd-923505542167", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet lark langchain-chroma" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "beec3e35-3750-408c-9f2a-d92cf0a9a321", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_chroma import Chroma\n", + "from langchain_core.documents import Document\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "docs = [\n", + " Document(\n", + " page_content=\"A bunch of scientists bring back dinosaurs and mayhem breaks loose\",\n", + " metadata={\"year\": 1993, \"rating\": 7.7, \"genre\": \"science fiction\"},\n", + " ),\n", + " Document(\n", + " page_content=\"Leo DiCaprio gets lost in a dream within a dream within a dream within a ...\",\n", + " metadata={\"year\": 2010, \"director\": \"Christopher Nolan\", \"rating\": 8.2},\n", + " ),\n", + " Document(\n", + " page_content=\"A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea\",\n", + " metadata={\"year\": 2006, \"director\": \"Satoshi Kon\", \"rating\": 8.6},\n", + " ),\n", + " Document(\n", + " page_content=\"A bunch of normal-sized women are supremely wholesome and some men pine after them\",\n", + " metadata={\"year\": 2019, \"director\": \"Greta Gerwig\", \"rating\": 8.3},\n", + " ),\n", + " Document(\n", + " page_content=\"Toys come alive and have a blast doing so\",\n", + " metadata={\"year\": 1995, \"genre\": \"animated\"},\n", + " ),\n", + " Document(\n", + " page_content=\"Three men walk into the Zone, three men walk out of the Zone\",\n", + " metadata={\n", + " \"year\": 1979,\n", + " \"director\": \"Andrei Tarkovsky\",\n", + " \"genre\": \"thriller\",\n", + " \"rating\": 9.9,\n", + " },\n", + " ),\n", + "]\n", + "vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())" + ] + }, + { + "cell_type": "markdown", + "id": "99771131-1efb-42e2-95f8-2aaa95f37677", + "metadata": {}, + "source": [ + "### Creating our self-querying retriever\n", + "\n", + "Now we can instantiate our retriever. To do this we'll need to provide some information upfront about the metadata fields that our documents support and a short description of the document contents." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7832ca43-cc17-4375-bf4e-679b99584568", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains.query_constructor.base import AttributeInfo\n", + "from langchain.retrievers.self_query.base import SelfQueryRetriever\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "metadata_field_info = [\n", + " AttributeInfo(\n", + " name=\"genre\",\n", + " description=\"The genre of the movie. One of ['science fiction', 'comedy', 'drama', 'thriller', 'romance', 'action', 'animated']\",\n", + " type=\"string\",\n", + " ),\n", + " AttributeInfo(\n", + " name=\"year\",\n", + " description=\"The year the movie was released\",\n", + " type=\"integer\",\n", + " ),\n", + " AttributeInfo(\n", + " name=\"director\",\n", + " description=\"The name of the movie director\",\n", + " type=\"string\",\n", + " ),\n", + " AttributeInfo(\n", + " name=\"rating\", description=\"A 1-10 rating for the movie\", type=\"float\"\n", + " ),\n", + "]\n", + "document_content_description = \"Brief summary of a movie\"\n", + "llm = ChatOpenAI(temperature=0)\n", + "retriever = SelfQueryRetriever.from_llm(\n", + " llm,\n", + " vectorstore,\n", + " document_content_description,\n", + " metadata_field_info,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "9c66f4c8-3682-46ac-8f17-0839194888a3", + "metadata": {}, + "source": [ + "### Testing it out\n", + "\n", + "And now we can actually try using our retriever!" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "21c5df28-ea78-4f4e-99d6-489c864d1a04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='Three men walk into the Zone, three men walk out of the Zone', metadata={'director': 'Andrei Tarkovsky', 'genre': 'thriller', 'rating': 9.9, 'year': 1979}),\n", + " Document(page_content='A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea', metadata={'director': 'Satoshi Kon', 'rating': 8.6, 'year': 2006})]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This example only specifies a filter\n", + "retriever.invoke(\"I want to watch a movie rated higher than 8.5\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "228e5d70-d4cf-43bb-bc8e-3d6f11e784f2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='A bunch of normal-sized women are supremely wholesome and some men pine after them', metadata={'director': 'Greta Gerwig', 'rating': 8.3, 'year': 2019})]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This example specifies a query and a filter\n", + "retriever.invoke(\"Has Greta Gerwig directed any movies about women\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8244591e-97b5-4aba-b1e5-fe5e1996cb99", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea', metadata={'director': 'Satoshi Kon', 'rating': 8.6, 'year': 2006}),\n", + " Document(page_content='Three men walk into the Zone, three men walk out of the Zone', metadata={'director': 'Andrei Tarkovsky', 'genre': 'thriller', 'rating': 9.9, 'year': 1979})]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This example specifies a composite filter\n", + "retriever.invoke(\"What's a highly rated (above 8.5) science fiction film?\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "420a6906-66fb-449f-8626-2e399ae5e6a8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='Toys come alive and have a blast doing so', metadata={'genre': 'animated', 'year': 1995})]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This example specifies a query and composite filter\n", + "retriever.invoke(\n", + " \"What's a movie after 1990 but before 2005 that's all about toys, and preferably is animated\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4f25a751-f1d2-405e-84d6-fe9e4f60ce95", + "metadata": {}, + "source": [ + "### Filter k\n", + "\n", + "We can also use the self query retriever to specify `k`: the number of documents to fetch.\n", + "\n", + "We can do this by passing `enable_limit=True` to the constructor." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ab56595f-0fb4-4b7f-8fc1-e85eff13255a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='A bunch of scientists bring back dinosaurs and mayhem breaks loose', metadata={'genre': 'science fiction', 'rating': 7.7, 'year': 1993}),\n", + " Document(page_content='Toys come alive and have a blast doing so', metadata={'genre': 'animated', 'year': 1995})]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "retriever = SelfQueryRetriever.from_llm(\n", + " llm,\n", + " vectorstore,\n", + " document_content_description,\n", + " metadata_field_info,\n", + " enable_limit=True,\n", + ")\n", + "\n", + "# This example only specifies a relevant query\n", + "retriever.invoke(\"What are two movies about dinosaurs\")" + ] + }, + { + "cell_type": "markdown", + "id": "51e144c4-cbf4-4540-92e7-9a68e05f2480", + "metadata": {}, + "source": [ + "## Constructing from scratch with LCEL\n", + "\n", + "To see what's going on under the hood, and to have more custom control, we can reconstruct our retriever from scratch.\n", + "\n", + "First, we need to create a query-construction chain. This chain will take a user query and generated a `StructuredQuery` object which captures the filters specified by the user. We provide some helper functions for creating a prompt and output parser. These have a number of tunable params that we'll ignore here for simplicity." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c5f501ac-46c1-4a54-9d23-c0530e8c88f0", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains.query_constructor.base import (\n", + " StructuredQueryOutputParser,\n", + " get_query_constructor_prompt,\n", + ")\n", + "\n", + "prompt = get_query_constructor_prompt(\n", + " document_content_description,\n", + " metadata_field_info,\n", + ")\n", + "output_parser = StructuredQueryOutputParser.from_components()\n", + "query_constructor = prompt | llm | output_parser" + ] + }, + { + "cell_type": "markdown", + "id": "8deb5d44-632f-4f41-b139-fc811979e6e8", + "metadata": {}, + "source": [ + "Let's look at our prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "eed553cb-8575-486b-8349-0806b7817a8c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Your goal is to structure the user's query to match the request schema provided below.\n", + "\n", + "<< Structured Request Schema >>\n", + "When responding use a markdown code snippet with a JSON object formatted in the following schema:\n", + "\n", + "```json\n", + "{\n", + " \"query\": string \\ text string to compare to document contents\n", + " \"filter\": string \\ logical condition statement for filtering documents\n", + "}\n", + "```\n", + "\n", + "The query string should contain only text that is expected to match the contents of documents. Any conditions in the filter should not be mentioned in the query as well.\n", + "\n", + "A logical condition statement is composed of one or more comparison and logical operation statements.\n", + "\n", + "A comparison statement takes the form: `comp(attr, val)`:\n", + "- `comp` (eq | ne | gt | gte | lt | lte | contain | like | in | nin): comparator\n", + "- `attr` (string): name of attribute to apply the comparison to\n", + "- `val` (string): is the comparison value\n", + "\n", + "A logical operation statement takes the form `op(statement1, statement2, ...)`:\n", + "- `op` (and | or | not): logical operator\n", + "- `statement1`, `statement2`, ... (comparison statements or logical operation statements): one or more statements to apply the operation to\n", + "\n", + "Make sure that you only use the comparators and logical operators listed above and no others.\n", + "Make sure that filters only refer to attributes that exist in the data source.\n", + "Make sure that filters only use the attributed names with its function names if there are functions applied on them.\n", + "Make sure that filters only use format `YYYY-MM-DD` when handling date data typed values.\n", + "Make sure that filters take into account the descriptions of attributes and only make comparisons that are feasible given the type of data being stored.\n", + "Make sure that filters are only used as needed. If there are no filters that should be applied return \"NO_FILTER\" for the filter value.\n", + "\n", + "<< Example 1. >>\n", + "Data Source:\n", + "```json\n", + "{\n", + " \"content\": \"Lyrics of a song\",\n", + " \"attributes\": {\n", + " \"artist\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Name of the song artist\"\n", + " },\n", + " \"length\": {\n", + " \"type\": \"integer\",\n", + " \"description\": \"Length of the song in seconds\"\n", + " },\n", + " \"genre\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"The song genre, one of \"pop\", \"rock\" or \"rap\"\"\n", + " }\n", + " }\n", + "}\n", + "```\n", + "\n", + "User Query:\n", + "What are songs by Taylor Swift or Katy Perry about teenage romance under 3 minutes long in the dance pop genre\n", + "\n", + "Structured Request:\n", + "```json\n", + "{\n", + " \"query\": \"teenager love\",\n", + " \"filter\": \"and(or(eq(\\\"artist\\\", \\\"Taylor Swift\\\"), eq(\\\"artist\\\", \\\"Katy Perry\\\")), lt(\\\"length\\\", 180), eq(\\\"genre\\\", \\\"pop\\\"))\"\n", + "}\n", + "```\n", + "\n", + "\n", + "<< Example 2. >>\n", + "Data Source:\n", + "```json\n", + "{\n", + " \"content\": \"Lyrics of a song\",\n", + " \"attributes\": {\n", + " \"artist\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Name of the song artist\"\n", + " },\n", + " \"length\": {\n", + " \"type\": \"integer\",\n", + " \"description\": \"Length of the song in seconds\"\n", + " },\n", + " \"genre\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"The song genre, one of \"pop\", \"rock\" or \"rap\"\"\n", + " }\n", + " }\n", + "}\n", + "```\n", + "\n", + "User Query:\n", + "What are songs that were not published on Spotify\n", + "\n", + "Structured Request:\n", + "```json\n", + "{\n", + " \"query\": \"\",\n", + " \"filter\": \"NO_FILTER\"\n", + "}\n", + "```\n", + "\n", + "\n", + "<< Example 3. >>\n", + "Data Source:\n", + "```json\n", + "{\n", + " \"content\": \"Brief summary of a movie\",\n", + " \"attributes\": {\n", + " \"genre\": {\n", + " \"description\": \"The genre of the movie. One of ['science fiction', 'comedy', 'drama', 'thriller', 'romance', 'action', 'animated']\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"year\": {\n", + " \"description\": \"The year the movie was released\",\n", + " \"type\": \"integer\"\n", + " },\n", + " \"director\": {\n", + " \"description\": \"The name of the movie director\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"rating\": {\n", + " \"description\": \"A 1-10 rating for the movie\",\n", + " \"type\": \"float\"\n", + " }\n", + "}\n", + "}\n", + "```\n", + "\n", + "User Query:\n", + "dummy question\n", + "\n", + "Structured Request:\n", + "\n" + ] + } + ], + "source": [ + "print(prompt.format(query=\"dummy question\"))" + ] + }, + { + "cell_type": "markdown", + "id": "00420512-c395-4661-8d07-c7f6f1b45793", + "metadata": {}, + "source": [ + "And what our full chain produces:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "139cce01-ca75-452b-8de2-033ceec27158", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "StructuredQuery(query='taxi driver', filter=Operation(operator=, arguments=[Comparison(comparator=, attribute='genre', value='science fiction'), Operation(operator=, arguments=[Comparison(comparator=, attribute='year', value=1990), Comparison(comparator=, attribute='year', value=2000)]), Comparison(comparator=, attribute='director', value='Luc Besson')]), limit=None)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_constructor.invoke(\n", + " {\n", + " \"query\": \"What are some sci-fi movies from the 90's directed by Luc Besson about taxi drivers\"\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "9582a5fa-ffed-4d50-ad74-9b12d7d94b2a", + "metadata": {}, + "source": [ + "The query constructor is the key element of the self-query retriever. To make a great retrieval system you'll need to make sure your query constructor works well. Often this requires adjusting the prompt, the examples in the prompt, the attribute descriptions, etc. For an example that walks through refining a query constructor on some hotel inventory data, [check out this cookbook](https://github.com/langchain-ai/langchain/blob/master/cookbook/self_query_hotel_search.ipynb).\n", + "\n", + "The next key element is the structured query translator. This is the object responsible for translating the generic `StructuredQuery` object into a metadata filter in the syntax of the vector store you're using. LangChain comes with a number of built-in translators. To see them all head to the [Integrations section](/docs/integrations/retrievers/self_query)." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "05f07ead-9aac-4079-9dde-784cb7aa1a8a", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.retrievers.self_query.chroma import ChromaTranslator\n", + "\n", + "retriever = SelfQueryRetriever(\n", + " query_constructor=query_constructor,\n", + " vectorstore=vectorstore,\n", + " structured_query_translator=ChromaTranslator(),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0ee155c9-7b02-4fe9-8de3-e37385c465af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='Toys come alive and have a blast doing so', metadata={'genre': 'animated', 'year': 1995})]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "retriever.invoke(\n", + " \"What's a movie after 1990 but before 2005 that's all about toys, and preferably is animated\"\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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/semantic-chunker.ipynb b/docs/versioned_docs/version-0.2.x/how_to/semantic-chunker.ipynb new file mode 100644 index 0000000000000..d3d5fd44556ba --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/semantic-chunker.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c3ee8d00", + "metadata": {}, + "source": [ + "# How to split text based on semantic similarity\n", + "\n", + "Taken from Greg Kamradt's wonderful notebook:\n", + "[5_Levels_Of_Text_Splitting](https://github.com/FullStackRetrieval-com/RetrievalTutorials/blob/main/tutorials/LevelsOfTextSplitting/5_Levels_Of_Text_Splitting.ipynb)\n", + "\n", + "All credit to him.\n", + "\n", + "This guide covers how to split chunks based on their semantic similarity. If embeddings are sufficiently far apart, chunks are split.\n", + "\n", + "At a high level, this splits into sentences, then groups into groups of 3\n", + "sentences, and then merges one that are similar in the embedding space." + ] + }, + { + "cell_type": "markdown", + "id": "542f4427", + "metadata": {}, + "source": [ + "## Install Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8c58769", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install --quiet langchain_experimental langchain_openai" + ] + }, + { + "cell_type": "markdown", + "id": "c20cdf54", + "metadata": {}, + "source": [ + "## Load Example Data" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "313fb032", + "metadata": {}, + "outputs": [], + "source": [ + "# This is a long document we can split up.\n", + "with open(\"../../../docs/modules/state_of_the_union.txt\") as f:\n", + " state_of_the_union = f.read()" + ] + }, + { + "cell_type": "markdown", + "id": "f7436e15", + "metadata": {}, + "source": [ + "## Create Text Splitter" + ] + }, + { + "cell_type": "markdown", + "id": "774a5199-c2ff-43bc-bf07-87573e0b8db4", + "metadata": {}, + "source": [ + "To instantiate a [SemanticChunker](https://api.python.langchain.com/en/latest/text_splitter/langchain_experimental.text_splitter.SemanticChunker.html), we must specify an embedding model. Below we will use [OpenAIEmbeddings](https://api.python.langchain.com/en/latest/embeddings/langchain_community.embeddings.openai.OpenAIEmbeddings.html). " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a88ff70c", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_experimental.text_splitter import SemanticChunker\n", + "from langchain_openai.embeddings import OpenAIEmbeddings\n", + "\n", + "text_splitter = SemanticChunker(OpenAIEmbeddings())" + ] + }, + { + "cell_type": "markdown", + "id": "91b14834", + "metadata": {}, + "source": [ + "## Split Text\n", + "\n", + "We split text in the usual way, e.g., by invoking `.create_documents` to create LangChain [Document](https://api.python.langchain.com/en/latest/documents/langchain_core.documents.base.Document.html) objects:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "295ec095", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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. Last year COVID-19 kept us apart. This year we are finally together again. Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. With a duty to one another to the American people to the Constitution. And with an unwavering resolve that freedom will always triumph over tyranny. Six days ago, Russia’s 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. He thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. He met the Ukrainian people. From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight. Let each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world. Please rise if you are able and show that, Yes, we the United States of America stand with the Ukrainian people. Throughout our history we’ve learned this lesson when dictators do not pay a price for their aggression they cause more chaos. They keep moving.\n" + ] + } + ], + "source": [ + "docs = text_splitter.create_documents([state_of_the_union])\n", + "print(docs[0].page_content)" + ] + }, + { + "cell_type": "markdown", + "id": "9aed73b2", + "metadata": {}, + "source": [ + "## Breakpoints\n", + "\n", + "This chunker works by determining when to \"break\" apart sentences. This is done by looking for differences in embeddings between any two sentences. When that difference is past some threshold, then they are split.\n", + "\n", + "There are a few ways to determine what that threshold is, which are controlled by the `breakpoint_threshold_type` kwarg.\n", + "\n", + "### Percentile\n", + "\n", + "The default way to split is based on percentile. In this method, all differences between sentences are calculated, and then any difference greater than the X percentile is split." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a9a3b9cd", + "metadata": {}, + "outputs": [], + "source": [ + "text_splitter = SemanticChunker(\n", + " OpenAIEmbeddings(), breakpoint_threshold_type=\"percentile\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f311e67e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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. Last year COVID-19 kept us apart. This year we are finally together again. Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. With a duty to one another to the American people to the Constitution. And with an unwavering resolve that freedom will always triumph over tyranny. Six days ago, Russia’s 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. He thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. He met the Ukrainian people. From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight. Let each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world. Please rise if you are able and show that, Yes, we the United States of America stand with the Ukrainian people. Throughout our history we’ve learned this lesson when dictators do not pay a price for their aggression they cause more chaos. They keep moving.\n" + ] + } + ], + "source": [ + "docs = text_splitter.create_documents([state_of_the_union])\n", + "print(docs[0].page_content)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "5f5930de", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "26\n" + ] + } + ], + "source": [ + "print(len(docs))" + ] + }, + { + "cell_type": "markdown", + "id": "b6b51104", + "metadata": {}, + "source": [ + "### Standard Deviation\n", + "\n", + "In this method, any difference greater than X standard deviations is split." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "ff5e005c", + "metadata": {}, + "outputs": [], + "source": [ + "text_splitter = SemanticChunker(\n", + " OpenAIEmbeddings(), breakpoint_threshold_type=\"standard_deviation\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "01b8ffc0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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. Last year COVID-19 kept us apart. This year we are finally together again. Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. With a duty to one another to the American people to the Constitution. And with an unwavering resolve that freedom will always triumph over tyranny. Six days ago, Russia’s 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. He thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. He met the Ukrainian people. From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight. Let each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world. Please rise if you are able and show that, Yes, we the United States of America stand with the Ukrainian people. Throughout our history we’ve learned this lesson when dictators do not pay a price for their aggression they cause more chaos. They keep moving. And the costs and the threats to America and the world keep rising. That’s why the NATO Alliance was created to secure peace and stability in Europe after World War 2. The United States is a member along with 29 other nations. It matters. American diplomacy matters. American resolve matters. Putin’s latest attack on Ukraine was premeditated and unprovoked. He rejected repeated efforts at diplomacy. He thought the West and NATO wouldn’t respond. And he thought he could divide us at home. Putin was wrong. We were ready. Here is what we did. We prepared extensively and carefully. We spent months building a coalition of other freedom-loving nations from Europe and the Americas to Asia and Africa to confront Putin. I spent countless hours unifying our European allies. We shared with the world in advance what we knew Putin was planning and precisely how he would try to falsely justify his aggression. We countered Russia’s lies with truth. And now that he has acted the free world is holding him accountable. Along with twenty-seven members of the European Union including France, Germany, Italy, as well as countries like the United Kingdom, Canada, Japan, Korea, Australia, New Zealand, and many others, even Switzerland. We are inflicting pain on Russia and supporting the people of Ukraine. Putin is now isolated from the world more than ever. Together with our allies –we are right now enforcing powerful economic sanctions. We are cutting off Russia’s largest banks from the international financial system. Preventing Russia’s central bank from defending the Russian Ruble making Putin’s $630 Billion “war fund” worthless. We are choking off Russia’s access to technology that will sap its economic strength and weaken its military for years to come. Tonight I say to the Russian oligarchs and corrupt leaders who have bilked billions of dollars off this violent regime no more. The U.S. Department of Justice is assembling a dedicated task force to go after the crimes of Russian oligarchs. We are joining with our European allies to find and seize your yachts your luxury apartments your private jets. We are coming for your ill-begotten gains. And tonight I am announcing that we will join our allies in closing off American air space to all Russian flights – further isolating Russia – and adding an additional squeeze –on their economy. The Ruble has lost 30% of its value. The Russian stock market has lost 40% of its value and trading remains suspended. Russia’s economy is reeling and Putin alone is to blame. Together with our allies we are providing support to the Ukrainians in their fight for freedom. Military assistance. Economic assistance. Humanitarian assistance. We are giving more than $1 Billion in direct assistance to Ukraine. And we will continue to aid the Ukrainian people as they defend their country and to help ease their suffering. Let me be clear, our forces are not engaged and will not engage in conflict with Russian forces in Ukraine. Our forces are not going to Europe to fight in Ukraine, but to defend our NATO Allies – in the event that Putin decides to keep moving west. For that purpose we’ve mobilized American ground forces, air squadrons, and ship deployments to protect NATO countries including Poland, Romania, Latvia, Lithuania, and Estonia. As I have made crystal clear the United States and our Allies will defend every inch of territory of NATO countries with the full force of our collective power. And we remain clear-eyed. The Ukrainians are fighting back with pure courage. But the next few days weeks, months, will be hard on them. Putin has unleashed violence and chaos. But while he may make gains on the battlefield – he will pay a continuing high price over the long run. And a proud Ukrainian people, who have known 30 years of independence, have repeatedly shown that they will not tolerate anyone who tries to take their country backwards. To all Americans, I will be honest with you, as I’ve always promised. A Russian dictator, invading a foreign country, has costs around the world. And I’m taking robust action to make sure the pain of our sanctions is targeted at Russia’s economy. And I will use every tool at our disposal to protect American businesses and consumers. Tonight, I can announce that the United States has worked with 30 other countries to release 60 Million barrels of oil from reserves around the world. America will lead that effort, releasing 30 Million barrels from our own Strategic Petroleum Reserve. And we stand ready to do more if necessary, unified with our allies. These steps will help blunt gas prices here at home. And I know the news about what’s happening can seem alarming.\n" + ] + } + ], + "source": [ + "docs = text_splitter.create_documents([state_of_the_union])\n", + "print(docs[0].page_content)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "8938a5e3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n" + ] + } + ], + "source": [ + "print(len(docs))" + ] + }, + { + "cell_type": "markdown", + "id": "6897261f", + "metadata": {}, + "source": [ + "### Interquartile\n", + "\n", + "In this method, the interquartile distance is used to split chunks." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8977355b", + "metadata": {}, + "outputs": [], + "source": [ + "text_splitter = SemanticChunker(\n", + " OpenAIEmbeddings(), breakpoint_threshold_type=\"interquartile\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "59a40364", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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. Last year COVID-19 kept us apart. This year we are finally together again. Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. With a duty to one another to the American people to the Constitution. And with an unwavering resolve that freedom will always triumph over tyranny. Six days ago, Russia’s 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. He thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. He met the Ukrainian people. From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland. In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight. Let each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world. Please rise if you are able and show that, Yes, we the United States of America stand with the Ukrainian people. Throughout our history we’ve learned this lesson when dictators do not pay a price for their aggression they cause more chaos. They keep moving.\n" + ] + } + ], + "source": [ + "docs = text_splitter.create_documents([state_of_the_union])\n", + "print(docs[0].page_content)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "3a0db107", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "25\n" + ] + } + ], + "source": [ + "print(len(docs))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1f65472", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/sequence.ipynb b/docs/versioned_docs/version-0.2.x/how_to/sequence.ipynb new file mode 100644 index 0000000000000..37b479ecf55f0 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/sequence.ipynb @@ -0,0 +1,263 @@ +{ + "cells": [ + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 0\n", + "keywords: [Runnable, Runnables, LCEL]\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to chain runnables\n", + "\n", + "One point about [LangChain Expression Language](/docs/concepts/#langchain-expression-language) is that any two runnables can be \"chained\" together into sequences. The output of the previous runnable's `.invoke()` call is passed as input to the next runnable. This can be done using the pipe operator (`|`), or the more explicit `.pipe()` method, which does the same thing.\n", + "\n", + "The resulting [`RunnableSequence`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableSequence.html) is itself a runnable, which means it can be invoked, streamed, or further chained just like any other runnable. Advantages of chaining runnables in this way are efficient streaming (the sequence will stream output as soon as it is available), and debugging and tracing with tools like [LangSmith](/docs/how_to/debugging).\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```\n", + "\n", + "## The pipe operator\n", + "\n", + "To show off how this works, let's go through an example. We'll walk through a common pattern in LangChain: using a [prompt template](/docs/modules/model_io/prompts/) to format input into a [chat model](/docs/modules/model_io/chat/), and finally converting the chat message output into a string with an [output parser](/docs/modules/model_io/output_parsers/).\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "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-sonnet-20240229\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "prompt = ChatPromptTemplate.from_template(\"tell me a joke about {topic}\")\n", + "\n", + "chain = prompt | model | StrOutputParser()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Prompts and models are both runnable, and the output type from the prompt call is the same as the input type of the chat model, so we can chain them together. We can then invoke the resulting sequence like any other runnable:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Here's a bear joke for you:\\n\\nWhy did the bear dissolve in water?\\nBecause it was a polar bear!\"" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke({\"topic\": \"bears\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coercion\n", + "\n", + "We can even combine this chain with more runnables to create another chain. This may involve some input/output formatting using other types of runnables, depending on the required inputs and outputs of the chain components.\n", + "\n", + "For example, let's say we wanted to compose the joke generating chain with another chain that evaluates whether or not the generated joke was funny.\n", + "\n", + "We would need to be careful with how we format the input into the next chain. In the below example, the dict in the chain is automatically parsed and converted into a [`RunnableParallel`](/docs/how_to/parallel), which runs all of its values in parallel and returns a dict with the results.\n", + "\n", + "This happens to be the same format the next prompt template expects. Here it is in action:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Haha, that\\'s a clever play on words! Using \"polar\" to imply the bear dissolved or became polar/polarized when put in water. Not the most hilarious joke ever, but it has a cute, groan-worthy pun that makes it mildly amusing. I appreciate a good pun or wordplay joke.'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "\n", + "analysis_prompt = ChatPromptTemplate.from_template(\"is this a funny joke? {joke}\")\n", + "\n", + "composed_chain = {\"joke\": chain} | analysis_prompt | model | StrOutputParser()\n", + "\n", + "composed_chain.invoke({\"topic\": \"bears\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Functions will also be coerced into runnables, so you can add custom logic to your chains too. The below chain results in the same logical flow as before:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Haha, that's a cute and punny joke! I like how it plays on the idea of beets blushing or turning red like someone blushing. Food puns can be quite amusing. While not a total knee-slapper, it's a light-hearted, groan-worthy dad joke that would make me chuckle and shake my head. Simple vegetable humor!\"" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "composed_chain_with_lambda = (\n", + " chain\n", + " | (lambda input: {\"joke\": input})\n", + " | analysis_prompt\n", + " | model\n", + " | StrOutputParser()\n", + ")\n", + "\n", + "composed_chain_with_lambda.invoke({\"topic\": \"beets\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, keep in mind that using functions like this may interfere with operations like streaming. See [this section](/docs/how_to/functions) for more information." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The `.pipe()` method\n", + "\n", + "We could also compose the same sequence using the `.pipe()` method. Here's what that looks like:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"I cannot reproduce any copyrighted material verbatim, but I can try to analyze the humor in the joke you provided without quoting it directly.\\n\\nThe joke plays on the idea that the Cylon raiders, who are the antagonists in the Battlestar Galactica universe, failed to locate the human survivors after attacking their home planets (the Twelve Colonies) due to using an outdated and poorly performing operating system (Windows Vista) for their targeting systems.\\n\\nThe humor stems from the juxtaposition of a futuristic science fiction setting with a relatable real-world frustration – the use of buggy, slow, or unreliable software or technology. It pokes fun at the perceived inadequacies of Windows Vista, which was widely criticized for its performance issues and other problems when it was released.\\n\\nBy attributing the Cylons' failure to locate the humans to their use of Vista, the joke creates an amusing and unexpected connection between a fictional advanced race of robots and a familiar technological annoyance experienced by many people in the real world.\\n\\nOverall, the joke relies on incongruity and relatability to generate humor, but without reproducing any copyrighted material directly.\"" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.runnables import RunnableParallel\n", + "\n", + "composed_chain_with_pipe = (\n", + " RunnableParallel({\"joke\": chain})\n", + " .pipe(analysis_prompt)\n", + " .pipe(model)\n", + " .pipe(StrOutputParser())\n", + ")\n", + "\n", + "composed_chain_with_pipe.invoke({\"topic\": \"battlestar galactica\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "You now know some ways to chain two runnables together.\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": 2 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/split_by_token.ipynb b/docs/versioned_docs/version-0.2.x/how_to/split_by_token.ipynb new file mode 100644 index 0000000000000..64d265d6f2a87 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/split_by_token.ipynb @@ -0,0 +1,676 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a05c860c", + "metadata": {}, + "source": [ + "# How to split text by tokens \n", + "\n", + "Language models have a token limit. You should not exceed the token limit. When you split your text into chunks it is therefore a good idea to count the number of tokens. There are many tokenizers. When you count tokens in your text you should use the same tokenizer as used in the language model. " + ] + }, + { + "cell_type": "markdown", + "id": "7683b36a", + "metadata": {}, + "source": [ + "## tiktoken\n", + "\n", + ":::{.callout-note}\n", + "[tiktoken](https://github.com/openai/tiktoken) is a fast `BPE` tokenizer created by `OpenAI`.\n", + ":::\n", + "\n", + "\n", + "We can use `tiktoken` to estimate tokens used. It will probably be more accurate for the OpenAI models.\n", + "\n", + "1. How the text is split: by character passed in.\n", + "2. How the chunk size is measured: by `tiktoken` tokenizer.\n", + "\n", + "[CharacterTextSplitter](https://api.python.langchain.com/en/latest/character/langchain_text_splitters.character.CharacterTextSplitter.html), [RecursiveCharacterTextSplitter](https://api.python.langchain.com/en/latest/character/langchain_text_splitters.character.RecursiveCharacterTextSplitter.html), and [TokenTextSplitter](https://api.python.langchain.com/en/latest/base/langchain_text_splitters.base.TokenTextSplitter.html) can be used with `tiktoken` directly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c4ef83e-f43a-4658-ad1a-3952e0a5bbe7", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain-text-splitters tiktoken" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1ad2d0f2", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_text_splitters import CharacterTextSplitter\n", + "\n", + "# This is a long document we can split up.\n", + "with open(\"../../../docs/modules/state_of_the_union.txt\") as f:\n", + " state_of_the_union = f.read()" + ] + }, + { + "cell_type": "markdown", + "id": "a3ba1d8a", + "metadata": {}, + "source": [ + "To split with a [CharacterTextSplitter](https://api.python.langchain.com/en/latest/character/langchain_text_splitters.character.CharacterTextSplitter.html) and then merge chunks with `tiktoken`, use its `.from_tiktoken_encoder()` method. Note that splits from this method can be larger than the chunk size measured by the `tiktoken` tokenizer.\n", + "\n", + "The `.from_tiktoken_encoder()` method takes either `encoding_name` as an argument (e.g. `cl100k_base`), or the `model_name` (e.g. `gpt-4`). All additional arguments like `chunk_size`, `chunk_overlap`, and `separators` are used to instantiate `CharacterTextSplitter`:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "825f7c0a", + "metadata": {}, + "outputs": [], + "source": [ + "text_splitter = CharacterTextSplitter.from_tiktoken_encoder(\n", + " encoding_name=\"cl100k_base\", chunk_size=100, chunk_overlap=0\n", + ")\n", + "texts = text_splitter.split_text(state_of_the_union)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ae35d165", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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", + "\n", + "Last year COVID-19 kept us apart. This year we are finally together again. \n", + "\n", + "Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n", + "\n", + "With a duty to one another to the American people to the Constitution.\n" + ] + } + ], + "source": [ + "print(texts[0])" + ] + }, + { + "cell_type": "markdown", + "id": "de5b6a6e", + "metadata": {}, + "source": [ + "To implement a hard constraint on the chunk size, we can use `RecursiveCharacterTextSplitter.from_tiktoken_encoder`, where each split will be recursively split if it has a larger size:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0262a991", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(\n", + " model_name=\"gpt-4\",\n", + " chunk_size=100,\n", + " chunk_overlap=0,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "04457e3a", + "metadata": {}, + "source": [ + "We can also load a `TokenTextSplitter` splitter, which works with `tiktoken` directly and will ensure each split is smaller than chunk size." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4454c70e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Madam Speaker, Madam Vice President, our\n" + ] + } + ], + "source": [ + "from langchain_text_splitters import TokenTextSplitter\n", + "\n", + "text_splitter = TokenTextSplitter(chunk_size=10, chunk_overlap=0)\n", + "\n", + "texts = text_splitter.split_text(state_of_the_union)\n", + "print(texts[0])" + ] + }, + { + "cell_type": "markdown", + "id": "3bc155d0", + "metadata": {}, + "source": [ + "Some written languages (e.g. Chinese and Japanese) have characters which encode to 2 or more tokens. Using the `TokenTextSplitter` directly can split the tokens for a character between two chunks causing malformed Unicode characters. Use `RecursiveCharacterTextSplitter.from_tiktoken_encoder` or `CharacterTextSplitter.from_tiktoken_encoder` to ensure chunks contain valid Unicode strings." + ] + }, + { + "cell_type": "markdown", + "id": "55f95f06", + "metadata": {}, + "source": [ + "## spaCy\n", + "\n", + ":::{.callout-note}\n", + "[spaCy](https://spacy.io/) is an open-source software library for advanced natural language processing, written in the programming languages Python and Cython.\n", + ":::\n", + "\n", + "LangChain implements splitters based on the [spaCy tokenizer](https://spacy.io/api/tokenizer).\n", + "\n", + "1. How the text is split: by `spaCy` tokenizer.\n", + "2. How the chunk size is measured: by number of characters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0b9242f-690c-4819-b35a-bb68187281ed", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet spacy" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f1de7767", + "metadata": {}, + "outputs": [], + "source": [ + "# This is a long document we can split up.\n", + "with open(\"../../../docs/modules/state_of_the_union.txt\") as f:\n", + " state_of_the_union = f.read()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cef2b29e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Madam Speaker, Madam Vice President, our First Lady and Second Gentleman.\n", + "\n", + "Members of Congress and the Cabinet.\n", + "\n", + "Justices of the Supreme Court.\n", + "\n", + "My fellow Americans. \n", + "\n", + "\n", + "\n", + "Last year COVID-19 kept us apart.\n", + "\n", + "This year we are finally together again. \n", + "\n", + "\n", + "\n", + "Tonight, we meet as Democrats Republicans and Independents.\n", + "\n", + "But most importantly as Americans. \n", + "\n", + "\n", + "\n", + "With a duty to one another to the American people to the Constitution. \n", + "\n", + "\n", + "\n", + "And with an unwavering resolve that freedom will always triumph over tyranny. \n", + "\n", + "\n", + "\n", + "Six days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways.\n", + "\n", + "But he badly miscalculated. \n", + "\n", + "\n", + "\n", + "He thought he could roll into Ukraine and the world would roll over.\n", + "\n", + "Instead he met a wall of strength he never imagined. \n", + "\n", + "\n", + "\n", + "He met the Ukrainian people. \n", + "\n", + "\n", + "\n", + "From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.\n" + ] + } + ], + "source": [ + "from langchain_text_splitters import SpacyTextSplitter\n", + "\n", + "text_splitter = SpacyTextSplitter(chunk_size=1000)\n", + "\n", + "texts = text_splitter.split_text(state_of_the_union)\n", + "print(texts[0])" + ] + }, + { + "cell_type": "markdown", + "id": "73dbcdb9", + "metadata": {}, + "source": [ + "## SentenceTransformers\n", + "\n", + "The [SentenceTransformersTokenTextSplitter](https://api.python.langchain.com/en/latest/sentence_transformers/langchain_text_splitters.sentence_transformers.SentenceTransformersTokenTextSplitter.html) is a specialized text splitter for use with the sentence-transformer models. The default behaviour is to split the text into chunks that fit the token window of the sentence transformer model that you would like to use.\n", + "\n", + "To split text and constrain token counts according to the sentence-transformers tokenizer, instantiate a `SentenceTransformersTokenTextSplitter`. You can optionally specify:\n", + "\n", + "- `chunk_overlap`: integer count of token overlap;\n", + "- `model_name`: sentence-transformer model name, defaulting to `\"sentence-transformers/all-mpnet-base-v2\"`;\n", + "- `tokens_per_chunk`: desired token count per chunk." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9dd5419e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + } + ], + "source": [ + "from langchain_text_splitters import SentenceTransformersTokenTextSplitter\n", + "\n", + "splitter = SentenceTransformersTokenTextSplitter(chunk_overlap=0)\n", + "text = \"Lorem \"\n", + "\n", + "count_start_and_stop_tokens = 2\n", + "text_token_count = splitter.count_tokens(text=text) - count_start_and_stop_tokens\n", + "print(text_token_count)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d7ad2213", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tokens in text to split: 514\n" + ] + } + ], + "source": [ + "token_multiplier = splitter.maximum_tokens_per_chunk // text_token_count + 1\n", + "\n", + "# `text_to_split` does not fit in a single chunk\n", + "text_to_split = text * token_multiplier\n", + "\n", + "print(f\"tokens in text to split: {splitter.count_tokens(text=text_to_split)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "818aea04", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lorem\n" + ] + } + ], + "source": [ + "text_chunks = splitter.split_text(text=text_to_split)\n", + "\n", + "print(text_chunks[1])" + ] + }, + { + "cell_type": "markdown", + "id": "ea2973ac", + "metadata": {}, + "source": [ + "## NLTK\n", + "\n", + ":::{.callout-note}\n", + "[The Natural Language Toolkit](https://en.wikipedia.org/wiki/Natural_Language_Toolkit), or more commonly [NLTK](https://www.nltk.org/), is a suite of libraries and programs for symbolic and statistical natural language processing (NLP) for English written in the Python programming language.\n", + ":::\n", + "\n", + "\n", + "Rather than just splitting on \"\\n\\n\", we can use `NLTK` to split based on [NLTK tokenizers](https://www.nltk.org/api/nltk.tokenize.html).\n", + "\n", + "1. How the text is split: by `NLTK` tokenizer.\n", + "2. How the chunk size is measured: by number of characters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6af9886-7d53-4aab-84f6-303c4cce7882", + "metadata": {}, + "outputs": [], + "source": [ + "# pip install nltk" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "aed17ddf", + "metadata": {}, + "outputs": [], + "source": [ + "# This is a long document we can split up.\n", + "with open(\"../../../docs/modules/state_of_the_union.txt\") as f:\n", + " state_of_the_union = f.read()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "20fa9c23", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_text_splitters import NLTKTextSplitter\n", + "\n", + "text_splitter = NLTKTextSplitter(chunk_size=1000)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5ea10835", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Madam Speaker, Madam Vice President, our First Lady and Second Gentleman.\n", + "\n", + "Members of Congress and the Cabinet.\n", + "\n", + "Justices of the Supreme Court.\n", + "\n", + "My fellow Americans.\n", + "\n", + "Last year COVID-19 kept us apart.\n", + "\n", + "This year we are finally together again.\n", + "\n", + "Tonight, we meet as Democrats Republicans and Independents.\n", + "\n", + "But most importantly as Americans.\n", + "\n", + "With a duty to one another to the American people to the Constitution.\n", + "\n", + "And with an unwavering resolve that freedom will always triumph over tyranny.\n", + "\n", + "Six days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways.\n", + "\n", + "But he badly miscalculated.\n", + "\n", + "He thought he could roll into Ukraine and the world would roll over.\n", + "\n", + "Instead he met a wall of strength he never imagined.\n", + "\n", + "He met the Ukrainian people.\n", + "\n", + "From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.\n", + "\n", + "Groups of citizens blocking tanks with their bodies.\n" + ] + } + ], + "source": [ + "texts = text_splitter.split_text(state_of_the_union)\n", + "print(texts[0])" + ] + }, + { + "cell_type": "markdown", + "id": "98a3f975", + "metadata": {}, + "source": [ + "## KoNLPY\n", + "\n", + ":::{.callout-note}\n", + "[KoNLPy: Korean NLP in Python](https://konlpy.org/en/latest/) is is a Python package for natural language processing (NLP) of the Korean language.\n", + ":::\n", + "\n", + "Token splitting involves the segmentation of text into smaller, more manageable units called tokens. These tokens are often words, phrases, symbols, or other meaningful elements crucial for further processing and analysis. In languages like English, token splitting typically involves separating words by spaces and punctuation marks. The effectiveness of token splitting largely depends on the tokenizer's understanding of the language structure, ensuring the generation of meaningful tokens. Since tokenizers designed for the English language are not equipped to understand the unique semantic structures of other languages, such as Korean, they cannot be effectively used for Korean language processing.\n", + "\n", + "### Token splitting for Korean with KoNLPy's Kkma Analyzer\n", + "In case of Korean text, KoNLPY includes at morphological analyzer called `Kkma` (Korean Knowledge Morpheme Analyzer). `Kkma` provides detailed morphological analysis of Korean text. It breaks down sentences into words and words into their respective morphemes, identifying parts of speech for each token. It can segment a block of text into individual sentences, which is particularly useful for processing long texts.\n", + "\n", + "### Usage Considerations\n", + "While `Kkma` is renowned for its detailed analysis, it is important to note that this precision may impact processing speed. Thus, `Kkma` is best suited for applications where analytical depth is prioritized over rapid text processing." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "88ec8f2f", + "metadata": {}, + "outputs": [], + "source": [ + "# pip install konlpy" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "ddfba6cf", + "metadata": {}, + "outputs": [], + "source": [ + "# This is a long Korean document that we want to split up into its component sentences.\n", + "with open(\"./your_korean_doc.txt\") as f:\n", + " korean_document = f.read()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "225dfc5c", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_text_splitters import KonlpyTextSplitter\n", + "\n", + "text_splitter = KonlpyTextSplitter()" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "cf156711", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "춘향전 옛날에 남원에 이 도령이라는 벼슬아치 아들이 있었다.\n", + "\n", + "그의 외모는 빛나는 달처럼 잘생겼고, 그의 학식과 기예는 남보다 뛰어났다.\n", + "\n", + "한편, 이 마을에는 춘향이라는 절세 가인이 살고 있었다.\n", + "\n", + "춘 향의 아름다움은 꽃과 같아 마을 사람들 로부터 많은 사랑을 받았다.\n", + "\n", + "어느 봄날, 도령은 친구들과 놀러 나갔다가 춘 향을 만 나 첫 눈에 반하고 말았다.\n", + "\n", + "두 사람은 서로 사랑하게 되었고, 이내 비밀스러운 사랑의 맹세를 나누었다.\n", + "\n", + "하지만 좋은 날들은 오래가지 않았다.\n", + "\n", + "도령의 아버지가 다른 곳으로 전근을 가게 되어 도령도 떠나 야만 했다.\n", + "\n", + "이별의 아픔 속에서도, 두 사람은 재회를 기약하며 서로를 믿고 기다리기로 했다.\n", + "\n", + "그러나 새로 부임한 관아의 사또가 춘 향의 아름다움에 욕심을 내 어 그녀에게 강요를 시작했다.\n", + "\n", + "춘 향 은 도령에 대한 자신의 사랑을 지키기 위해, 사또의 요구를 단호히 거절했다.\n", + "\n", + "이에 분노한 사또는 춘 향을 감옥에 가두고 혹독한 형벌을 내렸다.\n", + "\n", + "이야기는 이 도령이 고위 관직에 오른 후, 춘 향을 구해 내는 것으로 끝난다.\n", + "\n", + "두 사람은 오랜 시련 끝에 다시 만나게 되고, 그들의 사랑은 온 세상에 전해 지며 후세에까지 이어진다.\n", + "\n", + "- 춘향전 (The Tale of Chunhyang)\n" + ] + } + ], + "source": [ + "texts = text_splitter.split_text(korean_document)\n", + "# The sentences are split with \"\\n\\n\" characters.\n", + "print(texts[0])" + ] + }, + { + "cell_type": "markdown", + "id": "13dc0983", + "metadata": {}, + "source": [ + "## Hugging Face tokenizer\n", + "\n", + "[Hugging Face](https://huggingface.co/docs/tokenizers/index) has many tokenizers.\n", + "\n", + "We use Hugging Face tokenizer, the [GPT2TokenizerFast](https://huggingface.co/Ransaka/gpt2-tokenizer-fast) to count the text length in tokens.\n", + "\n", + "1. How the text is split: by character passed in.\n", + "2. How the chunk size is measured: by number of tokens calculated by the `Hugging Face` tokenizer." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a8ce51d5", + "metadata": {}, + "outputs": [], + "source": [ + "from transformers import GPT2TokenizerFast\n", + "\n", + "tokenizer = GPT2TokenizerFast.from_pretrained(\"gpt2\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "388369ed", + "metadata": {}, + "outputs": [], + "source": [ + "# This is a long document we can split up.\n", + "with open(\"../../../state_of_the_union.txt\") as f:\n", + " state_of_the_union = f.read()\n", + "from langchain_text_splitters import CharacterTextSplitter" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ca5e72c0", + "metadata": {}, + "outputs": [], + "source": [ + "text_splitter = CharacterTextSplitter.from_huggingface_tokenizer(\n", + " tokenizer, chunk_size=100, chunk_overlap=0\n", + ")\n", + "texts = text_splitter.split_text(state_of_the_union)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "37cdfbeb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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", + "\n", + "Last year COVID-19 kept us apart. This year we are finally together again. \n", + "\n", + "Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n", + "\n", + "With a duty to one another to the American people to the Constitution.\n" + ] + } + ], + "source": [ + "print(texts[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a43b0fa6", + "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" + }, + "vscode": { + "interpreter": { + "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/sql_csv.ipynb b/docs/versioned_docs/version-0.2.x/how_to/sql_csv.ipynb new file mode 100644 index 0000000000000..9d3c55a49d2a5 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/sql_csv.ipynb @@ -0,0 +1,789 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "674a0d41-e3e3-4423-a995-25d40128c518", + "metadata": {}, + "source": [ + "# How to do question answering over CSVs\n", + "\n", + "LLMs are great for building question-answering systems over various types of data sources. In this section we'll go over how to build Q&A systems over data stored in a CSV file(s). Like working with SQL databases, the key to working with CSV files is to give an LLM access to tools for querying and interacting with the data. The two main ways to do this are to either:\n", + "\n", + "* **RECOMMENDED**: Load the CSV(s) into a SQL database, and use the approaches outlined in the [SQL tutorial](/docs/tutorials/sql_qa).\n", + "* Give the LLM access to a Python environment where it can use libraries like Pandas to interact with the data.\n", + "\n", + "We will cover both approaches in this guide.\n", + "\n", + "## ⚠️ Security note ⚠️\n", + "\n", + "Both approaches mentioned above carry significant risks. Using SQL requires executing model-generated SQL queries. Using a library like Pandas requires letting the model execute Python code. Since it is easier to tightly scope SQL connection permissions and sanitize SQL queries than it is to sandbox Python environments, **we HIGHLY recommend interacting with CSV data via SQL.** For more on general security best practices, [see here](/docs/security)." + ] + }, + { + "cell_type": "markdown", + "id": "d20c20d7-71e1-4808-9012-48278f3a9b94", + "metadata": {}, + "source": [ + "## Setup\n", + "Dependencies for this guide:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3fcf245-b0aa-4aee-8f0a-9c9cf94b065e", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -qU langchain langchain-openai langchain-community langchain-experimental pandas" + ] + }, + { + "cell_type": "markdown", + "id": "7f2e34a3-0978-4856-8844-d8dfc6d5ac51", + "metadata": {}, + "source": [ + "Set required environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "53913d79-4a11-4bc6-bb49-dea2cc8c453b", + "metadata": {}, + "outputs": [], + "source": [ + "# Using LangSmith is recommended but not required. Uncomment below lines to use.\n", + "# import os\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "c23b4232-2f6a-4eb5-b0cb-1d48a9e02fcc", + "metadata": {}, + "source": [ + "Download the [Titanic dataset](https://www.kaggle.com/datasets/yasserh/titanic-dataset) if you don't already have it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c9099c7-5247-4edb-ba5d-10c3c4c60db4", + "metadata": {}, + "outputs": [], + "source": [ + "!wget https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv -O titanic.csv" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ad029641-6d6c-44cc-b16f-2d5472672adf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(887, 8)\n", + "['Survived', 'Pclass', 'Name', 'Sex', 'Age', 'Siblings/Spouses Aboard', 'Parents/Children Aboard', 'Fare']\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.read_csv(\"titanic.csv\")\n", + "print(df.shape)\n", + "print(df.columns.tolist())" + ] + }, + { + "cell_type": "markdown", + "id": "1779ab07-b715-49e5-ab2a-2e6be7d02927", + "metadata": {}, + "source": [ + "## SQL\n", + "\n", + "Using SQL to interact with CSV data is the recommended approach because it is easier to limit permissions and sanitize queries than with arbitrary Python.\n", + "\n", + "Most SQL databases make it easy to load a CSV file in as a table ([DuckDB](https://duckdb.org/docs/data/csv/overview.html), [SQLite](https://www.sqlite.org/csv.html), etc.). Once you've done this you can use all of the chain and agent-creating techniques outlined in the [SQL tutorial](/docs/tutorials/sql_qa). Here's a quick example of how we might do this with SQLite:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f61e9886-4713-4c88-87d4-dab439687f43", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "887" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "from sqlalchemy import create_engine\n", + "\n", + "engine = create_engine(\"sqlite:///titanic.db\")\n", + "df.to_sql(\"titanic\", engine, index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3275fc91-3777-4f78-8edf-d148001684b0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['titanic']\n", + "[(1, 2, 'Master. Alden Gates Caldwell', 'male', 0.83, 0, 2, 29.0), (0, 3, 'Master. Eino Viljami Panula', 'male', 1.0, 4, 1, 39.6875), (1, 3, 'Miss. Eleanor Ileen Johnson', 'female', 1.0, 1, 1, 11.1333), (1, 2, 'Master. Richard F Becker', 'male', 1.0, 2, 1, 39.0), (1, 1, 'Master. Hudson Trevor Allison', 'male', 0.92, 1, 2, 151.55), (1, 3, 'Miss. Maria Nakid', 'female', 1.0, 0, 2, 15.7417), (0, 3, 'Master. Sidney Leonard Goodwin', 'male', 1.0, 5, 2, 46.9), (1, 3, 'Miss. Helene Barbara Baclini', 'female', 0.75, 2, 1, 19.2583), (1, 3, 'Miss. Eugenie Baclini', 'female', 0.75, 2, 1, 19.2583), (1, 2, 'Master. Viljo Hamalainen', 'male', 0.67, 1, 1, 14.5), (1, 3, 'Master. Bertram Vere Dean', 'male', 1.0, 1, 2, 20.575), (1, 3, 'Master. Assad Alexander Thomas', 'male', 0.42, 0, 1, 8.5167), (1, 2, 'Master. Andre Mallet', 'male', 1.0, 0, 2, 37.0042), (1, 2, 'Master. George Sibley Richards', 'male', 0.83, 1, 1, 18.75)]\n" + ] + } + ], + "source": [ + "db = SQLDatabase(engine=engine)\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "print(db.run(\"SELECT * FROM titanic WHERE Age < 2;\"))" + ] + }, + { + "cell_type": "markdown", + "id": "42f5a3c3-707c-4331-9f5f-0cb4919763dd", + "metadata": {}, + "source": [ + "And create a [SQL agent](/docs/tutorials/sql_qa) to interact with it:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e868a586-4f4e-4b1d-ab11-fae1271dd551", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "edd92649-b178-47bd-b2b7-d5d4e14b3512", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.agent_toolkits import create_sql_agent\n", + "\n", + "agent_executor = create_sql_agent(llm, db=db, agent_type=\"openai-tools\", verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7aefe929-5e39-4ed1-b135-aaf88edce2eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new SQL Agent Executor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_list_tables` with `{}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3mtitanic\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_schema` with `{'table_names': 'titanic'}`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m\n", + "CREATE TABLE titanic (\n", + "\t\"Survived\" BIGINT, \n", + "\t\"Pclass\" BIGINT, \n", + "\t\"Name\" TEXT, \n", + "\t\"Sex\" TEXT, \n", + "\t\"Age\" FLOAT, \n", + "\t\"Siblings/Spouses Aboard\" BIGINT, \n", + "\t\"Parents/Children Aboard\" BIGINT, \n", + "\t\"Fare\" FLOAT\n", + ")\n", + "\n", + "/*\n", + "3 rows from titanic table:\n", + "Survived\tPclass\tName\tSex\tAge\tSiblings/Spouses Aboard\tParents/Children Aboard\tFare\n", + "0\t3\tMr. Owen Harris Braund\tmale\t22.0\t1\t0\t7.25\n", + "1\t1\tMrs. John Bradley (Florence Briggs Thayer) Cumings\tfemale\t38.0\t1\t0\t71.2833\n", + "1\t3\tMiss. Laina Heikkinen\tfemale\t26.0\t0\t0\t7.925\n", + "*/\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `sql_db_query` with `{'query': 'SELECT AVG(Age) AS Average_Age FROM titanic WHERE Survived = 1'}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[(28.408391812865496,)]\u001b[0m\u001b[32;1m\u001b[1;3mThe average age of survivors in the Titanic dataset is approximately 28.41 years.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': \"what's the average age of survivors\",\n", + " 'output': 'The average age of survivors in the Titanic dataset is approximately 28.41 years.'}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.invoke({\"input\": \"what's the average age of survivors\"})" + ] + }, + { + "cell_type": "markdown", + "id": "4d1eb128-842b-4018-87ab-bb269147f6ec", + "metadata": {}, + "source": [ + "This approach easily generalizes to multiple CSVs, since we can just load each of them into our database as its own table. See the [Multiple CSVs](/docs/how_to/sql_csv#multiple-csvs) section below." + ] + }, + { + "cell_type": "markdown", + "id": "fe7f2d91-2377-49dd-97a3-19d48a750715", + "metadata": {}, + "source": [ + "## Pandas\n", + "\n", + "Instead of SQL we can also use data analysis libraries like pandas and the code generating abilities of LLMs to interact with CSV data. Again, **this approach is not fit for production use cases unless you have extensive safeguards in place**. For this reason, our code-execution utilities and constructors live in the `langchain-experimental` package.\n", + "\n", + "### Chain\n", + "\n", + "Most LLMs have been trained on enough pandas Python code that they can generate it just by being asked to:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "27c84b27-9367-4c58-8a88-ade1fbf6683c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "```python\n", + "correlation = df['Age'].corr(df['Fare'])\n", + "correlation\n", + "```\n" + ] + } + ], + "source": [ + "ai_msg = llm.invoke(\n", + " \"I have a pandas DataFrame 'df' with columns 'Age' and 'Fare'. Write code to compute the correlation between the two columns. Return Markdown for a Python code snippet and nothing else.\"\n", + ")\n", + "print(ai_msg.content)" + ] + }, + { + "cell_type": "markdown", + "id": "f5e84003-5c39-496b-afa7-eaa50a01b7bb", + "metadata": {}, + "source": [ + "We can combine this ability with a Python-executing tool to create a simple data analysis chain. We'll first want to load our CSV table as a dataframe, and give the tool access to this dataframe:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "16abe312-b1a3-413f-bb9a-0e613d1e550b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "32.30542018038331" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_experimental.tools import PythonAstREPLTool\n", + "\n", + "df = pd.read_csv(\"titanic.csv\")\n", + "tool = PythonAstREPLTool(locals={\"df\": df})\n", + "tool.invoke(\"df['Fare'].mean()\")" + ] + }, + { + "cell_type": "markdown", + "id": "ab1b2e7c-6ea8-4674-98eb-a43c69f5c19d", + "metadata": {}, + "source": [ + "To help enforce proper use of our Python tool, we'll using [tool calling](/docs/how_to/tool_calling/):" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c6a9c8ec-1d06-4870-a584-b8d7b6c6ddfe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_SBrK246yUbdnJemXFC8Iod05', 'function': {'arguments': '{\"query\":\"df.corr()[\\'Age\\'][\\'Fare\\']\"}', 'name': 'python_repl_ast'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 125, 'total_tokens': 138}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, id='run-1fd332ba-fa72-4351-8182-d464e7368311-0', tool_calls=[{'name': 'python_repl_ast', 'args': {'query': \"df.corr()['Age']['Fare']\"}, 'id': 'call_SBrK246yUbdnJemXFC8Iod05'}])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "llm_with_tools = llm.bind_tools([tool], tool_choice=tool.name)\n", + "response = llm_with_tools.invoke(\n", + " \"I have a dataframe 'df' and want to know the correlation between the 'Age' and 'Fare' columns\"\n", + ")\n", + "response" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "b0e4015c-236d-42d7-ba8f-16052fa4f405", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'python_repl_ast',\n", + " 'args': {'query': \"df.corr()['Age']['Fare']\"},\n", + " 'id': 'call_SBrK246yUbdnJemXFC8Iod05'}]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response.tool_calls" + ] + }, + { + "cell_type": "markdown", + "id": "bdec46fb-7296-443c-9e97-cfa9045ff21d", + "metadata": {}, + "source": [ + "We'll add a tools output parser to extract the function call as a dict:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "476128f2-aa61-47f5-a371-dcff7b391d19", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'query': \"df[['Age', 'Fare']].corr()\"}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.output_parsers.openai_tools import JsonOutputKeyToolsParser\n", + "\n", + "parser = JsonOutputKeyToolsParser(key_name=tool.name, first_tool_only=True)\n", + "(llm_with_tools | parser).invoke(\n", + " \"I have a dataframe 'df' and want to know the correlation between the 'Age' and 'Fare' columns\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "59362ea0-cc5a-4841-b87c-51d6a87d5810", + "metadata": {}, + "source": [ + "And combine with a prompt so that we can just specify a question without needing to specify the dataframe info every invocation:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9e87a820-e4ce-417e-b580-043fb2d5c8f2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'query': \"df[['Age', 'Fare']].corr()\"}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system = f\"\"\"You have access to a pandas dataframe `df`. \\\n", + "Here is the output of `df.head().to_markdown()`:\n", + "\n", + "```\n", + "{df.head().to_markdown()}\n", + "```\n", + "\n", + "Given a user question, write the Python code to answer it. \\\n", + "Return ONLY the valid Python code and nothing else. \\\n", + "Don't assume you have access to any libraries other than built-in Python ones and pandas.\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", \"{question}\")])\n", + "code_chain = prompt | llm_with_tools | parser\n", + "code_chain.invoke({\"question\": \"What's the correlation between age and fare\"})" + ] + }, + { + "cell_type": "markdown", + "id": "63989e47-c0af-409e-9766-83c3fe6d69bb", + "metadata": {}, + "source": [ + "And lastly we'll add our Python tool so that the generated code is actually executed:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "2e56a891-4c3f-4e5a-a5ee-3973112ffeb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.11232863699941621" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = prompt | llm_with_tools | parser | tool # noqa\n", + "chain.invoke({\"question\": \"What's the correlation between age and fare\"})" + ] + }, + { + "cell_type": "markdown", + "id": "fbb12764-4a90-4e84-88b4-a25949084ea2", + "metadata": {}, + "source": [ + "And just like that we have a simple data analysis chain. We can take a peak at the intermediate steps by looking at the LangSmith trace: https://smith.langchain.com/public/b1309290-7212-49b7-bde2-75b39a32b49a/r\n", + "\n", + "We could add an additional LLM call at the end to generate a conversational response, so that we're not just responding with the tool output. For this we'll want to add a chat history `MessagesPlaceholder` to our prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "3fe3818d-0657-4729-ac46-ab5d4860d8f6", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain_core.messages import ToolMessage\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import MessagesPlaceholder\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "system = f\"\"\"You have access to a pandas dataframe `df`. \\\n", + "Here is the output of `df.head().to_markdown()`:\n", + "\n", + "```\n", + "{df.head().to_markdown()}\n", + "```\n", + "\n", + "Given a user question, write the Python code to answer it. \\\n", + "Don't assume you have access to any libraries other than built-in Python ones and pandas.\n", + "Respond directly to the question once you have enough information to answer it.\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " system,\n", + " ),\n", + " (\"human\", \"{question}\"),\n", + " # This MessagesPlaceholder allows us to optionally append an arbitrary number of messages\n", + " # at the end of the prompt using the 'chat_history' arg.\n", + " MessagesPlaceholder(\"chat_history\", optional=True),\n", + " ]\n", + ")\n", + "\n", + "\n", + "def _get_chat_history(x: dict) -> list:\n", + " \"\"\"Parse the chain output up to this point into a list of chat history messages to insert in the prompt.\"\"\"\n", + " ai_msg = x[\"ai_msg\"]\n", + " tool_call_id = x[\"ai_msg\"].additional_kwargs[\"tool_calls\"][0][\"id\"]\n", + " tool_msg = ToolMessage(tool_call_id=tool_call_id, content=str(x[\"tool_output\"]))\n", + " return [ai_msg, tool_msg]\n", + "\n", + "\n", + "chain = (\n", + " RunnablePassthrough.assign(ai_msg=prompt | llm_with_tools)\n", + " .assign(tool_output=itemgetter(\"ai_msg\") | parser | tool)\n", + " .assign(chat_history=_get_chat_history)\n", + " .assign(response=prompt | llm | StrOutputParser())\n", + " .pick([\"tool_output\", \"response\"])\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ff6e98ec-52f1-4ffd-9ea8-bacedfa29f28", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'tool_output': 0.11232863699941616,\n", + " 'response': 'The correlation between age and fare is approximately 0.1123.'}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke({\"question\": \"What's the correlation between age and fare\"})" + ] + }, + { + "cell_type": "markdown", + "id": "245a5a91-c6d2-4a40-9b9f-eb38f78c9d22", + "metadata": {}, + "source": [ + "Here's the LangSmith trace for this run: https://smith.langchain.com/public/14e38d70-45b1-4b81-8477-9fd2b7c07ea6/r" + ] + }, + { + "cell_type": "markdown", + "id": "6c24b4f4-abbf-4891-b200-814eb9c35bec", + "metadata": {}, + "source": [ + "### Agent\n", + "\n", + "For complex questions it can be helpful for an LLM to be able to iteratively execute code while maintaining the inputs and outputs of its previous executions. This is where Agents come into play. They allow an LLM to decide how many times a tool needs to be invoked and keep track of the executions it's made so far. The [create_pandas_dataframe_agent](https://api.python.langchain.com/en/latest/agents/langchain_experimental.agents.agent_toolkits.pandas.base.create_pandas_dataframe_agent.html) is a built-in agent that makes it easy to work with dataframes:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "35ea904e-795f-411b-bef8-6484dbb6e35c", + "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: `python_repl_ast` with `{'query': \"df[['Age', 'Fare']].corr().iloc[0,1]\"}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m0.11232863699941621\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `python_repl_ast` with `{'query': \"df[['Fare', 'Survived']].corr().iloc[0,1]\"}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m0.2561785496289603\u001b[0m\u001b[32;1m\u001b[1;3mThe correlation between Age and Fare is approximately 0.112, and the correlation between Fare and Survival is approximately 0.256.\n", + "\n", + "Therefore, the correlation between Fare and Survival (0.256) is greater than the correlation between Age and Fare (0.112).\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': \"What's the correlation between age and fare? is that greater than the correlation between fare and survival?\",\n", + " 'output': 'The correlation between Age and Fare is approximately 0.112, and the correlation between Fare and Survival is approximately 0.256.\\n\\nTherefore, the correlation between Fare and Survival (0.256) is greater than the correlation between Age and Fare (0.112).'}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_experimental.agents import create_pandas_dataframe_agent\n", + "\n", + "agent = create_pandas_dataframe_agent(llm, df, agent_type=\"openai-tools\", verbose=True)\n", + "agent.invoke(\n", + " {\n", + " \"input\": \"What's the correlation between age and fare? is that greater than the correlation between fare and survival?\"\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a65322f3-b13c-4949-82b2-4517b9a0859d", + "metadata": {}, + "source": [ + "Here's the LangSmith trace for this run: https://smith.langchain.com/public/6a86aee2-4f22-474a-9264-bd4c7283e665/r" + ] + }, + { + "cell_type": "markdown", + "id": "68492261-faef-47e7-8009-e20ef1420d5a", + "metadata": {}, + "source": [ + "### Multiple CSVs {#multiple-csvs}\n", + "\n", + "To handle multiple CSVs (or dataframes) we just need to pass multiple dataframes to our Python tool. Our `create_pandas_dataframe_agent` constructor can do this out of the box, we can pass in a list of dataframes instead of just one. If we're constructing a chain ourselves, we can do something like:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "77a70e1b-d3ee-4fa6-a4a0-d2e5005e6c8a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.14384991262954416" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_1 = df[[\"Age\", \"Fare\"]]\n", + "df_2 = df[[\"Fare\", \"Survived\"]]\n", + "\n", + "tool = PythonAstREPLTool(locals={\"df_1\": df_1, \"df_2\": df_2})\n", + "llm_with_tool = llm.bind_tools(tools=[tool], tool_choice=tool.name)\n", + "df_template = \"\"\"```python\n", + "{df_name}.head().to_markdown()\n", + ">>> {df_head}\n", + "```\"\"\"\n", + "df_context = \"\\n\\n\".join(\n", + " df_template.format(df_head=_df.head().to_markdown(), df_name=df_name)\n", + " for _df, df_name in [(df_1, \"df_1\"), (df_2, \"df_2\")]\n", + ")\n", + "\n", + "system = f\"\"\"You have access to a number of pandas dataframes. \\\n", + "Here is a sample of rows from each dataframe and the python code that was used to generate the sample:\n", + "\n", + "{df_context}\n", + "\n", + "Given a user question about the dataframes, write the Python code to answer it. \\\n", + "Don't assume you have access to any libraries other than built-in Python ones and pandas. \\\n", + "Make sure to refer only to the variables mentioned above.\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", \"{question}\")])\n", + "\n", + "chain = prompt | llm_with_tool | parser | tool\n", + "chain.invoke(\n", + " {\n", + " \"question\": \"return the difference in the correlation between age and fare and the correlation between fare and survival\"\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "7043363f-4ab1-41de-9318-c556e4ae66bc", + "metadata": {}, + "source": [ + "Here's the LangSmith trace for this run: https://smith.langchain.com/public/cc2a7d7f-7c5a-4e77-a10c-7b5420fcd07f/r" + ] + }, + { + "cell_type": "markdown", + "id": "a2256d09-23c2-4e52-bfc6-c84eba538586", + "metadata": {}, + "source": [ + "### Sandboxed code execution\n", + "\n", + "There are a number of tools like [E2B](/docs/integrations/tools/e2b_data_analysis) and [Bearly](/docs/integrations/tools/bearly) that provide sandboxed environments for Python code execution, to allow for safer code-executing chains and agents." + ] + }, + { + "cell_type": "markdown", + "id": "1728e791-f114-41e6-aa12-0436fdeeedae", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "For more advanced data analysis applications we recommend checking out:\n", + "\n", + "* [SQL tutorial](/docs/tutorials/sql_qa): Many of the challenges of working with SQL db's and CSV's are generic to any structured data type, so it's useful to read the SQL techniques even if you're using Pandas for CSV data analysis.\n", + "* [Tool use](/docs/how_to/tool_calling): Guides on general best practices when working with chains and agents that invoke tools\n", + "* [Agents](/docs/tutorials/agents): Understand the fundamentals of building LLM agents.\n", + "* Integrations: Sandboxed envs like [E2B](/docs/integrations/tools/e2b_data_analysis) and [Bearly](/docs/integrations/tools/bearly), utilities like [SQLDatabase](https://api.python.langchain.com/en/latest/utilities/langchain_community.utilities.sql_database.SQLDatabase.html#langchain_community.utilities.sql_database.SQLDatabase), related agents like [Spark DataFrame agent](/docs/integrations/toolkits/spark)." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/sql_large_db.ipynb b/docs/versioned_docs/version-0.2.x/how_to/sql_large_db.ipynb new file mode 100644 index 0000000000000..612cf0fc09590 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/sql_large_db.ipynb @@ -0,0 +1,623 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6751831d-9b08-434f-829b-d0052a3b119f", + "metadata": {}, + "source": [ + "# How to deal with large databases when doing SQL question-answering\n", + "\n", + "In order to write valid queries against a database, we need to feed the model the table names, table schemas, and feature values for it to query over. When there are many tables, columns, and/or high-cardinality columns, it becomes impossible for us to dump the full information about our database in every prompt. Instead, we must find ways to dynamically insert into the prompt only the most relevant information.\n", + "\n", + "In this guide we demonstrate methods for identifying such relevant information, and feeding this into a query-generation step. We will cover:\n", + "\n", + "1. Identifying a relevant subset of tables;\n", + "2. Identifying a relevant subset of column values.\n", + "\n", + "\n", + "## Setup\n", + "\n", + "First, get required packages and set environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "afd5c20e-c705-4ef4-b33b-71fa819215ce", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "592e0c93-5396-44ec-92f0-1635ddd59a42", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the below to use LangSmith. Not required.\n", + "# import os\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "id": "590ee096-db88-42af-90d4-99b8149df753", + "metadata": {}, + "source": [ + "The below example will use a SQLite connection with Chinook database. Follow [these installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", + "\n", + "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) as `Chinook_Sqlite.sql`\n", + "* Run `sqlite3 Chinook.db`\n", + "* Run `.read Chinook_Sqlite.sql`\n", + "* Test `SELECT * FROM Artist LIMIT 10;`\n", + "\n", + "Now, `Chinhook.db` is in our directory and we can interface with it using the SQLAlchemy-driven [SQLDatabase](https://api.python.langchain.com/en/latest/utilities/langchain_community.utilities.sql_database.SQLDatabase.html) class:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cebd3915-f58f-4e73-8459-265630ae8cd4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n", + "[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\n" + ] + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "print(db.run(\"SELECT * FROM Artist LIMIT 10;\"))" + ] + }, + { + "cell_type": "markdown", + "id": "2e572e1f-99b5-46a2-9023-76d1e6256c0a", + "metadata": {}, + "source": [ + "## Many tables\n", + "\n", + "One of the main pieces of information we need to include in our prompt is the schemas of the relevant tables. When we have very many tables, we can't fit all of the schemas in a single prompt. What we can do in such cases is first extract the names of the tables related to the user input, and then include only their schemas.\n", + "\n", + "One easy and reliable way to do this is using [tool-calling](/docs/how_to/tool_calling). Below, we show how we can use this feature to obtain output conforming to a desired format (in this case, a list of table names). We use the chat model's `.bind_tools` method to bind a tool in Pydantic format, and feed this into an output parser to reconstruct the object from the model's response.\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d278de7e-9228-4265-abf2-7a5e214a7dd7", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "dbfc94bb-1c64-4f77-9e65-fb2468f55a58", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Table(name='Genre')]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.output_parsers.openai_tools import PydanticToolsParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class Table(BaseModel):\n", + " \"\"\"Table in SQL database.\"\"\"\n", + "\n", + " name: str = Field(description=\"Name of table in SQL database.\")\n", + "\n", + "\n", + "table_names = \"\\n\".join(db.get_usable_table_names())\n", + "system = f\"\"\"Return the names of ALL the SQL tables that MIGHT be relevant to the user question. \\\n", + "The tables are:\n", + "\n", + "{table_names}\n", + "\n", + "Remember to include ALL POTENTIALLY RELEVANT tables, even if you're not sure that they're needed.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "llm_with_tools = llm.bind_tools([Table])\n", + "output_parser = PydanticToolsParser(tools=[Table])\n", + "\n", + "table_chain = prompt | llm_with_tools | output_parser\n", + "\n", + "table_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})" + ] + }, + { + "cell_type": "markdown", + "id": "1641dbba-d359-4cb2-ac52-82dfae99f392", + "metadata": {}, + "source": [ + "This works pretty well! Except, as we'll see below, we actually need a few other tables as well. This would be pretty difficult for the model to know based just on the user question. In this case, we might think to simplify our model's job by grouping the tables together. We'll just ask the model to choose between categories \"Music\" and \"Business\", and then take care of selecting all the relevant tables from there:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0ccb0bf5-c580-428f-9cde-a58772ae784e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Table(name='Music'), Table(name='Business')]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "system = \"\"\"Return the names of any SQL tables that are relevant to the user question.\n", + "The tables are:\n", + "\n", + "Music\n", + "Business\n", + "\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "\n", + "category_chain = prompt | llm_with_tools | output_parser\n", + "category_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "883eda7a-7d0f-4012-9658-6a1010c7cda9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Album',\n", + " 'Artist',\n", + " 'Genre',\n", + " 'MediaType',\n", + " 'Playlist',\n", + " 'PlaylistTrack',\n", + " 'Track',\n", + " 'Customer',\n", + " 'Employee',\n", + " 'Invoice',\n", + " 'InvoiceLine']" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from typing import List\n", + "\n", + "\n", + "def get_tables(categories: List[Table]) -> List[str]:\n", + " tables = []\n", + " for category in categories:\n", + " if category.name == \"Music\":\n", + " tables.extend(\n", + " [\n", + " \"Album\",\n", + " \"Artist\",\n", + " \"Genre\",\n", + " \"MediaType\",\n", + " \"Playlist\",\n", + " \"PlaylistTrack\",\n", + " \"Track\",\n", + " ]\n", + " )\n", + " elif category.name == \"Business\":\n", + " tables.extend([\"Customer\", \"Employee\", \"Invoice\", \"InvoiceLine\"])\n", + " return tables\n", + "\n", + "\n", + "table_chain = category_chain | get_tables # noqa\n", + "table_chain.invoke({\"input\": \"What are all the genres of Alanis Morisette songs\"})" + ] + }, + { + "cell_type": "markdown", + "id": "04d52d01-1ccf-4753-b34a-0dcbc4921f78", + "metadata": {}, + "source": [ + "Now that we've got a chain that can output the relevant tables for any query we can combine this with our [create_sql_query_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.sql_database.query.create_sql_query_chain.html), which can accept a list of `table_names_to_use` to determine which table schemas are included in the prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "79f2a5a2-eb99-47e3-9c2b-e5751a800174", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain.chains import create_sql_query_chain\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "query_chain = create_sql_query_chain(llm, db)\n", + "# Convert \"question\" key to the \"input\" key expected by current table_chain.\n", + "table_chain = {\"input\": itemgetter(\"question\")} | table_chain\n", + "# Set table_names_to_use using table_chain.\n", + "full_chain = RunnablePassthrough.assign(table_names_to_use=table_chain) | query_chain" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3c74b418-aa9a-4eb5-89dd-6e1a99a21344", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT DISTINCT \"g\".\"Name\"\n", + "FROM \"Genre\" g\n", + "JOIN \"Track\" t ON \"g\".\"GenreId\" = \"t\".\"GenreId\"\n", + "JOIN \"Album\" a ON \"t\".\"AlbumId\" = \"a\".\"AlbumId\"\n", + "JOIN \"Artist\" ar ON \"a\".\"ArtistId\" = \"ar\".\"ArtistId\"\n", + "WHERE \"ar\".\"Name\" = 'Alanis Morissette'\n", + "LIMIT 5;\n" + ] + } + ], + "source": [ + "query = full_chain.invoke(\n", + " {\"question\": \"What are all the genres of Alanis Morisette songs\"}\n", + ")\n", + "print(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "ace7bdbf-728e-4f7b-a361-b44dae404481", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"[('Rock',)]\"" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "7a717020-84c2-40f3-ba84-6624138d8e0c", + "metadata": {}, + "source": [ + "We can see the LangSmith trace for this run [here](https://smith.langchain.com/public/4fbad408-3554-4f33-ab47-1e510a1b52a3/r).\n", + "\n", + "We've seen how to dynamically include a subset of table schemas in a prompt within a chain. Another possible approach to this problem is to let an Agent decide for itself when to look up tables by giving it a Tool to do so. You can see an example of this in the [SQL: Agents](/docs/tutorials/agents) guide." + ] + }, + { + "cell_type": "markdown", + "id": "cb9e54fd-64ca-4ed5-847c-afc635aae4f5", + "metadata": {}, + "source": [ + "## High-cardinality columns\n", + "\n", + "In order to filter columns that contain proper nouns such as addresses, song names or artists, we first need to double-check the spelling in order to filter the data correctly. \n", + "\n", + "One naive strategy it to create a vector store with all the distinct proper nouns that exist in the database. We can then query that vector store each user input and inject the most relevant proper nouns into the prompt.\n", + "\n", + "First we need the unique values for each entity we want, for which we define a function that parses the result into a list of elements:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "dc97e6ba-6055-4677-b0cc-c5425d5d4c81", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['AC/DC', 'Accept', 'Aerosmith', 'Alanis Morissette', 'Alice In Chains']" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import ast\n", + "import re\n", + "\n", + "\n", + "def query_as_list(db, query):\n", + " res = db.run(query)\n", + " res = [el for sub in ast.literal_eval(res) for el in sub if el]\n", + " res = [re.sub(r\"\\b\\d+\\b\", \"\", string).strip() for string in res]\n", + " return res\n", + "\n", + "\n", + "proper_nouns = query_as_list(db, \"SELECT Name FROM Artist\")\n", + "proper_nouns += query_as_list(db, \"SELECT Title FROM Album\")\n", + "proper_nouns += query_as_list(db, \"SELECT Name FROM Genre\")\n", + "len(proper_nouns)\n", + "proper_nouns[:5]" + ] + }, + { + "cell_type": "markdown", + "id": "22efa968-1879-4d7a-858f-7899dfa57454", + "metadata": {}, + "source": [ + "Now we can embed and store all of our values in a vector database:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ea50abce-545a-4dc3-8795-8d364f7d142a", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import FAISS\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "vector_db = FAISS.from_texts(proper_nouns, OpenAIEmbeddings())\n", + "retriever = vector_db.as_retriever(search_kwargs={\"k\": 15})" + ] + }, + { + "cell_type": "markdown", + "id": "a5d1d5c0-0928-40a4-b961-f1afe03cd5d3", + "metadata": {}, + "source": [ + "And put together a query construction chain that first retrieves values from the database and inserts them into the prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "006b2955-4c06-4597-9c1d-442f77cd0261", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "system = \"\"\"You are a SQLite expert. Given an input question, create a syntactically\n", + "correct SQLite query to run. Unless otherwise specificed, do not return more than\n", + "{top_k} rows.\n", + "\n", + "Only return the SQL query with no markup or explanation.\n", + "\n", + "Here is the relevant table info: {table_info}\n", + "\n", + "Here is a non-exhaustive list of possible feature values. If filtering on a feature\n", + "value make sure to check its spelling against this list first:\n", + "\n", + "{proper_nouns}\n", + "\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", \"{input}\")])\n", + "\n", + "query_chain = create_sql_query_chain(llm, db, prompt=prompt)\n", + "retriever_chain = (\n", + " itemgetter(\"question\")\n", + " | retriever\n", + " | (lambda docs: \"\\n\".join(doc.page_content for doc in docs))\n", + ")\n", + "chain = RunnablePassthrough.assign(proper_nouns=retriever_chain) | query_chain" + ] + }, + { + "cell_type": "markdown", + "id": "12b0ed60-2536-4f82-85df-e096a272072a", + "metadata": {}, + "source": [ + "To try out our chain, let's see what happens when we try filtering on \"elenis moriset\", a mispelling of Alanis Morissette, without and with retrieval:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "5ba81336-0853-43da-8b07-5a256b8ba0b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT DISTINCT g.Name \n", + "FROM Track t\n", + "JOIN Album a ON t.AlbumId = a.AlbumId\n", + "JOIN Artist ar ON a.ArtistId = ar.ArtistId\n", + "JOIN Genre g ON t.GenreId = g.GenreId\n", + "WHERE ar.Name = 'Elenis Moriset';\n" + ] + }, + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Without retrieval\n", + "query = query_chain.invoke(\n", + " {\"question\": \"What are all the genres of elenis moriset songs\", \"proper_nouns\": \"\"}\n", + ")\n", + "print(query)\n", + "db.run(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "fcdd8432-07a4-4609-8214-b1591dd94950", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT DISTINCT Genre.Name\n", + "FROM Genre\n", + "JOIN Track ON Genre.GenreId = Track.GenreId\n", + "JOIN Album ON Track.AlbumId = Album.AlbumId\n", + "JOIN Artist ON Album.ArtistId = Artist.ArtistId\n", + "WHERE Artist.Name = 'Elenis Moriset'\n" + ] + }, + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Without retrieval\n", + "query = query_chain.invoke(\n", + " {\"question\": \"What are all the genres of elenis moriset songs\", \"proper_nouns\": \"\"}\n", + ")\n", + "print(query)\n", + "db.run(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cbbff7cc-c616-41eb-bbf5-08bd42c6808e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT DISTINCT g.Name\n", + "FROM Genre g\n", + "JOIN Track t ON g.GenreId = t.GenreId\n", + "JOIN Album a ON t.AlbumId = a.AlbumId\n", + "JOIN Artist ar ON a.ArtistId = ar.ArtistId\n", + "WHERE ar.Name = 'Alanis Morissette';\n" + ] + }, + { + "data": { + "text/plain": [ + "\"[('Rock',)]\"" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# With retrieval\n", + "query = chain.invoke({\"question\": \"What are all the genres of elenis moriset songs\"})\n", + "print(query)\n", + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "7f99181b-a75c-4ff3-b37b-33f99a506581", + "metadata": {}, + "source": [ + "We can see that with retrieval we're able to correct the spelling from \"Elenis Moriset\" to \"Alanis Morissette\" and get back a valid result.\n", + "\n", + "Another possible approach to this problem is to let an Agent decide for itself when to look up proper nouns. You can see an example of this in the [SQL: Agents](/docs/tutorials/agents) guide." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/sql_prompting.ipynb b/docs/versioned_docs/version-0.2.x/how_to/sql_prompting.ipynb new file mode 100644 index 0000000000000..3f3d6e5acba20 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/sql_prompting.ipynb @@ -0,0 +1,795 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to better prompt when doing SQL question-answering\n", + "\n", + "In this guide we'll go over prompting strategies to improve SQL query generation using [create_sql_query_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.sql_database.query.create_sql_query_chain.html). We'll largely focus on methods for getting relevant database-specific information in your prompt.\n", + "\n", + "We will cover: \n", + "\n", + "- How the dialect of the LangChain [SQLDatabase](https://api.python.langchain.com/en/latest/utilities/langchain_community.utilities.sql_database.SQLDatabase.html) impacts the prompt of the chain;\n", + "- How to format schema information into the prompt using `SQLDatabase.get_context`;\n", + "- How to build and select few-shot examples to assist the model.\n", + "\n", + "## Setup\n", + "\n", + "First, get required packages and set environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchain-experimental langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the below to use LangSmith. Not required.\n", + "# import os\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The below example will use a SQLite connection with Chinook database. Follow [these installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", + "\n", + "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) as `Chinook_Sqlite.sql`\n", + "* Run `sqlite3 Chinook.db`\n", + "* Run `.read Chinook_Sqlite.sql`\n", + "* Test `SELECT * FROM Artist LIMIT 10;`\n", + "\n", + "Now, `Chinhook.db` is in our directory and we can interface with it using the SQLAlchemy-driven `SQLDatabase` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n", + "[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\n" + ] + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\", sample_rows_in_table_info=3)\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "print(db.run(\"SELECT * FROM Artist LIMIT 10;\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dialect-specific prompting\n", + "\n", + "One of the simplest things we can do is make our prompt specific to the SQL dialect we're using. When using the built-in [create_sql_query_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.sql_database.query.create_sql_query_chain.html) and [SQLDatabase](https://api.python.langchain.com/en/latest/utilities/langchain_community.utilities.sql_database.SQLDatabase.html), this is handled for you for any of the following dialects:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['crate',\n", + " 'duckdb',\n", + " 'googlesql',\n", + " 'mssql',\n", + " 'mysql',\n", + " 'mariadb',\n", + " 'oracle',\n", + " 'postgresql',\n", + " 'sqlite',\n", + " 'clickhouse',\n", + " 'prestodb']" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.chains.sql_database.prompt import SQL_PROMPTS\n", + "\n", + "list(SQL_PROMPTS)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For example, using our current DB we can see that we'll get a SQLite-specific prompt.\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, first create a syntactically correct SQLite query to run, then look at the results of the query and return the answer to the input question.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most 5 results using the LIMIT clause as per SQLite. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Use the following format:\n", + "\n", + "Question: Question here\n", + "SQLQuery: SQL Query to run\n", + "SQLResult: Result of the SQLQuery\n", + "Answer: Final answer here\n", + "\n", + "Only use the following tables:\n", + "\u001b[33;1m\u001b[1;3m{table_info}\u001b[0m\n", + "\n", + "Question: \u001b[33;1m\u001b[1;3m{input}\u001b[0m\n" + ] + } + ], + "source": [ + "from langchain.chains import create_sql_query_chain\n", + "\n", + "chain = create_sql_query_chain(llm, db)\n", + "chain.get_prompts()[0].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table definitions and example rows\n", + "\n", + "In most SQL chains, we'll need to feed the model at least part of the database schema. Without this it won't be able to write valid queries. Our database comes with some convenience methods to give us the relevant context. Specifically, we can get the table names, their schemas, and a sample of rows from each table.\n", + "\n", + "Here we will use `SQLDatabase.get_context`, which provides available tables and their schemas:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['table_info', 'table_names']\n", + "\n", + "CREATE TABLE \"Album\" (\n", + "\t\"AlbumId\" INTEGER NOT NULL, \n", + "\t\"Title\" NVARCHAR(160) NOT NULL, \n", + "\t\"ArtistId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"AlbumId\"), \n", + "\tFOREIGN KEY(\"ArtistId\") REFERENCES \"Artist\" (\"ArtistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Album table:\n", + "AlbumId\tTitle\tArtistId\n", + "1\tFor Those About To Rock We Salute You\t1\n", + "2\tBalls to the Wall\t2\n", + "3\tRestless and Wild\t2\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Artist\" (\n", + "\t\"ArtistId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120), \n", + "\tPRIMARY KEY (\"ArtistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Artist table:\n", + "ArtistId\tName\n", + "1\tAC/DC\n", + "2\tAccept\n", + "3\tAerosmith\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Customer\" (\n", + "\t\"CustomerId\" INTEGER NOT NULL, \n", + "\t\"FirstName\" NVARCHAR(40) NOT NULL, \n", + "\t\"LastName\" NVARCHAR(20) NOT NULL, \n", + "\t\"Company\" NVARCHAR(80), \n", + "\t\"Address\" NVARCHAR(70), \n", + "\t\"City\" NVARCHAR(40), \n", + "\t\"State\" NVARCHAR(40), \n", + "\t\"Country\" NVARCHAR(40), \n", + "\t\"PostalCode\" NVARCHAR(10), \n", + "\t\"Phone\" NVARCHAR(24), \n", + "\t\"Fax\" NVARCHAR(24), \n", + "\t\"Email\" NVARCHAR(60) NOT NULL, \n", + "\t\"SupportRepId\" INTEGER, \n", + "\tPRIMARY KEY (\"CustomerId\"), \n", + "\tFOREIGN KEY(\"SupportRepId\") REFERENCES \"Employee\" (\"EmployeeId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Customer table:\n", + "CustomerId\tFirstName\tLastName\tCompany\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\tSupportRepId\n", + "1\tLuís\tGonçalves\tEmbraer - Empresa Brasileira de Aeronáutica S.A.\tAv. Brigadeiro Faria Lima, 2170\tSão José dos Campos\tSP\tBrazil\t12227-000\t+55 (12) 3923-5555\t+55 (12) 3923-5566\tluisg@embraer.com.br\t3\n", + "2\tLeonie\tKöhler\tNone\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t+49 0711 2842222\tNone\tleonekohler@surfeu.de\t5\n", + "3\tFrançois\tTremblay\tNone\t1498 rue Bélanger\tMontréal\tQC\tCanada\tH2G 1A7\t+1 (514) 721-4711\tNone\tftremblay@gmail.com\t3\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Employee\" (\n", + "\t\"EmployeeId\" INTEGER NOT NULL, \n", + "\t\"LastName\" NVARCHAR(20) NOT NULL, \n", + "\t\"FirstName\" NVARCHAR(20) NOT NULL, \n", + "\t\"Title\" NVARCHAR(30), \n", + "\t\"ReportsTo\" INTEGER, \n", + "\t\"BirthDate\" DATETIME, \n", + "\t\"HireDate\" DATETIME, \n", + "\t\"Address\" NVARCHAR(70), \n", + "\t\"City\" NVARCHAR(40), \n", + "\t\"State\" NVARCHAR(40), \n", + "\t\"Country\" NVARCHAR(40), \n", + "\t\"PostalCode\" NVARCHAR(10), \n", + "\t\"Phone\" NVARCHAR(24), \n", + "\t\"Fax\" NVARCHAR(24), \n", + "\t\"Email\" NVARCHAR(60), \n", + "\tPRIMARY KEY (\"EmployeeId\"), \n", + "\tFOREIGN KEY(\"ReportsTo\") REFERENCES \"Employee\" (\"EmployeeId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Employee table:\n", + "EmployeeId\tLastName\tFirstName\tTitle\tReportsTo\tBirthDate\tHireDate\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\n", + "1\tAdams\tAndrew\tGeneral Manager\tNone\t1962-02-18 00:00:00\t2002-08-14 00:00:00\t11120 Jasper Ave NW\tEdmonton\tAB\tCanada\tT5K 2N1\t+1 (780) 428-9482\t+1 (780) 428-3457\tandrew@chinookcorp.com\n", + "2\tEdwards\tNancy\tSales Manager\t1\t1958-12-08 00:00:00\t2002-05-01 00:00:00\t825 8 Ave SW\tCalgary\tAB\tCanada\tT2P 2T3\t+1 (403) 262-3443\t+1 (403) 262-3322\tnancy@chinookcorp.com\n", + "3\tPeacock\tJane\tSales Support Agent\t2\t1973-08-29 00:00:00\t2002-04-01 00:00:00\t1111 6 Ave SW\tCalgary\tAB\tCanada\tT2P 5M5\t+1 (403) 262-3443\t+1 (403) 262-6712\tjane@chinookcorp.com\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Genre\" (\n", + "\t\"GenreId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120), \n", + "\tPRIMARY KEY (\"GenreId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Genre table:\n", + "GenreId\tName\n", + "1\tRock\n", + "2\tJazz\n", + "3\tMetal\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Invoice\" (\n", + "\t\"InvoiceId\" INTEGER NOT NULL, \n", + "\t\"CustomerId\" INTEGER NOT NULL, \n", + "\t\"InvoiceDate\" DATETIME NOT NULL, \n", + "\t\"BillingAddress\" NVARCHAR(70), \n", + "\t\"BillingCity\" NVARCHAR(40), \n", + "\t\"BillingState\" NVARCHAR(40), \n", + "\t\"BillingCountry\" NVARCHAR(40), \n", + "\t\"BillingPostalCode\" NVARCHAR(10), \n", + "\t\"Total\" NUMERIC(10, 2) NOT NULL, \n", + "\tPRIMARY KEY (\"InvoiceId\"), \n", + "\tFOREIGN KEY(\"CustomerId\") REFERENCES \"Customer\" (\"CustomerId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Invoice table:\n", + "InvoiceId\tCustomerId\tInvoiceDate\tBillingAddress\tBillingCity\tBillingState\tBillingCountry\tBillingPostalCode\tTotal\n", + "1\t2\t2021-01-01 00:00:00\tTheodor-Heuss-Straße 34\tStuttgart\tNone\tGermany\t70174\t1.98\n", + "2\t4\t2021-01-02 00:00:00\tUllevålsveien 14\tOslo\tNone\tNorway\t0171\t3.96\n", + "3\t8\t2021-01-03 00:00:00\tGrétrystraat 63\tBrussels\tNone\tBelgium\t1000\t5.94\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"InvoiceLine\" (\n", + "\t\"InvoiceLineId\" INTEGER NOT NULL, \n", + "\t\"InvoiceId\" INTEGER NOT NULL, \n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", + "\t\"Quantity\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"InvoiceLineId\"), \n", + "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", + "\tFOREIGN KEY(\"InvoiceId\") REFERENCES \"Invoice\" (\"InvoiceId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from InvoiceLine table:\n", + "InvoiceLineId\tInvoiceId\tTrackId\tUnitPrice\tQuantity\n", + "1\t1\t2\t0.99\t1\n", + "2\t1\t4\t0.99\t1\n", + "3\t2\t6\t0.99\t1\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"MediaType\" (\n", + "\t\"MediaTypeId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120), \n", + "\tPRIMARY KEY (\"MediaTypeId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from MediaType table:\n", + "MediaTypeId\tName\n", + "1\tMPEG audio file\n", + "2\tProtected AAC audio file\n", + "3\tProtected MPEG-4 video file\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Playlist\" (\n", + "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120), \n", + "\tPRIMARY KEY (\"PlaylistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Playlist table:\n", + "PlaylistId\tName\n", + "1\tMusic\n", + "2\tMovies\n", + "3\tTV Shows\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"PlaylistTrack\" (\n", + "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \n", + "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", + "\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from PlaylistTrack table:\n", + "PlaylistId\tTrackId\n", + "1\t3402\n", + "1\t3389\n", + "1\t3390\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Track\" (\n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(200) NOT NULL, \n", + "\t\"AlbumId\" INTEGER, \n", + "\t\"MediaTypeId\" INTEGER NOT NULL, \n", + "\t\"GenreId\" INTEGER, \n", + "\t\"Composer\" NVARCHAR(220), \n", + "\t\"Milliseconds\" INTEGER NOT NULL, \n", + "\t\"Bytes\" INTEGER, \n", + "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", + "\tPRIMARY KEY (\"TrackId\"), \n", + "\tFOREIGN KEY(\"MediaTypeId\") REFERENCES \"MediaType\" (\"MediaTypeId\"), \n", + "\tFOREIGN KEY(\"GenreId\") REFERENCES \"Genre\" (\"GenreId\"), \n", + "\tFOREIGN KEY(\"AlbumId\") REFERENCES \"Album\" (\"AlbumId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Track table:\n", + "TrackId\tName\tAlbumId\tMediaTypeId\tGenreId\tComposer\tMilliseconds\tBytes\tUnitPrice\n", + "1\tFor Those About To Rock (We Salute You)\t1\t1\t1\tAngus Young, Malcolm Young, Brian Johnson\t343719\t11170334\t0.99\n", + "2\tBalls to the Wall\t2\t2\t1\tU. Dirkschneider, W. Hoffmann, H. Frank, P. Baltes, S. Kaufmann, G. Hoffmann\t342562\t5510424\t0.99\n", + "3\tFast As a Shark\t3\t2\t1\tF. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman\t230619\t3990994\t0.99\n", + "*/\n" + ] + } + ], + "source": [ + "context = db.get_context()\n", + "print(list(context))\n", + "print(context[\"table_info\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we don't have too many, or too wide of, tables, we can just insert the entirety of this information in our prompt:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, first create a syntactically correct SQLite query to run, then look at the results of the query and return the answer to the input question.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most 5 results using the LIMIT clause as per SQLite. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Use the following format:\n", + "\n", + "Question: Question here\n", + "SQLQuery: SQL Query to run\n", + "SQLResult: Result of the SQLQuery\n", + "Answer: Final answer here\n", + "\n", + "Only use the following tables:\n", + "\n", + "CREATE TABLE \"Album\" (\n", + "\t\"AlbumId\" INTEGER NOT NULL, \n", + "\t\"Title\" NVARCHAR(160) NOT NULL, \n", + "\t\"ArtistId\" INTEGER NOT NULL, \n", + "\tPRIMARY KEY (\"AlbumId\"), \n", + "\tFOREIGN KEY(\"ArtistId\") REFERENCES \"Artist\" (\"ArtistId\")\n", + ")\n", + "\n", + "/*\n", + "3 rows from Album table:\n", + "AlbumId\tTitle\tArtistId\n", + "1\tFor Those About To Rock We Salute You\t1\n", + "2\tBalls to the Wall\t2\n", + "3\tRestless and Wild\t2\n", + "*/\n", + "\n", + "\n", + "CREATE TABLE \"Artist\" (\n", + "\t\"ArtistId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(120)\n" + ] + } + ], + "source": [ + "prompt_with_context = chain.get_prompts()[0].partial(table_info=context[\"table_info\"])\n", + "print(prompt_with_context.pretty_repr()[:1500])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we do have database schemas that are too large to fit into our model's context window, we'll need to come up with ways of inserting only the relevant table definitions into the prompt based on the user input. For more on this head to the [Many tables, wide tables, high-cardinality feature](/docs/how_to/sql_large_db) guide." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Few-shot examples\n", + "\n", + "Including examples of natural language questions being converted to valid SQL 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": 7, + "metadata": {}, + "outputs": [], + "source": [ + "examples = [\n", + " {\"input\": \"List all artists.\", \"query\": \"SELECT * FROM Artist;\"},\n", + " {\n", + " \"input\": \"Find all albums for the artist 'AC/DC'.\",\n", + " \"query\": \"SELECT * FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'AC/DC');\",\n", + " },\n", + " {\n", + " \"input\": \"List all tracks in the 'Rock' genre.\",\n", + " \"query\": \"SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\",\n", + " },\n", + " {\n", + " \"input\": \"Find the total duration of all tracks.\",\n", + " \"query\": \"SELECT SUM(Milliseconds) FROM Track;\",\n", + " },\n", + " {\n", + " \"input\": \"List all customers from Canada.\",\n", + " \"query\": \"SELECT * FROM Customer WHERE Country = 'Canada';\",\n", + " },\n", + " {\n", + " \"input\": \"How many tracks are there in the album with ID 5?\",\n", + " \"query\": \"SELECT COUNT(*) FROM Track WHERE AlbumId = 5;\",\n", + " },\n", + " {\n", + " \"input\": \"Find the total number of invoices.\",\n", + " \"query\": \"SELECT COUNT(*) FROM Invoice;\",\n", + " },\n", + " {\n", + " \"input\": \"List all tracks that are longer than 5 minutes.\",\n", + " \"query\": \"SELECT * FROM Track WHERE Milliseconds > 300000;\",\n", + " },\n", + " {\n", + " \"input\": \"Who are the top 5 customers by total purchase?\",\n", + " \"query\": \"SELECT CustomerId, SUM(Total) AS TotalPurchase FROM Invoice GROUP BY CustomerId ORDER BY TotalPurchase DESC LIMIT 5;\",\n", + " },\n", + " {\n", + " \"input\": \"Which albums are from the year 2000?\",\n", + " \"query\": \"SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';\",\n", + " },\n", + " {\n", + " \"input\": \"How many employees are there\",\n", + " \"query\": 'SELECT COUNT(*) FROM \"Employee\"',\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can create a few-shot prompt with them like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate\n", + "\n", + "example_prompt = PromptTemplate.from_template(\"User input: {input}\\nSQL query: {query}\")\n", + "prompt = FewShotPromptTemplate(\n", + " examples=examples[:5],\n", + " example_prompt=example_prompt,\n", + " prefix=\"You are a SQLite expert. Given an input question, create a syntactically correct SQLite query to run. Unless otherwise specificed, do not return more than {top_k} rows.\\n\\nHere is the relevant table info: {table_info}\\n\\nBelow are a number of examples of questions and their corresponding SQL queries.\",\n", + " suffix=\"User input: {input}\\nSQL query: \",\n", + " input_variables=[\"input\", \"top_k\", \"table_info\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, create a syntactically correct SQLite query to run. Unless otherwise specificed, do not return more than 3 rows.\n", + "\n", + "Here is the relevant table info: foo\n", + "\n", + "Below are a number of examples of questions and their corresponding SQL queries.\n", + "\n", + "User input: List all artists.\n", + "SQL query: SELECT * FROM Artist;\n", + "\n", + "User input: Find all albums for the artist 'AC/DC'.\n", + "SQL query: SELECT * FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'AC/DC');\n", + "\n", + "User input: List all tracks in the 'Rock' genre.\n", + "SQL query: SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\n", + "\n", + "User input: Find the total duration of all tracks.\n", + "SQL query: SELECT SUM(Milliseconds) FROM Track;\n", + "\n", + "User input: List all customers from Canada.\n", + "SQL query: SELECT * FROM Customer WHERE Country = 'Canada';\n", + "\n", + "User input: How many artists are there?\n", + "SQL query: \n" + ] + } + ], + "source": [ + "print(prompt.format(input=\"How many artists are there?\", top_k=3, table_info=\"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.\n", + "\n", + "We default to OpenAI embeddings here, but you can swap them out for the model provider of your choice." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import FAISS\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", + " FAISS,\n", + " k=5,\n", + " input_keys=[\"input\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'input': 'List all artists.', 'query': 'SELECT * FROM Artist;'},\n", + " {'input': 'How many employees are there',\n", + " 'query': 'SELECT COUNT(*) FROM \"Employee\"'},\n", + " {'input': 'How many tracks are there in the album with ID 5?',\n", + " 'query': 'SELECT COUNT(*) FROM Track WHERE AlbumId = 5;'},\n", + " {'input': 'Which albums are from the year 2000?',\n", + " 'query': \"SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';\"},\n", + " {'input': \"List all tracks in the 'Rock' genre.\",\n", + " 'query': \"SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\"}]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example_selector.select_examples({\"input\": \"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": 12, + "metadata": {}, + "outputs": [], + "source": [ + "prompt = FewShotPromptTemplate(\n", + " example_selector=example_selector,\n", + " example_prompt=example_prompt,\n", + " prefix=\"You are a SQLite expert. Given an input question, create a syntactically correct SQLite query to run. Unless otherwise specificed, do not return more than {top_k} rows.\\n\\nHere is the relevant table info: {table_info}\\n\\nBelow are a number of examples of questions and their corresponding SQL queries.\",\n", + " suffix=\"User input: {input}\\nSQL query: \",\n", + " input_variables=[\"input\", \"top_k\", \"table_info\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You are a SQLite expert. Given an input question, create a syntactically correct SQLite query to run. Unless otherwise specificed, do not return more than 3 rows.\n", + "\n", + "Here is the relevant table info: foo\n", + "\n", + "Below are a number of examples of questions and their corresponding SQL queries.\n", + "\n", + "User input: List all artists.\n", + "SQL query: SELECT * FROM Artist;\n", + "\n", + "User input: How many employees are there\n", + "SQL query: SELECT COUNT(*) FROM \"Employee\"\n", + "\n", + "User input: How many tracks are there in the album with ID 5?\n", + "SQL query: SELECT COUNT(*) FROM Track WHERE AlbumId = 5;\n", + "\n", + "User input: Which albums are from the year 2000?\n", + "SQL query: SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';\n", + "\n", + "User input: List all tracks in the 'Rock' genre.\n", + "SQL query: SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');\n", + "\n", + "User input: how many artists are there?\n", + "SQL query: \n" + ] + } + ], + "source": [ + "print(prompt.format(input=\"how many artists are there?\", top_k=3, table_info=\"foo\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Trying it out, we see that the model identifies the relevant table:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'SELECT COUNT(*) FROM Artist;'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = create_sql_query_chain(llm, db, prompt)\n", + "chain.invoke({\"question\": \"how many artists are there?\"})" + ] + } + ], + "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": 4 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/sql_query_checking.ipynb b/docs/versioned_docs/version-0.2.x/how_to/sql_query_checking.ipynb new file mode 100644 index 0000000000000..99ca9f9ddeafd --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/sql_query_checking.ipynb @@ -0,0 +1,402 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4da7ae91-4973-4e97-a570-fa24024ec65d", + "metadata": {}, + "source": [ + "# How to do query validation as part of SQL question-answering\n", + "\n", + "Perhaps the most error-prone part of any SQL chain or agent is writing valid and safe SQL queries. In this guide we'll go over some strategies for validating our queries and handling invalid queries.\n", + "\n", + "We will cover: \n", + "\n", + "1. Appending a \"query validator\" step to the query generation;\n", + "2. Prompt engineering to reduce the incidence of errors.\n", + "\n", + "## Setup\n", + "\n", + "First, get required packages and set environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d40d5bc-3647-4b5d-808a-db470d40fe7a", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-community langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71f46270-e1c6-45b4-b36e-ea2e9f860eba", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the below to use LangSmith. Not required.\n", + "# import os\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"" + ] + }, + { + "cell_type": "markdown", + "id": "a0a2151b-cecf-4559-92a1-ca48824fed18", + "metadata": {}, + "source": [ + "The below example will use a SQLite connection with Chinook database. Follow [these installation steps](https://database.guide/2-sample-databases-sqlite/) to create `Chinook.db` in the same directory as this notebook:\n", + "\n", + "* Save [this file](https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql) as `Chinook_Sqlite.sql`\n", + "* Run `sqlite3 Chinook.db`\n", + "* Run `.read Chinook_Sqlite.sql`\n", + "* Test `SELECT * FROM Artist LIMIT 10;`\n", + "\n", + "Now, `Chinhook.db` is in our directory and we can interface with it using the SQLAlchemy-driven `SQLDatabase` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8cedc936-5268-4bfa-b838-bdcc1ee9573c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sqlite\n", + "['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']\n", + "[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]\n" + ] + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", + "print(db.dialect)\n", + "print(db.get_usable_table_names())\n", + "print(db.run(\"SELECT * FROM Artist LIMIT 10;\"))" + ] + }, + { + "cell_type": "markdown", + "id": "2d203315-fab7-4621-80da-41e9bf82d803", + "metadata": {}, + "source": [ + "## Query checker\n", + "\n", + "Perhaps the simplest strategy is to ask the model itself to check the original query for common mistakes. Suppose we have the following SQL query chain:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d81ebf69-75ad-4c92-baa9-fd152b8e622a", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ec66bb76-b1ad-48ad-a7d4-b518e9421b86", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains import create_sql_query_chain\n", + "\n", + "chain = create_sql_query_chain(llm, db)" + ] + }, + { + "cell_type": "markdown", + "id": "da01023d-cc05-43e3-a38d-ed9d56d3ad15", + "metadata": {}, + "source": [ + "And we want to validate its outputs. We can do so by extending the chain with a second prompt and model call:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "16686750-d8ee-4c60-8d67-b28281cb6164", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "system = \"\"\"Double check the user's {dialect} query for common mistakes, including:\n", + "- Using NOT IN with NULL values\n", + "- Using UNION when UNION ALL should have been used\n", + "- Using BETWEEN for exclusive ranges\n", + "- Data type mismatch in predicates\n", + "- Properly quoting identifiers\n", + "- Using the correct number of arguments for functions\n", + "- Casting to the correct data type\n", + "- Using the proper columns for joins\n", + "\n", + "If there are any of the above mistakes, rewrite the query.\n", + "If there are no mistakes, just reproduce the original query with no further commentary.\n", + "\n", + "Output the final SQL query only.\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"system\", system), (\"human\", \"{query}\")]\n", + ").partial(dialect=db.dialect)\n", + "validation_chain = prompt | llm | StrOutputParser()\n", + "\n", + "full_chain = {\"query\": chain} | validation_chain" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "28ef9c6e-21fa-4b62-8aa4-8cd398ce4c4d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT AVG(i.Total) AS AverageInvoice\n", + "FROM Invoice i\n", + "JOIN Customer c ON i.CustomerId = c.CustomerId\n", + "WHERE c.Country = 'USA'\n", + "AND c.Fax IS NULL\n", + "AND i.InvoiceDate >= '2003-01-01' \n", + "AND i.InvoiceDate < '2010-01-01'\n" + ] + } + ], + "source": [ + "query = full_chain.invoke(\n", + " {\n", + " \"question\": \"What's the average Invoice from an American customer whose Fax is missing since 2003 but before 2010\"\n", + " }\n", + ")\n", + "print(query)" + ] + }, + { + "cell_type": "markdown", + "id": "228a1b87-4e44-4d86-bed7-fd2d7a91fb23", + "metadata": {}, + "source": [ + "Note how we can see both steps of the chain in the [Langsmith trace](https://smith.langchain.com/public/8a743295-a57c-4e4c-8625-bc7e36af9d74/r)." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d01d78b5-89a0-4c12-b743-707ebe64ba86", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[(6.632999999999998,)]'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "6e133526-26bd-49da-9cfa-7adc0e59fd72", + "metadata": {}, + "source": [ + "The obvious downside of this approach is that we need to make two model calls instead of one to generate our query. To get around this we can try to perform the query generation and query check in a single model invocation:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7af0030a-549e-4e69-9298-3d0a038c2fdd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m System Message \u001b[0m================================\n", + "\n", + "You are a \u001b[33;1m\u001b[1;3m{dialect}\u001b[0m expert. Given an input question, creat a syntactically correct \u001b[33;1m\u001b[1;3m{dialect}\u001b[0m query to run.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most \u001b[33;1m\u001b[1;3m{top_k}\u001b[0m results using the LIMIT clause as per \u001b[33;1m\u001b[1;3m{dialect}\u001b[0m. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Only use the following tables:\n", + "\u001b[33;1m\u001b[1;3m{table_info}\u001b[0m\n", + "\n", + "Write an initial draft of the query. Then double check the \u001b[33;1m\u001b[1;3m{dialect}\u001b[0m query for common mistakes, including:\n", + "- Using NOT IN with NULL values\n", + "- Using UNION when UNION ALL should have been used\n", + "- Using BETWEEN for exclusive ranges\n", + "- Data type mismatch in predicates\n", + "- Properly quoting identifiers\n", + "- Using the correct number of arguments for functions\n", + "- Casting to the correct data type\n", + "- Using the proper columns for joins\n", + "\n", + "Use format:\n", + "\n", + "First draft: <>\n", + "Final answer: <>\n", + "\n", + "\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{input}\u001b[0m\n" + ] + } + ], + "source": [ + "system = \"\"\"You are a {dialect} expert. Given an input question, creat a syntactically correct {dialect} query to run.\n", + "Unless the user specifies in the question a specific number of examples to obtain, query for at most {top_k} results using the LIMIT clause as per {dialect}. You can order the results to return the most informative data in the database.\n", + "Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (\") to denote them as delimited identifiers.\n", + "Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.\n", + "Pay attention to use date('now') function to get the current date, if the question involves \"today\".\n", + "\n", + "Only use the following tables:\n", + "{table_info}\n", + "\n", + "Write an initial draft of the query. Then double check the {dialect} query for common mistakes, including:\n", + "- Using NOT IN with NULL values\n", + "- Using UNION when UNION ALL should have been used\n", + "- Using BETWEEN for exclusive ranges\n", + "- Data type mismatch in predicates\n", + "- Properly quoting identifiers\n", + "- Using the correct number of arguments for functions\n", + "- Casting to the correct data type\n", + "- Using the proper columns for joins\n", + "\n", + "Use format:\n", + "\n", + "First draft: <>\n", + "Final answer: <>\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"system\", system), (\"human\", \"{input}\")]\n", + ").partial(dialect=db.dialect)\n", + "\n", + "\n", + "def parse_final_answer(output: str) -> str:\n", + " return output.split(\"Final answer: \")[1]\n", + "\n", + "\n", + "chain = create_sql_query_chain(llm, db, prompt=prompt) | parse_final_answer\n", + "prompt.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "806e27a2-e511-45ea-a4ed-8ce8fa6e1d58", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "SELECT AVG(i.\"Total\") AS \"AverageInvoice\"\n", + "FROM \"Invoice\" i\n", + "JOIN \"Customer\" c ON i.\"CustomerId\" = c.\"CustomerId\"\n", + "WHERE c.\"Country\" = 'USA'\n", + "AND c.\"Fax\" IS NULL\n", + "AND i.\"InvoiceDate\" BETWEEN '2003-01-01' AND '2010-01-01';\n" + ] + } + ], + "source": [ + "query = chain.invoke(\n", + " {\n", + " \"question\": \"What's the average Invoice from an American customer whose Fax is missing since 2003 but before 2010\"\n", + " }\n", + ")\n", + "print(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "70fff2fa-1f86-4f83-9fd2-e87a5234d329", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[(6.632999999999998,)]'" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.run(query)" + ] + }, + { + "cell_type": "markdown", + "id": "fc8af115-7c23-421a-8fd7-29bf1b6687a4", + "metadata": {}, + "source": [ + "## Human-in-the-loop\n", + "\n", + "In some cases our data is sensitive enough that we never want to execute a SQL query without a human approving it first. Head to the [Tool use: Human-in-the-loop](/docs/how_to/tools_human) page to learn how to add a human-in-the-loop to any tool, chain or agent.\n", + "\n", + "## Error handling\n", + "\n", + "At some point, the model will make a mistake and craft an invalid SQL query. Or an issue will arise with our database. Or the model API will go down. We'll want to add some error handling behavior to our chains and agents so that we fail gracefully in these situations, and perhaps even automatically recover. To learn about error handling with tools, head to the [Tool use: Error handling](/docs/how_to/tools_error) page." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/streaming.ipynb b/docs/versioned_docs/version-0.2.x/how_to/streaming.ipynb new file mode 100644 index 0000000000000..8dc73214eac68 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/streaming.ipynb @@ -0,0 +1,1472 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "0bdb3b97-4989-4237-b43b-5943dbbd8302", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 1.5\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "bb7d49db-04d3-4399-bfe1-09f82bbe6015", + "metadata": {}, + "source": [ + "# How to stream\n", + "\n", + "Streaming is critical in making applications based on LLMs feel responsive to end-users.\n", + "\n", + "Important LangChain primitives like [chat models](/docs/concepts/#chat-models), [output parsers](/docs/concepts/#output-parsers), [prompts](/docs/concepts/#prompt-templates), [retrievers](/docs/concepts/#retrievers), and [agents](/docs/concepts/#agents) implement the LangChain [Runnable Interface](/docs/expression_language/interface).\n", + "\n", + "This interface provides two general approaches to stream content:\n", + "\n", + "1. sync `stream` and async `astream`: a **default implementation** of streaming that streams the **final output** from the chain.\n", + "2. async `astream_events` and async `astream_log`: these provide a way to stream both **intermediate steps** and **final output** from the chain.\n", + "\n", + "Let's take a look at both approaches, and try to understand how to use them.\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```\n", + "\n", + "## Using Stream\n", + "\n", + "All `Runnable` objects implement a sync method called `stream` and an async variant called `astream`. \n", + "\n", + "These methods are designed to stream the final output in chunks, yielding each chunk as soon as it is available.\n", + "\n", + "Streaming is only possible if all steps in the program know how to process an **input stream**; i.e., process an input chunk one at a time, and yield a corresponding output chunk.\n", + "\n", + "The complexity of this processing can vary, from straightforward tasks like emitting tokens produced by an LLM, to more challenging ones like streaming parts of JSON results before the entire JSON is complete.\n", + "\n", + "The best place to start exploring streaming is with the single most important components in LLMs apps-- the LLMs themselves!\n", + "\n", + "### LLMs and Chat Models\n", + "\n", + "Large language models and their chat variants are the primary bottleneck in LLM based apps.\n", + "\n", + "Large language models can take **several seconds** to generate a complete response to a query. This is far slower than the **~200-300 ms** threshold at which an application feels responsive to an end user.\n", + "\n", + "The key strategy to make the application feel more responsive is to show intermediate progress; viz., to stream the output from the model **token by token**.\n", + "\n", + "We will show examples of streaming using a chat model. Choose one from the options below:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cd351cf4", + "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": [ + "# | 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", + "os.environ[\"OPENAI_API_KEY\"] = getpass()\n", + "\n", + "model = ChatAnthropic(model=\"claude-3-sonnet-20240229\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "91787fc7-d941-48c0-a8b4-0ee61ab7dd5d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The| sky| appears| blue| during| the| da|ytime|.|" + ] + } + ], + "source": [ + "chunks = []\n", + "async for chunk in model.astream(\"what color is the sky?\"):\n", + " chunks.append(chunk)\n", + " print(chunk.content, end=\"|\", flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "66730a87-77d5-40d6-a68f-315121989bd1", + "metadata": {}, + "source": [ + "Let's inspect one of the chunks" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "dade3000-1ac4-4f5c-b5c6-a0217f9f8a6b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessageChunk(content='The', id='run-c3885fff-3783-4b6d-85c4-4aeb45a02b1a')" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chunks[0]" + ] + }, + { + "cell_type": "markdown", + "id": "a3a47193-2bd1-46bc-9c7e-ea0f6b08c4a5", + "metadata": {}, + "source": [ + "We got back something called an `AIMessageChunk`. This chunk represents a part of an `AIMessage`.\n", + "\n", + "Message chunks are additive by design -- one can simply add them up to get the state of the response so far!" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d3cf5f38-249c-4da0-94e6-5e5203fad52e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessageChunk(content='The sky appears blue during', id='run-c3885fff-3783-4b6d-85c4-4aeb45a02b1a')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chunks[0] + chunks[1] + chunks[2] + chunks[3] + chunks[4]" + ] + }, + { + "cell_type": "markdown", + "id": "59ffbd9a-3b79-44b6-8883-1371f9460c77", + "metadata": {}, + "source": [ + "### Chains\n", + "\n", + "Virtually all LLM applications involve more steps than just a call to a language model.\n", + "\n", + "Let's build a simple chain using `LangChain Expression Language` (`LCEL`) that combines a prompt, model and a parser and verify that streaming works.\n", + "\n", + "We will use [`StrOutputParser`](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.string.StrOutputParser.html) to parse the output from the model. This is a simple parser that extracts the `content` field from an `AIMessageChunk`, giving us the `token` returned by the model.\n", + "\n", + ":::{.callout-tip}\n", + "LCEL is a *declarative* way to specify a \"program\" by chainining together different LangChain primitives. Chains created using LCEL benefit from an automatic implementation of `stream` and `astream` allowing streaming of the final output. In fact, chains created with LCEL implement the entire standard Runnable interface.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a8562ae2-3fd1-4829-9801-a5a732b1798d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Here|'s| a| joke| about| a| par|rot|:|\n", + "\n", + "A man| goes| to| a| pet| shop| to| buy| a| par|rot|.| The| shop| owner| shows| him| two| stunning| pa|rr|ots| with| beautiful| pl|um|age|.|\n", + "\n", + "\"|There|'s| a| talking| par|rot| and| a| non|-|talking| par|rot|,\"| the| shop| owner| says|.| \"|The| talking| par|rot| costs| $|100|,| and| the| non|-|talking| par|rot| is| $|20|.\"|\n", + "\n", + "The| man| thinks| about| it| and| decides| to| buy| the| cheaper| non|-|talking| par|rot|.|\n", + "\n", + "When| he| gets| home|,| the| par|rot| immediately| speaks| up| and| says|,| \"|Hey|,| buddy|,| I|'m| actually| the| talking| par|rot|,| and| you| got| an| amazing| deal|!\"|\n", + "\n", + "The| man| is| stun|ned| and| rush|es| back| to| the| pet| shop| the| next| day|.|\n", + "\n", + "\"|That| par|rot| you| sold| me| can| talk|!\"| he| tells| the| shop| owner|.| \"|You| said| it| was| the| non|-|talking| par|rot|,| but| it|'s| been| talking| up| a| storm|!\"|\n", + "\n", + "The| shop| owner| n|ods| and| says|,| \"|Yeah|,| I| know|.| But| did| you| really| think| I| was| going| to| sell| you| the| talking| par|rot| for| just| $|20|?\"|" + ] + } + ], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "prompt = ChatPromptTemplate.from_template(\"tell me a joke about {topic}\")\n", + "parser = StrOutputParser()\n", + "chain = prompt | model | parser\n", + "\n", + "async for chunk in chain.astream({\"topic\": \"parrot\"}):\n", + " print(chunk, end=\"|\", flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "868bc412", + "metadata": {}, + "source": [ + "You might notice above that `parser` actually doesn't block the streaming output from the model, and instead processes each chunk individually. Many of the [LCEL primitives](/docs/expression_language/primitives) also support this kind of transform-style passthrough streaming, which can be very convenient when constructing apps.\n", + "\n", + "Certain runnables, like [prompt templates](/docs/modules/model_io/prompts) and [chat models](/docs/modules/model_io/chat), cannot process individual chunks and instead aggregate all previous steps. This will interrupt the streaming process. Custom functions can be [designed to return generators](/docs/expression_language/primitives/functions#streaming), which" + ] + }, + { + "cell_type": "markdown", + "id": "1b399fb4-5e3c-4581-9570-6df9b42b623d", + "metadata": {}, + "source": [ + ":::{.callout-note}\n", + "If the above functionality is not relevant to what you're building, you do not have to use the `LangChain Expression Language` to use LangChain and can instead rely on a standard **imperative** programming approach by\n", + "caling `invoke`, `batch` or `stream` on each component individually, assigning the results to variables and then using them downstream as you see fit.\n", + "\n", + "If that works for your needs, then that's fine by us 👌!\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "dfff2701-8887-486f-8b3b-eb26383d4bb6", + "metadata": {}, + "source": [ + "### Working with Input Streams\n", + "\n", + "What if you wanted to stream JSON from the output as it was being generated?\n", + "\n", + "If you were to rely on `json.loads` to parse the partial json, the parsing would fail as the partial json wouldn't be valid json.\n", + "\n", + "You'd likely be at a complete loss of what to do and claim that it wasn't possible to stream JSON.\n", + "\n", + "Well, turns out there is a way to do it -- the parser needs to operate on the **input stream**, and attempt to \"auto-complete\" the partial json into a valid state.\n", + "\n", + "Let's see such a parser in action to understand what this means." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5ff63cce-715a-4561-951f-9321c82e8d81", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{}\n", + "{'countries': []}\n", + "{'countries': [{}]}\n", + "{'countries': [{'name': ''}]}\n", + "{'countries': [{'name': 'France'}]}\n", + "{'countries': [{'name': 'France', 'population': 67}]}\n", + "{'countries': [{'name': 'France', 'population': 67413}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': ''}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain'}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351567}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351567}, {}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351567}, {'name': ''}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351567}, {'name': 'Japan'}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351567}, {'name': 'Japan', 'population': 125}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351567}, {'name': 'Japan', 'population': 125584}]}\n", + "{'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351567}, {'name': 'Japan', 'population': 125584000}]}\n" + ] + } + ], + "source": [ + "from langchain_core.output_parsers import JsonOutputParser\n", + "\n", + "chain = (\n", + " model | JsonOutputParser()\n", + ") # Due to a bug in older versions of Langchain, JsonOutputParser did not stream results from some models\n", + "async for text in chain.astream(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`'\n", + "):\n", + " print(text, flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "151d4323-a6cf-49be-8779-e8797c5e3b00", + "metadata": {}, + "source": [ + "Now, let's **break** streaming. We'll use the previous example and append an extraction function at the end that extracts the country names from the finalized JSON.\n", + "\n", + ":::{.callout-warning}\n", + "Any steps in the chain that operate on **finalized inputs** rather than on **input streams** can break streaming functionality via `stream` or `astream`.\n", + ":::\n", + "\n", + ":::{.callout-tip}\n", + "Later, we will discuss the `astream_events` API which streams results from intermediate steps. This API will stream results from intermediate steps even if the chain contains steps that only operate on **finalized inputs**.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d9c90117-9faa-4a01-b484-0db071808d1f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[None, '', 'France', 'France', 'France', 'France', 'France', None, 'France', '', 'France', 'Spain', 'France', 'Spain', 'France', 'Spain', 'France', 'Spain', 'France', 'Spain', None, 'France', 'Spain', '', 'France', 'Spain', 'Japan', 'France', 'Spain', 'Japan', 'France', 'Spain', 'Japan', 'France', 'Spain', 'Japan']|" + ] + } + ], + "source": [ + "from langchain_core.output_parsers import (\n", + " JsonOutputParser,\n", + ")\n", + "\n", + "\n", + "# A function that operates on finalized inputs\n", + "# rather than on an input_stream\n", + "def _extract_country_names(inputs):\n", + " \"\"\"A function that does not operates on input streams and breaks streaming.\"\"\"\n", + " if not isinstance(inputs, dict):\n", + " return \"\"\n", + "\n", + " if \"countries\" not in inputs:\n", + " return \"\"\n", + "\n", + " countries = inputs[\"countries\"]\n", + "\n", + " if not isinstance(countries, list):\n", + " return \"\"\n", + "\n", + " country_names = [\n", + " country.get(\"name\") for country in countries if isinstance(country, dict)\n", + " ]\n", + " return country_names\n", + "\n", + "\n", + "chain = model | JsonOutputParser() | _extract_country_names\n", + "\n", + "async for text in chain.astream(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`'\n", + "):\n", + " print(text, end=\"|\", flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "cab6dca2-2027-414d-a196-2db6e3ebb8a5", + "metadata": {}, + "source": [ + "#### Generator Functions\n", + "\n", + "Le'ts fix the streaming using a generator function that can operate on the **input stream**.\n", + "\n", + ":::{.callout-tip}\n", + "A generator function (a function that uses `yield`) allows writing code that operators on **input streams**\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "15984b2b-315a-4119-945b-2a3dabea3082", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "France|Spain|Japan|" + ] + } + ], + "source": [ + "from langchain_core.output_parsers import JsonOutputParser\n", + "\n", + "\n", + "async def _extract_country_names_streaming(input_stream):\n", + " \"\"\"A function that operates on input streams.\"\"\"\n", + " country_names_so_far = set()\n", + "\n", + " async for input in input_stream:\n", + " if not isinstance(input, dict):\n", + " continue\n", + "\n", + " if \"countries\" not in input:\n", + " continue\n", + "\n", + " countries = input[\"countries\"]\n", + "\n", + " if not isinstance(countries, list):\n", + " continue\n", + "\n", + " for country in countries:\n", + " name = country.get(\"name\")\n", + " if not name:\n", + " continue\n", + " if name not in country_names_so_far:\n", + " yield name\n", + " country_names_so_far.add(name)\n", + "\n", + "\n", + "chain = model | JsonOutputParser() | _extract_country_names_streaming\n", + "\n", + "async for text in chain.astream(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`'\n", + "):\n", + " print(text, end=\"|\", flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "d59823f5-9b9a-43c5-a213-34644e2f1d3d", + "metadata": {}, + "source": [ + ":::{.callout-note}\n", + "Because the code above is relying on JSON auto-completion, you may see partial names of countries (e.g., `Sp` and `Spain`), which is not what one would want for an extraction result!\n", + "\n", + "We're focusing on streaming concepts, not necessarily the results of the chains.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "6adf65b7-aa47-4321-98c7-a0abe43b833a", + "metadata": {}, + "source": [ + "### Non-streaming components\n", + "\n", + "Some built-in components like Retrievers do not offer any `streaming`. What happens if we try to `stream` them? 🤨" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b9b1c00d-8b44-40d0-9e2b-8a70d238f82b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[Document(page_content='harrison worked at kensho'),\n", + " Document(page_content='harrison likes spicy food')]]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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 OpenAIEmbeddings\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", + "vectorstore = FAISS.from_texts(\n", + " [\"harrison worked at kensho\", \"harrison likes spicy food\"],\n", + " embedding=OpenAIEmbeddings(),\n", + ")\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "chunks = [chunk for chunk in retriever.stream(\"where did harrison work?\")]\n", + "chunks" + ] + }, + { + "cell_type": "markdown", + "id": "6fd3e71b-439e-418f-8a8a-5232fba3d9fd", + "metadata": {}, + "source": [ + "Stream just yielded the final result from that component.\n", + "\n", + "This is OK 🥹! Not all components have to implement streaming -- in some cases streaming is either unnecessary, difficult or just doesn't make sense.\n", + "\n", + ":::{.callout-tip}\n", + "An LCEL chain constructed using non-streaming components, will still be able to stream in a lot of cases, with streaming of partial output starting after the last non-streaming step in the chain.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "957447e6-1e60-41ef-8c10-2654bd9e738d", + "metadata": {}, + "outputs": [], + "source": [ + "retrieval_chain = (\n", + " {\n", + " \"context\": retriever.with_config(run_name=\"Docs\"),\n", + " \"question\": RunnablePassthrough(),\n", + " }\n", + " | prompt\n", + " | model\n", + " | StrOutputParser()\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "94e50b5d-bf51-4eee-9da0-ee40dd9ce42b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Based| on| the| given| context|,| Harrison| worked| at| K|ens|ho|.|\n", + "\n", + "Here| are| |3| |made| up| sentences| about| this| place|:|\n", + "\n", + "1|.| K|ens|ho| was| a| cutting|-|edge| technology| company| known| for| its| innovative| solutions| in| artificial| intelligence| and| data| analytics|.|\n", + "\n", + "2|.| The| modern| office| space| at| K|ens|ho| featured| open| floor| plans|,| collaborative| work|sp|aces|,| and| a| vib|rant| atmosphere| that| fos|tered| creativity| and| team|work|.|\n", + "\n", + "3|.| With| its| prime| location| in| the| heart| of| the| city|,| K|ens|ho| attracted| top| talent| from| around| the| world|,| creating| a| diverse| and| dynamic| work| environment|.|" + ] + } + ], + "source": [ + "for chunk in retrieval_chain.stream(\n", + " \"Where did harrison work? \" \"Write 3 made up sentences about this place.\"\n", + "):\n", + " print(chunk, end=\"|\", flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "8657aa4e-3469-4b5b-a09c-60b53a23b1e7", + "metadata": {}, + "source": [ + "Now that we've seen how `stream` and `astream` work, let's venture into the world of streaming events. 🏞️" + ] + }, + { + "cell_type": "markdown", + "id": "baceb5c0-d4a4-4b98-8733-80ae4407b62d", + "metadata": {}, + "source": [ + "## Using Stream Events\n", + "\n", + "Event Streaming is a **beta** API. This API may change a bit based on feedback.\n", + "\n", + ":::{.callout-note}\n", + "Introduced in langchain-core **0.1.14**.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "61348df9-ec58-401e-be89-68a70042f88e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0.1.45'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import langchain_core\n", + "\n", + "langchain_core.__version__" + ] + }, + { + "cell_type": "markdown", + "id": "52e9e983-bbde-4906-9eca-4ccc06eabd91", + "metadata": {}, + "source": [ + "For the `astream_events` API to work properly:\n", + "\n", + "* Use `async` throughout the code to the extent possible (e.g., async tools etc)\n", + "* Propagate callbacks if defining custom functions / runnables\n", + "* Whenever using runnables without LCEL, make sure to call `.astream()` on LLMs rather than `.ainvoke` to force the LLM to stream tokens.\n", + "* Let us know if anything doesn't work as expected! :)\n", + "\n", + "### Event Reference\n", + "\n", + "Below is a reference table that shows some events that might be emitted by the various Runnable objects.\n", + "\n", + "\n", + ":::{.callout-note}\n", + "When streaming is implemented properly, the inputs to a runnable will not be known until after the input stream has been entirely consumed. This means that `inputs` will often be included only for `end` events and rather than for `start` events.\n", + ":::\n", + "\n", + "\n", + "| event | name | chunk | input | output |\n", + "|----------------------|------------------|---------------------------------|-----------------------------------------------|-------------------------------------------------|\n", + "| on_chat_model_start | [model name] | | {\"messages\": [[SystemMessage, HumanMessage]]} | |\n", + "| on_chat_model_stream | [model name] | AIMessageChunk(content=\"hello\") | | |\n", + "| on_chat_model_end | [model name] | | {\"messages\": [[SystemMessage, HumanMessage]]} | {\"generations\": [...], \"llm_output\": None, ...} |\n", + "| on_llm_start | [model name] | | {'input': 'hello'} | |\n", + "| on_llm_stream | [model name] | 'Hello' | | |\n", + "| on_llm_end | [model name] | | 'Hello human!' |\n", + "| on_chain_start | format_docs | | | |\n", + "| on_chain_stream | format_docs | \"hello world!, goodbye world!\" | | |\n", + "| on_chain_end | format_docs | | [Document(...)] | \"hello world!, goodbye world!\" |\n", + "| on_tool_start | some_tool | | {\"x\": 1, \"y\": \"2\"} | |\n", + "| on_tool_stream | some_tool | {\"x\": 1, \"y\": \"2\"} | | |\n", + "| on_tool_end | some_tool | | | {\"x\": 1, \"y\": \"2\"} |\n", + "| on_retriever_start | [retriever name] | | {\"query\": \"hello\"} | |\n", + "| on_retriever_chunk | [retriever name] | {documents: [...]} | | |\n", + "| on_retriever_end | [retriever name] | | {\"query\": \"hello\"} | {documents: [...]} |\n", + "| on_prompt_start | [template_name] | | {\"question\": \"hello\"} | |\n", + "| on_prompt_end | [template_name] | | {\"question\": \"hello\"} | ChatPromptValue(messages: [SystemMessage, ...]) |" + ] + }, + { + "cell_type": "markdown", + "id": "1f6ec135-3348-4041-8f55-bf3e59b3b2d0", + "metadata": {}, + "source": [ + "### Chat Model\n", + "\n", + "Let's start off by looking at the events produced by a chat model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c00df46e-7f6b-4e06-8abf-801898c8d57f", + "metadata": {}, + "outputs": [], + "source": [ + "events = []\n", + "async for event in model.astream_events(\"hello\", version=\"v1\"):\n", + " events.append(event)" + ] + }, + { + "cell_type": "markdown", + "id": "32972939-2995-4b2e-84db-045adb044fad", + "metadata": {}, + "source": [ + ":::{.callout-note}\n", + "\n", + "Hey what's that funny version=\"v1\" parameter in the API?! 😾\n", + "\n", + "This is a **beta API**, and we're almost certainly going to make some changes to it.\n", + "\n", + "This version parameter will allow us to minimize such breaking changes to your code. \n", + "\n", + "In short, we are annoying you now, so we don't have to annoy you later.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "ad2b8f47-da78-4569-a49a-53a8efaa26bc", + "metadata": {}, + "source": [ + "Let's take a look at the few of the start event and a few of the end events." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ce31b525-f47d-4828-85a7-912ce9f2e79b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'event': 'on_chat_model_start',\n", + " 'run_id': '26134ba4-e486-4552-94d9-a31a2dfe7f4a',\n", + " 'name': 'ChatAnthropic',\n", + " 'tags': [],\n", + " 'metadata': {},\n", + " 'data': {'input': 'hello'}},\n", + " {'event': 'on_chat_model_stream',\n", + " 'run_id': '26134ba4-e486-4552-94d9-a31a2dfe7f4a',\n", + " 'tags': [],\n", + " 'metadata': {},\n", + " 'name': 'ChatAnthropic',\n", + " 'data': {'chunk': AIMessageChunk(content='Hello', id='run-26134ba4-e486-4552-94d9-a31a2dfe7f4a')}},\n", + " {'event': 'on_chat_model_stream',\n", + " 'run_id': '26134ba4-e486-4552-94d9-a31a2dfe7f4a',\n", + " 'tags': [],\n", + " 'metadata': {},\n", + " 'name': 'ChatAnthropic',\n", + " 'data': {'chunk': AIMessageChunk(content='!', id='run-26134ba4-e486-4552-94d9-a31a2dfe7f4a')}}]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "events[:3]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "76cfe826-ee63-4310-ad48-55a95eb3b9d6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'event': 'on_chat_model_stream',\n", + " 'run_id': '26134ba4-e486-4552-94d9-a31a2dfe7f4a',\n", + " 'tags': [],\n", + " 'metadata': {},\n", + " 'name': 'ChatAnthropic',\n", + " 'data': {'chunk': AIMessageChunk(content='?', id='run-26134ba4-e486-4552-94d9-a31a2dfe7f4a')}},\n", + " {'event': 'on_chat_model_end',\n", + " 'name': 'ChatAnthropic',\n", + " 'run_id': '26134ba4-e486-4552-94d9-a31a2dfe7f4a',\n", + " 'tags': [],\n", + " 'metadata': {},\n", + " 'data': {'output': AIMessageChunk(content='Hello! How can I assist you today?', id='run-26134ba4-e486-4552-94d9-a31a2dfe7f4a')}}]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "events[-2:]" + ] + }, + { + "cell_type": "markdown", + "id": "98c8f173-e9c7-4c27-81a5-b7c85c12714d", + "metadata": {}, + "source": [ + "### Chain\n", + "\n", + "Let's revisit the example chain that parsed streaming JSON to explore the streaming events API." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "4328c56c-a303-427b-b1f2-f354e9af555c", + "metadata": {}, + "outputs": [], + "source": [ + "chain = (\n", + " model | JsonOutputParser()\n", + ") # Due to a bug in older versions of Langchain, JsonOutputParser did not stream results from some models\n", + "\n", + "events = [\n", + " event\n", + " async for event in chain.astream_events(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`',\n", + " version=\"v1\",\n", + " )\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "4cc00b99-a961-4221-a3c7-9d807114bbfb", + "metadata": {}, + "source": [ + "If you examine at the first few events, you'll notice that there are **3** different start events rather than **2** start events.\n", + "\n", + "The three start events correspond to:\n", + "\n", + "1. The chain (model + parser)\n", + "2. The model\n", + "3. The parser" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "8e66ea3d-a450-436a-aaac-d9478abc6c28", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'event': 'on_chain_start',\n", + " 'run_id': '93c65519-a480-43f2-b340-851706799c57',\n", + " 'name': 'RunnableSequence',\n", + " 'tags': [],\n", + " 'metadata': {},\n", + " 'data': {'input': 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`'}},\n", + " {'event': 'on_chat_model_start',\n", + " 'name': 'ChatAnthropic',\n", + " 'run_id': '6075a178-bc34-4ef2-bbb4-75c3ed96eb9c',\n", + " 'tags': ['seq:step:1'],\n", + " 'metadata': {},\n", + " 'data': {'input': {'messages': [[HumanMessage(content='output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`')]]}}},\n", + " {'event': 'on_chat_model_stream',\n", + " 'name': 'ChatAnthropic',\n", + " 'run_id': '6075a178-bc34-4ef2-bbb4-75c3ed96eb9c',\n", + " 'tags': ['seq:step:1'],\n", + " 'metadata': {},\n", + " 'data': {'chunk': AIMessageChunk(content='{', id='run-6075a178-bc34-4ef2-bbb4-75c3ed96eb9c')}}]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "events[:3]" + ] + }, + { + "cell_type": "markdown", + "id": "c8512238-d035-4acd-9248-a8570da064c9", + "metadata": {}, + "source": [ + "What do you think you'd see if you looked at the last 3 events? what about the middle?" + ] + }, + { + "cell_type": "markdown", + "id": "c742cfa4-9b03-4a5b-96d9-5fe56e95e3b4", + "metadata": {}, + "source": [ + "Let's use this API to take output the stream events from the model and the parser. We're ignoring start events, end events and events from the chain." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "630c71d6-8d94-4ce0-a78a-f20e90f628df", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Chat model chunk: '{'\n", + "Parser chunk: {}\n", + "Chat model chunk: '\\n '\n", + "Chat model chunk: '\"'\n", + "Chat model chunk: 'countries'\n", + "Chat model chunk: '\":'\n", + "Chat model chunk: ' ['\n", + "Parser chunk: {'countries': []}\n", + "Chat model chunk: '\\n '\n", + "Chat model chunk: '{'\n", + "Parser chunk: {'countries': [{}]}\n", + "Chat model chunk: '\\n '\n", + "Chat model chunk: '\"'\n", + "Chat model chunk: 'name'\n", + "Chat model chunk: '\":'\n", + "Chat model chunk: ' \"'\n", + "Parser chunk: {'countries': [{'name': ''}]}\n", + "Chat model chunk: 'France'\n", + "Parser chunk: {'countries': [{'name': 'France'}]}\n", + "Chat model chunk: '\",'\n", + "Chat model chunk: '\\n '\n", + "Chat model chunk: '\"'\n", + "Chat model chunk: 'population'\n", + "...\n" + ] + } + ], + "source": [ + "num_events = 0\n", + "\n", + "async for event in chain.astream_events(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`',\n", + " version=\"v1\",\n", + "):\n", + " kind = event[\"event\"]\n", + " if kind == \"on_chat_model_stream\":\n", + " print(\n", + " f\"Chat model chunk: {repr(event['data']['chunk'].content)}\",\n", + " flush=True,\n", + " )\n", + " if kind == \"on_parser_stream\":\n", + " print(f\"Parser chunk: {event['data']['chunk']}\", flush=True)\n", + " num_events += 1\n", + " if num_events > 30:\n", + " # Truncate the output\n", + " print(\"...\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "798ea891-997c-454c-bf60-43124f40ee1b", + "metadata": {}, + "source": [ + "Because both the model and the parser support streaming, we see sreaming events from both components in real time! Kind of cool isn't it? 🦜" + ] + }, + { + "cell_type": "markdown", + "id": "5084148b-bcdc-4373-9caa-6568f03e7b23", + "metadata": {}, + "source": [ + "### Filtering Events\n", + "\n", + "Because this API produces so many events, it is useful to be able to filter on events.\n", + "\n", + "You can filter by either component `name`, component `tags` or component `type`.\n", + "\n", + "#### By Name" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "4f0b581b-be63-4663-baba-c6d2b625cdf9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'event': 'on_parser_start', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': []}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': [{}]}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': [{'name': ''}]}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': [{'name': 'France'}]}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': [{'name': 'France', 'population': 67}]}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': [{'name': 'France', 'population': 67413}]}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': [{'name': 'France', 'population': 67413000}]}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': [{'name': 'France', 'population': 67413000}, {}]}}}\n", + "{'event': 'on_parser_stream', 'name': 'my_parser', 'run_id': 'b817e94b-db03-4b6f-8432-019dd59a2d93', 'tags': ['seq:step:2'], 'metadata': {}, 'data': {'chunk': {'countries': [{'name': 'France', 'population': 67413000}, {'name': ''}]}}}\n", + "...\n" + ] + } + ], + "source": [ + "chain = model.with_config({\"run_name\": \"model\"}) | JsonOutputParser().with_config(\n", + " {\"run_name\": \"my_parser\"}\n", + ")\n", + "\n", + "max_events = 0\n", + "async for event in chain.astream_events(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`',\n", + " version=\"v1\",\n", + " include_names=[\"my_parser\"],\n", + "):\n", + " print(event)\n", + " max_events += 1\n", + " if max_events > 10:\n", + " # Truncate output\n", + " print(\"...\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "c59d5626-7dba-4eb3-ad81-76c1092c5146", + "metadata": {}, + "source": [ + "#### By Type" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "096cd904-72f0-4ebe-a8b7-d0e730faea7f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'event': 'on_chat_model_start', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'input': {'messages': [[HumanMessage(content='output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`')]]}}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='{', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\\n ', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\"', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='countries', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\":', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content=' [', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\\n ', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='{', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\\n ', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'model', 'run_id': '02b68bbd-e99b-4a66-bf5f-6e238bfd0182', 'tags': ['seq:step:1'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\"', id='run-02b68bbd-e99b-4a66-bf5f-6e238bfd0182')}}\n", + "...\n" + ] + } + ], + "source": [ + "chain = model.with_config({\"run_name\": \"model\"}) | JsonOutputParser().with_config(\n", + " {\"run_name\": \"my_parser\"}\n", + ")\n", + "\n", + "max_events = 0\n", + "async for event in chain.astream_events(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`',\n", + " version=\"v1\",\n", + " include_types=[\"chat_model\"],\n", + "):\n", + " print(event)\n", + " max_events += 1\n", + " if max_events > 10:\n", + " # Truncate output\n", + " print(\"...\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "f1ec8dd4-9b5b-4000-b63f-5845bfc5a065", + "metadata": {}, + "source": [ + "#### By Tags\n", + "\n", + ":::{.callout-caution}\n", + "\n", + "Tags are inherited by child components of a given runnable. \n", + "\n", + "If you're using tags to filter, make sure that this is what you want.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "26bac0d2-76d9-446e-b346-82790236b88d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'event': 'on_chain_start', 'run_id': '55ab7082-7200-4545-8f45-bb0997b0bce8', 'name': 'RunnableSequence', 'tags': ['my_chain'], 'metadata': {}, 'data': {'input': 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`'}}\n", + "{'event': 'on_chat_model_start', 'name': 'ChatAnthropic', 'run_id': 'd2efdbe8-77e4-4b29-ae68-be163239385e', 'tags': ['seq:step:1', 'my_chain'], 'metadata': {}, 'data': {'input': {'messages': [[HumanMessage(content='output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`')]]}}}\n", + "{'event': 'on_chat_model_stream', 'name': 'ChatAnthropic', 'run_id': 'd2efdbe8-77e4-4b29-ae68-be163239385e', 'tags': ['seq:step:1', 'my_chain'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='{', id='run-d2efdbe8-77e4-4b29-ae68-be163239385e')}}\n", + "{'event': 'on_parser_start', 'name': 'JsonOutputParser', 'run_id': 'bc80bc6d-5ae5-4d3a-9bb6-006c0e9c67c5', 'tags': ['seq:step:2', 'my_chain'], 'metadata': {}, 'data': {}}\n", + "{'event': 'on_parser_stream', 'name': 'JsonOutputParser', 'run_id': 'bc80bc6d-5ae5-4d3a-9bb6-006c0e9c67c5', 'tags': ['seq:step:2', 'my_chain'], 'metadata': {}, 'data': {'chunk': {}}}\n", + "{'event': 'on_chain_stream', 'run_id': '55ab7082-7200-4545-8f45-bb0997b0bce8', 'tags': ['my_chain'], 'metadata': {}, 'name': 'RunnableSequence', 'data': {'chunk': {}}}\n", + "{'event': 'on_chat_model_stream', 'name': 'ChatAnthropic', 'run_id': 'd2efdbe8-77e4-4b29-ae68-be163239385e', 'tags': ['seq:step:1', 'my_chain'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\\n ', id='run-d2efdbe8-77e4-4b29-ae68-be163239385e')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'ChatAnthropic', 'run_id': 'd2efdbe8-77e4-4b29-ae68-be163239385e', 'tags': ['seq:step:1', 'my_chain'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\"', id='run-d2efdbe8-77e4-4b29-ae68-be163239385e')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'ChatAnthropic', 'run_id': 'd2efdbe8-77e4-4b29-ae68-be163239385e', 'tags': ['seq:step:1', 'my_chain'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='countries', id='run-d2efdbe8-77e4-4b29-ae68-be163239385e')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'ChatAnthropic', 'run_id': 'd2efdbe8-77e4-4b29-ae68-be163239385e', 'tags': ['seq:step:1', 'my_chain'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content='\":', id='run-d2efdbe8-77e4-4b29-ae68-be163239385e')}}\n", + "{'event': 'on_chat_model_stream', 'name': 'ChatAnthropic', 'run_id': 'd2efdbe8-77e4-4b29-ae68-be163239385e', 'tags': ['seq:step:1', 'my_chain'], 'metadata': {}, 'data': {'chunk': AIMessageChunk(content=' [', id='run-d2efdbe8-77e4-4b29-ae68-be163239385e')}}\n", + "...\n" + ] + } + ], + "source": [ + "chain = (model | JsonOutputParser()).with_config({\"tags\": [\"my_chain\"]})\n", + "\n", + "max_events = 0\n", + "async for event in chain.astream_events(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`',\n", + " version=\"v1\",\n", + " include_tags=[\"my_chain\"],\n", + "):\n", + " print(event)\n", + " max_events += 1\n", + " if max_events > 10:\n", + " # Truncate output\n", + " print(\"...\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "e05e54c4-61a2-4f6c-aa68-d2b09b5e1d4f", + "metadata": {}, + "source": [ + "### Non-streaming components\n", + "\n", + "Remember how some components don't stream well because they don't operate on **input streams**?\n", + "\n", + "While such components can break streaming of the final output when using `astream`, `astream_events` will still yield streaming events from intermediate steps that support streaming!" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "0e6451d3-3b11-4a71-ae19-998f4c10180f", + "metadata": {}, + "outputs": [], + "source": [ + "# Function that does not support streaming.\n", + "# It operates on the finalizes inputs rather than\n", + "# operating on the input stream.\n", + "def _extract_country_names(inputs):\n", + " \"\"\"A function that does not operates on input streams and breaks streaming.\"\"\"\n", + " if not isinstance(inputs, dict):\n", + " return \"\"\n", + "\n", + " if \"countries\" not in inputs:\n", + " return \"\"\n", + "\n", + " countries = inputs[\"countries\"]\n", + "\n", + " if not isinstance(countries, list):\n", + " return \"\"\n", + "\n", + " country_names = [\n", + " country.get(\"name\") for country in countries if isinstance(country, dict)\n", + " ]\n", + " return country_names\n", + "\n", + "\n", + "chain = (\n", + " model | JsonOutputParser() | _extract_country_names\n", + ") # This parser only works with OpenAI right now" + ] + }, + { + "cell_type": "markdown", + "id": "a972e1a6-80cd-4d59-90a0-73563f1503d4", + "metadata": {}, + "source": [ + "As expected, the `astream` API doesn't work correctly because `_extract_country_names` doesn't operate on streams." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "f9a8fe35-faab-4970-b8c0-5c780845d98a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[None, '', 'France', 'France', 'France', 'France', 'France', None, 'France', '', 'France', 'Spain', 'France', 'Spain', 'France', 'Spain', 'France', 'Spain', 'France', 'Spain', None, 'France', 'Spain', '', 'France', 'Spain', 'Japan', 'France', 'Spain', 'Japan', 'France', 'Spain', 'Japan', 'France', 'Spain', 'Japan']\n" + ] + } + ], + "source": [ + "async for chunk in chain.astream(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`',\n", + "):\n", + " print(chunk, flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "b279ea33-54f1-400a-acb1-b8445ccbf1fa", + "metadata": {}, + "source": [ + "Now, let's confirm that with astream_events we're still seeing streaming output from the model and the parser." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "b08215cd-bffa-4e76-aaf3-c52ee34f152c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Chat model chunk: '{'\n", + "Parser chunk: {}\n", + "Chat model chunk: '\\n '\n", + "Chat model chunk: '\"'\n", + "Chat model chunk: 'countries'\n", + "Chat model chunk: '\":'\n", + "Chat model chunk: ' ['\n", + "Parser chunk: {'countries': []}\n", + "Chat model chunk: '\\n '\n", + "Chat model chunk: '{'\n", + "Parser chunk: {'countries': [{}]}\n", + "Chat model chunk: '\\n '\n", + "Chat model chunk: '\"'\n", + "Chat model chunk: 'name'\n", + "Chat model chunk: '\":'\n", + "Chat model chunk: ' \"'\n", + "Parser chunk: {'countries': [{'name': ''}]}\n", + "Chat model chunk: 'France'\n", + "Parser chunk: {'countries': [{'name': 'France'}]}\n", + "Chat model chunk: '\",'\n", + "Chat model chunk: '\\n '\n", + "Chat model chunk: '\"'\n", + "Chat model chunk: 'population'\n", + "Chat model chunk: '\":'\n", + "Chat model chunk: ' '\n", + "Chat model chunk: '67'\n", + "Parser chunk: {'countries': [{'name': 'France', 'population': 67}]}\n", + "...\n" + ] + } + ], + "source": [ + "num_events = 0\n", + "\n", + "async for event in chain.astream_events(\n", + " 'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of \"countries\" which contains a list of countries. Each country should have the key `name` and `population`',\n", + " version=\"v1\",\n", + "):\n", + " kind = event[\"event\"]\n", + " if kind == \"on_chat_model_stream\":\n", + " print(\n", + " f\"Chat model chunk: {repr(event['data']['chunk'].content)}\",\n", + " flush=True,\n", + " )\n", + " if kind == \"on_parser_stream\":\n", + " print(f\"Parser chunk: {event['data']['chunk']}\", flush=True)\n", + " num_events += 1\n", + " if num_events > 30:\n", + " # Truncate the output\n", + " print(\"...\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "6e91bdd3-f4a3-4b3c-b21a-26365c6c1566", + "metadata": {}, + "source": [ + "### Propagating Callbacks\n", + "\n", + ":::{.callout-caution}\n", + "If you're using invoking runnables inside your tools, you need to propagate callbacks to the runnable; otherwise, no stream events will be generated.\n", + ":::\n", + "\n", + ":::{.callout-note}\n", + "When using `RunnableLambdas` or `@chain` decorator, callbacks are propagated automatically behind the scenes.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "1854206d-b3a5-4f91-9e00-bccbaebac61f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'event': 'on_tool_start', 'run_id': 'b5ffad93-6dcf-4c95-9dfa-a35675c6bbc3', 'name': 'bad_tool', 'tags': [], 'metadata': {}, 'data': {'input': 'hello'}}\n", + "{'event': 'on_tool_stream', 'run_id': 'b5ffad93-6dcf-4c95-9dfa-a35675c6bbc3', 'tags': [], 'metadata': {}, 'name': 'bad_tool', 'data': {'chunk': 'olleh'}}\n", + "{'event': 'on_tool_end', 'name': 'bad_tool', 'run_id': 'b5ffad93-6dcf-4c95-9dfa-a35675c6bbc3', 'tags': [], 'metadata': {}, 'data': {'output': 'olleh'}}\n" + ] + } + ], + "source": [ + "from langchain_core.runnables import RunnableLambda\n", + "from langchain_core.tools import tool\n", + "\n", + "\n", + "def reverse_word(word: str):\n", + " return word[::-1]\n", + "\n", + "\n", + "reverse_word = RunnableLambda(reverse_word)\n", + "\n", + "\n", + "@tool\n", + "def bad_tool(word: str):\n", + " \"\"\"Custom tool that doesn't propagate callbacks.\"\"\"\n", + " return reverse_word.invoke(word)\n", + "\n", + "\n", + "async for event in bad_tool.astream_events(\"hello\", version=\"v1\"):\n", + " print(event)" + ] + }, + { + "cell_type": "markdown", + "id": "23e68a99-7886-465b-8575-116022857469", + "metadata": {}, + "source": [ + "Here's a re-implementation that does propagate callbacks correctly. You'll notice that now we're getting events from the `reverse_word` runnable as well." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "a20a6cb3-bb43-465c-8cfc-0a7349d70968", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'event': 'on_tool_start', 'run_id': 'be7f9379-5340-433e-b1fc-84314353cd17', 'name': 'correct_tool', 'tags': [], 'metadata': {}, 'data': {'input': 'hello'}}\n", + "{'event': 'on_chain_start', 'name': 'reverse_word', 'run_id': '50bfe8a9-64c5-4ed8-8dae-03415b5b7c6e', 'tags': [], 'metadata': {}, 'data': {'input': 'hello'}}\n", + "{'event': 'on_chain_end', 'name': 'reverse_word', 'run_id': '50bfe8a9-64c5-4ed8-8dae-03415b5b7c6e', 'tags': [], 'metadata': {}, 'data': {'input': 'hello', 'output': 'olleh'}}\n", + "{'event': 'on_tool_stream', 'run_id': 'be7f9379-5340-433e-b1fc-84314353cd17', 'tags': [], 'metadata': {}, 'name': 'correct_tool', 'data': {'chunk': 'olleh'}}\n", + "{'event': 'on_tool_end', 'name': 'correct_tool', 'run_id': 'be7f9379-5340-433e-b1fc-84314353cd17', 'tags': [], 'metadata': {}, 'data': {'output': 'olleh'}}\n" + ] + } + ], + "source": [ + "@tool\n", + "def correct_tool(word: str, callbacks):\n", + " \"\"\"A tool that correctly propagates callbacks.\"\"\"\n", + " return reverse_word.invoke(word, {\"callbacks\": callbacks})\n", + "\n", + "\n", + "async for event in correct_tool.astream_events(\"hello\", version=\"v1\"):\n", + " print(event)" + ] + }, + { + "cell_type": "markdown", + "id": "640daa94-e4fe-4997-ab6e-45120f18b9ee", + "metadata": {}, + "source": [ + "If you're invoking runnables from within Runnable Lambdas or `@chains`, then callbacks will be passed automatically on your behalf." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "0ac0a3c1-f3a4-4157-b053-4fec8d2e698c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'event': 'on_chain_start', 'run_id': 'a5d11046-93fa-4cd9-9854-d3afa3d686ef', 'name': 'reverse_and_double', 'tags': [], 'metadata': {}, 'data': {'input': '1234'}}\n", + "{'event': 'on_chain_stream', 'run_id': 'a5d11046-93fa-4cd9-9854-d3afa3d686ef', 'tags': [], 'metadata': {}, 'name': 'reverse_and_double', 'data': {'chunk': '43214321'}}\n", + "{'event': 'on_chain_end', 'name': 'reverse_and_double', 'run_id': 'a5d11046-93fa-4cd9-9854-d3afa3d686ef', 'tags': [], 'metadata': {}, 'data': {'output': '43214321'}}\n" + ] + } + ], + "source": [ + "from langchain_core.runnables import RunnableLambda\n", + "\n", + "\n", + "async def reverse_and_double(word: str):\n", + " return await reverse_word.ainvoke(word) * 2\n", + "\n", + "\n", + "reverse_and_double = RunnableLambda(reverse_and_double)\n", + "\n", + "await reverse_and_double.ainvoke(\"1234\")\n", + "\n", + "async for event in reverse_and_double.astream_events(\"1234\", version=\"v1\"):\n", + " print(event)" + ] + }, + { + "cell_type": "markdown", + "id": "35a34268-9b3d-4857-b4ed-65d95f4a1293", + "metadata": {}, + "source": [ + "And with the `@chain` decorator:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "c896bb94-9d10-41ff-8fe2-d6b05b1ed74b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'event': 'on_chain_start', 'run_id': 'b3eff5c2-8339-4e15-98b3-85148d9ae350', 'name': 'reverse_and_double', 'tags': [], 'metadata': {}, 'data': {'input': '1234'}}\n", + "{'event': 'on_chain_stream', 'run_id': 'b3eff5c2-8339-4e15-98b3-85148d9ae350', 'tags': [], 'metadata': {}, 'name': 'reverse_and_double', 'data': {'chunk': '43214321'}}\n", + "{'event': 'on_chain_end', 'name': 'reverse_and_double', 'run_id': 'b3eff5c2-8339-4e15-98b3-85148d9ae350', 'tags': [], 'metadata': {}, 'data': {'output': '43214321'}}\n" + ] + } + ], + "source": [ + "from langchain_core.runnables import chain\n", + "\n", + "\n", + "@chain\n", + "async def reverse_and_double(word: str):\n", + " return await reverse_word.ainvoke(word) * 2\n", + "\n", + "\n", + "await reverse_and_double.ainvoke(\"1234\")\n", + "\n", + "async for event in reverse_and_double.astream_events(\"1234\", version=\"v1\"):\n", + " print(event)" + ] + }, + { + "cell_type": "markdown", + "id": "2a3efcd9", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Now you've learned some ways to stream both final outputs and internal steps with LangChain.\n", + "\n", + "To learn more, check out the other how-to guides in this section, or the [conceptual guide on Langchain Expression Language](/docs/concepts/#langchain-expression-language/)." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/streaming_llm.ipynb b/docs/versioned_docs/version-0.2.x/how_to/streaming_llm.ipynb new file mode 100644 index 0000000000000..d387752593cb1 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/streaming_llm.ipynb @@ -0,0 +1,170 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fc37c39a-7406-4c13-a754-b8e95fd970a0", + "metadata": {}, + "source": [ + "# How to stream responses from an LLM\n", + "\n", + "All `LLM`s 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 **default** implementations of standard runnable methods (i.e. `ainvoke`, `batch`, `abatch`, `stream`, `astream`, `astream_events`).\n", + "\n", + "The **default** streaming implementations provide an`Iterator` (or `AsyncIterator` for asynchronous streaming) that yields a single value: the final output from the underlying chat model provider.\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/llms/).\n", + "\n", + "\n", + "\n", + ":::{.callout-note}\n", + "\n", + "The **default** implementation does **not** provide support for token-by-token streaming, but it ensures that the model can be swapped in for any other model as it supports the same standard interface.\n", + "\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "2f13124a-7f9d-404f-b7ac-70d8ea49ef8e", + "metadata": {}, + "source": [ + "## Sync stream\n", + "\n", + "Below we use a `|` to help visualize the delimiter between tokens." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "9baa0527-b97d-41d3-babd-472ec5e59e3e", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "|Spark|ling| water|,| oh| so clear|\n", + "|Bubbles dancing|,| without| fear|\n", + "|Refreshing| taste|,| a| pure| delight|\n", + "|Spark|ling| water|,| my| thirst|'s| delight||" + ] + } + ], + "source": [ + "from langchain_openai import OpenAI\n", + "\n", + "llm = OpenAI(model=\"gpt-3.5-turbo-instruct\", temperature=0, max_tokens=512)\n", + "for chunk in llm.stream(\"Write me a 1 verse song about sparkling water.\"):\n", + " print(chunk, end=\"|\", flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "596e477b-a41d-4ff5-9b9a-a7bfb53c3680", + "metadata": {}, + "source": [ + "## Async streaming\n", + "\n", + "Let's see how to stream in an async setting using `astream`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d81140f2-384b-4470-bf93-957013c6620b", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "|Spark|ling| water|,| oh| so clear|\n", + "|Bubbles dancing|,| without| fear|\n", + "|Refreshing| taste|,| a| pure| delight|\n", + "|Spark|ling| water|,| my| thirst|'s| delight||" + ] + } + ], + "source": [ + "from langchain_openai import OpenAI\n", + "\n", + "llm = OpenAI(model=\"gpt-3.5-turbo-instruct\", temperature=0, max_tokens=512)\n", + "async for chunk in llm.astream(\"Write me a 1 verse song about sparkling water.\"):\n", + " print(chunk, end=\"|\", flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "9ab11306-b0db-4459-a9de-ecefb821c9b1", + "metadata": { + "tags": [] + }, + "source": [ + "## Async event streaming\n", + "\n", + "\n", + "LLMs 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", + ":::{.callout-tip}\n", + "\n", + "`astream_events` is most useful when implementing streaming in a larger LLM application that contains multiple steps (e.g., an application that involves an `agent`).\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "399d74c7-4438-4093-ae05-47fed0255626", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from langchain_openai import OpenAI\n", + "\n", + "llm = OpenAI(model=\"gpt-3.5-turbo-instruct\", temperature=0, max_tokens=512)\n", + "\n", + "idx = 0\n", + "\n", + "async for event in llm.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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/structured_output.ipynb b/docs/versioned_docs/version-0.2.x/how_to/structured_output.ipynb new file mode 100644 index 0000000000000..4a2ab17b660d5 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/structured_output.ipynb @@ -0,0 +1,586 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "27598444", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 3\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "6e3f0f72", + "metadata": {}, + "source": [ + "# How to return structured data from a model\n", + "\n", + "It is often useful to have a model return output that matches some specific schema. One common use-case is extracting data from arbitrary text to insert into a traditional database or use with some other downstrem system. This guide will show you a few different strategies you can use to do this.\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```\n", + "\n", + "## The `.with_structured_output()` method\n", + "\n", + "There are several strategies that models can use under the hood. For some of the most popular model providers, including [OpenAI](/docs/integrations/platforms/openai/), [Anthropic](/docs/integrations/platforms/anthropic/), and [Mistral](/docs/integrations/providers/mistralai/), LangChain implements a common interface that abstracts away these strategies called `.with_structured_output`.\n", + "\n", + "By invoking this method (and passing in [JSON schema](https://json-schema.org/) or a [Pydantic](https://docs.pydantic.dev/latest/) model) the model will add whatever model parameters + output parsers are necessary to get back structured output matching the requested schema. If the model supports more than one way to do this (e.g., function calling vs JSON mode) - you can configure which method to use by passing into that method.\n", + "\n", + "You can find the [current list of models that support this method here](/docs/integrations/chat/).\n", + "\n", + "Let's look at some examples of this in action! We'll use Pydantic to create a simple response schema.\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6d55008f", + "metadata": {}, + "outputs": [], + "source": [ + "# | output: false\n", + "# | echo: false\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(\n", + " model=\"gpt-4-0125-preview\",\n", + " temperature=0,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "070bf702", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Joke(setup='Why was the cat sitting on the computer?', punchline='Because it wanted to keep an eye on the mouse!', rating=None)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from typing import Optional\n", + "\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class Joke(BaseModel):\n", + " setup: str = Field(description=\"The setup of the joke\")\n", + " punchline: str = Field(description=\"The punchline to the joke\")\n", + " rating: Optional[int] = Field(description=\"How funny the joke is, from 1 to 10\")\n", + "\n", + "\n", + "structured_llm = model.with_structured_output(Joke)\n", + "\n", + "structured_llm.invoke(\"Tell me a joke about cats\")" + ] + }, + { + "cell_type": "markdown", + "id": "deddb6d3", + "metadata": {}, + "source": [ + "The result is a Pydantic model. Note that name of the model and the names and provided descriptions of parameters are very important, as they help guide the model's output.\n", + "\n", + "We can also pass in an OpenAI-style JSON schema dict if you prefer not to use Pydantic. This dict should contain three properties:\n", + "\n", + "- `name`: The name of the schema to output.\n", + "- `description`: A high level description of the schema to output.\n", + "- `parameters`: The nested details of the schema you want to extract, formatted as a [JSON schema](https://json-schema.org/) dict.\n", + "\n", + "In this case, the response is also a dict:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6700994a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'setup': 'Why was the cat sitting on the computer?',\n", + " 'punchline': 'To keep an eye on the mouse!'}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "structured_llm = model.with_structured_output(\n", + " {\n", + " \"name\": \"joke\",\n", + " \"description\": \"Joke to tell user.\",\n", + " \"parameters\": {\n", + " \"title\": \"Joke\",\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"setup\": {\"type\": \"string\", \"description\": \"The setup for the joke\"},\n", + " \"punchline\": {\"type\": \"string\", \"description\": \"The joke's punchline\"},\n", + " },\n", + " \"required\": [\"setup\", \"punchline\"],\n", + " },\n", + " }\n", + ")\n", + "\n", + "structured_llm.invoke(\"Tell me a joke about cats\")" + ] + }, + { + "cell_type": "markdown", + "id": "3da57988", + "metadata": {}, + "source": [ + "### Choosing between multiple schemas\n", + "\n", + "If you have multiple schemas that are valid outputs for the model, you can use Pydantic's `Union` type:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9194bcf2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Response(output=Joke(setup='Why was the cat sitting on the computer?', punchline='Because it wanted to keep an eye on the mouse!'))" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from typing import Union\n", + "\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class Joke(BaseModel):\n", + " setup: str = Field(description=\"The setup of the joke\")\n", + " punchline: str = Field(description=\"The punchline to the joke\")\n", + "\n", + "\n", + "class ConversationalResponse(BaseModel):\n", + " response: str = Field(description=\"A conversational response to the user's query\")\n", + "\n", + "\n", + "class Response(BaseModel):\n", + " output: Union[Joke, ConversationalResponse]\n", + "\n", + "\n", + "structured_llm = model.with_structured_output(Response)\n", + "\n", + "structured_llm.invoke(\"Tell me a joke about cats\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "84d86132", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Response(output=ConversationalResponse(response=\"I'm just a collection of code, so I don't have feelings, but thanks for asking! How can I assist you today?\"))" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "structured_llm.invoke(\"How are you today?\")" + ] + }, + { + "cell_type": "markdown", + "id": "e28c14d3", + "metadata": {}, + "source": [ + "If you are using JSON Schema, you can take advantage of other more complex schema descriptions to create a similar effect.\n", + "\n", + "You can also use tool calling directly to allow the model to choose between options, if your chosen model supports it. This involves a bit more parsing and setup. See [this how-to guide](/docs/how_to/tool_calling/) for more details." + ] + }, + { + "cell_type": "markdown", + "id": "39d7a555", + "metadata": {}, + "source": [ + "### Specifying the output method (Advanced)\n", + "\n", + "For models that support more than one means of outputting data, you can specify the preferred one like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "df0370e3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Joke(setup='Why was the cat sitting on the computer?', punchline='Because it wanted to keep an eye on the mouse!')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "structured_llm = model.with_structured_output(Joke, method=\"json_mode\")\n", + "\n", + "structured_llm.invoke(\n", + " \"Tell me a joke about cats, respond in JSON with `setup` and `punchline` keys\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5e92a98a", + "metadata": {}, + "source": [ + "In the above example, we use OpenAI's alternate JSON mode capability along with a more specific prompt.\n", + "\n", + "For specifics about the model you choose, peruse its entry in the [API reference pages](https://api.python.langchain.com/en/latest/langchain_api_reference.html).\n", + "\n", + "## Prompting techniques\n", + "\n", + "You can also prompt models to outputting information in a given format. This approach relies on designing good prompts and then parsing the output of the models. This is the only option for models that don't support `.with_structured_output()` or other built-in approaches.\n", + "\n", + "### Using `PydanticOutputParser`\n", + "\n", + "The following example uses the built-in [`PydanticOutputParser`](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.pydantic.PydanticOutputParser.html) to parse the output of a chat model prompted to match a the given Pydantic schema. Note that we are adding `format_instructions` directly to the prompt from a method on the parser:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6e514455", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "\n", + "from langchain.output_parsers import PydanticOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\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": "082fa166", + "metadata": {}, + "source": [ + "Let’s take a look at what information is sent to the model:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3d73d33d", + "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": [ + "query = \"Anna is 23 years old and she is 6 feet tall\"\n", + "\n", + "print(prompt.format_prompt(query=query).to_string())" + ] + }, + { + "cell_type": "markdown", + "id": "081956b9", + "metadata": {}, + "source": [ + "And now let's invoke it:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8d6b3d17", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "People(people=[Person(name='Anna', height_in_meters=1.8288)])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = prompt | model | parser\n", + "\n", + "chain.invoke({\"query\": query})" + ] + }, + { + "cell_type": "markdown", + "id": "6732dd87", + "metadata": {}, + "source": [ + "For a deeper dive into using output parsers with prompting techniques for structured output, see [this guide](/docs/how_to/output_parser_structured).\n", + "\n", + "### Custom Parsing\n", + "\n", + "You can also create a custom prompt and parser with [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language), using a plain function to parse the output from the model:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e8d37e15", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import re\n", + "from typing import List\n", + "\n", + "from langchain_core.messages import AIMessage\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\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": "markdown", + "id": "9f1bc8f7", + "metadata": {}, + "source": [ + "Here is the prompt sent to the model:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c8a30d0e", + "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", + "\n", + "print(prompt.format_prompt(query=query).to_string())" + ] + }, + { + "cell_type": "markdown", + "id": "ec018893", + "metadata": {}, + "source": [ + "And here's what it looks like when we invoke it:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e1e7baf6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'people': [{'name': 'Anna', 'height_in_meters': 1.8288}]}]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = prompt | model | extract_json\n", + "\n", + "chain.invoke({\"query\": query})" + ] + }, + { + "cell_type": "markdown", + "id": "7a39221a", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Now you've learned a few methods to make a model output structured data.\n", + "\n", + "To learn more, check out the other how-to guides in this section, or the conceptual guide on tool calling." + ] + }, + { + "cell_type": "markdown", + "id": "6e3759e2", + "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.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/time_weighted_vectorstore.ipynb b/docs/versioned_docs/version-0.2.x/how_to/time_weighted_vectorstore.ipynb new file mode 100644 index 0000000000000..a1c61fa1ab59e --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/time_weighted_vectorstore.ipynb @@ -0,0 +1,261 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e239cc79", + "metadata": {}, + "source": [ + "# How to use a time-weighted vector store retriever\n", + "\n", + "This retriever uses a combination of semantic similarity and a time decay.\n", + "\n", + "The algorithm for scoring them is:\n", + "\n", + "```\n", + "semantic_similarity + (1.0 - decay_rate) ^ hours_passed\n", + "```\n", + "\n", + "Notably, `hours_passed` refers to the hours passed since the object in the retriever **was last accessed**, not since it was created. This means that frequently accessed objects remain \"fresh\".\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "97e74400", + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import datetime, timedelta\n", + "\n", + "import faiss\n", + "from langchain.retrievers import TimeWeightedVectorStoreRetriever\n", + "from langchain_community.docstore import InMemoryDocstore\n", + "from langchain_community.vectorstores import FAISS\n", + "from langchain_core.documents import Document\n", + "from langchain_openai import OpenAIEmbeddings" + ] + }, + { + "cell_type": "markdown", + "id": "89635236", + "metadata": {}, + "source": [ + "## Low decay rate\n", + "\n", + "A low `decay rate` (in this, to be extreme, we will set it close to 0) means memories will be \"remembered\" for longer. A `decay rate` of 0 means memories never be forgotten, making this retriever equivalent to the vector lookup.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d3a1778d", + "metadata": {}, + "outputs": [], + "source": [ + "# Define your embedding model\n", + "embeddings_model = OpenAIEmbeddings()\n", + "# Initialize the vectorstore as empty\n", + "embedding_size = 1536\n", + "index = faiss.IndexFlatL2(embedding_size)\n", + "vectorstore = FAISS(embeddings_model, index, InMemoryDocstore({}), {})\n", + "retriever = TimeWeightedVectorStoreRetriever(\n", + " vectorstore=vectorstore, decay_rate=0.0000000000000000000000001, k=1\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "408fc114", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['c3dcf671-3c0a-4273-9334-c4a913076bfa']" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yesterday = datetime.now() - timedelta(days=1)\n", + "retriever.add_documents(\n", + " [Document(page_content=\"hello world\", metadata={\"last_accessed_at\": yesterday})]\n", + ")\n", + "retriever.add_documents([Document(page_content=\"hello foo\")])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8a5ed9ca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='hello world', metadata={'last_accessed_at': datetime.datetime(2023, 12, 27, 15, 30, 18, 457125), 'created_at': datetime.datetime(2023, 12, 27, 15, 30, 8, 442662), 'buffer_idx': 0})]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# \"Hello World\" is returned first because it is most salient, and the decay rate is close to 0., meaning it's still recent enough\n", + "retriever.get_relevant_documents(\"hello world\")" + ] + }, + { + "cell_type": "markdown", + "id": "d8bc4f96", + "metadata": {}, + "source": [ + "## High decay rate\n", + "\n", + "With a high `decay rate` (e.g., several 9's), the `recency score` quickly goes to 0! If you set this all the way to 1, `recency` is 0 for all objects, once again making this equivalent to a vector lookup.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e588d729", + "metadata": {}, + "outputs": [], + "source": [ + "# Define your embedding model\n", + "embeddings_model = OpenAIEmbeddings()\n", + "# Initialize the vectorstore as empty\n", + "embedding_size = 1536\n", + "index = faiss.IndexFlatL2(embedding_size)\n", + "vectorstore = FAISS(embeddings_model, index, InMemoryDocstore({}), {})\n", + "retriever = TimeWeightedVectorStoreRetriever(\n", + " vectorstore=vectorstore, decay_rate=0.999, k=1\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "43b4afb3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['eb1c4c86-01a8-40e3-8393-9a927295a950']" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yesterday = datetime.now() - timedelta(days=1)\n", + "retriever.add_documents(\n", + " [Document(page_content=\"hello world\", metadata={\"last_accessed_at\": yesterday})]\n", + ")\n", + "retriever.add_documents([Document(page_content=\"hello foo\")])" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0677113c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Document(page_content='hello foo', metadata={'last_accessed_at': datetime.datetime(2023, 12, 27, 15, 30, 50, 57185), 'created_at': datetime.datetime(2023, 12, 27, 15, 30, 44, 720490), 'buffer_idx': 1})]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# \"Hello Foo\" is returned first because \"hello world\" is mostly forgotten\n", + "retriever.get_relevant_documents(\"hello world\")" + ] + }, + { + "cell_type": "markdown", + "id": "c8b0075a", + "metadata": {}, + "source": [ + "## Virtual time\n", + "\n", + "Using some utils in LangChain, you can mock out the time component.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0b4188e7", + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "\n", + "from langchain.utils import mock_now" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "95d55764", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Document(page_content='hello world', metadata={'last_accessed_at': MockDateTime(2024, 2, 3, 10, 11), 'created_at': datetime.datetime(2023, 12, 27, 15, 30, 44, 532941), 'buffer_idx': 0})]\n" + ] + } + ], + "source": [ + "# Notice the last access time is that date time\n", + "with mock_now(datetime.datetime(2024, 2, 3, 10, 11)):\n", + " print(retriever.get_relevant_documents(\"hello world\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a6da4c6", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/tool_calling.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tool_calling.ipynb new file mode 100644 index 0000000000000..3edef318889b1 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tool_calling.ipynb @@ -0,0 +1,714 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to use a chat model to call tools\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 chat model to respond to a given prompt by \"calling a tool\".\n", + "While the name implies that the model is performing \n", + "some action, this is actually not the case! The model generates 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/how_to/structured_output/) \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", + "However, tool calling goes beyond [structured output](/docs/how_to/structured_output/)\n", + "since you can pass responses to caled tools back to the model to create longer interactions.\n", + "For instance, given a search engine tool, an LLM might handle a \n", + "query by first issuing a call to the search engine with arguments. 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", + "\n", + "Tool calling is not universal, but many popular 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.\n", + "\n", + "LangChain implements standard interfaces for defining tools, passing them to LLMs, \n", + "and representing tool calls. This guide will show you how to use them.\n", + "\n", + "```{=mdx}\n", + "import PrerequisiteLinks from \"@theme/PrerequisiteLinks\";\n", + "\n", + "\n", + "```\n", + "\n", + "## Passing tools to chat models\n", + "\n", + "Chat models that support 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": 1, + "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", + "metadata": {}, + "source": [ + "Or below, we define the schema using [Pydantic](https://docs.pydantic.dev):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "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", + "metadata": {}, + "source": [ + "We can bind them to chat models as follows:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```\n", + "\n", + "We'll use the `.bind_tools()` method to handle converting\n", + "`Multiply` to the proper format for the model, then and bind it (i.e.,\n", + "passing it in each time the model is invoked)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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", + "from langchain_openai import ChatOpenAI\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass()\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "llm_with_tools = llm.bind_tools(tools)" + ] + }, + { + "cell_type": "markdown", + "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.\n", + "\n", + "Note that chat models can call multiple tools at once.\n", + "\n", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'Multiply',\n", + " 'args': {'a': 3, 'b': 12},\n", + " 'id': 'call_KquHA7mSbgtAkpkmRPaFnJKa'},\n", + " {'name': 'Add',\n", + " 'args': {'a': 11, 'b': 49},\n", + " 'id': 'call_Fl0hQi4IBTzlpaJYlM5kPQhE'}]" + ] + }, + "execution_count": 5, + "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", + "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": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Multiply(a=3, b=12), Add(a=11, b=49)]" + ] + }, + "execution_count": 6, + "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", + "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:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[]\n", + "[{'name': 'Multiply', 'args': '', 'id': 'call_3aQwTP9CYlFxwOvQZPHDu6wL', '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_SQUoSsJz2p9Kx2x73GOgN1ja', '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", + "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": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[]\n", + "[{'name': 'Multiply', 'args': '', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]\n", + "[{'name': 'Multiply', 'args': '{\"a\"', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, ', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 1', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{\"a\"', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{\"a\": 11,', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{\"a\": 11, \"b\": ', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{\"a\": 11, \"b\": 49}', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]\n", + "[{'name': 'Multiply', 'args': '{\"a\": 3, \"b\": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{\"a\": 11, \"b\": 49}', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', '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": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(type(gathered.tool_call_chunks[0][\"args\"]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And below we accumulate tool calls to demonstrate partial parsing:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[]\n", + "[]\n", + "[{'name': 'Multiply', 'args': {}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3, 'b': 1}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {'a': 11}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {'a': 11}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]\n", + "[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]\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": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(type(gathered.tool_calls[0][\"args\"]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Passing tool outputs to the 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": 12, + "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_svc2GLSxNFALbaCAbSjMI9J8', 'function': {'arguments': '{\"a\": 3, \"b\": 12}', 'name': 'Multiply'}, 'type': 'function'}, {'id': 'call_r8jxte3zW6h3MEGV3zH2qzFh', '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-0125', 'system_fingerprint': 'fp_d9767fc5b9', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-a79ad1dd-95f1-4a46-b688-4c83f327a7b3-0', tool_calls=[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_svc2GLSxNFALbaCAbSjMI9J8'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_r8jxte3zW6h3MEGV3zH2qzFh'}]),\n", + " ToolMessage(content='36', tool_call_id='call_svc2GLSxNFALbaCAbSjMI9J8'),\n", + " ToolMessage(content='60', tool_call_id='call_r8jxte3zW6h3MEGV3zH2qzFh')]" + ] + }, + "execution_count": 12, + "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": 13, + "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-0125', 'system_fingerprint': 'fp_d9767fc5b9', 'finish_reason': 'stop', 'logprobs': None}, id='run-20b52149-e00d-48ea-97cf-f8de7a255f8c-0')" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "llm_with_tools.invoke(messages)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that we pass back the same `id` in the `ToolMessage` as the what we receive from the model in order to help the model match tool responses with tool calls.\n", + "\n", + "## 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": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'Multiply',\n", + " 'args': {'a': 119, 'b': 8},\n", + " 'id': 'call_T88XN6ECucTgbXXkyDeC2CQj'},\n", + " {'name': 'Add',\n", + " 'args': {'a': 952, 'b': -20},\n", + " 'id': 'call_licdlmGsRqzup8rhqJSb1yZ4'}]" + ] + }, + "execution_count": 14, + "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", + "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": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'Multiply',\n", + " 'args': {'a': 119, 'b': 8},\n", + " 'id': 'call_9MvuwQqg7dlJupJcoTWiEsDo'}]" + ] + }, + "execution_count": 15, + "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", + "metadata": {}, + "source": [ + "And 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", + "metadata": {}, + "source": [ + "## Binding model-specific formats (advanced)\n", + "\n", + "Providers adopt different conventions for formatting tool schemas. \n", + "For instance, OpenAI uses a format like this:\n", + "\n", + "- `type`: The type of the tool. At the time of writing, this is always `\"function\"`.\n", + "- `function`: An object containing tool parameters.\n", + "- `function.name`: The name of the schema to output.\n", + "- `function.description`: A high level description of the schema to output.\n", + "- `function.parameters`: The nested details of the schema you want to extract, formatted as a [JSON schema](https://json-schema.org/) dict.\n", + "\n", + "We can bind this model-specific format directly to the model as well if preferred. Here's an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_mn4ELw1NbuE0DFYhIeK0GrPe', 'function': {'arguments': '{\"a\":119,\"b\":8}', 'name': 'multiply'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 62, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-353e8a9a-7125-4f94-8c68-4f3da4c21120-0', tool_calls=[{'name': 'multiply', 'args': {'a': 119, 'b': 8}, 'id': 'call_mn4ELw1NbuE0DFYhIeK0GrPe'}])" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI()\n", + "\n", + "model_with_tools = model.bind(\n", + " tools=[\n", + " {\n", + " \"type\": \"function\",\n", + " \"function\": {\n", + " \"name\": \"multiply\",\n", + " \"description\": \"Multiply two integers together.\",\n", + " \"parameters\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"a\": {\"type\": \"number\", \"description\": \"First integer\"},\n", + " \"b\": {\"type\": \"number\", \"description\": \"Second integer\"},\n", + " },\n", + " \"required\": [\"a\", \"b\"],\n", + " },\n", + " },\n", + " }\n", + " ]\n", + ")\n", + "\n", + "model_with_tools.invoke(\"Whats 119 times 8?\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is functionally equivalent to the `bind_tools()` calls above." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Now you've learned how to bind tool schemas to a chat model and to call those tools. Next, check out some more specific uses of tool calling:\n", + "\n", + "- Building [tool-using chains and agents](/docs/use_cases/tool_use/)\n", + "- Getting [structured outputs](/docs/how_to/structured_output/) from models" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/toolkits.mdx b/docs/versioned_docs/version-0.2.x/how_to/toolkits.mdx new file mode 100644 index 0000000000000..36249f1c102b4 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/toolkits.mdx @@ -0,0 +1,22 @@ +--- +sidebar_position: 3 +--- +# How to use toolkits + + +Toolkits are collections of tools that are designed to be used together for specific tasks. They have convenient loading methods. +For a complete list of available ready-made toolkits, visit [Integrations](/docs/integrations/toolkits/). + +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() + +# Create agent +agent = create_agent_method(llm, tools, prompt) +``` diff --git a/docs/versioned_docs/version-0.2.x/how_to/tools.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tools.ipynb new file mode 100644 index 0000000000000..8aa06a940caee --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tools.ipynb @@ -0,0 +1,450 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "7f219241", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 4\n", + "sidebar_class_name: hidden\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "15780a65", + "metadata": {}, + "source": [ + "# How to use LangChain tools\n", + "\n", + "Tools are interfaces that an agent, chain, or LLM can use to interact with the world.\n", + "They combine a few things:\n", + "\n", + "1. The name of the tool\n", + "2. A description of what the tool is\n", + "3. JSON schema of what the inputs to the tool are\n", + "4. The function to call \n", + "5. Whether the result of a tool should be returned directly to the user\n", + "\n", + "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.\n", + "\n", + "The simpler the input to a tool is, the easier it is for an LLM to be able to use it.\n", + "Many agents will only work with tools that have a single string input.\n", + "For a list of agent types and which ones work with more complicated inputs, please see [this documentation](../agents/agent_types)\n", + "\n", + "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.\n", + "\n", + "## Default Tools\n", + "\n", + "Let's take a look at how to work with tools. To do this, we'll work with a built in tool." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "19297004", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.tools import WikipediaQueryRun\n", + "from langchain_community.utilities import WikipediaAPIWrapper" + ] + }, + { + "cell_type": "markdown", + "id": "1098e51a", + "metadata": {}, + "source": [ + "Now we initialize the tool. This is where we can configure it as we please" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "27a48655", + "metadata": {}, + "outputs": [], + "source": [ + "api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=100)\n", + "tool = WikipediaQueryRun(api_wrapper=api_wrapper)" + ] + }, + { + "cell_type": "markdown", + "id": "7db48439", + "metadata": {}, + "source": [ + "This is the default name" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "50f1ece1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Wikipedia'" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.name" + ] + }, + { + "cell_type": "markdown", + "id": "075499b1", + "metadata": {}, + "source": [ + "This is the default description" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "e9be09e2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.'" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.description" + ] + }, + { + "cell_type": "markdown", + "id": "89c86b00", + "metadata": {}, + "source": [ + "This is the default JSON schema of the inputs" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "963a2e8c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'query': {'title': 'Query', 'type': 'string'}}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.args" + ] + }, + { + "cell_type": "markdown", + "id": "5c467a35", + "metadata": {}, + "source": [ + "We can see if the tool should return directly to the user" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "039334b3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.return_direct" + ] + }, + { + "cell_type": "markdown", + "id": "fc421b02", + "metadata": {}, + "source": [ + "We can call this tool with a dictionary input" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "6669a13c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Page: LangChain\\nSummary: LangChain is a framework designed to simplify the creation of applications '" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.run({\"query\": \"langchain\"})" + ] + }, + { + "cell_type": "markdown", + "id": "587d6a58", + "metadata": {}, + "source": [ + "We can also call this tool with a single string input. \n", + "We can do this because this tool expects only a single input.\n", + "If it required multiple inputs, we would not be able to do that." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "8cb23935", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Page: LangChain\\nSummary: LangChain is a framework designed to simplify the creation of applications '" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.run(\"langchain\")" + ] + }, + { + "cell_type": "markdown", + "id": "19eee1d5", + "metadata": {}, + "source": [ + "## Customizing Default Tools\n", + "We can also modify the built in name, description, and JSON schema of the arguments.\n", + "\n", + "When defining the JSON schema of the arguments, it is important that the inputs remain the same as the function, so you shouldn't change that. But you can define custom descriptions for each input easily." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "599c4da7", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class WikiInputs(BaseModel):\n", + " \"\"\"Inputs to the wikipedia tool.\"\"\"\n", + "\n", + " query: str = Field(\n", + " description=\"query to look up in Wikipedia, should be 3 or less words\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "6bde63e1", + "metadata": {}, + "outputs": [], + "source": [ + "tool = WikipediaQueryRun(\n", + " name=\"wiki-tool\",\n", + " description=\"look up things in wikipedia\",\n", + " args_schema=WikiInputs,\n", + " api_wrapper=api_wrapper,\n", + " return_direct=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "eeaa1d9a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'wiki-tool'" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.name" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "7599d88c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'look up things in wikipedia'" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.description" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "80042cb1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'query': {'title': 'Query',\n", + " 'description': 'query to look up in Wikipedia, should be 3 or less words',\n", + " 'type': 'string'}}" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.args" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "8455fb9e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.return_direct" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "86f731a8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Page: LangChain\\nSummary: LangChain is a framework designed to simplify the creation of applications '" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.run(\"langchain\")" + ] + }, + { + "cell_type": "markdown", + "id": "c5b8b6bc", + "metadata": {}, + "source": [ + "## More Topics\n", + "\n", + "This was a quick introduction to tools in LangChain, but there is a lot more to learn\n", + "\n", + "**[Built-In Tools](/docs/integrations/tools/)**: For a list of all built-in tools, see [this page](/docs/integrations/tools/)\n", + " \n", + "**[Custom Tools](./custom_tools)**: Although built-in tools are useful, it's highly likely that you'll have to define your own tools. See [this guide](./custom_tools) for instructions on how to do so.\n", + " \n", + "**[Toolkits](./toolkits)**: Toolkits are collections of tools that work well together. For a more in depth description as well as a list of all built-in toolkits, see [this page](./toolkits)\n", + "\n", + "**[Tools as OpenAI Functions](./tools_as_openai_functions)**: Tools are very similar to OpenAI Functions, and can easily be converted to that format. See [this notebook](./tools_as_openai_functions) for instructions on how to do that.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78e2d0b3", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/tools_as_openai_functions.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tools_as_openai_functions.ipynb new file mode 100644 index 0000000000000..d321afd0dddd9 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tools_as_openai_functions.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4111c9d4", + "metadata": {}, + "source": [ + "# How to convert tools to OpenAI Functions\n", + "\n", + "This notebook goes over how to use LangChain tools as OpenAI functions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb220019-4012-4da4-bfee-01fb8189aa49", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -qU langchain-community langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d65d8a60", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.tools import MoveFileTool\n", + "from langchain_core.messages import HumanMessage\n", + "from langchain_core.utils.function_calling import convert_to_openai_function\n", + "from langchain_openai import ChatOpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "abd8dc72", + "metadata": {}, + "outputs": [], + "source": [ + "model = ChatOpenAI(model=\"gpt-3.5-turbo\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "3b3dc766", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [MoveFileTool()]\n", + "functions = [convert_to_openai_function(t) for t in tools]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d38c4a22-2e9e-4d15-a9e1-bf8103c6303b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'move_file',\n", + " 'description': 'Move or rename a file from one location to another',\n", + " 'parameters': {'type': 'object',\n", + " 'properties': {'source_path': {'description': 'Path of the file to move',\n", + " 'type': 'string'},\n", + " 'destination_path': {'description': 'New path for the moved file',\n", + " 'type': 'string'}},\n", + " 'required': ['source_path', 'destination_path']}}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "functions[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "230a7939", + "metadata": {}, + "outputs": [], + "source": [ + "message = model.invoke(\n", + " [HumanMessage(content=\"move file foo to bar\")], functions=functions\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "c118c940", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"source_path\": \"foo\",\\n \"destination_path\": \"bar\"\\n}', 'name': 'move_file'}})" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "message" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d618e3d8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'move_file',\n", + " 'arguments': '{\\n \"source_path\": \"foo\",\\n \"destination_path\": \"bar\"\\n}'}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "message.additional_kwargs[\"function_call\"]" + ] + }, + { + "cell_type": "markdown", + "id": "77dd0d9f-2f24-4535-a658-a061f91e009a", + "metadata": {}, + "source": [ + "With OpenAI chat models we can also automatically bind and convert function-like objects with `bind_functions`" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "24bb1518-8100-4ac3-acea-04acfac963d1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"source_path\": \"foo\",\\n \"destination_path\": \"bar\"\\n}', 'name': 'move_file'}})" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_with_functions = model.bind_functions(tools)\n", + "model_with_functions.invoke([HumanMessage(content=\"move file foo to bar\")])" + ] + }, + { + "cell_type": "markdown", + "id": "000ec6ff-ca67-4206-ba56-cc2a91b85ce6", + "metadata": {}, + "source": [ + "Or we can use the update OpenAI API that uses `tools` and `tool_choice` instead of `functions` and `function_call` by using `ChatOpenAI.bind_tools`:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "1a333e4e-df55-4e15-9d2e-4fd142d969f3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_btkY3xV71cEVAOHnNa5qwo44', 'function': {'arguments': '{\\n \"source_path\": \"foo\",\\n \"destination_path\": \"bar\"\\n}', 'name': 'move_file'}, 'type': 'function'}]})" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_with_tools = model.bind_tools(tools)\n", + "model_with_tools.invoke([HumanMessage(content=\"move file foo to bar\")])" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/tools_chain.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tools_chain.ipynb new file mode 100644 index 0000000000000..f6db5d6858dc8 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tools_chain.ipynb @@ -0,0 +1,498 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "500e8846-91c2-4716-9bd6-b9672c6daf78", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 0\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "14b94240", + "metadata": {}, + "source": [ + "# How to use tools in a chain\n", + "\n", + "In this guide, we will go over the basic ways to create Chains and Agents that call Tools. Tools can be just about anything — APIs, functions, databases, etc. Tools allow us to extend the capabilities of a model beyond just outputting text/messages. The key to using models with tools is correctly prompting a model and parsing its response so that it chooses the right tools and provides the right inputs for them." + ] + }, + { + "cell_type": "markdown", + "id": "e6b79a42-0349-42c6-9ce8-72220e838e8d", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages for this guide:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2274266-755a-4e90-b257-5180fb089af2", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain" + ] + }, + { + "cell_type": "markdown", + "id": "36a9c6fc-8264-462f-b8d7-9c7bbec22ef9", + "metadata": {}, + "source": [ + "If you'd like to trace your runs in [LangSmith](/docs/langsmith/) uncomment and set the following environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57a81b7a-4fd9-4f28-bc32-7b98b522e1b0", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "68946881", + "metadata": {}, + "source": [ + "## Create a tool\n", + "\n", + "First, we need to create a tool to call. For this example, we will create a custom tool from a function. For more information on creating custom tools, please see [this guide](/docs/how_to/custom_tools)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "90187d07", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d7009e1a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "multiply\n", + "multiply(first_int: int, second_int: int) -> int - Multiply two integers together.\n", + "{'first_int': {'title': 'First Int', 'type': 'integer'}, 'second_int': {'title': 'Second Int', 'type': 'integer'}}\n" + ] + } + ], + "source": [ + "print(multiply.name)\n", + "print(multiply.description)\n", + "print(multiply.args)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "be77e780", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multiply.invoke({\"first_int\": 4, \"second_int\": 5})" + ] + }, + { + "cell_type": "markdown", + "id": "19ba4d63", + "metadata": {}, + "source": [ + "## Chains\n", + "\n", + "If we know that we only need to use a tool a fixed number of times, we can create a chain for doing so. Let's create a simple chain that just multiplies user-specified numbers.\n", + "\n", + "![chain](/static/img/tool_chain.svg)\n", + "\n", + "### Tool/function calling\n", + "One of the most reliable ways to use tools with LLMs is with tool calling APIs (also sometimes called function calling). This only works with models that explicitly support tool calling. You can see which models support tool calling [here](/docs/integrations/chat/), and learn more about how to use tool calling in [this guide](/docs/how_to/function_calling).\n", + "\n", + "First we'll define our model and tools. We'll start with just a single tool, `multiply`.\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "9bce8935-1465-45ac-8a93-314222c753c4", + "metadata": {}, + "outputs": [], + "source": [ + "# | echo: false\n", + "# | output: false\n", + "\n", + "from langchain_openai.chat_models import ChatOpenAI\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)" + ] + }, + { + "cell_type": "markdown", + "id": "c22e6f0f-c5ad-4c0f-9514-e626704ea51c", + "metadata": {}, + "source": [ + "We'll use `bind_tools` to pass the definition of our tool in as part of each call to the model, so that the model can invoke the tool when appropriate:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3bfe2cdc-7d72-457c-a9a1-5fa1e0bcde55", + "metadata": {}, + "outputs": [], + "source": [ + "llm_with_tools = llm.bind_tools([multiply])" + ] + }, + { + "cell_type": "markdown", + "id": "07fc830e-a6d2-4fac-904b-b94072e64018", + "metadata": {}, + "source": [ + "When the model invokes the tool, this will show up in the `AIMessage.tool_calls` attribute of the output:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "68f30343-14ef-48f1-badd-b6a03977316d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'multiply',\n", + " 'args': {'first_int': 5, 'second_int': 42},\n", + " 'id': 'call_cCP9oA3tRz7HDrjFn1FdmDaG'}]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "msg = llm_with_tools.invoke(\"whats 5 times forty two\")\n", + "msg.tool_calls" + ] + }, + { + "cell_type": "markdown", + "id": "330015a3-a5a7-433a-826a-6277766f6c27", + "metadata": {}, + "source": [ + "Check out the [LangSmith trace here](https://smith.langchain.com/public/81ff0cbd-e05b-4720-bf61-2c9807edb708/r)." + ] + }, + { + "cell_type": "markdown", + "id": "8ba1764d-0272-4f98-adcf-b48cb2c0a315", + "metadata": {}, + "source": [ + "### Invoking the tool\n", + "\n", + "Great! We're able to generate tool invocations. But what if we want to actually call the tool? To do so we'll need to pass the generated tool args to our tool. As a simple example we'll just extract the arguments of the first tool_call:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "4f5325ca-e5dc-4d1a-ba36-b085a029c90a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "92" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from operator import itemgetter\n", + "\n", + "chain = llm_with_tools | (lambda x: x.tool_calls[0][\"args\"]) | multiply\n", + "chain.invoke(\"What's four times 23\")" + ] + }, + { + "cell_type": "markdown", + "id": "79a9eb63-383d-4dd4-a162-08b4a52ef4d9", + "metadata": {}, + "source": [ + "Check out the [LangSmith trace here](https://smith.langchain.com/public/16bbabb9-fc9b-41e5-a33d-487c42df4f85/r)." + ] + }, + { + "cell_type": "markdown", + "id": "0521d3d5", + "metadata": {}, + "source": [ + "## Agents\n", + "\n", + "Chains are great when we know the specific sequence of tool usage needed for any user input. But for certain use cases, how many times we use tools depends on the input. In these cases, we want to let the model itself decide how many times to use tools and in what order. [Agents](/docs/tutorials/agents) let us do just this.\n", + "\n", + "LangChain comes with a number of built-in agents that are optimized for different use cases. Read about all the [agent types here](/docs/modules/agents/agent_types/).\n", + "\n", + "We'll use the [tool calling agent](https://api.python.langchain.com/en/latest/agents/langchain.agents.tool_calling_agent.base.create_tool_calling_agent.html), which is generally the most reliable kind and the recommended one for most use cases.\n", + "\n", + "![agent](/static/img/tool_agent.svg)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "21723cf4-9421-4a8d-92a6-eeeb8f4367f1", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain.agents import AgentExecutor, create_tool_calling_agent" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "6be83879-9da3-4dd9-b147-a79f76affd7a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m System Message \u001b[0m================================\n", + "\n", + "You are a helpful assistant\n", + "\n", + "=============================\u001b[1m Messages Placeholder \u001b[0m=============================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{chat_history}\u001b[0m\n", + "\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{input}\u001b[0m\n", + "\n", + "=============================\u001b[1m Messages Placeholder \u001b[0m=============================\n", + "\n", + "\u001b[33;1m\u001b[1;3m{agent_scratchpad}\u001b[0m\n" + ] + } + ], + "source": [ + "# Get the prompt to use - can be replaced with any prompt that includes variables \"agent_scratchpad\" and \"input\"!\n", + "prompt = hub.pull(\"hwchase17/openai-tools-agent\")\n", + "prompt.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "616f9714-5b18-4eed-b88a-d38e4cb1de99", + "metadata": {}, + "source": [ + "Agents are also great because they make it easy to use multiple tools. To learn how to build Chains that use multiple tools, check out the [Chains with multiple tools](/docs/use_cases/tool_use/multiple_tools) page." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "95c86d32-ee45-4c87-a28c-14eff19b49e9", + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent\n", + "\n", + "\n", + "tools = [multiply, add, exponentiate]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "17b09ac6-c9b7-4340-a8a0-3d3061f7888c", + "metadata": {}, + "outputs": [], + "source": [ + "# Construct the tool calling agent\n", + "agent = create_tool_calling_agent(llm, tools, prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "675091d2-cac9-45c4-a5d7-b760ee6c1986", + "metadata": {}, + "outputs": [], + "source": [ + "# Create an agent executor by passing in the agent and tools\n", + "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)" + ] + }, + { + "cell_type": "markdown", + "id": "a6099ab6-2fa6-452d-b73c-7fb65daab451", + "metadata": {}, + "source": [ + "With an agent, we can ask questions that require arbitrarily-many uses of our tools:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "f7dbb240-809e-4e41-8f63-1a4636e8e26d", + "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: `exponentiate` with `{'base': 3, 'exponent': 5}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3m243\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `add` with `{'first_int': 12, 'second_int': 3}`\n", + "\n", + "\n", + "\u001b[0m\u001b[33;1m\u001b[1;3m15\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `multiply` with `{'first_int': 243, 'second_int': 15}`\n", + "\n", + "\n", + "\u001b[0m\u001b[36;1m\u001b[1;3m3645\u001b[0m\u001b[32;1m\u001b[1;3m\n", + "Invoking: `exponentiate` with `{'base': 405, 'exponent': 2}`\n", + "\n", + "\n", + "\u001b[0m\u001b[38;5;200m\u001b[1;3m164025\u001b[0m\u001b[32;1m\u001b[1;3mThe result of taking 3 to the fifth power is 243. \n", + "\n", + "The sum of twelve and three is 15. \n", + "\n", + "Multiplying 243 by 15 gives 3645. \n", + "\n", + "Finally, squaring 3645 gives 164025.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'Take 3 to the fifth power and multiply that by the sum of twelve and three, then square the whole result',\n", + " 'output': 'The result of taking 3 to the fifth power is 243. \\n\\nThe sum of twelve and three is 15. \\n\\nMultiplying 243 by 15 gives 3645. \\n\\nFinally, squaring 3645 gives 164025.'}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.invoke(\n", + " {\n", + " \"input\": \"Take 3 to the fifth power and multiply that by the sum of twelve and three, then square the whole result\"\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8fdb0ed9-1763-4778-a7d6-026578cd9585", + "metadata": {}, + "source": [ + "Check out the [LangSmith trace here](https://smith.langchain.com/public/eeeb27a4-a2f8-4f06-a3af-9c983f76146c/r)." + ] + }, + { + "cell_type": "markdown", + "id": "b0e4b7f4-58ce-4ca0-a986-d05a436a7ccf", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Here we've gone over the basic ways to use Tools with Chains and Agents. We recommend the following sections to explore next:\n", + "\n", + "- [Agents](/docs/tutorials/agents): Everything related to Agents.\n", + "- [Choosing between multiple tools](/docs/how_to/tools_multiple): How to make tool chains that select from multiple tools.\n", + "- [Prompting for tool use](/docs/how_to/tools_prompting): How to make tool chains that prompt models directly, without using function-calling APIs.\n", + "- [Parallel tool use](/docs/how_to/tools_parallel): How to make tool chains that invoke multiple tools at once." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/tools_error.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tools_error.ipynb new file mode 100644 index 0000000000000..22afe3bdfc3a0 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tools_error.ipynb @@ -0,0 +1,404 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5d60cbb9-2a6a-43ea-a9e9-f67b16ddd2b2", + "metadata": {}, + "source": [ + "# How to handle tool errors\n", + "\n", + "Using a model to invoke a tool has some obvious potential failure modes. Firstly, the model needs to return a output that can be parsed at all. Secondly, the model needs to return tool arguments that are valid.\n", + "\n", + "We can build error handling into our chains to mitigate these failure modes." + ] + }, + { + "cell_type": "markdown", + "id": "712c774f-27c7-4351-a196-39900ca155f5", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63056c24-9834-4e3d-8bc5-54b1e6c5df86", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain-core langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "68107597-0c8c-4bb5-8c12-9992fabdf71a", + "metadata": {}, + "source": [ + "If you'd like to trace your runs in [LangSmith](/docs/langsmith/) uncomment and set the following environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08785b6d-722d-4620-b6ec-36deb3842c69", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "0a50f93a-5d6f-4691-8f98-27239a1c2f95", + "metadata": {}, + "source": [ + "## Chain\n", + "\n", + "Suppose we have the following (dummy) tool and tool-calling chain. We'll make our tool intentionally convoluted to try and trip up the model.\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "86258950-5e61-4340-81b9-84a5d26e8773", + "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": 2, + "id": "1d20604e-c4d1-4d21-841b-23e4f61aec36", + "metadata": {}, + "outputs": [], + "source": [ + "# Define tool\n", + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:\n", + " \"\"\"Do something complex with a complex tool.\"\"\"\n", + " return int_arg * float_arg" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "553c2c13-28c8-4451-8a3a-6c31d52dc31d", + "metadata": {}, + "outputs": [], + "source": [ + "llm_with_tools = llm.bind_tools(\n", + " [complex_tool],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "802b2eca-9f79-4d6c-8257-85139ca5c752", + "metadata": {}, + "outputs": [], + "source": [ + "# Define chain\n", + "chain = llm_with_tools | (lambda msg: msg.tool_calls[0][\"args\"]) | complex_tool" + ] + }, + { + "cell_type": "markdown", + "id": "c34f005e-63f0-4841-9461-ca36c36607fc", + "metadata": {}, + "source": [ + "We can see that when we try to invoke this chain with even a fairly explicit input, the model fails to correctly call the tool (it forgets the `dict_arg` argument)." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d354664c-ac44-4967-a35f-8912b3ad9477", + "metadata": {}, + "outputs": [ + { + "ename": "ValidationError", + "evalue": "1 validation error for complex_toolSchema\ndict_arg\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)", + "Cell \u001b[0;32mIn[12], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mchain\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43muse complex tool. the args are 5, 2.1, empty dictionary. don\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mt forget dict_arg\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\n\u001b[1;32m 3\u001b[0m \u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:2499\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 2497\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 2498\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 2499\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2500\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2501\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 2502\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2503\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2504\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2505\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2506\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 2507\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/tools.py:241\u001b[0m, in \u001b[0;36mBaseTool.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 234\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 235\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 236\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mstr\u001b[39m, Dict],\n\u001b[1;32m 237\u001b[0m config: Optional[RunnableConfig] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 238\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 239\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Any:\n\u001b[1;32m 240\u001b[0m config \u001b[38;5;241m=\u001b[39m ensure_config(config)\n\u001b[0;32m--> 241\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[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 242\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 243\u001b[0m \u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mcallbacks\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 244\u001b[0m \u001b[43m \u001b[49m\u001b[43mtags\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtags\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 245\u001b[0m \u001b[43m \u001b[49m\u001b[43mmetadata\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mmetadata\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 246\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_name\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mrun_name\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 247\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_id\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpop\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mrun_id\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 248\u001b[0m \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 249\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/tools.py:387\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, run_id, **kwargs)\u001b[0m\n\u001b[1;32m 385\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ValidationError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 386\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_validation_error:\n\u001b[0;32m--> 387\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[1;32m 388\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_validation_error, \u001b[38;5;28mbool\u001b[39m):\n\u001b[1;32m 389\u001b[0m observation \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTool input validation error\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/tools.py:378\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, run_id, **kwargs)\u001b[0m\n\u001b[1;32m 364\u001b[0m run_manager \u001b[38;5;241m=\u001b[39m callback_manager\u001b[38;5;241m.\u001b[39mon_tool_start(\n\u001b[1;32m 365\u001b[0m {\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdescription\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdescription},\n\u001b[1;32m 366\u001b[0m tool_input \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(tool_input, \u001b[38;5;28mstr\u001b[39m) \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mstr\u001b[39m(tool_input),\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 375\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs,\n\u001b[1;32m 376\u001b[0m )\n\u001b[1;32m 377\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 378\u001b[0m parsed_input \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse_input\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtool_input\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 379\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 380\u001b[0m observation \u001b[38;5;241m=\u001b[39m (\n\u001b[1;32m 381\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_run(\u001b[38;5;241m*\u001b[39mtool_args, run_manager\u001b[38;5;241m=\u001b[39mrun_manager, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mtool_kwargs)\n\u001b[1;32m 382\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_arg_supported\n\u001b[1;32m 383\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 384\u001b[0m )\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/tools.py:283\u001b[0m, in \u001b[0;36mBaseTool._parse_input\u001b[0;34m(self, tool_input)\u001b[0m\n\u001b[1;32m 281\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 282\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m input_args \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m--> 283\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[43minput_args\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparse_obj\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtool_input\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 284\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m {\n\u001b[1;32m 285\u001b[0m k: \u001b[38;5;28mgetattr\u001b[39m(result, k)\n\u001b[1;32m 286\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m result\u001b[38;5;241m.\u001b[39mdict()\u001b[38;5;241m.\u001b[39mitems()\n\u001b[1;32m 287\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m k \u001b[38;5;129;01min\u001b[39;00m tool_input\n\u001b[1;32m 288\u001b[0m }\n\u001b[1;32m 289\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m tool_input\n", + "File \u001b[0;32m~/langchain/.venv/lib/python3.9/site-packages/pydantic/v1/main.py:526\u001b[0m, in \u001b[0;36mBaseModel.parse_obj\u001b[0;34m(cls, obj)\u001b[0m\n\u001b[1;32m 524\u001b[0m exc \u001b[38;5;241m=\u001b[39m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m expected dict not \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mobj\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 525\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ValidationError([ErrorWrapper(exc, loc\u001b[38;5;241m=\u001b[39mROOT_KEY)], \u001b[38;5;28mcls\u001b[39m) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01me\u001b[39;00m\n\u001b[0;32m--> 526\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[43mobj\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/.venv/lib/python3.9/site-packages/pydantic/v1/main.py:341\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(__pydantic_self__, **data)\u001b[0m\n\u001b[1;32m 339\u001b[0m values, fields_set, validation_error \u001b[38;5;241m=\u001b[39m validate_model(__pydantic_self__\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m, data)\n\u001b[1;32m 340\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m validation_error:\n\u001b[0;32m--> 341\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m validation_error\n\u001b[1;32m 342\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 343\u001b[0m object_setattr(__pydantic_self__, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__dict__\u001b[39m\u001b[38;5;124m'\u001b[39m, values)\n", + "\u001b[0;31mValidationError\u001b[0m: 1 validation error for complex_toolSchema\ndict_arg\n field required (type=value_error.missing)" + ] + } + ], + "source": [ + "chain.invoke(\n", + " \"use complex tool. the args are 5, 2.1, empty dictionary. don't forget dict_arg\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "890d989d-2d39-4571-9a55-d3496b9b5d27", + "metadata": {}, + "source": [ + "## Try/except tool call\n", + "\n", + "The simplest way to more gracefully handle errors is to try/except the tool-calling step and return a helpful message on errors:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8fedb550-683d-45ae-8876-ae7acb332019", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Any\n", + "\n", + "from langchain_core.runnables import Runnable, RunnableConfig\n", + "\n", + "\n", + "def try_except_tool(tool_args: dict, config: RunnableConfig) -> Runnable:\n", + " try:\n", + " complex_tool.invoke(tool_args, config=config)\n", + " except Exception as e:\n", + " return f\"Calling tool with arguments:\\n\\n{tool_args}\\n\\nraised the following error:\\n\\n{type(e)}: {e}\"\n", + "\n", + "\n", + "chain = llm_with_tools | (lambda msg: msg.tool_calls[0][\"args\"]) | try_except_tool" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "71a2c98d-c0be-4c0a-bb3d-41ad4596526c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Calling tool with arguments:\n", + "\n", + "{'int_arg': 5, 'float_arg': 2.1}\n", + "\n", + "raised the following error:\n", + "\n", + ": 1 validation error for complex_toolSchema\n", + "dict_arg\n", + " field required (type=value_error.missing)\n" + ] + } + ], + "source": [ + "print(\n", + " chain.invoke(\n", + " \"use complex tool. the args are 5, 2.1, empty dictionary. don't forget dict_arg\"\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3b2f6393-cb47-49d0-921c-09550a049fe4", + "metadata": {}, + "source": [ + "## Fallbacks\n", + "\n", + "We can also try to fallback to a better model in the event of a tool invocation error. In this case we'll fall back to an identical chain that uses `gpt-4-1106-preview` instead of `gpt-3.5-turbo`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "02cc4223-35fa-4240-976a-012299ca703c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10.5" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = llm_with_tools | (lambda msg: msg.tool_calls[0][\"args\"]) | complex_tool\n", + "better_model = ChatOpenAI(model=\"gpt-4-1106-preview\", temperature=0).bind_tools(\n", + " [complex_tool], tool_choice=\"complex_tool\"\n", + ")\n", + "better_chain = better_model | (lambda msg: msg.tool_calls[0][\"args\"]) | complex_tool\n", + "\n", + "chain_with_fallback = chain.with_fallbacks([better_chain])\n", + "chain_with_fallback.invoke(\n", + " \"use complex tool. the args are 5, 2.1, empty dictionary. don't forget dict_arg\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "412f8c4e-cc83-4d87-84a1-5ba2f8edb1e9", + "metadata": {}, + "source": [ + "Looking at the [Langsmith trace](https://smith.langchain.com/public/00e91fc2-e1a4-4b0f-a82e-e6b3119d196c/r) for this chain run, we can see that the first chain call fails as expected and it's the fallback that succeeds." + ] + }, + { + "cell_type": "markdown", + "id": "304b59cd-cd25-4205-9769-36595c8f3b59", + "metadata": {}, + "source": [ + "## Retry with exception\n", + "\n", + "To take things one step further, we can try to automatically re-run the chain with the exception passed in, so that the model may be able to correct its behavior:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "b5659956-9454-468a-9753-a3ff9052b8f5", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from typing import Any\n", + "\n", + "from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "\n", + "class CustomToolException(Exception):\n", + " \"\"\"Custom LangChain tool exception.\"\"\"\n", + "\n", + " def __init__(self, tool_call: ToolCall, exception: Exception) -> None:\n", + " super().__init__()\n", + " self.tool_call = tool_call\n", + " self.exception = exception\n", + "\n", + "\n", + "def tool_custom_exception(msg: AIMessage, config: RunnableConfig) -> Runnable:\n", + " try:\n", + " return complex_tool.invoke(msg.tool_calls[0][\"args\"], config=config)\n", + " except Exception as e:\n", + " raise CustomToolException(msg.tool_calls[0], e)\n", + "\n", + "\n", + "def exception_to_messages(inputs: dict) -> dict:\n", + " exception = inputs.pop(\"exception\")\n", + "\n", + " # Add historical messages to the original input, so the model knows that it made a mistake with the last tool call.\n", + " messages = [\n", + " AIMessage(content=\"\", tool_calls=[exception.tool_call]),\n", + " ToolMessage(\n", + " tool_call_id=exception.tool_call[\"id\"], content=str(exception.exception)\n", + " ),\n", + " HumanMessage(\n", + " content=\"The last tool call raised an exception. Try calling the tool again with corrected arguments. Do not repeat mistakes.\"\n", + " ),\n", + " ]\n", + " inputs[\"last_output\"] = messages\n", + " return inputs\n", + "\n", + "\n", + "# We add a last_output MessagesPlaceholder to our prompt which if not passed in doesn't\n", + "# affect the prompt at all, but gives us the option to insert an arbitrary list of Messages\n", + "# into the prompt if needed. We'll use this on retries to insert the error message.\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"human\", \"{input}\"), MessagesPlaceholder(\"last_output\", optional=True)]\n", + ")\n", + "chain = prompt | llm_with_tools | tool_custom_exception\n", + "\n", + "# If the initial chain call fails, we rerun it withe the exception passed in as a message.\n", + "self_correcting_chain = chain.with_fallbacks(\n", + " [exception_to_messages | chain], exception_key=\"exception\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "4c45f5bd-cbb4-47d5-b4b6-aec50673c750", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10.5" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "self_correcting_chain.invoke(\n", + " {\n", + " \"input\": \"use complex tool. the args are 5, 2.1, empty dictionary. don't forget dict_arg\"\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "50d269a9-3cab-4a37-ba2f-805296453627", + "metadata": {}, + "source": [ + "And our chain succeeds! Looking at the [LangSmith trace](https://smith.langchain.com/public/c11e804c-e14f-4059-bd09-64766f999c14/r), we can see that indeed our initial chain still fails, and it's only on retrying that the chain succeeds." + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/tools_human.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tools_human.ipynb new file mode 100644 index 0000000000000..b7421d629761b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tools_human.ipynb @@ -0,0 +1,298 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b09b745d-f006-4ecc-8772-afa266c43605", + "metadata": {}, + "source": [ + "# How to add a human-in-the-loop for tools\n", + "\n", + "There are certain tools that we don't trust a model to execute on its own. One thing we can do in such situations is require human approval before the tool is invoked." + ] + }, + { + "cell_type": "markdown", + "id": "09178c30-a633-4d7b-88ea-092316f14b6f", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e44bec05-9aa4-47b1-a660-c0a183533598", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain" + ] + }, + { + "cell_type": "markdown", + "id": "f09629b6-7f62-4879-a791-464739ca6b6b", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2bed0ccf-20cc-4fd3-9947-55471dd8c4da", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below:\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "43721981-4595-4721-bea0-5c67696426d3", + "metadata": {}, + "source": [ + "## Chain\n", + "\n", + "Suppose we have the following (dummy) tools and tool-calling chain:\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e0ff02ac-e750-493b-9b09-4578711a6726", + "metadata": {}, + "outputs": [], + "source": [ + "# | echo: false\n", + "# | outout: false\n", + "\n", + "from langchain_anthropic import ChatAnthropic\n", + "\n", + "llm = ChatAnthropic(model=\"claude-3-sonnet-20240229\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0221fdfd-2a18-4449-a123-e6b0b15bb3d9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'count_emails',\n", + " 'args': {'last_n_days': 5},\n", + " 'id': 'toolu_012VHuh7vk5dVNct5SgZj3gh',\n", + " 'output': 10}]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from operator import itemgetter\n", + "from typing import Dict, List\n", + "\n", + "from langchain_core.messages import AIMessage\n", + "from langchain_core.runnables import Runnable, RunnablePassthrough\n", + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def count_emails(last_n_days: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return last_n_days * 2\n", + "\n", + "\n", + "@tool\n", + "def send_email(message: str, recipient: str) -> str:\n", + " \"Add two integers.\"\n", + " return f\"Successfully sent email to {recipient}.\"\n", + "\n", + "\n", + "tools = [count_emails, send_email]\n", + "llm_with_tools = llm.bind_tools(tools)\n", + "\n", + "\n", + "def call_tools(msg: AIMessage) -> List[Dict]:\n", + " \"\"\"Simple sequential tool calling helper.\"\"\"\n", + " tool_map = {tool.name: tool for tool in tools}\n", + " tool_calls = msg.tool_calls.copy()\n", + " for tool_call in tool_calls:\n", + " tool_call[\"output\"] = tool_map[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n", + " return tool_calls\n", + "\n", + "\n", + "chain = llm_with_tools | call_tools\n", + "chain.invoke(\"how many emails did i get in the last 5 days?\")" + ] + }, + { + "cell_type": "markdown", + "id": "258c1c7b-a765-4558-93fe-d0defbc29223", + "metadata": {}, + "source": [ + "## Adding human approval\n", + "\n", + "We can add a simple human approval step to our tool_chain function:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "341fb055-0315-47bc-8f72-ed6103d2981f", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "\n", + "def human_approval(msg: AIMessage) -> Runnable:\n", + " tool_strs = \"\\n\\n\".join(\n", + " json.dumps(tool_call, indent=2) for tool_call in msg.tool_calls\n", + " )\n", + " input_msg = (\n", + " f\"Do you approve of the following tool invocations\\n\\n{tool_strs}\\n\\n\"\n", + " \"Anything except 'Y'/'Yes' (case-insensitive) will be treated as a no.\"\n", + " )\n", + " resp = input(input_msg)\n", + " if resp.lower() not in (\"yes\", \"y\"):\n", + " raise ValueError(f\"Tool invocations not approved:\\n\\n{tool_strs}\")\n", + " return msg" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "25dca07b-56ca-4b94-9955-d4f3e9895e03", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Do you approve of the following tool invocations\n", + "\n", + "{\n", + " \"name\": \"count_emails\",\n", + " \"args\": {\n", + " \"last_n_days\": 5\n", + " },\n", + " \"id\": \"toolu_01LCpjpFxrRspygDscnHYyPm\"\n", + "}\n", + "\n", + "Anything except 'Y'/'Yes' (case-insensitive) will be treated as a no. yes\n" + ] + }, + { + "data": { + "text/plain": [ + "[{'name': 'count_emails',\n", + " 'args': {'last_n_days': 5},\n", + " 'id': 'toolu_01LCpjpFxrRspygDscnHYyPm',\n", + " 'output': 10}]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain = llm_with_tools | human_approval | call_tools\n", + "chain.invoke(\"how many emails did i get in the last 5 days?\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f558f2cd-847b-4ef9-a770-3961082b540c", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Do you approve of the following tool invocations\n", + "\n", + "{\n", + " \"name\": \"send_email\",\n", + " \"args\": {\n", + " \"message\": \"What's up homie\",\n", + " \"recipient\": \"sally@gmail.com\"\n", + " },\n", + " \"id\": \"toolu_0158qJVd1AL32Y1xxYUAtNEy\"\n", + "}\n", + "\n", + "Anything except 'Y'/'Yes' (case-insensitive) will be treated as a no. no\n" + ] + }, + { + "ename": "ValueError", + "evalue": "Tool invocations not approved:\n\n{\n \"name\": \"send_email\",\n \"args\": {\n \"message\": \"What's up homie\",\n \"recipient\": \"sally@gmail.com\"\n },\n \"id\": \"toolu_0158qJVd1AL32Y1xxYUAtNEy\"\n}", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[11], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mchain\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSend sally@gmail.com an email saying \u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mWhat\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43ms up homie\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:2499\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 2497\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 2498\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 2499\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2500\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2501\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 2502\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2503\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2504\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2505\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2506\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 2507\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:3961\u001b[0m, in \u001b[0;36mRunnableLambda.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 3959\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Invoke this runnable synchronously.\"\"\"\u001b[39;00m\n\u001b[1;32m 3960\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mfunc\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m-> 3961\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[43m_call_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 3962\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_invoke\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3963\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3964\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_config\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig\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[43mfunc\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3965\u001b[0m \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 3966\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3967\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 3968\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\n\u001b[1;32m 3969\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mCannot invoke a coroutine function synchronously.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 3970\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mUse `ainvoke` instead.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 3971\u001b[0m )\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:1625\u001b[0m, in \u001b[0;36mRunnable._call_with_config\u001b[0;34m(self, func, input, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1621\u001b[0m context \u001b[38;5;241m=\u001b[39m copy_context()\n\u001b[1;32m 1622\u001b[0m context\u001b[38;5;241m.\u001b[39mrun(var_child_runnable_config\u001b[38;5;241m.\u001b[39mset, child_config)\n\u001b[1;32m 1623\u001b[0m output \u001b[38;5;241m=\u001b[39m cast(\n\u001b[1;32m 1624\u001b[0m Output,\n\u001b[0;32m-> 1625\u001b[0m \u001b[43mcontext\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1626\u001b[0m \u001b[43m \u001b[49m\u001b[43mcall_func_with_variable_args\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[1;32m 1627\u001b[0m \u001b[43m \u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[1;32m 1628\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[1;32m 1629\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1630\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1631\u001b[0m \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 1632\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m,\n\u001b[1;32m 1633\u001b[0m )\n\u001b[1;32m 1634\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 1635\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_error(e)\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/config.py:347\u001b[0m, in \u001b[0;36mcall_func_with_variable_args\u001b[0;34m(func, input, config, run_manager, **kwargs)\u001b[0m\n\u001b[1;32m 345\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m accepts_run_manager(func):\n\u001b[1;32m 346\u001b[0m kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrun_manager\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m run_manager\n\u001b[0;32m--> 347\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;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", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/base.py:3835\u001b[0m, in \u001b[0;36mRunnableLambda._invoke\u001b[0;34m(self, input, run_manager, config, **kwargs)\u001b[0m\n\u001b[1;32m 3833\u001b[0m output \u001b[38;5;241m=\u001b[39m chunk\n\u001b[1;32m 3834\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 3835\u001b[0m output \u001b[38;5;241m=\u001b[39m \u001b[43mcall_func_with_variable_args\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 3836\u001b[0m \u001b[43m \u001b[49m\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[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[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[43mkwargs\u001b[49m\n\u001b[1;32m 3837\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3838\u001b[0m \u001b[38;5;66;03m# If the output is a runnable, invoke it\u001b[39;00m\n\u001b[1;32m 3839\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(output, Runnable):\n", + "File \u001b[0;32m~/langchain/libs/core/langchain_core/runnables/config.py:347\u001b[0m, in \u001b[0;36mcall_func_with_variable_args\u001b[0;34m(func, input, config, run_manager, **kwargs)\u001b[0m\n\u001b[1;32m 345\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m accepts_run_manager(func):\n\u001b[1;32m 346\u001b[0m kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrun_manager\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m run_manager\n\u001b[0;32m--> 347\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;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", + "Cell \u001b[0;32mIn[9], line 14\u001b[0m, in \u001b[0;36mhuman_approval\u001b[0;34m(msg)\u001b[0m\n\u001b[1;32m 12\u001b[0m resp \u001b[38;5;241m=\u001b[39m \u001b[38;5;28minput\u001b[39m(input_msg)\n\u001b[1;32m 13\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m resp\u001b[38;5;241m.\u001b[39mlower() \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m (\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124myes\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124my\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m---> 14\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTool invocations not approved:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00mtool_strs\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 15\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m msg\n", + "\u001b[0;31mValueError\u001b[0m: Tool invocations not approved:\n\n{\n \"name\": \"send_email\",\n \"args\": {\n \"message\": \"What's up homie\",\n \"recipient\": \"sally@gmail.com\"\n },\n \"id\": \"toolu_0158qJVd1AL32Y1xxYUAtNEy\"\n}" + ] + } + ], + "source": [ + "chain.invoke(\"Send sally@gmail.com an email saying 'What's up homie'\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e938d8f1-df93-4726-a465-78e596312246", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/tools_multiple.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tools_multiple.ipynb new file mode 100644 index 0000000000000..10a93a15e16c8 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tools_multiple.ipynb @@ -0,0 +1,273 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "1ea1fe24-fe1e-463b-a52c-79f0ef02328e", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 2\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "95982bf1-7d9d-4dd6-a4ad-9de0719fe17f", + "metadata": {}, + "source": [ + "# How to use an LLM to choose between multiple tools\n", + "\n", + "In our [Quickstart](/docs/use_cases/tool_use/quickstart) we went over how to build a Chain that calls a single `multiply` tool. Now let's take a look at how we might augment this chain so that it can pick from a number of tools to call. We'll focus on Chains since [Agents](/docs/tutorials/agents) can route between multiple tools by default." + ] + }, + { + "cell_type": "markdown", + "id": "3fafec38-443a-42ad-a913-5be7667e3734", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages for this guide:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78411bf1-0117-4f33-a3d7-f3d77a97bb78", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain-core" + ] + }, + { + "cell_type": "markdown", + "id": "59d08fd0-ddd9-4c74-bcea-a5ca3a86e542", + "metadata": {}, + "source": [ + "If you'd like to trace your runs in [LangSmith](/docs/langsmith/) uncomment and set the following environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4185e74b-0500-4cad-ace0-bac37de466ac", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "d28159f5-b7d0-4385-aa44-4cd1b64507bb", + "metadata": {}, + "source": [ + "## Tools\n", + "\n", + "Recall we already had a `multiply` tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e13ec98c-8521-4d63-b521-caf92da87b70", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int" + ] + }, + { + "cell_type": "markdown", + "id": "3de233af-b3bd-4f0c-8b1a-83527143a8db", + "metadata": {}, + "source": [ + "And now we can add to it an `exponentiate` and `add` tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e93661cd-a2ba-4ada-91ad-baf1b60879ec", + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent" + ] + }, + { + "cell_type": "markdown", + "id": "bbea4555-ed10-4a18-b802-e9a3071f132b", + "metadata": {}, + "source": [ + "The main difference between using one Tool and many is that we can't be sure which Tool the model will invoke upfront, so we cannot hardcode, like we did in the [Quickstart](/docs/use_cases/tool_use/quickstart), a specific tool into our chain. Instead we'll add `call_tools`, a `RunnableLambda` that takes the output AI message with tools calls and routes to the correct tools.\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f00f0f3f-8530-4c1d-a26c-d20824e31faf", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_anthropic import ChatAnthropic\n", + "\n", + "llm = ChatAnthropic(model=\"claude-3-sonnet-20240229\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c35359ae-a740-48c5-b5e7-1a377fb25aa2", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "from typing import Dict, List, Union\n", + "\n", + "from langchain_core.messages import AIMessage\n", + "from langchain_core.runnables import (\n", + " Runnable,\n", + " RunnableLambda,\n", + " RunnableMap,\n", + " RunnablePassthrough,\n", + ")\n", + "\n", + "tools = [multiply, exponentiate, add]\n", + "llm_with_tools = llm.bind_tools(tools)\n", + "tool_map = {tool.name: tool for tool in tools}\n", + "\n", + "\n", + "def call_tools(msg: AIMessage) -> Runnable:\n", + " \"\"\"Simple sequential tool calling helper.\"\"\"\n", + " tool_map = {tool.name: tool for tool in tools}\n", + " tool_calls = msg.tool_calls.copy()\n", + " for tool_call in tool_calls:\n", + " tool_call[\"output\"] = tool_map[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n", + " return tool_calls\n", + "\n", + "\n", + "chain = llm_with_tools | call_tools" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ea6dbb32-ec9b-4c70-a90f-a2db93978cf1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'multiply',\n", + " 'args': {'first_int': 23, 'second_int': 7},\n", + " 'id': 'toolu_01Wf8kUs36kxRKLDL8vs7G8q',\n", + " 'output': 161}]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\"What's 23 times 7\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "b1c6c0f8-6d04-40d4-a40e-8719ca7b27c2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'add',\n", + " 'args': {'first_int': 1000000, 'second_int': 1000000000},\n", + " 'id': 'toolu_012aK4xZBQg2sXARsFZnqxHh',\n", + " 'output': 1001000000}]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\"add a million plus a billion\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ce76f299-1a4d-421c-afa4-a6346e34285c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'exponentiate',\n", + " 'args': {'base': 37, 'exponent': 3},\n", + " 'id': 'toolu_01VDU6X3ugDb9cpnnmCZFPbC',\n", + " 'output': 50653}]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\"cube thirty-seven\")" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/tools_parallel.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tools_parallel.ipynb new file mode 100644 index 0000000000000..034c13bf22e9a --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tools_parallel.ipynb @@ -0,0 +1,215 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "95982bf1-7d9d-4dd6-a4ad-9de0719fe17f", + "metadata": {}, + "source": [ + "# How to call tools in parallel\n", + "\n", + "In the [Chains with multiple tools](/docs/use_cases/tool_use/multiple_tools) guide we saw how to build function-calling chains that select between multiple tools. Some models, like the OpenAI models released in Fall 2023, also support parallel function calling, which allows you to invoke multiple functions (or the same function multiple times) in a single model call. Our previous chain from the multiple tools guides actually already supports this." + ] + }, + { + "cell_type": "markdown", + "id": "3fafec38-443a-42ad-a913-5be7667e3734", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages for this guide:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78411bf1-0117-4f33-a3d7-f3d77a97bb78", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain-core" + ] + }, + { + "cell_type": "markdown", + "id": "59d08fd0-ddd9-4c74-bcea-a5ca3a86e542", + "metadata": {}, + "source": [ + "If you'd like to trace your runs in [LangSmith](/docs/langsmith/) uncomment and set the following environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4185e74b-0500-4cad-ace0-bac37de466ac", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "d28159f5-b7d0-4385-aa44-4cd1b64507bb", + "metadata": {}, + "source": [ + "## Tools" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e13ec98c-8521-4d63-b521-caf92da87b70", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int\n", + "\n", + "\n", + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent" + ] + }, + { + "cell_type": "markdown", + "id": "119d419c-1c61-4e0d-834a-5dabb72f5514", + "metadata": {}, + "source": [ + "# Chain\n", + "\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f67d91d8-cc38-4065-8f80-901e079954dd", + "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": 8, + "id": "c35359ae-a740-48c5-b5e7-1a377fb25aa2", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "from typing import Dict, List, Union\n", + "\n", + "from langchain_core.messages import AIMessage\n", + "from langchain_core.runnables import (\n", + " Runnable,\n", + " RunnableLambda,\n", + " RunnableMap,\n", + " RunnablePassthrough,\n", + ")\n", + "\n", + "tools = [multiply, exponentiate, add]\n", + "llm_with_tools = llm.bind_tools(tools)\n", + "tool_map = {tool.name: tool for tool in tools}\n", + "\n", + "\n", + "def call_tools(msg: AIMessage) -> Runnable:\n", + " \"\"\"Simple sequential tool calling helper.\"\"\"\n", + " tool_map = {tool.name: tool for tool in tools}\n", + " tool_calls = msg.tool_calls.copy()\n", + " for tool_call in tool_calls:\n", + " tool_call[\"output\"] = tool_map[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n", + " return tool_calls\n", + "\n", + "\n", + "chain = llm_with_tools | call_tools" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "ea6dbb32-ec9b-4c70-a90f-a2db93978cf1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'multiply',\n", + " 'args': {'first_int': 23, 'second_int': 7},\n", + " 'id': 'call_22tgOrsVLyLMsl2RLbUhtycw',\n", + " 'output': 161},\n", + " {'name': 'multiply',\n", + " 'args': {'first_int': 5, 'second_int': 18},\n", + " 'id': 'call_EbKHEG3TjqBhEwb7aoxUtgzf',\n", + " 'output': 90},\n", + " {'name': 'add',\n", + " 'args': {'first_int': 1000000, 'second_int': 1000000000},\n", + " 'id': 'call_LUhu2IT3vINxlTc5fCVY6Nhi',\n", + " 'output': 1001000000},\n", + " {'name': 'exponentiate',\n", + " 'args': {'base': 37, 'exponent': 3},\n", + " 'id': 'call_bnCZIXelOKkmcyd4uGXId9Ct',\n", + " 'output': 50653}]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke(\n", + " \"What's 23 times 7, and what's five times 18 and add a million plus a billion and cube thirty-seven\"\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.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/tools_prompting.ipynb b/docs/versioned_docs/version-0.2.x/how_to/tools_prompting.ipynb new file mode 100644 index 0000000000000..5dc4940bcd5b0 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/tools_prompting.ipynb @@ -0,0 +1,415 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "3243cb05-8243-421f-99fa-98201abb3094", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 3\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "14b94240", + "metadata": {}, + "source": [ + "# How to use tools without function calling\n", + "\n", + "In this guide we'll build a Chain that does not rely on any special model APIs (like tool calling, which we showed in the [Quickstart](/docs/use_cases/tool_use/quickstart)) and instead just prompts the model directly to invoke tools." + ] + }, + { + "cell_type": "markdown", + "id": "a0a22cb8-19e7-450a-9d1b-6848d2c81cd1", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We'll need to install the following packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c556c5e-b785-428b-8e7d-efd34a2a1adb", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai" + ] + }, + { + "cell_type": "markdown", + "id": "5e727d22-f861-4eee-882a-688f8efc885e", + "metadata": {}, + "source": [ + "And set these environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "527ef906-0104-4872-b4e5-f371cf73feba", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n", + "\n", + "# If you'd like to use LangSmith, uncomment the below:\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()" + ] + }, + { + "cell_type": "markdown", + "id": "68946881", + "metadata": {}, + "source": [ + "## Create a tool\n", + "\n", + "First, we need to create a tool to call. For this example, we will create a custom tool from a function. For more information on all details related to creating custom tools, please see [this guide](/docs/how_to/custom_tools)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "90187d07", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def multiply(first_int: int, second_int: int) -> int:\n", + " \"\"\"Multiply two integers together.\"\"\"\n", + " return first_int * second_int" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d7009e1a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "multiply\n", + "multiply(first_int: int, second_int: int) -> int - Multiply two integers together.\n", + "{'first_int': {'title': 'First Int', 'type': 'integer'}, 'second_int': {'title': 'Second Int', 'type': 'integer'}}\n" + ] + } + ], + "source": [ + "print(multiply.name)\n", + "print(multiply.description)\n", + "print(multiply.args)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "be77e780", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multiply.invoke({\"first_int\": 4, \"second_int\": 5})" + ] + }, + { + "cell_type": "markdown", + "id": "15dd690e-e54d-4209-91a4-181f69a452ac", + "metadata": {}, + "source": [ + "## Creating our prompt\n", + "\n", + "We'll want to write a prompt that specifies the tools the model has access to, the arguments to those tools, and the desired output format of the model. In this case we'll instruct it to output a JSON blob of the form `{\"name\": \"...\", \"arguments\": {...}}`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c64818f0-9364-423c-922e-bdfb8f01e726", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'multiply: multiply(first_int: int, second_int: int) -> int - Multiply two integers together.'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.tools.render import render_text_description\n", + "\n", + "rendered_tools = render_text_description([multiply])\n", + "rendered_tools" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "63552d4d-8bd6-4aca-8805-56e236f6552d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "\n", + "system_prompt = f\"\"\"You are an assistant that has access to the following set of tools. Here are the names and descriptions for each tool:\n", + "\n", + "{rendered_tools}\n", + "\n", + "Given the user input, return the name and input of the tool to use. Return your response as a JSON blob with 'name' and 'arguments' keys.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"system\", system_prompt), (\"user\", \"{input}\")]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "14df2cd5-b6fa-4b10-892d-e8692c7931e5", + "metadata": {}, + "source": [ + "## Adding an output parser\n", + "\n", + "We'll use the `JsonOutputParser` for parsing our models output to JSON." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f129f5bd-127c-4c95-8f34-8f437da7ca8f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'multiply', 'arguments': {'first_int': 13, 'second_int': 4}}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.output_parsers import JsonOutputParser\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n", + "chain = prompt | model | JsonOutputParser()\n", + "chain.invoke({\"input\": \"what's thirteen times 4\"})" + ] + }, + { + "cell_type": "markdown", + "id": "8e29dd4c-8eb5-457f-92d1-8add076404dc", + "metadata": {}, + "source": [ + "## Invoking the tool\n", + "\n", + "We can invoke the tool as part of the chain by passing along the model-generated \"arguments\" to it:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0555b384-fde6-4404-86e0-7ea199003d58", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "52" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from operator import itemgetter\n", + "\n", + "chain = prompt | model | JsonOutputParser() | itemgetter(\"arguments\") | multiply\n", + "chain.invoke({\"input\": \"what's thirteen times 4\"})" + ] + }, + { + "cell_type": "markdown", + "id": "8d60b2cb-6ce0-48fc-8d18-d2337161a53d", + "metadata": {}, + "source": [ + "## Choosing from multiple tools\n", + "\n", + "Suppose we have multiple tools we want the chain to be able to choose from:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "95c86d32-ee45-4c87-a28c-14eff19b49e9", + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def add(first_int: int, second_int: int) -> int:\n", + " \"Add two integers.\"\n", + " return first_int + second_int\n", + "\n", + "\n", + "@tool\n", + "def exponentiate(base: int, exponent: int) -> int:\n", + " \"Exponentiate the base to the exponent power.\"\n", + " return base**exponent" + ] + }, + { + "cell_type": "markdown", + "id": "748405ff-4c85-4bd7-82e1-30458b5a4106", + "metadata": {}, + "source": [ + "With function calling, we can do this like so:" + ] + }, + { + "cell_type": "markdown", + "id": "eb3aa89e-40e1-45ec-b1f3-ab28cfc8e42d", + "metadata": {}, + "source": [ + "If we want to run the model selected tool, we can do so using a function that returns the tool based on the model output. Specifically, our function will action return it's own subchain that gets the \"arguments\" part of the model output and passes it to the chosen tool:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "db254773-5b8e-43d0-aabe-c21566c154cd", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [add, exponentiate, multiply]\n", + "\n", + "\n", + "def tool_chain(model_output):\n", + " tool_map = {tool.name: tool for tool in tools}\n", + " chosen_tool = tool_map[model_output[\"name\"]]\n", + " return itemgetter(\"arguments\") | chosen_tool" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ad9f5cff-b86a-45fc-9ce4-b0aa9025a378", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1135" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rendered_tools = render_text_description(tools)\n", + "system_prompt = f\"\"\"You are an assistant that has access to the following set of tools. Here are the names and descriptions for each tool:\n", + "\n", + "{rendered_tools}\n", + "\n", + "Given the user input, return the name and input of the tool to use. Return your response as a JSON blob with 'name' and 'arguments' keys.\"\"\"\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [(\"system\", system_prompt), (\"user\", \"{input}\")]\n", + ")\n", + "\n", + "chain = prompt | model | JsonOutputParser() | tool_chain\n", + "chain.invoke({\"input\": \"what's 3 plus 1132\"})" + ] + }, + { + "cell_type": "markdown", + "id": "b4a9c5aa-f60a-4017-af6f-1ff6e04bfb61", + "metadata": {}, + "source": [ + "## Returning tool inputs\n", + "\n", + "It can be helpful to return not only tool outputs but also tool inputs. We can easily do this with LCEL by `RunnablePassthrough.assign`-ing the tool output. This will take whatever the input is to the RunnablePassrthrough components (assumed to be a dictionary) and add a key to it while still passing through everything that's currently in the input:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "45404406-859d-4caa-8b9d-5838162c80a0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'add',\n", + " 'arguments': {'first_int': 3, 'second_int': 1132},\n", + " 'output': 1135}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "chain = (\n", + " prompt | model | JsonOutputParser() | RunnablePassthrough.assign(output=tool_chain)\n", + ")\n", + "chain.invoke({\"input\": \"what's 3 plus 1132\"})" + ] + } + ], + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/vectorstore_retriever.ipynb b/docs/versioned_docs/version-0.2.x/how_to/vectorstore_retriever.ipynb new file mode 100644 index 0000000000000..372222666be73 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/vectorstore_retriever.ipynb @@ -0,0 +1,211 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "ee14951b", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 0\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "105cddce", + "metadata": {}, + "source": [ + "# How to use a vectorstore as a retriever\n", + "\n", + "A vector store retriever is a retriever that uses a vector store to retrieve documents. It is a lightweight wrapper around the vector store class to make it conform to the retriever interface.\n", + "It uses the search methods implemented by a vector store, like similarity search and MMR, to query the texts in the vector store.\n", + "\n", + "Once you construct a vector store, it's very easy to construct a retriever. Let's walk through an example.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "103dbfe3", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.document_loaders import TextLoader\n", + "\n", + "loader = TextLoader(\"../../state_of_the_union.txt\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "174e3c69", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import FAISS\n", + "from langchain_openai import OpenAIEmbeddings\n", + "from langchain_text_splitters import CharacterTextSplitter\n", + "\n", + "documents = loader.load()\n", + "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", + "texts = text_splitter.split_documents(documents)\n", + "embeddings = OpenAIEmbeddings()\n", + "db = FAISS.from_documents(texts, embeddings)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "52df5f55", + "metadata": {}, + "outputs": [], + "source": [ + "retriever = db.as_retriever()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "32334fda", + "metadata": {}, + "outputs": [], + "source": [ + "docs = retriever.get_relevant_documents(\"what did he say about ketanji brown jackson\")" + ] + }, + { + "cell_type": "markdown", + "id": "fd7b19f0", + "metadata": {}, + "source": [ + "## Maximum marginal relevance retrieval\n", + "By default, the vector store retriever uses similarity search. If the underlying vector store supports maximum marginal relevance search, you can specify that as the search type.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b286ac04", + "metadata": {}, + "outputs": [], + "source": [ + "retriever = db.as_retriever(search_type=\"mmr\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "07f937f7", + "metadata": {}, + "outputs": [], + "source": [ + "docs = retriever.get_relevant_documents(\"what did he say about ketanji brown jackson\")" + ] + }, + { + "cell_type": "markdown", + "id": "6ce77789", + "metadata": {}, + "source": [ + "\n", + "## Similarity score threshold retrieval\n", + "\n", + "You can also set a retrieval method that sets a similarity score threshold and only returns documents with a score above that threshold." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "dbb38a03", + "metadata": {}, + "outputs": [], + "source": [ + "retriever = db.as_retriever(\n", + " search_type=\"similarity_score_threshold\", search_kwargs={\"score_threshold\": 0.5}\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "56f6c9ae", + "metadata": {}, + "outputs": [], + "source": [ + "docs = retriever.get_relevant_documents(\"what did he say about ketanji brown jackson\")" + ] + }, + { + "cell_type": "markdown", + "id": "329f5b26", + "metadata": {}, + "source": [ + "\n", + "## Specifying top k\n", + "You can also specify search kwargs like `k` to use when doing retrieval.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d712c91d", + "metadata": {}, + "outputs": [], + "source": [ + "retriever = db.as_retriever(search_kwargs={\"k\": 1})" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "a79b573b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docs = retriever.get_relevant_documents(\"what did he say about ketanji brown jackson\")\n", + "len(docs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d3b34eb", + "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 +} diff --git a/docs/versioned_docs/version-0.2.x/how_to/vectorstores.mdx b/docs/versioned_docs/version-0.2.x/how_to/vectorstores.mdx new file mode 100644 index 0000000000000..66775203d486a --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/how_to/vectorstores.mdx @@ -0,0 +1,178 @@ +# How to create and query vector stores + +:::info +Head to [Integrations](/docs/integrations/vectorstores/) for documentation on built-in integrations with 3rd-party vector stores. +::: + +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. + +## Get started + +This guide showcases basic functionality related to vector stores. A key part of working with vector stores is creating the vector to put in them, +which is usually created via embeddings. Therefore, it is recommended that you familiarize yourself with the [text embedding model interfaces](/docs/how_to/embed_text) before diving into this. + +Before using the vectorstore at all, we need to load some data and initialize an embedding model. + +We want to use OpenAIEmbeddings so we have to get the OpenAI API Key. + +```python +import os +import getpass + +os.environ['OPENAI_API_KEY'] = getpass.getpass('OpenAI API Key:') +``` + +```python +from langchain_community.document_loaders import TextLoader +from langchain_openai import OpenAIEmbeddings +from langchain_text_splitters import CharacterTextSplitter + +# Load the document, split it into chunks, embed each chunk and load it into the vector store. +raw_documents = TextLoader('state_of_the_union.txt').load() +text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) +documents = text_splitter.split_documents(raw_documents) +``` + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +There are many great vector store options, here are a few that are free, open-source, and run entirely on your local machine. Review all integrations for many great hosted offerings. + + + + + +This walkthrough uses the `chroma` vector database, which runs on your local machine as a library. + +```bash +pip install langchain-chroma +``` + +```python +from langchain_chroma import Chroma + +db = Chroma.from_documents(documents, OpenAIEmbeddings()) +``` + + + + +This walkthrough uses the `FAISS` vector database, which makes use of the Facebook AI Similarity Search (FAISS) library. + +```bash +pip install faiss-cpu +``` + +```python +from langchain_community.vectorstores import FAISS + +db = FAISS.from_documents(documents, OpenAIEmbeddings()) +``` + + + + +This notebook shows how to use functionality related to the LanceDB vector database based on the Lance data format. + +```bash +pip install lancedb +``` + +```python +from langchain_community.vectorstores import LanceDB + +import lancedb + +db = lancedb.connect("/tmp/lancedb") +table = db.create_table( + "my_table", + data=[ + { + "vector": embeddings.embed_query("Hello World"), + "text": "Hello World", + "id": "1", + } + ], + mode="overwrite", +) +db = LanceDB.from_documents(documents, OpenAIEmbeddings()) +``` + + + + + +## Similarity search + +All vectorstores expose a `similarity_search` method. +This will take incoming documents, create an embedding of them, and then find all documents with the most similar embedding. + +```python +query = "What did the president say about Ketanji Brown Jackson" +docs = db.similarity_search(query) +print(docs[0].page_content) +``` + + + +``` + Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. + + Tonight, I’d 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. + + One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. + + And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence. +``` + + + +### Similarity search by vector + +It is also possible to do a search for documents similar to a given embedding vector using `similarity_search_by_vector` which accepts an embedding vector as a parameter instead of a string. + +```python +embedding_vector = OpenAIEmbeddings().embed_query(query) +docs = db.similarity_search_by_vector(embedding_vector) +print(docs[0].page_content) +``` + + + +``` + Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. + + Tonight, I’d 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. + + One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. + + And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence. +``` + + + +## Async Operations + + +Vector stores are usually run as a separate service that requires some IO operations, and therefore they might be called asynchronously. That gives performance benefits as you don't waste time waiting for responses from external services. That might also be important if you work with an asynchronous framework, such as [FastAPI](https://fastapi.tiangolo.com/). + +LangChain supports async operation on vector stores. All the methods might be called using their async counterparts, with the prefix `a`, meaning `async`. + +```python +docs = await db.asimilarity_search(query) +docs +``` + + + +``` +[Document(page_content='Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n\nTonight, I’d 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. \n\nAnd I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.', metadata={'source': 'state_of_the_union.txt'}), + Document(page_content='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 she’s been nominated, she’s received a broad range of support—from the Fraternal Order of Police to former judges appointed by Democrats and Republicans. \n\nAnd if we are to advance liberty and justice, we need to secure the Border and fix the immigration system. \n\nWe can do both. At our border, we’ve installed new technology like cutting-edge scanners to better detect drug smuggling. \n\nWe’ve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \n\nWe’re putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \n\nWe’re securing commitments and supporting partners in South and Central America to host more refugees and secure their own borders.', metadata={'source': 'state_of_the_union.txt'}), + Document(page_content='And for our LGBTQ+ Americans, let’s finally get the bipartisan Equality Act to my desk. The onslaught of state laws targeting transgender Americans and their families is wrong. \n\nAs 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\nWhile it often appears that we never agree, that isn’t 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\nAnd soon, we’ll 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\nSo tonight I’m offering a Unity Agenda for the Nation. Four big things we can do together. \n\nFirst, beat the opioid epidemic.', metadata={'source': 'state_of_the_union.txt'}), + Document(page_content='Tonight, I’m announcing a crackdown on these companies overcharging American businesses and consumers. \n\nAnd as Wall Street firms take over more nursing homes, quality in those homes has gone down and costs have gone up. \n\nThat ends on my watch. \n\nMedicare is going to set higher standards for nursing homes and make sure your loved ones get the care they deserve and expect. \n\nWe’ll 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\nLet’s pass the Paycheck Fairness Act and paid leave. \n\nRaise the minimum wage to $15 an hour and extend the Child Tax Credit, so no one has to raise a family in poverty. \n\nLet’s increase Pell Grants and increase our historic support of HBCUs, and invest in what Jill—our First Lady who teaches full-time—calls America’s best-kept secret: community colleges.', metadata={'source': 'state_of_the_union.txt'})] +``` + + \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/installation.mdx b/docs/versioned_docs/version-0.2.x/installation.mdx new file mode 100644 index 0000000000000..e84ff564604e1 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/installation.mdx @@ -0,0 +1,89 @@ +--- +sidebar_position: 2 +--- + +# Installation + +## Official release + +To install LangChain run: + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from "@theme/CodeBlock"; + + + + pip install langchain + + + conda install langchain -c conda-forge + + + +This will install the bare minimum requirements of LangChain. +A lot of the value of LangChain comes when integrating it with various model providers, datastores, etc. +By default, the dependencies needed to do that are NOT installed. You will need to install the dependencies for specific integrations separately. + +## From source + +If you want to install from source, you can do so by cloning the repo and be sure that the directory is `PATH/TO/REPO/langchain/libs/langchain` running: + +```bash +pip install -e . +``` + +## LangChain core +The `langchain-core` package contains base abstractions that the rest of the LangChain ecosystem uses, along with the LangChain Expression Language. It is automatically installed by `langchain`, but can also be used separately. Install with: + +```bash +pip install langchain-core +``` + +## LangChain community +The `langchain-community` package contains third-party integrations. It is automatically installed by `langchain`, but can also be used separately. Install with: + +```bash +pip install langchain-community +``` + +## LangChain experimental +The `langchain-experimental` package holds experimental LangChain code, intended for research and experimental uses. +Install with: + +```bash +pip install langchain-experimental +``` + +## LangGraph +`langgraph` is a library for building stateful, multi-actor applications with LLMs, built on top of (and intended to be used with) LangChain. +Install with: + +```bash +pip install langgraph +``` +## LangServe +LangServe helps developers deploy LangChain runnables and chains as a REST API. +LangServe is automatically installed by LangChain CLI. +If not using LangChain CLI, install with: + +```bash +pip install "langserve[all]" +``` +for both client and server dependencies. Or `pip install "langserve[client]"` for client code, and `pip install "langserve[server]"` for server code. + +## LangChain CLI +The LangChain CLI is useful for working with LangChain templates and other LangServe projects. +Install with: + +```bash +pip install langchain-cli +``` + +## LangSmith SDK +The LangSmith SDK is automatically installed by LangChain. +If not using LangChain, install with: + +```bash +pip install langsmith +``` diff --git a/docs/versioned_docs/version-0.2.x/integrations/adapters/_category_.yml b/docs/versioned_docs/version-0.2.x/integrations/adapters/_category_.yml new file mode 100644 index 0000000000000..f2009d077dc07 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/adapters/_category_.yml @@ -0,0 +1 @@ +label: 'Adapters' diff --git a/docs/versioned_docs/version-0.2.x/integrations/adapters/openai-old.ipynb b/docs/versioned_docs/version-0.2.x/integrations/adapters/openai-old.ipynb new file mode 100644 index 0000000000000..85add30845404 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/adapters/openai-old.ipynb @@ -0,0 +1,285 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "700a516b", + "metadata": {}, + "source": [ + "# OpenAI Adapter(Old)\n", + "\n", + "**Please ensure OpenAI library is less than 1.0.0; otherwise, refer to the newer doc [OpenAI Adapter](/docs/integrations/adapters/openai/).**\n", + "\n", + "A lot of people get started with OpenAI but want to explore other models. LangChain's integrations with many model providers make this easy to do so. While LangChain has it's own message and model APIs, we've also made it as easy as possible to explore other models by exposing an adapter to adapt LangChain models to the OpenAI api.\n", + "\n", + "At the moment this only deals with output and does not return other information (token counts, stop reasons, etc)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6017f26a", + "metadata": {}, + "outputs": [], + "source": [ + "import openai\n", + "from langchain_community.adapters import openai as lc_openai" + ] + }, + { + "cell_type": "markdown", + "id": "b522ceda", + "metadata": {}, + "source": [ + "## ChatCompletion.create" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "1d22eb61", + "metadata": {}, + "outputs": [], + "source": [ + "messages = [{\"role\": \"user\", \"content\": \"hi\"}]" + ] + }, + { + "cell_type": "markdown", + "id": "d550d3ad", + "metadata": {}, + "source": [ + "Original OpenAI call" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "012d81ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'role': 'assistant', 'content': 'Hello! How can I assist you today?'}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = openai.ChatCompletion.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\", temperature=0\n", + ")\n", + "result[\"choices\"][0][\"message\"].to_dict_recursive()" + ] + }, + { + "cell_type": "markdown", + "id": "db5b5500", + "metadata": {}, + "source": [ + "LangChain OpenAI wrapper call" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "c67a5ac8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'role': 'assistant', 'content': 'Hello! How can I assist you today?'}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lc_result = lc_openai.ChatCompletion.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\", temperature=0\n", + ")\n", + "lc_result[\"choices\"][0][\"message\"]" + ] + }, + { + "cell_type": "markdown", + "id": "034ba845", + "metadata": {}, + "source": [ + "Swapping out model providers" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "f7c94827", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'role': 'assistant', 'content': ' Hello!'}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lc_result = lc_openai.ChatCompletion.create(\n", + " messages=messages, model=\"claude-2\", temperature=0, provider=\"ChatAnthropic\"\n", + ")\n", + "lc_result[\"choices\"][0][\"message\"]" + ] + }, + { + "cell_type": "markdown", + "id": "cb3f181d", + "metadata": {}, + "source": [ + "## ChatCompletion.stream" + ] + }, + { + "cell_type": "markdown", + "id": "f7b8cd18", + "metadata": {}, + "source": [ + "Original OpenAI call" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "fd8cb1ea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'role': 'assistant', 'content': ''}\n", + "{'content': 'Hello'}\n", + "{'content': '!'}\n", + "{'content': ' How'}\n", + "{'content': ' can'}\n", + "{'content': ' I'}\n", + "{'content': ' assist'}\n", + "{'content': ' you'}\n", + "{'content': ' today'}\n", + "{'content': '?'}\n", + "{}\n" + ] + } + ], + "source": [ + "for c in openai.ChatCompletion.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\", temperature=0, stream=True\n", + "):\n", + " print(c[\"choices\"][0][\"delta\"].to_dict_recursive())" + ] + }, + { + "cell_type": "markdown", + "id": "0b2a076b", + "metadata": {}, + "source": [ + "LangChain OpenAI wrapper call" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "9521218c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'role': 'assistant', 'content': ''}\n", + "{'content': 'Hello'}\n", + "{'content': '!'}\n", + "{'content': ' How'}\n", + "{'content': ' can'}\n", + "{'content': ' I'}\n", + "{'content': ' assist'}\n", + "{'content': ' you'}\n", + "{'content': ' today'}\n", + "{'content': '?'}\n", + "{}\n" + ] + } + ], + "source": [ + "for c in lc_openai.ChatCompletion.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\", temperature=0, stream=True\n", + "):\n", + " print(c[\"choices\"][0][\"delta\"])" + ] + }, + { + "cell_type": "markdown", + "id": "0fc39750", + "metadata": {}, + "source": [ + "Swapping out model providers" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "68f0214e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'role': 'assistant', 'content': ' Hello'}\n", + "{'content': '!'}\n", + "{}\n" + ] + } + ], + "source": [ + "for c in lc_openai.ChatCompletion.create(\n", + " messages=messages,\n", + " model=\"claude-2\",\n", + " temperature=0,\n", + " stream=True,\n", + " provider=\"ChatAnthropic\",\n", + "):\n", + " print(c[\"choices\"][0][\"delta\"])" + ] + } + ], + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/integrations/adapters/openai.ipynb b/docs/versioned_docs/version-0.2.x/integrations/adapters/openai.ipynb new file mode 100644 index 0000000000000..05fe49fda6cc7 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/adapters/openai.ipynb @@ -0,0 +1,318 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "700a516b", + "metadata": {}, + "source": [ + "# OpenAI Adapter\n", + "\n", + "**Please ensure OpenAI library is version 1.0.0 or higher; otherwise, refer to the older doc [OpenAI Adapter(Old)](/docs/integrations/adapters/openai-old/).**\n", + "\n", + "A lot of people get started with OpenAI but want to explore other models. LangChain's integrations with many model providers make this easy to do so. While LangChain has it's own message and model APIs, we've also made it as easy as possible to explore other models by exposing an adapter to adapt LangChain models to the OpenAI api.\n", + "\n", + "At the moment this only deals with output and does not return other information (token counts, stop reasons, etc)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6017f26a", + "metadata": {}, + "outputs": [], + "source": [ + "import openai\n", + "from langchain_community.adapters import openai as lc_openai" + ] + }, + { + "cell_type": "markdown", + "id": "b522ceda", + "metadata": {}, + "source": [ + "## chat.completions.create" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1d22eb61", + "metadata": {}, + "outputs": [], + "source": [ + "messages = [{\"role\": \"user\", \"content\": \"hi\"}]" + ] + }, + { + "cell_type": "markdown", + "id": "d550d3ad", + "metadata": {}, + "source": [ + "Original OpenAI call" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "012d81ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'content': 'Hello! How can I assist you today?',\n", + " 'role': 'assistant',\n", + " 'function_call': None,\n", + " 'tool_calls': None}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = openai.chat.completions.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\", temperature=0\n", + ")\n", + "result.choices[0].message.model_dump()" + ] + }, + { + "cell_type": "markdown", + "id": "db5b5500", + "metadata": {}, + "source": [ + "LangChain OpenAI wrapper call" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c67a5ac8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'role': 'assistant', 'content': 'Hello! How can I help you today?'}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lc_result = lc_openai.chat.completions.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\", temperature=0\n", + ")\n", + "\n", + "lc_result.choices[0].message # Attribute access" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "37a6e461-8608-47f6-ac45-12ad753c062a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'role': 'assistant', 'content': 'Hello! How can I help you today?'}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lc_result[\"choices\"][0][\"message\"] # Also compatible with index access" + ] + }, + { + "cell_type": "markdown", + "id": "034ba845", + "metadata": {}, + "source": [ + "Swapping out model providers" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f7c94827", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'role': 'assistant', 'content': 'Hello! How can I assist you today?'}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lc_result = lc_openai.chat.completions.create(\n", + " messages=messages, model=\"claude-2\", temperature=0, provider=\"ChatAnthropic\"\n", + ")\n", + "lc_result.choices[0].message" + ] + }, + { + "cell_type": "markdown", + "id": "cb3f181d", + "metadata": {}, + "source": [ + "## chat.completions.stream" + ] + }, + { + "cell_type": "markdown", + "id": "f7b8cd18", + "metadata": {}, + "source": [ + "Original OpenAI call" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "fd8cb1ea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'content': '', 'function_call': None, 'role': 'assistant', 'tool_calls': None}\n", + "{'content': 'Hello', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': '!', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': ' How', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': ' can', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': ' I', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': ' assist', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': ' you', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': ' today', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': '?', 'function_call': None, 'role': None, 'tool_calls': None}\n", + "{'content': None, 'function_call': None, 'role': None, 'tool_calls': None}\n" + ] + } + ], + "source": [ + "for c in openai.chat.completions.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\", temperature=0, stream=True\n", + "):\n", + " print(c.choices[0].delta.model_dump())" + ] + }, + { + "cell_type": "markdown", + "id": "0b2a076b", + "metadata": {}, + "source": [ + "LangChain OpenAI wrapper call" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9521218c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'role': 'assistant', 'content': ''}\n", + "{'content': 'Hello'}\n", + "{'content': '!'}\n", + "{'content': ' How'}\n", + "{'content': ' can'}\n", + "{'content': ' I'}\n", + "{'content': ' assist'}\n", + "{'content': ' you'}\n", + "{'content': ' today'}\n", + "{'content': '?'}\n", + "{}\n" + ] + } + ], + "source": [ + "for c in lc_openai.chat.completions.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\", temperature=0, stream=True\n", + "):\n", + " print(c.choices[0].delta)" + ] + }, + { + "cell_type": "markdown", + "id": "0fc39750", + "metadata": {}, + "source": [ + "Swapping out model providers" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "68f0214e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'role': 'assistant', 'content': ''}\n", + "{'content': 'Hello'}\n", + "{'content': '!'}\n", + "{'content': ' How'}\n", + "{'content': ' can'}\n", + "{'content': ' I'}\n", + "{'content': ' assist'}\n", + "{'content': ' you'}\n", + "{'content': ' today'}\n", + "{'content': '?'}\n", + "{}\n" + ] + } + ], + "source": [ + "for c in lc_openai.chat.completions.create(\n", + " messages=messages,\n", + " model=\"claude-2\",\n", + " temperature=0,\n", + " stream=True,\n", + " provider=\"ChatAnthropic\",\n", + "):\n", + " print(c[\"choices\"][0][\"delta\"])" + ] + } + ], + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/integrations/callbacks/argilla.ipynb b/docs/versioned_docs/version-0.2.x/integrations/callbacks/argilla.ipynb new file mode 100644 index 0000000000000..fc656f9687a3b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/callbacks/argilla.ipynb @@ -0,0 +1,420 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Argilla\n", + "\n", + ">[Argilla](https://argilla.io/) is an open-source data curation platform for LLMs.\n", + "> Using Argilla, everyone can build robust language models through faster data curation \n", + "> using both human and machine feedback. We provide support for each step in the MLOps cycle, \n", + "> from data labeling to model monitoring.\n", + "\n", + "\n", + " \"Open\n", + "" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this guide we will demonstrate how to track the inputs and responses of your LLM to generate a dataset in Argilla, using the `ArgillaCallbackHandler`.\n", + "\n", + "It's useful to keep track of the inputs and outputs of your LLMs to generate datasets for future fine-tuning. This is especially useful when you're using a LLM to generate data for a specific task, such as question answering, summarization, or translation." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Installation and Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai argilla" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Getting API Credentials\n", + "\n", + "To get the Argilla API credentials, follow the next steps:\n", + "\n", + "1. Go to your Argilla UI.\n", + "2. Click on your profile picture and go to \"My settings\".\n", + "3. Then copy the API Key.\n", + "\n", + "In Argilla the API URL will be the same as the URL of your Argilla UI.\n", + "\n", + "To get the OpenAI API credentials, please visit https://platform.openai.com/account/api-keys" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"ARGILLA_API_URL\"] = \"...\"\n", + "os.environ[\"ARGILLA_API_KEY\"] = \"...\"\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = \"...\"" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup Argilla\n", + "\n", + "To use the `ArgillaCallbackHandler` we will need to create a new `FeedbackDataset` in Argilla to keep track of your LLM experiments. To do so, please use the following code:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import argilla as rg" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from packaging.version import parse as parse_version\n", + "\n", + "if parse_version(rg.__version__) < parse_version(\"1.8.0\"):\n", + " raise RuntimeError(\n", + " \"`FeedbackDataset` is only available in Argilla v1.8.0 or higher, please \"\n", + " \"upgrade `argilla` as `pip install argilla --upgrade`.\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = rg.FeedbackDataset(\n", + " fields=[\n", + " rg.TextField(name=\"prompt\"),\n", + " rg.TextField(name=\"response\"),\n", + " ],\n", + " questions=[\n", + " rg.RatingQuestion(\n", + " name=\"response-rating\",\n", + " description=\"How would you rate the quality of the response?\",\n", + " values=[1, 2, 3, 4, 5],\n", + " required=True,\n", + " ),\n", + " rg.TextQuestion(\n", + " name=\"response-feedback\",\n", + " description=\"What feedback do you have for the response?\",\n", + " required=False,\n", + " ),\n", + " ],\n", + " guidelines=\"You're asked to rate the quality of the response and provide feedback.\",\n", + ")\n", + "\n", + "rg.init(\n", + " api_url=os.environ[\"ARGILLA_API_URL\"],\n", + " api_key=os.environ[\"ARGILLA_API_KEY\"],\n", + ")\n", + "\n", + "dataset.push_to_argilla(\"langchain-dataset\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> 📌 NOTE: at the moment, just the prompt-response pairs are supported as `FeedbackDataset.fields`, so the `ArgillaCallbackHandler` will just track the prompt i.e. the LLM input, and the response i.e. the LLM output." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tracking" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use the `ArgillaCallbackHandler` you can either use the following code, or just reproduce one of the examples presented in the following sections." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.callbacks.argilla_callback import ArgillaCallbackHandler\n", + "\n", + "argilla_callback = ArgillaCallbackHandler(\n", + " dataset_name=\"langchain-dataset\",\n", + " api_url=os.environ[\"ARGILLA_API_URL\"],\n", + " api_key=os.environ[\"ARGILLA_API_KEY\"],\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scenario 1: Tracking an LLM\n", + "\n", + "First, let's just run a single LLM a few times and capture the resulting prompt-response pairs in Argilla." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LLMResult(generations=[[Generation(text='\\n\\nQ: What did the fish say when he hit the wall? \\nA: Dam.', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\\n\\nThe Moon \\n\\nThe moon is high in the midnight sky,\\nSparkling like a star above.\\nThe night so peaceful, so serene,\\nFilling up the air with love.\\n\\nEver changing and renewing,\\nA never-ending light of grace.\\nThe moon remains a constant view,\\nA reminder of life’s gentle pace.\\n\\nThrough time and space it guides us on,\\nA never-fading beacon of hope.\\nThe moon shines down on us all,\\nAs it continues to rise and elope.', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\\n\\nQ. What did one magnet say to the other magnet?\\nA. \"I find you very attractive!\"', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text=\"\\n\\nThe world is charged with the grandeur of God.\\nIt will flame out, like shining from shook foil;\\nIt gathers to a greatness, like the ooze of oil\\nCrushed. Why do men then now not reck his rod?\\n\\nGenerations have trod, have trod, have trod;\\nAnd all is seared with trade; bleared, smeared with toil;\\nAnd wears man's smudge and shares man's smell: the soil\\nIs bare now, nor can foot feel, being shod.\\n\\nAnd for all this, nature is never spent;\\nThere lives the dearest freshness deep down things;\\nAnd though the last lights off the black West went\\nOh, morning, at the brown brink eastward, springs —\\n\\nBecause the Holy Ghost over the bent\\nWorld broods with warm breast and with ah! bright wings.\\n\\n~Gerard Manley Hopkins\", generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\\n\\nQ: What did one ocean say to the other ocean?\\nA: Nothing, they just waved.', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text=\"\\n\\nA poem for you\\n\\nOn a field of green\\n\\nThe sky so blue\\n\\nA gentle breeze, the sun above\\n\\nA beautiful world, for us to love\\n\\nLife is a journey, full of surprise\\n\\nFull of joy and full of surprise\\n\\nBe brave and take small steps\\n\\nThe future will be revealed with depth\\n\\nIn the morning, when dawn arrives\\n\\nA fresh start, no reason to hide\\n\\nSomewhere down the road, there's a heart that beats\\n\\nBelieve in yourself, you'll always succeed.\", generation_info={'finish_reason': 'stop', 'logprobs': None})]], llm_output={'token_usage': {'completion_tokens': 504, 'total_tokens': 528, 'prompt_tokens': 24}, 'model_name': 'text-davinci-003'})" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.callbacks.stdout import StdOutCallbackHandler\n", + "from langchain_openai import OpenAI\n", + "\n", + "argilla_callback = ArgillaCallbackHandler(\n", + " dataset_name=\"langchain-dataset\",\n", + " api_url=os.environ[\"ARGILLA_API_URL\"],\n", + " api_key=os.environ[\"ARGILLA_API_KEY\"],\n", + ")\n", + "callbacks = [StdOutCallbackHandler(), argilla_callback]\n", + "\n", + "llm = OpenAI(temperature=0.9, callbacks=callbacks)\n", + "llm.generate([\"Tell me a joke\", \"Tell me a poem\"] * 3)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Argilla UI with LangChain LLM input-response](https://docs.argilla.io/en/latest/_images/llm.png)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scenario 2: Tracking an LLM in a chain\n", + "\n", + "Then we can create a chain using a prompt template, and then track the initial prompt and the final response in Argilla." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n", + "Prompt after formatting:\n", + "\u001b[32;1m\u001b[1;3mYou are a playwright. Given the title of play, it is your job to write a synopsis for that title.\n", + "Title: Documentary about Bigfoot in Paris\n", + "Playwright: This is a synopsis for the above play:\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "[{'text': \"\\n\\nDocumentary about Bigfoot in Paris focuses on the story of a documentary filmmaker and their search for evidence of the legendary Bigfoot creature in the city of Paris. The play follows the filmmaker as they explore the city, meeting people from all walks of life who have had encounters with the mysterious creature. Through their conversations, the filmmaker unravels the story of Bigfoot and finds out the truth about the creature's presence in Paris. As the story progresses, the filmmaker learns more and more about the mysterious creature, as well as the different perspectives of the people living in the city, and what they think of the creature. In the end, the filmmaker's findings lead them to some surprising and heartwarming conclusions about the creature's existence and the importance it holds in the lives of the people in Paris.\"}]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.chains import LLMChain\n", + "from langchain.prompts import PromptTemplate\n", + "from langchain_core.callbacks.stdout import StdOutCallbackHandler\n", + "from langchain_openai import OpenAI\n", + "\n", + "argilla_callback = ArgillaCallbackHandler(\n", + " dataset_name=\"langchain-dataset\",\n", + " api_url=os.environ[\"ARGILLA_API_URL\"],\n", + " api_key=os.environ[\"ARGILLA_API_KEY\"],\n", + ")\n", + "callbacks = [StdOutCallbackHandler(), argilla_callback]\n", + "llm = OpenAI(temperature=0.9, callbacks=callbacks)\n", + "\n", + "template = \"\"\"You are a playwright. Given the title of play, it is your job to write a synopsis for that title.\n", + "Title: {title}\n", + "Playwright: This is a synopsis for the above play:\"\"\"\n", + "prompt_template = PromptTemplate(input_variables=[\"title\"], template=template)\n", + "synopsis_chain = LLMChain(llm=llm, prompt=prompt_template, callbacks=callbacks)\n", + "\n", + "test_prompts = [{\"title\": \"Documentary about Bigfoot in Paris\"}]\n", + "synopsis_chain.apply(test_prompts)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Argilla UI with LangChain Chain input-response](https://docs.argilla.io/en/latest/_images/chain.png)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scenario 3: Using an Agent with Tools\n", + "\n", + "Finally, as a more advanced workflow, you can create an agent that uses some tools. So that `ArgillaCallbackHandler` will keep track of the input and the output, but not about the intermediate steps/thoughts, so that given a prompt we log the original prompt and the final response to that given prompt." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Note that for this scenario we'll be using Google Search API (Serp API) so you will need to both install `google-search-results` as `pip install google-search-results`, and to set the Serp API Key as `os.environ[\"SERPAPI_API_KEY\"] = \"...\"` (you can find it at https://serpapi.com/dashboard), otherwise the example below won't work." + ] + }, + { + "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;3m I need to answer a historical question\n", + "Action: Search\n", + "Action Input: \"who was the first president of the United States of America\" \u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mGeorge Washington\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m George Washington was the first president\n", + "Final Answer: George Washington was the first president of the United States of America.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "'George Washington was the first president of the United States of America.'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.agents import AgentType, initialize_agent, load_tools\n", + "from langchain_core.callbacks.stdout import StdOutCallbackHandler\n", + "from langchain_openai import OpenAI\n", + "\n", + "argilla_callback = ArgillaCallbackHandler(\n", + " dataset_name=\"langchain-dataset\",\n", + " api_url=os.environ[\"ARGILLA_API_URL\"],\n", + " api_key=os.environ[\"ARGILLA_API_KEY\"],\n", + ")\n", + "callbacks = [StdOutCallbackHandler(), argilla_callback]\n", + "llm = OpenAI(temperature=0.9, callbacks=callbacks)\n", + "\n", + "tools = load_tools([\"serpapi\"], llm=llm, callbacks=callbacks)\n", + "agent = initialize_agent(\n", + " tools,\n", + " llm,\n", + " agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,\n", + " callbacks=callbacks,\n", + ")\n", + "agent.run(\"Who was the first president of the United States of America?\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Argilla UI with LangChain Agent input-response](https://docs.argilla.io/en/latest/_images/agent.png)" + ] + } + ], + "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.12" + }, + "vscode": { + "interpreter": { + "hash": "a53ebf4a859167383b364e7e7521d0add3c2dbbdecce4edf676e8c4634ff3fbb" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/versioned_docs/version-0.2.x/integrations/callbacks/comet_tracing.ipynb b/docs/versioned_docs/version-0.2.x/integrations/callbacks/comet_tracing.ipynb new file mode 100644 index 0000000000000..088786ed91e96 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/callbacks/comet_tracing.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5371a9bb", + "metadata": {}, + "source": [ + "# Comet Tracing\n", + "\n", + "There are two ways to trace your LangChains executions with Comet:\n", + "\n", + "1. Setting the `LANGCHAIN_COMET_TRACING` environment variable to \"true\". This is the recommended way.\n", + "2. Import the `CometTracer` manually and pass it explicitely." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17c04cc6-c93d-4b6c-a033-e897577f4ed1", + "metadata": { + "ExecuteTime": { + "end_time": "2023-05-18T12:47:46.580776Z", + "start_time": "2023-05-18T12:47:46.577833Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import comet_llm\n", + "\n", + "os.environ[\"LANGCHAIN_COMET_TRACING\"] = \"true\"\n", + "\n", + "# Connect to Comet if no API Key is set\n", + "comet_llm.init()\n", + "\n", + "# comet documentation to configure comet using env variables\n", + "# https://www.comet.com/docs/v2/api-and-sdk/llm-sdk/configuration/\n", + "# here we are configuring the comet project\n", + "os.environ[\"COMET_PROJECT_NAME\"] = \"comet-example-langchain-tracing\"\n", + "\n", + "from langchain.agents import AgentType, initialize_agent, load_tools\n", + "from langchain.llms import OpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b62cd48", + "metadata": { + "ExecuteTime": { + "end_time": "2023-05-18T12:47:47.445229Z", + "start_time": "2023-05-18T12:47:47.436424Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Agent run with tracing. Ensure that OPENAI_API_KEY is set appropriately to run this example.\n", + "\n", + "llm = OpenAI(temperature=0)\n", + "tools = load_tools([\"llm-math\"], llm=llm)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfa16b79-aa4b-4d41-a067-70d1f593f667", + "metadata": { + "ExecuteTime": { + "end_time": "2023-05-18T12:48:01.816137Z", + "start_time": "2023-05-18T12:47:49.109574Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "agent = initialize_agent(\n", + " tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True\n", + ")\n", + "\n", + "agent.run(\"What is 2 raised to .123243 power?\") # this should be traced\n", + "# An url for the chain like the following should print in your console:\n", + "# https://www.comet.com//\n", + "# The url can be used to view the LLM chain in Comet." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e212e7d", + "metadata": {}, + "outputs": [], + "source": [ + "# Now, we unset the environment variable and use a context manager.\n", + "if \"LANGCHAIN_COMET_TRACING\" in os.environ:\n", + " del os.environ[\"LANGCHAIN_COMET_TRACING\"]\n", + "\n", + "from langchain_community.callbacks.tracers.comet import CometTracer\n", + "\n", + "tracer = CometTracer()\n", + "\n", + "# Recreate the LLM, tools and agent and passing the callback to each of them\n", + "llm = OpenAI(temperature=0)\n", + "tools = load_tools([\"llm-math\"], llm=llm)\n", + "agent = initialize_agent(\n", + " tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True\n", + ")\n", + "\n", + "agent.run(\n", + " \"What is 2 raised to .123243 power?\", callbacks=[tracer]\n", + ") # this should be traced" + ] + } + ], + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/integrations/callbacks/confident.ipynb b/docs/versioned_docs/version-0.2.x/integrations/callbacks/confident.ipynb new file mode 100644 index 0000000000000..a5110206291ba --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/callbacks/confident.ipynb @@ -0,0 +1,309 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Confident\n", + "\n", + ">[DeepEval](https://confident-ai.com) package for unit testing LLMs.\n", + "> Using Confident, everyone can build robust language models through faster iterations\n", + "> using both unit testing and integration testing. We provide support for each step in the iteration\n", + "> from synthetic data creation to testing.\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this guide we will demonstrate how to test and measure LLMs in performance. We show how you can use our callback to measure performance and how you can define your own metric and log them into our dashboard.\n", + "\n", + "DeepEval also offers:\n", + "- How to generate synthetic data\n", + "- How to measure performance\n", + "- A dashboard to monitor and review results over time" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Installation and Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai deepeval langchain-chroma" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Getting API Credentials\n", + "\n", + "To get the DeepEval API credentials, follow the next steps:\n", + "\n", + "1. Go to https://app.confident-ai.com\n", + "2. Click on \"Organization\"\n", + "3. Copy the API Key.\n", + "\n", + "\n", + "When you log in, you will also be asked to set the `implementation` name. The implementation name is required to describe the type of implementation. (Think of what you want to call your project. We recommend making it descriptive.)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "!deepeval login" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup DeepEval\n", + "\n", + "You can, by default, use the `DeepEvalCallbackHandler` to set up the metrics you want to track. However, this has limited support for metrics at the moment (more to be added soon). It currently supports:\n", + "- [Answer Relevancy](https://docs.confident-ai.com/docs/measuring_llm_performance/answer_relevancy)\n", + "- [Bias](https://docs.confident-ai.com/docs/measuring_llm_performance/debias)\n", + "- [Toxicness](https://docs.confident-ai.com/docs/measuring_llm_performance/non_toxic)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from deepeval.metrics.answer_relevancy import AnswerRelevancy\n", + "\n", + "# Here we want to make sure the answer is minimally relevant\n", + "answer_relevancy_metric = AnswerRelevancy(minimum_score=0.5)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get Started" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use the `DeepEvalCallbackHandler`, we need the `implementation_name`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.callbacks.confident_callback import DeepEvalCallbackHandler\n", + "\n", + "deepeval_callback = DeepEvalCallbackHandler(\n", + " implementation_name=\"langchainQuickstart\", metrics=[answer_relevancy_metric]\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scenario 1: Feeding into LLM\n", + "\n", + "You can then feed it into your LLM with OpenAI." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LLMResult(generations=[[Generation(text='\\n\\nQ: What did the fish say when he hit the wall? \\nA: Dam.', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\\n\\nThe Moon \\n\\nThe moon is high in the midnight sky,\\nSparkling like a star above.\\nThe night so peaceful, so serene,\\nFilling up the air with love.\\n\\nEver changing and renewing,\\nA never-ending light of grace.\\nThe moon remains a constant view,\\nA reminder of life’s gentle pace.\\n\\nThrough time and space it guides us on,\\nA never-fading beacon of hope.\\nThe moon shines down on us all,\\nAs it continues to rise and elope.', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\\n\\nQ. What did one magnet say to the other magnet?\\nA. \"I find you very attractive!\"', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text=\"\\n\\nThe world is charged with the grandeur of God.\\nIt will flame out, like shining from shook foil;\\nIt gathers to a greatness, like the ooze of oil\\nCrushed. Why do men then now not reck his rod?\\n\\nGenerations have trod, have trod, have trod;\\nAnd all is seared with trade; bleared, smeared with toil;\\nAnd wears man's smudge and shares man's smell: the soil\\nIs bare now, nor can foot feel, being shod.\\n\\nAnd for all this, nature is never spent;\\nThere lives the dearest freshness deep down things;\\nAnd though the last lights off the black West went\\nOh, morning, at the brown brink eastward, springs —\\n\\nBecause the Holy Ghost over the bent\\nWorld broods with warm breast and with ah! bright wings.\\n\\n~Gerard Manley Hopkins\", generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\\n\\nQ: What did one ocean say to the other ocean?\\nA: Nothing, they just waved.', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text=\"\\n\\nA poem for you\\n\\nOn a field of green\\n\\nThe sky so blue\\n\\nA gentle breeze, the sun above\\n\\nA beautiful world, for us to love\\n\\nLife is a journey, full of surprise\\n\\nFull of joy and full of surprise\\n\\nBe brave and take small steps\\n\\nThe future will be revealed with depth\\n\\nIn the morning, when dawn arrives\\n\\nA fresh start, no reason to hide\\n\\nSomewhere down the road, there's a heart that beats\\n\\nBelieve in yourself, you'll always succeed.\", generation_info={'finish_reason': 'stop', 'logprobs': None})]], llm_output={'token_usage': {'completion_tokens': 504, 'total_tokens': 528, 'prompt_tokens': 24}, 'model_name': 'text-davinci-003'})" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_openai import OpenAI\n", + "\n", + "llm = OpenAI(\n", + " temperature=0,\n", + " callbacks=[deepeval_callback],\n", + " verbose=True,\n", + " openai_api_key=\"\",\n", + ")\n", + "output = llm.generate(\n", + " [\n", + " \"What is the best evaluation tool out there? (no bias at all)\",\n", + " ]\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can then check the metric if it was successful by calling the `is_successful()` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "answer_relevancy_metric.is_successful()\n", + "# returns True/False" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you have ran that, you should be able to see our dashboard below. \n", + "\n", + "![Dashboard](https://docs.confident-ai.com/assets/images/dashboard-screenshot-b02db73008213a211b1158ff052d969e.png)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scenario 2: Tracking an LLM in a chain without callbacks\n", + "\n", + "To track an LLM in a chain without callbacks, you can plug into it at the end.\n", + "\n", + "We can start by defining a simple chain as shown below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "from langchain.chains import RetrievalQA\n", + "from langchain_chroma import Chroma\n", + "from langchain_community.document_loaders import TextLoader\n", + "from langchain_openai import OpenAI, OpenAIEmbeddings\n", + "from langchain_text_splitters import CharacterTextSplitter\n", + "\n", + "text_file_url = \"https://raw.githubusercontent.com/hwchase17/chat-your-data/master/state_of_the_union.txt\"\n", + "\n", + "openai_api_key = \"sk-XXX\"\n", + "\n", + "with open(\"state_of_the_union.txt\", \"w\") as f:\n", + " response = requests.get(text_file_url)\n", + " f.write(response.text)\n", + "\n", + "loader = TextLoader(\"state_of_the_union.txt\")\n", + "documents = loader.load()\n", + "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", + "texts = text_splitter.split_documents(documents)\n", + "\n", + "embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key)\n", + "docsearch = Chroma.from_documents(texts, embeddings)\n", + "\n", + "qa = RetrievalQA.from_chain_type(\n", + " llm=OpenAI(openai_api_key=openai_api_key),\n", + " chain_type=\"stuff\",\n", + " retriever=docsearch.as_retriever(),\n", + ")\n", + "\n", + "# Providing a new question-answering pipeline\n", + "query = \"Who is the president?\"\n", + "result = qa.run(query)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After defining a chain, you can then manually check for answer similarity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "answer_relevancy_metric.measure(result, query)\n", + "answer_relevancy_metric.is_successful()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### What's next?\n", + "\n", + "You can create your own custom metrics [here](https://docs.confident-ai.com/docs/quickstart/custom-metrics). \n", + "\n", + "DeepEval also offers other features such as being able to [automatically create unit tests](https://docs.confident-ai.com/docs/quickstart/synthetic-data-creation), [tests for hallucination](https://docs.confident-ai.com/docs/measuring_llm_performance/factual_consistency).\n", + "\n", + "If you are interested, check out our Github repository here [https://github.com/confident-ai/deepeval](https://github.com/confident-ai/deepeval). We welcome any PRs and discussions on how to improve LLM performance." + ] + } + ], + "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.12" + }, + "vscode": { + "interpreter": { + "hash": "a53ebf4a859167383b364e7e7521d0add3c2dbbdecce4edf676e8c4634ff3fbb" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/versioned_docs/version-0.2.x/integrations/callbacks/context.ipynb b/docs/versioned_docs/version-0.2.x/integrations/callbacks/context.ipynb new file mode 100644 index 0000000000000..a053b2261c808 --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/callbacks/context.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Context\n", + "\n", + ">[Context](https://context.ai/) provides user analytics for LLM-powered products and features.\n", + "\n", + "With `Context`, you can start understanding your users and improving their experiences in less than 30 minutes.\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this guide we will show you how to integrate with Context." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Installation and Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain langchain-openai context-python" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Getting API Credentials\n", + "\n", + "To get your Context API token:\n", + "\n", + "1. Go to the settings page within your Context account (https://with.context.ai/settings).\n", + "2. Generate a new API Token.\n", + "3. Store this token somewhere secure." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup Context\n", + "\n", + "To use the `ContextCallbackHandler`, import the handler from Langchain and instantiate it with your Context API token.\n", + "\n", + "Ensure you have installed the `context-python` package before using the handler." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2024-03-06T19:05:26.534124Z", + "iopub.status.busy": "2024-03-06T19:05:26.533924Z", + "iopub.status.idle": "2024-03-06T19:05:26.798727Z", + "shell.execute_reply": "2024-03-06T19:05:26.798135Z", + "shell.execute_reply.started": "2024-03-06T19:05:26.534109Z" + } + }, + "outputs": [], + "source": [ + "from langchain_community.callbacks.context_callback import ContextCallbackHandler" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "token = os.environ[\"CONTEXT_API_TOKEN\"]\n", + "\n", + "context_callback = ContextCallbackHandler(token)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Usage\n", + "### Context callback within a chat model\n", + "\n", + "The Context callback handler can be used to directly record transcripts between users and AI assistants." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "from langchain.schema import (\n", + " HumanMessage,\n", + " SystemMessage,\n", + ")\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "token = os.environ[\"CONTEXT_API_TOKEN\"]\n", + "\n", + "chat = ChatOpenAI(\n", + " headers={\"user_id\": \"123\"}, temperature=0, callbacks=[ContextCallbackHandler(token)]\n", + ")\n", + "\n", + "messages = [\n", + " SystemMessage(\n", + " content=\"You are a helpful assistant that translates English to French.\"\n", + " ),\n", + " HumanMessage(content=\"I love programming.\"),\n", + "]\n", + "\n", + "print(chat(messages))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Context callback within Chains\n", + "\n", + "The Context callback handler can also be used to record the inputs and outputs of chains. Note that intermediate steps of the chain are not recorded - only the starting inputs and final outputs.\n", + "\n", + "__Note:__ Ensure that you pass the same context object to the chat model and the chain.\n", + "\n", + "Wrong:\n", + "> ```python\n", + "> chat = ChatOpenAI(temperature=0.9, callbacks=[ContextCallbackHandler(token)])\n", + "> chain = LLMChain(llm=chat, prompt=chat_prompt_template, callbacks=[ContextCallbackHandler(token)])\n", + "> ```\n", + "\n", + "Correct:\n", + ">```python\n", + ">handler = ContextCallbackHandler(token)\n", + ">chat = ChatOpenAI(temperature=0.9, callbacks=[callback])\n", + ">chain = LLMChain(llm=chat, prompt=chat_prompt_template, callbacks=[callback])\n", + ">```\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "from langchain.chains import LLMChain\n", + "from langchain.prompts import PromptTemplate\n", + "from langchain.prompts.chat import (\n", + " ChatPromptTemplate,\n", + " HumanMessagePromptTemplate,\n", + ")\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "token = os.environ[\"CONTEXT_API_TOKEN\"]\n", + "\n", + "human_message_prompt = HumanMessagePromptTemplate(\n", + " prompt=PromptTemplate(\n", + " template=\"What is a good name for a company that makes {product}?\",\n", + " input_variables=[\"product\"],\n", + " )\n", + ")\n", + "chat_prompt_template = ChatPromptTemplate.from_messages([human_message_prompt])\n", + "callback = ContextCallbackHandler(token)\n", + "chat = ChatOpenAI(temperature=0.9, callbacks=[callback])\n", + "chain = LLMChain(llm=chat, prompt=chat_prompt_template, callbacks=[callback])\n", + "print(chain.run(\"colorful socks\"))" + ] + } + ], + "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.12" + }, + "vscode": { + "interpreter": { + "hash": "a53ebf4a859167383b364e7e7521d0add3c2dbbdecce4edf676e8c4634ff3fbb" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/versioned_docs/version-0.2.x/integrations/callbacks/fiddler.ipynb b/docs/versioned_docs/version-0.2.x/integrations/callbacks/fiddler.ipynb new file mode 100644 index 0000000000000..55d246aa91d4b --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/callbacks/fiddler.ipynb @@ -0,0 +1,215 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0cebf93b", + "metadata": {}, + "source": [ + "# Fiddler\n", + "\n", + ">[Fiddler](https://www.fiddler.ai/) is the pioneer in enterprise Generative and Predictive system ops, offering a unified platform that enables Data Science, MLOps, Risk, Compliance, Analytics, and other LOB teams to monitor, explain, analyze, and improve ML deployments at enterprise scale. " + ] + }, + { + "cell_type": "markdown", + "id": "38d746c2", + "metadata": {}, + "source": [ + "## 1. Installation and Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0151955", + "metadata": {}, + "outputs": [], + "source": [ + "#!pip install langchain langchain-community langchain-openai fiddler-client" + ] + }, + { + "cell_type": "markdown", + "id": "5662f2e5-d510-4eef-b44b-fa929e5b4ad4", + "metadata": {}, + "source": [ + "## 2. Fiddler connection details " + ] + }, + { + "cell_type": "markdown", + "id": "64fac323", + "metadata": {}, + "source": [ + "*Before you can add information about your model with Fiddler*\n", + "\n", + "1. The URL you're using to connect to Fiddler\n", + "2. Your organization ID\n", + "3. Your authorization token\n", + "\n", + "These can be found by navigating to the *Settings* page of your Fiddler environment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6f8b73e-d350-40f0-b7a4-fb1e68a65a22", + "metadata": {}, + "outputs": [], + "source": [ + "URL = \"\" # Your Fiddler instance URL, Make sure to include the full URL (including https://). For example: https://demo.fiddler.ai\n", + "ORG_NAME = \"\"\n", + "AUTH_TOKEN = \"\" # Your Fiddler instance auth token\n", + "\n", + "# Fiddler project and model names, used for model registration\n", + "PROJECT_NAME = \"\"\n", + "MODEL_NAME = \"\" # Model name in Fiddler" + ] + }, + { + "cell_type": "markdown", + "id": "0645805a", + "metadata": {}, + "source": [ + "## 3. Create a fiddler callback handler instance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13de4f9a", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.callbacks.fiddler_callback import FiddlerCallbackHandler\n", + "\n", + "fiddler_handler = FiddlerCallbackHandler(\n", + " url=URL,\n", + " org=ORG_NAME,\n", + " project=PROJECT_NAME,\n", + " model=MODEL_NAME,\n", + " api_key=AUTH_TOKEN,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2276368e-f1dc-46be-afe3-18796e7a66f2", + "metadata": {}, + "source": [ + "## Example 1 : Basic Chain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9de0fd1", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_openai import OpenAI\n", + "\n", + "# Note : Make sure openai API key is set in the environment variable OPENAI_API_KEY\n", + "llm = OpenAI(temperature=0, streaming=True, callbacks=[fiddler_handler])\n", + "output_parser = StrOutputParser()\n", + "\n", + "chain = llm | output_parser\n", + "\n", + "# Invoke the chain. Invocation will be logged to Fiddler, and metrics automatically generated\n", + "chain.invoke(\"How far is moon from earth?\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "309bde0b-e1ce-446c-98ac-3690c26a2676", + "metadata": {}, + "outputs": [], + "source": [ + "# Few more invocations\n", + "chain.invoke(\"What is the temperature on Mars?\")\n", + "chain.invoke(\"How much is 2 + 200000?\")\n", + "chain.invoke(\"Which movie won the oscars this year?\")\n", + "chain.invoke(\"Can you write me a poem about insomnia?\")\n", + "chain.invoke(\"How are you doing today?\")\n", + "chain.invoke(\"What is the meaning of life?\")" + ] + }, + { + "cell_type": "markdown", + "id": "48fa4782-c867-4510-9430-4ffa3de3b5eb", + "metadata": {}, + "source": [ + "## Example 2 : Chain with prompt templates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2aa2c220-8946-4844-8d3c-8f69d744d13f", + "metadata": {}, + "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", + "]\n", + "\n", + "example_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"human\", \"{input}\"),\n", + " (\"ai\", \"{output}\"),\n", + " ]\n", + ")\n", + "\n", + "few_shot_prompt = FewShotChatMessagePromptTemplate(\n", + " example_prompt=example_prompt,\n", + " examples=examples,\n", + ")\n", + "\n", + "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", + "# Note : Make sure openai API key is set in the environment variable OPENAI_API_KEY\n", + "llm = OpenAI(temperature=0, streaming=True, callbacks=[fiddler_handler])\n", + "\n", + "chain = final_prompt | llm\n", + "\n", + "# Invoke the chain. Invocation will be logged to Fiddler, and metrics automatically generated\n", + "chain.invoke({\"input\": \"What's the square of a triangle?\"})" + ] + } + ], + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/integrations/callbacks/infino.ipynb b/docs/versioned_docs/version-0.2.x/integrations/callbacks/infino.ipynb new file mode 100644 index 0000000000000..07831d5ef988c --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/callbacks/infino.ipynb @@ -0,0 +1,473 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8d10861f-a550-4443-bc63-4ce2ae13b841", + "metadata": {}, + "source": [ + "# Infino\n", + "\n", + ">[Infino](https://github.com/infinohq/infino) is a scalable telemetry store designed for logs, metrics, and traces. Infino can function as a standalone observability solution or as the storage layer in your observability stack.\n", + "\n", + "This example shows how one can track the following while calling OpenAI and ChatOpenAI models via `LangChain` and [Infino](https://github.com/infinohq/infino):\n", + "\n", + "* prompt input\n", + "* response from `ChatGPT` or any other `LangChain` model\n", + "* latency\n", + "* errors\n", + "* number of tokens consumed" + ] + }, + { + "cell_type": "markdown", + "id": "64d14c88-b71c-4524-ab1b-4250a7dbb62b", + "metadata": {}, + "source": [ + "## Initializing" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ed46c894-caa6-49b2-85d1-f275374fa308", + "metadata": {}, + "outputs": [], + "source": [ + "# Install necessary dependencies.\n", + "%pip install --upgrade --quiet infinopy\n", + "%pip install --upgrade --quiet matplotlib\n", + "%pip install --upgrade --quiet tiktoken" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c9d9424-0879-4f14-91e5-1292e22820d7", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.callbacks.infino_callback import InfinoCallbackHandler" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3a5a0976-9953-41d8-880c-eb3f2992e936", + "metadata": {}, + "outputs": [], + "source": [ + "import datetime as dt\n", + "import json\n", + "import time\n", + "\n", + "import matplotlib.dates as md\n", + "import matplotlib.pyplot as plt\n", + "from infinopy import InfinoClient\n", + "from langchain_openai import OpenAI" + ] + }, + { + "cell_type": "markdown", + "id": "9f90210d-c805-4a0c-81e4-d5298942afc4", + "metadata": {}, + "source": [ + "## Start Infino server, initialize the Infino client" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "748b9858-5145-4351-976a-ca2d54e836a6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a1159e99c6bdb3101139157acee6aba7ae9319375e77ab6fbc79beff75abeca3\n" + ] + } + ], + "source": [ + "# Start server using the Infino docker image.\n", + "!docker run --rm --detach --name infino-example -p 3000:3000 infinohq/infino:latest\n", + "\n", + "# Create Infino client.\n", + "client = InfinoClient()" + ] + }, + { + "cell_type": "markdown", + "id": "b6b81cda-b841-43ee-8c5e-b1576555765f", + "metadata": {}, + "source": [ + "## Read the questions dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b659fd0c-0d8c-470e-8b6c-867a117f2a27", + "metadata": {}, + "outputs": [], + "source": [ + "# These are a subset of questions from Stanford's QA dataset -\n", + "# https://rajpurkar.github.io/SQuAD-explorer/\n", + "data = \"\"\"In what country is Normandy located?\n", + "When were the Normans in Normandy?\n", + "From which countries did the Norse originate?\n", + "Who was the Norse leader?\n", + "What century did the Normans first gain their separate identity?\n", + "Who gave their name to Normandy in the 1000's and 1100's\n", + "What is France a region of?\n", + "Who did King Charles III swear fealty to?\n", + "When did the Frankish identity emerge?\n", + "Who was the duke in the battle of Hastings?\n", + "Who ruled the duchy of Normandy\n", + "What religion were the Normans\n", + "What type of major impact did the Norman dynasty have on modern Europe?\n", + "Who was famed for their Christian spirit?\n", + "Who assimilted the Roman language?\n", + "Who ruled the country of Normandy?\n", + "What principality did William the conqueror found?\n", + "What is the original meaning of the word Norman?\n", + "When was the Latin version of the word Norman first recorded?\n", + "What name comes from the English words Normans/Normanz?\"\"\"\n", + "\n", + "questions = data.split(\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "dce1b820-3f1a-4b94-b848-4c6032cadc18", + "metadata": {}, + "source": [ + "## Example 1: LangChain OpenAI Q&A; Publish metrics and logs to Infino" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d5cebf35-2d10-48b8-ab11-c4a574c595d2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "In what country is Normandy located?\n", + "generations=[[Generation(text='\\n\\nNormandy is located in France.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 16, 'prompt_tokens': 7, 'completion_tokens': 9}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('67a516e3-d48a-4e83-92ba-a139079bd3b1'))]\n", + "When were the Normans in Normandy?\n", + "generations=[[Generation(text='\\n\\nThe Normans first settled in Normandy in the late 9th century.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 24, 'prompt_tokens': 8, 'completion_tokens': 16}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('6417a773-c863-4942-9607-c8a0c5d486e7'))]\n", + "From which countries did the Norse originate?\n", + "generations=[[Generation(text='\\n\\nThe Norse originated from Scandinavia, which includes the modern-day countries of Norway, Sweden, and Denmark.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 32, 'prompt_tokens': 8, 'completion_tokens': 24}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('70547d72-7925-454e-97fb-5539f8788c3f'))]\n", + "Who was the Norse leader?\n", + "generations=[[Generation(text='\\n\\nThe most famous Norse leader was the legendary Viking king Ragnar Lodbrok. He was a legendary Viking hero and ruler who is said to have lived in the 9th century. He is known for his legendary exploits, including leading a Viking raid on Paris in 845.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 62, 'prompt_tokens': 6, 'completion_tokens': 56}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('04500e37-44ab-4e56-9017-76fe8c19e2ca'))]\n", + "What century did the Normans first gain their separate identity?\n", + "generations=[[Generation(text='\\n\\nThe Normans first gained their separate identity in the 11th century.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 28, 'prompt_tokens': 12, 'completion_tokens': 16}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('adf319b7-1022-40df-9afe-1d65f869d83d'))]\n", + "Who gave their name to Normandy in the 1000's and 1100's\n", + "generations=[[Generation(text='\\n\\nThe Normans, a people from northern France, gave their name to Normandy in the 1000s and 1100s. The Normans were descendants of Vikings who had settled in the region in the late 800s.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 57, 'prompt_tokens': 13, 'completion_tokens': 44}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('1a0503bc-d033-4b69-a5fa-5e1796566133'))]\n", + "What is France a region of?\n", + "generations=[[Generation(text='\\n\\nFrance is a region of Europe.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 16, 'prompt_tokens': 7, 'completion_tokens': 9}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('7485d954-1c14-4dff-988a-25a0aa0871cc'))]\n", + "Who did King Charles III swear fealty to?\n", + "generations=[[Generation(text='\\n\\nKing Charles III swore fealty to King Philip II of Spain.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 25, 'prompt_tokens': 10, 'completion_tokens': 15}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('292c7143-4a08-43cd-a1e1-42cb1f594f33'))]\n", + "When did the Frankish identity emerge?\n", + "generations=[[Generation(text='\\n\\nThe Frankish identity began to emerge in the late 5th century, when the Franks began to expand their power and influence in the region. The Franks were a Germanic tribe that had settled in the area of modern-day France and Germany. They eventually established the Merovingian dynasty, which ruled much of Western Europe from the mid-6th century until 751.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 85, 'prompt_tokens': 8, 'completion_tokens': 77}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('3d9475c2-931e-4217-8bc3-b3e970e7597c'))]\n", + "Who was the duke in the battle of Hastings?\n", + "generations=[[Generation(text='\\n\\nThe Duke of Normandy, William the Conqueror, was the leader of the Norman forces at the Battle of Hastings in 1066.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'total_tokens': 39, 'prompt_tokens': 11, 'completion_tokens': 28}, 'model_name': 'text-davinci-003'} run=[RunInfo(run_id=UUID('b8f84619-ea5f-4c18-b411-b62194f36fe0'))]\n" + ] + } + ], + "source": [ + "# Set your key here.\n", + "# os.environ[\"OPENAI_API_KEY\"] = \"YOUR_API_KEY\"\n", + "\n", + "# Create callback handler. This logs latency, errors, token usage, prompts as well as prompt responses to Infino.\n", + "handler = InfinoCallbackHandler(\n", + " model_id=\"test_openai\", model_version=\"0.1\", verbose=False\n", + ")\n", + "\n", + "# Create LLM.\n", + "llm = OpenAI(temperature=0.1)\n", + "\n", + "# Number of questions to ask the OpenAI model. We limit to a short number here to save $$ while running this demo.\n", + "num_questions = 10\n", + "\n", + "questions = questions[0:num_questions]\n", + "for question in questions:\n", + " print(question)\n", + "\n", + " # We send the question to OpenAI API, with Infino callback.\n", + " llm_result = llm.generate([question], callbacks=[handler])\n", + " print(llm_result)" + ] + }, + { + "cell_type": "markdown", + "id": "b68ec697-c922-4fd9-aad1-f49c6ac24e8a", + "metadata": {}, + "source": [ + "## Create Metric Charts\n", + "\n", + "We now use matplotlib to create graphs of latency, errors and tokens consumed." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2eeeb6aa-e7b4-4a12-bbc6-6c4f1459810a", + "metadata": {}, + "outputs": [], + "source": [ + "# Helper function to create a graph using matplotlib.\n", + "def plot(data, title):\n", + " data = json.loads(data)\n", + "\n", + " # Extract x and y values from the data\n", + " timestamps = [item[\"time\"] for item in data]\n", + " dates = [dt.datetime.fromtimestamp(ts) for ts in timestamps]\n", + " y = [item[\"value\"] for item in data]\n", + "\n", + " plt.rcParams[\"figure.figsize\"] = [6, 4]\n", + " plt.subplots_adjust(bottom=0.2)\n", + " plt.xticks(rotation=25)\n", + " ax = plt.gca()\n", + " xfmt = md.DateFormatter(\"%Y-%m-%d %H:%M:%S\")\n", + " ax.xaxis.set_major_formatter(xfmt)\n", + "\n", + " # Create the plot\n", + " plt.plot(dates, y)\n", + "\n", + " # Set labels and title\n", + " plt.xlabel(\"Time\")\n", + " plt.ylabel(\"Value\")\n", + " plt.title(title)\n", + "\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f078c612-89e0-4a1d-b1a8-bf36b664a10e", + "metadata": {}, + "outputs": [], + "source": [ + "response = client.search_ts(\"__name__\", \"latency\", 0, int(time.time()))\n", + "plot(response.text, \"Latency\")\n", + "\n", + "response = client.search_ts(\"__name__\", \"error\", 0, int(time.time()))\n", + "plot(response.text, \"Errors\")\n", + "\n", + "response = client.search_ts(\"__name__\", \"prompt_tokens\", 0, int(time.time()))\n", + "plot(response.text, \"Prompt Tokens\")\n", + "\n", + "response = client.search_ts(\"__name__\", \"completion_tokens\", 0, int(time.time()))\n", + "plot(response.text, \"Completion Tokens\")\n", + "\n", + "response = client.search_ts(\"__name__\", \"total_tokens\", 0, int(time.time()))\n", + "plot(response.text, \"Total Tokens\")" + ] + }, + { + "cell_type": "markdown", + "id": "c3d61822-1781-4bc6-97a2-2abc5c2b2e75", + "metadata": {}, + "source": [ + "## Full text query on prompt or prompt outputs." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a0f051f0-e2bc-44e7-8dfb-bfd5bbd0fc9f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Results for normandy : [{\"time\":1696947743,\"fields\":{\"prompt_response\":\"\\n\\nThe Normans, a people from northern France, gave their name to Normandy in the 1000s and 1100s. The Normans were descendants of Vikings who had settled in the region in the late 800s.\"},\"text\":\"\\n\\nThe Normans, a people from northern France, gave their name to Normandy in the 1000s and 1100s. The Normans were descendants of Vikings who had settled in the region in the late 800s.\"},{\"time\":1696947740,\"fields\":{\"prompt\":\"Who gave their name to Normandy in the 1000's and 1100's\"},\"text\":\"Who gave their name to Normandy in the 1000's and 1100's\"},{\"time\":1696947733,\"fields\":{\"prompt_response\":\"\\n\\nThe Normans first settled in Normandy in the late 9th century.\"},\"text\":\"\\n\\nThe Normans first settled in Normandy in the late 9th century.\"},{\"time\":1696947732,\"fields\":{\"prompt_response\":\"\\n\\nNormandy is located in France.\"},\"text\":\"\\n\\nNormandy is located in France.\"},{\"time\":1696947731,\"fields\":{\"prompt\":\"In what country is Normandy located?\"},\"text\":\"In what country is Normandy located?\"}]\n", + "===\n", + "Results for king charles III : [{\"time\":1696947745,\"fields\":{\"prompt_response\":\"\\n\\nKing Charles III swore fealty to King Philip II of Spain.\"},\"text\":\"\\n\\nKing Charles III swore fealty to King Philip II of Spain.\"},{\"time\":1696947744,\"fields\":{\"prompt\":\"Who did King Charles III swear fealty to?\"},\"text\":\"Who did King Charles III swear fealty to?\"}]\n" + ] + } + ], + "source": [ + "# Search for a particular prompt text.\n", + "query = \"normandy\"\n", + "response = client.search_log(query, 0, int(time.time()))\n", + "print(\"Results for\", query, \":\", response.text)\n", + "\n", + "print(\"===\")\n", + "\n", + "query = \"king charles III\"\n", + "response = client.search_log(\"king charles III\", 0, int(time.time()))\n", + "print(\"Results for\", query, \":\", response.text)" + ] + }, + { + "cell_type": "markdown", + "id": "32dc6598-e405-47a1-ac8c-5e41c4e1ff57", + "metadata": {}, + "source": [ + "# Example 2: Summarize a piece of text using ChatOpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "2cdca23e-4247-4876-aa6b-042cb7d3b126", + "metadata": {}, + "outputs": [], + "source": [ + "# Set your key here.\n", + "# os.environ[\"OPENAI_API_KEY\"] = \"YOUR_API_KEY\"\n", + "\n", + "from langchain.chains.summarize import load_summarize_chain\n", + "from langchain_community.document_loaders import WebBaseLoader\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "# Create callback handler. This logs latency, errors, token usage, prompts, as well as prompt responses to Infino.\n", + "handler = InfinoCallbackHandler(\n", + " model_id=\"test_chatopenai\", model_version=\"0.1\", verbose=False\n", + ")\n", + "\n", + "urls = [\n", + " \"https://lilianweng.github.io/posts/2023-06-23-agent/\",\n", + " \"https://medium.com/lyft-engineering/lyftlearn-ml-model-training-infrastructure-built-on-kubernetes-aef8218842bb\",\n", + " \"https://blog.langchain.dev/week-of-10-2-langchain-release-notes/\",\n", + "]\n", + "\n", + "for url in urls:\n", + " loader = WebBaseLoader(url)\n", + " docs = loader.load()\n", + "\n", + " llm = ChatOpenAI(temperature=0, model_name=\"gpt-3.5-turbo-16k\", callbacks=[handler])\n", + " chain = load_summarize_chain(llm, chain_type=\"stuff\", verbose=False)\n", + "\n", + " chain.run(docs)" + ] + }, + { + "cell_type": "markdown", + "id": "cafd7df7-c1f9-42ea-bb0e-dff623eac52c", + "metadata": {}, + "source": [ + "## Create Metric Charts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f1ef0b90-8d09-45c4-8b23-5fad5ba6d0d5", + "metadata": {}, + "outputs": [], + "source": [ + "response = client.search_ts(\"__name__\", \"latency\", 0, int(time.time()))\n", + "plot(response.text, \"Latency\")\n", + "\n", + "response = client.search_ts(\"__name__\", \"error\", 0, int(time.time()))\n", + "plot(response.text, \"Errors\")\n", + "\n", + "response = client.search_ts(\"__name__\", \"prompt_tokens\", 0, int(time.time()))\n", + "plot(response.text, \"Prompt Tokens\")\n", + "\n", + "response = client.search_ts(\"__name__\", \"completion_tokens\", 0, int(time.time()))\n", + "plot(response.text, \"Completion Tokens\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "2ee18e1e-46ab-4080-92e6-f13affb384ac", + "metadata": {}, + "outputs": [], + "source": [ + "## Full text query on prompt or prompt outputs" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "edcc9d45-563e-42dd-9a0a-75caa36775e8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "===\n" + ] + } + ], + "source": [ + "# Search for a particular prompt text.\n", + "query = \"machine learning\"\n", + "response = client.search_log(query, 0, int(time.time()))\n", + "\n", + "# The output can be verbose - uncomment below if it needs to be printed.\n", + "# print(\"Results for\", query, \":\", response.text)\n", + "\n", + "print(\"===\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "ada8d518-ff99-4796-835d-9234b898e9c7", + "metadata": {}, + "outputs": [], + "source": [ + "## Stop Infino server" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "1566f92f-29f5-499e-996a-86b0de4e4456", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "infino-example\n" + ] + } + ], + "source": [ + "!docker rm -f infino-example" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b16dc8d-e60b-4195-a0bd-72ef4e0b6b83", + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/versioned_docs/version-0.2.x/integrations/callbacks/labelstudio.ipynb b/docs/versioned_docs/version-0.2.x/integrations/callbacks/labelstudio.ipynb new file mode 100644 index 0000000000000..68e17408c589d --- /dev/null +++ b/docs/versioned_docs/version-0.2.x/integrations/callbacks/labelstudio.ipynb @@ -0,0 +1,401 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Label Studio\n", + "\n", + "\n", + ">[Label Studio](https://labelstud.io/guide/get_started) is an open-source data labeling platform that provides LangChain with flexibility when it comes to labeling data for fine-tuning large language models (LLMs). It also enables the preparation of custom training data and the collection and evaluation of responses through human feedback.\n", + "\n", + "In this guide, you will learn how to connect a LangChain pipeline to `Label Studio` to:\n", + "\n", + "- Aggregate all input prompts, conversations, and responses in a single `Label Studio` project. This consolidates all the data in one place for easier labeling and analysis.\n", + "- Refine prompts and responses to create a dataset for supervised fine-tuning (SFT) and reinforcement learning with human feedback (RLHF) scenarios. The labeled data can be used to further train the LLM to improve its performance.\n", + "- Evaluate model responses through human feedback. `Label Studio` provides an interface for humans to review and provide feedback on model responses, allowing evaluation and iteration." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Installation and setup" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "First install latest versions of Label Studio and Label Studio API client:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain label-studio label-studio-sdk langchain-openai" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Next, run `label-studio` on the command line to start the local LabelStudio instance at `http://localhost:8080`. See the [Label Studio installation guide](https://labelstud.io/guide/install) for more options." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "You'll need a token to make API calls.\n", + "\n", + "Open your LabelStudio instance in your browser, go to `Account & Settings > Access Token` and copy the key.\n", + "\n", + "Set environment variables with your LabelStudio URL, API key and OpenAI API key:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"LABEL_STUDIO_URL\"] = \"\" # e.g. http://localhost:8080\n", + "os.environ[\"LABEL_STUDIO_API_KEY\"] = \"\"\n", + "os.environ[\"OPENAI_API_KEY\"] = \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Collecting LLMs prompts and responses" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data used for labeling is stored in projects within Label Studio. Every project is identified by an XML configuration that details the specifications for input and output data. \n", + "\n", + "Create a project that takes human input in text format and outputs an editable LLM response in a text area:\n", + "\n", + "```xml\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "