Compare commits

..

46 Commits

Author SHA1 Message Date
Erick Friis
4f76246cf2 standard-tests: release 0.3.4 (#28245) 2024-11-20 19:35:58 -08:00
Erick Friis
4bdf1d7d1a standard-tests: fix decorator init test (#28246) 2024-11-21 03:35:43 +00:00
Erick Friis
60e572f591 standard-tests: tool tests (#28244) 2024-11-20 19:26:16 -08:00
Erick Friis
35e6052df5 infra: remove stale dockerfiles from repo (#28243)
deleting the following docker things from monorepo. they aren't
currently usable because of old dependencies, and I'd rather avoid
people using them / having to maintain them

- /docker
- this folder has a compose file that spins up postgres,pgvector
(separate from postgres and very stale version),mongo instance with
default user/password that we've gotten security pings about before. not
worth having
- also spins up a custom dockerfile with onttotext/graphdb - not even
sure what that is
- /libs/langchain/dockerfile + dev.dockerfile
  - super old poetry version, doesn't implement the right thing anymore
- .github/workflows/_release_docker.yml, langchain_release_docker.yml
  - not used anymore, not worth having an alternate release path
2024-11-21 00:05:01 +00:00
Erick Friis
161ab736ce standard-tests: release 0.3.3 (#28242) 2024-11-20 23:47:02 +00:00
Erick Friis
8738973267 docs: vectorstore standard tests (#28241) 2024-11-20 23:38:08 +00:00
Eugene Yurtsev
2acc83f146 mistralai[patch]: 0.2.2 release (#28240)
mistralai 0.2.2 release
2024-11-20 22:18:15 +00:00
Eugene Yurtsev
1a66175e38 mistral[patch]: Propagate tool call id (#28238)
mistralai-large-2411 requires tool call id

Older models accept tool call id if its provided

mistral-large-2407 
mistral-large-2402
2024-11-20 17:02:30 -05:00
shroominic
dee72c46c1 community: Outlines integration (#27449)
In collaboration with @rlouf I build an
[outlines](https://dottxt-ai.github.io/outlines/latest/) integration for
langchain!

I think this is really useful for doing any type of structured output
locally.
[Dottxt](https://dottxt.co) spend alot of work optimising this process
at a lower level
([outlines-core](https://pypi.org/project/outlines-core/0.1.14/) written
in rust) so I think this is a better alternative over all current
approaches in langchain to do structured output.
It also implements the `.with_structured_output` method so it should be
a drop in replacement for a lot of applications.

The integration includes:
- **Outlines LLM class**
- **ChatOutlines class**
- **Tutorial Cookbooks**
- **Documentation Page**
- **Validation and error messages** 
- **Exposes Outlines Structured output features**
- **Support for multiple backends**
- **Integration and Unit Tests**

Dependencies: `outlines` + additional (depending on backend used)

I am not sure if the unit-tests comply with all requirements, if not I
suggest to just remove them since I don't see a useful way to do it
differently.

### Quick overview:

Chat Models:
<img width="698" alt="image"
src="https://github.com/user-attachments/assets/05a499b9-858c-4397-a9ff-165c2b3e7acc">

Structured Output:
<img width="955" alt="image"
src="https://github.com/user-attachments/assets/b9fcac11-d3e5-4698-b1ae-8c4cb3d54c45">

---------

Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-11-20 16:31:31 -05:00
Mikelarg
2901fa20cc community: Add deprecation warning for GigaChat integration in langchain-community (#28022)
- **Description:** We have released the
[langchain-gigachat](https://github.com/ai-forever/langchain-gigachat?tab=readme-ov-file)
with new GigaChat integration that support's function/tool calling. This
PR deprecated legacy GigaChat class in community package.

---------

Co-authored-by: Erick Friis <erick@langchain.dev>
2024-11-20 21:03:47 +00:00
Renzo-vS
567dc1e422 community: fix duplicate content (#28003)
Thank you for reading my first PR!

**Description:**
Deduplicate content in AzureSearch vectorstore.
Currently, by default, the content of the retrieval is placed both in
metadata and page_content of a Document.
This PR removes the content from metadata, and leaves it in
page_content.

**Issue:**:
Previously, the content was popped from result before metadata was
populated.
In #25828 , the order was changed which leads to a response with
duplicated content.
This was not the intention of that PR and seems undesirable.

Looking forward to seeing my contribution in the next version!

Cheers, 
Renzo
2024-11-20 12:49:03 -08:00
Jorge Piedrahita Ortiz
abaea28417 community: SamabanovaCloud tool calling and Structured output (#27967)
**Description:** Add tool calling and structured output support for
SambaNovaCloud chat models, docs included

---------

Co-authored-by: Erick Friis <erick@langchain.dev>
2024-11-20 19:12:08 +00:00
ccurme
cb32bab69d docs: update notebook env dependencies (#28221) 2024-11-20 14:10:42 -05:00
af su
7c7ee07d30 huggingface[fix]: HuggingFaceEndpointEmbeddings model parameter passing error when async embed (#27953)
This change refines the handling of _model_kwargs in POST requests.
Instead of nesting _model_kwargs as a dictionary under the parameters
key, it is now directly unpacked and merged into the request's JSON
payload. This ensures that the model parameters are passed correctly and
avoids unnecessary nesting.E. g.:

```python
import asyncio

from langchain_huggingface.embeddings import HuggingFaceEndpointEmbeddings

embedding_input = ["This input will get multiplied" * 10000]

embeddings = HuggingFaceEndpointEmbeddings(
    model="http://127.0.0.1:8081/embed",
    model_kwargs={"truncate": True},
)

# Truncated parameters in synchronized methods are handled correctly
embeddings.embed_documents(texts=embedding_input)
# The truncate parameter is not handled correctly in the asynchronous method,
# and 413 Request Entity Too Large is returned.
asyncio.run(embeddings.aembed_documents(texts=embedding_input))
```

Co-authored-by: af su <saf@zjuici.com>
Co-authored-by: Erick Friis <erick@langchain.dev>
2024-11-20 19:08:56 +00:00
Eric Pinzur
923ef85105 langchain_chroma: fixed integration tests (#27968)
Description:
* I'm planning to add `Document.id` support to the Chroma VectorStore,
but first I wanted to make sure all the integration tests were passing
first. They weren't. This PR fixes the broken tests.
* I found 2 issues:
* This change (from a year ago, exactly :) ) for supporting multi-modal
embeddings:
https://docs.trychroma.com/deployment/migration#migration-to-0.4.16---november-7,-2023
* This change https://github.com/langchain-ai/langchain/pull/27827 due
to an update in the chroma client.
  
Also ran `format` and `lint` on the changes.

Note: I am not a member of the Chroma team.
2024-11-20 11:05:02 -08:00
CLOVA Studio 개발
218b4e073e community: fix some features on Naver ChatModel & embedding model (#28228)
# Description

- adding stopReason to response_metadata to call stream and astream
- excluding NCP_APIGW_API_KEY input required validation
- to remove warning Field "model_name" has conflict with protected
namespace "model_".

cc. @vbarda
2024-11-20 10:35:41 -08:00
Erick Friis
4da35623af docs: formatting fix (#28235) 2024-11-20 18:08:47 +00:00
Erick Friis
43e24cd4a1 docs, standard-tests: property tags, support tool decorator (#28234) 2024-11-20 17:19:03 +00:00
Soham Das
4027da1b6e docs: fix typo in migration guide: migrate_agent.ipynb (#28227)
PR Title: `docs: fix typo in migration guide`

PR Message:
- **Description**: This PR fixes a small typo in the "How to Migrate
from Legacy LangChain Agents to LangGraph" guide. "In this cases" -> "In
this case"
- **Issue**: N/A (no issue linked for this typo fix)
- **Dependencies**: None
- **Twitter handle**: N/A
2024-11-20 11:44:22 -05:00
Erick Friis
16918842bf docs: add conceptual testing docs (#28205) 2024-11-19 22:46:26 +00:00
Lance Martin
6bda89f9a1 Clarify bind tools takes a list (#28222)
Thank you for contributing to LangChain!

- [ ] **PR title**: "package: description"
- Where "package" is whichever of langchain, community, core, etc. is
being modified. Use "docs: ..." for purely docs changes, "infra: ..."
for CI changes.
  - Example: "community: add foobar LLM"


- [ ] **PR message**: ***Delete this entire checklist*** and replace
with
    - **Description:** a description of the change
    - **Issue:** the issue # it fixes, if applicable
    - **Dependencies:** any dependencies required for this change
- **Twitter handle:** if your PR gets announced, and you'd like a
mention, we'll gladly shout you out!


- [ ] **Add tests and docs**: If you're adding a new integration, please
include
1. a test for the integration, preferably unit tests that do not rely on
network access,
2. an example notebook showing its use. It lives in
`docs/docs/integrations` directory.


- [ ] **Lint and test**: Run `make format`, `make lint` and `make test`
from the root of the package(s) you've modified. See contribution
guidelines for more: https://python.langchain.com/docs/contributing/

Additional guidelines:
- Make sure optional dependencies are imported within a function.
- Please do not add dependencies to pyproject.toml files (even optional
ones) unless they are required for unit tests.
- Most PRs should not touch more than one package.
- Changes should be backwards compatible.
- If you are adding something to community, do not re-import it in
langchain.

If no one reviews your PR within a few days, please @-mention one of
baskaryan, efriis, eyurtsev, ccurme, vbarda, hwchase17.
2024-11-19 12:59:10 -08:00
William FH
197b885911 [CLI] Relax constraints (#28218) 2024-11-19 09:31:56 -08:00
Eugene Yurtsev
5599a0a537 core[minor]: Add other langgraph packages to sys_info (#28190)
Add other langgraph packages to sys_info output
2024-11-19 09:20:25 -05:00
Erick Friis
97f752c92d docs: more standard test stubs (#28202) 2024-11-19 03:59:52 +00:00
Erick Friis
0a06732d3e docs: links in integration contrib (#28200) 2024-11-19 03:25:47 +00:00
Erick Friis
0dbaf05bb7 standard-tests: rename langchain_standard_tests to langchain_tests, release 0.3.2 (#28203) 2024-11-18 19:10:39 -08:00
Erick Friis
24eea2e398 infra: allow non-langchainai packages (#28199) 2024-11-19 01:43:08 +00:00
Erick Friis
d9d689572a openai: release 0.2.9, o1 streaming (#28197) 2024-11-18 23:54:38 +00:00
Erick Friis
cbeb8601d6 docs: efficient rebuild (#28195)
if you run `make build start` in one tab, then start editing files, you
can efficient rebuild notebooks with `make generate-files md-sync
render`
2024-11-18 22:09:16 +00:00
ccurme
018f4102f4 docs: fix embeddings tabs (#28193)
- Update fake embeddings to deterministic fake embeddings
- Fix indentation
2024-11-18 16:00:20 -05:00
Mahdi Massahi
6dfea7e508 docs: fixed a typo (#28191)
**Description**: removed the redundant phrase (typo)
2024-11-18 15:46:47 -05:00
Eugene Yurtsev
3a63055ce2 docs[patch]: Add missing link to streaming concepts page (#28189)
Add missing streaming concept
2024-11-18 14:35:10 -05:00
ccurme
a1db744b20 docs: add component tabs to integration landing pages (#28142)
- Add to embedding model tabs
- Add tabs for vector stores
- Add "hello world" examples in integration landing pages using tabs
2024-11-18 13:34:35 -05:00
Erick Friis
c26b3575f8 docs: community integration guide clarification (#28186) 2024-11-18 17:58:07 +00:00
Erick Friis
093f24ba4d docs: standard test update (#28185) 2024-11-18 17:49:21 +00:00
Talha Munir
0c051e57e0 docs: fix grammatical error in delegation to sync methods (#28165)
### **Description**  
Fixed a grammatical error in the documentation section about the
delegation to synchronous methods to improve readability and clarity.

### **Issue**  
No associated issue.

### **Dependencies**  
No additional dependencies required.

### **Twitter handle**  
N/A

---------

Co-authored-by: ccurme <chester.curme@gmail.com>
2024-11-18 16:27:30 +00:00
DreamOfStars
22a8652ecc langchain: add missing punctuation in react_single_input.py (#28161)
- [x] **PR title**: "langchain: add missing punctuation in
react_single_input.py"

- [x] **PR message**: 
- **Description:** Add missing single quote to line 12: "Invalid Format:
Missing 'Action:' after 'Thought:"
2024-11-18 09:38:48 -05:00
Eugene Yurtsev
76e210a349 docs: link to langgraph platform (#28150)
Link to langgraph platform
2024-11-16 22:37:58 -05:00
Eric Pinzur
0a57fc0016 community: OpenSearchVectorStore: use engine set at init() time by default (#28147)
Description:
* Updated the OpenSearchVectorStore to use the `engine` parameter
captured at `init()` time as the default when adding documents to the
store.

Formatted, Linted, and Tested.
2024-11-16 17:07:42 -05:00
Zapiron
e6fe8cc2fb docs: Fix wrong import of AttributeInfo (#28155)
Fix wrong import of `AttributeInfo` from
`langchain.chains.query_constructor.base` to
`langchain.chains.query_constructor.schema`
2024-11-16 16:59:35 -05:00
Zapiron
0b2bea4c0e docs: Resolve incorrect import for AttributeInfo (#28154)
`AttributeInfo` is incorrectly imported from
`langchain.chains.query_constructor.base` instead of
`langchain.chains.query_constructor.schema`
2024-11-16 16:57:55 -05:00
Alexey Morozov
3b602d0453 docs: Added missing installation for required packages in tutorial notebooks (#28156)
**Description:** some of the required packages are missing in the
installation cell in tutorial notebooks. So I added required packages to
installation cell or created latter one if it was not presented in the
notebook at all.

Tested in colab: "Kernel" -> "Run all cells". All the notebooks under
`docs/tutorials` run as expected without `ModuleNotFoundError` error.

---------

Co-authored-by: Chester Curme <chester.curme@gmail.com>
2024-11-16 21:51:30 +00:00
Zapiron
2de59d0b3e docs: Fixed mini typo (#28149)
Fix mini typo from objets to objects
2024-11-16 16:31:31 -05:00
Erick Friis
709c418022 docs: how to contribute integrations (#28143) 2024-11-15 14:52:17 -08:00
Erick Friis
683644320b docs: reorg sidebar (#27978) 2024-11-15 14:28:18 -08:00
Piyush Jain
c48fdbba6a docs:Moved AWS tab ahead in the list as per integration telemetry (#28144)
Moving ahead per integration telemetry
2024-11-15 22:24:27 +00:00
150 changed files with 4991 additions and 1759 deletions

View File

@@ -11,7 +11,9 @@ from typing import Dict, Any
def load_packages_yaml() -> Dict[str, Any]:
"""Load and parse the packages.yml file."""
with open("langchain/libs/packages.yml", "r") as f:
return yaml.safe_load(f)
all_packages = yaml.safe_load(f)
return {k: v for k, v in all_packages.items() if k["repo"]}
def get_target_dir(package_name: str) -> Path:
@@ -23,24 +25,19 @@ def get_target_dir(package_name: str) -> Path:
return base_path / "partners" / package_name_short
def clean_target_directories(packages: Dict[str, Any]) -> None:
def clean_target_directories(packages: list) -> None:
"""Remove old directories that will be replaced."""
for package in packages["packages"]:
if package["repo"] != "langchain-ai/langchain":
target_dir = get_target_dir(package["name"])
if target_dir.exists():
print(f"Removing {target_dir}")
shutil.rmtree(target_dir)
for package in packages:
target_dir = get_target_dir(package["name"])
if target_dir.exists():
print(f"Removing {target_dir}")
shutil.rmtree(target_dir)
def move_libraries(packages: Dict[str, Any]) -> None:
def move_libraries(packages: list) -> None:
"""Move libraries from their source locations to the target directories."""
for package in packages["packages"]:
# Skip if it's the main langchain repo or disabled
if package["repo"] == "langchain-ai/langchain" or package.get(
"disabled", False
):
continue
for package in packages:
repo_name = package["repo"].split("/")[1]
source_path = package["path"]
@@ -68,7 +65,14 @@ def main():
"""Main function to orchestrate the library sync process."""
try:
# Load packages configuration
packages = load_packages_yaml()
package_yaml = load_packages_yaml()
packages = [
p
for p in package_yaml["packages"]
if not p.get("disabled", False)
and p["repo"].startswith("langchain-ai/")
and p["repo"] != "langchain-ai/langchain"
]
# Clean target directories
clean_target_directories(packages)

View File

@@ -219,11 +219,7 @@ jobs:
# Replace all dashes in the package name with underscores,
# since that's how Python imports packages with dashes in the name.
if [ "$PKG_NAME" == "langchain-tests" ]; then
IMPORT_NAME="langchain_standard_tests"
else
IMPORT_NAME="$(echo "$PKG_NAME" | sed s/-/_/g)"
fi
IMPORT_NAME="$(echo "$PKG_NAME" | sed s/-/_/g)"
poetry run python -c "import $IMPORT_NAME; print(dir($IMPORT_NAME))"

View File

@@ -1,62 +0,0 @@
name: release_docker
on:
workflow_call:
inputs:
dockerfile:
required: true
type: string
description: "Path to the Dockerfile to build"
image:
required: true
type: string
description: "Name of the image to build"
env:
TEST_TAG: ${{ inputs.image }}:test
LATEST_TAG: ${{ inputs.image }}:latest
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get git tag
uses: actions-ecosystem/action-get-latest-tag@v1
id: get-latest-tag
- name: Set docker tag
env:
VERSION: ${{ steps.get-latest-tag.outputs.tag }}
run: |
echo "VERSION_TAG=${{ inputs.image }}:${VERSION#v}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build for Test
uses: docker/build-push-action@v5
with:
context: .
file: ${{ inputs.dockerfile }}
load: true
tags: ${{ env.TEST_TAG }}
- name: Test
run: |
docker run --rm ${{ env.TEST_TAG }} python -c "import langchain"
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v5
with:
context: .
file: ${{ inputs.dockerfile }}
# We can only build for the intersection of platforms supported by
# QEMU and base python image, for now build only for
# linux/amd64 and linux/arm64
platforms: linux/amd64,linux/arm64
tags: ${{ env.LATEST_TAG }},${{ env.VERSION_TAG }}
push: true

View File

@@ -37,9 +37,9 @@ jobs:
# Get unique repositories
REPOS=$(echo "$REPOS_UNSORTED" | sort -u)
# Checkout each unique repository
# Checkout each unique repository that is in langchain-ai org
for repo in $REPOS; do
if [ "$repo" != "langchain-ai/langchain" ]; then
if [[ "$repo" != "langchain-ai/langchain" && "$repo" == langchain-ai/* ]]; then
REPO_NAME=$(echo $repo | cut -d'/' -f2)
echo "Checking out $repo to $REPO_NAME"
git clone --depth 1 https://github.com/$repo.git $REPO_NAME

View File

@@ -1,14 +0,0 @@
---
name: docker/langchain/langchain Release
on:
workflow_dispatch: # Allows to trigger the workflow manually in GitHub UI
workflow_call: # Allows triggering from another workflow
jobs:
release:
uses: ./.github/workflows/_release_docker.yml
with:
dockerfile: docker/Dockerfile.base
image: langchain/langchain
secrets: inherit

View File

@@ -129,7 +129,7 @@ Please see [here](https://python.langchain.com) for full documentation, which in
- [🦜🛠️ LangSmith](https://docs.smith.langchain.com/): Trace and evaluate your language model applications and intelligent agents to help you move from prototype to production.
- [🦜🕸️ LangGraph](https://langchain-ai.github.io/langgraph/): Create stateful, multi-actor applications with LLMs. Integrates smoothly with LangChain, but can be used without it.
- [🦜🏓 LangServe](https://python.langchain.com/docs/langserve): Deploy LangChain runnables and chains as REST APIs.
- [🦜🕸️ LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/#langgraph-platform): Deploy LLM applications built with LangGraph into production.
## 💁 Contributing

View File

@@ -1,3 +0,0 @@
FROM python:3.11
RUN pip install langchain

View File

@@ -1,12 +0,0 @@
# Makefile
build_graphdb:
docker build --tag graphdb ./graphdb
start_graphdb:
docker-compose up -d graphdb
down:
docker-compose down -v --remove-orphans
.PHONY: build_graphdb start_graphdb down

View File

@@ -1,84 +0,0 @@
# docker-compose to make it easier to spin up integration tests.
# Services should use NON standard ports to avoid collision with
# any existing services that might be used for development.
# ATTENTION: When adding a service below use a non-standard port
# increment by one from the preceding port.
# For credentials always use `langchain` and `langchain` for the
# username and password.
version: "3"
name: langchain-tests
services:
redis:
image: redis/redis-stack-server:latest
# We use non standard ports since
# these instances are used for testing
# and users may already have existing
# redis instances set up locally
# for other projects
ports:
- "6020:6379"
volumes:
- ./redis-volume:/data
graphdb:
image: graphdb
ports:
- "6021:7200"
mongo:
image: mongo:latest
container_name: mongo_container
ports:
- "6022:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: langchain
MONGO_INITDB_ROOT_PASSWORD: langchain
postgres:
image: postgres:16
environment:
POSTGRES_DB: langchain
POSTGRES_USER: langchain
POSTGRES_PASSWORD: langchain
ports:
- "6023:5432"
command: |
postgres -c log_statement=all
healthcheck:
test:
[
"CMD-SHELL",
"psql postgresql://langchain:langchain@localhost/langchain --command 'SELECT 1;' || exit 1",
]
interval: 5s
retries: 60
volumes:
- postgres_data:/var/lib/postgresql/data
pgvector:
# postgres with the pgvector extension
image: ankane/pgvector
environment:
POSTGRES_DB: langchain
POSTGRES_USER: langchain
POSTGRES_PASSWORD: langchain
ports:
- "6024:5432"
command: |
postgres -c log_statement=all
healthcheck:
test:
[
"CMD-SHELL",
"psql postgresql://langchain:langchain@localhost/langchain --command 'SELECT 1;' || exit 1",
]
interval: 5s
retries: 60
volumes:
- postgres_data_pgvector:/var/lib/postgresql/data
vdms:
image: intellabs/vdms:latest
container_name: vdms_container
ports:
- "6025:55555"
volumes:
postgres_data:
postgres_data_pgvector:

View File

@@ -1,5 +0,0 @@
FROM ontotext/graphdb:10.5.1
RUN mkdir -p /opt/graphdb/dist/data/repositories/langchain
COPY config.ttl /opt/graphdb/dist/data/repositories/langchain/
COPY graphdb_create.sh /run.sh
ENTRYPOINT bash /run.sh

View File

@@ -1,46 +0,0 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix rep: <http://www.openrdf.org/config/repository#>.
@prefix sr: <http://www.openrdf.org/config/repository/sail#>.
@prefix sail: <http://www.openrdf.org/config/sail#>.
@prefix graphdb: <http://www.ontotext.com/config/graphdb#>.
[] a rep:Repository ;
rep:repositoryID "langchain" ;
rdfs:label "" ;
rep:repositoryImpl [
rep:repositoryType "graphdb:SailRepository" ;
sr:sailImpl [
sail:sailType "graphdb:Sail" ;
graphdb:read-only "false" ;
# Inference and Validation
graphdb:ruleset "empty" ;
graphdb:disable-sameAs "true" ;
graphdb:check-for-inconsistencies "false" ;
# Indexing
graphdb:entity-id-size "32" ;
graphdb:enable-context-index "false" ;
graphdb:enablePredicateList "true" ;
graphdb:enable-fts-index "false" ;
graphdb:fts-indexes ("default" "iri") ;
graphdb:fts-string-literals-index "default" ;
graphdb:fts-iris-index "none" ;
# Queries and Updates
graphdb:query-timeout "0" ;
graphdb:throw-QueryEvaluationException-on-timeout "false" ;
graphdb:query-limit-results "0" ;
# Settable in the file but otherwise hidden in the UI and in the RDF4J console
graphdb:base-URL "http://example.org/owlim#" ;
graphdb:defaultNS "" ;
graphdb:imports "" ;
graphdb:repository-type "file-repository" ;
graphdb:storage-folder "storage" ;
graphdb:entity-index-size "10000000" ;
graphdb:in-memory-literal-properties "true" ;
graphdb:enable-literal-index "true" ;
]
].

View File

@@ -1,28 +0,0 @@
#! /bin/bash
REPOSITORY_ID="langchain"
GRAPHDB_URI="http://localhost:7200/"
echo -e "\nUsing GraphDB: ${GRAPHDB_URI}"
function startGraphDB {
echo -e "\nStarting GraphDB..."
exec /opt/graphdb/dist/bin/graphdb
}
function waitGraphDBStart {
echo -e "\nWaiting GraphDB to start..."
for _ in $(seq 1 5); do
CHECK_RES=$(curl --silent --write-out '%{http_code}' --output /dev/null ${GRAPHDB_URI}/rest/repositories)
if [ "${CHECK_RES}" = '200' ]; then
echo -e "\nUp and running"
break
fi
sleep 30s
echo "CHECK_RES: ${CHECK_RES}"
done
}
startGraphDB &
waitGraphDBStart
wait

View File

@@ -38,7 +38,7 @@ install-py-deps:
generate-files:
mkdir -p $(INTERMEDIATE_DIR)
cp -r $(SOURCE_DIR)/* $(INTERMEDIATE_DIR)
cp -rp $(SOURCE_DIR)/* $(INTERMEDIATE_DIR)
$(PYTHON) scripts/tool_feat_table.py $(INTERMEDIATE_DIR)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -46,7 +46,7 @@ Most popular LangChain integrations implement asynchronous support of their APIs
When an asynchronous implementation is not available, LangChain tries to provide a default implementation, even if it incurs
a **slight** overhead.
By default, LangChain will delegate the execution of a unimplemented asynchronous methods to the synchronous counterparts. LangChain almost always assumes that the synchronous method should be treated as a blocking operation and should be run in a separate thread.
By default, LangChain will delegate the execution of unimplemented asynchronous methods to the synchronous counterparts. LangChain almost always assumes that the synchronous method should be treated as a blocking operation and should be run in a separate thread.
This is done using [asyncio.loop.run_in_executor](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor) functionality provided by the `asyncio` library. LangChain uses the default executor provided by the `asyncio` library, which lazily initializes a thread pool executor with a default number of threads that is reused in the given event loop. While this strategy incurs a slight overhead due to context switching between threads, it guarantees that every asynchronous method has a default implementation that works out of the box.
## Performance

View File

@@ -22,6 +22,7 @@ The conceptual guide does not cover step-by-step instructions or specific implem
- **[Memory](https://langchain-ai.github.io/langgraph/concepts/memory/)**: Information about a conversation that is persisted so that it can be used in future conversations.
- **[Multimodality](/docs/concepts/multimodality)**: The ability to work with data that comes in different forms, such as text, audio, images, and video.
- **[Runnable interface](/docs/concepts/runnables)**: The base abstraction that many LangChain components and the LangChain Expression Language are built on.
- **[Streaming](/docs/concepts/streaming)**: LangChain streaming APIs for surfacing results as they are generated.
- **[LangChain Expression Language (LCEL)](/docs/concepts/lcel)**: A syntax for orchestrating LangChain components. Most useful for simpler applications.
- **[Document loaders](/docs/concepts/document_loaders)**: Load a source as a list of documents.
- **[Retrieval](/docs/concepts/retrieval)**: Information retrieval systems can retrieve structured or unstructured data from a datasource in response to a query.
@@ -39,6 +40,7 @@ The conceptual guide does not cover step-by-step instructions or specific implem
- **[Callbacks](/docs/concepts/callbacks)**: Callbacks enable the execution of custom auxiliary code in built-in components. Callbacks are used to stream outputs from LLMs in LangChain, trace the intermediate steps of an application, and more.
- **[Tracing](/docs/concepts/tracing)**: The process of recording the steps that an application takes to go from input to output. Tracing is essential for debugging and diagnosing issues in complex applications.
- **[Evaluation](/docs/concepts/evaluation)**: The process of assessing the performance and effectiveness of AI applications. This involves testing the model's responses against a set of predefined criteria or benchmarks to ensure it meets the desired quality standards and fulfills the intended purpose. This process is vital for building reliable applications.
- **[Testing](/docs/concepts/testing)**: The process of verifying that a component of an integration or application works as expected. Testing is essential for ensuring that the application behaves correctly and that changes to the codebase do not introduce new bugs.
## Glossary
@@ -61,6 +63,7 @@ The conceptual guide does not cover step-by-step instructions or specific implem
- **[InjectedToolArg](/docs/concepts/tools#injectedtoolarg)**: Mechanism to inject arguments into tool functions.
- **[input and output types](/docs/concepts/runnables#input-and-output-types)**: Types used for input and output in Runnables.
- **[Integration packages](/docs/concepts/architecture/#integration-packages)**: Third-party packages that integrate with LangChain.
- **[Integration tests](/docs/concepts/testing#integration-tests)**: Tests that verify the correctness of the interaction between components, usually run with access to the underlying API that powers an integration.
- **[invoke](/docs/concepts/runnables)**: A standard method to invoke a Runnable.
- **[JSON mode](/docs/concepts/structured_outputs#json-mode)**: Returning responses in JSON format.
- **[langchain-community](/docs/concepts/architecture#langchain-community)**: Community-driven components for LangChain.
@@ -77,6 +80,7 @@ The conceptual guide does not cover step-by-step instructions or specific implem
- **[role](/docs/concepts/messages#role)**: Represents the role (e.g., user, assistant) of a chat message.
- **[RunnableConfig](/docs/concepts/runnables/#runnableconfig)**: Use to pass run time information to Runnables (e.g., `run_name`, `run_id`, `tags`, `metadata`, `max_concurrency`, `recursion_limit`, `configurable`).
- **[Standard parameters for chat models](/docs/concepts/chat_models#standard-parameters)**: Parameters such as API key, `temperature`, and `max_tokens`,
- **[Standard tests](/docs/concepts/testing#standard-tests)**: A defined set of unit and integration tests that all integrations must pass.
- **[stream](/docs/concepts/streaming)**: Use to stream output from a Runnable or a graph.
- **[Tokenization](/docs/concepts/tokens)**: The process of converting data into tokens and vice versa.
- **[Tokens](/docs/concepts/tokens)**: The basic unit that a language model reads, processes, and generates under the hood.
@@ -85,6 +89,7 @@ The conceptual guide does not cover step-by-step instructions or specific implem
- **[@tool](/docs/concepts/tools/#create-tools-using-the-tool-decorator)**: Decorator for creating tools in LangChain.
- **[Toolkits](/docs/concepts/tools#toolkits)**: A collection of tools that can be used together.
- **[ToolMessage](/docs/concepts/messages#toolmessage)**: Represents a message that contains the results of a tool execution.
- **[Unit tests](/docs/concepts/testing#unit-tests)**: Tests that verify the correctness of individual components, run in isolation without access to the Internet.
- **[Vector stores](/docs/concepts/vectorstores)**: Datastores specialized for storing and efficiently searching vector embeddings.
- **[with_structured_output](/docs/concepts/structured_outputs/#structured-output-method)**: A helper method for chat models that natively support [tool calling](/docs/concepts/tool_calling) to get structured output matching a given schema specified via Pydantic, JSON schema or a function.
- **[with_types](/docs/concepts/runnables#with_types)**: Method to overwrite the input and output types of a runnable. Useful when working with complex LCEL chains and deploying with LangServe.

View File

@@ -0,0 +1,81 @@
# Testing
<span data-heading-keywords="tests,testing,unit,integration"></span>
Testing is a critical part of the development process that ensures your code works as expected and meets the desired quality standards.
In the LangChain ecosystem, we have 2 main types of tests: **unit tests** and **integration tests**.
For integrations that implement standard LangChain abstractions, we have a set of **standard tests** (both unit and integration) that help maintain compatibility between different components and ensure reliability of high-usage ones.
## Unit Tests
**Definition**: Unit tests are designed to validate the smallest parts of your code—individual functions or methods—ensuring they work as expected in isolation. They do not rely on external systems or integrations.
**Example**: Testing the `convert_langchain_aimessage_to_dict` function to confirm it correctly converts an AI message to a dictionary format:
```python
from langchain_core.messages import AIMessage, ToolCall, convert_to_openai_messages
def test_convert_to_openai_messages():
ai_message = AIMessage(
content="Let me call that tool for you!",
tool_calls=[
ToolCall(name='parrot_multiply_tool', id='1', args={'a': 2, 'b': 3}),
]
)
result = convert_to_openai_messages(ai_message)
expected = {
"role": "assistant",
"tool_calls": [
{
"type": "function",
"id": "1",
"function": {
"name": "parrot_multiply_tool",
"arguments": '{"a": 2, "b": 3}',
},
}
],
"content": "Let me call that tool for you!",
}
assert result == expected # Ensure conversion matches expected output
```
---
## Integration Tests
**Definition**: Integration tests validate that multiple components or systems work together as expected. For tools or integrations relying on external services, these tests often ensure end-to-end functionality.
**Example**: Testing `ParrotMultiplyTool` with access to an API service that multiplies two numbers and adds 80:
```python
def test_integration_with_service():
tool = ParrotMultiplyTool()
result = tool.invoke({"a": 2, "b": 3})
assert result == 86
```
---
## Standard Tests
**Definition**: Standard tests are pre-defined tests provided by LangChain to ensure consistency and reliability across all tools and integrations. They include both unit and integration test templates tailored for LangChain components.
**Example**: Subclassing LangChain's `ToolsUnitTests` or `ToolsIntegrationTests` to automatically run standard tests:
```python
from langchain_tests.unit_tests import ToolsUnitTests
class TestParrotMultiplyToolUnit(ToolsUnitTests):
@property
def tool_constructor(self):
return ParrotMultiplyTool
def tool_invoke_params_example(self):
return {"a": 2, "b": 3}
```
To learn more, check out our guide on [how to add standard tests to an integration](../../contributing/how_to/integrations/standard_tests).

View File

@@ -77,7 +77,7 @@ The central concept to understand is that LangChain provides a standardized inte
The `.bind_tools()` method can be used to specify which tools are available for a model to call.
```python
model_with_tools = model.bind_tools([tools_list])
model_with_tools = model.bind_tools(tools_list)
```
As a specific example, let's take a function `multiply` and bind it as a tool to a model that supports tool calling.

View File

@@ -29,7 +29,7 @@ or new agents/chains from outside contributors without an existing GitHub discus
- New features must come with docs, unit tests, and (if appropriate) integration tests.
- New integrations must come with docs, unit tests, and (if appropriate) integration tests.
- See [this page](../integrations.mdx) for more details on contributing new integrations.
- See [this page](../integrations/index.mdx) for more details on contributing new integrations.
- New functionality should not inherit from or use deprecated methods or classes.
- We will reject features that are likely to lead to security vulnerabilities or reports.
- Do not add any hard dependencies. Integrations may add optional dependencies.

View File

@@ -2,4 +2,8 @@
- [**Documentation**](documentation/index.mdx): Help improve our docs, including this one!
- [**Code**](code/index.mdx): Help us write code, fix bugs, or improve our infrastructure.
- [**Integrations**](integrations.mdx): Help us integrate with your favorite vendors and tools.
## Integrations
- [**Start Here**](integrations/index.mdx): Help us integrate with your favorite vendors and tools.
- [**Standard Tests**](integrations/standard_tests): Ensure your integration passes an expected set of tests.

View File

@@ -1,203 +0,0 @@
---
sidebar_position: 5
---
# Contribute Integrations
To begin, make sure you have all the dependencies outlined in guide on [Contributing Code](code/index.mdx).
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
:::caution
Before starting a **partner** package, please confirm your intent with the LangChain team. Partner packages require more maintenance as separate packages, so we will close PRs that add new partner packages without prior discussion. See the above section for how to add a community integration.
:::
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](testing.mdx).
### Write documentation
Documentation is generated from Jupyter notebooks in the `docs/` directory. You should place the notebooks with examples
to the relevant `docs/docs/integrations` directory in the monorepo root.
### (If Necessary) Deprecate community integration
Note: this is only necessary if you're migrating an existing community integration into
a partner package. If the component you're integrating is net-new to LangChain (i.e.
not already in the `community` package), you can skip this step.
Let's pretend we migrated our `ChatParrotLink` chat model from the community package to
the partner package. We would need to deprecate the old model in the community package.
We would do that by adding a `@deprecated` decorator to the old model as follows, in
`libs/community/langchain_community/chat_models/parrot_link.py`.
Before our change, our chat model might look like this:
```python
class ChatParrotLink(BaseChatModel):
...
```
After our change, it would look like this:
```python
from langchain_core._api.deprecation import deprecated
@deprecated(
since="0.0.<next community version>",
removal="0.2.0",
alternative_import="langchain_parrot_link.ChatParrotLink"
)
class ChatParrotLink(BaseChatModel):
...
```
You should do this for *each* component that you're migrating to the partner package.
### Additional steps
Contributor steps:
- [ ] Add secret names to manual integrations workflow in `.github/workflows/_integration_test.yml`
- [ ] Add secrets to release workflow (for pre-release testing) in `.github/workflows/_release.yml`
Maintainer steps (Contributors should **not** do these):
- [ ] set up pypi and test pypi projects
- [ ] add credential secrets to Github Actions
- [ ] add package to conda-forge
## Partner package in external repo
Partner packages in external repos must be coordinated between the LangChain team and
the partner organization to ensure that they are maintained and updated.
If you're interested in creating a partner package in an external repo, please start
with one in the LangChain repo, and then reach out to the LangChain team to discuss
how to move it to an external repo.

View File

@@ -0,0 +1,51 @@
## How to add a community integration (not recommended)
:::danger
We recommend following the [main integration guide](./index.mdx) to add new integrations instead.
If you follow this guide, there is a high likelihood we will close your PR with the above
guide linked without much discussion.
:::
The `langchain-community` package is in `libs/community`.
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`

View File

@@ -0,0 +1,132 @@
# How to publish an integration package from a template
:::danger
This guide is a work-in-progress.
:::
First, duplicate this template repository: https://github.com/langchain-ai/integration-repo-template
In this guide, we will create a `libs/langchain-parrot-link` folder, simulating the creation
of a partner package for a fake company, "Parrot Link AI".
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
mkdir libs
cd libs/
langchain-cli integration new
> Name: parrot-link
> Name of integration in PascalCase [ParrotLink]: ParrotLink
```
This will create a new package in `libs/parrot-link` with the following structure:
```
libs/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](../testing.mdx).
## Write documentation
Documentation is generated from Jupyter notebooks in the `docs/` directory. You should place the notebooks with examples
to the relevant `docs/docs/integrations` directory in the monorepo root.
## (If Necessary) Deprecate community integration
Note: this is only necessary if you're migrating an existing community integration into
a partner package. If the component you're integrating is net-new to LangChain (i.e.
not already in the `community` package), you can skip this step.
Let's pretend we migrated our `ChatParrotLink` chat model from the community package to
the partner package. We would need to deprecate the old model in the community package.
We would do that by adding a `@deprecated` decorator to the old model as follows, in
`libs/community/langchain_community/chat_models/parrot_link.py`.
Before our change, our chat model might look like this:
```python
class ChatParrotLink(BaseChatModel):
...
```
After our change, it would look like this:
```python
from langchain_core._api.deprecation import deprecated
@deprecated(
since="0.0.<next community version>",
removal="0.2.0",
alternative_import="langchain_parrot_link.ChatParrotLink"
)
class ChatParrotLink(BaseChatModel):
...
```
You should do this for *each* component that you're migrating to the partner package.
## Additional steps
Contributor steps:
- [ ] Add secret names to manual integrations workflow in `.github/workflows/_integration_test.yml`
- [ ] Add secrets to release workflow (for pre-release testing) in `.github/workflows/_release.yml`
- [ ] set up pypi and test pypi projects
- [ ] add credential secrets to Github Actions
- [ ] add package to conda-forge

View File

@@ -0,0 +1,79 @@
---
sidebar_position: 5
---
# Contribute Integrations
LangChain integrations are packages that provide access to language models, vector stores, and other components that can be used in LangChain.
This guide will walk you through how to contribute new integrations to LangChain, by
publishing an integration package to PyPi, and adding documentation for it
to the LangChain Monorepo.
These instructions will evolve over the next few months as we improve our integration
processes.
## Components to Integrate
:::info
See the [Conceptual Guide](../../../concepts/index.mdx) for an overview of all components
supported in LangChain
:::
While any component can be integrated into LangChain, at this time we are only accepting
new integrations in the docs of the following kinds:
<table>
<tr>
<th>Integrate these ✅</th>
<th>Not these ❌</th>
</tr>
<tr>
<td>
<ul>
<li>Chat Models</li>
<li>Tools/Toolkits</li>
<li>Retrievers</li>
<li>Document Loaders</li>
<li>Vector Stores</li>
<li>Embedding Models</li>
</ul>
</td>
<td>
<ul>
<li>LLMs (Text-Completion Models)</li>
<li>Key-Value Stores</li>
<li>Document Transformers</li>
<li>Model Caches</li>
<li>Graphs</li>
<li>Message Histories</li>
<li>Callbacks</li>
<li>Chat Loaders</li>
<li>Adapters</li>
<li>Other abstractions</li>
</ul>
</td>
</tr>
</table>
## How to contribute an integration
The only step necessary to "be" a LangChain integration is to add documentation
that will render on this site (https://python.langchain.com/).
As a prerequisite to adding your integration to our documentation, you must:
1. Confirm that your integration is in the [list of components](#components-to-integrate) we are currently accepting.
2. Ensure that your integration is in a separate package that can be installed with `pip install <your-package>`.
3. [Implement the standard tests](/docs/contributing/how_to/integrations/standard_tests) for your integration and successfully run them.
3. Write documentation for your integration in the `docs/docs/integrations/<component_type>` directory of the LangChain monorepo.
4. Add a provider page for your integration in the `docs/docs/integrations/providers` directory of the LangChain monorepo.
Once you have completed these steps, you can submit a PR to the LangChain monorepo to add your integration to the documentation.
## Further Reading
If you're starting from scratch, you can follow the [Integration Template Guide](./from_template.mdx) to create and publish a new integration package
to the above spec.

View File

@@ -0,0 +1,468 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to add standard tests to an integration\n",
"\n",
"When creating either a custom class for yourself or a new tool to publish in a LangChain integration, it is important to add standard tests to ensure it works as expected. This guide will show you how to add standard tests to a tool, and you can **[Skip to the test templates](#standard-test-templates-per-component)** for implementing tests for each integration.\n",
"\n",
"## Setup\n",
"\n",
"First, let's install 2 dependencies:\n",
"\n",
"- `langchain-core` will define the interfaces we want to import to define our custom tool.\n",
"- `langchain-tests==0.3.3` will provide the standard tests we want to use.\n",
"\n",
":::note\n",
"\n",
"Because added tests in new versions of `langchain-tests` will always break your CI/CD pipelines, we recommend pinning the \n",
"version of `langchain-tests` to avoid unexpected changes.\n",
"\n",
":::"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install -U langchain-core langchain-tests==0.3.2 pytest pytest-socket"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's say we're publishing a package, `langchain_parrot_link`, that exposes a\n",
"tool called `ParrotMultiplyTool`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"langchain_parrot_link/tools.py\"\n",
"from langchain_core.tools import BaseTool\n",
"\n",
"\n",
"class ParrotMultiplyTool(BaseTool):\n",
" name: str = \"ParrotMultiplyTool\"\n",
" description: str = (\n",
" \"Multiply two numbers like a parrot. Parrots always add \"\n",
" \"eighty for their matey.\"\n",
" )\n",
"\n",
" def _run(self, a: int, b: int) -> int:\n",
" return a * b + 80"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And we'll assume you've structured your package the same way as the main LangChain\n",
"packages:\n",
"\n",
"```\n",
"/\n",
"├── langchain_parrot_link/\n",
"│ └── tools.py\n",
"└── tests/\n",
" ├── unit_tests/\n",
" │ └── test_tools.py\n",
" └── integration_tests/\n",
" └── test_tools.py\n",
"```\n",
"\n",
"## Add and configure standard tests\n",
"\n",
"There are 2 namespaces in the `langchain-tests` package: \n",
"\n",
"- [unit tests](../../../concepts/testing.mdx#unit-tests) (`langchain_tests.unit_tests`): designed to be used to test the tool in isolation and without access to external services\n",
"- [integration tests](../../../concepts/testing.mdx#unit-tests) (`langchain_tests.integration_tests`): designed to be used to test the tool with access to external services (in particular, the external service that the tool is designed to interact with).\n",
"\n",
"Both types of tests are implemented as [`pytest` class-based test suites](https://docs.pytest.org/en/7.1.x/getting-started.html#group-multiple-tests-in-a-class).\n",
"\n",
"By subclassing the base classes for each type of standard test (see below), you get all of the standard tests for that type, and you\n",
"can override the properties that the test suite uses to configure the tests.\n",
"\n",
"### Standard tools tests\n",
"\n",
"Here's how you would configure the standard unit tests for the custom tool, e.g. in `tests/test_tools.py`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"title": "tests/test_custom_tool.py"
},
"outputs": [],
"source": [
"# title=\"tests/unit_tests/test_tools.py\"\n",
"from typing import Type\n",
"\n",
"from langchain_parrot_link.tools import ParrotMultiplyTool\n",
"from langchain_tests.unit_tests import ToolsUnitTests\n",
"\n",
"\n",
"class TestParrotMultiplyToolUnit(ToolsUnitTests):\n",
" @property\n",
" def tool_constructor(self) -> Type[ParrotMultiplyTool]:\n",
" return ParrotMultiplyTool\n",
"\n",
" @property\n",
" def tool_constructor_params(self) -> dict:\n",
" # if your tool constructor instead required initialization arguments like\n",
" # `def __init__(self, some_arg: int):`, you would return those here\n",
" # as a dictionary, e.g.: `return {'some_arg': 42}`\n",
" return {}\n",
"\n",
" @property\n",
" def tool_invoke_params_example(self) -> dict:\n",
" \"\"\"\n",
" Returns a dictionary representing the \"args\" of an example tool call.\n",
"\n",
" This should NOT be a ToolCall dict - i.e. it should not\n",
" have {\"name\", \"id\", \"args\"} keys.\n",
" \"\"\"\n",
" return {\"a\": 2, \"b\": 3}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/integration_tests/test_tools.py\"\n",
"from typing import Type\n",
"\n",
"from langchain_parrot_link.tools import ParrotMultiplyTool\n",
"from langchain_tests.integration_tests import ToolsIntegrationTests\n",
"\n",
"\n",
"class TestParrotMultiplyToolIntegration(ToolsIntegrationTests):\n",
" @property\n",
" def tool_constructor(self) -> Type[ParrotMultiplyTool]:\n",
" return ParrotMultiplyTool\n",
"\n",
" @property\n",
" def tool_constructor_params(self) -> dict:\n",
" # if your tool constructor instead required initialization arguments like\n",
" # `def __init__(self, some_arg: int):`, you would return those here\n",
" # as a dictionary, e.g.: `return {'some_arg': 42}`\n",
" return {}\n",
"\n",
" @property\n",
" def tool_invoke_params_example(self) -> dict:\n",
" \"\"\"\n",
" Returns a dictionary representing the \"args\" of an example tool call.\n",
"\n",
" This should NOT be a ToolCall dict - i.e. it should not\n",
" have {\"name\", \"id\", \"args\"} keys.\n",
" \"\"\"\n",
" return {\"a\": 2, \"b\": 3}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and you would run these with the following commands from your project root\n",
"\n",
"```bash\n",
"# run unit tests without network access\n",
"pytest --disable-socket --allow-unix-socket tests/unit_tests\n",
"\n",
"# run integration tests\n",
"pytest tests/integration_tests\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Standard test templates per component:\n",
"\n",
"Above, we implement the **unit** and **integration** standard tests for a tool. Below are the templates for implementing the standard tests for each component:\n",
"\n",
"<details>\n",
" <summary>Chat Models</summary>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/unit_tests/test_chat_models.py\"\n",
"from typing import Tuple, Type\n",
"\n",
"from langchain_parrot_link.chat_models import ChatParrotLink\n",
"from langchain_tests.unit_tests import ChatModelUnitTests\n",
"\n",
"\n",
"class TestChatParrotLinkUnit(ChatModelUnitTests):\n",
" @property\n",
" def chat_model_class(self) -> Type[ChatParrotLink]:\n",
" return ChatParrotLink\n",
"\n",
" @property\n",
" def chat_model_params(self) -> dict:\n",
" return {\"model\": \"bird-brain-001\", \"temperature\": 0}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/integration_tests/test_chat_models.py\"\n",
"from typing import Type\n",
"\n",
"from langchain_parrot_link.chat_models import ChatParrotLink\n",
"from langchain_tests.integration_tests import ChatModelIntegrationTests\n",
"\n",
"\n",
"class TestChatParrotLinkIntegration(ChatModelIntegrationTests):\n",
" @property\n",
" def chat_model_class(self) -> Type[ChatParrotLink]:\n",
" return ChatParrotLink\n",
"\n",
" @property\n",
" def chat_model_params(self) -> dict:\n",
" return {\"model\": \"bird-brain-001\", \"temperature\": 0}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"</details>\n",
"<details>\n",
" <summary>Embedding Models</summary>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/unit_tests/test_embeddings.py\"\n",
"from typing import Tuple, Type\n",
"\n",
"from langchain_parrot_link.embeddings import ParrotLinkEmbeddings\n",
"from langchain_tests.unit_tests import EmbeddingsUnitTests\n",
"\n",
"\n",
"class TestParrotLinkEmbeddingsUnit(EmbeddingsUnitTests):\n",
" @property\n",
" def embeddings_class(self) -> Type[ParrotLinkEmbeddings]:\n",
" return ParrotLinkEmbeddings\n",
"\n",
" @property\n",
" def embedding_model_params(self) -> dict:\n",
" return {\"model\": \"nest-embed-001\", \"temperature\": 0}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/integration_tests/test_embeddings.py\"\n",
"from typing import Type\n",
"\n",
"from langchain_parrot_link.embeddings import ParrotLinkEmbeddings\n",
"from langchain_tests.integration_tests import EmbeddingsIntegrationTests\n",
"\n",
"\n",
"class TestParrotLinkEmbeddingsIntegration(EmbeddingsIntegrationTests):\n",
" @property\n",
" def embeddings_class(self) -> Type[ParrotLinkEmbeddings]:\n",
" return ParrotLinkEmbeddings\n",
"\n",
" @property\n",
" def embedding_model_params(self) -> dict:\n",
" return {\"model\": \"nest-embed-001\", \"temperature\": 0}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"</details>\n",
"<details>\n",
" <summary>Tools/Toolkits</summary>\n",
" <p>Note: The standard tests for tools/toolkits are implemented in the example in the main body of this guide too.</p>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/unit_tests/test_tools.py\"\n",
"from typing import Type\n",
"\n",
"from langchain_parrot_link.tools import ParrotMultiplyTool\n",
"from langchain_tests.unit_tests import ToolsUnitTests\n",
"\n",
"\n",
"class TestParrotMultiplyToolUnit(ToolsUnitTests):\n",
" @property\n",
" def tool_constructor(self) -> Type[ParrotMultiplyTool]:\n",
" return ParrotMultiplyTool\n",
"\n",
" @property\n",
" def tool_constructor_params(self) -> dict:\n",
" # if your tool constructor instead required initialization arguments like\n",
" # `def __init__(self, some_arg: int):`, you would return those here\n",
" # as a dictionary, e.g.: `return {'some_arg': 42}`\n",
" return {}\n",
"\n",
" @property\n",
" def tool_invoke_params_example(self) -> dict:\n",
" \"\"\"\n",
" Returns a dictionary representing the \"args\" of an example tool call.\n",
"\n",
" This should NOT be a ToolCall dict - i.e. it should not\n",
" have {\"name\", \"id\", \"args\"} keys.\n",
" \"\"\"\n",
" return {\"a\": 2, \"b\": 3}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/integration_tests/test_tools.py\"\n",
"from typing import Type\n",
"\n",
"from langchain_parrot_link.tools import ParrotMultiplyTool\n",
"from langchain_tests.integration_tests import ToolsIntegrationTests\n",
"\n",
"\n",
"class TestParrotMultiplyToolIntegration(ToolsIntegrationTests):\n",
" @property\n",
" def tool_constructor(self) -> Type[ParrotMultiplyTool]:\n",
" return ParrotMultiplyTool\n",
"\n",
" @property\n",
" def tool_constructor_params(self) -> dict:\n",
" # if your tool constructor instead required initialization arguments like\n",
" # `def __init__(self, some_arg: int):`, you would return those here\n",
" # as a dictionary, e.g.: `return {'some_arg': 42}`\n",
" return {}\n",
"\n",
" @property\n",
" def tool_invoke_params_example(self) -> dict:\n",
" \"\"\"\n",
" Returns a dictionary representing the \"args\" of an example tool call.\n",
"\n",
" This should NOT be a ToolCall dict - i.e. it should not\n",
" have {\"name\", \"id\", \"args\"} keys.\n",
" \"\"\"\n",
" return {\"a\": 2, \"b\": 3}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"</details>\n",
"<details>\n",
" <summary>Vector Stores</summary>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/integration_tests/test_vectorstores_sync.py\"\n",
"\n",
"from typing import AsyncGenerator, Generator\n",
"\n",
"import pytest\n",
"from langchain_core.vectorstores import VectorStore\n",
"from langchain_parrot_link.vectorstores import ParrotVectorStore\n",
"from langchain_standard_tests.integration_tests.vectorstores import (\n",
" AsyncReadWriteTestSuite,\n",
" ReadWriteTestSuite,\n",
")\n",
"\n",
"\n",
"class TestSync(ReadWriteTestSuite):\n",
" @pytest.fixture()\n",
" def vectorstore(self) -> Generator[VectorStore, None, None]: # type: ignore\n",
" \"\"\"Get an empty vectorstore for unit tests.\"\"\"\n",
" store = ParrotVectorStore()\n",
" # note: store should be EMPTY at this point\n",
" # if you need to delete data, you may do so here\n",
" try:\n",
" yield store\n",
" finally:\n",
" # cleanup operations, or deleting data\n",
" pass\n",
"\n",
"\n",
"class TestAsync(AsyncReadWriteTestSuite):\n",
" @pytest.fixture()\n",
" async def vectorstore(self) -> AsyncGenerator[VectorStore, None]: # type: ignore\n",
" \"\"\"Get an empty vectorstore for unit tests.\"\"\"\n",
" store = ParrotVectorStore()\n",
" # note: store should be EMPTY at this point\n",
" # if you need to delete data, you may do so here\n",
" try:\n",
" yield store\n",
" finally:\n",
" # cleanup operations, or deleting data\n",
" pass"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"</details>"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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": 2
}

View File

@@ -16,7 +16,8 @@ More coming soon! We are working on tutorials to help you make your first contri
- [**Documentation**](how_to/documentation/index.mdx): Help improve our docs, including this one!
- [**Code**](how_to/code/index.mdx): Help us write code, fix bugs, or improve our infrastructure.
- [**Integrations**](how_to/integrations.mdx): Help us integrate with your favorite vendors and tools.
- [**Integrations**](how_to/integrations/index.mdx): Help us integrate with your favorite vendors and tools.
- [**Standard Tests**](how_to/integrations/standard_tests): Ensure your integration passes an expected set of tests.
## Reference

View File

@@ -61,5 +61,5 @@ The `/libs` directory contains the code for the LangChain packages.
To learn more about how to contribute code see the following guidelines:
- [Code](../how_to/code/index.mdx): Learn how to develop in the LangChain codebase.
- [Integrations](../how_to/integrations.mdx): Learn how to contribute to third-party integrations to `langchain-community` or to start a new partner package.
- [Integrations](../how_to/integrations/index.mdx): Learn how to contribute to third-party integrations to `langchain-community` or to start a new partner package.
- [Testing](../how_to/testing.mdx): Guidelines to learn how to write tests for the packages.

View File

@@ -15,87 +15,9 @@ The base Embeddings class in LangChain provides two methods: one for embedding d
### Setup
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import EmbeddingTabs from "@theme/EmbeddingTabs";
<Tabs>
<TabItem value="openai" label="OpenAI" default>
To start we'll need to install the OpenAI partner package:
```bash
pip install langchain-openai
```
Accessing the API requires an API key, which you can get by creating an account and heading [here](https://platform.openai.com/account/api-keys). Once we have a key we'll want to set it as an environment variable by running:
```bash
export OPENAI_API_KEY="..."
```
If you'd prefer not to set an environment variable you can pass the key in directly via the `api_key` named parameter when initiating the OpenAI LLM class:
```python
from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings(api_key="...")
```
Otherwise you can initialize without any params:
```python
from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()
```
</TabItem>
<TabItem value="cohere" label="Cohere">
To start we'll need to install the Cohere SDK package:
```bash
pip install langchain-cohere
```
Accessing the API requires an API key, which you can get by creating an account and heading [here](https://dashboard.cohere.com/api-keys). Once we have a key we'll want to set it as an environment variable by running:
```shell
export COHERE_API_KEY="..."
```
If you'd prefer not to set an environment variable you can pass the key in directly via the `cohere_api_key` named parameter when initiating the Cohere LLM class:
```python
from langchain_cohere import CohereEmbeddings
embeddings_model = CohereEmbeddings(cohere_api_key="...", model='embed-english-v3.0')
```
Otherwise you can initialize simply as shown below:
```python
from langchain_cohere import CohereEmbeddings
embeddings_model = CohereEmbeddings(model='embed-english-v3.0')
```
Do note that it is mandatory to pass the model parameter while initializing the CohereEmbeddings class.
</TabItem>
<TabItem value="huggingface" label="Hugging Face">
To start we'll need to install the Hugging Face partner package:
```bash
pip install langchain-huggingface
```
You can then load any [Sentence Transformers model](https://huggingface.co/models?library=sentence-transformers) from the Hugging Face Hub.
```python
from langchain_huggingface import HuggingFaceEmbeddings
embeddings_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
```
</TabItem>
</Tabs>
<EmbeddingTabs customVarName="embeddings_model" />
### `embed_documents`
#### Embed list of texts

View File

@@ -331,7 +331,7 @@
"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."
"We can do all types of arbitrary formatting of messages here. In this case, let's just add a SystemMessage to the start of the list of messages."
]
},
{

View File

@@ -95,7 +95,7 @@
"metadata": {},
"outputs": [],
"source": [
"from langchain.chains.query_constructor.base import AttributeInfo\n",
"from langchain.chains.query_constructor.schema import AttributeInfo\n",
"from langchain.retrievers.self_query.base import SelfQueryRetriever\n",
"from langchain_openai import ChatOpenAI\n",
"\n",

View File

@@ -1,223 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to add standard tests to a tool\n",
"\n",
"When creating either a custom tool or a new tool to publish in a LangChain integration, it is important to add standard tests to ensure the tool works as expected. This guide will show you how to add standard tests to a tool.\n",
"\n",
"## Setup\n",
"\n",
"First, let's install 2 dependencies:\n",
"\n",
"- `langchain-core` will define the interfaces we want to import to define our custom tool.\n",
"- `langchain-tests==0.3.0` will provide the standard tests we want to use.\n",
"\n",
":::note\n",
"\n",
"The `langchain-tests` package contains the module `langchain_standard_tests`. This name\n",
"mistmatch is due to this package historically being called `langchain_standard_tests` and\n",
"the name not being available on PyPi. This will either be reconciled by our \n",
"[PEP 541 request](https://github.com/pypi/support/issues/5062) (we welcome upvotes!), \n",
"or in a new release of `langchain-tests`.\n",
"\n",
"Because added tests in new versions of `langchain-tests` will always break your CI/CD pipelines, we recommend pinning the \n",
"version of `langchain-tests==0.3.0` to avoid unexpected changes.\n",
"\n",
":::"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install -U langchain-core langchain-tests==0.3.0 pytest pytest-socket"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's say we're publishing a package, `langchain_parrot_link`, that exposes a\n",
"tool called `ParrotMultiplyTool`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"langchain_parrot_link/tools.py\"\n",
"from langchain_core.tools import BaseTool\n",
"\n",
"\n",
"class ParrotMultiplyTool(BaseTool):\n",
" name: str = \"ParrotMultiplyTool\"\n",
" description: str = (\n",
" \"Multiply two numbers like a parrot. Parrots always add \"\n",
" \"eighty for their matey.\"\n",
" )\n",
"\n",
" def _run(self, a: int, b: int) -> int:\n",
" return a * b + 80"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And we'll assume you've structured your package the same way as the main LangChain\n",
"packages:\n",
"\n",
"```\n",
"/\n",
"├── langchain_parrot_link/\n",
"│ └── tools.py\n",
"└── tests/\n",
" ├── unit_tests/\n",
" │ └── test_tools.py\n",
" └── integration_tests/\n",
" └── test_tools.py\n",
"```\n",
"\n",
"## Add and configure standard tests\n",
"\n",
"There are 2 namespaces in the `langchain-tests` package: \n",
"\n",
"- unit tests (`langchain_standard_tests.unit_tests`): designed to be used to test the tool in isolation and without access to external services\n",
"- integration tests (`langchain_standard_tests.integration_tests`): designed to be used to test the tool with access to external services (in particular, the external service that the tool is designed to interact with).\n",
"\n",
":::note\n",
"\n",
"Integration tests can also be run without access to external services, **if** they are properly mocked.\n",
"\n",
":::\n",
"\n",
"Both types of tests are implemented as [`pytest` class-based test suites](https://docs.pytest.org/en/7.1.x/getting-started.html#group-multiple-tests-in-a-class).\n",
"\n",
"By subclassing the base classes for each type of standard test (see below), you get all of the standard tests for that type, and you\n",
"can override the properties that the test suite uses to configure the tests.\n",
"\n",
"### Standard tools tests\n",
"\n",
"Here's how you would configure the standard unit tests for the custom tool, e.g. in `tests/test_tools.py`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"title": "tests/test_custom_tool.py"
},
"outputs": [],
"source": [
"# title=\"tests/unit_tests/test_custom_tool.py\"\n",
"from typing import Type\n",
"\n",
"from langchain_parrot_link.tools import ParrotMultiplyTool\n",
"from langchain_standard_tests.unit_tests import ToolsUnitTests\n",
"\n",
"\n",
"class MultiplyToolUnitTests(ToolsUnitTests):\n",
" @property\n",
" def tool_constructor(self) -> Type[ParrotMultiplyTool]:\n",
" return ParrotMultiplyTool\n",
"\n",
" def tool_constructor_params(self) -> dict:\n",
" # if your tool constructor instead required initialization arguments like\n",
" # `def __init__(self, some_arg: int):`, you would return those here\n",
" # as a dictionary, e.g.: `return {'some_arg': 42}`\n",
" return {}\n",
"\n",
" def tool_invoke_params_example(self) -> dict:\n",
" \"\"\"\n",
" Returns a dictionary representing the \"args\" of an example tool call.\n",
"\n",
" This should NOT be a ToolCall dict - i.e. it should not\n",
" have {\"name\", \"id\", \"args\"} keys.\n",
" \"\"\"\n",
" return {\"a\": 2, \"b\": 3}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# title=\"tests/integration_tests/test_custom_tool.py\"\n",
"from typing import Type\n",
"\n",
"from langchain_parrot_link.tools import ParrotMultiplyTool\n",
"from langchain_standard_tests.integration_tests import ToolsIntegrationTests\n",
"\n",
"\n",
"class MultiplyToolIntegrationTests(ToolsIntegrationTests):\n",
" @property\n",
" def tool_constructor(self) -> Type[ParrotMultiplyTool]:\n",
" return ParrotMultiplyTool\n",
"\n",
" def tool_constructor_params(self) -> dict:\n",
" # if your tool constructor instead required initialization arguments like\n",
" # `def __init__(self, some_arg: int):`, you would return those here\n",
" # as a dictionary, e.g.: `return {'some_arg': 42}`\n",
" return {}\n",
"\n",
" def tool_invoke_params_example(self) -> dict:\n",
" \"\"\"\n",
" Returns a dictionary representing the \"args\" of an example tool call.\n",
"\n",
" This should NOT be a ToolCall dict - i.e. it should not\n",
" have {\"name\", \"id\", \"args\"} keys.\n",
" \"\"\"\n",
" return {\"a\": 2, \"b\": 3}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and you would run these with the following commands from your project root\n",
"\n",
"```bash\n",
"# run unit tests without network access\n",
"pytest --disable-socket --enable-unix-socket tests/unit_tests\n",
"\n",
"# run integration tests\n",
"pytest tests/integration_tests\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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": 2
}

View File

@@ -8,7 +8,7 @@
"source": [
"# GigaChat\n",
"This notebook shows how to use LangChain with [GigaChat](https://developers.sber.ru/portal/products/gigachat).\n",
"To use you need to install ```gigachat``` python package."
"To use you need to install ```langchain_gigachat``` python package."
]
},
{
@@ -22,7 +22,7 @@
},
"outputs": [],
"source": [
"%pip install --upgrade --quiet gigachat"
"%pip install --upgrade --quiet langchain-gigachat"
]
},
{
@@ -53,20 +53,20 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from langchain_community.chat_models import GigaChat\n",
"from langchain_gigachat import GigaChat\n",
"\n",
"chat = GigaChat(verify_ssl_certs=False, scope=\"GIGACHAT_API_PERS\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 6,
"metadata": {
"collapsed": false
},

View File

@@ -15,6 +15,14 @@ If you'd like to contribute an integration, see [Contributing integrations](/doc
:::
import ChatModelTabs from "@theme/ChatModelTabs";
<ChatModelTabs openaiParams={`model="gpt-4o-mini"`} />
```python
model.invoke("Hello, world!")
```
## Featured Providers
:::info

View File

@@ -0,0 +1,348 @@
{
"cells": [
{
"cell_type": "raw",
"id": "afaf8039",
"metadata": {},
"source": [
"---\n",
"sidebar_label: Outlines\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "e49f1e0d",
"metadata": {},
"source": [
"# ChatOutlines\n",
"\n",
"This will help you getting started with Outlines [chat models](/docs/concepts/chat_models/). For detailed documentation of all ChatOutlines features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/chat_models/outlines.chat_models.ChatOutlines.html).\n",
"\n",
"[Outlines](https://github.com/outlines-dev/outlines) is a library for constrained language generation. It allows you to use large language models (LLMs) with various backends while applying constraints to the generated output.\n",
"\n",
"## Overview\n",
"### Integration details\n",
"\n",
"| Class | Package | Local | Serializable | JS support | Package downloads | Package latest |\n",
"| :--- | :--- | :---: | :---: | :---: | :---: | :---: |\n",
"| [ChatOutlines](https://api.python.langchain.com/en/latest/chat_models/outlines.chat_models.ChatOutlines.html) | [langchain-community](https://api.python.langchain.com/en/latest/community_api_reference.html) | ✅ | ❌ | ❌ | ![PyPI - Downloads](https://img.shields.io/pypi/dm/langchain-community?style=flat-square&label=%20) | ![PyPI - Version](https://img.shields.io/pypi/v/langchain-community?style=flat-square&label=%20) |\n",
"\n",
"### Model features\n",
"| [Tool calling](/docs/how_to/tool_calling) | [Structured output](/docs/how_to/structured_output/) | JSON mode | [Image input](/docs/how_to/multimodal_inputs/) | Audio input | Video input | [Token-level streaming](/docs/how_to/chat_streaming/) | Native async | [Token usage](/docs/how_to/chat_token_usage_tracking/) | [Logprobs](/docs/how_to/logprobs/) |\n",
"| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |\n",
"| ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | \n",
"\n",
"## Setup\n",
"\n",
"To access Outlines models you'll need to have an internet connection to download the model weights from huggingface. Depending on the backend you need to install the required dependencies (see [Outlines docs](https://dottxt-ai.github.io/outlines/latest/installation/))\n",
"\n",
"### Credentials\n",
"\n",
"There is no built-in auth mechanism for Outlines.\n",
"\n",
"### Installation\n",
"\n",
"The LangChain Outlines integration lives in the `langchain-community` package and requires the `outlines` library:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "652d6238-1f87-422a-b135-f5abbb8652fc",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain-community outlines"
]
},
{
"cell_type": "markdown",
"id": "a38cde65-254d-4219-a441-068766c0d4b5",
"metadata": {},
"source": [
"## Instantiation\n",
"\n",
"Now we can instantiate our model object and generate chat completions:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb09c344-1836-4e0c-acf8-11d13ac1dbae",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.chat_models.outlines import ChatOutlines\n",
"\n",
"# For llamacpp backend\n",
"model = ChatOutlines(model=\"TheBloke/phi-2-GGUF/phi-2.Q4_K_M.gguf\", backend=\"llamacpp\")\n",
"\n",
"# For vllm backend (not available on Mac)\n",
"model = ChatOutlines(model=\"meta-llama/Llama-3.2-1B\", backend=\"vllm\")\n",
"\n",
"# For mlxlm backend (only available on Mac)\n",
"model = ChatOutlines(model=\"mistralai/Ministral-8B-Instruct-2410\", backend=\"mlxlm\")\n",
"\n",
"# For huggingface transformers backend\n",
"model = ChatOutlines(model=\"microsoft/phi-2\") # defaults to transformers backend"
]
},
{
"cell_type": "markdown",
"id": "2b4f3e15",
"metadata": {},
"source": [
"## Invocation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "62e0dbc3",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from langchain_core.messages import HumanMessage\n",
"\n",
"messages = [HumanMessage(content=\"What will the capital of mars be called?\")]\n",
"response = model.invoke(messages)\n",
"\n",
"response.content"
]
},
{
"cell_type": "markdown",
"id": "18e2bfc0-7e78-4528-a73f-499ac150dca8",
"metadata": {},
"source": [
"## Streaming\n",
"\n",
"ChatOutlines supports streaming of tokens:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e197d1d7-a070-4c96-9f8a-a0e86d046e0b",
"metadata": {},
"outputs": [],
"source": [
"messages = [HumanMessage(content=\"Count to 10 in French:\")]\n",
"\n",
"for chunk in model.stream(messages):\n",
" print(chunk.content, end=\"\", flush=True)"
]
},
{
"cell_type": "markdown",
"id": "ccc3e2f6",
"metadata": {},
"source": [
"## Chaining"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a032003",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful assistant that translates {input_language} to {output_language}.\",\n",
" ),\n",
" (\"human\", \"{input}\"),\n",
" ]\n",
")\n",
"\n",
"chain = prompt | model\n",
"chain.invoke(\n",
" {\n",
" \"input_language\": \"English\",\n",
" \"output_language\": \"German\",\n",
" \"input\": \"I love programming.\",\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d1ee55bc-ffc8-4cfa-801c-993953a08cfd",
"metadata": {},
"source": [
"## Constrained Generation\n",
"\n",
"ChatOutlines allows you to apply various constraints to the generated output:\n",
"\n",
"### Regex Constraint"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"outputs": [],
"source": [
"model.regex = r\"((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\"\n",
"\n",
"response = model.invoke(\"What is the IP address of Google's DNS server?\")\n",
"\n",
"response.content"
]
},
{
"cell_type": "markdown",
"id": "4a5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"source": [
"### Type Constraints"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"outputs": [],
"source": [
"model.type_constraints = int\n",
"response = model.invoke(\"What is the answer to life, the universe, and everything?\")\n",
"\n",
"response.content"
]
},
{
"cell_type": "markdown",
"id": "6a5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"source": [
"### Pydantic and JSON Schemas"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7a5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"outputs": [],
"source": [
"from pydantic import BaseModel\n",
"\n",
"\n",
"class Person(BaseModel):\n",
" name: str\n",
"\n",
"\n",
"model.json_schema = Person\n",
"response = model.invoke(\"Who are the main contributors to LangChain?\")\n",
"person = Person.model_validate_json(response.content)\n",
"\n",
"person"
]
},
{
"cell_type": "markdown",
"id": "8a5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"source": [
"### Context Free Grammars"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9a5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"outputs": [],
"source": [
"model.grammar = \"\"\"\n",
"?start: expression\n",
"?expression: term ((\"+\" | \"-\") term)*\n",
"?term: factor ((\"*\" | \"/\") factor)*\n",
"?factor: NUMBER | \"-\" factor | \"(\" expression \")\"\n",
"%import common.NUMBER\n",
"%import common.WS\n",
"%ignore WS\n",
"\"\"\"\n",
"response = model.invoke(\"Give me a complex arithmetic expression:\")\n",
"\n",
"response.content"
]
},
{
"cell_type": "markdown",
"id": "aa5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"source": [
"## LangChain's Structured Output\n",
"\n",
"You can also use LangChain's Structured Output with ChatOutlines:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"outputs": [],
"source": [
"from pydantic import BaseModel\n",
"\n",
"\n",
"class AnswerWithJustification(BaseModel):\n",
" answer: str\n",
" justification: str\n",
"\n",
"\n",
"_model = model.with_structured_output(AnswerWithJustification)\n",
"result = _model.invoke(\"What weighs more, a pound of bricks or a pound of feathers?\")\n",
"\n",
"result"
]
},
{
"cell_type": "markdown",
"id": "ca5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
"metadata": {},
"source": [
"## API reference\n",
"\n",
"For detailed documentation of all ChatOutlines features and configurations head to the API reference: https://api.python.langchain.com/en/latest/chat_models/outlines.chat_models.ChatOutlines.html\n",
"\n",
"## Full Outlines Documentation: \n",
"\n",
"https://dottxt-ai.github.io/outlines/latest/"
]
}
],
"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.9.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -19,7 +19,7 @@
"source": [
"# ChatSambaNovaCloud\n",
"\n",
"This will help you getting started with SambaNovaCloud [chat models](/docs/concepts/chat_models). For detailed documentation of all ChatSambaNovaCloud features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/chat_models/langchain_community.chat_models.sambanova.ChatSambaNovaCloud.html).\n",
"This will help you getting started with SambaNovaCloud [chat models](/docs/concepts/chat_models). For detailed documentation of all ChatSambaNovaCloud features and configurations head to the [API reference](https://python.langchain.com/api_reference/community/chat_models/langchain_community.chat_models.sambanova.ChatSambaNovaCloud.html).\n",
"\n",
"**[SambaNova](https://sambanova.ai/)'s** [SambaNova Cloud](https://cloud.sambanova.ai/) is a platform for performing inference with open-source models\n",
"\n",
@@ -28,13 +28,13 @@
"\n",
"| Class | Package | Local | Serializable | JS support | Package downloads | Package latest |\n",
"| :--- | :--- | :---: | :---: | :---: | :---: | :---: |\n",
"| [ChatSambaNovaCloud](https://api.python.langchain.com/en/latest/chat_models/langchain_community.chat_models.sambanova.ChatSambaNovaCloud.html) | [langchain-community](https://python.langchain.com/v0.2/api_reference/community/index.html) | ❌ | ❌ | ❌ | ![PyPI - Downloads](https://img.shields.io/pypi/dm/langchain_community?style=flat-square&label=%20) | ![PyPI - Version](https://img.shields.io/pypi/v/langchain_community?style=flat-square&label=%20) |\n",
"| [ChatSambaNovaCloud](https://python.langchain.com/api_reference/community/chat_models/langchain_community.chat_models.sambanova.ChatSambaNovaCloud.html) | [langchain-community](https://python.langchain.com/api_reference/community/index.html) | ❌ | ❌ | ❌ | ![PyPI - Downloads](https://img.shields.io/pypi/dm/langchain_community?style=flat-square&label=%20) | ![PyPI - Version](https://img.shields.io/pypi/v/langchain_community?style=flat-square&label=%20) |\n",
"\n",
"### Model features\n",
"\n",
"| [Tool calling](/docs/how_to/tool_calling) | [Structured output](/docs/how_to/structured_output/) | JSON mode | [Image input](/docs/how_to/multimodal_inputs/) | Audio input | Video input | [Token-level streaming](/docs/how_to/chat_streaming/) | Native async | [Token usage](/docs/how_to/chat_token_usage_tracking/) | [Logprobs](/docs/how_to/logprobs/) |\n",
"| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |\n",
"| | | | | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | \n",
"| | | | | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | \n",
"\n",
"## Setup\n",
"\n",
@@ -116,14 +116,18 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.chat_models.sambanova import ChatSambaNovaCloud\n",
"\n",
"llm = ChatSambaNovaCloud(\n",
" model=\"llama3-405b\", max_tokens=1024, temperature=0.7, top_k=1, top_p=0.01\n",
" model=\"Meta-Llama-3.1-70B-Instruct\",\n",
" max_tokens=1024,\n",
" temperature=0.7,\n",
" top_k=1,\n",
" top_p=0.01,\n",
")"
]
},
@@ -142,7 +146,7 @@
{
"data": {
"text/plain": [
"AIMessage(content=\"J'adore la programmation.\", response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 11, 'completion_tokens': 9, 'completion_tokens_after_first_per_sec': 97.07042823956884, 'completion_tokens_after_first_per_sec_first_ten': 276.3343994441849, 'completion_tokens_per_sec': 23.775192800224037, 'end_time': 1726158364.7954874, 'is_last_response': True, 'prompt_tokens': 56, 'start_time': 1726158364.3670964, 'time_to_first_token': 0.3459765911102295, 'total_latency': 0.3785458261316473, 'total_tokens': 65, 'total_tokens_per_sec': 171.70972577939582}, 'model_name': 'Meta-Llama-3.1-405B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1726158364}, id='7154b676-9d5a-4b1a-a425-73bbe69f28fc')"
"AIMessage(content=\"J'adore la programmation.\", additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 7, 'completion_tokens': 8, 'completion_tokens_after_first_per_sec': 195.0204119588971, 'completion_tokens_after_first_per_sec_first_ten': 618.3422770734173, 'completion_tokens_per_sec': 53.25837044790076, 'end_time': 1731535338.1864908, 'is_last_response': True, 'prompt_tokens': 55, 'start_time': 1731535338.0133238, 'time_to_first_token': 0.13727331161499023, 'total_latency': 0.15021112986973353, 'total_tokens': 63, 'total_tokens_per_sec': 419.4096672772185}, 'model_name': 'Meta-Llama-3.1-70B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1731535338}, id='f04b7c2c-bc46-47e0-9c6b-19a002e8f390')"
]
},
"execution_count": 3,
@@ -196,7 +200,7 @@
{
"data": {
"text/plain": [
"AIMessage(content='Ich liebe Programmieren.', response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 11, 'completion_tokens': 6, 'completion_tokens_after_first_per_sec': 47.80258530102961, 'completion_tokens_after_first_per_sec_first_ten': 215.59002827036753, 'completion_tokens_per_sec': 5.263977583489829, 'end_time': 1726158506.3777263, 'is_last_response': True, 'prompt_tokens': 51, 'start_time': 1726158505.1611376, 'time_to_first_token': 1.1119918823242188, 'total_latency': 1.1398224830627441, 'total_tokens': 57, 'total_tokens_per_sec': 50.00778704315337}, 'model_name': 'Meta-Llama-3.1-405B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1726158505}, id='226471ac-8c52-44bb-baa7-f9d2f8c54477')"
"AIMessage(content='Ich liebe das Programmieren.', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 2.3333333333333335, 'completion_tokens': 6, 'completion_tokens_after_first_per_sec': 106.06729752831038, 'completion_tokens_after_first_per_sec_first_ten': 204.92722183833433, 'completion_tokens_per_sec': 26.32497272023831, 'end_time': 1731535339.9997504, 'is_last_response': True, 'prompt_tokens': 50, 'start_time': 1731535339.7539687, 'time_to_first_token': 0.19864177703857422, 'total_latency': 0.22792046410696848, 'total_tokens': 56, 'total_tokens_per_sec': 245.6997453888909}, 'model_name': 'Meta-Llama-3.1-70B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1731535339}, id='dfe0bee6-b297-472e-ac9d-29906d162dcb')"
]
},
"execution_count": 5,
@@ -243,17 +247,24 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Yer lookin' fer some info on owls, eh? Alright then, matey, settle yerself down with a pint o' grog and listen close.\n",
"Yer lookin' fer some knowledge about owls, eh? Alright then, matey, settle yerself down with a pint o' grog and listen close. \n",
"\n",
"Owls be nocturnal birds o' prey, meanin' they do most o' their huntin' at night. They got big, round eyes that be perfect fer seein' in the dark, like a trusty lantern on a dark sea. Their ears be sharp as a cutlass, too, helpin' 'em pinpoint the slightest sound o' a scurvy rodent scurryin' through the underbrush.\n",
"Owls be a fascinatin' lot, with their big round eyes and silent wings. They be birds o' prey, which means they hunt other creatures fer food. There be over 220 species o' owls, rangin' in size from the tiny Elf Owl (which be smaller than a parrot) to the Great Grey Owl (which be as big as a small eagle).\n",
"\n",
"These birds be known fer their silent flight, like a ghost ship sailin' through the night. Their feathers be special, with a soft, fringed edge that helps 'em sneak up on their prey. And when they strike, it be swift and deadly, like a pirate's sword.\n",
"One o' the most interestin' things about owls be their eyes. They be huge, with some species havin' eyes that be as big as their brains! This lets 'em see in the dark, which be perfect fer nocturnal huntin'. They also have special feathers on their faces that help 'em hear better, and their ears be specially designed to pinpoint sounds.\n",
"\n",
"Owls be found all over the world, from the frozen tundras o' the north to the scorching deserts o' the south. They come in all shapes and sizes, from the tiny elf owl to the great grey owl, which be as big as a small dog.\n",
"Owls be known fer their silent flight, which be due to the special shape o' their wings. They be able to fly without makin' a sound, which be perfect fer sneakin' up on prey. They also be very agile, with some species able to fly through tight spaces and make sharp turns.\n",
"\n",
"Now, I know what ye be thinkin', \"Pirate, what about their hootin'?\" Aye, owls be famous fer their hoots, which be a form o' communication. They use different hoots to warn off predators, attract a mate, or even just to say, \"Shiver me timbers, I be happy to be alive!\"\n",
"Some o' the most common species o' owls include:\n",
"\n",
"So there ye have it, me hearty. Owls be fascinatin' creatures, and I hope ye found this info as interestin' as a chest overflowin' with gold doubloons. Fair winds and following seas!"
"* Barn Owl: A medium-sized owl with a heart-shaped face and a screechin' call.\n",
"* Tawny Owl: A large owl with a distinctive hootin' call and a reddish-brown plumage.\n",
"* Great Horned Owl: A big owl with ear tufts and a deep hootin' call.\n",
"* Snowy Owl: A white owl with a round face and a soft, hootin' call.\n",
"\n",
"Owls be found all over the world, in a variety o' habitats, from forests to deserts. They be an important part o' many ecosystems, helpin' to keep populations o' small mammals and birds under control.\n",
"\n",
"So there ye have it, matey! Owls be amazin' creatures, with their big eyes, silent wings, and sharp talons. Now go forth and spread the word about these fascinatin' birds!"
]
}
],
@@ -283,7 +294,7 @@
{
"data": {
"text/plain": [
"AIMessage(content='The capital of France is Paris.', response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 13, 'completion_tokens': 8, 'completion_tokens_after_first_per_sec': 86.00726488715989, 'completion_tokens_after_first_per_sec_first_ten': 326.92555640828857, 'completion_tokens_per_sec': 21.74539360394493, 'end_time': 1726159287.9987085, 'is_last_response': True, 'prompt_tokens': 43, 'start_time': 1726159287.5738964, 'time_to_first_token': 0.34342360496520996, 'total_latency': 0.36789400760944074, 'total_tokens': 51, 'total_tokens_per_sec': 138.62688422514893}, 'model_name': 'Meta-Llama-3.1-405B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1726159287}, id='9b4ef015-50a2-434b-b980-29f8aa90c3e8')"
"AIMessage(content='The capital of France is Paris.', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 1, 'completion_tokens': 7, 'completion_tokens_after_first_per_sec': 442.126212227688, 'completion_tokens_after_first_per_sec_first_ten': 0, 'completion_tokens_per_sec': 46.28540439646366, 'end_time': 1731535343.0321083, 'is_last_response': True, 'prompt_tokens': 42, 'start_time': 1731535342.8808727, 'time_to_first_token': 0.137664794921875, 'total_latency': 0.15123558044433594, 'total_tokens': 49, 'total_tokens_per_sec': 323.99783077524563}, 'model_name': 'Meta-Llama-3.1-70B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1731535342}, id='c4b8c714-df38-4206-9aa8-fc8231f7275a')"
]
},
"execution_count": 7,
@@ -321,7 +332,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Quantum computers use quantum bits (qubits) to process vast amounts of data simultaneously, leveraging quantum mechanics to solve complex problems exponentially faster than classical computers."
"Quantum computers use quantum bits (qubits) to process info, leveraging superposition and entanglement to perform calculations exponentially faster than classical computers for certain complex problems."
]
}
],
@@ -340,13 +351,202 @@
" print(chunk.content, end=\"\", flush=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tool calling"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"from datetime import datetime\n",
"\n",
"from langchain_core.messages import HumanMessage, ToolMessage\n",
"from langchain_core.tools import tool\n",
"\n",
"\n",
"@tool\n",
"def get_time(kind: str = \"both\") -> str:\n",
" \"\"\"Returns current date, current time or both.\n",
" Args:\n",
" kind(str): date, time or both\n",
" Returns:\n",
" str: current date, current time or both\n",
" \"\"\"\n",
" if kind == \"date\":\n",
" date = datetime.now().strftime(\"%m/%d/%Y\")\n",
" return f\"Current date: {date}\"\n",
" elif kind == \"time\":\n",
" time = datetime.now().strftime(\"%H:%M:%S\")\n",
" return f\"Current time: {time}\"\n",
" else:\n",
" date = datetime.now().strftime(\"%m/%d/%Y\")\n",
" time = datetime.now().strftime(\"%H:%M:%S\")\n",
" return f\"Current date: {date}, Current time: {time}\"\n",
"\n",
"\n",
"tools = [get_time]\n",
"\n",
"\n",
"def invoke_tools(tool_calls, messages):\n",
" available_functions = {tool.name: tool for tool in tools}\n",
" for tool_call in tool_calls:\n",
" selected_tool = available_functions[tool_call[\"name\"]]\n",
" tool_output = selected_tool.invoke(tool_call[\"args\"])\n",
" print(f\"Tool output: {tool_output}\")\n",
" messages.append(ToolMessage(tool_output, tool_call_id=tool_call[\"id\"]))\n",
" return messages"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"llm_with_tools = llm.bind_tools(tools=tools)\n",
"messages = [\n",
" HumanMessage(\n",
" content=\"I need to schedule a meeting for two weeks from today. Can you tell me the exact date of the meeting?\"\n",
" )\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Intermediate model response: [{'name': 'get_time', 'args': {'kind': 'date'}, 'id': 'call_7352ce7a18e24a7c9d', 'type': 'tool_call'}]\n",
"Tool output: Current date: 11/13/2024\n",
"final response: The meeting should be scheduled for two weeks from November 13th, 2024.\n"
]
}
],
"source": [
"response = llm_with_tools.invoke(messages)\n",
"while len(response.tool_calls) > 0:\n",
" print(f\"Intermediate model response: {response.tool_calls}\")\n",
" messages.append(response)\n",
" messages = invoke_tools(response.tool_calls, messages)\n",
" response = llm_with_tools.invoke(messages)\n",
"\n",
"print(f\"final response: {response.content}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Structured Outputs"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Joke(setup='Why did the cat join a band?', punchline='Because it wanted to be the purr-cussionist!')"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from pydantic import BaseModel, Field\n",
"\n",
"\n",
"class Joke(BaseModel):\n",
" \"\"\"Joke to tell user.\"\"\"\n",
"\n",
" setup: str = Field(description=\"The setup of the joke\")\n",
" punchline: str = Field(description=\"The punchline to the joke\")\n",
"\n",
"\n",
"structured_llm = llm.with_structured_output(Joke)\n",
"\n",
"structured_llm.invoke(\"Tell me a joke about cats\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Input Image"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"multimodal_llm = ChatSambaNovaCloud(\n",
" model=\"Llama-3.2-11B-Vision-Instruct\",\n",
" max_tokens=1024,\n",
" temperature=0.7,\n",
" top_k=1,\n",
" top_p=0.01,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The weather in this image is a serene and peaceful atmosphere, with a blue sky and white clouds, suggesting a pleasant day with mild temperatures and gentle breezes.\n"
]
}
],
"source": [
"import base64\n",
"\n",
"import httpx\n",
"\n",
"image_url = (\n",
" \"https://images.pexels.com/photos/147411/italy-mountains-dawn-daybreak-147411.jpeg\"\n",
")\n",
"image_data = base64.b64encode(httpx.get(image_url).content).decode(\"utf-8\")\n",
"\n",
"message = HumanMessage(\n",
" content=[\n",
" {\"type\": \"text\", \"text\": \"describe the weather in this image in 1 sentence\"},\n",
" {\n",
" \"type\": \"image_url\",\n",
" \"image_url\": {\"url\": f\"data:image/jpeg;base64,{image_data}\"},\n",
" },\n",
" ],\n",
")\n",
"response = multimodal_llm.invoke([message])\n",
"print(response.content)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## API reference\n",
"\n",
"For detailed documentation of all ChatSambaNovaCloud features and configurations head to the API reference: https://api.python.langchain.com/en/latest/chat_models/langchain_community.chat_models.sambanova.ChatSambaNovaCloud.html"
"For detailed documentation of all ChatSambaNovaCloud features and configurations head to the API reference: https://python.langchain.com/api_reference/community/chat_models/langchain_community.chat_models.sambanova.ChatSambaNovaCloud.html"
]
}
],
@@ -366,7 +566,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.19"
"version": "3.9.20"
}
},
"nbformat": 4,

View File

@@ -0,0 +1,268 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Outlines\n",
"\n",
"This will help you getting started with Outlines LLM. For detailed documentation of all Outlines features and configurations head to the [API reference](https://api.python.langchain.com/en/latest/llms/outlines.llms.Outlines.html).\n",
"\n",
"[Outlines](https://github.com/outlines-dev/outlines) is a library for constrained language generation. It allows you to use large language models (LLMs) with various backends while applying constraints to the generated output.\n",
"\n",
"## Overview\n",
"\n",
"### Integration details\n",
"| Class | Package | Local | Serializable | JS support | Package downloads | Package latest |\n",
"| :--- | :--- | :---: | :---: | :---: | :---: | :---: |\n",
"| [Outlines](https://python.langchain.com/api_reference/community/llms/langchain_community.llms.outlines.Outlines.html) | [langchain-community](https://python.langchain.com/api_reference/community/index.html) | ✅ | beta | ❌ | ![PyPI - Downloads](https://img.shields.io/pypi/dm/langchain-community?style=flat-square&label=%20) | ![PyPI - Version](https://img.shields.io/pypi/v/langchain-community?style=flat-square&label=%20) |\n",
"\n",
"## Setup\n",
"\n",
"To access Outlines models you'll need to have an internet connection to download the model weights from huggingface. Depending on the backend you need to install the required dependencies (see [Outlines docs](https://dottxt-ai.github.io/outlines/latest/installation/))\n",
"\n",
"### Credentials\n",
"\n",
"There is no built-in auth mechanism for Outlines.\n",
"\n",
"## Installation\n",
"\n",
"The LangChain Outlines integration lives in the `langchain-community` package and requires the `outlines` library:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%pip install -qU langchain-community outlines"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Instantiation\n",
"\n",
"Now we can instantiate our model object and generate chat completions:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.llms import Outlines\n",
"\n",
"# For use with llamacpp backend\n",
"model = Outlines(model=\"microsoft/Phi-3-mini-4k-instruct\", backend=\"llamacpp\")\n",
"\n",
"# For use with vllm backend (not available on Mac)\n",
"model = Outlines(model=\"microsoft/Phi-3-mini-4k-instruct\", backend=\"vllm\")\n",
"\n",
"# For use with mlxlm backend (only available on Mac)\n",
"model = Outlines(model=\"microsoft/Phi-3-mini-4k-instruct\", backend=\"mlxlm\")\n",
"\n",
"# For use with huggingface transformers backend\n",
"model = Outlines(\n",
" model=\"microsoft/Phi-3-mini-4k-instruct\"\n",
") # defaults to backend=\"transformers\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Invocation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model.invoke(\"Hello how are you?\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Chaining"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import PromptTemplate\n",
"\n",
"prompt = PromptTemplate.from_template(\"How to say {input} in {output_language}:\\n\")\n",
"\n",
"chain = prompt | model\n",
"chain.invoke(\n",
" {\n",
" \"output_language\": \"German\",\n",
" \"input\": \"I love programming.\",\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Streaming\n",
"\n",
"Outlines supports streaming of tokens:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for chunk in model.stream(\"Count to 10 in French:\"):\n",
" print(chunk, end=\"\", flush=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Constrained Generation\n",
"\n",
"Outlines allows you to apply various constraints to the generated output:\n",
"\n",
"#### Regex Constraint"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model.regex = r\"((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\"\n",
"response = model.invoke(\"What is the IP address of Google's DNS server?\")\n",
"\n",
"response"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Type Constraints"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model.type_constraints = int\n",
"response = model.invoke(\"What is the answer to life, the universe, and everything?\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### JSON Schema"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pydantic import BaseModel\n",
"\n",
"\n",
"class Person(BaseModel):\n",
" name: str\n",
"\n",
"\n",
"model.json_schema = Person\n",
"response = model.invoke(\"Who is the author of LangChain?\")\n",
"person = Person.model_validate_json(response)\n",
"\n",
"person"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Grammar Constraint"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model.grammar = \"\"\"\n",
"?start: expression\n",
"?expression: term ((\"+\" | \"-\") term)\n",
"?term: factor ((\"\" | \"/\") factor)\n",
"?factor: NUMBER | \"-\" factor | \"(\" expression \")\"\n",
"%import common.NUMBER\n",
"%import common.WS\n",
"%ignore WS\n",
"\"\"\"\n",
"response = model.invoke(\"Give me a complex arithmetic expression:\")\n",
"\n",
"response"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## API reference\n",
"\n",
"For detailed documentation of all ChatOutlines features and configurations head to the API reference: https://api.python.langchain.com/en/latest/chat_models/outlines.chat_models.ChatOutlines.html\n",
"\n",
"## Outlines Documentation: \n",
"\n",
"https://dottxt-ai.github.io/outlines/latest/"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.9.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,201 @@
# Outlines
>[Outlines](https://github.com/dottxt-ai/outlines) is a Python library for constrained language generation. It provides a unified interface to various language models and allows for structured generation using techniques like regex matching, type constraints, JSON schemas, and context-free grammars.
Outlines supports multiple backends, including:
- Hugging Face Transformers
- llama.cpp
- vLLM
- MLX
This integration allows you to use Outlines models with LangChain, providing both LLM and chat model interfaces.
## Installation and Setup
To use Outlines with LangChain, you'll need to install the Outlines library:
```bash
pip install outlines
```
Depending on the backend you choose, you may need to install additional dependencies:
- For Transformers: `pip install transformers torch datasets`
- For llama.cpp: `pip install llama-cpp-python`
- For vLLM: `pip install vllm`
- For MLX: `pip install mlx`
## LLM
To use Outlines as an LLM in LangChain, you can use the `Outlines` class:
```python
from langchain_community.llms import Outlines
```
## Chat Models
To use Outlines as a chat model in LangChain, you can use the `ChatOutlines` class:
```python
from langchain_community.chat_models import ChatOutlines
```
## Model Configuration
Both `Outlines` and `ChatOutlines` classes share similar configuration options:
```python
model = Outlines(
model="meta-llama/Llama-2-7b-chat-hf", # Model identifier
backend="transformers", # Backend to use (transformers, llamacpp, vllm, or mlxlm)
max_tokens=256, # Maximum number of tokens to generate
stop=["\n"], # Optional list of stop strings
streaming=True, # Whether to stream the output
# Additional parameters for structured generation:
regex=None,
type_constraints=None,
json_schema=None,
grammar=None,
# Additional model parameters:
model_kwargs={"temperature": 0.7}
)
```
### Model Identifier
The `model` parameter can be:
- A Hugging Face model name (e.g., "meta-llama/Llama-2-7b-chat-hf")
- A local path to a model
- For GGUF models, the format is "repo_id/file_name" (e.g., "TheBloke/Llama-2-7B-Chat-GGUF/llama-2-7b-chat.Q4_K_M.gguf")
### Backend Options
The `backend` parameter specifies which backend to use:
- `"transformers"`: For Hugging Face Transformers models (default)
- `"llamacpp"`: For GGUF models using llama.cpp
- `"transformers_vision"`: For vision-language models (e.g., LLaVA)
- `"vllm"`: For models using the vLLM library
- `"mlxlm"`: For models using the MLX framework
### Structured Generation
Outlines provides several methods for structured generation:
1. **Regex Matching**:
```python
model = Outlines(
model="meta-llama/Llama-2-7b-chat-hf",
regex=r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)"
)
```
This will ensure the generated text matches the specified regex pattern (in this case, a valid IP address).
2. **Type Constraints**:
```python
model = Outlines(
model="meta-llama/Llama-2-7b-chat-hf",
type_constraints=int
)
```
This restricts the output to valid Python types (int, float, bool, datetime.date, datetime.time, datetime.datetime).
3. **JSON Schema**:
```python
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
model = Outlines(
model="meta-llama/Llama-2-7b-chat-hf",
json_schema=Person
)
```
This ensures the generated output adheres to the specified JSON schema or Pydantic model.
4. **Context-Free Grammar**:
```python
model = Outlines(
model="meta-llama/Llama-2-7b-chat-hf",
grammar="""
?start: expression
?expression: term (("+" | "-") term)*
?term: factor (("*" | "/") factor)*
?factor: NUMBER | "-" factor | "(" expression ")"
%import common.NUMBER
"""
)
```
This generates text that adheres to the specified context-free grammar in EBNF format.
## Usage Examples
### LLM Example
```python
from langchain_community.llms import Outlines
llm = Outlines(model="meta-llama/Llama-2-7b-chat-hf", max_tokens=100)
result = llm.invoke("Tell me a short story about a robot.")
print(result)
```
### Chat Model Example
```python
from langchain_community.chat_models import ChatOutlines
from langchain_core.messages import HumanMessage, SystemMessage
chat = ChatOutlines(model="meta-llama/Llama-2-7b-chat-hf", max_tokens=100)
messages = [
SystemMessage(content="You are a helpful AI assistant."),
HumanMessage(content="What's the capital of France?")
]
result = chat.invoke(messages)
print(result.content)
```
### Streaming Example
```python
from langchain_community.chat_models import ChatOutlines
from langchain_core.messages import HumanMessage
chat = ChatOutlines(model="meta-llama/Llama-2-7b-chat-hf", streaming=True)
for chunk in chat.stream("Tell me a joke about programming."):
print(chunk.content, end="", flush=True)
print()
```
### Structured Output Example
```python
from langchain_community.llms import Outlines
from pydantic import BaseModel
class MovieReview(BaseModel):
title: str
rating: int
summary: str
llm = Outlines(
model="meta-llama/Llama-2-7b-chat-hf",
json_schema=MovieReview
)
result = llm.invoke("Write a short review for the movie 'Inception'.")
print(result)
```
## Additional Features
### Tokenizer Access
You can access the underlying tokenizer for the model:
```python
tokenizer = llm.tokenizer
encoded = tokenizer.encode("Hello, world!")
decoded = tokenizer.decode(encoded)
```

View File

@@ -9,7 +9,7 @@ For more info how to get access to GigaChat [follow here](https://developers.sbe
GigaChat package can be installed via pip from PyPI:
```bash
pip install gigachat
pip install langchain-gigachat
```
## LLMs
@@ -25,7 +25,7 @@ from langchain_community.llms import GigaChat
See a [usage example](/docs/integrations/chat/gigachat).
```python
from langchain_community.chat_models import GigaChat
from langchain_gigachat.chat_models import GigaChat
```
## Embeddings
@@ -33,5 +33,5 @@ from langchain_community.chat_models import GigaChat
See a [usage example](/docs/integrations/text_embedding/gigachat).
```python
from langchain_community.embeddings import GigaChatEmbeddings
from langchain_gigachat.embeddings import GigaChatEmbeddings
```

View File

@@ -136,7 +136,7 @@
},
"outputs": [],
"source": [
"from langchain.chains.query_constructor.base import AttributeInfo\n",
"from langchain.chains.query_constructor.schema import AttributeInfo\n",
"from langchain.retrievers.self_query.base import SelfQueryRetriever\n",
"from langchain_openai import OpenAI\n",
"\n",

View File

@@ -45,7 +45,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": null,
"id": "36521c2a",
"metadata": {},
"outputs": [],
@@ -53,8 +53,10 @@
"import getpass\n",
"import os\n",
"\n",
"if not os.getenv(\"OPENAI_API_KEY\"):\n",
" os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"Enter your AzureOpenAI API key: \")"
"if not os.getenv(\"AZURE_OPENAI_API_KEY\"):\n",
" os.environ[\"AZURE_OPENAI_API_KEY\"] = getpass.getpass(\n",
" \"Enter your AzureOpenAI API key: \"\n",
" )"
]
},
{

View File

@@ -31,12 +31,12 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"id": "282239c8-e03a-4abc-86c1-ca6120231a20",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.embeddings import BedrockEmbeddings\n",
"from langchain_aws import BedrockEmbeddings\n",
"\n",
"embeddings = BedrockEmbeddings(\n",
" credentials_profile_name=\"bedrock-admin\", region_name=\"us-east-1\"\n",

View File

@@ -15,11 +15,14 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
"collapsed": false,
"pycharm": {
"is_executing": true
}
},
"outputs": [],
"source": [
"%pip install --upgrade --quiet gigachat"
"%pip install --upgrade --quiet langchain-gigachat"
]
},
{
@@ -50,13 +53,13 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from langchain_community.embeddings import GigaChatEmbeddings\n",
"from langchain_gigachat import GigaChatEmbeddings\n",
"\n",
"embeddings = GigaChatEmbeddings(verify_ssl_certs=False, scope=\"GIGACHAT_API_PERS\")"
]
@@ -81,13 +84,7 @@
"outputs": [
{
"data": {
"text/plain": [
"[0.8398333191871643,\n",
" -0.14180311560630798,\n",
" -0.6161925792694092,\n",
" -0.17103666067123413,\n",
" 1.2884578704833984]"
]
"text/plain": "[0.8398333191871643,\n -0.14180311560630798,\n -0.6161925792694092,\n -0.17103666067123413,\n 1.2884578704833984]"
},
"execution_count": 8,
"metadata": {},

View File

@@ -11,6 +11,14 @@ import { CategoryTable, IndexTable } from "@theme/FeatureTables";
This page documents integrations with various model providers that allow you to use embeddings in LangChain.
import EmbeddingTabs from "@theme/EmbeddingTabs";
<EmbeddingTabs/>
```python
embeddings.embed_query("Hello, world!")
```
<CategoryTable category="text_embedding" />
## All embedding models

View File

@@ -3,12 +3,24 @@ sidebar_position: 0
sidebar_class_name: hidden
---
# Vectorstores
# Vector stores
import { CategoryTable, IndexTable } from "@theme/FeatureTables";
A [vector store](/docs/concepts/vectorstores) stores [embedded](/docs/concepts/embedding_models) data and performs similarity search.
**Select embedding model:**
import EmbeddingTabs from "@theme/EmbeddingTabs";
<EmbeddingTabs/>
**Select vector store:**
import VectorStoreTabs from "@theme/VectorStoreTabs";
<VectorStoreTabs/>
<CategoryTable category="vectorstores" />
## All Vectorstores

View File

@@ -33,7 +33,7 @@ Concretely, the framework consists of the following open-source libraries:
- **`langchain`**: Chains, agents, and retrieval strategies that make up an application's cognitive architecture.
- **`langchain-community`**: Third-party integrations that are community maintained.
- **[LangGraph](https://langchain-ai.github.io/langgraph)**: Build robust and stateful multi-actor applications with LLMs by modeling steps as edges and nodes in a graph. Integrates smoothly with LangChain, but can be used without it.
- **[LangServe](/docs/langserve)**: Deploy LangChain chains as REST APIs.
- **[LangGraphPlatform](https://langchain-ai.github.io/langgraph/concepts/#langgraph-platform)**: Deploy LLM applications built with LangGraph to production.
- **[LangSmith](https://docs.smith.langchain.com)**: A developer platform that lets you debug, test, evaluate, and monitor LLM applications.

View File

@@ -318,7 +318,7 @@
"source": [
"## Conclusion\n",
"\n",
"That's it! In this tutorial you've learned how to create your first simple LLM application. You've learned how to work with language models, how to how to create a prompt template, and how to get great observability into chains you create with LangSmith.\n",
"That's it! In this tutorial you've learned how to create your first simple LLM application. You've learned how to work with language models, how to create a prompt template, and how to get great observability into chains you create with LangSmith.\n",
"\n",
"This just scratches the surface of what you will want to learn to become a proficient AI Engineer. Luckily - we've got a lot of other resources!\n",
"\n",

View File

@@ -156,7 +156,7 @@
"metadata": {},
"outputs": [],
"source": [
"%pip install --upgrade --quiet tiktoken langchain langgraph beautifulsoup4\n",
"%pip install --upgrade --quiet tiktoken langchain langgraph beautifulsoup4 langchain-community\n",
"\n",
"# Set env var OPENAI_API_KEY or load from a .env file\n",
"# import dotenv\n",

View File

@@ -132,7 +132,7 @@ should ensure that they are passing Pydantic 2 objects to these APIs rather than
Pydantic 1 objects (created via the `pydantic.v1` namespace of pydantic 2).
:::caution
While `v1` objets may be accepted by some of these APIs, users are advised to
While `v1` objects may be accepted by some of these APIs, users are advised to
use Pydantic 2 objects to avoid future issues.
:::

View File

@@ -184,7 +184,18 @@ if __name__ == "__main__":
source_paths_stripped = [p.strip() for p in source_path_strs]
source_paths = [intermediate_docs_dir / p for p in source_paths_stripped if p]
else:
source_paths = intermediate_docs_dir.glob("**/*.ipynb")
original_paths = list(intermediate_docs_dir.glob("**/*.ipynb"))
# exclude files that exist in output directory and are newer
relative_paths = [p.relative_to(intermediate_docs_dir) for p in original_paths]
out_paths = [
output_docs_dir / p.parent / (p.stem + ".md") for p in relative_paths
]
source_paths = [
p
for p, o in zip(original_paths, out_paths)
if not o.exists() or o.stat().st_mtime < p.stat().st_mtime
]
print(f"rebuilding {len(source_paths)}/{len(relative_paths)} notebooks")
with multiprocessing.Pool() as pool:
pool.map(

View File

@@ -204,34 +204,34 @@ module.exports = {
},
{
type: "category",
label: "LLMs",
label: "Retrievers",
collapsible: false,
items: [
{
type: "autogenerated",
dirName: "integrations/llms",
dirName: "integrations/retrievers",
className: "hidden",
},
],
link: {
type: "doc",
id: "integrations/llms/index",
id: "integrations/retrievers/index",
},
},
{
type: "category",
label: "Embedding models",
label: "Tools/Toolkits",
collapsible: false,
items: [
{
type: "autogenerated",
dirName: "integrations/text_embedding",
dirName: "integrations/tools",
className: "hidden",
},
],
link: {
type: "doc",
id: "integrations/text_embedding/index",
id: "integrations/tools/index",
},
},
{
@@ -268,50 +268,18 @@ module.exports = {
},
{
type: "category",
label: "Retrievers",
label: "Embedding models",
collapsible: false,
items: [
{
type: "autogenerated",
dirName: "integrations/retrievers",
dirName: "integrations/text_embedding",
className: "hidden",
},
],
link: {
type: "doc",
id: "integrations/retrievers/index",
},
},
{
type: "category",
label: "Tools/Toolkits",
collapsible: false,
items: [
{
type: "autogenerated",
dirName: "integrations/tools",
className: "hidden",
},
],
link: {
type: "doc",
id: "integrations/tools/index",
},
},
{
type: "category",
label: "Key-value stores",
collapsible: false,
items: [
{
type: "autogenerated",
dirName: "integrations/stores",
className: "hidden",
},
],
link: {
type: "doc",
id: "integrations/stores/index",
id: "integrations/text_embedding/index",
},
},
{
@@ -319,6 +287,38 @@ module.exports = {
label: "Other",
collapsed: true,
items: [
{
type: "category",
label: "LLMs",
collapsible: false,
items: [
{
type: "autogenerated",
dirName: "integrations/llms",
className: "hidden",
},
],
link: {
type: "doc",
id: "integrations/llms/index",
},
},
{
type: "category",
label: "Key-value stores",
collapsible: false,
items: [
{
type: "autogenerated",
dirName: "integrations/stores",
className: "hidden",
},
],
link: {
type: "doc",
id: "integrations/stores/index",
},
},
{
type: "category",
label: "Document transformers",

View File

@@ -78,7 +78,7 @@ export default function ChatModelTabs(props) {
azureParams ??
`\n azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],\n azure_deployment=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],\n openai_api_version=os.environ["AZURE_OPENAI_API_VERSION"],\n`;
const nvidiaParamsOrDefault = nvidiaParams ?? `model="meta/llama3-70b-instruct"`
const awsBedrockParamsOrDefault = awsBedrockParams ?? `model_id="anthropic.claude-3-5-sonnet-20240620-v1:0"`;
const awsBedrockParamsOrDefault = awsBedrockParams ?? `model="anthropic.claude-3-5-sonnet-20240620-v1:0",\n beta_use_converse_api=True`;
const llmVarName = customVarName ?? "model";
@@ -114,11 +114,20 @@ export default function ChatModelTabs(props) {
value: "Google",
label: "Google",
text: `from langchain_google_vertexai import ChatVertexAI\n\n${llmVarName} = ChatVertexAI(${googleParamsOrDefault})`,
apiKeyName: "GOOGLE_API_KEY",
apiKeyText: "# Ensure your VertexAI credentials are configured",
packageName: "langchain-google-vertexai",
default: false,
shouldHide: hideGoogle,
},
{
value: "AWS",
label: "AWS",
text: `from langchain_aws import ChatBedrock\n\n${llmVarName} = ChatBedrock(${awsBedrockParamsOrDefault})`,
apiKeyText: "# Ensure your AWS credentials are configured",
packageName: "langchain-aws",
default: false,
shouldHide: hideAWS,
},
{
value: "Cohere",
label: "Cohere",
@@ -173,15 +182,6 @@ export default function ChatModelTabs(props) {
default: false,
shouldHide: hideTogether,
},
{
value: "AWS",
label: "AWS",
text: `from langchain_aws import ChatBedrock\n\n${llmVarName} = ChatBedrock(${awsBedrockParamsOrDefault})`,
apiKeyText: "# Ensure your AWS credentials are configured",
packageName: "langchain-aws",
default: false,
shouldHide: hideAWS,
},
];
return (

View File

@@ -7,15 +7,41 @@ export default function EmbeddingTabs(props) {
const {
openaiParams,
hideOpenai,
azureOpenaiParams,
hideAzureOpenai,
googleParams,
hideGoogle,
awsParams,
hideAws,
huggingFaceParams,
hideHuggingFace,
ollamaParams,
hideOllama,
cohereParams,
hideCohere,
mistralParams,
hideMistral,
nomicParams,
hideNomic,
nvidiaParams,
hideNvidia,
fakeEmbeddingParams,
hideFakeEmbedding,
customVarName,
} = props;
const openAIParamsOrDefault = openaiParams ?? `model="text-embedding-3-large"`;
const azureParamsOrDefault =
azureOpenaiParams ??
`\n azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],\n azure_deployment=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],\n openai_api_version=os.environ["AZURE_OPENAI_API_VERSION"],\n`;
const googleParamsOrDefault = googleParams ?? `model="text-embedding-004"`;
const awsParamsOrDefault = awsParams ?? `model_id="amazon.titan-embed-text-v2:0"`;
const huggingFaceParamsOrDefault = huggingFaceParams ?? `model="sentence-transformers/all-mpnet-base-v2"`;
const ollamaParamsOrDefault = ollamaParams ?? `model="llama3"`;
const cohereParamsOrDefault = cohereParams ?? `model="embed-english-v3.0"`;
const mistralParamsOrDefault = mistralParams ?? `model="mistral-embed"`;
const nomicsParamsOrDefault = nomicParams ?? `model="nomic-embed-text-v1.5"`;
const nvidiaParamsOrDefault = nvidiaParams ?? `model="NV-Embed-QA"`;
const fakeEmbeddingParamsOrDefault = fakeEmbeddingParams ?? `size=4096`;
const embeddingVarName = customVarName ?? "embeddings";
@@ -30,6 +56,33 @@ export default function EmbeddingTabs(props) {
default: true,
shouldHide: hideOpenai,
},
{
value: "Azure",
label: "Azure",
text: `from langchain_openai import AzureOpenAIEmbeddings\n\n${embeddingVarName} = AzureOpenAIEmbeddings(${azureParamsOrDefault})`,
apiKeyName: "AZURE_OPENAI_API_KEY",
packageName: "langchain-openai",
default: false,
shouldHide: hideAzureOpenai,
},
{
value: "Google",
label: "Google",
text: `from langchain_google_vertexai import VertexAIEmbeddings\n\n${embeddingVarName} = VertexAIEmbeddings(${googleParamsOrDefault})`,
apiKeyName: undefined,
packageName: "langchain-google-vertexai",
default: false,
shouldHide: hideGoogle,
},
{
value: "AWS",
label: "AWS",
text: `from langchain_aws import BedrockEmbeddings\n\n${embeddingVarName} = BedrockEmbeddings(${awsParamsOrDefault})`,
apiKeyName: undefined,
packageName: "langchain-aws",
default: false,
shouldHide: hideAws,
},
{
value: "HuggingFace",
label: "HuggingFace",
@@ -40,9 +93,54 @@ export default function EmbeddingTabs(props) {
shouldHide: hideHuggingFace,
},
{
value: "Fake Embedding",
label: "Fake Embedding",
text: `from langchain_core.embeddings import FakeEmbeddings\n\n${embeddingVarName} = FakeEmbeddings(${fakeEmbeddingParamsOrDefault})`,
value: "Ollama",
label: "Ollama",
text: `from langchain_ollama import OllamaEmbeddings\n\n${embeddingVarName} = OllamaEmbeddings(${ollamaParamsOrDefault})`,
apiKeyName: undefined,
packageName: "langchain-ollama",
default: false,
shouldHide: hideOllama,
},
{
value: "Cohere",
label: "Cohere",
text: `from langchain_cohere import CohereEmbeddings\n\n${embeddingVarName} = CohereEmbeddings(${cohereParamsOrDefault})`,
apiKeyName: "COHERE_API_KEY",
packageName: "langchain-cohere",
default: false,
shouldHide: hideCohere,
},
{
value: "MistralAI",
label: "MistralAI",
text: `from langchain_mistralai import MistralAIEmbeddings\n\n${embeddingVarName} = MistralAIEmbeddings(${mistralParamsOrDefault})`,
apiKeyName: "MISTRALAI_API_KEY",
packageName: "langchain-mistralai",
default: false,
shouldHide: hideMistral,
},
{
value: "Nomic",
label: "Nomic",
text: `from langchain_nomic import NomicEmbeddings\n\n${embeddingVarName} = NomicEmbeddings(${nomicsParamsOrDefault})`,
apiKeyName: "NOMIC_API_KEY",
packageName: "langchain-nomic",
default: false,
shouldHide: hideNomic,
},
{
value: "NVIDIA",
label: "NVIDIA",
text: `from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings\n\n${embeddingVarName} = NVIDIAEmbeddings(${nvidiaParamsOrDefault})`,
apiKeyName: "NVIDIA_API_KEY",
packageName: "langchain-nvidia-ai-endpoints",
default: false,
shouldHide: hideNvidia,
},
{
value: "Fake",
label: "Fake",
text: `from langchain_core.embeddings import DeterministicFakeEmbedding\n\n${embeddingVarName} = DeterministicFakeEmbedding(${fakeEmbeddingParamsOrDefault})`,
apiKeyName: undefined,
packageName: "langchain-core",
default: false,
@@ -55,9 +153,7 @@ export default function EmbeddingTabs(props) {
{tabItems
.filter((tabItem) => !tabItem.shouldHide)
.map((tabItem) => {
const apiKeyText = tabItem.apiKeyName ? `import getpass
os.environ["${tabItem.apiKeyName}"] = getpass.getpass()` : '';
const apiKeyText = tabItem.apiKeyName ? `import getpass\n\nos.environ["${tabItem.apiKeyName}"] = getpass.getpass()` : '';
return (
<TabItem
value={tabItem.value}

View File

@@ -406,7 +406,7 @@ const FEATURE_TABLES = {
},
{
name: "NVIDIA",
link: "NVIDIAEmbeddings",
link: "nvidia_ai_endpoints",
package: "langchain-nvidia",
apiLink: "https://python.langchain.com/api_reference/nvidia_ai_endpoints/embeddings/langchain_nvidia_ai_endpoints.embeddings.NVIDIAEmbeddings.html"
},

View File

@@ -0,0 +1,92 @@
import React from "react";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import CodeBlock from "@theme-original/CodeBlock";
export default function VectorStoreTabs(props) {
const { customVarName } = props;
const vectorStoreVarName = customVarName ?? "vector_store";
const tabItems = [
{
value: "In-memory",
label: "In-memory",
text: `from langchain_core.vector_stores import InMemoryVectorStore\n\n${vectorStoreVarName} = InMemoryVectorStore(embeddings)`,
packageName: "langchain-core",
default: true,
},
{
value: "AstraDB",
label: "AstraDB",
text: `from langchain_astradb import AstraDBVectorStore\n\n${vectorStoreVarName} = AstraDBVectorStore(\n embedding=embeddings,\n api_endpoint=ASTRA_DB_API_ENDPOINT,\n collection_name="astra_vector_langchain",\n token=ASTRA_DB_APPLICATION_TOKEN,\n namespace=ASTRA_DB_NAMESPACE,\n)`,
packageName: "langchain-astradb",
default: false,
},
{
value: "Chroma",
label: "Chroma",
text: `from langchain_chroma import Chroma\n\n${vectorStoreVarName} = Chroma(embedding_function=embeddings)`,
packageName: "langchain-chroma",
default: false,
},
{
value: "FAISS",
label: "FAISS",
text: `from langchain_community.vectorstores import FAISS\n\n${vectorStoreVarName} = FAISS(embedding_function=embeddings)`,
packageName: "langchain-community",
default: false,
},
{
value: "Milvus",
label: "Milvus",
text: `from langchain_milvus import Milvus\n\n${vectorStoreVarName} = Milvus(embedding_function=embeddings)`,
packageName: "langchain-milvus",
default: false,
},
{
value: "MongoDB",
label: "MongoDB",
text: `from langchain_mongodb import MongoDBAtlasVectorSearch\n\n${vectorStoreVarName} = MongoDBAtlasVectorSearch(\n embedding=embeddings,\n collection=MONGODB_COLLECTION,\n index_name=ATLAS_VECTOR_SEARCH_INDEX_NAME,\n relevance_score_fn="cosine",\n)`,
packageName: "langchain-mongodb",
default: false,
},
{
value: "PGVector",
label: "PGVector",
text: `from langchain_postgres import PGVector\n\n${vectorStoreVarName} = PGVector(\n embedding=embeddings,\n collection_name="my_docs",\n connection="postgresql+psycopg://...",\n)`,
packageName: "langchain-postgres",
default: false,
},
{
value: "Pinecone",
label: "Pinecone",
text: `from langchain_pinecone import PineconeVectorStore\nfrom pinecone import Pinecone\n\npc = Pinecone(api_key=...)\nindex = pc.Index(index_name)\n\n${vectorStoreVarName} = PineconeVectorStore(embedding=embeddings, index=index)`,
packageName: "langchain-pinecone",
default: false,
},
{
value: "Qdrant",
label: "Qdrant",
text: `from langchain_qdrant import QdrantVectorStore\nfrom qdrant_client import QdrantClient\n\nclient = QdrantClient(":memory:")\n${vectorStoreVarName} = QdrantVectorStore(\n client=client,\n collection_name="test",\n embedding=embeddings,\n)`,
packageName: "langchain-qdrant",
default: false,
},
];
return (
<Tabs groupId="vectorStoreTabs">
{tabItems.map((tabItem) => (
<TabItem
key={tabItem.value}
value={tabItem.value}
label={tabItem.label}
default={tabItem.default}
>
<CodeBlock language="bash">{`pip install -qU ${tabItem.packageName}`}</CodeBlock>
<CodeBlock language="python">{tabItem.text}</CodeBlock>
</TabItem>
))}
</Tabs>
);
}

5
libs/cli/poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -270,6 +270,7 @@ description = "Python bindings for GritQL"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "gritql-0.1.4-py2.py3-none-any.whl", hash = "sha256:a71d27c2bfb904d0150ccb0c28878ef640940b9540b6f1ef964133be1ddcb0e7"},
{file = "gritql-0.1.4-py3-none-any.whl", hash = "sha256:6e9f8c638bbf3dda58222832c976c716a1ca02a920e7549df58bf1a0bb9ebeef"},
{file = "gritql-0.1.4.tar.gz", hash = "sha256:487d0c1a98cb17cc9681121e53ac15f39e1cb87c4317a2ca1872e33d3d3a0a47"},
]
@@ -1365,4 +1366,4 @@ serve = []
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<4.0"
content-hash = "733676371ee89686b906db3bc190d39128fb664bf34d9b3ccd66aac75c8cc2aa"
content-hash = "ed6ba14db90499ff27fefaab277bb8b879a48e024f4dabf944d76d8f7375002b"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "langchain-cli"
version = "0.0.31"
version = "0.0.32"
description = "CLI for interacting with LangChain"
authors = ["Erick Friis <erick@langchain.dev>"]
readme = "README.md"
@@ -14,10 +14,10 @@ license = "MIT"
[tool.poetry.dependencies]
python = ">=3.9,<4.0"
typer = { extras = ["all"], version = "^0.9.0" }
gitpython = "^3.1.40"
gitpython = "^3"
langserve = { extras = ["all"], version = ">=0.0.51" }
uvicorn = "^0.23.2"
tomlkit = "^0.12.2"
uvicorn = ">=0.23,<1.0"
tomlkit = ">=0.12"
gritql = "^0.1.1"
[tool.poetry.scripts]

View File

@@ -55,6 +55,7 @@ openai<2
openapi-pydantic>=0.3.2,<0.4
oracle-ads>=2.9.1,<3
oracledb>=2.2.0,<3
outlines[test]>=0.1.0,<0.2
pandas>=2.0.1,<3
pdfminer-six>=20221105,<20240706
pgvector>=0.1.6,<0.2

View File

@@ -143,6 +143,7 @@ if TYPE_CHECKING:
from langchain_community.chat_models.openai import (
ChatOpenAI,
)
from langchain_community.chat_models.outlines import ChatOutlines
from langchain_community.chat_models.pai_eas_endpoint import (
PaiEasChatEndpoint,
)
@@ -228,6 +229,7 @@ __all__ = [
"ChatOCIModelDeploymentTGI",
"ChatOllama",
"ChatOpenAI",
"ChatOutlines",
"ChatPerplexity",
"ChatReka",
"ChatPremAI",
@@ -294,6 +296,7 @@ _module_lookup = {
"ChatOCIModelDeploymentTGI": "langchain_community.chat_models.oci_data_science",
"ChatOllama": "langchain_community.chat_models.ollama",
"ChatOpenAI": "langchain_community.chat_models.openai",
"ChatOutlines": "langchain_community.chat_models.outlines",
"ChatReka": "langchain_community.chat_models.reka",
"ChatPerplexity": "langchain_community.chat_models.perplexity",
"ChatSambaNovaCloud": "langchain_community.chat_models.sambanova",

View File

@@ -13,6 +13,7 @@ from typing import (
Type,
)
from langchain_core._api.deprecation import deprecated
from langchain_core.callbacks import (
AsyncCallbackManagerForLLMRun,
CallbackManagerForLLMRun,
@@ -113,6 +114,11 @@ def _convert_delta_to_message_chunk(
return default_class(content=content) # type: ignore[call-arg]
@deprecated(
since="0.3.5",
removal="1.0",
alternative_import="langchain_gigachat.GigaChat",
)
class GigaChat(_BaseGigaChat, BaseChatModel):
"""`GigaChat` large language models API.

View File

@@ -35,7 +35,7 @@ from langchain_core.messages import (
)
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
from langchain_core.utils import convert_to_secret_str, get_from_env
from pydantic import AliasChoices, Field, SecretStr, model_validator
from pydantic import AliasChoices, ConfigDict, Field, SecretStr, model_validator
from typing_extensions import Self
_DEFAULT_BASE_URL = "https://clovastudio.stream.ntruss.com"
@@ -51,6 +51,12 @@ def _convert_chunk_to_message_chunk(
role = message.get("role")
content = message.get("content") or ""
if sse.event == "result":
response_metadata = {}
if "stopReason" in sse_data:
response_metadata["stopReason"] = sse_data["stopReason"]
return AIMessageChunk(content="", response_metadata=response_metadata)
if role == "user" or default_class == HumanMessageChunk:
return HumanMessageChunk(content=content)
elif role == "assistant" or default_class == AIMessageChunk:
@@ -124,8 +130,6 @@ async def _aiter_sse(
event_data = sse.json()
if sse.event == "signal" and event_data.get("data", {}) == "[DONE]":
return
if sse.event == "result":
return
yield sse
@@ -210,10 +214,7 @@ class ChatClovaX(BaseChatModel):
timeout: int = Field(gt=0, default=90)
max_retries: int = Field(ge=1, default=2)
class Config:
"""Configuration for this pydantic object."""
populate_by_name = True
model_config = ConfigDict(populate_by_name=True, protected_namespaces=())
@property
def _default_params(self) -> Dict[str, Any]:
@@ -286,7 +287,7 @@ class ChatClovaX(BaseChatModel):
if not self.ncp_apigw_api_key:
self.ncp_apigw_api_key = convert_to_secret_str(
get_from_env("ncp_apigw_api_key", "NCP_APIGW_API_KEY")
get_from_env("ncp_apigw_api_key", "NCP_APIGW_API_KEY", "")
)
if not self.base_url:
@@ -311,22 +312,28 @@ class ChatClovaX(BaseChatModel):
return self
def default_headers(self) -> Dict[str, Any]:
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
clovastudio_api_key = (
self.ncp_clovastudio_api_key.get_secret_value()
if self.ncp_clovastudio_api_key
else None
)
if clovastudio_api_key:
headers["X-NCP-CLOVASTUDIO-API-KEY"] = clovastudio_api_key
apigw_api_key = (
self.ncp_apigw_api_key.get_secret_value()
if self.ncp_apigw_api_key
else None
)
return {
"Content-Type": "application/json",
"Accept": "application/json",
"X-NCP-CLOVASTUDIO-API-KEY": clovastudio_api_key,
"X-NCP-APIGW-API-KEY": apigw_api_key,
}
if apigw_api_key:
headers["X-NCP-APIGW-API-KEY"] = apigw_api_key
return headers
def _create_message_dicts(
self, messages: List[BaseMessage], stop: Optional[List[str]]
@@ -363,8 +370,6 @@ class ChatClovaX(BaseChatModel):
and event_data.get("data", {}) == "[DONE]"
):
return
if sse.event == "result":
return
if sse.event == "error":
raise SSEError(message=sse.data)
yield sse

View File

@@ -0,0 +1,532 @@
from __future__ import annotations
import importlib.util
import platform
from collections.abc import AsyncIterator
from typing import (
Any,
Callable,
Dict,
Iterator,
List,
Optional,
Sequence,
Tuple,
Type,
TypedDict,
TypeVar,
Union,
get_origin,
)
from langchain_core.callbacks import CallbackManagerForLLMRun
from langchain_core.callbacks.manager import AsyncCallbackManagerForLLMRun
from langchain_core.language_models import LanguageModelInput
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage
from langchain_core.output_parsers import JsonOutputParser, PydanticOutputParser
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
from langchain_core.runnables import Runnable
from langchain_core.tools import BaseTool
from langchain_core.utils.function_calling import convert_to_openai_tool
from pydantic import BaseModel, Field, model_validator
from typing_extensions import Literal
from langchain_community.adapters.openai import convert_message_to_dict
_BM = TypeVar("_BM", bound=BaseModel)
_DictOrPydanticClass = Union[Dict[str, Any], Type[_BM], Type]
class ChatOutlines(BaseChatModel):
"""Outlines chat model integration.
Setup:
pip install outlines
Key init args — client params:
backend: Literal["llamacpp", "transformers", "transformers_vision", "vllm", "mlxlm"] = "transformers"
Specifies the backend to use for the model.
Key init args — completion params:
model: str
Identifier for the model to use with Outlines.
max_tokens: int = 256
The maximum number of tokens to generate.
stop: Optional[List[str]] = None
A list of strings to stop generation when encountered.
streaming: bool = True
Whether to stream the results, token by token.
See full list of supported init args and their descriptions in the params section.
Instantiate:
from langchain_community.chat_models import ChatOutlines
chat = ChatOutlines(model="meta-llama/Llama-2-7b-chat-hf")
Invoke:
chat.invoke([HumanMessage(content="Say foo:")])
Stream:
for chunk in chat.stream([HumanMessage(content="Count to 10:")]):
print(chunk.content, end="", flush=True)
""" # noqa: E501
client: Any = None # :meta private:
model: str
"""Identifier for the model to use with Outlines.
The model identifier should be a string specifying:
- A Hugging Face model name (e.g., "meta-llama/Llama-2-7b-chat-hf")
- A local path to a model
- For GGUF models, the format is "repo_id/file_name"
(e.g., "TheBloke/Llama-2-7B-Chat-GGUF/llama-2-7b-chat.Q4_K_M.gguf")
Examples:
- "TheBloke/Llama-2-7B-Chat-GGUF/llama-2-7b-chat.Q4_K_M.gguf"
- "meta-llama/Llama-2-7b-chat-hf"
"""
backend: Literal[
"llamacpp", "transformers", "transformers_vision", "vllm", "mlxlm"
] = "transformers"
"""Specifies the backend to use for the model.
Supported backends are:
- "llamacpp": For GGUF models using llama.cpp
- "transformers": For Hugging Face Transformers models (default)
- "transformers_vision": For vision-language models (e.g., LLaVA)
- "vllm": For models using the vLLM library
- "mlxlm": For models using the MLX framework
Note: Ensure you have the necessary dependencies installed for the chosen backend.
The system will attempt to import required packages and may raise an ImportError
if they are not available.
"""
max_tokens: int = 256
"""The maximum number of tokens to generate."""
stop: Optional[List[str]] = None
"""A list of strings to stop generation when encountered."""
streaming: bool = True
"""Whether to stream the results, token by token."""
regex: Optional[str] = None
"""Regular expression for structured generation.
If provided, Outlines will guarantee that the generated text matches this regex.
This can be useful for generating structured outputs like IP addresses, dates, etc.
Example: (valid IP address)
regex = r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)"
Note: Computing the regex index can take some time, so it's recommended to reuse
the same regex for multiple generations if possible.
For more details, see: https://dottxt-ai.github.io/outlines/reference/generation/regex/
"""
type_constraints: Optional[Union[type, str]] = None
"""Type constraints for structured generation.
Restricts the output to valid Python types. Supported types include:
int, float, bool, datetime.date, datetime.time, datetime.datetime.
Example:
type_constraints = int
For more details, see: https://dottxt-ai.github.io/outlines/reference/generation/format/
"""
json_schema: Optional[Union[Any, Dict, Callable]] = None
"""Pydantic model, JSON Schema, or callable (function signature)
for structured JSON generation.
Outlines can generate JSON output that follows a specified structure,
which is useful for:
1. Parsing the answer (e.g., with Pydantic), storing it, or returning it to a user.
2. Calling a function with the result.
You can provide:
- A Pydantic model
- A JSON Schema (as a Dict)
- A callable (function signature)
The generated JSON will adhere to the specified structure.
For more details, see: https://dottxt-ai.github.io/outlines/reference/generation/json/
"""
grammar: Optional[str] = None
"""Context-free grammar for structured generation.
If provided, Outlines will generate text that adheres to the specified grammar.
The grammar should be defined in EBNF format.
This can be useful for generating structured outputs like mathematical expressions,
programming languages, or custom domain-specific languages.
Example:
grammar = '''
?start: expression
?expression: term (("+" | "-") term)*
?term: factor (("*" | "/") factor)*
?factor: NUMBER | "-" factor | "(" expression ")"
%import common.NUMBER
'''
Note: Grammar-based generation is currently experimental and may have performance
limitations. It uses greedy generation to mitigate these issues.
For more details and examples, see:
https://dottxt-ai.github.io/outlines/reference/generation/cfg/
"""
custom_generator: Optional[Any] = None
"""Set your own outlines generator object to override the default behavior."""
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
"""Additional parameters to pass to the underlying model.
Example:
model_kwargs = {"temperature": 0.8, "seed": 42}
"""
@model_validator(mode="after")
def validate_environment(self) -> "ChatOutlines":
"""Validate that outlines is installed and create a model instance."""
num_constraints = sum(
[
bool(self.regex),
bool(self.type_constraints),
bool(self.json_schema),
bool(self.grammar),
]
)
if num_constraints > 1:
raise ValueError(
"Either none or exactly one of regex, type_constraints, "
"json_schema, or grammar can be provided."
)
return self.build_client()
def build_client(self) -> "ChatOutlines":
try:
import outlines.models as models
except ImportError:
raise ImportError(
"Could not import the Outlines library. "
"Please install it with `pip install outlines`."
)
def check_packages_installed(
packages: List[Union[str, Tuple[str, str]]],
) -> None:
missing_packages = [
pkg if isinstance(pkg, str) else pkg[0]
for pkg in packages
if importlib.util.find_spec(pkg[1] if isinstance(pkg, tuple) else pkg)
is None
]
if missing_packages:
raise ImportError(
f"Missing packages: {', '.join(missing_packages)}. "
"You can install them with:\n\n"
f" pip install {' '.join(missing_packages)}"
)
if self.backend == "llamacpp":
check_packages_installed([("llama-cpp-python", "llama_cpp")])
if ".gguf" in self.model:
creator, repo_name, file_name = self.model.split("/", 2)
repo_id = f"{creator}/{repo_name}"
else:
raise ValueError("GGUF file_name must be provided for llama.cpp.")
self.client = models.llamacpp(repo_id, file_name, **self.model_kwargs)
elif self.backend == "transformers":
check_packages_installed(["transformers", "torch", "datasets"])
self.client = models.transformers(
model_name=self.model, **self.model_kwargs
)
elif self.backend == "transformers_vision":
if hasattr(models, "transformers_vision"):
from transformers import LlavaNextForConditionalGeneration
self.client = models.transformers_vision(
self.model,
model_class=LlavaNextForConditionalGeneration,
**self.model_kwargs,
)
else:
raise ValueError("transformers_vision backend is not supported")
elif self.backend == "vllm":
if platform.system() == "Darwin":
raise ValueError("vLLM backend is not supported on macOS.")
check_packages_installed(["vllm"])
self.client = models.vllm(self.model, **self.model_kwargs)
elif self.backend == "mlxlm":
check_packages_installed(["mlx"])
self.client = models.mlxlm(self.model, **self.model_kwargs)
else:
raise ValueError(f"Unsupported backend: {self.backend}")
return self
@property
def _llm_type(self) -> str:
return "outlines-chat"
@property
def _default_params(self) -> Dict[str, Any]:
return {
"max_tokens": self.max_tokens,
"stop_at": self.stop,
**self.model_kwargs,
}
@property
def _identifying_params(self) -> Dict[str, Any]:
return {
"model": self.model,
"backend": self.backend,
"regex": self.regex,
"type_constraints": self.type_constraints,
"json_schema": self.json_schema,
"grammar": self.grammar,
**self._default_params,
}
@property
def _generator(self) -> Any:
from outlines import generate
if self.custom_generator:
return self.custom_generator
constraints = [
self.regex,
self.type_constraints,
self.json_schema,
self.grammar,
]
num_constraints = sum(constraint is not None for constraint in constraints)
if num_constraints != 1 and num_constraints != 0:
raise ValueError(
"Either none or exactly one of regex, type_constraints, "
"json_schema, or grammar can be provided."
)
if self.regex:
return generate.regex(self.client, regex_str=self.regex)
if self.type_constraints:
return generate.format(self.client, python_type=self.type_constraints)
if self.json_schema:
return generate.json(self.client, schema_object=self.json_schema)
if self.grammar:
return generate.cfg(self.client, cfg_str=self.grammar)
return generate.text(self.client)
def _convert_messages_to_openai_format(
self, messages: list[BaseMessage]
) -> list[dict]:
return [convert_message_to_dict(message) for message in messages]
def _convert_messages_to_prompt(self, messages: list[BaseMessage]) -> str:
"""Convert a list of messages to a single prompt."""
if self.backend == "llamacpp": # get base_model_name from gguf repo_id
from huggingface_hub import ModelCard
repo_creator, gguf_repo_name, file_name = self.model.split("/")
model_card = ModelCard.load(f"{repo_creator}/{gguf_repo_name}")
if hasattr(model_card.data, "base_model"):
model_name = model_card.data.base_model
else:
raise ValueError(f"Base model name not found for {self.model}")
else:
model_name = self.model
from transformers import AutoTokenizer
return AutoTokenizer.from_pretrained(model_name).apply_chat_template(
self._convert_messages_to_openai_format(messages),
tokenize=False,
add_generation_prompt=True,
)
def bind_tools(
self,
tools: Sequence[Dict[str, Any] | type | Callable[..., Any] | BaseTool],
*,
tool_choice: Optional[Union[Dict, bool, str]] = None,
**kwargs: Any,
) -> Runnable[LanguageModelInput, BaseMessage]:
"""Bind tool-like objects to this chat model
tool_choice: does not currently support "any", "auto" choices like OpenAI
tool-calling API. should be a dict of the form to force this tool
{"type": "function", "function": {"name": <<tool_name>>}}.
"""
formatted_tools = [convert_to_openai_tool(tool) for tool in tools]
tool_names = [ft["function"]["name"] for ft in formatted_tools]
if tool_choice:
if isinstance(tool_choice, dict):
if not any(
tool_choice["function"]["name"] == name for name in tool_names
):
raise ValueError(
f"Tool choice {tool_choice=} was specified, but the only "
f"provided tools were {tool_names}."
)
elif isinstance(tool_choice, str):
chosen = [
f for f in formatted_tools if f["function"]["name"] == tool_choice
]
if not chosen:
raise ValueError(
f"Tool choice {tool_choice=} was specified, but the only "
f"provided tools were {tool_names}."
)
elif isinstance(tool_choice, bool):
if len(formatted_tools) > 1:
raise ValueError(
"tool_choice=True can only be specified when a single tool is "
f"passed in. Received {len(tools)} tools."
)
tool_choice = formatted_tools[0]
kwargs["tool_choice"] = tool_choice
formatted_tools = [convert_to_openai_tool(tool) for tool in tools]
return super().bind_tools(tools=formatted_tools, **kwargs)
def with_structured_output(
self,
schema: Optional[_DictOrPydanticClass],
*,
include_raw: bool = False,
**kwargs: Any,
) -> Runnable[LanguageModelInput, Union[dict, BaseModel]]:
if get_origin(schema) is TypedDict:
raise NotImplementedError("TypedDict is not supported yet by Outlines")
self.json_schema = schema
if isinstance(schema, type) and issubclass(schema, BaseModel):
parser: Union[PydanticOutputParser, JsonOutputParser] = (
PydanticOutputParser(pydantic_object=schema)
)
else:
parser = JsonOutputParser()
if include_raw: # TODO
raise NotImplementedError("include_raw is not yet supported")
return self | parser
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:
params = {**self._default_params, **kwargs}
if stop:
params["stop_at"] = stop
prompt = self._convert_messages_to_prompt(messages)
response = ""
if self.streaming:
for chunk in self._stream(
messages=messages,
stop=stop,
run_manager=run_manager,
**kwargs,
):
if isinstance(chunk.message.content, str):
response += chunk.message.content
else:
raise ValueError(
"Invalid content type, only str is supported, "
f"got {type(chunk.message.content)}"
)
else:
response = self._generator(prompt, **params)
message = AIMessage(content=response)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
def _stream(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> Iterator[ChatGenerationChunk]:
params = {**self._default_params, **kwargs}
if stop:
params["stop_at"] = stop
prompt = self._convert_messages_to_prompt(messages)
for token in self._generator.stream(prompt, **params):
if run_manager:
run_manager.on_llm_new_token(token)
message_chunk = AIMessageChunk(content=token)
chunk = ChatGenerationChunk(message=message_chunk)
yield chunk
async def _agenerate(
self,
messages: List[BaseMessage],
stop: List[str] | None = None,
run_manager: AsyncCallbackManagerForLLMRun | None = None,
**kwargs: Any,
) -> ChatResult:
if hasattr(self._generator, "agenerate"):
params = {**self._default_params, **kwargs}
if stop:
params["stop_at"] = stop
prompt = self._convert_messages_to_prompt(messages)
response = await self._generator.agenerate(prompt, **params)
message = AIMessage(content=response)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
elif self.streaming:
response = ""
async for chunk in self._astream(messages, stop, run_manager, **kwargs):
response += chunk.message.content or ""
message = AIMessage(content=response)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
else:
return await super()._agenerate(messages, stop, run_manager, **kwargs)
async def _astream(
self,
messages: List[BaseMessage],
stop: List[str] | None = None,
run_manager: AsyncCallbackManagerForLLMRun | None = None,
**kwargs: Any,
) -> AsyncIterator[ChatGenerationChunk]:
if hasattr(self._generator, "astream"):
params = {**self._default_params, **kwargs}
if stop:
params["stop_at"] = stop
prompt = self._convert_messages_to_prompt(messages)
async for token in self._generator.astream(prompt, **params):
if run_manager:
await run_manager.on_llm_new_token(token)
message_chunk = AIMessageChunk(content=token)
chunk = ChatGenerationChunk(message=message_chunk)
yield chunk
else:
async for chunk in super()._astream(messages, stop, run_manager, **kwargs):
yield chunk

View File

@@ -1,10 +1,25 @@
import json
from typing import Any, Dict, Iterator, List, Optional, Tuple
from operator import itemgetter
from typing import (
Any,
Callable,
Dict,
Iterator,
List,
Literal,
Optional,
Sequence,
Tuple,
Type,
Union,
cast,
)
import requests
from langchain_core.callbacks import (
CallbackManagerForLLMRun,
)
from langchain_core.language_models import LanguageModelInput
from langchain_core.language_models.chat_models import (
BaseChatModel,
generate_from_stream,
@@ -19,9 +34,24 @@ from langchain_core.messages import (
SystemMessage,
ToolMessage,
)
from langchain_core.output_parsers import (
JsonOutputParser,
PydanticOutputParser,
)
from langchain_core.output_parsers.base import OutputParserLike
from langchain_core.output_parsers.openai_tools import (
JsonOutputKeyToolsParser,
PydanticToolsParser,
make_invalid_tool_call,
parse_tool_call,
)
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
from langchain_core.runnables import Runnable, RunnableMap, RunnablePassthrough
from langchain_core.tools import BaseTool
from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env
from pydantic import Field, SecretStr
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain_core.utils.pydantic import is_basemodel_subclass
from pydantic import BaseModel, Field, SecretStr
from requests import Response
@@ -35,6 +65,7 @@ def _convert_message_to_dict(message: BaseMessage) -> Dict[str, Any]:
Returns:
messages_dict: role / content dict
"""
message_dict: Dict[str, Any] = {}
if isinstance(message, ChatMessage):
message_dict = {"role": message.role, "content": message.content}
elif isinstance(message, SystemMessage):
@@ -43,8 +74,16 @@ def _convert_message_to_dict(message: BaseMessage) -> Dict[str, Any]:
message_dict = {"role": "user", "content": message.content}
elif isinstance(message, AIMessage):
message_dict = {"role": "assistant", "content": message.content}
if "tool_calls" in message.additional_kwargs:
message_dict["tool_calls"] = message.additional_kwargs["tool_calls"]
if message_dict["content"] == "":
message_dict["content"] = None
elif isinstance(message, ToolMessage):
message_dict = {"role": "tool", "content": message.content}
message_dict = {
"role": "tool",
"content": message.content,
"tool_call_id": message.tool_call_id,
}
else:
raise TypeError(f"Got unknown type {message}")
return message_dict
@@ -64,14 +103,18 @@ def _create_message_dicts(messages: List[BaseMessage]) -> List[Dict[str, Any]]:
return message_dicts
def _is_pydantic_class(obj: Any) -> bool:
return isinstance(obj, type) and is_basemodel_subclass(obj)
class ChatSambaNovaCloud(BaseChatModel):
"""
SambaNova Cloud chat model.
Setup:
To use, you should have the environment variables:
``SAMBANOVA_URL`` set with your SambaNova Cloud URL.
``SAMBANOVA_API_KEY`` set with your SambaNova Cloud API Key.
`SAMBANOVA_URL` set with your SambaNova Cloud URL.
`SAMBANOVA_API_KEY` set with your SambaNova Cloud API Key.
http://cloud.sambanova.ai/
Example:
.. code-block:: python
@@ -123,8 +166,10 @@ class ChatSambaNovaCloud(BaseChatModel):
top_k = model top k,
stream_options = include usage to get generation metrics
)
Invoke:
.. code-block:: python
messages = [
SystemMessage(content="your are an AI assistant."),
HumanMessage(content="tell me a joke."),
@@ -134,26 +179,78 @@ class ChatSambaNovaCloud(BaseChatModel):
Stream:
.. code-block:: python
for chunk in chat.stream(messages):
print(chunk.content, end="", flush=True)
for chunk in chat.stream(messages):
print(chunk.content, end="", flush=True)
Async:
.. code-block:: python
response = chat.ainvoke(messages)
await response
response = chat.ainvoke(messages)
await response
Tool calling:
.. code-block:: python
from pydantic import BaseModel, Field
class GetWeather(BaseModel):
'''Get the current weather in a given location'''
location: str = Field(
...,
description="The city and state, e.g. Los Angeles, CA"
)
llm_with_tools = llm.bind_tools([GetWeather, GetPopulation])
ai_msg = llm_with_tools.invoke("Should I bring my umbrella today in LA?")
ai_msg.tool_calls
.. code-block:: none
[
{
'name': 'GetWeather',
'args': {'location': 'Los Angeles, CA'},
'id': 'call_adf61180ea2b4d228a'
}
]
Structured output:
.. code-block:: python
from typing import Optional
from pydantic import BaseModel, Field
class Joke(BaseModel):
'''Joke to tell user.'''
setup: str = Field(description="The setup of the joke")
punchline: str = Field(description="The punchline to the joke")
structured_model = llm.with_structured_output(Joke)
structured_model.invoke("Tell me a joke about cats")
.. code-block:: python
Joke(setup="Why did the cat join a band?",
punchline="Because it wanted to be the purr-cussionist!")
See `ChatSambanovaCloud.with_structured_output()` for more.
Token usage:
.. code-block:: python
response = chat.invoke(messages)
print(response.response_metadata["usage"]["prompt_tokens"]
print(response.response_metadata["usage"]["total_tokens"]
response = chat.invoke(messages)
print(response.response_metadata["usage"]["prompt_tokens"]
print(response.response_metadata["usage"]["total_tokens"]
Response metadata
.. code-block:: python
response = chat.invoke(messages)
print(response.response_metadata)
response = chat.invoke(messages)
print(response.response_metadata)
"""
sambanova_url: str = Field(default="")
@@ -180,9 +277,12 @@ class ChatSambaNovaCloud(BaseChatModel):
top_k: Optional[int] = Field(default=None)
"""model top k"""
stream_options: dict = Field(default={"include_usage": True})
stream_options: Dict[str, Any] = Field(default={"include_usage": True})
"""stream options, include usage to get generation metrics"""
additional_headers: Dict[str, Any] = Field(default={})
"""Additional headers to sent in request"""
class Config:
populate_by_name = True
@@ -230,36 +330,409 @@ class ChatSambaNovaCloud(BaseChatModel):
)
super().__init__(**kwargs)
def bind_tools(
self,
tools: Sequence[Union[Dict[str, Any], Type[Any], Callable[..., Any], BaseTool]],
*,
tool_choice: Optional[Union[Dict[str, Any], bool, str]] = None,
parallel_tool_calls: Optional[bool] = False,
**kwargs: Any,
) -> Runnable[LanguageModelInput, BaseMessage]:
"""Bind tool-like objects to this chat model
tool_choice: does not currently support "any", choice like
should be one of ["auto", "none", "required"]
"""
formatted_tools = [convert_to_openai_tool(tool) for tool in tools]
if tool_choice:
if isinstance(tool_choice, str):
# tool_choice is a tool/function name
if tool_choice not in ("auto", "none", "required"):
tool_choice = "auto"
elif isinstance(tool_choice, bool):
if tool_choice:
tool_choice = "required"
elif isinstance(tool_choice, dict):
raise ValueError(
"tool_choice must be one of ['auto', 'none', 'required']"
)
else:
raise ValueError(
f"Unrecognized tool_choice type. Expected str, bool"
f"Received: {tool_choice}"
)
else:
tool_choice = "auto"
kwargs["tool_choice"] = tool_choice
kwargs["parallel_tool_calls"] = parallel_tool_calls
return super().bind(tools=formatted_tools, **kwargs)
def with_structured_output(
self,
schema: Optional[Union[Dict[str, Any], Type[BaseModel]]] = None,
*,
method: Literal[
"function_calling", "json_mode", "json_schema"
] = "function_calling",
include_raw: bool = False,
**kwargs: Any,
) -> Runnable[LanguageModelInput, Union[Dict[str, Any], BaseModel]]:
"""Model wrapper that returns outputs formatted to match the given schema.
Args:
schema:
The output schema. Can be passed in as:
- an OpenAI function/tool schema,
- a JSON Schema,
- a TypedDict class,
- or a Pydantic.BaseModel class.
If `schema` is a Pydantic class then the model output will be a
Pydantic instance of that class, and the model-generated fields will be
validated by the Pydantic class. Otherwise the model output will be a
dict and will not be validated. See :meth:`langchain_core.utils.function_calling.convert_to_openai_tool`
for more on how to properly specify types and descriptions of
schema fields when specifying a Pydantic or TypedDict class.
method:
The method for steering model generation, either "function_calling"
"json_mode" or "json_schema".
If "function_calling" then the schema will be converted
to an OpenAI function and the returned model will make use of the
function-calling API. If "json_mode" or "json_schema" then OpenAI's
JSON mode will be used.
Note that if using "json_mode" or "json_schema" then you must include instructions
for formatting the output into the desired schema into the model call.
include_raw:
If False then only the parsed structured output is returned. If
an error occurs during model output parsing it will be raised. If True
then both the raw model response (a BaseMessage) and the parsed model
response will be returned. If an error occurs during output parsing it
will be caught and returned as well. The final output is always a dict
with keys "raw", "parsed", and "parsing_error".
Returns:
A Runnable that takes same inputs as a :class:`langchain_core.language_models.chat.BaseChatModel`.
If `include_raw` is False and `schema` is a Pydantic class, Runnable outputs
an instance of `schema` (i.e., a Pydantic object).
Otherwise, if `include_raw` is False then Runnable outputs a dict.
If `include_raw` is True, then Runnable outputs a dict with keys:
- `"raw"`: BaseMessage
- `"parsed"`: None if there was a parsing error, otherwise the type depends on the `schema` as described above.
- `"parsing_error"`: Optional[BaseException]
Example: schema=Pydantic class, method="function_calling", include_raw=False:
.. code-block:: python
from typing import Optional
from langchain_community.chat_models import ChatSambaNovaCloud
from pydantic import BaseModel, Field
class AnswerWithJustification(BaseModel):
'''An answer to the user question along with justification for the answer.'''
answer: str
justification: str = Field(
description="A justification for the answer."
)
llm = ChatSambaNovaCloud(model="Meta-Llama-3.1-70B-Instruct", temperature=0)
structured_llm = llm.with_structured_output(AnswerWithJustification)
structured_llm.invoke(
"What weighs more a pound of bricks or a pound of feathers"
)
# -> AnswerWithJustification(
# answer='They weigh the same',
# justification='A pound is a unit of weight or mass, so a pound of bricks and a pound of feathers both weigh the same.'
# )
Example: schema=Pydantic class, method="function_calling", include_raw=True:
.. code-block:: python
from langchain_community.chat_models import ChatSambaNovaCloud
from pydantic import BaseModel
class AnswerWithJustification(BaseModel):
'''An answer to the user question along with justification for the answer.'''
answer: str
justification: str
llm = ChatSambaNovaCloud(model="Meta-Llama-3.1-70B-Instruct", temperature=0)
structured_llm = llm.with_structured_output(
AnswerWithJustification, include_raw=True
)
structured_llm.invoke(
"What weighs more a pound of bricks or a pound of feathers"
)
# -> {
# 'raw': AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"answer": "They weigh the same.", "justification": "A pound is a unit of weight or mass, so one pound of bricks and one pound of feathers both weigh the same amount."}', 'name': 'AnswerWithJustification'}, 'id': 'call_17a431fc6a4240e1bd', 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'usage': {'acceptance_rate': 5, 'completion_tokens': 53, 'completion_tokens_after_first_per_sec': 343.7964936837758, 'completion_tokens_after_first_per_sec_first_ten': 439.1205661878638, 'completion_tokens_per_sec': 162.8511306784833, 'end_time': 1731527851.0698032, 'is_last_response': True, 'prompt_tokens': 213, 'start_time': 1731527850.7137961, 'time_to_first_token': 0.20475482940673828, 'total_latency': 0.32545061111450196, 'total_tokens': 266, 'total_tokens_per_sec': 817.3283162354066}, 'model_name': 'Meta-Llama-3.1-70B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1731527850}, id='95667eaf-447f-4b53-bb6e-b6e1094ded88', tool_calls=[{'name': 'AnswerWithJustification', 'args': {'answer': 'They weigh the same.', 'justification': 'A pound is a unit of weight or mass, so one pound of bricks and one pound of feathers both weigh the same amount.'}, 'id': 'call_17a431fc6a4240e1bd', 'type': 'tool_call'}]),
# 'parsed': AnswerWithJustification(answer='They weigh the same.', justification='A pound is a unit of weight or mass, so one pound of bricks and one pound of feathers both weigh the same amount.'),
# 'parsing_error': None
# }
Example: schema=TypedDict class, method="function_calling", include_raw=False:
.. code-block:: python
# IMPORTANT: If you are using Python <=3.8, you need to import Annotated
# from typing_extensions, not from typing.
from typing_extensions import Annotated, TypedDict
from langchain_community.chat_models import ChatSambaNovaCloud
class AnswerWithJustification(TypedDict):
'''An answer to the user question along with justification for the answer.'''
answer: str
justification: Annotated[
Optional[str], None, "A justification for the answer."
]
llm = ChatSambaNovaCloud(model="Meta-Llama-3.1-70B-Instruct", temperature=0)
structured_llm = llm.with_structured_output(AnswerWithJustification)
structured_llm.invoke(
"What weighs more a pound of bricks or a pound of feathers"
)
# -> {
# 'answer': 'They weigh the same',
# 'justification': 'A pound is a unit of weight or mass, so one pound of bricks and one pound of feathers both weigh the same amount.'
# }
Example: schema=OpenAI function schema, method="function_calling", include_raw=False:
.. code-block:: python
from langchain_community.chat_models import ChatSambaNovaCloud
oai_schema = {
'name': 'AnswerWithJustification',
'description': 'An answer to the user question along with justification for the answer.',
'parameters': {
'type': 'object',
'properties': {
'answer': {'type': 'string'},
'justification': {'description': 'A justification for the answer.', 'type': 'string'}
},
'required': ['answer']
}
}
llm = ChatSambaNovaCloud(model="Meta-Llama-3.1-70B-Instruct", temperature=0)
structured_llm = llm.with_structured_output(oai_schema)
structured_llm.invoke(
"What weighs more a pound of bricks or a pound of feathers"
)
# -> {
# 'answer': 'They weigh the same',
# 'justification': 'A pound is a unit of weight or mass, so one pound of bricks and one pound of feathers both weigh the same amount.'
# }
Example: schema=Pydantic class, method="json_mode", include_raw=True:
.. code-block::
from langchain_community.chat_models import ChatSambaNovaCloud
from pydantic import BaseModel
class AnswerWithJustification(BaseModel):
answer: str
justification: str
llm = ChatSambaNovaCloud(model="Meta-Llama-3.1-70B-Instruct", temperature=0)
structured_llm = llm.with_structured_output(
AnswerWithJustification,
method="json_mode",
include_raw=True
)
structured_llm.invoke(
"Answer the following question. "
"Make sure to return a JSON blob with keys 'answer' and 'justification'.\n\n"
"What's heavier a pound of bricks or a pound of feathers?"
)
# -> {
# 'raw': AIMessage(content='{\n "answer": "They are the same weight",\n "justification": "A pound is a unit of weight or mass, so a pound of bricks and a pound of feathers both weigh the same amount, one pound. The difference is in their density and volume. A pound of feathers would take up more space than a pound of bricks due to the difference in their densities."\n}', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 5.3125, 'completion_tokens': 79, 'completion_tokens_after_first_per_sec': 292.65701089829776, 'completion_tokens_after_first_per_sec_first_ten': 346.43324678555325, 'completion_tokens_per_sec': 200.012158915008, 'end_time': 1731528071.1708555, 'is_last_response': True, 'prompt_tokens': 70, 'start_time': 1731528070.737394, 'time_to_first_token': 0.16693782806396484, 'total_latency': 0.3949759876026827, 'total_tokens': 149, 'total_tokens_per_sec': 377.2381225105847}, 'model_name': 'Meta-Llama-3.1-70B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1731528070}, id='83208297-3eb9-4021-a856-ca78a15758df'),
# 'parsed': AnswerWithJustification(answer='They are the same weight', justification='A pound is a unit of weight or mass, so a pound of bricks and a pound of feathers both weigh the same amount, one pound. The difference is in their density and volume. A pound of feathers would take up more space than a pound of bricks due to the difference in their densities.'),
# 'parsing_error': None
# }
Example: schema=None, method="json_mode", include_raw=True:
.. code-block::
from langchain_community.chat_models import ChatSambaNovaCloud
llm = ChatSambaNovaCloud(model="Meta-Llama-3.1-70B-Instruct", temperature=0)
structured_llm = llm.with_structured_output(method="json_mode", include_raw=True)
structured_llm.invoke(
"Answer the following question. "
"Make sure to return a JSON blob with keys 'answer' and 'justification'.\n\n"
"What's heavier a pound of bricks or a pound of feathers?"
)
# -> {
# 'raw': AIMessage(content='{\n "answer": "They are the same weight",\n "justification": "A pound is a unit of weight or mass, so a pound of bricks and a pound of feathers both weigh the same amount, one pound. The difference is in their density and volume. A pound of feathers would take up more space than a pound of bricks due to the difference in their densities."\n}', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 4.722222222222222, 'completion_tokens': 79, 'completion_tokens_after_first_per_sec': 357.1315485254867, 'completion_tokens_after_first_per_sec_first_ten': 416.83279609305305, 'completion_tokens_per_sec': 240.92819585198137, 'end_time': 1731528164.8474727, 'is_last_response': True, 'prompt_tokens': 70, 'start_time': 1731528164.4906917, 'time_to_first_token': 0.13837409019470215, 'total_latency': 0.3278985247892492, 'total_tokens': 149, 'total_tokens_per_sec': 454.4088757208256}, 'model_name': 'Meta-Llama-3.1-70B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1731528164}, id='15261eaf-8a25-42ef-8ed5-f63d8bf5b1b0'),
# 'parsed': {
# 'answer': 'They are the same weight',
# 'justification': 'A pound is a unit of weight or mass, so a pound of bricks and a pound of feathers both weigh the same amount, one pound. The difference is in their density and volume. A pound of feathers would take up more space than a pound of bricks due to the difference in their densities.'},
# },
# 'parsing_error': None
# }
Example: schema=None, method="json_schema", include_raw=True:
.. code-block::
from langchain_community.chat_models import ChatSambaNovaCloud
class AnswerWithJustification(BaseModel):
answer: str
justification: str
llm = ChatSambaNovaCloud(model="Meta-Llama-3.1-70B-Instruct", temperature=0)
structured_llm = llm.with_structured_output(AnswerWithJustification, method="json_schema", include_raw=True)
structured_llm.invoke(
"Answer the following question. "
"Make sure to return a JSON blob with keys 'answer' and 'justification'.\n\n"
"What's heavier a pound of bricks or a pound of feathers?"
)
# -> {
# 'raw': AIMessage(content='{\n "answer": "They are the same weight",\n "justification": "A pound is a unit of weight or mass, so a pound of bricks and a pound of feathers both weigh the same amount, one pound. The difference is in their density and volume. A pound of feathers would take up more space than a pound of bricks due to the difference in their densities."\n}', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'usage': {'acceptance_rate': 5.3125, 'completion_tokens': 79, 'completion_tokens_after_first_per_sec': 292.65701089829776, 'completion_tokens_after_first_per_sec_first_ten': 346.43324678555325, 'completion_tokens_per_sec': 200.012158915008, 'end_time': 1731528071.1708555, 'is_last_response': True, 'prompt_tokens': 70, 'start_time': 1731528070.737394, 'time_to_first_token': 0.16693782806396484, 'total_latency': 0.3949759876026827, 'total_tokens': 149, 'total_tokens_per_sec': 377.2381225105847}, 'model_name': 'Meta-Llama-3.1-70B-Instruct', 'system_fingerprint': 'fastcoe', 'created': 1731528070}, id='83208297-3eb9-4021-a856-ca78a15758df'),
# 'parsed': AnswerWithJustification(answer='They are the same weight', justification='A pound is a unit of weight or mass, so a pound of bricks and a pound of feathers both weigh the same amount, one pound. The difference is in their density and volume. A pound of feathers would take up more space than a pound of bricks due to the difference in their densities.'),
# 'parsing_error': None
# }
""" # noqa: E501
if kwargs is not None:
raise ValueError(f"Received unsupported arguments {kwargs}")
is_pydantic_schema = _is_pydantic_class(schema)
if method == "function_calling":
if schema is None:
raise ValueError(
"`schema` must be specified when method is `function_calling`. "
"Received None."
)
tool_name = convert_to_openai_tool(schema)["function"]["name"]
llm = self.bind_tools([schema], tool_choice=tool_name)
if is_pydantic_schema:
output_parser: OutputParserLike[Any] = PydanticToolsParser(
tools=[schema],
first_tool_only=True,
)
else:
output_parser = JsonOutputKeyToolsParser(
key_name=tool_name, first_tool_only=True
)
elif method == "json_mode":
llm = self
# TODO bind response format when json mode available by API
# llm = self.bind(response_format={"type": "json_object"})
if is_pydantic_schema:
schema = cast(Type[BaseModel], schema)
output_parser = PydanticOutputParser(pydantic_object=schema)
else:
output_parser = JsonOutputParser()
elif method == "json_schema":
if schema is None:
raise ValueError(
"`schema` must be specified when method is not `json_mode`. "
"Received None."
)
llm = self
# TODO bind response format when json schema available by API,
# update example
# llm = self.bind(
# response_format={"type": "json_object", "json_schema": schema}
# )
if is_pydantic_schema:
schema = cast(Type[BaseModel], schema)
output_parser = PydanticOutputParser(pydantic_object=schema)
else:
output_parser = JsonOutputParser()
else:
raise ValueError(
f"Unrecognized method argument. Expected one of `function_calling` or "
f"`json_mode`. Received: `{method}`"
)
if include_raw:
parser_assign = RunnablePassthrough.assign(
parsed=itemgetter("raw") | output_parser, parsing_error=lambda _: None
)
parser_none = RunnablePassthrough.assign(parsed=lambda _: None)
parser_with_fallback = parser_assign.with_fallbacks(
[parser_none], exception_key="parsing_error"
)
return RunnableMap(raw=llm) | parser_with_fallback
else:
return llm | output_parser
def _handle_request(
self, messages_dicts: List[Dict], stop: Optional[List[str]] = None
) -> Dict[str, Any]:
self,
messages_dicts: List[Dict[str, Any]],
stop: Optional[List[str]] = None,
streaming: bool = False,
**kwargs: Any,
) -> Response:
"""
Performs a post request to the LLM API.
Args:
messages_dicts: List of role / content dicts to use as input.
stop: list of stop tokens
streaming: wether to do a streaming call
Returns:
An iterator of response dicts.
"""
data = {
"messages": messages_dicts,
"max_tokens": self.max_tokens,
"stop": stop,
"model": self.model,
"temperature": self.temperature,
"top_p": self.top_p,
"top_k": self.top_k,
}
if streaming:
data = {
"messages": messages_dicts,
"max_tokens": self.max_tokens,
"stop": stop,
"model": self.model,
"temperature": self.temperature,
"top_p": self.top_p,
"top_k": self.top_k,
"stream": True,
"stream_options": self.stream_options,
**kwargs,
}
else:
data = {
"messages": messages_dicts,
"max_tokens": self.max_tokens,
"stop": stop,
"model": self.model,
"temperature": self.temperature,
"top_p": self.top_p,
"top_k": self.top_k,
**kwargs,
}
http_session = requests.Session()
response = http_session.post(
self.sambanova_url,
headers={
"Authorization": f"Bearer {self.sambanova_api_key.get_secret_value()}",
"Content-Type": "application/json",
**self.additional_headers,
},
json=data,
stream=streaming,
)
if response.status_code != 200:
raise RuntimeError(
@@ -267,27 +740,78 @@ class ChatSambaNovaCloud(BaseChatModel):
f"{response.status_code}.",
f"{response.text}.",
)
response_dict = response.json()
if response_dict.get("error"):
raise RuntimeError(
f"Sambanova /complete call failed with status code "
f"{response.status_code}.",
f"{response_dict}.",
)
return response_dict
return response
def _handle_streaming_request(
self, messages_dicts: List[Dict], stop: Optional[List[str]] = None
) -> Iterator[Dict]:
def _process_response(self, response: Response) -> AIMessage:
"""
Performs an streaming post request to the LLM API.
Process a non streaming response from the api
Args:
messages_dicts: List of role / content dicts to use as input.
stop: list of stop tokens
response: A request Response object
Returns
generation: an AIMessage with model generation
"""
try:
response_dict = response.json()
if response_dict.get("error"):
raise RuntimeError(
f"Sambanova /complete call failed with status code "
f"{response.status_code}.",
f"{response_dict}.",
)
except Exception as e:
raise RuntimeError(
f"Sambanova /complete call failed couldn't get JSON response {e}"
f"response: {response.text}"
)
content = response_dict["choices"][0]["message"].get("content", "")
if content is None:
content = ""
additional_kwargs: Dict[str, Any] = {}
tool_calls = []
invalid_tool_calls = []
raw_tool_calls = response_dict["choices"][0]["message"].get("tool_calls")
if raw_tool_calls:
additional_kwargs["tool_calls"] = raw_tool_calls
for raw_tool_call in raw_tool_calls:
if isinstance(raw_tool_call["function"]["arguments"], dict):
raw_tool_call["function"]["arguments"] = json.dumps(
raw_tool_call["function"].get("arguments", {})
)
try:
tool_calls.append(parse_tool_call(raw_tool_call, return_id=True))
except Exception as e:
invalid_tool_calls.append(
make_invalid_tool_call(raw_tool_call, str(e))
)
message = AIMessage(
content=content,
additional_kwargs=additional_kwargs,
tool_calls=tool_calls,
invalid_tool_calls=invalid_tool_calls,
response_metadata={
"finish_reason": response_dict["choices"][0]["finish_reason"],
"usage": response_dict.get("usage"),
"model_name": response_dict["model"],
"system_fingerprint": response_dict["system_fingerprint"],
"created": response_dict["created"],
},
id=response_dict["id"],
)
return message
def _process_stream_response(
self, response: Response
) -> Iterator[BaseMessageChunk]:
"""
Process a streaming response from the api
Args:
response: An iterable request Response object
Yields:
An iterator of response dicts.
generation: an AIMessageChunk with model partial generation
"""
try:
import sseclient
@@ -296,37 +820,9 @@ class ChatSambaNovaCloud(BaseChatModel):
"could not import sseclient library"
"Please install it with `pip install sseclient-py`."
)
data = {
"messages": messages_dicts,
"max_tokens": self.max_tokens,
"stop": stop,
"model": self.model,
"temperature": self.temperature,
"top_p": self.top_p,
"top_k": self.top_k,
"stream": True,
"stream_options": self.stream_options,
}
http_session = requests.Session()
response = http_session.post(
self.sambanova_url,
headers={
"Authorization": f"Bearer {self.sambanova_api_key.get_secret_value()}",
"Content-Type": "application/json",
},
json=data,
stream=True,
)
client = sseclient.SSEClient(response)
if response.status_code != 200:
raise RuntimeError(
f"Sambanova /complete call failed with status code "
f"{response.status_code}."
f"{response.text}."
)
for event in client.events():
if event.event == "error_event":
raise RuntimeError(
@@ -353,7 +849,31 @@ class ChatSambaNovaCloud(BaseChatModel):
f"{response.status_code}."
f"{event.data}."
)
yield data
if len(data["choices"]) > 0:
finish_reason = data["choices"][0].get("finish_reason")
content = data["choices"][0]["delta"]["content"]
id = data["id"]
chunk = AIMessageChunk(
content=content, id=id, additional_kwargs={}
)
else:
content = ""
id = data["id"]
metadata = {
"finish_reason": finish_reason,
"usage": data.get("usage"),
"model_name": data["model"],
"system_fingerprint": data["system_fingerprint"],
"created": data["created"],
}
chunk = AIMessageChunk(
content=content,
id=id,
response_metadata=metadata,
additional_kwargs={},
)
yield chunk
except Exception as e:
raise RuntimeError(
f"Error getting content chunk raw streamed response: {e}"
@@ -390,21 +910,14 @@ class ChatSambaNovaCloud(BaseChatModel):
if stream_iter:
return generate_from_stream(stream_iter)
messages_dicts = _create_message_dicts(messages)
response = self._handle_request(messages_dicts, stop)
message = AIMessage(
content=response["choices"][0]["message"]["content"],
additional_kwargs={},
response_metadata={
"finish_reason": response["choices"][0]["finish_reason"],
"usage": response.get("usage"),
"model_name": response["model"],
"system_fingerprint": response["system_fingerprint"],
"created": response["created"],
response = self._handle_request(messages_dicts, stop, streaming=False, **kwargs)
message = self._process_response(response)
generation = ChatGeneration(
message=message,
generation_info={
"finish_reason": message.response_metadata["finish_reason"]
},
id=response["id"],
)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
def _stream(
@@ -431,34 +944,9 @@ class ChatSambaNovaCloud(BaseChatModel):
chunk: ChatGenerationChunk with model partial generation
"""
messages_dicts = _create_message_dicts(messages)
finish_reason = None
for partial_response in self._handle_streaming_request(messages_dicts, stop):
if len(partial_response["choices"]) > 0:
finish_reason = partial_response["choices"][0].get("finish_reason")
content = partial_response["choices"][0]["delta"]["content"]
id = partial_response["id"]
chunk = ChatGenerationChunk(
message=AIMessageChunk(content=content, id=id, additional_kwargs={})
)
else:
content = ""
id = partial_response["id"]
metadata = {
"finish_reason": finish_reason,
"usage": partial_response.get("usage"),
"model_name": partial_response["model"],
"system_fingerprint": partial_response["system_fingerprint"],
"created": partial_response["created"],
}
chunk = ChatGenerationChunk(
message=AIMessageChunk(
content=content,
id=id,
response_metadata=metadata,
additional_kwargs={},
)
)
response = self._handle_request(messages_dicts, stop, streaming=True, **kwargs)
for ai_message_chunk in self._process_stream_response(response):
chunk = ChatGenerationChunk(message=ai_message_chunk)
if run_manager:
run_manager.on_llm_new_token(chunk.text, chunk=chunk)
yield chunk
@@ -617,10 +1105,10 @@ class ChatSambaStudio(BaseChatModel):
process_prompt: Optional[bool] = Field(default=True)
"""whether process prompt (for CoE generic v1 and v2 endpoints)"""
stream_options: dict = Field(default={"include_usage": True})
stream_options: Dict[str, Any] = Field(default={"include_usage": True})
"""stream options, include usage to get generation metrics"""
special_tokens: dict = Field(
special_tokens: Dict[str, Any] = Field(
default={
"start": "<|begin_of_text|>",
"start_role": "<|begin_of_text|><|start_header_id|>{role}<|end_header_id|>",

View File

@@ -4,6 +4,7 @@ import logging
from functools import cached_property
from typing import Any, Dict, List, Optional
from langchain_core._api.deprecation import deprecated
from langchain_core.embeddings import Embeddings
from langchain_core.utils import pre_init
from langchain_core.utils.pydantic import get_fields
@@ -15,6 +16,11 @@ MAX_BATCH_SIZE_CHARS = 1000000
MAX_BATCH_SIZE_PARTS = 90
@deprecated(
since="0.3.5",
removal="1.0",
alternative_import="langchain_gigachat.GigaChatEmbeddings",
)
class GigaChatEmbeddings(BaseModel, Embeddings):
"""GigaChat Embeddings models.

View File

@@ -7,6 +7,7 @@ from langchain_core.utils import convert_to_secret_str, get_from_env
from pydantic import (
AliasChoices,
BaseModel,
ConfigDict,
Field,
SecretStr,
model_validator,
@@ -86,8 +87,7 @@ class ClovaXEmbeddings(BaseModel, Embeddings):
timeout: int = Field(gt=0, default=60)
class Config:
arbitrary_types_allowed = True
model_config = ConfigDict(arbitrary_types_allowed=True, protected_namespaces=())
@property
def lc_secrets(self) -> Dict[str, str]:
@@ -115,7 +115,7 @@ class ClovaXEmbeddings(BaseModel, Embeddings):
if not self.ncp_apigw_api_key:
self.ncp_apigw_api_key = convert_to_secret_str(
get_from_env("ncp_apigw_api_key", "NCP_APIGW_API_KEY")
get_from_env("ncp_apigw_api_key", "NCP_APIGW_API_KEY", "")
)
if not self.base_url:
@@ -143,22 +143,28 @@ class ClovaXEmbeddings(BaseModel, Embeddings):
return self
def default_headers(self) -> Dict[str, Any]:
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
clovastudio_api_key = (
self.ncp_clovastudio_api_key.get_secret_value()
if self.ncp_clovastudio_api_key
else None
)
if clovastudio_api_key:
headers["X-NCP-CLOVASTUDIO-API-KEY"] = clovastudio_api_key
apigw_api_key = (
self.ncp_apigw_api_key.get_secret_value()
if self.ncp_apigw_api_key
else None
)
return {
"Content-Type": "application/json",
"Accept": "application/json",
"X-NCP-CLOVASTUDIO-API-KEY": clovastudio_api_key,
"X-NCP-APIGW-API-KEY": apigw_api_key,
}
if apigw_api_key:
headers["X-NCP-APIGW-API-KEY"] = apigw_api_key
return headers
def _embed_text(self, text: str) -> List[float]:
payload = {"text": text}

View File

@@ -458,6 +458,12 @@ def _import_openlm() -> Type[BaseLLM]:
return OpenLM
def _import_outlines() -> Type[BaseLLM]:
from langchain_community.llms.outlines import Outlines
return Outlines
def _import_pai_eas_endpoint() -> Type[BaseLLM]:
from langchain_community.llms.pai_eas_endpoint import PaiEasEndpoint
@@ -807,6 +813,8 @@ def __getattr__(name: str) -> Any:
return _import_openllm()
elif name == "OpenLM":
return _import_openlm()
elif name == "Outlines":
return _import_outlines()
elif name == "PaiEasEndpoint":
return _import_pai_eas_endpoint()
elif name == "Petals":
@@ -954,6 +962,7 @@ __all__ = [
"OpenAIChat",
"OpenLLM",
"OpenLM",
"Outlines",
"PaiEasEndpoint",
"Petals",
"PipelineAI",
@@ -1076,6 +1085,7 @@ def get_type_to_cls_dict() -> Dict[str, Callable[[], Type[BaseLLM]]]:
"vertexai_model_garden": _import_vertex_model_garden,
"openllm": _import_openllm,
"openllm_client": _import_openllm,
"outlines": _import_outlines,
"vllm": _import_vllm,
"vllm_openai": _import_vllm_openai,
"watsonxllm": _import_watsonxllm,

View File

@@ -0,0 +1,314 @@
from __future__ import annotations
import importlib.util
import logging
import platform
from typing import Any, Callable, Dict, Iterator, List, Literal, Optional, Tuple, Union
from langchain_core.callbacks import CallbackManagerForLLMRun
from langchain_core.language_models.llms import LLM
from langchain_core.outputs import GenerationChunk
from pydantic import BaseModel, Field, model_validator
logger = logging.getLogger(__name__)
class Outlines(LLM):
"""LLM wrapper for the Outlines library."""
client: Any = None # :meta private:
model: str
"""Identifier for the model to use with Outlines.
The model identifier should be a string specifying:
- A Hugging Face model name (e.g., "meta-llama/Llama-2-7b-chat-hf")
- A local path to a model
- For GGUF models, the format is "repo_id/file_name"
(e.g., "TheBloke/Llama-2-7B-Chat-GGUF/llama-2-7b-chat.Q4_K_M.gguf")
Examples:
- "TheBloke/Llama-2-7B-Chat-GGUF/llama-2-7b-chat.Q4_K_M.gguf"
- "meta-llama/Llama-2-7b-chat-hf"
"""
backend: Literal[
"llamacpp", "transformers", "transformers_vision", "vllm", "mlxlm"
] = "transformers"
"""Specifies the backend to use for the model.
Supported backends are:
- "llamacpp": For GGUF models using llama.cpp
- "transformers": For Hugging Face Transformers models (default)
- "transformers_vision": For vision-language models (e.g., LLaVA)
- "vllm": For models using the vLLM library
- "mlxlm": For models using the MLX framework
Note: Ensure you have the necessary dependencies installed for the chosen backend.
The system will attempt to import required packages and may raise an ImportError
if they are not available.
"""
max_tokens: int = 256
"""The maximum number of tokens to generate."""
stop: Optional[List[str]] = None
"""A list of strings to stop generation when encountered."""
streaming: bool = True
"""Whether to stream the results, token by token."""
regex: Optional[str] = None
"""Regular expression for structured generation.
If provided, Outlines will guarantee that the generated text matches this regex.
This can be useful for generating structured outputs like IP addresses, dates, etc.
Example: (valid IP address)
regex = r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)"
Note: Computing the regex index can take some time, so it's recommended to reuse
the same regex for multiple generations if possible.
For more details, see: https://dottxt-ai.github.io/outlines/reference/generation/regex/
"""
type_constraints: Optional[Union[type, str]] = None
"""Type constraints for structured generation.
Restricts the output to valid Python types. Supported types include:
int, float, bool, datetime.date, datetime.time, datetime.datetime.
Example:
type_constraints = int
For more details, see: https://dottxt-ai.github.io/outlines/reference/generation/format/
"""
json_schema: Optional[Union[BaseModel, Dict, Callable]] = None
"""Pydantic model, JSON Schema, or callable (function signature)
for structured JSON generation.
Outlines can generate JSON output that follows a specified structure,
which is useful for:
1. Parsing the answer (e.g., with Pydantic), storing it, or returning it to a user.
2. Calling a function with the result.
You can provide:
- A Pydantic model
- A JSON Schema (as a Dict)
- A callable (function signature)
The generated JSON will adhere to the specified structure.
For more details, see: https://dottxt-ai.github.io/outlines/reference/generation/json/
"""
grammar: Optional[str] = None
"""Context-free grammar for structured generation.
If provided, Outlines will generate text that adheres to the specified grammar.
The grammar should be defined in EBNF format.
This can be useful for generating structured outputs like mathematical expressions,
programming languages, or custom domain-specific languages.
Example:
grammar = '''
?start: expression
?expression: term (("+" | "-") term)*
?term: factor (("*" | "/") factor)*
?factor: NUMBER | "-" factor | "(" expression ")"
%import common.NUMBER
'''
Note: Grammar-based generation is currently experimental and may have performance
limitations. It uses greedy generation to mitigate these issues.
For more details and examples, see:
https://dottxt-ai.github.io/outlines/reference/generation/cfg/
"""
custom_generator: Optional[Any] = None
"""Set your own outlines generator object to override the default behavior."""
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
"""Additional parameters to pass to the underlying model.
Example:
model_kwargs = {"temperature": 0.8, "seed": 42}
"""
@model_validator(mode="after")
def validate_environment(self) -> "Outlines":
"""Validate that outlines is installed and create a model instance."""
num_constraints = sum(
[
bool(self.regex),
bool(self.type_constraints),
bool(self.json_schema),
bool(self.grammar),
]
)
if num_constraints > 1:
raise ValueError(
"Either none or exactly one of regex, type_constraints, "
"json_schema, or grammar can be provided."
)
return self.build_client()
def build_client(self) -> "Outlines":
try:
import outlines.models as models
except ImportError:
raise ImportError(
"Could not import the Outlines library. "
"Please install it with `pip install outlines`."
)
def check_packages_installed(
packages: List[Union[str, Tuple[str, str]]],
) -> None:
missing_packages = [
pkg if isinstance(pkg, str) else pkg[0]
for pkg in packages
if importlib.util.find_spec(pkg[1] if isinstance(pkg, tuple) else pkg)
is None
]
if missing_packages:
raise ImportError( # todo this is displaying wrong
f"Missing packages: {', '.join(missing_packages)}. "
"You can install them with:\n\n"
f" pip install {' '.join(missing_packages)}"
)
if self.backend == "llamacpp":
if ".gguf" in self.model:
creator, repo_name, file_name = self.model.split("/", 2)
repo_id = f"{creator}/{repo_name}"
else: # todo add auto-file-selection if no file is given
raise ValueError("GGUF file_name must be provided for llama.cpp.")
check_packages_installed([("llama-cpp-python", "llama_cpp")])
self.client = models.llamacpp(repo_id, file_name, **self.model_kwargs)
elif self.backend == "transformers":
check_packages_installed(["transformers", "torch", "datasets"])
self.client = models.transformers(self.model, **self.model_kwargs)
elif self.backend == "transformers_vision":
check_packages_installed(
["transformers", "datasets", "torchvision", "PIL", "flash_attn"]
)
from transformers import LlavaNextForConditionalGeneration
if not hasattr(models, "transformers_vision"):
raise ValueError(
"transformers_vision backend is not supported, "
"please install the correct outlines version."
)
self.client = models.transformers_vision(
self.model,
model_class=LlavaNextForConditionalGeneration,
**self.model_kwargs,
)
elif self.backend == "vllm":
if platform.system() == "Darwin":
raise ValueError("vLLM backend is not supported on macOS.")
check_packages_installed(["vllm"])
self.client = models.vllm(self.model, **self.model_kwargs)
elif self.backend == "mlxlm":
check_packages_installed(["mlx"])
self.client = models.mlxlm(self.model, **self.model_kwargs)
else:
raise ValueError(f"Unsupported backend: {self.backend}")
return self
@property
def _llm_type(self) -> str:
return "outlines"
@property
def _default_params(self) -> Dict[str, Any]:
return {
"max_tokens": self.max_tokens,
"stop_at": self.stop,
**self.model_kwargs,
}
@property
def _identifying_params(self) -> Dict[str, Any]:
return {
"model": self.model,
"backend": self.backend,
"regex": self.regex,
"type_constraints": self.type_constraints,
"json_schema": self.json_schema,
"grammar": self.grammar,
**self._default_params,
}
@property
def _generator(self) -> Any:
from outlines import generate
if self.custom_generator:
return self.custom_generator
if self.regex:
return generate.regex(self.client, regex_str=self.regex)
if self.type_constraints:
return generate.format(self.client, python_type=self.type_constraints)
if self.json_schema:
return generate.json(self.client, schema_object=self.json_schema)
if self.grammar:
return generate.cfg(self.client, cfg_str=self.grammar)
return generate.text(self.client)
def _call(
self,
prompt: str,
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> str:
params = {**self._default_params, **kwargs}
if stop:
params["stop_at"] = stop
response = ""
if self.streaming:
for chunk in self._stream(
prompt=prompt,
stop=params["stop_at"],
run_manager=run_manager,
**params,
):
response += chunk.text
else:
response = self._generator(prompt, **params)
return response
def _stream(
self,
prompt: str,
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> Iterator[GenerationChunk]:
params = {**self._default_params, **kwargs}
if stop:
params["stop_at"] = stop
for token in self._generator.stream(prompt, **params):
if run_manager:
run_manager.on_llm_new_token(token)
yield GenerationChunk(text=token)
@property
def tokenizer(self) -> Any:
"""Access the tokenizer for the underlying model.
.encode() to tokenize text.
.decode() to convert tokens back to text.
"""
if hasattr(self.client, "tokenizer"):
return self.client.tokenizer
raise ValueError("Tokenizer not found")

View File

@@ -1798,7 +1798,9 @@ def _result_to_document(result: Dict) -> Document:
fields_metadata = json.loads(result[FIELDS_METADATA])
else:
fields_metadata = {
key: value for key, value in result.items() if key != FIELDS_CONTENT_VECTOR
key: value
for key, value in result.items()
if key not in [FIELDS_CONTENT_VECTOR, FIELDS_CONTENT]
}
# IDs
if FIELDS_ID in result:
@@ -1806,7 +1808,7 @@ def _result_to_document(result: Dict) -> Document:
else:
fields_id = {}
return Document(
page_content=result.pop(FIELDS_CONTENT),
page_content=result[FIELDS_CONTENT],
metadata={
**fields_id,
**fields_metadata,

View File

@@ -401,7 +401,7 @@ class OpenSearchVectorSearch(VectorStore):
self.is_aoss = _is_aoss_enabled(http_auth=http_auth)
self.client = _get_opensearch_client(opensearch_url, **kwargs)
self.async_client = _get_async_opensearch_client(opensearch_url, **kwargs)
self.engine = kwargs.get("engine")
self.engine = kwargs.get("engine", "nmslib")
@property
def embeddings(self) -> Embeddings:
@@ -420,7 +420,7 @@ class OpenSearchVectorSearch(VectorStore):
index_name = kwargs.get("index_name", self.index_name)
text_field = kwargs.get("text_field", "text")
dim = len(embeddings[0])
engine = kwargs.get("engine", "nmslib")
engine = kwargs.get("engine", self.engine)
space_type = kwargs.get("space_type", "l2")
ef_search = kwargs.get("ef_search", 512)
ef_construction = kwargs.get("ef_construction", 512)
@@ -461,7 +461,7 @@ class OpenSearchVectorSearch(VectorStore):
index_name = kwargs.get("index_name", self.index_name)
text_field = kwargs.get("text_field", "text")
dim = len(embeddings[0])
engine = kwargs.get("engine", "nmslib")
engine = kwargs.get("engine", self.engine)
space_type = kwargs.get("space_type", "l2")
ef_search = kwargs.get("ef_search", 512)
ef_construction = kwargs.get("ef_construction", 512)
@@ -530,7 +530,7 @@ class OpenSearchVectorSearch(VectorStore):
)
if is_appx_search:
engine = kwargs.get("engine", "nmslib")
engine = kwargs.get("engine", self.engine)
space_type = kwargs.get("space_type", "l2")
ef_search = kwargs.get("ef_search", 512)
ef_construction = kwargs.get("ef_construction", 512)

View File

@@ -4,7 +4,7 @@ from typing import Type
import pytest
from langchain_core.language_models import BaseChatModel
from langchain_standard_tests.integration_tests import ChatModelIntegrationTests
from langchain_tests.integration_tests import ChatModelIntegrationTests
from langchain_community.chat_models.litellm import ChatLiteLLM

View File

@@ -4,7 +4,7 @@ from typing import Type, cast
import pytest
from langchain_core.language_models import BaseChatModel
from langchain_standard_tests.integration_tests import ChatModelIntegrationTests
from langchain_tests.integration_tests import ChatModelIntegrationTests
from pydantic import SecretStr
from langchain_community.chat_models.moonshot import MoonshotChat

View File

@@ -0,0 +1,177 @@
# flake8: noqa
"""Test ChatOutlines wrapper."""
from typing import Generator
import re
import platform
import pytest
from langchain_community.chat_models.outlines import ChatOutlines
from langchain_core.messages import AIMessage, HumanMessage, BaseMessage
from langchain_core.messages import BaseMessageChunk
from pydantic import BaseModel
from tests.unit_tests.callbacks.fake_callback_handler import FakeCallbackHandler
MODEL = "microsoft/Phi-3-mini-4k-instruct"
LLAMACPP_MODEL = "bartowski/qwen2.5-7b-ins-v3-GGUF/qwen2.5-7b-ins-v3-Q4_K_M.gguf"
BACKENDS = ["transformers", "llamacpp"]
if platform.system() != "Darwin":
BACKENDS.append("vllm")
if platform.system() == "Darwin":
BACKENDS.append("mlxlm")
@pytest.fixture(params=BACKENDS)
def chat_model(request: pytest.FixtureRequest) -> ChatOutlines:
if request.param == "llamacpp":
return ChatOutlines(model=LLAMACPP_MODEL, backend=request.param)
else:
return ChatOutlines(model=MODEL, backend=request.param)
def test_chat_outlines_inference(chat_model: ChatOutlines) -> None:
"""Test valid ChatOutlines inference."""
messages = [HumanMessage(content="Say foo:")]
output = chat_model.invoke(messages)
assert isinstance(output, AIMessage)
assert len(output.content) > 1
def test_chat_outlines_streaming(chat_model: ChatOutlines) -> None:
"""Test streaming tokens from ChatOutlines."""
messages = [HumanMessage(content="How do you say 'hello' in Spanish?")]
generator = chat_model.stream(messages)
stream_results_string = ""
assert isinstance(generator, Generator)
for chunk in generator:
assert isinstance(chunk, BaseMessageChunk)
if isinstance(chunk.content, str):
stream_results_string += chunk.content
else:
raise ValueError(
f"Invalid content type, only str is supported, "
f"got {type(chunk.content)}"
)
assert len(stream_results_string.strip()) > 1
def test_chat_outlines_streaming_callback(chat_model: ChatOutlines) -> None:
"""Test that streaming correctly invokes on_llm_new_token callback."""
MIN_CHUNKS = 5
callback_handler = FakeCallbackHandler()
chat_model.callbacks = [callback_handler]
chat_model.verbose = True
messages = [HumanMessage(content="Can you count to 10?")]
chat_model.invoke(messages)
assert callback_handler.llm_streams >= MIN_CHUNKS
def test_chat_outlines_regex(chat_model: ChatOutlines) -> None:
"""Test regex for generating a valid IP address"""
ip_regex = r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)"
chat_model.regex = ip_regex
assert chat_model.regex == ip_regex
messages = [HumanMessage(content="What is the IP address of Google's DNS server?")]
output = chat_model.invoke(messages)
assert isinstance(output, AIMessage)
assert re.match(
ip_regex, str(output.content)
), f"Generated output '{output.content}' is not a valid IP address"
def test_chat_outlines_type_constraints(chat_model: ChatOutlines) -> None:
"""Test type constraints for generating an integer"""
chat_model.type_constraints = int
messages = [
HumanMessage(
content="What is the answer to life, the universe, and everything?"
)
]
output = chat_model.invoke(messages)
assert isinstance(int(str(output.content)), int)
def test_chat_outlines_json(chat_model: ChatOutlines) -> None:
"""Test json for generating a valid JSON object"""
class Person(BaseModel):
name: str
chat_model.json_schema = Person
messages = [HumanMessage(content="Who are the main contributors to LangChain?")]
output = chat_model.invoke(messages)
person = Person.model_validate_json(str(output.content))
assert isinstance(person, Person)
def test_chat_outlines_grammar(chat_model: ChatOutlines) -> None:
"""Test grammar for generating a valid arithmetic expression"""
if chat_model.backend == "mlxlm":
pytest.skip("MLX grammars not yet supported.")
chat_model.grammar = """
?start: expression
?expression: term (("+" | "-") term)*
?term: factor (("*" | "/") factor)*
?factor: NUMBER | "-" factor | "(" expression ")"
%import common.NUMBER
%import common.WS
%ignore WS
"""
messages = [HumanMessage(content="Give me a complex arithmetic expression:")]
output = chat_model.invoke(messages)
# Validate the output is a non-empty string
assert (
isinstance(output.content, str) and output.content.strip()
), "Output should be a non-empty string"
# Use a simple regex to check if the output contains basic arithmetic operations and numbers
assert re.search(
r"[\d\+\-\*/\(\)]+", output.content
), f"Generated output '{output.content}' does not appear to be a valid arithmetic expression"
def test_chat_outlines_with_structured_output(chat_model: ChatOutlines) -> None:
"""Test that ChatOutlines can generate structured outputs"""
class AnswerWithJustification(BaseModel):
"""An answer to the user question along with justification for the answer."""
answer: str
justification: str
structured_chat_model = chat_model.with_structured_output(AnswerWithJustification)
result = structured_chat_model.invoke(
"What weighs more, a pound of bricks or a pound of feathers?"
)
assert isinstance(result, AnswerWithJustification)
assert isinstance(result.answer, str)
assert isinstance(result.justification, str)
assert len(result.answer) > 0
assert len(result.justification) > 0
structured_chat_model_with_raw = chat_model.with_structured_output(
AnswerWithJustification, include_raw=True
)
result_with_raw = structured_chat_model_with_raw.invoke(
"What weighs more, a pound of bricks or a pound of feathers?"
)
assert isinstance(result_with_raw, dict)
assert "raw" in result_with_raw
assert "parsed" in result_with_raw
assert "parsing_error" in result_with_raw
assert isinstance(result_with_raw["raw"], BaseMessage)
assert isinstance(result_with_raw["parsed"], AnswerWithJustification)
assert result_with_raw["parsing_error"] is None

View File

@@ -0,0 +1,123 @@
# flake8: noqa
"""Test Outlines wrapper."""
from typing import Generator
import re
import platform
import pytest
from langchain_community.llms.outlines import Outlines
from pydantic import BaseModel
from tests.unit_tests.callbacks.fake_callback_handler import FakeCallbackHandler
MODEL = "microsoft/Phi-3-mini-4k-instruct"
LLAMACPP_MODEL = "microsoft/Phi-3-mini-4k-instruct-gguf/Phi-3-mini-4k-instruct-q4.gguf"
BACKENDS = ["transformers", "llamacpp"]
if platform.system() != "Darwin":
BACKENDS.append("vllm")
if platform.system() == "Darwin":
BACKENDS.append("mlxlm")
@pytest.fixture(params=BACKENDS)
def llm(request: pytest.FixtureRequest) -> Outlines:
if request.param == "llamacpp":
return Outlines(model=LLAMACPP_MODEL, backend=request.param, max_tokens=100)
else:
return Outlines(model=MODEL, backend=request.param, max_tokens=100)
def test_outlines_inference(llm: Outlines) -> None:
"""Test valid outlines inference."""
output = llm.invoke("Say foo:")
assert isinstance(output, str)
assert len(output) > 1
def test_outlines_streaming(llm: Outlines) -> None:
"""Test streaming tokens from Outlines."""
generator = llm.stream("Q: How do you say 'hello' in Spanish?\n\nA:")
stream_results_string = ""
assert isinstance(generator, Generator)
for chunk in generator:
print(chunk)
assert isinstance(chunk, str)
stream_results_string += chunk
print(stream_results_string)
assert len(stream_results_string.strip()) > 1
def test_outlines_streaming_callback(llm: Outlines) -> None:
"""Test that streaming correctly invokes on_llm_new_token callback."""
MIN_CHUNKS = 5
callback_handler = FakeCallbackHandler()
llm.callbacks = [callback_handler]
llm.verbose = True
llm.invoke("Q: Can you count to 10? A:'1, ")
assert callback_handler.llm_streams >= MIN_CHUNKS
def test_outlines_regex(llm: Outlines) -> None:
"""Test regex for generating a valid IP address"""
ip_regex = r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)"
llm.regex = ip_regex
assert llm.regex == ip_regex
output = llm.invoke("Q: What is the IP address of googles dns server?\n\nA: ")
assert isinstance(output, str)
assert re.match(
ip_regex, output
), f"Generated output '{output}' is not a valid IP address"
def test_outlines_type_constraints(llm: Outlines) -> None:
"""Test type constraints for generating an integer"""
llm.type_constraints = int
output = llm.invoke(
"Q: What is the answer to life, the universe, and everything?\n\nA: "
)
assert int(output)
def test_outlines_json(llm: Outlines) -> None:
"""Test json for generating a valid JSON object"""
class Person(BaseModel):
name: str
llm.json_schema = Person
output = llm.invoke("Q: Who is the author of LangChain?\n\nA: ")
person = Person.model_validate_json(output)
assert isinstance(person, Person)
def test_outlines_grammar(llm: Outlines) -> None:
"""Test grammar for generating a valid arithmetic expression"""
llm.grammar = """
?start: expression
?expression: term (("+" | "-") term)*
?term: factor (("*" | "/") factor)*
?factor: NUMBER | "-" factor | "(" expression ")"
%import common.NUMBER
%import common.WS
%ignore WS
"""
output = llm.invoke("Here is a complex arithmetic expression: ")
# Validate the output is a non-empty string
assert (
isinstance(output, str) and output.strip()
), "Output should be a non-empty string"
# Use a simple regex to check if the output contains basic arithmetic operations and numbers
assert re.search(
r"[\d\+\-\*/\(\)]+", output
), f"Generated output '{output}' does not appear to be a valid arithmetic expression"

View File

@@ -2,7 +2,7 @@ from typing import Generator, Tuple
import pytest
from langchain_core.documents import Document
from langchain_standard_tests.integration_tests.base_store import BaseStoreSyncTests
from langchain_tests.integration_tests.base_store import BaseStoreSyncTests
from langchain_community.storage.mongodb import MongoDBByteStore, MongoDBStore

View File

@@ -3,7 +3,7 @@
import uuid
import pytest
from langchain_standard_tests.integration_tests.vectorstores import (
from langchain_tests.integration_tests.vectorstores import (
AsyncReadWriteTestSuite,
ReadWriteTestSuite,
)

View File

@@ -11,7 +11,7 @@ from langchain_core.messages import (
SystemMessage,
ToolMessage,
)
from langchain_standard_tests.unit_tests import ChatModelUnitTests
from langchain_tests.unit_tests import ChatModelUnitTests
from langchain_community.chat_models.cloudflare_workersai import (
ChatCloudflareWorkersAI,

View File

@@ -36,6 +36,7 @@ EXPECTED_ALL = [
"ChatOCIModelDeploymentTGI",
"ChatOllama",
"ChatOpenAI",
"ChatOutlines",
"ChatPerplexity",
"ChatPremAI",
"ChatSambaNovaCloud",

View File

@@ -4,7 +4,7 @@ from typing import Type
import pytest
from langchain_core.language_models import BaseChatModel
from langchain_standard_tests.unit_tests import ChatModelUnitTests
from langchain_tests.unit_tests import ChatModelUnitTests
from langchain_community.chat_models.litellm import ChatLiteLLM

View File

@@ -0,0 +1,91 @@
import pytest
from _pytest.monkeypatch import MonkeyPatch
from pydantic import BaseModel, Field
from langchain_community.chat_models.outlines import ChatOutlines
def test_chat_outlines_initialization(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
chat = ChatOutlines(
model="microsoft/Phi-3-mini-4k-instruct",
max_tokens=42,
stop=["\n"],
)
assert chat.model == "microsoft/Phi-3-mini-4k-instruct"
assert chat.max_tokens == 42
assert chat.backend == "transformers"
assert chat.stop == ["\n"]
def test_chat_outlines_backend_llamacpp(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
chat = ChatOutlines(
model="TheBloke/Llama-2-7B-Chat-GGUF/llama-2-7b-chat.Q4_K_M.gguf",
backend="llamacpp",
)
assert chat.backend == "llamacpp"
def test_chat_outlines_backend_vllm(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
chat = ChatOutlines(model="microsoft/Phi-3-mini-4k-instruct", backend="vllm")
assert chat.backend == "vllm"
def test_chat_outlines_backend_mlxlm(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
chat = ChatOutlines(model="microsoft/Phi-3-mini-4k-instruct", backend="mlxlm")
assert chat.backend == "mlxlm"
def test_chat_outlines_with_regex(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
regex = r"\d{3}-\d{3}-\d{4}"
chat = ChatOutlines(model="microsoft/Phi-3-mini-4k-instruct", regex=regex)
assert chat.regex == regex
def test_chat_outlines_with_type_constraints(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
chat = ChatOutlines(model="microsoft/Phi-3-mini-4k-instruct", type_constraints=int)
assert chat.type_constraints == int # noqa
def test_chat_outlines_with_json_schema(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
class TestSchema(BaseModel):
name: str = Field(description="A person's name")
age: int = Field(description="A person's age")
chat = ChatOutlines(
model="microsoft/Phi-3-mini-4k-instruct", json_schema=TestSchema
)
assert chat.json_schema == TestSchema
def test_chat_outlines_with_grammar(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
grammar = """
?start: expression
?expression: term (("+" | "-") term)*
?term: factor (("*" | "/") factor)*
?factor: NUMBER | "-" factor | "(" expression ")"
%import common.NUMBER
"""
chat = ChatOutlines(model="microsoft/Phi-3-mini-4k-instruct", grammar=grammar)
assert chat.grammar == grammar
def test_raise_for_multiple_output_constraints(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(ChatOutlines, "build_client", lambda self: self)
with pytest.raises(ValueError):
ChatOutlines(
model="microsoft/Phi-3-mini-4k-instruct",
type_constraints=int,
regex=r"\d{3}-\d{3}-\d{4}",
)

View File

@@ -67,6 +67,7 @@ EXPECT_ALL = [
"OpenAIChat",
"OpenLLM",
"OpenLM",
"Outlines",
"PaiEasEndpoint",
"Petals",
"PipelineAI",

View File

@@ -0,0 +1,92 @@
import pytest
from _pytest.monkeypatch import MonkeyPatch
from langchain_community.llms.outlines import Outlines
def test_outlines_initialization(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
llm = Outlines(
model="microsoft/Phi-3-mini-4k-instruct",
max_tokens=42,
stop=["\n"],
)
assert llm.model == "microsoft/Phi-3-mini-4k-instruct"
assert llm.max_tokens == 42
assert llm.backend == "transformers"
assert llm.stop == ["\n"]
def test_outlines_backend_llamacpp(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
llm = Outlines(
model="TheBloke/Llama-2-7B-Chat-GGUF/llama-2-7b-chat.Q4_K_M.gguf",
backend="llamacpp",
)
assert llm.backend == "llamacpp"
def test_outlines_backend_vllm(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
llm = Outlines(model="microsoft/Phi-3-mini-4k-instruct", backend="vllm")
assert llm.backend == "vllm"
def test_outlines_backend_mlxlm(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
llm = Outlines(model="microsoft/Phi-3-mini-4k-instruct", backend="mlxlm")
assert llm.backend == "mlxlm"
def test_outlines_with_regex(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
regex = r"\d{3}-\d{3}-\d{4}"
llm = Outlines(model="microsoft/Phi-3-mini-4k-instruct", regex=regex)
assert llm.regex == regex
def test_outlines_with_type_constraints(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
llm = Outlines(model="microsoft/Phi-3-mini-4k-instruct", type_constraints=int)
assert llm.type_constraints == int # noqa
def test_outlines_with_json_schema(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
from pydantic import BaseModel, Field
class TestSchema(BaseModel):
name: str = Field(description="A person's name")
age: int = Field(description="A person's age")
llm = Outlines(model="microsoft/Phi-3-mini-4k-instruct", json_schema=TestSchema)
assert llm.json_schema == TestSchema
def test_outlines_with_grammar(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
grammar = """
?start: expression
?expression: term (("+" | "-") term)*
?term: factor (("*" | "/") factor)*
?factor: NUMBER | "-" factor | "(" expression ")"
%import common.NUMBER
"""
llm = Outlines(model="microsoft/Phi-3-mini-4k-instruct", grammar=grammar)
assert llm.grammar == grammar
def test_raise_for_multiple_output_constraints(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(Outlines, "build_client", lambda self: self)
with pytest.raises(ValueError):
Outlines(
model="microsoft/Phi-3-mini-4k-instruct",
type_constraints=int,
regex=r"\d{3}-\d{3}-\d{4}",
)
Outlines(
model="microsoft/Phi-3-mini-4k-instruct",
type_constraints=int,
regex=r"\d{3}-\d{3}-\d{4}",
)

View File

@@ -3,7 +3,7 @@ from typing import Any
import pytest
from langchain_core.documents import Document
from langchain_standard_tests.integration_tests.vectorstores import (
from langchain_tests.integration_tests.vectorstores import (
AsyncReadWriteTestSuite,
ReadWriteTestSuite,
)

View File

@@ -47,7 +47,6 @@ def print_sys_info(*, additional_pkgs: Sequence[str] = ()) -> None:
# Packages that do not start with "langchain" prefix.
other_langchain_packages = [
"langserve",
"langgraph",
"langsmith",
]
@@ -55,8 +54,17 @@ def print_sys_info(*, additional_pkgs: Sequence[str] = ()) -> None:
name for _, name, _ in pkgutil.iter_modules() if name.startswith("langchain")
]
langgraph_pkgs = [
name for _, name, _ in pkgutil.iter_modules() if name.startswith("langgraph")
]
all_packages = sorted(
set(langchain_pkgs + other_langchain_packages + list(additional_pkgs))
set(
langchain_pkgs
+ langgraph_pkgs
+ other_langchain_packages
+ list(additional_pkgs)
)
)
# Always surface these packages to the top

View File

@@ -3,7 +3,7 @@
from collections.abc import AsyncGenerator, Generator
import pytest
from langchain_standard_tests.integration_tests.indexer import (
from langchain_tests.integration_tests.indexer import (
AsyncDocumentIndexTestSuite,
DocumentIndexerTestSuite,
)

View File

@@ -1,5 +1,5 @@
import pytest
from langchain_standard_tests.integration_tests.base_store import (
from langchain_tests.integration_tests.base_store import (
BaseStoreAsyncTests,
BaseStoreSyncTests,
)

View File

@@ -2,7 +2,7 @@ from pathlib import Path
from unittest.mock import AsyncMock, Mock
import pytest
from langchain_standard_tests.integration_tests.vectorstores import (
from langchain_tests.integration_tests.vectorstores import (
AsyncReadWriteTestSuite,
ReadWriteTestSuite,
)

View File

@@ -1,48 +0,0 @@
# This is a Dockerfile for running unit tests
ARG POETRY_HOME=/opt/poetry
# Use the Python base image
FROM python:3.11.2-bullseye AS builder
# Define the version of Poetry to install (default is 1.4.2)
ARG POETRY_VERSION=1.4.2
# Define the directory to install Poetry to (default is /opt/poetry)
ARG POETRY_HOME
# Create a Python virtual environment for Poetry and install it
RUN python3 -m venv ${POETRY_HOME} && \
$POETRY_HOME/bin/pip install --upgrade pip && \
$POETRY_HOME/bin/pip install poetry==${POETRY_VERSION}
# Test if Poetry is installed in the expected path
RUN echo "Poetry version:" && $POETRY_HOME/bin/poetry --version
# Set the working directory for the app
WORKDIR /app
# Use a multi-stage build to install dependencies
FROM builder AS dependencies
ARG POETRY_HOME
# Copy only the dependency files for installation
COPY pyproject.toml poetry.lock poetry.toml ./
# Install the Poetry dependencies (this layer will be cached as long as the dependencies don't change)
RUN $POETRY_HOME/bin/poetry install --no-interaction --no-ansi --with test
# Use a multi-stage build to run tests
FROM dependencies AS tests
# Copy the rest of the app source code (this layer will be invalidated and rebuilt whenever the source code changes)
COPY . .
RUN /opt/poetry/bin/poetry install --no-interaction --no-ansi --with test
# Set the entrypoint to run tests using Poetry
ENTRYPOINT ["/opt/poetry/bin/poetry", "run", "pytest"]
# Set the default command to run all unit tests
CMD ["tests/unit_tests"]

View File

@@ -1,63 +0,0 @@
# This is a Dockerfile for the Development Container
# Use the Python base image
ARG VARIANT="3.11-bullseye"
FROM mcr.microsoft.com/devcontainers/python:0-${VARIANT} AS langchain-dev-base
USER vscode
# Define the version of Poetry to install (default is 1.4.2)
# Define the directory of python virtual environment
ARG PYTHON_VIRTUALENV_HOME=/home/vscode/langchain-py-env \
POETRY_VERSION=1.3.2
ENV POETRY_VIRTUALENVS_IN_PROJECT=false \
POETRY_NO_INTERACTION=true
# Install Poetry outside of the v`irtual environment to avoid conflicts
RUN python3 -m pip install --user pipx && \
python3 -m pipx ensurepath && \
pipx install poetry==${POETRY_VERSION}
# Create a Python virtual environment for the project
RUN python3 -m venv ${PYTHON_VIRTUALENV_HOME} && \
$PYTHON_VIRTUALENV_HOME/bin/pip install --upgrade pip
ENV PATH="$PYTHON_VIRTUALENV_HOME/bin:$PATH" \
VIRTUAL_ENV=$PYTHON_VIRTUALENV_HOME
# Setup for bash
RUN poetry completions bash >> /home/vscode/.bash_completion && \
echo "export PATH=$PYTHON_VIRTUALENV_HOME/bin:$PATH" >> ~/.bashrc
# Set the working directory for the app
WORKDIR /workspaces/langchain
# Use a multi-stage build to install dependencies
FROM langchain-dev-base AS langchain-dev-dependencies
ARG PYTHON_VIRTUALENV_HOME
# Copy only the dependency files for installation
COPY libs/langchain/pyproject.toml libs/langchain/poetry.toml libs/langchain/poetry.lock ./
# Copy the langchain library for installation
COPY libs/langchain/ libs/langchain/
# Copy the core library for installation
COPY libs/core ../core
# Copy the community library for installation
COPY libs/community/ ../community/
# Copy the text-splitters library for installation
COPY libs/text-splitters/ ../text-splitters/
# Copy the partners library for installation
COPY libs/partners ../partners/
# Copy the standard-tests library for installation
COPY libs/standard-tests ../standard-tests/
# Install the Poetry dependencies (this layer will be cached as long as the dependencies don't change)
RUN poetry install --no-interaction --no-ansi --with dev,test,docs

View File

@@ -9,7 +9,7 @@ from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
FINAL_ANSWER_ACTION = "Final Answer:"
MISSING_ACTION_AFTER_THOUGHT_ERROR_MESSAGE = (
"Invalid Format: Missing 'Action:' after 'Thought:"
"Invalid Format: Missing 'Action:' after 'Thought:'"
)
MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE = (
"Invalid Format: Missing 'Action Input:' after 'Action:'"

View File

@@ -5,7 +5,7 @@ from langchain_core.language_models import BaseChatModel
from langchain_core.messages import AIMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_standard_tests.integration_tests import ChatModelIntegrationTests
from langchain_tests.integration_tests import ChatModelIntegrationTests
from pydantic import BaseModel
from langchain.chat_models import init_chat_model

View File

@@ -5,7 +5,7 @@ from typing import Dict, List, Literal, Type, cast
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import AIMessage
from langchain_standard_tests.integration_tests import ChatModelIntegrationTests
from langchain_tests.integration_tests import ChatModelIntegrationTests
from langchain_anthropic import ChatAnthropic

View File

@@ -3,7 +3,7 @@
from typing import Type
from langchain_core.language_models import BaseChatModel
from langchain_standard_tests.unit_tests import ChatModelUnitTests
from langchain_tests.unit_tests import ChatModelUnitTests
from langchain_anthropic import ChatAnthropic

View File

@@ -1,12 +1,16 @@
"""Test Chroma functionality."""
import uuid
from typing import Generator
from typing import (
Generator,
cast,
)
import chromadb
import pytest # type: ignore[import-not-found]
import requests
from chromadb.api.client import SharedSystemClient
from chromadb.api.types import Embeddable
from langchain_core.documents import Document
from langchain_core.embeddings.fake import FakeEmbeddings as Fak
@@ -17,6 +21,15 @@ from tests.integration_tests.fake_embeddings import (
)
class MyEmbeddingFunction:
def __init__(self, fak: Fak):
self.fak = fak
def __call__(self, input: Embeddable) -> list[list[float]]:
texts = cast(list[str], input)
return self.fak.embed_documents(texts=texts)
@pytest.fixture()
def client() -> Generator[chromadb.ClientAPI, None, None]:
SharedSystemClient.clear_system_cache()
@@ -254,8 +267,8 @@ def test_chroma_update_document() -> None:
# Assert that the updated document is returned by the search
assert output == [Document(page_content=updated_content, metadata={"page": "0"})]
assert new_embedding == embedding.embed_documents([updated_content])[0]
assert new_embedding != old_embedding
assert list(new_embedding) == list(embedding.embed_documents([updated_content])[0])
assert list(new_embedding) != list(old_embedding)
# TODO: RELEVANCE SCORE IS BROKEN. FIX TEST
@@ -341,17 +354,17 @@ def batch_support_chroma_version() -> bool:
)
def test_chroma_large_batch() -> None:
client = chromadb.HttpClient()
embedding_function = Fak(size=255)
embedding_function = MyEmbeddingFunction(fak=Fak(size=255))
col = client.get_or_create_collection(
"my_collection",
embedding_function=embedding_function.embed_documents, # type: ignore
embedding_function=embedding_function, # type: ignore
)
docs = ["This is a test document"] * (client.max_batch_size + 100) # type: ignore
docs = ["This is a test document"] * (client.get_max_batch_size() + 100) # type: ignore
db = Chroma.from_texts(
client=client,
collection_name=col.name,
texts=docs,
embedding=embedding_function,
embedding=embedding_function.fak,
ids=[str(uuid.uuid4()) for _ in range(len(docs))],
)
@@ -369,18 +382,18 @@ def test_chroma_large_batch() -> None:
)
def test_chroma_large_batch_update() -> None:
client = chromadb.HttpClient()
embedding_function = Fak(size=255)
embedding_function = MyEmbeddingFunction(fak=Fak(size=255))
col = client.get_or_create_collection(
"my_collection",
embedding_function=embedding_function.embed_documents, # type: ignore
embedding_function=embedding_function, # type: ignore
)
docs = ["This is a test document"] * (client.max_batch_size + 100) # type: ignore
docs = ["This is a test document"] * (client.get_max_batch_size() + 100) # type: ignore
ids = [str(uuid.uuid4()) for _ in range(len(docs))]
db = Chroma.from_texts(
client=client,
collection_name=col.name,
texts=docs,
embedding=embedding_function,
embedding=embedding_function.fak,
ids=ids,
)
new_docs = [
@@ -408,7 +421,7 @@ def test_chroma_legacy_batching() -> None:
embedding_function = Fak(size=255)
col = client.get_or_create_collection(
"my_collection",
embedding_function=embedding_function.embed_documents, # type: ignore
embedding_function=MyEmbeddingFunction, # type: ignore
)
docs = ["This is a test document"] * 100
db = Chroma.from_texts(

View File

@@ -4,7 +4,7 @@ from typing import Type
import pytest
from langchain_core.language_models import BaseChatModel
from langchain_standard_tests.integration_tests import ( # type: ignore[import-not-found]
from langchain_tests.integration_tests import ( # type: ignore[import-not-found]
ChatModelIntegrationTests, # type: ignore[import-not-found]
)

View File

@@ -3,7 +3,7 @@
from typing import Tuple, Type
from langchain_core.embeddings import Embeddings
from langchain_standard_tests.unit_tests.embeddings import EmbeddingsUnitTests
from langchain_tests.unit_tests.embeddings import EmbeddingsUnitTests
from langchain_fireworks import FireworksEmbeddings

View File

@@ -3,7 +3,7 @@
from typing import Tuple, Type
from langchain_core.language_models import BaseChatModel
from langchain_standard_tests.unit_tests import ( # type: ignore[import-not-found]
from langchain_tests.unit_tests import ( # type: ignore[import-not-found]
ChatModelUnitTests, # type: ignore[import-not-found]
)

View File

@@ -395,7 +395,7 @@ def test_json_mode_structured_output() -> None:
def test_tool_calling_no_arguments() -> None:
# Note: this is a variant of a test in langchain_standard_tests
# Note: this is a variant of a test in langchain_tests
# that as of 2024-08-19 fails with "Failed to call a function. Please
# adjust your prompt." when `tool_choice="any"` is specified, but
# passes when `tool_choice` is not specified.

View File

@@ -5,7 +5,7 @@ from typing import Optional, Type
import pytest
from langchain_core.language_models import BaseChatModel
from langchain_core.rate_limiters import InMemoryRateLimiter
from langchain_standard_tests.integration_tests import (
from langchain_tests.integration_tests import (
ChatModelIntegrationTests,
)

View File

@@ -3,7 +3,7 @@
from typing import Type
from langchain_core.language_models import BaseChatModel
from langchain_standard_tests.unit_tests.chat_models import (
from langchain_tests.unit_tests.chat_models import (
ChatModelUnitTests,
)

View File

@@ -127,7 +127,7 @@ class HuggingFaceEndpointEmbeddings(BaseModel, Embeddings):
texts = [text.replace("\n", " ") for text in texts]
_model_kwargs = self.model_kwargs or {}
responses = await self.async_client.post(
json={"inputs": texts, "parameters": _model_kwargs}, task=self.task
json={"inputs": texts, **_model_kwargs}, task=self.task
)
return json.loads(responses.decode())

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