mirror of
https://github.com/hwchase17/langchain.git
synced 2025-06-26 16:43:35 +00:00
Add api cross ref linking (#8275)
Example of how it would show up in our python docs:  Examples added to the reference docs: https://api.python.langchain.com/en/wfh-api_crosslink/vectorstores/langchain.vectorstores.chroma.Chroma.html#langchain.vectorstores.chroma.Chroma 
This commit is contained in:
parent
a612800ef0
commit
01a9b06400
2
.github/workflows/codespell.yml
vendored
2
.github/workflows/codespell.yml
vendored
@ -20,3 +20,5 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Codespell
|
- name: Codespell
|
||||||
uses: codespell-project/actions-codespell@v2
|
uses: codespell-project/actions-codespell@v2
|
||||||
|
with:
|
||||||
|
skip: guide_imports.json
|
||||||
|
@ -7,20 +7,66 @@
|
|||||||
|
|
||||||
# -- Path setup --------------------------------------------------------------
|
# -- Path setup --------------------------------------------------------------
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import toml
|
||||||
|
from docutils import nodes
|
||||||
|
from sphinx.util.docutils import SphinxDirective
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
#
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import toml
|
|
||||||
|
|
||||||
|
_DIR = Path(__file__).parent.absolute()
|
||||||
sys.path.insert(0, os.path.abspath("."))
|
sys.path.insert(0, os.path.abspath("."))
|
||||||
sys.path.insert(0, os.path.abspath("../../libs/langchain"))
|
sys.path.insert(0, os.path.abspath("../../libs/langchain"))
|
||||||
|
|
||||||
with open("../../libs/langchain/pyproject.toml") as f:
|
with (_DIR.parents[1] / "libs" / "langchain" / "pyproject.toml").open("r") as f:
|
||||||
data = toml.load(f)
|
data = toml.load(f)
|
||||||
|
with (_DIR / "guide_imports.json").open("r") as f:
|
||||||
|
imported_classes = json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleLinksDirective(SphinxDirective):
|
||||||
|
"""Directive to generate a list of links to examples.
|
||||||
|
|
||||||
|
We have a script that extracts links to API reference docs
|
||||||
|
from our notebook examples. This directive uses that information
|
||||||
|
to backlink to the examples from the API reference docs."""
|
||||||
|
|
||||||
|
has_content = False
|
||||||
|
required_arguments = 1
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Run the directive.
|
||||||
|
|
||||||
|
Called any time :example_links:`ClassName` is used
|
||||||
|
in the template *.rst files."""
|
||||||
|
class_or_func_name = self.arguments[0]
|
||||||
|
links = imported_classes.get(class_or_func_name, {})
|
||||||
|
list_node = nodes.bullet_list()
|
||||||
|
for doc_name, link in links.items():
|
||||||
|
item_node = nodes.list_item()
|
||||||
|
para_node = nodes.paragraph()
|
||||||
|
link_node = nodes.reference()
|
||||||
|
link_node["refuri"] = link
|
||||||
|
link_node.append(nodes.Text(doc_name))
|
||||||
|
para_node.append(link_node)
|
||||||
|
item_node.append(para_node)
|
||||||
|
list_node.append(item_node)
|
||||||
|
if list_node.children:
|
||||||
|
title_node = nodes.title()
|
||||||
|
title_node.append(nodes.Text(f"Examples using {class_or_func_name}"))
|
||||||
|
return [title_node, list_node]
|
||||||
|
return [list_node]
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.add_directive("example_links", ExampleLinksDirective)
|
||||||
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ Functions
|
|||||||
|
|
||||||
.. autosummary::
|
.. autosummary::
|
||||||
:toctree: {module}
|
:toctree: {module}
|
||||||
|
:template: function.rst
|
||||||
|
|
||||||
{fstring}
|
{fstring}
|
||||||
|
|
||||||
|
1
docs/api_reference/guide_imports.json
Normal file
1
docs/api_reference/guide_imports.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,9 +0,0 @@
|
|||||||
Evaluation
|
|
||||||
=======================
|
|
||||||
|
|
||||||
LangChain has a number of convenient evaluation chains you can use off the shelf to grade your models' oupputs.
|
|
||||||
|
|
||||||
.. automodule:: langchain.evaluation
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:inherited-members:
|
|
@ -26,3 +26,5 @@
|
|||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
.. example_links:: {{ objname }}
|
8
docs/api_reference/templates/function.rst
Normal file
8
docs/api_reference/templates/function.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
:mod:`{{module}}`.{{objname}}
|
||||||
|
{{ underline }}==============
|
||||||
|
|
||||||
|
.. currentmodule:: {{ module }}
|
||||||
|
|
||||||
|
.. autofunction:: {{ objname }}
|
||||||
|
|
||||||
|
.. example_links:: {{ objname }}
|
150
docs/docs_skeleton/generate_api_reference_links.py
Normal file
150
docs/docs_skeleton/generate_api_reference_links.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
# Base URL for all class documentation
|
||||||
|
_BASE_URL = "https://api.python.langchain.com/en/latest/"
|
||||||
|
|
||||||
|
# Regular expression to match Python code blocks
|
||||||
|
code_block_re = re.compile(r"^(```python\n)(.*?)(```\n)", re.DOTALL | re.MULTILINE)
|
||||||
|
# Regular expression to match langchain import lines
|
||||||
|
_IMPORT_RE = re.compile(r"(from\s+(langchain\.\w+(\.\w+)*?)\s+import\s+)(\w+)")
|
||||||
|
|
||||||
|
_CURRENT_PATH = Path(__file__).parent.absolute()
|
||||||
|
# Directory where generated markdown files are stored
|
||||||
|
_DOCS_DIR = _CURRENT_PATH / "docs"
|
||||||
|
_JSON_PATH = _CURRENT_PATH.parent / "api_reference" / "guide_imports.json"
|
||||||
|
|
||||||
|
|
||||||
|
def find_files(path):
|
||||||
|
"""Find all MDX files in the given path"""
|
||||||
|
for root, _, files in os.walk(path):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".mdx") or file.endswith(".md"):
|
||||||
|
yield os.path.join(root, file)
|
||||||
|
|
||||||
|
|
||||||
|
def get_full_module_name(module_path, class_name):
|
||||||
|
"""Get full module name using inspect"""
|
||||||
|
module = importlib.import_module(module_path)
|
||||||
|
class_ = getattr(module, class_name)
|
||||||
|
return inspect.getmodule(class_).__name__
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function"""
|
||||||
|
global_imports = {}
|
||||||
|
|
||||||
|
for file in find_files(_DOCS_DIR):
|
||||||
|
print(f"Adding links for imports in {file}")
|
||||||
|
|
||||||
|
# replace_imports now returns the import information rather than writing it to a file
|
||||||
|
file_imports = replace_imports(file)
|
||||||
|
|
||||||
|
if file_imports:
|
||||||
|
# Use relative file path as key
|
||||||
|
relative_path = os.path.relpath(file, _DOCS_DIR)
|
||||||
|
doc_url = f"https://python.langchain.com/docs/{relative_path.replace('.mdx', '').replace('.md', '')}"
|
||||||
|
for import_info in file_imports:
|
||||||
|
doc_title = import_info["title"]
|
||||||
|
class_name = import_info["imported"]
|
||||||
|
if class_name not in global_imports:
|
||||||
|
global_imports[class_name] = {}
|
||||||
|
global_imports[class_name][doc_title] = doc_url
|
||||||
|
|
||||||
|
# Write the global imports information to a JSON file
|
||||||
|
with _JSON_PATH.open("w") as f:
|
||||||
|
json.dump(global_imports, f)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_doc_title(data: str, file_name: str) -> str:
|
||||||
|
try:
|
||||||
|
return re.findall(r"^#\s+(.*)", data, re.MULTILINE)[0]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
# Parse the rst-style titles
|
||||||
|
try:
|
||||||
|
return re.findall(r"^(.*)\n=+\n", data, re.MULTILINE)[0]
|
||||||
|
except IndexError:
|
||||||
|
return file_name
|
||||||
|
|
||||||
|
|
||||||
|
def replace_imports(file):
|
||||||
|
"""Replace imports in each Python code block with links to their documentation and append the import info in a comment"""
|
||||||
|
all_imports = []
|
||||||
|
with open(file, "r") as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
file_name = os.path.basename(file)
|
||||||
|
_DOC_TITLE = _get_doc_title(data, file_name)
|
||||||
|
|
||||||
|
def replacer(match):
|
||||||
|
# Extract the code block content
|
||||||
|
code = match.group(2)
|
||||||
|
# Replace if any import comment exists
|
||||||
|
# TODO: Use our own custom <code> component rather than this
|
||||||
|
# injection method
|
||||||
|
existing_comment_re = re.compile(r"^<!--IMPORTS:.*?-->\n", re.MULTILINE)
|
||||||
|
code = existing_comment_re.sub("", code)
|
||||||
|
|
||||||
|
# Process imports in the code block
|
||||||
|
imports = []
|
||||||
|
for import_match in _IMPORT_RE.finditer(code):
|
||||||
|
class_name = import_match.group(4)
|
||||||
|
try:
|
||||||
|
module_path = get_full_module_name(import_match.group(2), class_name)
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.warning(f"Could not find module for {class_name}, {e}")
|
||||||
|
continue
|
||||||
|
except ImportError as e:
|
||||||
|
# Some CentOS OpenSSL issues can cause this to fail
|
||||||
|
logger.warning(f"Failed to load for class {class_name}, {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
url = (
|
||||||
|
_BASE_URL
|
||||||
|
+ "/"
|
||||||
|
+ module_path.split(".")[1]
|
||||||
|
+ "/"
|
||||||
|
+ module_path
|
||||||
|
+ "."
|
||||||
|
+ class_name
|
||||||
|
+ ".html"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the import information to our list
|
||||||
|
imports.append(
|
||||||
|
{
|
||||||
|
"imported": class_name,
|
||||||
|
"source": import_match.group(2),
|
||||||
|
"docs": url,
|
||||||
|
"title": _DOC_TITLE,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if imports:
|
||||||
|
all_imports.extend(imports)
|
||||||
|
# Create a unique comment containing the import information
|
||||||
|
import_comment = f"<!--IMPORTS:{json.dumps(imports)}-->"
|
||||||
|
# Inject the import comment at the start of the code block
|
||||||
|
return match.group(1) + import_comment + "\n" + code + match.group(3)
|
||||||
|
else:
|
||||||
|
# If there are no imports, return the original match
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
# Use re.sub to replace each Python code block
|
||||||
|
data = code_block_re.sub(replacer, data)
|
||||||
|
|
||||||
|
with open(file, "w") as f:
|
||||||
|
f.write(data)
|
||||||
|
return all_imports
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -21,7 +21,7 @@ function Imports({ imports }) {
|
|||||||
</h4>
|
</h4>
|
||||||
<ul style={{ paddingBottom: "1rem" }}>
|
<ul style={{ paddingBottom: "1rem" }}>
|
||||||
{imports.map(({ imported, source, docs }) => (
|
{imports.map(({ imported, source, docs }) => (
|
||||||
<li>
|
<li key={imported}>
|
||||||
<a href={docs}>
|
<a href={docs}>
|
||||||
<span>{imported}</span>
|
<span>{imported}</span>
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
@ -34,14 +34,25 @@ function Imports({ imports }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CodeBlockWrapper({ children, ...props }) {
|
export default function CodeBlockWrapper({ children, ...props }) {
|
||||||
|
// Initialize imports as an empty array
|
||||||
|
let imports = [];
|
||||||
|
|
||||||
|
// Check if children is a string
|
||||||
if (typeof children === "string") {
|
if (typeof children === "string") {
|
||||||
return <CodeBlock {...props}>{children}</CodeBlock>;
|
// Search for an IMPORTS comment in the code
|
||||||
|
const match = /<!--IMPORTS:(.*?)-->\n/.exec(children);
|
||||||
|
if (match) {
|
||||||
|
imports = JSON.parse(match[1]);
|
||||||
|
children = children.replace(match[0], "");
|
||||||
|
}
|
||||||
|
} else if (children.imports) {
|
||||||
|
imports = children.imports;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CodeBlock {...props}>{children.content}</CodeBlock>
|
<CodeBlock {...props}>{children}</CodeBlock>
|
||||||
<Imports imports={children.imports} />
|
{imports.length > 0 && <Imports imports={imports} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,10 +1,32 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
### See: https://github.com/urllib3/urllib3/issues/2168
|
||||||
|
# Requests lib breaks for old SSL versions,
|
||||||
|
# which are defaults on Amazon Linux 2 (which Vercel uses for builds)
|
||||||
|
yum -y update
|
||||||
|
yum remove openssl-devel -y
|
||||||
|
yum install gcc bzip2-devel libffi-devel zlib-devel wget tar -y
|
||||||
|
yum install openssl11 -y
|
||||||
|
yum install openssl11-devel -y
|
||||||
|
# Install python 3.11 to connect with openSSL 1.1.1
|
||||||
|
wget https://www.python.org/ftp/python/3.11.4/Python-3.11.4.tgz
|
||||||
|
tar xzf Python-3.11.4.tgz
|
||||||
|
cd Python-3.11.4
|
||||||
|
./configure
|
||||||
|
make altinstall
|
||||||
|
# Check python version
|
||||||
|
echo "Python Version"
|
||||||
|
python3.11 --version
|
||||||
cd ..
|
cd ..
|
||||||
python3 --version
|
###
|
||||||
python3 -m venv .venv
|
|
||||||
|
# Install nbdev and generate docs
|
||||||
|
cd ..
|
||||||
|
python3.11 -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
python3 -m pip install -r vercel_requirements.txt
|
python3.11 -m pip install --upgrade pip
|
||||||
|
python3.11 -m pip install -r vercel_requirements.txt
|
||||||
cp -r extras/* docs_skeleton/docs
|
cp -r extras/* docs_skeleton/docs
|
||||||
cd docs_skeleton
|
cd docs_skeleton
|
||||||
nbdoc_build
|
nbdoc_build
|
||||||
|
python3.11 generate_api_reference_links.py
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"### ChatGPT Data\n",
|
"# ChatGPT Data\n",
|
||||||
"\n",
|
"\n",
|
||||||
">[ChatGPT](https://chat.openai.com) is an artificial intelligence (AI) chatbot developed by OpenAI.\n",
|
">[ChatGPT](https://chat.openai.com) is an artificial intelligence (AI) chatbot developed by OpenAI.\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## OctoAI Compute Service\n",
|
"# OctoAI Compute Service\n",
|
||||||
"This example goes over how to use LangChain to interact with `OctoAI` [LLM endpoints](https://octoai.cloud/templates)\n",
|
"This example goes over how to use LangChain to interact with `OctoAI` [LLM endpoints](https://octoai.cloud/templates)\n",
|
||||||
"## Environment setup\n",
|
"## Environment setup\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## AWS Lambda API"
|
"# AWS Lambda API"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"id": "cd835d40",
|
"id": "cd835d40",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## Multi-modal outputs: Image & Text"
|
"# Multi-modal outputs: Image & Text"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"id": "cd835d40",
|
"id": "cd835d40",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## Multi-modal outputs: Image & Text"
|
"# Multi-modal outputs: Image & Text"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
-e ../libs/langchain
|
||||||
nbdoc
|
nbdoc
|
Loading…
Reference in New Issue
Block a user