Compare commits

..

27 Commits

Author SHA1 Message Date
Chester Curme
d3b05c1753 x 2025-08-11 13:03:57 -04:00
Chester Curme
fb4996ccda x 2025-08-11 13:02:20 -04:00
ccurme
45a067509f fix(core): fix tracing for PDFs in v1 messages (#32434) 2025-08-11 12:18:32 -04:00
Mason Daugherty
23c3fa65d4 feat(docs): enhance ResponseMetadata documentation with provider field usage notes (#32472) 2025-08-11 09:16:16 -04:00
Mason Daugherty
13d67cf37e fix(ollama): reasoning should come before text content (#32476) 2025-08-08 19:34:36 -04:00
Mason Daugherty
7f989d3c3b feat(docs): clarify ToolMessage contentfield usage 2025-08-08 13:02:59 -04:00
Mason Daugherty
b7968c2b7d feat(docs): add link to artifact usage in ToolMessage 2025-08-08 12:51:15 -04:00
Mason Daugherty
2f0c6421a1 Merge branch 'master' into wip-v0.4 2025-08-08 10:21:44 -04:00
Chester Curme
cfe13f673a Merge branch 'master' into wip-v0.4
# Conflicts:
#	libs/core/langchain_core/version.py
#	libs/core/pyproject.toml
#	libs/core/uv.lock
#	libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py
#	libs/partners/openai/uv.lock
2025-08-08 09:04:57 -04:00
Mason Daugherty
5599c59d4a chore: formatting across codebase (#32456)
To prevent polluting future PRs
2025-08-07 22:09:26 -04:00
Mason Daugherty
11d68a0b9e bump locks 2025-08-07 15:51:36 -04:00
Mason Daugherty
566774a893 Merge branch 'wip-v0.4' of github.com:langchain-ai/langchain into wip-v0.4 2025-08-07 15:50:40 -04:00
Mason Daugherty
255a6d668a feat: allow bypassing CI using PR label 2025-08-07 15:50:15 -04:00
Mason Daugherty
cbf4c0e565 Merge branch 'master' into wip-v0.4 2025-08-07 15:33:12 -04:00
Mason Daugherty
dc66737f03 fix: docs and formatting (#32448) 2025-08-07 15:17:25 -04:00
Christophe Bornet
499dc35cfb chore(core): bump mypy version to 1.17 (#32390)
Co-authored-by: Mason Daugherty <mason@langchain.dev>
2025-08-07 13:26:29 -04:00
Mason Daugherty
42c1159991 feat: add TextAccessor, deprecate .text() as method (#32441)
Adds backward compat for `.text()` on messages while keeping `.text`
access

_The kicker:_

Any previous use of `.text()` will now need a `# type: ignore[operator]`
to silence type checkers. However, it will still behave as expected at
runtime. Deprecating in v0.4.0, to be removed in v2.0.0.
2025-08-07 12:16:31 -04:00
Mason Daugherty
cc6139860c fix: docs typing issues 2025-08-06 23:50:33 -04:00
Mason Daugherty
ae8f58ac6f fix(settings): update Python terminal settings and default interpreter path 2025-08-06 23:37:40 -04:00
Mason Daugherty
346731544b Merge branch 'master' into wip-v0.4 2025-08-06 18:24:10 -04:00
Mason Daugherty
c1b86cc929 feat: minor core work, v1 standard tests & (most of) v1 ollama (#32315)
Resolves #32215

---------

Co-authored-by: Chester Curme <chester.curme@gmail.com>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
Co-authored-by: Nuno Campos <nuno@langchain.dev>
2025-08-06 18:22:02 -04:00
Mason Daugherty
376f70be96 sync wip with master (#32436)
Co-authored-by: Kanav Bansal <13186335+bansalkanav@users.noreply.github.com>
Co-authored-by: Pranav Bhartiya <124018094+pranauww@users.noreply.github.com>
Co-authored-by: Nelson Sproul <nelson.sproul@gmail.com>
Co-authored-by: John Bledsoe <jmbledsoe@gmail.com>
2025-08-06 17:57:05 -04:00
ccurme
ac2de920b1 chore: increment versions for 0.4 branch (#32419) 2025-08-05 15:39:37 -04:00
ccurme
e02eed5489 feat: standard outputs (#32287)
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: Nuno Campos <nuno@langchain.dev>
2025-08-05 15:17:32 -04:00
Chester Curme
5414527236 Merge branch 'master' into wip-v0.4 2025-08-04 11:55:14 -04:00
Chester Curme
881c6534a6 Merge branch 'master' into wip-v0.4
# Conflicts:
#	.github/workflows/_integration_test.yml
#	.github/workflows/_release.yml
#	.github/workflows/api_doc_build.yml
#	.github/workflows/people.yml
#	.github/workflows/run_notebooks.yml
#	.github/workflows/scheduled_test.yml
#	SECURITY.md
#	docs/docs/integrations/vectorstores/pgvectorstore.ipynb
#	libs/langchain_v1/langchain/chat_models/base.py
#	libs/langchain_v1/tests/integration_tests/chat_models/test_base.py
#	libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py
2025-07-30 13:16:17 -04:00
Mason Daugherty
5e9eb19a83 chore: update branch with changes from master (#32277)
Co-authored-by: Maxime Grenu <69890511+cluster2600@users.noreply.github.com>
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: jmaillefaud <jonathan.maillefaud@evooq.ch>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
Co-authored-by: tanwirahmad <tanwirahmad@users.noreply.github.com>
Co-authored-by: Christophe Bornet <cbornet@hotmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: niceg <79145285+growmuye@users.noreply.github.com>
Co-authored-by: Chaitanya varma <varmac301@gmail.com>
Co-authored-by: dishaprakash <57954147+dishaprakash@users.noreply.github.com>
Co-authored-by: Chester Curme <chester.curme@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Kanav Bansal <13186335+bansalkanav@users.noreply.github.com>
Co-authored-by: Aleksandr Filippov <71711753+alex-feel@users.noreply.github.com>
Co-authored-by: Alex Feel <afilippov@spotware.com>
2025-07-28 10:39:41 -04:00
353 changed files with 26871 additions and 13940 deletions

View File

@@ -7,4 +7,4 @@ To learn how to contribute to LangChain, please follow the [contribution guide h
## New features
For new features, please start a new [discussion on our forum](https://forum.langchain.com/), where the maintainers will help with scoping out the necessary changes.
For new features, please start a new [discussion](https://forum.langchain.com/), where the maintainers will help with scoping out the necessary changes.

View File

@@ -220,7 +220,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v4
with:
name: dist
path: ${{ inputs.working-directory }}/dist/
@@ -379,7 +379,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v4
if: startsWith(inputs.working-directory, 'libs/core')
with:
name: dist
@@ -447,7 +447,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v4
with:
name: dist
path: ${{ inputs.working-directory }}/dist/
@@ -486,7 +486,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v4
with:
name: dist
path: ${{ inputs.working-directory }}/dist/

View File

@@ -85,7 +85,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v4
with:
name: test-dist
path: ${{ inputs.working-directory }}/dist/

View File

@@ -9,13 +9,15 @@
</div>
[![Release Notes](https://img.shields.io/github/release/langchain-ai/langchain?style=flat-square)](https://github.com/langchain-ai/langchain/releases)
[![CI](https://github.com/langchain-ai/langchain/actions/workflows/check_diffs.yml/badge.svg)](https://github.com/langchain-ai/langchain/actions/workflows/check_diffs.yml)
[![PyPI - License](https://img.shields.io/pypi/l/langchain-core?style=flat-square)](https://opensource.org/licenses/MIT)
[![PyPI - Downloads](https://img.shields.io/pepy/dt/langchain)](https://pypistats.org/packages/langchain-core)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/langchain-core?style=flat-square)](https://pypistats.org/packages/langchain-core)
[![GitHub star chart](https://img.shields.io/github/stars/langchain-ai/langchain?style=flat-square)](https://star-history.com/#langchain-ai/langchain)
[![Open Issues](https://img.shields.io/github/issues-raw/langchain-ai/langchain?style=flat-square)](https://github.com/langchain-ai/langchain/issues)
[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode&style=flat-square)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/langchain-ai/langchain)
[<img src="https://github.com/codespaces/badge.svg" alt="Open in Github Codespace" title="Open in Github Codespace" width="150" height="20">](https://codespaces.new/langchain-ai/langchain)
[![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/langchain-ai/langchain)
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/langchainai.svg?style=social&label=Follow%20%40LangChainAI)](https://twitter.com/langchainai)
[![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/langchain-ai/langchain)
> [!NOTE]
> Looking for the JS/TS library? Check out [LangChain.js](https://github.com/langchain-ai/langchainjs).
@@ -66,7 +68,7 @@ reliably handle complex tasks with LangGraph, our low-level agent orchestration
framework. LangGraph offers customizable architecture, long-term memory, and
human-in-the-loop workflows — and is trusted in production by companies like LinkedIn,
Uber, Klarna, and GitLab.
- [LangGraph Platform](https://docs.langchain.com/langgraph-platform) - Deploy
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) - Deploy
and scale agents effortlessly with a purpose-built deployment platform for long
running, stateful workflows. Discover, reuse, configure, and share agents across
teams — and iterate quickly with visual prototyping in
@@ -83,4 +85,3 @@ concepts behind the LangChain framework.
- [LangChain Forum](https://forum.langchain.com/): Connect with the community and share all of your technical questions, ideas, and feedback.
- [API Reference](https://python.langchain.com/api_reference/): Detailed reference on
navigating base packages and integrations for LangChain.
- [Chat LangChain](https://chat.langchain.com/): Ask questions & chat with our documentation

View File

@@ -79,17 +79,6 @@
"tool_executor = ToolExecutor(tools)"
]
},
{
"cell_type": "markdown",
"id": "168152fc",
"metadata": {},
"source": [
"📘 **Note on `SystemMessage` usage with LangGraph-based agents**\n",
"\n",
"When constructing the `messages` list for an agent, you *must* manually include any `SystemMessage`s.\n",
"Unlike some agent executors in LangChain that set a default, LangGraph requires explicit inclusion."
]
},
{
"cell_type": "markdown",
"id": "fe6e8f78-1ef7-42ad-b2bf-835ed5850553",

View File

@@ -217,7 +217,11 @@ def _load_package_modules(
# Get the full namespace of the module
namespace = str(relative_module_name).replace(".py", "").replace("/", ".")
# Keep only the top level namespace
top_namespace = namespace.split(".")[0]
# (but make special exception for content_blocks and v1.messages)
if namespace == "messages.content_blocks" or namespace == "v1.messages":
top_namespace = namespace # Keep full namespace for content_blocks
else:
top_namespace = namespace.split(".")[0]
try:
# If submodule is present, we need to construct the paths in a slightly
@@ -545,14 +549,7 @@ def _build_index(dirs: List[str]) -> None:
"ai21": "AI21",
"ibm": "IBM",
}
ordered = [
"core",
"langchain",
"text-splitters",
"community",
"experimental",
"standard-tests",
]
ordered = ["core", "langchain", "text-splitters", "community", "experimental"]
main_ = [dir_ for dir_ in ordered if dir_ in dirs]
integrations = sorted(dir_ for dir_ in dirs if dir_ not in main_)
doc = """# LangChain Python API Reference

View File

@@ -1,4 +1,4 @@
# Async programming with LangChain
# Async programming with langchain
:::info Prerequisites
* [Runnable interface](/docs/concepts/runnables)
@@ -12,7 +12,7 @@ You are expected to be familiar with asynchronous programming in Python before r
This guide specifically focuses on what you need to know to work with LangChain in an asynchronous context, assuming that you are already familiar with asynchronous programming.
:::
## LangChain asynchronous APIs
## Langchain asynchronous APIs
Many LangChain APIs are designed to be asynchronous, allowing you to build efficient and responsive applications.

View File

@@ -147,7 +147,7 @@ An `AIMessage` has the following attributes. The attributes which are **standard
| `tool_calls` | Standardized | Tool calls associated with the message. See [tool calling](/docs/concepts/tool_calling) for details. |
| `invalid_tool_calls` | Standardized | Tool calls with parsing errors associated with the message. See [tool calling](/docs/concepts/tool_calling) for details. |
| `usage_metadata` | Standardized | Usage metadata for a message, such as [token counts](/docs/concepts/tokens). See [Usage Metadata API Reference](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.ai.UsageMetadata.html). |
| `id` | Standardized | An optional unique identifier for the message, ideally provided by the provider/model that created the message. See [Message IDs](#message-ids) for details. |
| `id` | Standardized | An optional unique identifier for the message, ideally provided by the provider/model that created the message. |
| `response_metadata` | Raw | Response metadata, e.g., response headers, logprobs, token counts. |
#### content
@@ -243,37 +243,3 @@ At the moment, the output of the model will be in terms of LangChain messages, s
need OpenAI format for the output as well.
The [convert_to_openai_messages](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.convert_to_openai_messages.html) utility function can be used to convert from LangChain messages to OpenAI format.
## Message IDs
LangChain messages include an optional `id` field that serves as a unique identifier. Understanding when and how these IDs are assigned can be helpful for debugging, tracing, and working with message history.
### When Messages Get IDs
Messages receive IDs in the following scenarios:
**Automatically assigned by LangChain:**
- When generated through chat model invocation (`.invoke()`, `.stream()`, `.astream()`) with an active run manager/tracing context
- IDs follow the format:
- `run-$RUN_ID` (e.g., `run-ba48f958-6402-41a5-b461-5e250a4ebd36-0`)
- `run-$RUN_ID-$IDX` (e.g., `run-ba48f958-6402-41a5-b461-5e250a4ebd36-1`) when there are multiple generations from a single chat model invocation.
**Provider-assigned IDs (highest priority):**
- When the model provider assigns its own ID to the message
- These take precedence over LangChain-generated run IDs
- Format varies by provider
### When Messages Don't Get IDs
Messages will **not** receive IDs in these situations:
- **Manual message creation**: Messages created directly (e.g., `AIMessage(content="hello")`) without going through chat models
- **No run manager context**: When there's no active callback/tracing infrastructure
### ID Priority System
LangChain follows a clear precedence system for message IDs:
1. **Provider-assigned IDs** (highest priority): IDs from the model provider
2. **LangChain run IDs** (medium priority): IDs starting with `run-`
3. **Manual IDs** (lowest priority): IDs explicitly set by users

View File

@@ -53,29 +53,17 @@ This is how you use MessagesPlaceholder.
```python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.messages import HumanMessage
prompt_template = ChatPromptTemplate([
("system", "You are a helpful assistant"),
MessagesPlaceholder("msgs")
])
# Simple example with one message
prompt_template.invoke({"msgs": [HumanMessage(content="hi!")]})
# More complex example with conversation history
messages_to_pass = [
HumanMessage(content="What's the capital of France?"),
AIMessage(content="The capital of France is Paris."),
HumanMessage(content="And what about Germany?")
]
formatted_prompt = prompt_template.invoke({"msgs": messages_to_pass})
print(formatted_prompt)
```
This will produce a list of four messages total: the system message plus the three messages we passed in (two HumanMessages and one AIMessage).
This will produce a list of two messages, the first one being a system message, and the second one being the HumanMessage we passed in.
If we had passed in 5 messages, then it would have produced 6 messages in total (the system message plus the 5 passed in).
This is useful for letting a list of messages be slotted into a particular spot.

View File

@@ -29,22 +29,6 @@ model_with_structure = model.with_structured_output(schema)
structured_output = model_with_structure.invoke(user_input)
```
:::warning[Tool Order Matters]
When combining structured output with additional tools, bind tools **first**, then apply structured output:
```python
# Correct
model_with_tools = model.bind_tools([tool1, tool2])
structured_model = model_with_tools.with_structured_output(schema)
# Incorrect - will cause tool resolution errors
structured_model = model.with_structured_output(schema)
broken_model = structured_model.bind_tools([tool1, tool2])
```
:::
## Schema definition
The central concept is that the output structure of model responses needs to be represented in some way.

View File

@@ -31,7 +31,7 @@ The key attributes that correspond to the tool's **schema**:
The key methods to execute the function associated with the **tool**:
- **invoke**: Invokes the tool with the given arguments.
- **ainvoke**: Invokes the tool with the given arguments, asynchronously. Used for [async programming with LangChain](/docs/concepts/async).
- **ainvoke**: Invokes the tool with the given arguments, asynchronously. Used for [async programming with Langchain](/docs/concepts/async).
## Create tools using the `@tool` decorator
@@ -171,26 +171,6 @@ Please see the [InjectedState](https://langchain-ai.github.io/langgraph/referenc
Please see the [InjectedStore](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.tool_node.InjectedStore) documentation for more details.
## Tool Artifacts vs. Injected State
Although similar conceptually, tool artifacts in LangChain and [injected state in LangGraph](https://langchain-ai.github.io/langgraph/reference/agents/#langgraph.prebuilt.tool_node.InjectedState) serve different purposes and operate at different levels of abstraction.
**Tool Artifacts**
- **Purpose:** Store and pass data between tool executions within a single chain/workflow
- **Scope:** Limited to tool-to-tool communication
- **Lifecycle:** Tied to individual tool calls and their immediate context
- **Usage:** Temporary storage for intermediate results that tools need to share
**Injected State (LangGraph)**
- **Purpose:** Maintain persistent state across the entire graph execution
- **Scope:** Global to the entire graph workflow
- **Lifecycle:** Persists throughout the entire graph execution and can be saved/restored
- **Usage:** Long-term state management, conversation memory, user context, workflow checkpointing
Tool artifacts are ephemeral data passed between tools, while injected state is persistent workflow-level state that survives across multiple steps, tool calls, and even execution sessions in LangGraph.
## Best practices
When designing tools to be used by models, keep the following in mind:

View File

@@ -223,49 +223,6 @@ If codespell is incorrectly flagging a word, you can skip spellcheck for that wo
ignore-words-list = 'momento,collison,ned,foor,reworkd,parth,whats,aapply,mysogyny,unsecure'
```
### Pre-commit
We use [pre-commit](https://pre-commit.com/) to ensure commits are formatted/linted.
#### Installing Pre-commit
First, install pre-commit:
```bash
# Option 1: Using uv (recommended)
uv tool install pre-commit
# Option 2: Using Homebrew (globally for macOS/Linux)
brew install pre-commit
# Option 3: Using pip
pip install pre-commit
```
Then install the git hook scripts:
```bash
pre-commit install
```
#### How Pre-commit Works
Once installed, pre-commit will automatically run on every `git commit`. Hooks are specified in `.pre-commit-config.yaml` and will:
- Format code using `ruff` for the specific library/package you're modifying
- Only run on files that have changed
- Prevent commits if formatting fails
#### Skipping Pre-commit
In exceptional cases, you can skip pre-commit hooks with:
```bash
git commit --no-verify
```
However, this is discouraged as the CI system will still enforce the same formatting rules.
## Working with optional dependencies
`langchain`, `langchain-community`, and `langchain-experimental` rely on optional dependencies to keep these packages lightweight.

View File

@@ -159,7 +159,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 8,
"id": "321e3036-abd2-4e1f-bcc6-606efd036954",
"metadata": {
"execution": {
@@ -183,7 +183,7 @@
],
"source": [
"configurable_model.invoke(\n",
" \"what's your name\", config={\"configurable\": {\"model\": \"claude-3-5-sonnet-latest\"}}\n",
" \"what's your name\", config={\"configurable\": {\"model\": \"claude-3-5-sonnet-20240620\"}}\n",
")"
]
},
@@ -234,7 +234,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 7,
"id": "6c8755ba-c001-4f5a-a497-be3f1db83244",
"metadata": {
"execution": {
@@ -261,7 +261,7 @@
" \"what's your name\",\n",
" config={\n",
" \"configurable\": {\n",
" \"first_model\": \"claude-3-5-sonnet-latest\",\n",
" \"first_model\": \"claude-3-5-sonnet-20240620\",\n",
" \"first_temperature\": 0.5,\n",
" \"first_max_tokens\": 100,\n",
" }\n",
@@ -336,7 +336,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 9,
"id": "e57dfe9f-cd24-4e37-9ce9-ccf8daf78f89",
"metadata": {
"execution": {
@@ -368,14 +368,14 @@
"source": [
"llm_with_tools.invoke(\n",
" \"what's bigger in 2024 LA or NYC\",\n",
" config={\"configurable\": {\"model\": \"claude-3-5-sonnet-latest\"}},\n",
" config={\"configurable\": {\"model\": \"claude-3-5-sonnet-20240620\"}},\n",
").tool_calls"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "langchain-monorepo",
"display_name": "langchain",
"language": "python",
"name": "python3"
},
@@ -389,7 +389,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
"version": "3.10.16"
}
},
"nbformat": 4,

View File

@@ -741,13 +741,13 @@
"\n",
"If you're using tools with agents, you will likely need an error handling strategy, so the agent can recover from the error and continue execution.\n",
"\n",
"A simple strategy is to throw a `ToolException` from inside the tool and specify an error handler using `handle_tool_errors`. \n",
"A simple strategy is to throw a `ToolException` from inside the tool and specify an error handler using `handle_tool_error`. \n",
"\n",
"When the error handler is specified, the exception will be caught and the error handler will decide which output to return from the tool.\n",
"\n",
"You can set `handle_tool_errors` to `True`, a string value, or a function. If it's a function, the function should take a `ToolException` as a parameter and return a value.\n",
"You can set `handle_tool_error` to `True`, a string value, or a function. If it's a function, the function should take a `ToolException` as a parameter and return a value.\n",
"\n",
"Please note that only raising a `ToolException` won't be effective. You need to first set the `handle_tool_errors` of the tool because its default value is `False`."
"Please note that only raising a `ToolException` won't be effective. You need to first set the `handle_tool_error` of the tool because its default value is `False`."
]
},
{
@@ -777,7 +777,7 @@
"id": "9d93b217-1d44-4d31-8956-db9ea680ff4f",
"metadata": {},
"source": [
"Here's an example with the default `handle_tool_errors=True` behavior."
"Here's an example with the default `handle_tool_error=True` behavior."
]
},
{
@@ -807,7 +807,7 @@
"source": [
"get_weather_tool = StructuredTool.from_function(\n",
" func=get_weather,\n",
" handle_tool_errors=True,\n",
" handle_tool_error=True,\n",
")\n",
"\n",
"get_weather_tool.invoke({\"city\": \"foobar\"})"
@@ -818,7 +818,7 @@
"id": "f91d6dc0-3271-4adc-a155-21f2e62ffa56",
"metadata": {},
"source": [
"We can set `handle_tool_errors` to a string that will always be returned."
"We can set `handle_tool_error` to a string that will always be returned."
]
},
{
@@ -848,7 +848,7 @@
"source": [
"get_weather_tool = StructuredTool.from_function(\n",
" func=get_weather,\n",
" handle_tool_errors=\"There is no such city, but it's probably above 0K there!\",\n",
" handle_tool_error=\"There is no such city, but it's probably above 0K there!\",\n",
")\n",
"\n",
"get_weather_tool.invoke({\"city\": \"foobar\"})"
@@ -893,7 +893,7 @@
"\n",
"get_weather_tool = StructuredTool.from_function(\n",
" func=get_weather,\n",
" handle_tool_errors=_handle_error,\n",
" handle_tool_error=_handle_error,\n",
")\n",
"\n",
"get_weather_tool.invoke({\"city\": \"foobar\"})"

View File

@@ -122,13 +122,13 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"from langchain_experimental.graph_transformers import LLMGraphTransformer\n",
"# from langchain_experimental.graph_transformers import LLMGraphTransformer\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(temperature=0, model_name=\"gpt-4-turbo\")\n",

View File

@@ -34,7 +34,7 @@ These are the core building blocks you can use when building applications.
[Chat Models](/docs/concepts/chat_models) are newer forms of language models that take messages in and output a message.
See [supported integrations](/docs/integrations/chat/) for details on getting started with chat models from a specific provider.
- [How to: initialize any model in one line](/docs/how_to/chat_models_universal_init/)
- [How to: init any model in one line](/docs/how_to/chat_models_universal_init/)
- [How to: work with local models](/docs/how_to/local_llms)
- [How to: do function/tool calling](/docs/how_to/tool_calling)
- [How to: get models to return structured output](/docs/how_to/structured_output)
@@ -47,7 +47,7 @@ See [supported integrations](/docs/integrations/chat/) for details on getting st
- [How to: use chat model to call tools](/docs/how_to/tool_calling)
- [How to: stream tool calls](/docs/how_to/tool_streaming)
- [How to: handle rate limits](/docs/how_to/chat_model_rate_limiting)
- [How to: few-shot prompt tool behavior](/docs/how_to/tools_few_shot)
- [How to: few shot prompt tool behavior](/docs/how_to/tools_few_shot)
- [How to: bind model-specific formatted tools](/docs/how_to/tools_model_specific)
- [How to: force a specific tool call](/docs/how_to/tool_choice)
- [How to: pass multimodal data directly to models](/docs/how_to/multimodal_inputs/)
@@ -64,8 +64,8 @@ See [supported integrations](/docs/integrations/chat/) for details on getting st
[Prompt Templates](/docs/concepts/prompt_templates) are responsible for formatting user input into a format that can be passed to a language model.
- [How to: use few-shot examples](/docs/how_to/few_shot_examples)
- [How to: use few-shot examples in chat models](/docs/how_to/few_shot_examples_chat/)
- [How to: use few shot examples](/docs/how_to/few_shot_examples)
- [How to: use few shot examples in chat models](/docs/how_to/few_shot_examples_chat/)
- [How to: partially format prompt templates](/docs/how_to/prompts_partial)
- [How to: compose prompts together](/docs/how_to/prompts_composition)
- [How to: use multimodal prompts](/docs/how_to/multimodal_prompts/)
@@ -168,7 +168,7 @@ See [supported integrations](/docs/integrations/vectorstores/) for details on ge
Indexing is the process of keeping your vectorstore in-sync with the underlying data source.
- [How to: reindex data to keep your vectorstore in sync with the underlying data source](/docs/how_to/indexing)
- [How to: reindex data to keep your vectorstore in-sync with the underlying data source](/docs/how_to/indexing)
### Tools
@@ -178,7 +178,7 @@ LangChain [Tools](/docs/concepts/tools) contain a description of the tool (to pa
- [How to: use built-in tools and toolkits](/docs/how_to/tools_builtin)
- [How to: use chat models to call tools](/docs/how_to/tool_calling)
- [How to: pass tool outputs to chat models](/docs/how_to/tool_results_pass_to_model)
- [How to: pass runtime values to tools](/docs/how_to/tool_runtime)
- [How to: pass run time values to tools](/docs/how_to/tool_runtime)
- [How to: add a human-in-the-loop for tools](/docs/how_to/tools_human)
- [How to: handle tool errors](/docs/how_to/tools_error)
- [How to: force models to call a tool](/docs/how_to/tool_choice)
@@ -297,7 +297,7 @@ For a high-level tutorial, check out [this guide](/docs/tutorials/sql_qa/).
You can use an LLM to do question answering over graph databases.
For a high-level tutorial, check out [this guide](/docs/tutorials/graph/).
- [How to: add a semantic layer over a database](/docs/how_to/graph_semantic)
- [How to: add a semantic layer over the database](/docs/how_to/graph_semantic)
- [How to: construct knowledge graphs](/docs/how_to/graph_constructing)
### Summarization
@@ -345,7 +345,7 @@ LangGraph is an extension of LangChain aimed at
building robust and stateful multi-actor applications with LLMs by modeling steps as edges and nodes in a graph.
LangGraph documentation is currently hosted on a separate site.
You can find the [LangGraph guides here](https://langchain-ai.github.io/langgraph/guides/).
You can peruse [LangGraph how-to guides here](https://langchain-ai.github.io/langgraph/how-tos/).
## [LangSmith](https://docs.smith.langchain.com/)

View File

@@ -46,7 +46,7 @@
"\n",
"1. [`llama.cpp`](https://github.com/ggerganov/llama.cpp): C++ implementation of llama inference code with [weight optimization / quantization](https://finbarr.ca/how-is-llama-cpp-possible/)\n",
"2. [`gpt4all`](https://docs.gpt4all.io/index.html): Optimized C backend for inference\n",
"3. [`ollama`](https://github.com/ollama/ollama): Bundles model weights and environment into an app that runs on device and serves the LLM\n",
"3. [`Ollama`](https://ollama.ai/): Bundles model weights and environment into an app that runs on device and serves the LLM\n",
"4. [`llamafile`](https://github.com/Mozilla-Ocho/llamafile): Bundles model weights and everything needed to run the model in a single file, allowing you to run the LLM locally from this file without any additional installation steps\n",
"\n",
"In general, these frameworks will do a few things:\n",
@@ -74,12 +74,12 @@
"\n",
"## Quickstart\n",
"\n",
"[Ollama](https://ollama.com/) is one way to easily run inference on macOS.\n",
"[`Ollama`](https://ollama.ai/) is one way to easily run inference on macOS.\n",
" \n",
"The instructions [here](https://github.com/ollama/ollama?tab=readme-ov-file#ollama) provide details, which we summarize:\n",
"The instructions [here](https://github.com/jmorganca/ollama?tab=readme-ov-file#ollama) provide details, which we summarize:\n",
" \n",
"* [Download and run](https://ollama.ai/download) the app\n",
"* From command line, fetch a model from this [list of options](https://ollama.com/search): e.g., `ollama pull gpt-oss:20b`\n",
"* From command line, fetch a model from this [list of options](https://github.com/jmorganca/ollama): e.g., `ollama pull llama3.1:8b`\n",
"* When the app is running, all models are automatically served on `localhost:11434`\n"
]
},
@@ -95,7 +95,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "86178adb",
"metadata": {},
"outputs": [
@@ -111,11 +111,11 @@
}
],
"source": [
"from langchain_ollama import ChatOllama\n",
"from langchain_ollama import OllamaLLM\n",
"\n",
"llm = ChatOllama(model=\"gpt-oss:20b\", validate_model_on_init=True)\n",
"llm = OllamaLLM(model=\"llama3.1:8b\")\n",
"\n",
"llm.invoke(\"The first man on the moon was ...\").content"
"llm.invoke(\"The first man on the moon was ...\")"
]
},
{
@@ -200,7 +200,7 @@
"\n",
"### Running Apple silicon GPU\n",
"\n",
"`ollama` and [`llamafile`](https://github.com/Mozilla-Ocho/llamafile?tab=readme-ov-file#gpu-support) will automatically utilize the GPU on Apple devices.\n",
"`Ollama` and [`llamafile`](https://github.com/Mozilla-Ocho/llamafile?tab=readme-ov-file#gpu-support) will automatically utilize the GPU on Apple devices.\n",
" \n",
"Other frameworks require the user to set up the environment to utilize the Apple GPU.\n",
"\n",
@@ -212,15 +212,15 @@
"\n",
"In particular, ensure that conda is using the correct virtual environment that you created (`miniforge3`).\n",
"\n",
"e.g., for me:\n",
"E.g., for me:\n",
"\n",
"```shell\n",
"```\n",
"conda activate /Users/rlm/miniforge3/envs/llama\n",
"```\n",
"\n",
"With the above confirmed, then:\n",
"\n",
"```shell\n",
"```\n",
"CMAKE_ARGS=\"-DLLAMA_METAL=on\" FORCE_CMAKE=1 pip install -U llama-cpp-python --no-cache-dir\n",
"```"
]
@@ -236,16 +236,20 @@
"\n",
"1. [`HuggingFace`](https://huggingface.co/TheBloke) - Many quantized model are available for download and can be run with framework such as [`llama.cpp`](https://github.com/ggerganov/llama.cpp). You can also download models in [`llamafile` format](https://huggingface.co/models?other=llamafile) from HuggingFace.\n",
"2. [`gpt4all`](https://gpt4all.io/index.html) - The model explorer offers a leaderboard of metrics and associated quantized models available for download \n",
"3. [`ollama`](https://github.com/jmorganca/ollama) - Several models can be accessed directly via `pull`\n",
"3. [`Ollama`](https://github.com/jmorganca/ollama) - Several models can be accessed directly via `pull`\n",
"\n",
"### Ollama\n",
"\n",
"With [Ollama](https://github.com/ollama/ollama), fetch a model via `ollama pull <model family>:<tag>`."
"With [Ollama](https://github.com/jmorganca/ollama), fetch a model via `ollama pull <model family>:<tag>`:\n",
"\n",
"* E.g., for Llama 2 7b: `ollama pull llama2` will download the most basic version of the model (e.g., smallest # parameters and 4 bit quantization)\n",
"* We can also specify a particular version from the [model list](https://github.com/jmorganca/ollama?tab=readme-ov-file#model-library), e.g., `ollama pull llama2:13b`\n",
"* See the full set of parameters on the [API reference page](https://python.langchain.com/api_reference/community/llms/langchain_community.llms.ollama.Ollama.html)"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 42,
"id": "8ecd2f78",
"metadata": {},
"outputs": [
@@ -261,7 +265,7 @@
}
],
"source": [
"llm = ChatOllama(model=\"gpt-oss:20b\")\n",
"llm = OllamaLLM(model=\"llama2:13b\")\n",
"llm.invoke(\"The first man on the moon was ... think step by step\")"
]
},
@@ -690,7 +694,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "langchain",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -704,7 +708,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
"version": "3.10.5"
}
},
"nbformat": 4,

View File

@@ -15,7 +15,7 @@
"id": "f2195672-0cab-4967-ba8a-c6544635547d",
"metadata": {},
"source": [
"# How to deal with high-cardinality categoricals when doing query analysis\n",
"# How deal with high cardinality categoricals when doing query analysis\n",
"\n",
"You may want to do query analysis to create a filter on a categorical column. One of the difficulties here is that you usually need to specify the EXACT categorical value. The issue is you need to make sure the LLM generates that categorical value exactly. This can be done relatively easy with prompting when there are only a few values that are valid. When there are a high number of valid values then it becomes more difficult, as those values may not fit in the LLM context, or (if they do) there may be too many for the LLM to properly attend to.\n",
"\n",

View File

@@ -74,12 +74,12 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"id": "a88ff70c",
"metadata": {},
"outputs": [],
"source": [
"from langchain_experimental.text_splitter import SemanticChunker\n",
"# from langchain_experimental.text_splitter import SemanticChunker\n",
"from langchain_openai.embeddings import OpenAIEmbeddings\n",
"\n",
"text_splitter = SemanticChunker(OpenAIEmbeddings())"

View File

@@ -612,56 +612,11 @@
},
{
"cell_type": "code",
"execution_count": 18,
"execution_count": null,
"id": "35ea904e-795f-411b-bef8-6484dbb6e35c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Invoking: `python_repl_ast` with `{'query': \"df[['Age', 'Fare']].corr().iloc[0,1]\"}`\n",
"\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3m0.11232863699941621\u001b[0m\u001b[32;1m\u001b[1;3m\n",
"Invoking: `python_repl_ast` with `{'query': \"df[['Fare', 'Survived']].corr().iloc[0,1]\"}`\n",
"\n",
"\n",
"\u001b[0m\u001b[36;1m\u001b[1;3m0.2561785496289603\u001b[0m\u001b[32;1m\u001b[1;3mThe correlation between Age and Fare is approximately 0.112, and the correlation between Fare and Survival is approximately 0.256.\n",
"\n",
"Therefore, the correlation between Fare and Survival (0.256) is greater than the correlation between Age and Fare (0.112).\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'input': \"What's the correlation between age and fare? is that greater than the correlation between fare and survival?\",\n",
" 'output': 'The correlation between Age and Fare is approximately 0.112, and the correlation between Fare and Survival is approximately 0.256.\\n\\nTherefore, the correlation between Fare and Survival (0.256) is greater than the correlation between Age and Fare (0.112).'}"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_experimental.agents import create_pandas_dataframe_agent\n",
"\n",
"agent = create_pandas_dataframe_agent(\n",
" llm, df, agent_type=\"openai-tools\", verbose=True, allow_dangerous_code=True\n",
")\n",
"agent.invoke(\n",
" {\n",
" \"input\": \"What's the correlation between age and fare? is that greater than the correlation between fare and survival?\"\n",
" }\n",
")"
]
"outputs": [],
"source": "from langchain_experimental.agents import create_pandas_dataframe_agent\n\nagent = create_pandas_dataframe_agent(\n llm, df, agent_type=\"openai-tools\", verbose=True, allow_dangerous_code=True\n)\nagent.invoke(\n {\n \"input\": \"What's the correlation between age and fare? is that greater than the correlation between fare and survival?\"\n }\n)"
},
{
"cell_type": "markdown",
@@ -786,4 +741,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
}
}

View File

@@ -614,7 +614,6 @@
" HumanMessage(\"Now about caterpillars\", name=\"example_user\"),\n",
" AIMessage(\n",
" \"\",\n",
" name=\"example_assistant\",\n",
" tool_calls=[\n",
" {\n",
" \"name\": \"joke\",\n",
@@ -910,7 +909,7 @@
" ),\n",
" (\"human\", \"{query}\"),\n",
" ]\n",
").partial(schema=People.model_json_schema())\n",
").partial(schema=People.schema())\n",
"\n",
"\n",
"# Custom parser\n",
@@ -998,91 +997,6 @@
"\n",
"chain.invoke({\"query\": query})"
]
},
{
"cell_type": "markdown",
"id": "xfejabhtn2",
"metadata": {},
"source": [
"## Combining with Additional Tools\n",
"\n",
"When you need to use both structured output and additional tools (like web search), note the order of operations:\n",
"\n",
"**Correct Order**:\n",
"```python\n",
"# 1. Bind tools first\n",
"llm_with_tools = llm.bind_tools([web_search_tool, calculator_tool])\n",
"\n",
"# 2. Apply structured output\n",
"structured_llm = llm_with_tools.with_structured_output(MySchema)\n",
"```\n",
"\n",
"**Incorrect Order**:\n",
"\n",
"```python\n",
"# This will fail with \"Tool 'MySchema' not found\" error\n",
"structured_llm = llm.with_structured_output(MySchema)\n",
"broken_llm = structured_llm.bind_tools([web_search_tool])\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "653798ca",
"metadata": {},
"source": [
"**Why Order Matters:**\n",
"`with_structured_output()` internally uses tool calling to enforce the schema. When you bind additional tools afterward, it creates a conflict in the tool resolution system."
]
},
{
"cell_type": "markdown",
"id": "1345f4a4",
"metadata": {},
"source": [
"**Complete Example:**"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0835637b",
"metadata": {},
"outputs": [],
"source": [
"from pydantic import BaseModel, Field\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"\n",
"class SearchResult(BaseModel):\n",
" \"\"\"Structured search result.\"\"\"\n",
"\n",
" query: str = Field(description=\"The search query\")\n",
" findings: str = Field(description=\"Summary of findings\")\n",
"\n",
"\n",
"# Define tools\n",
"search_tool = {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"web_search\",\n",
" \"description\": \"Search the web for information\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\"query\": {\"type\": \"string\", \"description\": \"Search query\"}},\n",
" \"required\": [\"query\"],\n",
" },\n",
" },\n",
"}\n",
"\n",
"# Correct approach\n",
"llm = ChatOpenAI()\n",
"llm_with_search = llm.bind_tools([search_tool])\n",
"structured_search_llm = llm_with_search.with_structured_output(SearchResult)\n",
"\n",
"# Now you can use both search and get structured output\n",
"result = structured_search_llm.invoke(\"Search for latest AI research and summarize\")"
]
}
],
"metadata": {

View File

@@ -147,7 +147,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 5,
"id": "74de0286-b003-4b48-9cdd-ecab435515ca",
"metadata": {},
"outputs": [],
@@ -157,7 +157,7 @@
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"\n",
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\", temperature=0)"
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\", temperature=0)"
]
},
{

View File

@@ -38,7 +38,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
@@ -53,7 +53,7 @@
"if \"ANTHROPIC_API_KEY\" not in os.environ:\n",
" os.environ[\"ANTHROPIC_API_KEY\"] = getpass()\n",
"\n",
"model = ChatAnthropic(model=\"claude-3-5-sonnet-latest\", temperature=0)"
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\", temperature=0)"
]
},
{

View File

@@ -53,7 +53,7 @@
"\n",
"To keep the most recent messages, we set `strategy=\"last\"`. We'll also set `include_system=True` to include the `SystemMessage`, and `start_on=\"human\"` to make sure the resulting chat history is valid. \n",
"\n",
"This is a good default configuration when using `trim_messages` based on token count. Remember to adjust `token_counter` and `max_tokens` for your use case. Keep in mind that new queries added to the chat history will be included in the token count unless you trim prior to adding the new query.\n",
"This is a good default configuration when using `trim_messages` based on token count. Remember to adjust `token_counter` and `max_tokens` for your use case.\n",
"\n",
"Notice that for our `token_counter` we can pass in a function (more on that below) or a language model (since language models have a message token counting method). It makes sense to pass in a model when you're trimming your messages to fit into the context window of that specific model:"
]
@@ -525,7 +525,7 @@
"id": "4d91d390-e7f7-467b-ad87-d100411d7a21",
"metadata": {},
"source": [
"Looking at [the LangSmith trace](https://smith.langchain.com/public/65af12c4-c24d-4824-90f0-6547566e59bb/r) we can see that before the messages are passed to the model they are first trimmed.\n",
"Looking at the LangSmith trace we can see that before the messages are passed to the model they are first trimmed: https://smith.langchain.com/public/65af12c4-c24d-4824-90f0-6547566e59bb/r\n",
"\n",
"Looking at just the trimmer, we can see that it's a Runnable object that can be invoked like all Runnables:"
]
@@ -620,7 +620,7 @@
"id": "556b7b4c-43cb-41de-94fc-1a41f4ec4d2e",
"metadata": {},
"source": [
"Looking at [the LangSmith trace](https://smith.langchain.com/public/17dd700b-9994-44ca-930c-116e00997315/r) we can see that we retrieve all of our messages but before the messages are passed to the model they are trimmed to be just the system message and last human message."
"Looking at the LangSmith trace we can see that we retrieve all of our messages but before the messages are passed to the model they are trimmed to be just the system message and last human message: https://smith.langchain.com/public/17dd700b-9994-44ca-930c-116e00997315/r"
]
},
{
@@ -630,7 +630,7 @@
"source": [
"## API reference\n",
"\n",
"For a complete description of all arguments head to the [API reference](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html)."
"For a complete description of all arguments head to the API reference: https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html"
]
}
],

View File

@@ -124,7 +124,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"id": "cb09c344-1836-4e0c-acf8-11d13ac1dbae",
"metadata": {},
"outputs": [],
@@ -132,7 +132,7 @@
"from langchain_anthropic import ChatAnthropic\n",
"\n",
"llm = ChatAnthropic(\n",
" model=\"claude-3-5-sonnet-latest\",\n",
" model=\"claude-3-5-sonnet-20240620\",\n",
" temperature=0,\n",
" max_tokens=1024,\n",
" timeout=None,\n",
@@ -1240,58 +1240,6 @@
"response = llm_with_tools.invoke(\"How do I update a web app to TypeScript 5.5?\")"
]
},
{
"cell_type": "markdown",
"id": "kloc4rvd1w",
"metadata": {},
"source": [
"#### Web search + structured output\n",
"\n",
"When combining web search tools with structured output, it's important to **bind the tools first and then apply structured output**:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "rjjergy6ef",
"metadata": {},
"outputs": [],
"source": [
"from pydantic import BaseModel, Field\n",
"from langchain_anthropic import ChatAnthropic\n",
"\n",
"\n",
"# Define structured output schema\n",
"class ResearchResult(BaseModel):\n",
" \"\"\"Structured research result from web search.\"\"\"\n",
"\n",
" topic: str = Field(description=\"The research topic\")\n",
" summary: str = Field(description=\"Summary of key findings\")\n",
" key_points: list[str] = Field(description=\"List of important points discovered\")\n",
"\n",
"\n",
"# Configure web search tool\n",
"websearch_tools = [\n",
" {\n",
" \"type\": \"web_search_20250305\",\n",
" \"name\": \"web_search\",\n",
" \"max_uses\": 10,\n",
" }\n",
"]\n",
"\n",
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-20241022\")\n",
"\n",
"# Correct order: bind tools first, then structured output\n",
"llm_with_search = llm.bind_tools(websearch_tools)\n",
"research_llm = llm_with_search.with_structured_output(ResearchResult)\n",
"\n",
"# Now you can use both web search and get structured output\n",
"result = research_llm.invoke(\"Research the latest developments in quantum computing\")\n",
"print(f\"Topic: {result.topic}\")\n",
"print(f\"Summary: {result.summary}\")\n",
"print(f\"Key Points: {result.key_points}\")"
]
},
{
"cell_type": "markdown",
"id": "1478cdc6-2e52-4870-80f9-b4ddf88f2db2",

View File

@@ -129,7 +129,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"id": "cb09c344-1836-4e0c-acf8-11d13ac1dbae",
"metadata": {},
"outputs": [],
@@ -137,7 +137,7 @@
"from langchain_aws import ChatBedrockConverse\n",
"\n",
"llm = ChatBedrockConverse(\n",
" model_id=\"anthropic.claude-3-5-sonnet-latest-v1:0\",\n",
" model_id=\"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n",
" # region_name=...,\n",
" # aws_access_key_id=...,\n",
" # aws_secret_access_key=...,\n",

View File

@@ -17,9 +17,9 @@
"source": [
"# ChatOllama\n",
"\n",
"[Ollama](https://ollama.com/) allows you to run open-source large language models, such as `gpt-oss`, locally.\n",
"[Ollama](https://ollama.ai/) allows you to run open-source large language models, such as Llama 2, locally.\n",
"\n",
"`ollama` bundles model weights, configuration, and data into a single package, defined by a Modelfile.\n",
"Ollama bundles model weights, configuration, and data into a single package, defined by a Modelfile.\n",
"\n",
"It optimizes setup and configuration details, including GPU usage.\n",
"\n",
@@ -28,14 +28,14 @@
"## Overview\n",
"### Integration details\n",
"\n",
"| Class | Package | Local | Serializable | [JS support](https://js.langchain.com/docs/integrations/chat/ollama) | Package downloads | Package latest |\n",
"| Class | Package | Local | Serializable | [JS support](https://js.langchain.com/v0.2/docs/integrations/chat/ollama) | Package downloads | Package latest |\n",
"| :--- | :--- | :---: | :---: | :---: | :---: | :---: |\n",
"| [ChatOllama](https://python.langchain.com/api_reference/ollama/chat_models/langchain_ollama.chat_models.ChatOllama.html#chatollama) | [langchain-ollama](https://python.langchain.com/api_reference/ollama/index.html) | ✅ | ❌ | ✅ | ![PyPI - Downloads](https://img.shields.io/pypi/dm/langchain-ollama?style=flat-square&label=%20) | ![PyPI - Version](https://img.shields.io/pypi/v/langchain-ollama?style=flat-square&label=%20) |\n",
"| [ChatOllama](https://python.langchain.com/v0.2/api_reference/ollama/chat_models/langchain_ollama.chat_models.ChatOllama.html) | [langchain-ollama](https://python.langchain.com/v0.2/api_reference/ollama/index.html) | ✅ | ❌ | ✅ | ![PyPI - Downloads](https://img.shields.io/pypi/dm/langchain-ollama?style=flat-square&label=%20) | ![PyPI - Version](https://img.shields.io/pypi/v/langchain-ollama?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",
"\n",
"## Setup\n",
"\n",
@@ -45,17 +45,17 @@
" * macOS users can install via Homebrew with `brew install ollama` and start with `brew services start ollama`\n",
"* Fetch available LLM model via `ollama pull <name-of-model>`\n",
" * View a list of available models via the [model library](https://ollama.ai/library)\n",
" * e.g., `ollama pull gpt-oss:20b`\n",
" * e.g., `ollama pull llama3`\n",
"* This will download the default tagged version of the model. Typically, the default points to the latest, smallest sized-parameter model.\n",
"\n",
"> On Mac, the models will be download to `~/.ollama/models`\n",
">\n",
"> On Linux (or WSL), the models will be stored at `/usr/share/ollama/.ollama/models`\n",
"\n",
"* Specify the exact version of the model of interest as such `ollama pull gpt-oss:20b` (View the [various tags for the `Vicuna`](https://ollama.ai/library/vicuna/tags) model in this instance)\n",
"* Specify the exact version of the model of interest as such `ollama pull vicuna:13b-v1.5-16k-q4_0` (View the [various tags for the `Vicuna`](https://ollama.ai/library/vicuna/tags) model in this instance)\n",
"* To view all pulled models, use `ollama list`\n",
"* To chat directly with a model from the command line, use `ollama run <name-of-model>`\n",
"* View the [Ollama documentation](https://github.com/ollama/ollama/blob/main/docs/README.md) for more commands. You can run `ollama help` in the terminal to see available commands.\n"
"* View the [Ollama documentation](https://github.com/ollama/ollama/tree/main/docs) for more commands. You can run `ollama help` in the terminal to see available commands.\n"
]
},
{
@@ -102,11 +102,7 @@
"id": "b18bd692076f7cf7",
"metadata": {},
"source": [
":::warning\n",
"Make sure you're using the latest Ollama version!\n",
":::\n",
"\n",
"Update by running:"
"Make sure you're using the latest Ollama version for structured outputs. Update by running:"
]
},
{
@@ -261,10 +257,10 @@
"source": [
"## Tool calling\n",
"\n",
"We can use [tool calling](/docs/concepts/tool_calling/) with an LLM [that has been fine-tuned for tool use](https://ollama.com/search?&c=tools) such as `gpt-oss`:\n",
"We can use [tool calling](/docs/concepts/tool_calling/) with an LLM [that has been fine-tuned for tool use](https://ollama.com/search?&c=tools) such as `llama3.1`:\n",
"\n",
"```\n",
"ollama pull gpt-oss:20b\n",
"ollama pull llama3.1\n",
"```\n",
"\n",
"Details on creating custom tools are available in [this guide](/docs/how_to/custom_tools/). Below, we demonstrate how to create a tool using the `@tool` decorator on a normal python function."
@@ -272,7 +268,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 13,
"id": "f767015f",
"metadata": {},
"outputs": [
@@ -304,8 +300,7 @@
"\n",
"\n",
"llm = ChatOllama(\n",
" model=\"gpt-oss:20b\",\n",
" validate_model_on_init=True,\n",
" model=\"llama3.1\",\n",
" temperature=0,\n",
").bind_tools([validate_user])\n",
"\n",
@@ -326,7 +321,9 @@
"source": [
"## Multi-modal\n",
"\n",
"Ollama has limited support for multi-modal LLMs, such as [gemma3](https://ollama.com/library/gemma3)\n",
"Ollama has support for multi-modal LLMs, such as [bakllava](https://ollama.com/library/bakllava) and [llava](https://ollama.com/library/llava).\n",
"\n",
" ollama pull bakllava\n",
"\n",
"Be sure to update Ollama so that you have the most recent version to support multi-modal."
]
@@ -521,7 +518,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "langchain",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -535,7 +532,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
"version": "3.10.4"
}
},
"nbformat": 4,

View File

@@ -1,334 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Oxylabs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[Oxylabs](https://oxylabs.io/) is a web intelligence collection platform that enables companies worldwide to unlock data-driven insights.\n",
"\n",
"## Overview\n",
"\n",
"Oxylabs document loader allows to load data from search engines, e-commerce sites, travel platforms, and any other website. It supports geolocation, browser rendering, data parsing, multiple user agents and many more parameters. Check out [Oxylabs documentation](https://developers.oxylabs.io/scraping-solutions/web-scraper-api) for more information.\n",
"\n",
"\n",
"### Integration details\n",
"\n",
"| Class | Package | Local | Serializable | Pricing |\n",
"|:--------------|:------------------------------------------------------------------|:-----:|:------------:|:-----------------------------:|\n",
"| OxylabsLoader | [langchain-oxylabs](https://github.com/oxylabs/langchain-oxylabs) | ✅ | ❌ | Free 5,000 results for 1 week |\n",
"\n",
"### Loader features\n",
"| Document Lazy Loading |\n",
"|:---------------------:|\n",
"| ✅ |\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Install the required dependencies.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"%pip install -U langchain-oxylabs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Credentials\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Set up the proper API keys and environment variables.\n",
"Create your API user credentials: Sign up for a free trial or purchase the product\n",
"in the [Oxylabs dashboard](https://dashboard.oxylabs.io/en/registration)\n",
"to create your API user credentials (OXYLABS_USERNAME and OXYLABS_PASSWORD)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"os.environ[\"OXYLABS_USERNAME\"] = getpass.getpass(\"Enter your Oxylabs username: \")\n",
"os.environ[\"OXYLABS_PASSWORD\"] = getpass.getpass(\"Enter your Oxylabs password: \")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Initialization"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"ExecuteTime": {
"end_time": "2025-08-06T10:57:51.630011Z",
"start_time": "2025-08-06T10:57:51.623814Z"
}
},
"outputs": [],
"source": [
"from langchain_oxylabs import OxylabsLoader"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"ExecuteTime": {
"end_time": "2025-08-06T10:57:53.685413Z",
"start_time": "2025-08-06T10:57:53.628859Z"
}
},
"outputs": [],
"source": [
"loader = OxylabsLoader(\n",
" urls=[\n",
" \"https://sandbox.oxylabs.io/products/1\",\n",
" \"https://sandbox.oxylabs.io/products/2\",\n",
" ],\n",
" params={\"markdown\": True},\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": "## Load"
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"ExecuteTime": {
"end_time": "2025-08-06T10:59:51.487327Z",
"start_time": "2025-08-06T10:59:48.592743Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2751\n",
"[![](data:image/svg+xml...)![logo](data:image/gif;base64...)![logo](/_next/image?url=%2F_next%2Fstatic%2Fmedia%2FnavLogo.a8764883.png&w=750&q=75)](/)\n",
"\n",
"Game platforms:\n",
"\n",
"* **All**\n",
"\n",
"* [Nintendo platform](/products/category/nintendo)\n",
"\n",
"+ wii\n",
"+ wii-u\n",
"+ nintendo-64\n",
"+ switch\n",
"+ gamecube\n",
"+ game-boy-advance\n",
"+ 3ds\n",
"+ ds\n",
"\n",
"* [Xbox platform](/products/category/xbox-platform)\n",
"\n",
"* **Dreamcast**\n",
"\n",
"* [Playstation platform](/products/category/playstation-platform)\n",
"\n",
"* **Pc**\n",
"\n",
"* **Stadia**\n",
"\n",
"Go Back\n",
"\n",
"Note!This is a sandbox website used for web scraping. Information listed in this website does not have any real meaning and should not be associated with the actual products.\n",
"\n",
"![The Legend of Zelda: Ocarina of Time](data:image/gif;base64...)![The Legend of Zelda: Ocarina of Time](/assets/action-adventure.svg)\n",
"\n",
"## The Legend of Zelda: Ocarina of Time\n",
"\n",
"**Developer:** Nintendo**Platform:****Type:** singleplayer\n",
"\n",
"As a young boy, Link is tricked by Ganondorf, the King of the Gerudo Thieves. The evil human uses Link to g\n",
"5542\n",
"[![](data:image/svg+xml...)![logo](data:image/gif;base64...)![logo](/_next/image?url=%2F_next%2Fstatic%2Fmedia%2FnavLogo.a8764883.png&w=750&q=75)](/)\n",
"\n",
"Game platforms:\n",
"\n",
"* **All**\n",
"\n",
"* [Nintendo platform](/products/category/nintendo)\n",
"\n",
"+ wii\n",
"+ wii-u\n",
"+ nintendo-64\n",
"+ switch\n",
"+ gamecube\n",
"+ game-boy-advance\n",
"+ 3ds\n",
"+ ds\n",
"\n",
"* [Xbox platform](/products/category/xbox-platform)\n",
"\n",
"* **Dreamcast**\n",
"\n",
"* [Playstation platform](/products/category/playstation-platform)\n",
"\n",
"* **Pc**\n",
"\n",
"* **Stadia**\n",
"\n",
"Go Back\n",
"\n",
"Note!This is a sandbox website used for web scraping. Information listed in this website does not have any real meaning and should not be associated with the actual products.\n",
"\n",
"![Super Mario Galaxy](data:image/gif;base64...)![Super Mario Galaxy](/assets/action.svg)\n",
"\n",
"## Super Mario Galaxy\n",
"\n",
"**Developer:** Nintendo**Platform:****Type:** singleplayer\n",
"\n",
"[Metacritic's 2007 Wii Game of the Year] The ultimate Nintendo hero is taking the ultimate step ... out into space. Join Mario as he ushers in a new era of video games, de\n"
]
}
],
"source": [
"for document in loader.load():\n",
" print(document.page_content[:1000])"
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "## Lazy Load"
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"for document in loader.lazy_load():\n",
" print(document.page_content[:1000])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Advanced examples\n",
"\n",
"The following examples show the usage of `OxylabsLoader` with geolocation, currency, pagination and user agent parameters for Amazon Search and Google Search sources."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"ExecuteTime": {
"end_time": "2025-08-06T11:04:19.901122Z",
"start_time": "2025-08-06T11:04:19.838933Z"
}
},
"outputs": [],
"source": [
"loader = OxylabsLoader(\n",
" queries=[\"gaming headset\", \"gaming chair\", \"computer mouse\"],\n",
" params={\n",
" \"source\": \"amazon_search\",\n",
" \"parse\": True,\n",
" \"geo_location\": \"DE\",\n",
" \"currency\": \"EUR\",\n",
" \"pages\": 3,\n",
" },\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"ExecuteTime": {
"end_time": "2025-08-06T11:07:17.648142Z",
"start_time": "2025-08-06T11:07:17.595629Z"
}
},
"outputs": [],
"source": [
"loader = OxylabsLoader(\n",
" queries=[\"europe gdp per capita\", \"us gdp per capita\"],\n",
" params={\n",
" \"source\": \"google_search\",\n",
" \"parse\": True,\n",
" \"geo_location\": \"Paris, France\",\n",
" \"user_agent_type\": \"mobile\",\n",
" },\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## API reference\n",
"\n",
"[More information about this package.](https://github.com/oxylabs/langchain-oxylabs)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -132,12 +132,13 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.documents import Document\n",
"from langchain_experimental.graph_transformers import LLMGraphTransformer\n",
"\n",
"# from langchain_experimental.graph_transformers import LLMGraphTransformer\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"# Define the LLMGraphTransformer\n",

View File

@@ -548,12 +548,12 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.documents import Document\n",
"from langchain_experimental.graph_transformers import LLMGraphTransformer"
"# from langchain_experimental.graph_transformers import LLMGraphTransformer"
]
},
{

View File

@@ -44,7 +44,9 @@
"tags": []
},
"outputs": [],
"source": "%pip install --upgrade --quiet llama-cpp-python"
"source": [
"%pip install --upgrade --quiet llama-cpp-python"
]
},
{
"cell_type": "markdown",
@@ -62,7 +64,9 @@
"execution_count": null,
"metadata": {},
"outputs": [],
"source": "!CMAKE_ARGS=\"-DGGML_CUDA=on\" FORCE_CMAKE=1 pip install llama-cpp-python"
"source": [
"!CMAKE_ARGS=\"-DGGML_CUDA=on\" FORCE_CMAKE=1 pip install llama-cpp-python"
]
},
{
"cell_type": "markdown",
@@ -76,7 +80,9 @@
"execution_count": null,
"metadata": {},
"outputs": [],
"source": "!CMAKE_ARGS=\"-DGGML_CUDA=on\" FORCE_CMAKE=1 pip install --upgrade --force-reinstall llama-cpp-python --no-cache-dir"
"source": [
"!CMAKE_ARGS=\"-DGGML_CUDA=on\" FORCE_CMAKE=1 pip install --upgrade --force-reinstall llama-cpp-python --no-cache-dir"
]
},
{
"cell_type": "markdown",
@@ -94,7 +100,9 @@
"execution_count": null,
"metadata": {},
"outputs": [],
"source": "!CMAKE_ARGS=\"-DLLAMA_METAL=on\" FORCE_CMAKE=1 pip install llama-cpp-python"
"source": [
"!CMAKE_ARGS=\"-DLLAMA_METAL=on\" FORCE_CMAKE=1 pip install llama-cpp-python"
]
},
{
"cell_type": "markdown",
@@ -108,7 +116,9 @@
"execution_count": null,
"metadata": {},
"outputs": [],
"source": "!CMAKE_ARGS=\"-DLLAMA_METAL=on\" FORCE_CMAKE=1 pip install llama-cpp-python --force-reinstall --no-binary :all: --no-cache-dir"
"source": [
"!CMAKE_ARGS=\"-DLLAMA_METAL=on\" FORCE_CMAKE=1 pip install --upgrade --force-reinstall llama-cpp-python --no-cache-dir"
]
},
{
"cell_type": "markdown",
@@ -164,7 +174,9 @@
"execution_count": null,
"metadata": {},
"outputs": [],
"source": "!python -m pip install -e . --force-reinstall --no-cache-dir"
"source": [
"!python -m pip install -e . --force-reinstall --no-cache-dir"
]
},
{
"cell_type": "markdown",
@@ -706,4 +718,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
}
}

View File

@@ -1,215 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# RecallioMemory + LangChain Integration Demo\n",
"A minimal notebook to show drop-in usage of RecallioMemory in LangChain (with scoped writes and recall)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install recallio langchain langchain-recallio openai"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup: API Keys & Imports"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_recallio.memory import RecallioMemory\n",
"from langchain_openai import ChatOpenAI\n",
"from langchain.prompts import ChatPromptTemplate\n",
"import os\n",
"\n",
"# Set your keys here or use environment variables\n",
"RECALLIO_API_KEY = os.getenv(\"RECALLIO_API_KEY\", \"YOUR_RECALLIO_API_KEY\")\n",
"OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\", \"YOUR_OPENAI_API_KEY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Initialize RecallioMemory"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"memory = RecallioMemory(\n",
" project_id=\"project_abc\",\n",
" api_key=RECALLIO_API_KEY,\n",
" session_id=\"demo-session-001\",\n",
" user_id=\"demo-user-42\",\n",
" default_tags=[\"test\", \"langchain\"],\n",
" return_messages=True,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Build a LangChain ConversationChain with RecallioMemory"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# You can swap in any supported LLM here\n",
"llm = ChatOpenAI(api_key=OPENAI_API_KEY, temperature=0)\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"The following is a friendly conversation between a human and an AI. \"\n",
" \"The AI is talkative and provides lots of specific details from its context. \"\n",
" \"If the AI does not know the answer to a question, it truthfully says it does not know.\",\n",
" ),\n",
" (\"placeholder\", \"{history}\"), # RecallioMemory will fill this slot\n",
" (\"human\", \"{input}\"),\n",
" ]\n",
")\n",
"\n",
"# LCEL chain that returns an AIMessage\n",
"base_chain = prompt | llm\n",
"\n",
"\n",
"# Create a stateful chain using RecallioMemory\n",
"def chat_with_memory(user_input: str):\n",
" # Load conversation history from memory\n",
" memory_vars = memory.load_memory_variables({\"input\": user_input})\n",
"\n",
" # Run the chain with history and user input\n",
" response = base_chain.invoke(\n",
" {\"input\": user_input, \"history\": memory_vars.get(\"history\", \"\")}\n",
" )\n",
"\n",
" # Save the conversation to memory\n",
" memory.save_context({\"input\": user_input}, {\"output\": response.content})\n",
"\n",
" return response"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example: Chat with Memory"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Bot: Hello Guillaume! It's nice to meet you. How can I assist you today?\n"
]
}
],
"source": [
"# First user message note the AI remembers the name\n",
"resp1 = chat_with_memory(\"Hi! My name is Guillaume. Remember that.\")\n",
"print(\"Bot:\", resp1.content)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Bot: Your name is Guillaume.\n"
]
}
],
"source": [
"# Second user message AI should recall the name from memory\n",
"resp2 = chat_with_memory(\"What is my name?\")\n",
"print(\"Bot:\", resp2.content)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## See What Is Stored in Recallio\n",
"This is for debugging/demo only; in production, you wouldn't do this on every run."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Current memory variables: {'history': [HumanMessage(content='Name is Guillaume', additional_kwargs={}, response_metadata={})]}\n"
]
}
],
"source": [
"print(\"Current memory variables:\", memory.load_memory_variables({}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Clear Memory (Optional Cleanup - Requires Manager level Key)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# memory.clear()\n",
"# print(\"Memory cleared.\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -1,38 +0,0 @@
# Anchor Browser
[Anchor](https://anchorbrowser.io?utm=langchain) is the platform for AI Agentic browser automation, which solves the challenge of automating workflows for web applications that lack APIs or have limited API coverage. It simplifies the creation, deployment, and management of browser-based automations, transforming complex web interactions into simple API endpoints.
`langchain-anchorbrowser` provides 3 main tools:
- `AnchorContentTool` - For web content extractions in Markdown or HTML format.
- `AnchorScreenshotTool` - For web page screenshots.
- `AnchorWebTaskTools` - To perform web tasks.
## Quickstart
### Installation
Install the package:
```bash
pip install langchain-anchorbrowser
```
### Usage
Import and utilize your intended tool. The full list of Anchor Browser available tools see **Tool Features** table in [Anchor Browser tool page](/docs/integrations/tools/anchor_browser)
```python
from langchain_anchorbrowser import AnchorContentTool
# Get Markdown Content for https://www.anchorbrowser.io
AnchorContentTool().invoke(
{"url": "https://www.anchorbrowser.io", "format": "markdown"}
)
```
## Additional Resources
- [PyPi](https://pypi.org/project/langchain-anchorbrowser)
- [Github](https://github.com/anchorbrowser/langchain-anchorbrowser)
- [Anchor Browser Docs](https://docs.anchorbrowser.io/introduction?utm=langchain)
- [Anchor Browser API Reference](https://docs.anchorbrowser.io/api-reference/ai-tools/perform-web-task?utm=langchain)

View File

@@ -929,41 +929,6 @@ from langchain_google_community.gmail.search import GmailSearch
from langchain_google_community.gmail.send_message import GmailSendMessage
```
### MCP Toolbox
[MCP Toolbox](https://github.com/googleapis/genai-toolbox) provides a simple and efficient way to connect to your databases, including those on Google Cloud like [Cloud SQL](https://cloud.google.com/sql/docs) and [AlloyDB](https://cloud.google.com/alloydb/docs/overview). With MCP Toolbox, you can seamlessly integrate your database with LangChain to build powerful, data-driven applications.
#### Installation
To get started, [install the Toolbox server and client](https://github.com/googleapis/genai-toolbox/releases/).
[Configure](https://googleapis.github.io/genai-toolbox/getting-started/configure/) a `tools.yaml` to define your tools, and then execute toolbox to start the server:
```bash
toolbox --tools-file "tools.yaml"
```
Then, install the Toolbox client:
```bash
pip install toolbox-langchain
```
#### Getting Started
Here is a quick example of how to use MCP Toolbox to connect to your database:
```python
from toolbox_langchain import ToolboxClient
async with ToolboxClient("http://127.0.0.1:5000") as client:
tools = client.load_toolset()
```
See [usage example and setup instructions](/docs/integrations/tools/toolbox).
### Memory
Store conversation history using Google Cloud databases.

View File

@@ -1,11 +1,18 @@
# DigitalOcean Gradient
# ChatGradient
This will help you getting started with DigitalOcean Gradient [chat models](/docs/concepts/chat_models).
## Overview
### Integration details
| Class | Package | Package downloads | Package latest |
| :--- | :--- | :---: | :---: |
| [ChatGradient](https://python.langchain.com/api_reference/langchain-gradient/chat_models/langchain_gradient.chat_models.ChatGradient.html) | [langchain-gradient](https://python.langchain.com/api_reference/langchain-gradient/) | ![PyPI - Downloads](https://img.shields.io/pypi/dm/langchain-gradient?style=flat-square&label=%20) | ![PyPI - Version](https://img.shields.io/pypi/v/langchain-gradient?style=flat-square&label=%20) |
## Setup
langchain-gradient uses DigitalOcean's Gradient™ AI Platform.
langchain-gradient uses DigitalOcean Gradient Platform.
Create an account on DigitalOcean, acquire a `DIGITALOCEAN_INFERENCE_KEY` API key from the Gradient Platform, and install the `langchain-gradient` integration package.

View File

@@ -1,14 +1,14 @@
# Ollama
>[Ollama](https://ollama.com/) allows you to run open-source large language models,
> such as [gpt-oss](https://ollama.com/library/gpt-oss), locally.
> such as [Llama3.1](https://ai.meta.com/blog/meta-llama-3-1/), locally.
>
>`Ollama` bundles model weights, configuration, and data into a single package, defined by a Modelfile.
>It optimizes setup and configuration details, including GPU usage.
>For a complete list of supported models and model variants, see the [Ollama model library](https://ollama.ai/library).
See [this guide](/docs/how_to/local_llms#ollama) for more details
on how to use `ollama` with LangChain.
See [this guide](/docs/how_to/local_llms) for more details
on how to use `Ollama` with LangChain.
## Installation and Setup
### Ollama installation
@@ -26,7 +26,7 @@ ollama serve
After starting ollama, run `ollama pull <name-of-model>` to download a model from the [Ollama model library](https://ollama.ai/library):
```bash
ollama pull gpt-oss:20b
ollama pull llama3.1
```
- This will download the default tagged version of the model. Typically, the default points to the latest, smallest sized-parameter model.

View File

@@ -1,31 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Recallio\n",
"\n",
"[Recallio](https://recallio.ai/) is a powerfull API allowing to store, index, and retrieve application “memories” with built-in fact extraction, dynamic summaries, reranked recall, and a full knowledge-graph layer.\n",
"\n",
"\n",
"## Installation\n",
"\n",
"```bash\n",
"pip install langchain-recallio\n",
"```\n",
"\n",
"```python\n",
"from langchain_recallio.memory import RecallioMemory\n",
"```"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -1,26 +0,0 @@
# Scrapeless
[Scrapeless](https://scrapeless.com) offers flexible and feature-rich data acquisition services with extensive parameter customization and multi-format export support.
## Installation and Setup
```bash
pip install langchain-scrapeless
```
You'll need to set up your Scrapeless API key:
```python
import os
os.environ["SCRAPELESS_API_KEY"] = "your-api-key"
```
## Tools
The Scrapeless integration provides several tools:
- [ScrapelessDeepSerpGoogleSearchTool](/docs/integrations/tools/scrapeless_scraping_api) - Enables comprehensive extraction of Google SERP data across all result types.
- [ScrapelessDeepSerpGoogleTrendsTool](/docs/integrations/tools/scrapeless_scraping_api) - Retrieves keyword trend data from Google, including popularity over time, regional interest, and related searches.
- [ScrapelessUniversalScrapingTool](/docs/integrations/tools/scrapeless_universal_scraping) - Access and extract data from JS-Render websites that typically block bots.
- [ScrapelessCrawlerCrawlTool](/docs/integrations/tools/scrapeless_crawl) - Crawl a website and its linked pages to extract comprehensive data.
- [ScrapelessCrawlerScrapeTool](/docs/integrations/tools/scrapeless_crawl) - Extract information from a single webpage.

View File

@@ -1,43 +0,0 @@
# langchain-siliconflow
This package contains the LangChain integration with SiliconFlow
## Installation
```bash
pip install -U langchain-siliconflow
```
And you should configure credentials by setting the following environment variables:
```bash
export SILICONFLOW_API_KEY="your-api-key"
```
You can set the following environment variable to use the `.cn` endpoint:
```bash
export SILICONFLOW_BASE_URL="https://api.siliconflow.cn/v1"
```
## Chat Models
`ChatSiliconFlow` class exposes chat models from SiliconFlow.
```python
from langchain_siliconflow import ChatSiliconFlow
llm = ChatSiliconFlow()
llm.invoke("Sing a ballad of LangChain.")
```
## Embeddings
`SiliconFlowEmbeddings` class exposes embeddings from SiliconFlow.
```python
from langchain_siliconflow import SiliconFlowEmbeddings
embeddings = SiliconFlowEmbeddings()
embeddings.embed_query("What is the meaning of life?")
```

View File

@@ -1,23 +0,0 @@
# MCP Toolbox
The [MCP Toolbox](https://googleapis.github.io/genai-toolbox/getting-started/introduction/) in LangChain allows you to equip an agent with a set of tools. When the agent receives a query, it can intelligently select and use the most appropriate tool provided by MCP Toolbox to fulfill the request.
## What is it?
MCP Toolbox is essentially a container for your tools. Think of it as a multi-tool device for your agent; it can hold any tools you create. The agent then decides which specific tool to use based on the user's input.
This is particularly useful when you have an agent that needs to perform a variety of tasks that require different capabilities.
## Installation
To get started, you'll need to install the necessary package:
```bash
pip install toolbox-langchain
```
## Tutorial
For a complete, step-by-step guide on how to create, configure, and use MCP Toolbox with your agents, please refer to our detailed Jupyter notebook tutorial.
**[➡️ View the full tutorial here](/docs/integrations/tools/toolbox)**.

View File

@@ -1,101 +0,0 @@
# TrueFoundry
TrueFoundry provides an enterprise-ready [AI Gateway](https://www.truefoundry.com/ai-gateway) to provide governance and observability to agentic frameworks like LangChain. TrueFoundry AI Gateway serves as a unified interface for LLM access, providing:
- **Unified API Access**: Connect to 250+ LLMs (OpenAI, Claude, Gemini, Groq, Mistral) through one API
- **Low Latency**: Sub-3ms internal latency with intelligent routing and load balancing
- **Enterprise Security**: SOC 2, HIPAA, GDPR compliance with RBAC and audit logging
- **Quota and cost management**: Token-based quotas, rate limiting, and comprehensive usage tracking
- **Observability**: Full request/response logging, metrics, and traces with customizable retention
## Prerequisites
Before integrating LangChain with TrueFoundry, ensure you have:
1. **TrueFoundry Account**: A [TrueFoundry account](https://www.truefoundry.com/register) with at least one model provider configured. Follow quick start guide [here](https://docs.truefoundry.com/gateway/quick-start)
2. **Personal Access Token**: Generate a token by following the [TrueFoundry token generation guide](https://docs.truefoundry.com/gateway/authentication)
## Quickstart
You can connect to TrueFoundry's unified LLM gateway through the `ChatOpenAI` interface.
- Set the `base_url` to your TrueFoundry endpoint (explained below)
- Set the `api_key` to your TrueFoundry [PAT (Personal Access Token)](https://docs.truefoundry.com/gateway/authentication#personal-access-token-pat)
- Use the same `model-name` as shown in the unified code snippet
![TrueFoundry metrics](/img/unified-code-tfy.png)
### Installation
```bash
pip install langchain-openai
```
### Basic Setup
Connect to TrueFoundry by updating the `ChatOpenAI` model in LangChain:
```python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
api_key=TRUEFOUNDRY_API_KEY,
base_url=TRUEFOUNDRY_GATEWAY_BASE_URL,
model="openai-main/gpt-4o" # Similarly you can call any model from any model provider
)
llm.invoke("What is the meaning of life, universe and everything?")
```
The request is routed through your TrueFoundry gateway to the specified model provider. TrueFoundry automatically handles rate limiting, load balancing, and observability.
### LangGraph Integration
```python
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState
from langchain_core.messages import HumanMessage
# Define your LangGraph workflow
def call_model(state: MessagesState):
model = ChatOpenAI(
api_key=TRUEFOUNDRY_API_KEY,
base_url=TRUEFOUNDRY_GATEWAY_BASE_URL,
# Copy the exact model name from gateway
model="openai-main/gpt-4o"
)
response = model.invoke(state["messages"])
return {"messages": [response]}
# Build workflow
workflow = StateGraph(MessagesState)
workflow.add_node("agent", call_model)
workflow.set_entry_point("agent")
workflow.set_finish_point("agent")
app = workflow.compile()
# Run agent through TrueFoundry
result = app.invoke({"messages": [HumanMessage(content="Hello!")]})
```
## Observability and Governance
![TrueFoundry metrics](/img/gateway-metrics.png)
With the Metrics Dashboard, you can monitor and analyze:
- **Performance Metrics**: Track key latency metrics like Request Latency, Time to First Token (TTFS), and Inter-Token Latency (ITL) with P99, P90, and P50 percentiles
- **Cost and Token Usage**: Gain visibility into your application's costs with detailed breakdowns of input/output tokens and the associated expenses for each model
- **Usage Patterns**: Understand how your application is being used with detailed analytics on user activity, model distribution, and team-based usage
- **Rate Limiting & Load Balancing**: Configure limits, distribute traffic across models, and set up fallbacks
## Support
For questions, issues, or support:
- **Email**: [support@truefoundry.com](mailto:support@truefoundry.com)
- **Documentation**: [https://docs.truefoundry.com/](https://docs.truefoundry.com/)

View File

@@ -1,307 +0,0 @@
{
"cells": [
{
"cell_type": "raw",
"id": "2ce4bdbc",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"---\n",
"sidebar_label: anchor_browser\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "a6f91f20",
"metadata": {},
"source": [
"# Anchor Browser\n",
"\n",
"Anchor is a platform for AI Agentic browser automation, which solves the challenge of automating workflows for web applications that lack APIs or have limited API coverage. It simplifies the creation, deployment, and management of browser-based automations, transforming complex web interactions into simple API endpoints.\n",
"\n",
"This notebook provides a quick overview for getting started with Anchor Browser tools. For more information of Anchor Browser visit [Anchorbrowser.io](https://anchorbrowser.io?utm=langchain) or the [Anchor Browser Docs](https://docs.anchorbrowser.io?utm=langchain)\n",
"\n",
"## Overview\n",
"\n",
"### Integration details\n",
"\n",
"Anchor Browser package for LangChain is [langchain-anchorbrowser](https://pypi.org/project/langchain-anchorbrowser), and the current latest version is ![PyPI - Version](https://img.shields.io/pypi/v/langchain-anchorbrowser?style=flat-square&label=%20).\n",
"\n",
"\n",
"### Tool features\n",
"| Tool Name | Package | Description | Parameters |\n",
"| :--- | :--- | :--- | :---|\n",
"| `AnchorContentTool` | langchain-anchorbrowser | Extract text content from web pages | `url`, `format` |\n",
"| `AnchorScreenshotTool` | langchain-anchorbrowser | Take screenshots of web pages | `url`, `width`, `height`, `image_quality`, `wait`, `scroll_all_content`, `capture_full_height`, `s3_target_address` |\n",
"| `AnchorWebTaskToolKit` | langchain-anchorbrowser | Perform intelligent web tasks using AI (Simple & Advanced modes) | see below |\n",
"\n",
"The parameters allowed in `langchain-anchorbrowser` are only a subset of those listed in the Anchor Browser API reference respectively: [Get Webpage Content](https://docs.anchorbrowser.io/sdk-reference/tools/get-webpage-content?utm=langchain), [Screenshot Webpage](https://docs.anchorbrowser.io/sdk-reference/tools/screenshot-webpage?utm=langchain), and [Perform Web Task](https://docs.anchorbrowser.io/sdk-reference/ai-tools/perform-web-task?utm=langchain).\n",
"\n",
"**Info:** Anchor currently implements `SimpleAnchorWebTaskTool` and `AdvancedAnchorWebTaskTool` tools for langchain with `browser_use` agent. For \n",
"\n",
"#### AnchorWebTaskToolKit Tools\n",
"\n",
"The difference between each tool in this toolkit is the pydantic configuration structure.\n",
"| Tool Name | Package | Parameters |\n",
"| :--- | :--- | :--- |\n",
"| `SimpleAnchorWebTaskTool` | langchain-anchorbrowser | prompt, url |\n",
"| `AdvancedAnchorWebTaskTool` | langchain-anchorbrowser | prompt, url, output_schema |\n",
"\n",
"## Setup\n",
"\n",
"The integration lives in the `langchain-anchorbrowser` package."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f85b4089",
"metadata": {},
"outputs": [],
"source": [
"%pip install --quiet -U langchain-anchorbrowser"
]
},
{
"cell_type": "markdown",
"id": "b15e9266",
"metadata": {},
"source": [
"### Credentials\n",
"\n",
"Use your Anchor Browser Credentials. Get them on Anchor Browser [API Keys page](https://app.anchorbrowser.io/api-keys?utm=langchain) as needed."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e0b178a2-8816-40ca-b57c-ccdd86dde9c9",
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"if not os.environ.get(\"ANCHORBROWSER_API_KEY\"):\n",
" os.environ[\"ANCHORBROWSER_API_KEY\"] = getpass.getpass(\"ANCHORBROWSER API key:\\n\")"
]
},
{
"cell_type": "markdown",
"id": "1c97218f-f366-479d-8bf7-fe9f2f6df73f",
"metadata": {},
"source": [
"## Instantiation\n",
"\n",
"Instantiace easily Anchor Browser tools instances."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b3ddfe9-ca79-494c-a7ab-1f56d9407a64",
"metadata": {},
"outputs": [],
"source": [
"from langchain_anchorbrowser import (\n",
" AnchorContentTool,\n",
" AnchorScreenshotTool,\n",
" AdvancedAnchorWebTaskTool,\n",
")\n",
"\n",
"anchor_content_tool = AnchorContentTool()\n",
"anchor_screenshot_tool = AnchorScreenshotTool()\n",
"anchor_advanced_web_task_tool = AdvancedAnchorWebTaskTool()"
]
},
{
"cell_type": "markdown",
"id": "74147a1a",
"metadata": {},
"source": [
"## Invocation\n",
"\n",
"### [Invoke directly with args](/docs/concepts/tools/#use-the-tool-directly)\n",
"\n",
"The full available argument list appear above in the tool features table."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "65310a8b-eb0c-4d9e-a618-4f4abe2414fc",
"metadata": {},
"outputs": [],
"source": [
"# Get Markdown Content for https://www.anchorbrowser.io\n",
"anchor_content_tool.invoke(\n",
" {\"url\": \"https://www.anchorbrowser.io\", \"format\": \"markdown\"}\n",
")\n",
"\n",
"# Get a Screenshot for https://docs.anchorbrowser.io\n",
"anchor_screenshot_tool.invoke(\n",
" {\"url\": \"https://docs.anchorbrowser.io\", \"width\": 1280, \"height\": 720}\n",
")\n",
"\n",
"# Get a Screenshot for https://docs.anchorbrowser.io\n",
"anchor_advanced_web_task_tool.invoke(\n",
" {\n",
" \"prompt\": \"Collect the node names and their CPU average %\",\n",
" \"url\": \"https://play.grafana.org/a/grafana-k8s-app/navigation/nodes?from=now-1h&to=now&refresh=1m\",\n",
" \"output_schema\": {\n",
" \"nodes_cpu_usage\": [\n",
" {\"node\": \"string\", \"cluster\": \"string\", \"cpu_avg_percentage\": \"number\"}\n",
" ]\n",
" },\n",
" }\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d6e73897",
"metadata": {},
"source": [
"### [Invoke with ToolCall](/docs/concepts/tool_calling/#tool-execution)\n",
"\n",
"We can also invoke the tool with a model-generated ToolCall, in which case a ToolMessage will be returned:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f90e33a7",
"metadata": {},
"outputs": [],
"source": [
"# This is usually generated by a model, but we'll create a tool call directly for demo purposes.\n",
"model_generated_tool_call = {\n",
" \"args\": {\"url\": \"https://www.anchorbrowser.io\", \"format\": \"markdown\"},\n",
" \"id\": \"1\",\n",
" \"name\": anchor_content_tool.name,\n",
" \"type\": \"tool_call\",\n",
"}\n",
"anchor_content_tool.invoke(model_generated_tool_call)"
]
},
{
"cell_type": "markdown",
"id": "659f9fbd-6fcf-445f-aa8c-72d8e60154bd",
"metadata": {},
"source": [
"## Chaining\n",
"\n",
"We can use our tool in a chain by first binding it to a [tool-calling model](/docs/how_to/tool_calling/) and then calling it:\n",
"## Use within an agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c67bfd54",
"metadata": {},
"outputs": [],
"source": [
"%pip install -qU langchain langchain-openai"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "af3123ad-7a02-40e5-b58e-7d56e23e5830",
"metadata": {},
"outputs": [],
"source": [
"from langchain.chat_models import init_chat_model\n",
"\n",
"llm = init_chat_model(model=\"gpt-4o\", model_provider=\"openai\")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "210511c8",
"metadata": {},
"outputs": [],
"source": [
"if not os.environ.get(\"OPENAI_API_KEY\"):\n",
" os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OPENAI API key:\\n\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fdbf35b5-3aaf-4947-9ec6-48c21533fb95",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.runnables import RunnableConfig, chain\n",
"\n",
"prompt = ChatPromptTemplate(\n",
" [\n",
" (\"system\", \"You are a helpful assistant.\"),\n",
" (\"human\", \"{user_input}\"),\n",
" (\"placeholder\", \"{messages}\"),\n",
" ]\n",
")\n",
"\n",
"# specifying tool_choice will force the model to call this tool.\n",
"llm_with_tools = llm.bind_tools(\n",
" [anchor_content_tool], tool_choice=anchor_content_tool.name\n",
")\n",
"\n",
"llm_chain = prompt | llm_with_tools\n",
"\n",
"\n",
"@chain\n",
"def tool_chain(user_input: str, config: RunnableConfig):\n",
" input_ = {\"user_input\": user_input}\n",
" ai_msg = llm_chain.invoke(input_, config=config)\n",
" tool_msgs = anchor_content_tool.batch(ai_msg.tool_calls, config=config)\n",
" return llm_chain.invoke({**input_, \"messages\": [ai_msg, *tool_msgs]}, config=config)\n",
"\n",
"\n",
"tool_chain.invoke(input())"
]
},
{
"cell_type": "markdown",
"id": "4ac8146c",
"metadata": {},
"source": [
"## API reference\n",
"\n",
" - [PyPi](https://pypi.org/project/langchain-anchorbrowser)\n",
" - [Github](https://github.com/anchorbrowser/langchain-anchorbrowser)\n",
" - [Anchor Browser Docs](https://docs.anchorbrowser.io/introduction?utm=langchain)\n",
" - [Anchor Browser API Reference](https://docs.anchorbrowser.io/api-reference/ai-tools/perform-web-task?utm=langchain)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "langchain",
"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.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,339 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a6f91f20",
"metadata": {},
"source": [
"# Scrapeless\n",
"\n",
"**Scrapeless** offers flexible and feature-rich data acquisition services with extensive parameter customization and multi-format export support. These capabilities empower LangChain to integrate and leverage external data more effectively. The core functional modules include:\n",
"\n",
"**DeepSerp**\n",
"- **Google Search**: Enables comprehensive extraction of Google SERP data across all result types.\n",
" - Supports selection of localized Google domains (e.g., `google.com`, `google.ad`) to retrieve region-specific search results.\n",
" - Pagination supported for retrieving results beyond the first page.\n",
" - Supports a search result filtering toggle to control whether to exclude duplicate or similar content.\n",
"- **Google Trends**: Retrieves keyword trend data from Google, including popularity over time, regional interest, and related searches.\n",
" - Supports multi-keyword comparison.\n",
" - Supports multiple data types: `interest_over_time`, `interest_by_region`, `related_queries`, and `related_topics`.\n",
" - Allows filtering by specific Google properties (Web, YouTube, News, Shopping) for source-specific trend analysis.\n",
"\n",
"**Universal Scraping**\n",
"- Designed for modern, JavaScript-heavy websites, allowing dynamic content extraction.\n",
" - Global premium proxy support for bypassing geo-restrictions and improving reliability.\n",
"\n",
"**Crawler**\n",
"- **Crawl**: Recursively crawl a website and its linked pages to extract site-wide content.\n",
" - Supports configurable crawl depth and scoped URL targeting.\n",
"- **Scrape**: Extract content from a single webpage with high precision.\n",
" - Supports \"main content only\" extraction to exclude ads, footers, and other non-essential elements.\n",
" - Allows batch scraping of multiple standalone URLs.\n",
"\n",
"## Overview\n",
"\n",
"### Integration details\n",
"\n",
"| Class | Package | Serializable | JS support | Package latest |\n",
"| :--- | :--- | :---: | :---: | :---: |\n",
"| [ScrapelessUniversalScrapingTool](https://pypi.org/project/langchain-scrapeless/) | [langchain-scrapeless](https://pypi.org/project/langchain-scrapeless/) | ✅ | ❌ | ![PyPI - Version](https://img.shields.io/pypi/v/langchain-scrapeless?style=flat-square&label=%20) |\n",
"\n",
"### Tool features\n",
"\n",
"|Native async|Returns artifact|Return data|\n",
"|:-:|:-:|:-:|\n",
"|✅|✅|html, markdown, links, metadata, structured content|\n",
"\n",
"\n",
"## Setup\n",
"\n",
"The integration lives in the `langchain-scrapeless` package."
]
},
{
"cell_type": "raw",
"id": "ca676665",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"!pip install langchain-scrapeless"
]
},
{
"cell_type": "markdown",
"id": "b15e9266",
"metadata": {},
"source": [
"### Credentials\n",
"\n",
"You'll need a Scrapeless API key to use this tool. You can set it as an environment variable:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0b178a2-8816-40ca-b57c-ccdd86dde9c9",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"os.environ[\"SCRAPELESS_API_KEY\"] = \"your-api-key\""
]
},
{
"cell_type": "markdown",
"id": "1c97218f-f366-479d-8bf7-fe9f2f6df73f",
"metadata": {},
"source": [
"## Instantiation\n",
"\n",
"Here we show how to instantiate an instance of the Scrapeless Universal Scraping Tool. This tool allows you to scrape any website using a headless browser with JavaScript rendering capabilities, customizable output types, and geo-specific proxy support.\n",
"\n",
"The tool accepts the following parameters during instantiation:\n",
"- `url` (required, str): The URL of the website to scrape.\n",
"- `headless` (optional, bool): Whether to use a headless browser. Default is True.\n",
"- `js_render` (optional, bool): Whether to enable JavaScript rendering. Default is True.\n",
"- `js_wait_until` (optional, str): Defines when to consider the JavaScript-rendered page ready. Default is `'domcontentloaded'`. Options include:\n",
" - `load`: Wait until the page is fully loaded.\n",
" - `domcontentloaded`: Wait until the DOM is fully loaded.\n",
" - `networkidle0`: Wait until the network is idle.\n",
" - `networkidle2`: Wait until the network is idle for 2 seconds.\n",
"- `outputs` (optional, str): The specific type of data to extract from the page. Options include:\n",
" - `phone_numbers`\n",
" - `headings`\n",
" - `images`\n",
" - `audios`\n",
" - `videos`\n",
" - `links`\n",
" - `menus`\n",
" - `hashtags`\n",
" - `emails`\n",
" - `metadata`\n",
" - `tables`\n",
" - `favicon`\n",
"- `response_type` (optional, str): Defines the format of the response. Default is `'html'`. Options include:\n",
" - `html`: Return the raw HTML of the page.\n",
" - `plaintext`: Return the plain text content.\n",
" - `markdown`: Return a Markdown version of the page.\n",
" - `png`: Return a PNG screenshot.\n",
" - `jpeg`: Return a JPEG screenshot.\n",
"- `response_image_full_page` (optional, bool): Whether to capture and return a full-page image when using screenshot output (png or jpeg). Default is False.\n",
"- `selector` (optional, str): A specific CSS selector to scope scraping within a part of the page. Default is `None`.\n",
"- `proxy_country` (optional, str): Two-letter country code for geo-specific proxy access (e.g., `'us'`, `'gb'`, `'de'`, `'jp'`). Default is `'ANY'`."
]
},
{
"cell_type": "markdown",
"id": "74147a1a",
"metadata": {},
"source": [
"## Invocation\n",
"\n",
"### Basic Usage"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "65310a8b-eb0c-4d9e-a618-4f4abe2414fc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<!DOCTYPE html><html><head>\n",
" <title>Example Domain</title>\n",
"\n",
" <meta charset=\"utf-8\">\n",
" <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n",
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n",
" <style type=\"text/css\">\n",
" body {\n",
" background-color: #f0f0f2;\n",
" margin: 0;\n",
" padding: 0;\n",
" font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n",
" \n",
" }\n",
" div {\n",
" width: 600px;\n",
" margin: 5em auto;\n",
" padding: 2em;\n",
" background-color: #fdfdff;\n",
" border-radius: 0.5em;\n",
" box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n",
" }\n",
" a:link, a:visited {\n",
" color: #38488f;\n",
" text-decoration: none;\n",
" }\n",
" @media (max-width: 700px) {\n",
" div {\n",
" margin: 0 auto;\n",
" width: auto;\n",
" }\n",
" }\n",
" </style> \n",
"</head>\n",
"\n",
"<body>\n",
"<div>\n",
" <h1>Example Domain</h1>\n",
" <p>This domain is for use in illustrative examples in documents. You may use this\n",
" domain in literature without prior coordination or asking for permission.</p>\n",
" <p><a href=\"https://www.iana.org/domains/example\">More information...</a></p>\n",
"</div>\n",
"\n",
"\n",
"</body></html>\n"
]
}
],
"source": [
"from langchain_scrapeless import ScrapelessUniversalScrapingTool\n",
"\n",
"tool = ScrapelessUniversalScrapingTool()\n",
"\n",
"# Basic usage\n",
"result = tool.invoke(\"https://example.com\")\n",
"print(result)"
]
},
{
"cell_type": "markdown",
"id": "d6e73897",
"metadata": {},
"source": [
"### Advanced Usage with Parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f90e33a7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# Well hello there.\n",
"\n",
"Welcome to exmaple.com.\n",
"Chances are you got here by mistake (example.com, anyone?)\n"
]
}
],
"source": [
"from langchain_scrapeless import ScrapelessUniversalScrapingTool\n",
"\n",
"tool = ScrapelessUniversalScrapingTool()\n",
"\n",
"result = tool.invoke({\"url\": \"https://exmaple.com\", \"response_type\": \"markdown\"})\n",
"print(result)"
]
},
{
"cell_type": "markdown",
"id": "659f9fbd-6fcf-445f-aa8c-72d8e60154bd",
"metadata": {},
"source": [
"### Use within an agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af3123ad-7a02-40e5-b58e-7d56e23e5830",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Use the scrapeless scraping tool to fetch https://www.scrapeless.com/en and extract the h1 tag.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" scrapeless_universal_scraping (call_jBrvMVL2ixhvf6gklhi7Gqtb)\n",
" Call ID: call_jBrvMVL2ixhvf6gklhi7Gqtb\n",
" Args:\n",
" url: https://www.scrapeless.com/en\n",
" outputs: headings\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: scrapeless_universal_scraping\n",
"\n",
"{\"headings\":[\"Effortless Web Scraping Toolkitfor Business and Developers\",\"4.8\",\"4.5\",\"8.5\",\"A Flexible Toolkit for Accessing Public Web Data\",\"Deep SerpApi\",\"Scraping Browser\",\"Universal Scraping API\",\"Customized Services\",\"From Simple Data Scraping to Complex Anti-Bot Challenges, Scrapeless Has You Covered.\",\"Fully Compatible with Key Programming Languages and Tools\",\"Enterprise-level Data Scraping Solution\",\"Customized Data Scraping Solutions\",\"High Concurrency and High-Performance Scraping\",\"Data Cleaning and Transformation\",\"Real-Time Data Push and API Integration\",\"Data Security and Privacy Protection\",\"Enterprise-level SLA\",\"Why Scrapeless: Simplify Your Data Flow Effortlessly.\",\"Articles\",\"Organized Fresh Data\",\"Prices\",\"No need to hassle with browser maintenance\",\"Reviews\",\"Only pay for successful requests\",\"Products\",\"Fully scalable\",\"Unleash Your Competitive Edgein Data within the Industry\",\"Regulate Compliance for All Users\",\"Web Scraping Blog\",\"Scrapeless MCP Server Is Officially Live! Build Your Ultimate AI-Web Connector\",\"Product Updates | New Profile Feature\",\"How to Track Your Ranking on ChatGPT?\",\"For Scraping\",\"For Data\",\"For AI\",\"Top Scraper API\",\"Learning Center\",\"Legal\"]}\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The h1 tag extracted from the website https://www.scrapeless.com/en is \"Effortless Web Scraping Toolkit for Business and Developers\".\n"
]
}
],
"source": [
"from langchain_openai import ChatOpenAI\n",
"from langchain_scrapeless import ScrapelessUniversalScrapingTool\n",
"from langgraph.prebuilt import create_react_agent\n",
"\n",
"llm = ChatOpenAI()\n",
"\n",
"tool = ScrapelessUniversalScrapingTool()\n",
"\n",
"# Use the tool with an agent\n",
"tools = [tool]\n",
"agent = create_react_agent(llm, tools)\n",
"\n",
"for chunk in agent.stream(\n",
" {\n",
" \"messages\": [\n",
" (\n",
" \"human\",\n",
" \"Use the scrapeless scraping tool to fetch https://www.scrapeless.com/en and extract the h1 tag.\",\n",
" )\n",
" ]\n",
" },\n",
" stream_mode=\"values\",\n",
"):\n",
" chunk[\"messages\"][-1].pretty_print()"
]
},
{
"cell_type": "markdown",
"id": "4ac8146c",
"metadata": {},
"source": [
"## API reference\n",
"\n",
"- [Scrapeless Documentation](https://docs.scrapeless.com/en/universal-scraping-api/quickstart/introduction/)\n",
"- [Scrapeless API Reference](https://apidocs.scrapeless.com/api-12948840)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "langchain",
"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.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -153,7 +153,7 @@
"from langgraph.prebuilt import create_react_agent\n",
"\n",
"llm = ChatAnthropic(\n",
" model=\"claude-3-5-sonnet-latest\",\n",
" model=\"claude-3-5-sonnet-20240620\",\n",
")\n",
"\n",
"langgraph_agent_executor = create_react_agent(llm, stripe_agent_toolkit.get_tools())\n",

File diff suppressed because one or more lines are too long

View File

@@ -73,9 +73,8 @@
]
},
{
"cell_type": "markdown",
"id": "72461be913bfaf2b",
"metadata": {},
"cell_type": "markdown",
"source": [
"## Instantiation\n",
"\n",
@@ -84,26 +83,26 @@
"Instantiation\n",
"The tool accepts various parameters during instantiation:\n",
"\n",
"- `max_results` (optional, int): Maximum number of search results to return. Default is 5.\n",
"- `topic` (optional, str): Category of the search. Can be `'general'`, `'news'`, or `'finance'`. Default is `'general'`.\n",
"- `include_answer` (optional, bool): Include an answer to original query in results. Default is False.\n",
"- `include_raw_content` (optional, bool): Include cleaned and parsed HTML of each search result. Default is False.\n",
"- `include_images` (optional, bool): Include a list of query related images in the response. Default is False.\n",
"- `include_image_descriptions` (optional, bool): Include descriptive text for each image. Default is False.\n",
"- `search_depth` (optional, str): Depth of the search, either `'basic'` or `'advanced'`. Default is `'basic'`.\n",
"- `time_range` (optional, str): The time range back from the current date to filter results - `'day'`, `'week'`, `'month'`, or `'year'`. Default is None.\n",
"- `include_domains` (optional, List[str]): List of domains to specifically include. Default is None.\n",
"- `exclude_domains` (optional, List[str]): List of domains to specifically exclude. Default is None.\n",
"- max_results (optional, int): Maximum number of search results to return. Default is 5.\n",
"- topic (optional, str): Category of the search. Can be \"general\", \"news\", or \"finance\". Default is \"general\".\n",
"- include_answer (optional, bool): Include an answer to original query in results. Default is False.\n",
"- include_raw_content (optional, bool): Include cleaned and parsed HTML of each search result. Default is False.\n",
"- include_images (optional, bool): Include a list of query related images in the response. Default is False.\n",
"- include_image_descriptions (optional, bool): Include descriptive text for each image. Default is False.\n",
"- search_depth (optional, str): Depth of the search, either \"basic\" or \"advanced\". Default is \"basic\".\n",
"- time_range (optional, str): The time range back from the current date to filter results - \"day\", \"week\", \"month\", or \"year\". Default is None.\n",
"- include_domains (optional, List[str]): List of domains to specifically include. Default is None.\n",
"- exclude_domains (optional, List[str]): List of domains to specifically exclude. Default is None.\n",
"\n",
"For a comprehensive overview of the available parameters, refer to the [Tavily Search API documentation](https://docs.tavily.com/documentation/api-reference/endpoint/search)"
]
],
"id": "72461be913bfaf2b"
},
{
"cell_type": "code",
"execution_count": null,
"id": "dc382e5426394836",
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"from langchain_tavily import TavilySearch\n",
"\n",
@@ -119,12 +118,12 @@
" # include_domains=None,\n",
" # exclude_domains=None\n",
")"
]
],
"id": "dc382e5426394836"
},
{
"cell_type": "markdown",
"id": "f997d2733b63f655",
"metadata": {},
"cell_type": "markdown",
"source": [
"## Invocation\n",
"\n",
@@ -135,22 +134,18 @@
"- The following arguments can also be set during invocation : `include_images`, `search_depth` , `time_range`, `include_domains`, `exclude_domains`, `include_images`\n",
"- For reliability and performance reasons, certain parameters that affect response size cannot be modified during invocation: `include_answer` and `include_raw_content`. These limitations prevent unexpected context window issues and ensure consistent results.\n",
"\n",
":::note\n",
"\n",
"The optional arguments are available for agents to dynamically set, if you set an argument during instantiation and then invoke the tool with a different value, the tool will use the value you passed during invocation.\n",
"\n",
":::"
]
"NOTE: The optional arguments are available for agents to dynamically set, if you set an argument during instantiation and then invoke the tool with a different value, the tool will use the value you passed during invocation."
],
"id": "f997d2733b63f655"
},
{
"cell_type": "code",
"execution_count": null,
"id": "5e75399230ab9fc1",
"metadata": {},
"cell_type": "code",
"outputs": [],
"source": [
"tool.invoke({\"query\": \"What happened at the last wimbledon\"})"
]
"execution_count": null,
"source": "tool.invoke({\"query\": \"What happened at the last wimbledon\"})",
"id": "5e75399230ab9fc1"
},
{
"cell_type": "markdown",
@@ -159,7 +154,7 @@
"source": [
"### [Invoke with ToolCall](/docs/concepts/tools)\n",
"\n",
"We can also invoke the tool with a model-generated `ToolCall`, in which case a `ToolMessage` will be returned:"
"We can also invoke the tool with a model-generated ToolCall, in which case a ToolMessage will be returned:"
]
},
{
@@ -238,7 +233,7 @@
"id": "1020a506-473b-4e6a-a563-7aaf92c4d183",
"metadata": {},
"source": [
"We will need to install `langgraph`:"
"We will need to install langgraph:"
]
},
{
@@ -261,21 +256,21 @@
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"================================\u001B[1m Human Message \u001B[0m=================================\n",
"\n",
"What nation hosted the Euro 2024? Include only wikipedia sources.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"==================================\u001B[1m Ai Message \u001B[0m==================================\n",
"Tool Calls:\n",
" tavily_search (call_yxmR4K2uadsQ8LKoyi8JyoLD)\n",
" Call ID: call_yxmR4K2uadsQ8LKoyi8JyoLD\n",
" Args:\n",
" query: Euro 2024 host nation\n",
" include_domains: ['wikipedia.org']\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"=================================\u001B[1m Tool Message \u001B[0m=================================\n",
"Name: tavily_search\n",
"\n",
"{\"query\": \"Euro 2024 host nation\", \"follow_up_questions\": null, \"answer\": null, \"images\": [], \"results\": [{\"title\": \"UEFA Euro 2024 - Wikipedia\", \"url\": \"https://en.wikipedia.org/wiki/UEFA_Euro_2024\", \"content\": \"Tournament details Host country Germany Dates 14 June 14 July Teams 24 Venue(s) 10 (in 10 host cities) Final positions Champions Spain (4th title) Runners-up England Tournament statistics Matches played 51 Goals scored 117 (2.29 per match) Attendance 2,681,288 (52,574 per match) Top scorer(s) Harry Kane Georges Mikautadze Jamal Musiala Cody Gakpo Ivan Schranz Dani Olmo (3 goals each) Best player(s) Rodri Best young player Lamine Yamal ← 2020 2028 → The 2024 UEFA European Football Championship, commonly referred to as UEFA Euro 2024 (stylised as UEFA EURO 2024) or simply Euro 2024, was the 17th UEFA European Championship, the quadrennial international football championship organised by UEFA for the European men's national teams of their member associations. Germany hosted the tournament, which took place from 14 June to 14 July 2024. The tournament involved 24 teams, with Georgia making their European Championship debut. [4] Host nation Germany were eliminated by Spain in the quarter-finals; Spain went on to win the tournament for a record fourth time after defeating England 21 in the final.\", \"score\": 0.9104262, \"raw_content\": null}, {\"title\": \"UEFA Euro 2024 - Simple English Wikipedia, the free encyclopedia\", \"url\": \"https://simple.wikipedia.org/wiki/UEFA_Euro_2024\", \"content\": \"The 2024 UEFA European Football Championship, also known as UEFA Euro 2024 or simply Euro 2024, was the 17th edition of the UEFA European Championship. Germany was hosting the tournament. ... The UEFA Executive Committee voted for the host in a secret ballot, with only a simple majority (more than half of the valid votes) required to determine\", \"score\": 0.81418616, \"raw_content\": null}, {\"title\": \"Championnat d'Europe de football 2024 — Wikipédia\", \"url\": \"https://fr.wikipedia.org/wiki/Championnat_d'Europe_de_football_2024\", \"content\": \"Le Championnat d'Europe de l'UEFA de football 2024 est la 17 e édition du Championnat d'Europe de football, communément abrégé en Euro 2024, compétition organisée par l'UEFA et rassemblant les meilleures équipes nationales masculines européennes. L'Allemagne est désignée pays organisateur de la compétition le 27 septembre 2018. C'est la troisième fois que des matches du Championnat\", \"score\": 0.8055255, \"raw_content\": null}, {\"title\": \"UEFA Euro 2024 bids - Wikipedia\", \"url\": \"https://en.wikipedia.org/wiki/UEFA_Euro_2024_bids\", \"content\": \"The bidding process of UEFA Euro 2024 ended on 27 September 2018 in Nyon, Switzerland, when Germany was announced to be the host. [1] Two bids came before the deadline, 3 March 2017, which were Germany and Turkey as single bids. ... Press agencies revealed on 24 October 2013, that the European football governing body UEFA would have decided on\", \"score\": 0.7882741, \"raw_content\": null}, {\"title\": \"2024 UEFA European Under-19 Championship - Wikipedia\", \"url\": \"https://en.wikipedia.org/wiki/2024_UEFA_European_Under-19_Championship\", \"content\": \"The 2024 UEFA European Under-19 Championship (also known as UEFA Under-19 Euro 2024) was the 21st edition of the UEFA European Under-19 Championship (71st edition if the Under-18 and Junior eras are included), the annual international youth football championship organised by UEFA for the men's under-19 national teams of Europe. Northern Ireland hosted the tournament from 15 to 28 July 2024.\", \"score\": 0.7783298, \"raw_content\": null}], \"response_time\": 1.67}\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"==================================\u001B[1m Ai Message \u001B[0m==================================\n",
"\n",
"The nation that hosted Euro 2024 was Germany. You can find more information on the [Wikipedia page for UEFA Euro 2024](https://en.wikipedia.org/wiki/UEFA_Euro_2024).\n"
]
@@ -309,14 +304,8 @@
"source": [
"## API reference\n",
"\n",
"For detailed documentation of all Tavily Search API features and configurations head to the [API reference](https://docs.tavily.com/documentation/api-reference/endpoint/search)."
"For detailed documentation of all Tavily Search API features and configurations head to the API reference: https://docs.tavily.com/documentation/api-reference/endpoint/search"
]
},
{
"cell_type": "markdown",
"id": "589ff839",
"metadata": {},
"source": []
}
],
"metadata": {

View File

@@ -1,378 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "554b9f85",
"metadata": {},
"source": [
"# MCP Toolbox for Databases\n",
"\n",
"Integrate your databases with LangChain agents using MCP Toolbox.\n",
"\n",
"## Overview\n",
"\n",
"[MCP Toolbox for Databases](https://github.com/googleapis/genai-toolbox) is an open source MCP server for databases. It was designed with enterprise-grade and production-quality in mind. It enables you to develop tools easier, faster, and more securely by handling the complexities such as connection pooling, authentication, and more.\n",
"\n",
"Toolbox Tools can be seemlessly integrated with Langchain applications. For more\n",
"information on [getting\n",
"started](https://googleapis.github.io/genai-toolbox/getting-started/local_quickstart/) or\n",
"[configuring](https://googleapis.github.io/genai-toolbox/getting-started/configure/)\n",
"MCP Toolbox, see the\n",
"[documentation](https://googleapis.github.io/genai-toolbox/getting-started/introduction/).\n",
"\n",
"![architecture](https://raw.githubusercontent.com/googleapis/genai-toolbox/refs/heads/main/docs/en/getting-started/introduction/architecture.png)"
]
},
{
"cell_type": "markdown",
"id": "788ff64c",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"This guide assumes you have already done the following:\n",
"\n",
"1. Installed [Python 3.9+](https://wiki.python.org/moin/BeginnersGuide/Download) and [pip](https://pip.pypa.io/en/stable/installation/).\n",
"2. Installed [PostgreSQL 16+ and the `psql` command-line client](https://www.postgresql.org/download/)."
]
},
{
"cell_type": "markdown",
"id": "4847d196",
"metadata": {},
"source": [
"### 1. Setup your Database\n",
"\n",
"First, let's set up a PostgreSQL database. We'll create a new database, a dedicated user for MCP Toolbox, and a `hotels` table with some sample data.\n",
"\n",
"Connect to PostgreSQL using the `psql` command. You may need to adjust the command based on your PostgreSQL setup (e.g., if you need to specify a host or a different superuser).\n",
"\n",
"```bash\n",
"psql -U postgres\n",
"```\n",
"\n",
"Now, run the following SQL commands to create the user, database, and grant the necessary permissions:\n",
"\n",
"```sql\n",
"CREATE USER toolbox_user WITH PASSWORD 'my-password';\n",
"CREATE DATABASE toolbox_db;\n",
"GRANT ALL PRIVILEGES ON DATABASE toolbox_db TO toolbox_user;\n",
"ALTER DATABASE toolbox_db OWNER TO toolbox_user;\n",
"```\n",
"\n",
"Connect to your newly created database with the new user:\n",
"\n",
"```sql\n",
"\\c toolbox_db toolbox_user\n",
"```\n",
"\n",
"Finally, create the `hotels` table and insert some data:\n",
"\n",
"```sql\n",
"CREATE TABLE hotels(\n",
" id INTEGER NOT NULL PRIMARY KEY,\n",
" name VARCHAR NOT NULL,\n",
" location VARCHAR NOT NULL,\n",
" price_tier VARCHAR NOT NULL,\n",
" booked BIT NOT NULL\n",
");\n",
"\n",
"INSERT INTO hotels(id, name, location, price_tier, booked)\n",
"VALUES \n",
" (1, 'Hilton Basel', 'Basel', 'Luxury', B'0'),\n",
" (2, 'Marriott Zurich', 'Zurich', 'Upscale', B'0'),\n",
" (3, 'Hyatt Regency Basel', 'Basel', 'Upper Upscale', B'0');\n",
"```\n",
"You can now exit `psql` by typing `\\q`."
]
},
{
"cell_type": "markdown",
"id": "855133f8",
"metadata": {},
"source": [
"### 2. Install MCP Toolbox\n",
"\n",
"Next, we will install MCP Toolbox, define our tools in a `tools.yaml` configuration file, and run the MCP Toolbox server.\n",
"\n",
"For **macOS** users, the easiest way to install is with [Homebrew](https://formulae.brew.sh/formula/mcp-toolbox):\n",
"\n",
"```bash\n",
"brew install mcp-toolbox\n",
"```\n",
"\n",
"For other platforms, [download the latest MCP Toolbox binary for your operating system and architecture.](https://github.com/googleapis/genai-toolbox/releases)\n",
"\n",
"Create a `tools.yaml` file. This file defines the data sources MCP Toolbox can connect to and the tools it can expose to your agent. For production use, always use environment variables for secrets.\n",
"\n",
"```yaml\n",
"sources:\n",
" my-pg-source:\n",
" kind: postgres\n",
" host: 127.0.0.1\n",
" port: 5432\n",
" database: toolbox_db\n",
" user: toolbox_user\n",
" password: my-password\n",
"\n",
"tools:\n",
" search-hotels-by-location:\n",
" kind: postgres-sql\n",
" source: my-pg-source\n",
" description: Search for hotels based on location.\n",
" parameters:\n",
" - name: location\n",
" type: string\n",
" description: The location of the hotel.\n",
" statement: SELECT id, name, location, price_tier FROM hotels WHERE location ILIKE '%' || $1 || '%';\n",
" book-hotel:\n",
" kind: postgres-sql\n",
" source: my-pg-source\n",
" description: >-\n",
" Book a hotel by its ID. If the hotel is successfully booked, returns a confirmation message.\n",
" parameters:\n",
" - name: hotel_id\n",
" type: integer\n",
" description: The ID of the hotel to book.\n",
" statement: UPDATE hotels SET booked = B'1' WHERE id = $1;\n",
"\n",
"toolsets:\n",
" hotel_toolset:\n",
" - search-hotels-by-location\n",
" - book-hotel\n",
"```\n",
"\n",
"Now, in a separate terminal window, start the MCP Toolbox server. If you installed via Homebrew, you can just run `toolbox`. If you downloaded the binary manually, you'll need to run `./toolbox` from the directory where you saved it:\n",
"\n",
"```bash\n",
"toolbox --tools-file \"tools.yaml\"\n",
"```\n",
"\n",
"MCP Toolbox will start on `http://127.0.0.1:5000` by default and will hot-reload if you make changes to your `tools.yaml` file."
]
},
{
"cell_type": "markdown",
"id": "b9b2f041",
"metadata": {},
"source": [
"## Instantiation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4c31f3b",
"metadata": {},
"outputs": [],
"source": [
"!pip install toolbox-langchain"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14a68a49",
"metadata": {},
"outputs": [],
"source": [
"from toolbox_langchain import ToolboxClient\n",
"\n",
"with ToolboxClient(\"http://127.0.0.1:5000\") as client:\n",
" search_tool = await client.aload_tool(\"search-hotels-by-location\")"
]
},
{
"cell_type": "markdown",
"id": "95eec50c",
"metadata": {},
"source": [
"## Invocation\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8e99351b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[{\"id\":1,\"location\":\"Basel\",\"name\":\"Hilton Basel\",\"price_tier\":\"Luxury\"},{\"id\":3,\"location\":\"Basel\",\"name\":\"Hyatt Regency Basel\",\"price_tier\":\"Upper Upscale\"}]\n"
]
}
],
"source": [
"from toolbox_langchain import ToolboxClient\n",
"\n",
"with ToolboxClient(\"http://127.0.0.1:5000\") as client:\n",
" search_tool = await client.aload_tool(\"search-hotels-by-location\")\n",
" results = search_tool.invoke({\"location\": \"Basel\"})\n",
" print(results)"
]
},
{
"cell_type": "markdown",
"id": "9e8dbd39",
"metadata": {},
"source": [
"## Use within an agent\n",
"\n",
"Now for the fun part! We'll install the required LangChain packages and create an agent that can use the tools we defined in MCP Toolbox."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9b716a84",
"metadata": {
"id": "install-packages"
},
"outputs": [],
"source": [
"%pip install -U --quiet toolbox-langchain langgraph langchain-google-vertexai"
]
},
{
"cell_type": "markdown",
"id": "affda34b",
"metadata": {},
"source": [
"With the packages installed, we can define our agent. We will use `ChatVertexAI` for the model and `ToolboxClient` to load our tools. The `create_react_agent` from `langgraph.prebuilt` creates a robust agent that can reason about which tools to call.\n",
"\n",
"**Note:** Ensure your MCP Toolbox server is running in a separate terminal before executing the code below."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ddd82892",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.prebuilt import create_react_agent\n",
"from langchain_google_vertexai import ChatVertexAI\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"from toolbox_langchain import ToolboxClient\n",
"\n",
"prompt = \"\"\"\n",
"You're a helpful hotel assistant. You handle hotel searching and booking.\n",
"When the user searches for a hotel, list the full details for each hotel found: id, name, location, and price tier.\n",
"Always use the hotel ID for booking operations.\n",
"For any bookings, provide a clear confirmation message.\n",
"Don't ask for clarification or confirmation from the user; perform the requested action directly.\n",
"\"\"\"\n",
"\n",
"\n",
"async def run_queries(agent_executor):\n",
" config = {\"configurable\": {\"thread_id\": \"hotel-thread-1\"}}\n",
"\n",
" # --- Query 1: Search for hotels ---\n",
" query1 = \"I need to find a hotel in Basel.\"\n",
" print(f'\\n--- USER: \"{query1}\" ---')\n",
" inputs1 = {\"messages\": [(\"user\", prompt + query1)]}\n",
" async for event in agent_executor.astream_events(\n",
" inputs1, config=config, version=\"v2\"\n",
" ):\n",
" if event[\"event\"] == \"on_chat_model_end\" and event[\"data\"][\"output\"].content:\n",
" print(f\"--- AGENT: ---\\n{event['data']['output'].content}\")\n",
"\n",
" # --- Query 2: Book a hotel ---\n",
" query2 = \"Great, please book the Hyatt Regency Basel for me.\"\n",
" print(f'\\n--- USER: \"{query2}\" ---')\n",
" inputs2 = {\"messages\": [(\"user\", query2)]}\n",
" async for event in agent_executor.astream_events(\n",
" inputs2, config=config, version=\"v2\"\n",
" ):\n",
" if event[\"event\"] == \"on_chat_model_end\" and event[\"data\"][\"output\"].content:\n",
" print(f\"--- AGENT: ---\\n{event['data']['output'].content}\")"
]
},
{
"cell_type": "markdown",
"id": "54552733",
"metadata": {},
"source": [
"## Run the agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9f7c199b",
"metadata": {},
"outputs": [],
"source": [
"async def main():\n",
" await run_hotel_agent()\n",
"\n",
"\n",
"async def run_hotel_agent():\n",
" model = ChatVertexAI(model_name=\"gemini-2.5-flash\")\n",
"\n",
" # Load the tools from the running MCP Toolbox server\n",
" async with ToolboxClient(\"http://127.0.0.1:5000\") as client:\n",
" tools = await client.aload_toolset(\"hotel_toolset\")\n",
"\n",
" agent = create_react_agent(model, tools, checkpointer=MemorySaver())\n",
"\n",
" await run_queries(agent)\n",
"\n",
"\n",
"await main()"
]
},
{
"cell_type": "markdown",
"id": "79bce43d",
"metadata": {},
"source": [
"You've successfully connected a LangChain agent to a local database using MCP Toolbox! 🥳\n",
"\n",
"## API reference\n",
"\n",
"The primary class for this integration is `ToolboxClient`.\n",
"\n",
"For more information, see the following resources:\n",
"- [Toolbox Official Documentation](https://googleapis.github.io/genai-toolbox/)\n",
"- [Toolbox GitHub Repository](https://github.com/googleapis/genai-toolbox)\n",
"- [Toolbox LangChain SDK](https://github.com/googleapis/mcp-toolbox-python-sdk/tree/main/packages/toolbox-langchain)\n",
"\n",
"MCP Toolbox has a variety of features to make developing Gen AI tools for databases seamless:\n",
"- [Authenticated Parameters](https://googleapis.github.io/genai-toolbox/resources/tools/#authenticated-parameters): Bind tool inputs to values from OIDC tokens automatically, making it easy to run sensitive queries without potentially leaking data\n",
"- [Authorized Invocations](https://googleapis.github.io/genai-toolbox/resources/tools/#authorized-invocations): Restrict access to use a tool based on the users Auth token\n",
"- [OpenTelemetry](https://googleapis.github.io/genai-toolbox/how-to/export_telemetry/): Get metrics and tracing from MCP Toolbox with [OpenTelemetry](https://opentelemetry.io/docs/)\n",
"\n",
"# Community and Support\n",
"\n",
"We encourage you to get involved with the community:\n",
"- ⭐️ Head over to the [GitHub repository](https://github.com/googleapis/genai-toolbox) to get started and follow along with updates.\n",
"- 📚 Dive into the [official documentation](https://googleapis.github.io/genai-toolbox/getting-started/introduction/) for more advanced features and configurations.\n",
"- 💬 Join our [Discord server](https://discord.com/invite/a4XjGqtmnG) to connect with the community and ask questions."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -11,7 +11,7 @@ LangChain simplifies every stage of the LLM application lifecycle:
- **Development**: Build your applications using LangChain's open-source [components](/docs/concepts) and [third-party integrations](/docs/integrations/providers/).
Use [LangGraph](/docs/concepts/architecture/#langgraph) to build stateful agents with first-class streaming and human-in-the-loop support.
- **Productionization**: Use [LangSmith](https://docs.smith.langchain.com/) to inspect, monitor and evaluate your applications, so that you can continuously optimize and deploy with confidence.
- **Deployment**: Turn your LangGraph applications into production-ready APIs and Assistants with [LangGraph Platform](https://docs.langchain.com/langgraph-platform).
- **Deployment**: Turn your LangGraph applications into production-ready APIs and Assistants with [LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/).
import ThemedImage from '@theme/ThemedImage';
import useBaseUrl from '@docusaurus/useBaseUrl';

View File

@@ -45,7 +45,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 5,
"metadata": {},
"outputs": [
{
@@ -74,7 +74,7 @@
"\n",
"uncoercible_message = {\"role\": \"HumanMessage\", \"random_field\": \"random value\"}\n",
"\n",
"model = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
"\n",
"model.invoke([uncoercible_message])"
]

View File

@@ -85,7 +85,7 @@
"As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your chain or agent.\n",
"The best way to do this is with [LangSmith](https://smith.langchain.com).\n",
"\n",
"After you sign up at the link above, **(you'll need to create an API key from the Settings -> API Keys page on the LangSmith website)**, make sure to set your environment variables to start logging traces:\n",
"After you sign up at the link above, make sure to set your environment variables to start logging traces:\n",
"\n",
"```shell\n",
"export LANGSMITH_TRACING=\"true\"\n",
@@ -720,7 +720,7 @@
" AIMessage(content='yes!', additional_kwargs={}, response_metadata={})]"
]
},
"execution_count": 109,
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
@@ -771,13 +771,8 @@
"\n",
"\n",
"def call_model(state: State):\n",
" print(f\"Messages before trimming: {len(state['messages'])}\")\n",
" # highlight-start\n",
" trimmed_messages = trimmer.invoke(state[\"messages\"])\n",
" print(f\"Messages after trimming: {len(trimmed_messages)}\")\n",
" print(\"Remaining messages:\")\n",
" for msg in trimmed_messages:\n",
" print(f\" {type(msg).__name__}: {msg.content}\")\n",
" prompt = prompt_template.invoke(\n",
" {\"messages\": trimmed_messages, \"language\": state[\"language\"]}\n",
" )\n",
@@ -797,7 +792,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Now if we try asking the model our name, it won't know it since we trimmed that part of the chat history. (By defining our trim stragegy as `'last'`, we are only keeping the most recent messages that fit within the `max_tokens`.)"
"Now if we try asking the model our name, it won't know it since we trimmed that part of the chat history:"
]
},
{
@@ -809,20 +804,9 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Messages before trimming: 12\n",
"Messages after trimming: 8\n",
"Remaining messages:\n",
" SystemMessage: you're a good assistant\n",
" HumanMessage: whats 2 + 2\n",
" AIMessage: 4\n",
" HumanMessage: thanks\n",
" AIMessage: no problem!\n",
" HumanMessage: having fun?\n",
" AIMessage: yes!\n",
" HumanMessage: What is my name?\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"I don't know your name. If you'd like to share it, feel free!\n"
"I don't know your name. You haven't told me yet!\n"
]
}
],
@@ -856,27 +840,15 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Messages before trimming: 12\n",
"Messages after trimming: 8\n",
"Remaining messages:\n",
" SystemMessage: you're a good assistant\n",
" HumanMessage: whats 2 + 2\n",
" AIMessage: 4\n",
" HumanMessage: thanks\n",
" AIMessage: no problem!\n",
" HumanMessage: having fun?\n",
" AIMessage: yes!\n",
" HumanMessage: What math problem was asked?\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The math problem that was asked was \"what's 2 + 2.\"\n"
"You asked what 2 + 2 equals.\n"
]
}
],
"source": [
"config = {\"configurable\": {\"thread_id\": \"abc678\"}}\n",
"\n",
"query = \"What math problem was asked?\"\n",
"query = \"What math problem did I ask?\"\n",
"language = \"English\"\n",
"\n",
"input_messages = messages + [HumanMessage(query)]\n",
@@ -918,9 +890,9 @@
"text": [
"|Hi| Todd|!| Here|s| a| joke| for| you|:\n",
"\n",
"|Why| don't| scientists| trust| atoms|?\n",
"|Why| don|t| skeleton|s| fight| each| other|?\n",
"\n",
"|Because| they| make| up| everything|!||"
"|Because| they| don|t| have| the| guts|!||"
]
}
],

View File

@@ -49,7 +49,7 @@
"metadata": {},
"outputs": [],
"source": [
"pip install -U langchain-core"
"pip install --upgrade --quiet langchain-core"
]
},
{
@@ -89,7 +89,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"id": "39f3ce3e",
"metadata": {},
"outputs": [],
@@ -124,7 +124,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"id": "5509b6a6",
"metadata": {},
"outputs": [
@@ -134,7 +134,7 @@
"Classification(sentiment='positive', aggressiveness=1, language='Spanish')"
]
},
"execution_count": 4,
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
@@ -157,17 +157,17 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"id": "9154474c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'sentiment': 'angry', 'aggressiveness': 8, 'language': 'Spanish'}"
"{'sentiment': 'enojado', 'aggressiveness': 8, 'language': 'es'}"
]
},
"execution_count": 4,
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
@@ -218,7 +218,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 11,
"id": "6a5f7961",
"metadata": {},
"outputs": [],
@@ -237,7 +237,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 15,
"id": "e5a5881f",
"metadata": {},
"outputs": [],
@@ -268,17 +268,17 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 12,
"id": "d9b9d53d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Classification(sentiment='happy', aggressiveness=1, language='spanish')"
"Classification(sentiment='positive', aggressiveness=1, language='Spanish')"
]
},
"execution_count": 7,
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
@@ -291,17 +291,17 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 13,
"id": "1c12fa00",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Classification(sentiment='sad', aggressiveness=4, language='spanish')"
"Classification(sentiment='enojado', aggressiveness=8, language='es')"
]
},
"execution_count": 8,
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
@@ -314,17 +314,17 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 14,
"id": "0bdfcb05",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Classification(sentiment='happy', aggressiveness=1, language='english')"
"Classification(sentiment='neutral', aggressiveness=1, language='English')"
]
},
"execution_count": 9,
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
@@ -359,7 +359,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "langchain-monorepo",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -373,7 +373,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
"version": "3.10.4"
}
},
"nbformat": 4,

View File

@@ -192,7 +192,7 @@
"source": [
":::tip\n",
"\n",
"If we've enabled LangSmith, we can see that this run is logged to LangSmith, and can see the [LangSmith trace](https://docs.smith.langchain.com/observability/concepts#traces). The LangSmith trace reports [token](/docs/concepts/tokens/) usage information, latency, [standard model parameters](/docs/concepts/chat_models/#standard-parameters) (such as temperature), and other information.\n",
"If we've enabled LangSmith, we can see that this run is logged to LangSmith, and can see the [LangSmith trace](https://smith.langchain.com/public/88baa0b2-7c1a-4d09-ba30-a47985dde2ea/r). The LangSmith trace reports [token](/docs/concepts/tokens/) usage information, latency, [standard model parameters](/docs/concepts/chat_models/#standard-parameters) (such as temperature), and other information.\n",
"\n",
":::\n",
"\n",

View File

@@ -236,7 +236,7 @@
"We can use [create_stuff_documents_chain](https://python.langchain.com/api_reference/langchain/chains/langchain.chains.combine_documents.stuff.create_stuff_documents_chain.html), especially if using larger context window models such as:\n",
"\n",
"* 128k token OpenAI `gpt-4o` \n",
"* 200k token Anthropic `claude-3-5-sonnet-latest`\n",
"* 200k token Anthropic `claude-3-5-sonnet-20240620`\n",
"\n",
"The chain will take a list of documents, insert them all into a prompt, and pass that prompt to an LLM:"
]

View File

@@ -142,7 +142,8 @@ const config = {
respectPrefersColorScheme: true,
},
announcementBar: {
content: "Our new LangChain Academy Course Deep Research with LangGraph is now live! <a href='https://academy.langchain.com/courses/deep-research-with-langgraph/?utm_medium=internal&utm_source=docs&utm_campaign=q3-2025_deep-research-course_co' target='_blank'>Enroll for free</a>.",
content:
'<strong>Our <a href="https://academy.langchain.com/courses/ambient-agents/?utm_medium=internal&utm_source=docs&utm_campaign=q2-2025_ambient-agents_co" target="_blank">Building Ambient Agents with LangGraph</a> course is now available on LangChain Academy!</strong>',
backgroundColor: "#d0c9fe",
},
prism: {

View File

@@ -5,14 +5,6 @@ echo "VERCEL_GIT_COMMIT_REF: $VERCEL_GIT_COMMIT_REF"
echo "VERCEL_GIT_REPO_OWNER: $VERCEL_GIT_REPO_OWNER"
echo "VERCEL_GIT_REPO_SLUG: $VERCEL_GIT_REPO_SLUG"
echo "Checking for skip-preview tags..."
COMMIT_MESSAGE=$(git log -1 --pretty=%B)
echo "Commit message: $COMMIT_MESSAGE"
if [[ "$COMMIT_MESSAGE" == *"[skip-preview]"* ]] || [[ "$COMMIT_MESSAGE" == *"[no-preview]"* ]] || [[ "$COMMIT_MESSAGE" == *"[skip-deploy]"* ]]; then
echo "🛑 Skip-preview tag found in commit message - skipping preview deployment"
exit 0
fi
if { \
[ "$VERCEL_ENV" == "production" ] || \
@@ -21,10 +13,10 @@ if { \
[ "$VERCEL_GIT_COMMIT_REF" == "v0.2" ] || \
[ "$VERCEL_GIT_COMMIT_REF" == "v0.3rc" ]; \
} && [ "$VERCEL_GIT_REPO_OWNER" == "langchain-ai" ]
then
then
echo "✅ Production build - proceeding with build"
exit 1
fi
fi
echo "Checking for changes in docs/"

View File

@@ -1,6 +1,5 @@
from datetime import datetime, timedelta, timezone
from pathlib import Path
import re
import requests
from ruamel.yaml import YAML
@@ -12,18 +11,10 @@ PACKAGE_YML = Path(__file__).parents[2] / "libs" / "packages.yml"
def _get_downloads(p: dict) -> int:
url = f"https://pepy.tech/badge/{p['name']}/month"
svg = requests.get(url, timeout=10).text
texts = re.findall(r"<text[^>]*>([^<]+)</text>", svg)
latest = texts[-1].strip() if texts else "0"
# parse "1.2k", "3.4M", "12,345" -> int
latest = latest.replace(",", "")
if latest.endswith(("k", "K")):
return int(float(latest[:-1]) * 1_000)
if latest.endswith(("m", "M")):
return int(float(latest[:-1]) * 1_000_000)
return int(float(latest) if "." in latest else int(latest))
url = f"https://pypistats.org/api/packages/{p['name']}/recent?period=month"
r = requests.get(url)
r.raise_for_status()
return r.json()["data"]["last_month"]
current_datetime = datetime.now(timezone.utc)

View File

@@ -101,12 +101,7 @@ def package_row(p: dict) -> str:
link = p["provider_page"]
title = p["name_title"]
provider = f"[{title}]({link})" if link else title
return (
f"| {provider} | [{p['name']}]({p['package_url']}) | "
f"![Downloads](https://static.pepy.tech/badge/{p['name']}/month) | "
f"![PyPI - Version](https://img.shields.io/pypi/v/{p['name']}?style=flat-square&label=%20&color=orange) | "
f"{js} |"
)
return f"| {provider} | [{p['name']}]({p['package_url']}) | ![PyPI - Downloads](https://img.shields.io/pypi/dm/{p['name']}?style=flat-square&label=%20&color=blue) | ![PyPI - Version](https://img.shields.io/pypi/v/{p['name']}?style=flat-square&label=%20&color=orange) | {js} |"
def table() -> str:

View File

@@ -182,10 +182,6 @@ DATABASE_TOOL_FEAT_TABLE = {
"link": "/docs/integrations/tools/cassandra_database",
"operations": "SELECT and schema introspection",
},
"MCP Toolbox": {
"link": "/docs/integrations/tools/toolbox",
"operations": "Any SQL operation",
},
}
FINANCE_TOOL_FEAT_TABLE = {

View File

@@ -27,7 +27,7 @@ module.exports = {
},
{
type: "category",
link: { type: 'doc', id: 'tutorials/index' },
link: {type: 'doc', id: 'tutorials/index'},
label: "Tutorials",
collapsible: false,
items: [{
@@ -38,7 +38,7 @@ module.exports = {
},
{
type: "category",
link: { type: 'doc', id: 'how_to/index' },
link: {type: 'doc', id: 'how_to/index'},
label: "How-to guides",
collapsible: false,
items: [{
@@ -49,7 +49,7 @@ module.exports = {
},
{
type: "category",
link: { type: 'doc', id: 'concepts/index' },
link: {type: 'doc', id: 'concepts/index'},
label: "Conceptual guide",
collapsible: false,
items: [{
@@ -103,7 +103,7 @@ module.exports = {
{
type: "category",
label: "Migrating from v0.0 chains",
link: { type: 'doc', id: 'versions/migrating_chains/index' },
link: {type: 'doc', id: 'versions/migrating_chains/index'},
collapsible: false,
collapsed: false,
items: [{
@@ -115,7 +115,7 @@ module.exports = {
{
type: "category",
label: "Upgrading to LangGraph memory",
link: { type: 'doc', id: 'versions/migrating_memory/index' },
link: {type: 'doc', id: 'versions/migrating_memory/index'},
collapsible: false,
collapsed: false,
items: [{
@@ -418,7 +418,7 @@ module.exports = {
},
],
},
],
link: {
type: "generated-index",
@@ -434,7 +434,7 @@ module.exports = {
},
{
type: "category",
link: { type: 'doc', id: 'contributing/tutorials/index' },
link: {type: 'doc', id: 'contributing/tutorials/index'},
label: "Tutorials",
collapsible: false,
items: [{
@@ -445,7 +445,7 @@ module.exports = {
},
{
type: "category",
link: { type: 'doc', id: 'contributing/how_to/index' },
link: {type: 'doc', id: 'contributing/how_to/index'},
label: "How-to guides",
collapsible: false,
items: [{
@@ -456,7 +456,7 @@ module.exports = {
},
{
type: "category",
link: { type: 'doc', id: 'contributing/reference/index' },
link: {type: 'doc', id: 'contributing/reference/index'},
label: "Reference & FAQ",
collapsible: false,
items: [{

View File

@@ -231,13 +231,6 @@ ${llmVarName} = ChatWatsonx(
model: "llama-3.1-sonar-small-128k-online",
apiKeyName: "PPLX_API_KEY",
packageName: "langchain-perplexity",
},
{
value: "deepseek",
label: "DeepSeek",
model: "deepseek-chat",
apiKeyName: "DEEPSEEK_API_KEY",
packageName: "langchain-deepseek",
}
].map((item) => ({
...item,

View File

@@ -822,17 +822,10 @@ const FEATURE_TABLES = {
api: "Package",
apiLink: "https://python.langchain.com/api_reference/community/document_loaders/langchain_community.document_loaders.sitemap.SitemapLoader.html"
},
{
name: "Spider",
link: "spider",
source: "Crawler and scraper that returns LLM-ready data.",
api: "API",
apiLink: "https://python.langchain.com/api_reference/community/document_loaders/langchain_community.document_loaders.spider.SpiderLoader.html"
},
{
name: "Firecrawl",
link: "firecrawl",
source: "API service that can be deployed locally.",
source: "API service that can be deployed locally, hosted version has free credits.",
api: "API",
apiLink: "https://python.langchain.com/api_reference/community/document_loaders/langchain_community.document_loaders.firecrawl.FireCrawlLoader.html"
},
@@ -856,13 +849,6 @@ const FEATURE_TABLES = {
source: "Web interaction and structured data extraction from any web page using an AgentQL query or a Natural Language prompt",
api: "API",
apiLink: "https://python.langchain.com/docs/integrations/document_loaders/agentql/"
},
{
name: "Oxylabs",
link: "oxylabs",
source: "Web intelligence platform enabling the access to various data sources.",
api: "API",
apiLink: "https://github.com/oxylabs/langchain-oxylabs"
}
]
},

View File

@@ -77,7 +77,7 @@ export default function VectorStoreTabs(props) {
{
value: "Qdrant",
label: "Qdrant",
text: `from qdrant_client.models import Distance, VectorParams\nfrom langchain_qdrant import QdrantVectorStore\nfrom qdrant_client import QdrantClient\n${useFakeEmbeddings ? fakeEmbeddingsString : ""}\nclient = QdrantClient(":memory:")\n\nvector_size = len(embeddings.embed_query("sample text"))\n\nif not client.collection_exists("test"):\n client.create_collection(\n collection_name="test",\n vectors_config=VectorParams(size=vector_size, distance=Distance.COSINE)\n )\n${vectorStoreVarName} = QdrantVectorStore(\n client=client,\n collection_name="test",\n embedding=embeddings,\n)`,
text: `from langchain_qdrant import QdrantVectorStore\nfrom qdrant_client import QdrantClient\n${useFakeEmbeddings ? fakeEmbeddingsString : ""}\nclient = QdrantClient(":memory:")\n${vectorStoreVarName} = QdrantVectorStore(\n client=client,\n collection_name="test",\n embedding=embeddings,\n)`,
packageName: "langchain-qdrant",
default: false,
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

View File

@@ -11,5 +11,3 @@ numpy>=1.26.0,<2.0.0
simsimd>=5.0.0
# Fix sentencepiece build error - use newer version that supports modern CMake
sentencepiece>=0.2.1
# Fix langchain-azure-ai dependency conflict with langchain-core
langchain-core @ file:///home/runner/work/langchain/langchain/langchain/libs/core

View File

@@ -1,5 +1,3 @@
"""LangChain CLI."""
from langchain_cli._version import __version__
__all__ = [

View File

@@ -1,5 +1,3 @@
"""LangChain CLI."""
from typing import Annotated, Optional
import typer
@@ -36,21 +34,20 @@ app.command(
)
def _version_callback(*, show_version: bool) -> None:
def version_callback(show_version: bool) -> None: # noqa: FBT001
if show_version:
typer.echo(f"langchain-cli {__version__}")
raise typer.Exit
@app.callback()
def _main(
*,
version: bool = typer.Option(
def main(
version: bool = typer.Option( # noqa: FBT001
False, # noqa: FBT003
"--version",
"-v",
help="Print the current CLI version.",
callback=_version_callback,
callback=version_callback,
is_eager=True,
),
) -> None:

View File

@@ -1,5 +1,3 @@
"""LangChain CLI constants."""
DEFAULT_GIT_REPO = "https://github.com/langchain-ai/langchain.git"
DEFAULT_GIT_SUBDIRECTORY = "templates"
DEFAULT_GIT_REF = "master"

View File

@@ -13,7 +13,7 @@ def create_demo_server(
*,
config_keys: Sequence[str] = (),
playground_type: Literal["default", "chat"] = "default",
) -> FastAPI:
):
"""Create a demo server for the current template."""
app = FastAPI()
package_root = get_package_root()
@@ -40,11 +40,9 @@ def create_demo_server(
return app
def create_demo_server_configurable() -> FastAPI:
"""Create a configurable demo server."""
def create_demo_server_configurable():
return create_demo_server(config_keys=["configurable"])
def create_demo_server_chat() -> FastAPI:
"""Create a chat demo server."""
def create_demo_server_chat():
return create_demo_server(playground_type="chat")

View File

@@ -1 +0,0 @@
"""Namespaces."""

View File

@@ -8,7 +8,6 @@ from pathlib import Path
from typing import Annotated, Optional
import typer
import uvicorn
from langchain_cli.utils.events import create_events
from langchain_cli.utils.git import (
@@ -262,7 +261,7 @@ def add(
cmd = ["pip", "install", "-e", *installed_destination_strs]
cmd_str = " \\\n ".join(installed_destination_strs)
typer.echo(f"Running: pip install -e \\\n {cmd_str}")
subprocess.run(cmd, cwd=cwd, check=True) # noqa: S603
subprocess.run(cmd, cwd=cwd) # noqa: S603
chain_names = []
for e in installed_exports:
@@ -368,6 +367,8 @@ def serve(
app_str = app if app is not None else "app.server:app"
host_str = host if host is not None else "127.0.0.1"
import uvicorn
uvicorn.run(
app_str,
host=host_str,

View File

@@ -15,8 +15,6 @@ integration_cli = typer.Typer(no_args_is_help=True, add_completion=False)
class Replacements(TypedDict):
"""Replacements."""
__package_name__: str
__module_name__: str
__ModuleName__: str
@@ -129,7 +127,6 @@ def new(
subprocess.run(
["poetry", "install", "--with", "lint,test,typing,test_integration"], # noqa: S607
cwd=destination_dir,
check=True,
)
else:
# confirm src and dst are the same length

View File

@@ -1 +0,0 @@
"""Migrations."""

View File

@@ -1 +0,0 @@
"""Generate migrations."""

View File

@@ -3,7 +3,6 @@
import importlib
import inspect
import pkgutil
from types import ModuleType
def generate_raw_migrations(
@@ -90,7 +89,7 @@ def generate_top_level_imports(pkg: str) -> list[tuple[str, str]]:
items = []
# Function to handle importing from modules
def handle_module(module: ModuleType, module_name: str) -> None:
def handle_module(module, module_name) -> None:
if hasattr(module, "__all__"):
all_objects = module.__all__
for name in all_objects:

View File

@@ -1,6 +1,3 @@
"""Migration as Grit file."""
def split_package(package: str) -> tuple[str, str]:
"""Split a package name into the containing package and the final name."""
parts = package.split(".")

View File

@@ -1,11 +1,8 @@
"""Generate migrations utilities."""
import ast
import inspect
import os
import pathlib
from pathlib import Path
from types import ModuleType
from typing import Any, Optional
HERE = Path(__file__).parent
@@ -18,14 +15,12 @@ PARTNER_PKGS = PKGS_ROOT / "partners"
class ImportExtractor(ast.NodeVisitor):
"""Import extractor."""
def __init__(self, *, from_package: Optional[str] = None) -> None:
"""Extract all imports from the given code, optionally filtering by package."""
self.imports: list = []
self.package = from_package
def visit_ImportFrom(self, node: ast.ImportFrom) -> None: # noqa: N802
def visit_ImportFrom(self, node) -> None: # noqa: N802
if node.module and (
self.package is None or str(node.module).startswith(self.package)
):
@@ -44,7 +39,7 @@ def _get_class_names(code: str) -> list[str]:
# Define a node visitor class to collect class names
class ClassVisitor(ast.NodeVisitor):
def visit_ClassDef(self, node: ast.ClassDef) -> None: # noqa: N802
def visit_ClassDef(self, node) -> None: # noqa: N802
class_names.append(node.name)
self.generic_visit(node)
@@ -63,7 +58,7 @@ def is_subclass(class_obj: Any, classes_: list[type]) -> bool:
)
def find_subclasses_in_module(module: ModuleType, classes_: list[type]) -> list[str]:
def find_subclasses_in_module(module, classes_: list[type]) -> list[str]:
"""Find all classes in the module that inherit from one of the classes."""
subclasses = []
# Iterate over all attributes of the module that are classes
@@ -75,7 +70,8 @@ def find_subclasses_in_module(module: ModuleType, classes_: list[type]) -> list[
def _get_all_classnames_from_file(file: Path, pkg: str) -> list[tuple[str, str]]:
"""Extract all class names from a file."""
code = Path(file).read_text(encoding="utf-8")
with open(file, encoding="utf-8") as f:
code = f.read()
module_name = _get_current_module(file, pkg)
class_names = _get_class_names(code)
@@ -88,7 +84,8 @@ def identify_all_imports_in_file(
from_package: Optional[str] = None,
) -> list[tuple[str, str]]:
"""Let's also identify all the imports in the given file."""
code = Path(file).read_text(encoding="utf-8")
with open(file, encoding="utf-8") as f:
code = f.read()
return find_imports_from_package(code, from_package=from_package)
@@ -146,7 +143,6 @@ def find_imports_from_package(
*,
from_package: Optional[str] = None,
) -> list[tuple[str, str]]:
"""Find imports in code."""
# Parse the code into an AST
tree = ast.parse(code)
# Create an instance of the visitor
@@ -158,7 +154,8 @@ def find_imports_from_package(
def _get_current_module(path: Path, pkg_root: str) -> str:
"""Convert a path to a module name."""
relative_path = path.relative_to(pkg_root).with_suffix("")
path_as_pathlib = pathlib.Path(os.path.abspath(path))
relative_path = path_as_pathlib.relative_to(pkg_root).with_suffix("")
posix_path = relative_path.as_posix()
norm_path = os.path.normpath(str(posix_path))
fully_qualified_module = norm_path.replace("/", ".")

View File

@@ -7,9 +7,7 @@ from pathlib import Path
from typing import Annotated, Optional
import typer
import uvicorn
from langchain_cli.utils.github import list_packages
from langchain_cli.utils.packages import get_langserve_export, get_package_root
package_cli = typer.Typer(no_args_is_help=True, add_completion=False)
@@ -81,7 +79,7 @@ def new(
# poetry install
if with_poetry:
subprocess.run(["poetry", "install"], cwd=destination_dir, check=True) # noqa: S607
subprocess.run(["poetry", "install"], cwd=destination_dir) # noqa: S607
@package_cli.command()
@@ -130,6 +128,8 @@ def serve(
)
)
import uvicorn
uvicorn.run(
script,
factory=True,
@@ -142,6 +142,8 @@ def serve(
@package_cli.command()
def list(contains: Annotated[Optional[str], typer.Argument()] = None) -> None: # noqa: A001
"""List all or search for available templates."""
from langchain_cli.utils.github import list_packages
packages = list_packages(contains=contains)
for package in packages:
typer.echo(package)

View File

@@ -1 +0,0 @@
"""Utilities."""

View File

@@ -1,5 +1,3 @@
"""Events utilities."""
import http.client
import json
from typing import Any, Optional, TypedDict
@@ -10,19 +8,11 @@ WRITE_KEY = "310apTK0HUFl4AOv"
class EventDict(TypedDict):
"""Event data structure for analytics tracking.
Attributes:
event: The name of the event.
properties: Optional dictionary of event properties.
"""
event: str
properties: Optional[dict[str, Any]]
def create_events(events: list[EventDict]) -> Optional[Any]:
"""Create events."""
try:
data = {
"events": [

View File

@@ -1,10 +1,7 @@
"""Find and replace text in files."""
from pathlib import Path
def find_and_replace(source: str, replacements: dict[str, str]) -> str:
"""Find and replace text in a string."""
rtn = source
# replace keys in deterministic alphabetical order
@@ -16,7 +13,6 @@ def find_and_replace(source: str, replacements: dict[str, str]) -> str:
def replace_file(source: Path, replacements: dict[str, str]) -> None:
"""Replace text in a file."""
try:
content = source.read_text()
except UnicodeDecodeError:
@@ -28,7 +24,6 @@ def replace_file(source: Path, replacements: dict[str, str]) -> None:
def replace_glob(parent: Path, glob: str, replacements: dict[str, str]) -> None:
"""Replace text in files matching a glob pattern."""
for file in parent.glob(glob):
if not file.is_file():
continue

View File

@@ -1,7 +1,4 @@
"""Git utilities."""
import hashlib
import logging
import re
import shutil
from collections.abc import Sequence
@@ -16,12 +13,8 @@ from langchain_cli.constants import (
DEFAULT_GIT_SUBDIRECTORY,
)
logger = logging.getLogger(__name__)
class DependencySource(TypedDict):
"""Dependency source information."""
git: str
ref: Optional[str]
subdirectory: Optional[str]
@@ -36,7 +29,6 @@ def parse_dependency_string(
branch: Optional[str],
api_path: Optional[str],
) -> DependencySource:
"""Parse a dependency string into a DependencySource."""
if dep is not None and dep.startswith("git+"):
if repo is not None or branch is not None:
msg = (
@@ -129,7 +121,6 @@ def parse_dependencies(
branch: list[str],
api_path: list[str],
) -> list[DependencySource]:
"""Parse dependencies."""
num_deps = max(
len(dependencies) if dependencies is not None else 0,
len(repo),
@@ -177,22 +168,22 @@ def _get_repo_path(gitstring: str, ref: Optional[str], repo_dir: Path) -> Path:
def update_repo(gitstring: str, ref: Optional[str], repo_dir: Path) -> Path:
"""Update a git repository to the specified ref."""
# see if path already saved
repo_path = _get_repo_path(gitstring, ref, repo_dir)
if repo_path.exists():
# try pulling
try:
repo = Repo(repo_path)
if repo.active_branch.name == ref:
repo.remotes.origin.pull()
return repo_path
if repo.active_branch.name != ref:
raise ValueError
repo.remotes.origin.pull()
except Exception:
logger.exception("Failed to pull existing repo")
# if it fails, delete and clone again
shutil.rmtree(repo_path)
# if it fails, delete and clone again
shutil.rmtree(repo_path)
Repo.clone_from(gitstring, repo_path, branch=ref, depth=1)
else:
Repo.clone_from(gitstring, repo_path, branch=ref, depth=1)
Repo.clone_from(gitstring, repo_path, branch=ref, depth=1)
return repo_path
@@ -205,7 +196,7 @@ def copy_repo(
Raises FileNotFound error if it can't find source
"""
def ignore_func(_: str, files: list[str]) -> list[str]:
def ignore_func(_, files):
return [f for f in files if f == ".git"]
shutil.copytree(source, destination, ignore=ignore_func)

View File

@@ -1,12 +1,9 @@
"""GitHub utilities."""
import http.client
import json
from typing import Optional
def list_packages(*, contains: Optional[str] = None) -> list[str]:
"""List all packages in the langchain repository templates directory."""
conn = http.client.HTTPSConnection("api.github.com")
try:
headers = {

View File

@@ -1,5 +1,3 @@
"""Packages utilities."""
from pathlib import Path
from typing import Any, Optional, TypedDict
@@ -7,7 +5,6 @@ from tomlkit import load
def get_package_root(cwd: Optional[Path] = None) -> Path:
"""Get package root directory."""
# traverse path for routes to host (any directory holding a pyproject.toml file)
package_root = Path.cwd() if cwd is None else cwd
visited: set[Path] = set()
@@ -38,8 +35,7 @@ class LangServeExport(TypedDict):
def get_langserve_export(filepath: Path) -> LangServeExport:
"""Get LangServe export information from a pyproject.toml file."""
with filepath.open() as f:
with open(filepath) as f:
data: dict[str, Any] = load(f)
try:
module = data["tool"]["langserve"]["export_module"]

View File

@@ -1,5 +1,3 @@
"""Pyproject.toml utilities."""
import contextlib
from collections.abc import Iterable
from pathlib import Path
@@ -20,7 +18,7 @@ def add_dependencies_to_pyproject_toml(
local_editable_dependencies: Iterable[tuple[str, Path]],
) -> None:
"""Add dependencies to pyproject.toml."""
with pyproject_toml.open(encoding="utf-8") as f:
with open(pyproject_toml, encoding="utf-8") as f:
# tomlkit types aren't amazing - treat as Dict instead
pyproject: dict[str, Any] = load(f)
pyproject["tool"]["poetry"]["dependencies"].update(
@@ -29,7 +27,7 @@ def add_dependencies_to_pyproject_toml(
for name, loc in local_editable_dependencies
},
)
with pyproject_toml.open("w", encoding="utf-8") as f:
with open(pyproject_toml, "w", encoding="utf-8") as f:
dump(pyproject, f)
@@ -38,13 +36,12 @@ def remove_dependencies_from_pyproject_toml(
local_editable_dependencies: Iterable[str],
) -> None:
"""Remove dependencies from pyproject.toml."""
with pyproject_toml.open(encoding="utf-8") as f:
with open(pyproject_toml, encoding="utf-8") as f:
pyproject: dict[str, Any] = load(f)
# tomlkit types aren't amazing - treat as Dict instead
dependencies = pyproject["tool"]["poetry"]["dependencies"]
for name in local_editable_dependencies:
with contextlib.suppress(KeyError):
del dependencies[name]
with pyproject_toml.open("w", encoding="utf-8") as f:
with open(pyproject_toml, "w", encoding="utf-8") as f:
dump(pyproject, f)

View File

@@ -48,41 +48,58 @@ exclude = [
]
[tool.ruff.lint]
select = [ "ALL",]
select = [
"A", # flake8-builtins
"B", # flake8-bugbear
"ARG", # flake8-unused-arguments
"ASYNC", # flake8-async
"C4", # flake8-comprehensions
"COM", # flake8-commas
"D", # pydocstyle
"E", # pycodestyle error
"EM", # flake8-errmsg
"F", # pyflakes
"FA", # flake8-future-annotations
"FBT", # flake8-boolean-trap
"FLY", # flake8-flynt
"I", # isort
"ICN", # flake8-import-conventions
"INT", # flake8-gettext
"ISC", # isort-comprehensions
"N", # pep8-naming
"PT", # flake8-pytest-style
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PERF", # flake8-perf
"PYI", # flake8-pyi
"Q", # flake8-quotes
"RET", # flake8-return
"RSE", # flake8-rst-docstrings
"RUF", # ruff
"S", # flake8-bandit
"SLF", # flake8-self
"SLOT", # flake8-slots
"SIM", # flake8-simplify
"T10", # flake8-debugger
"T20", # flake8-print
"TID", # flake8-tidy-imports
"UP", # pyupgrade
"W", # pycodestyle warning
"YTT", # flake8-2020
]
ignore = [
"C90", # McCabe complexity
"D100", # pydocstyle: Missing docstring in public module
"D101", # pydocstyle: Missing docstring in public class
"D102", # pydocstyle: Missing docstring in public method
"D103", # pydocstyle: Missing docstring in public function
"D104", # pydocstyle: Missing docstring in public package
"D105", # pydocstyle: Missing docstring in magic method
"D107", # pydocstyle: Missing docstring in __init__
"D407", # pydocstyle: Missing-dashed-underline-after-section
"COM812", # Messes with the formatter
"FIX002", # Line contains TODO
"PERF203", # Rarely useful
"PLR09", # Too many something (arg, statements, etc)
"RUF012", # Doesn't play well with Pydantic
"TC001", # Doesn't play well with Pydantic
"TC002", # Doesn't play well with Pydantic
"TC003", # Doesn't play well with Pydantic
"TD002", # Missing author in TODO
"TD003", # Missing issue link in TODO
# TODO rules
"ANN401",
"BLE",
"D1",
]
unfixable = [
"B028", # People should intentionally tune the stacklevel
"PLW1510", # People should intentionally set the check argument
]
flake8-annotations.allow-star-arg-any = true
flake8-annotations.mypy-init-return = true
flake8-type-checking.runtime-evaluated-base-classes = ["pydantic.BaseModel","langchain_core.load.serializable.Serializable","langchain_core.runnables.base.RunnableSerializable"]
pep8-naming.classmethod-decorators = [ "classmethod", "langchain_core.utils.pydantic.pre_init", "pydantic.field_validator", "pydantic.v1.root_validator",]
pydocstyle.convention = "google"
pyupgrade.keep-runtime-typing = true
[tool.ruff.lint.per-file-ignores]
"tests/**" = [ "D1", "S", "SLF",]
"scripts/**" = [ "INP", "S",]
[tool.mypy]
exclude = [
"langchain_cli/integration_template",

View File

@@ -1 +0,0 @@
"""Scripts."""

View File

@@ -1,8 +1,8 @@
"""Script to generate migrations for the migration script."""
import json
import os
import pkgutil
from pathlib import Path
from typing import Optional
import click
@@ -73,18 +73,19 @@ def generic(
else:
dumped = dump_migrations_as_grit(name, migrations)
Path(output).write_text(dumped)
with open(output, "w") as f:
f.write(dumped)
def handle_partner(pkg: str, output: Optional[str] = None) -> None:
"""Handle partner package migrations."""
migrations = get_migrations_for_partner_package(pkg)
# Run with python 3.9+
name = pkg.removeprefix("langchain_")
data = dump_migrations_as_grit(name, migrations)
output_name = f"{name}.grit" if output is None else output
if migrations:
Path(output_name).write_text(data)
with open(output_name, "w") as f:
f.write(data)
click.secho(f"LangChain migration script saved to {output_name}")
else:
click.secho(f"No migrations found for {pkg}", fg="yellow")
@@ -103,13 +104,13 @@ def partner(pkg: str, output: str) -> None:
@click.argument("json_file")
def json_to_grit(json_file: str) -> None:
"""Generate a Grit migration from an old JSON migration file."""
file = Path(json_file)
with file.open() as f:
with open(json_file) as f:
migrations = json.load(f)
name = file.stem
name = os.path.basename(json_file).removesuffix(".json").removesuffix(".grit")
data = dump_migrations_as_grit(name, migrations)
output_name = f"{name}.grit"
Path(output_name).write_text(data)
with open(output_name, "w") as f:
f.write(data)
click.secho(f"GritQL migration script saved to {output_name}")

View File

@@ -14,6 +14,3 @@ class File:
return False
return self.content == __value.content
def __hash__(self) -> int:
return hash((self.name, self.content))

View File

@@ -57,6 +57,3 @@ class Folder:
return False
return True
def __hash__(self) -> int:
return hash((self.name, tuple(self.files)))

View File

@@ -21,13 +21,13 @@ For full documentation see the [API reference](https://python.langchain.com/api_
## 1⃣ Core Interface: Runnables
The concept of a `Runnable` is central to LangChain Core it is the interface that most LangChain Core components implement, giving them
The concept of a Runnable is central to LangChain Core it is the interface that most LangChain Core components implement, giving them
- a common invocation interface (`invoke()`, `batch()`, `stream()`, etc.)
- a common invocation interface (invoke, batch, stream, etc.)
- built-in utilities for retries, fallbacks, schemas and runtime configurability
- easy deployment with [LangGraph](https://github.com/langchain-ai/langgraph)
- easy deployment with [LangServe](https://github.com/langchain-ai/langserve)
For more check out the [runnable docs](https://python.langchain.com/docs/concepts/runnables/). Examples of components that implement the interface include: LLMs, Chat Models, Prompts, Retrievers, Tools, Output Parsers.
For more check out the [runnable docs](https://python.langchain.com/docs/expression_language/interface). Examples of components that implement the interface include: LLMs, Chat Models, Prompts, Retrievers, Tools, Output Parsers.
You can use LangChain Core objects in two ways:
@@ -51,7 +51,7 @@ LangChain Expression Language (LCEL) is a _declarative language_ for composing L
LangChain Core compiles LCEL sequences to an _optimized execution plan_, with automatic parallelization, streaming, tracing, and async support.
For more check out the [LCEL docs](https://python.langchain.com/docs/concepts/lcel/).
For more check out the [LCEL docs](https://python.langchain.com/docs/expression_language/).
![Diagram outlining the hierarchical organization of the LangChain framework, displaying the interconnected parts across multiple layers.](https://raw.githubusercontent.com/langchain-ai/langchain/master/docs/static/svg/langchain_stack_112024.svg "LangChain Framework Overview")
@@ -59,6 +59,8 @@ For more advanced use cases, also check out [LangGraph](https://github.com/langc
## 📕 Releases & Versioning
`langchain-core` is currently on version `0.1.x`.
As `langchain-core` contains the base abstractions and runtime for the whole LangChain ecosystem, we will communicate any breaking changes with advance notice and version bumps. The exception for this is anything in `langchain_core.beta`. The reason for `langchain_core.beta` is that given the rate of change of the field, being able to move quickly is still a priority, and this module is our attempt to do so.
Minor version increases will occur for:

View File

@@ -147,6 +147,7 @@ def beta(
return obj
elif isinstance(obj, property):
# note(erick): this block doesn't seem to be used?
if not _obj_type:
_obj_type = "attribute"
wrapped = None
@@ -167,7 +168,6 @@ def beta(
self.__orig_fget = fget
self.__orig_fset = fset
self.__orig_fdel = fdel
self.__doc__ = doc
def __get__(
self, instance: Any, owner: Union[type, None] = None

View File

@@ -7,6 +7,8 @@ from typing import TYPE_CHECKING, Any, Optional, Union
from typing_extensions import Self
from langchain_core.v1.messages import AIMessage, AIMessageChunk, MessageV1
if TYPE_CHECKING:
from collections.abc import Sequence
from uuid import UUID
@@ -66,7 +68,9 @@ class LLMManagerMixin:
self,
token: str,
*,
chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
chunk: Optional[
Union[GenerationChunk, ChatGenerationChunk, AIMessageChunk]
] = None,
run_id: UUID,
parent_run_id: Optional[UUID] = None,
**kwargs: Any,
@@ -75,8 +79,8 @@ class LLMManagerMixin:
Args:
token (str): The new token.
chunk (GenerationChunk | ChatGenerationChunk): The new generated chunk,
containing content and other information.
chunk (GenerationChunk | ChatGenerationChunk | AIMessageChunk): The new
generated chunk, containing content and other information.
run_id (UUID): The run ID. This is the ID of the current run.
parent_run_id (UUID): The parent run ID. This is the ID of the parent run.
kwargs (Any): Additional keyword arguments.
@@ -84,7 +88,7 @@ class LLMManagerMixin:
def on_llm_end(
self,
response: LLMResult,
response: Union[LLMResult, AIMessage],
*,
run_id: UUID,
parent_run_id: Optional[UUID] = None,
@@ -93,7 +97,7 @@ class LLMManagerMixin:
"""Run when LLM ends running.
Args:
response (LLMResult): The response which was generated.
response (LLMResult | AIMessage): The response which was generated.
run_id (UUID): The run ID. This is the ID of the current run.
parent_run_id (UUID): The parent run ID. This is the ID of the parent run.
kwargs (Any): Additional keyword arguments.
@@ -261,7 +265,7 @@ class CallbackManagerMixin:
def on_chat_model_start(
self,
serialized: dict[str, Any],
messages: list[list[BaseMessage]],
messages: Union[list[list[BaseMessage]], list[MessageV1]],
*,
run_id: UUID,
parent_run_id: Optional[UUID] = None,
@@ -439,6 +443,9 @@ class BaseCallbackHandler(
run_inline: bool = False
"""Whether to run the callback inline."""
accepts_new_messages: bool = False
"""Whether the callback accepts new message format."""
@property
def ignore_llm(self) -> bool:
"""Whether to ignore LLM callbacks."""
@@ -509,7 +516,7 @@ class AsyncCallbackHandler(BaseCallbackHandler):
async def on_chat_model_start(
self,
serialized: dict[str, Any],
messages: list[list[BaseMessage]],
messages: Union[list[list[BaseMessage]], list[MessageV1]],
*,
run_id: UUID,
parent_run_id: Optional[UUID] = None,
@@ -540,7 +547,9 @@ class AsyncCallbackHandler(BaseCallbackHandler):
self,
token: str,
*,
chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
chunk: Optional[
Union[GenerationChunk, ChatGenerationChunk, AIMessageChunk]
] = None,
run_id: UUID,
parent_run_id: Optional[UUID] = None,
tags: Optional[list[str]] = None,
@@ -550,8 +559,8 @@ class AsyncCallbackHandler(BaseCallbackHandler):
Args:
token (str): The new token.
chunk (GenerationChunk | ChatGenerationChunk): The new generated chunk,
containing content and other information.
chunk (GenerationChunk | ChatGenerationChunk | AIMessageChunk): The new
generated chunk, containing content and other information.
run_id (UUID): The run ID. This is the ID of the current run.
parent_run_id (UUID): The parent run ID. This is the ID of the parent run.
tags (Optional[list[str]]): The tags.
@@ -560,7 +569,7 @@ class AsyncCallbackHandler(BaseCallbackHandler):
async def on_llm_end(
self,
response: LLMResult,
response: Union[LLMResult, AIMessage],
*,
run_id: UUID,
parent_run_id: Optional[UUID] = None,
@@ -570,7 +579,7 @@ class AsyncCallbackHandler(BaseCallbackHandler):
"""Run when LLM ends running.
Args:
response (LLMResult): The response which was generated.
response (LLMResult | AIMessage): The response which was generated.
run_id (UUID): The run ID. This is the ID of the current run.
parent_run_id (UUID): The parent run ID. This is the ID of the parent run.
tags (Optional[list[str]]): The tags.
@@ -594,8 +603,8 @@ class AsyncCallbackHandler(BaseCallbackHandler):
parent_run_id: The parent run ID. This is the ID of the parent run.
tags: The tags.
kwargs (Any): Additional keyword arguments.
- response (LLMResult): The response which was generated before
the error occurred.
- response (LLMResult | AIMessage): The response which was generated
before the error occurred.
"""
async def on_chain_start(

View File

@@ -29,8 +29,16 @@ from langchain_core.callbacks.base import (
)
from langchain_core.callbacks.stdout import StdOutCallbackHandler
from langchain_core.messages import BaseMessage, get_buffer_string
from langchain_core.messages.utils import convert_from_v1_message
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, LLMResult
from langchain_core.tracers.schemas import Run
from langchain_core.utils.env import env_var_is_set
from langchain_core.v1.messages import (
AIMessage,
AIMessageChunk,
MessageV1,
MessageV1Types,
)
if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Coroutine, Generator, Sequence
@@ -39,7 +47,7 @@ if TYPE_CHECKING:
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.documents import Document
from langchain_core.outputs import ChatGenerationChunk, GenerationChunk, LLMResult
from langchain_core.outputs import GenerationChunk
from langchain_core.runnables.config import RunnableConfig
logger = logging.getLogger(__name__)
@@ -238,6 +246,46 @@ def shielded(func: Func) -> Func:
return cast("Func", wrapped)
def _convert_llm_events(
event_name: str, args: tuple[Any, ...], kwargs: dict[str, Any]
) -> tuple[tuple[Any, ...], dict[str, Any]]:
args_list = list(args)
if (
event_name == "on_chat_model_start"
and isinstance(args_list[1], list)
and args_list[1]
and isinstance(args_list[1][0], MessageV1Types)
):
batch = [
convert_from_v1_message(item)
for item in args_list[1]
if isinstance(item, MessageV1Types)
]
args_list[1] = [batch]
elif (
event_name == "on_llm_new_token"
and "chunk" in kwargs
and isinstance(kwargs["chunk"], MessageV1Types)
):
chunk = kwargs["chunk"]
kwargs["chunk"] = ChatGenerationChunk(text=chunk.text, message=chunk)
elif event_name == "on_llm_end" and isinstance(args_list[0], MessageV1Types):
args_list[0] = LLMResult(
generations=[
[
ChatGeneration(
text=args_list[0].text,
message=convert_from_v1_message(args_list[0]),
)
]
]
)
else:
pass
return tuple(args_list), kwargs
def handle_event(
handlers: list[BaseCallbackHandler],
event_name: str,
@@ -268,6 +316,8 @@ def handle_event(
if ignore_condition_name is None or not getattr(
handler, ignore_condition_name
):
if not handler.accepts_new_messages:
args, kwargs = _convert_llm_events(event_name, args, kwargs)
event = getattr(handler, event_name)(*args, **kwargs)
if asyncio.iscoroutine(event):
coros.append(event)
@@ -362,6 +412,8 @@ async def _ahandle_event_for_handler(
) -> None:
try:
if ignore_condition_name is None or not getattr(handler, ignore_condition_name):
if not handler.accepts_new_messages:
args, kwargs = _convert_llm_events(event_name, args, kwargs)
event = getattr(handler, event_name)
if asyncio.iscoroutinefunction(event):
await event(*args, **kwargs)
@@ -681,7 +733,9 @@ class CallbackManagerForLLMRun(RunManager, LLMManagerMixin):
self,
token: str,
*,
chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
chunk: Optional[
Union[GenerationChunk, ChatGenerationChunk, AIMessageChunk]
] = None,
**kwargs: Any,
) -> None:
"""Run when LLM generates a new token.
@@ -707,11 +761,11 @@ class CallbackManagerForLLMRun(RunManager, LLMManagerMixin):
**kwargs,
)
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
def on_llm_end(self, response: Union[LLMResult, AIMessage], **kwargs: Any) -> None:
"""Run when LLM ends running.
Args:
response (LLMResult): The LLM result.
response (LLMResult | AIMessage): The LLM result.
**kwargs (Any): Additional keyword arguments.
"""
@@ -738,8 +792,9 @@ class CallbackManagerForLLMRun(RunManager, LLMManagerMixin):
Args:
error (Exception or KeyboardInterrupt): The error.
kwargs (Any): Additional keyword arguments.
- response (LLMResult): The response which was generated before
the error occurred.
- response (LLMResult | AIMessage): The response which was generated
before the error occurred.
"""
if not self.handlers:
return
@@ -780,7 +835,9 @@ class AsyncCallbackManagerForLLMRun(AsyncRunManager, LLMManagerMixin):
self,
token: str,
*,
chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
chunk: Optional[
Union[GenerationChunk, ChatGenerationChunk, AIMessageChunk]
] = None,
**kwargs: Any,
) -> None:
"""Run when LLM generates a new token.
@@ -807,11 +864,13 @@ class AsyncCallbackManagerForLLMRun(AsyncRunManager, LLMManagerMixin):
)
@shielded
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
async def on_llm_end(
self, response: Union[LLMResult, AIMessage], **kwargs: Any
) -> None:
"""Run when LLM ends running.
Args:
response (LLMResult): The LLM result.
response (LLMResult | AIMessage): The LLM result.
**kwargs (Any): Additional keyword arguments.
"""
@@ -839,10 +898,8 @@ class AsyncCallbackManagerForLLMRun(AsyncRunManager, LLMManagerMixin):
Args:
error (Exception or KeyboardInterrupt): The error.
kwargs (Any): Additional keyword arguments.
- response (LLMResult): The response which was generated before
the error occurred.
- response (LLMResult | AIMessage): The response which was generated
before the error occurred.
"""
if not self.handlers:
@@ -1384,7 +1441,7 @@ class CallbackManager(BaseCallbackManager):
def on_chat_model_start(
self,
serialized: dict[str, Any],
messages: list[list[BaseMessage]],
messages: Union[list[list[BaseMessage]], list[MessageV1]],
run_id: Optional[UUID] = None,
**kwargs: Any,
) -> list[CallbackManagerForLLMRun]:
@@ -1392,7 +1449,7 @@ class CallbackManager(BaseCallbackManager):
Args:
serialized (dict[str, Any]): The serialized LLM.
messages (list[list[BaseMessage]]): The list of messages.
messages (list[list[BaseMessage | MessageV1]]): The list of messages.
run_id (UUID, optional): The ID of the run. Defaults to None.
**kwargs (Any): Additional keyword arguments.
@@ -1401,6 +1458,32 @@ class CallbackManager(BaseCallbackManager):
list of messages as an LLM run.
"""
if messages and isinstance(messages[0], MessageV1Types):
run_id_ = run_id if run_id is not None else uuid.uuid4()
handle_event(
self.handlers,
"on_chat_model_start",
"ignore_chat_model",
serialized,
messages,
run_id=run_id_,
parent_run_id=self.parent_run_id,
tags=self.tags,
metadata=self.metadata,
**kwargs,
)
return [
CallbackManagerForLLMRun(
run_id=run_id_,
handlers=self.handlers,
inheritable_handlers=self.inheritable_handlers,
parent_run_id=self.parent_run_id,
tags=self.tags,
inheritable_tags=self.inheritable_tags,
metadata=self.metadata,
inheritable_metadata=self.inheritable_metadata,
)
]
managers = []
for message_list in messages:
if run_id is not None:
@@ -1903,7 +1986,7 @@ class AsyncCallbackManager(BaseCallbackManager):
async def on_chat_model_start(
self,
serialized: dict[str, Any],
messages: list[list[BaseMessage]],
messages: Union[list[list[BaseMessage]], list[MessageV1]],
run_id: Optional[UUID] = None,
**kwargs: Any,
) -> list[AsyncCallbackManagerForLLMRun]:
@@ -1911,7 +1994,7 @@ class AsyncCallbackManager(BaseCallbackManager):
Args:
serialized (dict[str, Any]): The serialized LLM.
messages (list[list[BaseMessage]]): The list of messages.
messages (list[list[BaseMessage | MessageV1]]): The list of messages.
run_id (UUID, optional): The ID of the run. Defaults to None.
**kwargs (Any): Additional keyword arguments.
@@ -1920,10 +2003,51 @@ class AsyncCallbackManager(BaseCallbackManager):
async callback managers, one for each LLM Run
corresponding to each inner message list.
"""
if messages and isinstance(messages[0], MessageV1Types):
run_id_ = run_id if run_id is not None else uuid.uuid4()
inline_tasks = []
non_inline_tasks = []
for handler in self.handlers:
task = ahandle_event(
[handler],
"on_chat_model_start",
"ignore_chat_model",
serialized,
messages,
run_id=run_id_,
parent_run_id=self.parent_run_id,
tags=self.tags,
metadata=self.metadata,
**kwargs,
)
if handler.run_inline:
inline_tasks.append(task)
else:
non_inline_tasks.append(task)
managers = [
AsyncCallbackManagerForLLMRun(
run_id=run_id_,
handlers=self.handlers,
inheritable_handlers=self.inheritable_handlers,
parent_run_id=self.parent_run_id,
tags=self.tags,
inheritable_tags=self.inheritable_tags,
metadata=self.metadata,
inheritable_metadata=self.inheritable_metadata,
)
]
# Run inline tasks sequentially
for task in inline_tasks:
await task
# Run non-inline tasks concurrently
if non_inline_tasks:
await asyncio.gather(*non_inline_tasks)
return managers
inline_tasks = []
non_inline_tasks = []
managers = []
for message_list in messages:
if run_id is not None:
run_id_ = run_id

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
import sys
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Union
from typing_extensions import override
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.messages import BaseMessage
from langchain_core.outputs import LLMResult
from langchain_core.v1.messages import AIMessage, MessageV1
class StreamingStdOutCallbackHandler(BaseCallbackHandler):
@@ -32,7 +33,7 @@ class StreamingStdOutCallbackHandler(BaseCallbackHandler):
def on_chat_model_start(
self,
serialized: dict[str, Any],
messages: list[list[BaseMessage]],
messages: Union[list[list[BaseMessage]], list[MessageV1]],
**kwargs: Any,
) -> None:
"""Run when LLM starts running.
@@ -54,7 +55,7 @@ class StreamingStdOutCallbackHandler(BaseCallbackHandler):
sys.stdout.write(token)
sys.stdout.flush()
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
def on_llm_end(self, response: Union[LLMResult, AIMessage], **kwargs: Any) -> None:
"""Run when LLM ends running.
Args:

View File

@@ -4,14 +4,16 @@ import threading
from collections.abc import Generator
from contextlib import contextmanager
from contextvars import ContextVar
from typing import Any, Optional
from typing import Any, Optional, Union
from typing_extensions import override
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.messages import AIMessage
from langchain_core.messages.ai import UsageMetadata, add_usage
from langchain_core.messages.utils import convert_from_v1_message
from langchain_core.outputs import ChatGeneration, LLMResult
from langchain_core.v1.messages import AIMessage as AIMessageV1
class UsageMetadataCallbackHandler(BaseCallbackHandler):
@@ -58,9 +60,17 @@ class UsageMetadataCallbackHandler(BaseCallbackHandler):
return str(self.usage_metadata)
@override
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
def on_llm_end(
self, response: Union[LLMResult, AIMessageV1], **kwargs: Any
) -> None:
"""Collect token usage."""
# Check for usage_metadata (langchain-core >= 0.2.2)
if isinstance(response, AIMessageV1):
response = LLMResult(
generations=[
[ChatGeneration(message=convert_from_v1_message(response))]
]
)
try:
generation = response.generations[0][0]
except IndexError:

View File

@@ -1,8 +1,10 @@
import copy
import re
from collections.abc import Sequence
from typing import Optional
from langchain_core.messages import BaseMessage
from langchain_core.v1.messages import MessageV1
def _is_openai_data_block(block: dict) -> bool:
@@ -138,3 +140,37 @@ def _normalize_messages(messages: Sequence[BaseMessage]) -> list[BaseMessage]:
formatted_messages.append(formatted_message)
return formatted_messages
def _normalize_messages_v1(messages: Sequence[MessageV1]) -> list[MessageV1]:
"""Extend support for message formats.
Chat models implement support for images in OpenAI Chat Completions format, as well
as other multimodal data as standard data blocks. This function extends support to
audio and file data in OpenAI Chat Completions format by converting them to standard
data blocks.
"""
formatted_messages = []
for message in messages:
formatted_message = message
if isinstance(message.content, list):
for idx, block in enumerate(message.content):
if (
isinstance(block, dict)
# Subset to (PDF) files and audio, as most relevant chat models
# support images in OAI format (and some may not yet support the
# standard data block format)
and block.get("type") in {"file", "input_audio"}
and _is_openai_data_block(block) # type: ignore[arg-type]
):
if formatted_message is message:
formatted_message = copy.copy(message)
# Also shallow-copy content
formatted_message.content = list(formatted_message.content)
formatted_message.content[idx] = ( # type: ignore[call-overload]
_convert_openai_format_to_data_block(block) # type: ignore[arg-type]
)
formatted_messages.append(formatted_message)
return formatted_messages

View File

@@ -31,6 +31,7 @@ from langchain_core.messages import (
from langchain_core.prompt_values import PromptValue
from langchain_core.runnables import Runnable, RunnableSerializable
from langchain_core.utils import get_pydantic_field_names
from langchain_core.v1.messages import AIMessage as AIMessageV1
if TYPE_CHECKING:
from langchain_core.outputs import LLMResult
@@ -85,7 +86,9 @@ def _get_token_ids_default_method(text: str) -> list[int]:
LanguageModelInput = Union[PromptValue, str, Sequence[MessageLikeRepresentation]]
LanguageModelOutput = Union[BaseMessage, str]
LanguageModelLike = Runnable[LanguageModelInput, LanguageModelOutput]
LanguageModelOutputVar = TypeVar("LanguageModelOutputVar", BaseMessage, str)
LanguageModelOutputVar = TypeVar(
"LanguageModelOutputVar", BaseMessage, str, AIMessageV1
)
def _get_verbosity() -> bool:

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