From 3ba0a662cca1612eeb84c9aa6103df36dfe83e43 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 16 Aug 2024 09:20:39 -0700 Subject: [PATCH 1/3] ci[minor]: Add validate notebooks GH action (#321) * ci[minor]: Add validate notebooks GH action * update cmd to look inside examples too * cr * add examples package.json * cr --- .github/workflows/validate-new-notebooks.yml | 61 ++ examples/package.json | 24 + langgraph/package.json | 2 +- package.json | 3 +- yarn.lock | 916 +++++++++++++++++-- 5 files changed, 953 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/validate-new-notebooks.yml create mode 100644 examples/package.json diff --git a/.github/workflows/validate-new-notebooks.yml b/.github/workflows/validate-new-notebooks.yml new file mode 100644 index 00000000..b1dc31f0 --- /dev/null +++ b/.github/workflows/validate-new-notebooks.yml @@ -0,0 +1,61 @@ +name: Validate new notebooks + +# If another push to the same PR or branch happens while this workflow is still running, +# cancel the earlier run in favor of the next run. +# +# There's no point in testing an outdated version of the code. GitHub only allows +# a limited number of job runners to be active at the same time, so it's better to cancel +# pointless jobs early so that more useful jobs can run sooner. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - main + pull_request: + branches: + - main + paths: + - 'docs/docs/**' + - 'examples/**' + workflow_dispatch: + +jobs: + validate-new-notebooks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "yarn" + - name: Install dependencies + run: yarn install --immutable + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v44 + - name: Check for new or modified notebooks + id: check_notebooks + run: | + notebooks=$(echo '${{ steps.changed-files.outputs.all_changed_files }}' | tr ' ' '\n' | grep -E '^(docs/docs|examples)/.*\.ipynb$' || true) + echo "Affected notebooks: $notebooks" + echo "has_affected_notebooks=$([ -n "$notebooks" ] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT + - name: Build LangGraph + if: steps.check_notebooks.outputs.has_affected_notebooks == 'true' + run: yarn build + - name: Validate affected notebooks + if: steps.check_notebooks.outputs.has_affected_notebooks == 'true' + run: | + notebooks=$(echo '${{ steps.changed-files.outputs.all_changed_files }}' | tr ' ' '\n' | grep -E '^(docs/docs|examples)/.*\.ipynb$' || true) + if [ -n "$notebooks" ]; then + cd ./langgraph + for notebook in $notebooks; do + absolute_path="$GITHUB_WORKSPACE/$notebook" + yarn notebook_validate "$absolute_path" + done + else + echo "No notebooks to validate." + fi diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 00000000..c50e0be2 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,24 @@ +{ + "name": "examples", + "description": "Dependencies for LangGraph examples.", + "devDependencies": { + "@langchain/anthropic": "^0.2.15", + "@langchain/cloudflare": "^0.0.7", + "@langchain/community": "^0.2.27", + "@langchain/core": "^0.2.24", + "@langchain/groq": "^0.0.16", + "@langchain/langgraph": "workspace:*", + "@langchain/mistralai": "^0.0.28", + "@langchain/openai": "^0.2.6", + "@xenova/transformers": "2.17.2", + "cheerio": "^1.0.0", + "chromadb": "^1.8.1", + "d3": "^7.9.0", + "dotenv": "^16.4.5", + "langchain": "^0.2.16", + "tslab": "^1.0.22", + "uuid": "^10.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.23.2" + } +} diff --git a/langgraph/package.json b/langgraph/package.json index 75de370b..011568c7 100644 --- a/langgraph/package.json +++ b/langgraph/package.json @@ -39,7 +39,7 @@ "@langchain/anthropic": "^0.2.12", "@langchain/community": "^0.2.25", "@langchain/openai": "^0.2.4", - "@langchain/scripts": "^0.0.19", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/package.json b/package.json index cb3180b6..56fdbb92 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "url": "git@github.com:langchain-ai/langgraphjs.git" }, "workspaces": [ - "langgraph" + "langgraph", + "examples" ], "scripts": { "build": "turbo run build", diff --git a/yarn.lock b/yarn.lock index b31888f9..d7e28453 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,6 +49,21 @@ __metadata: languageName: node linkType: hard +"@anthropic-ai/sdk@npm:^0.25.2": + version: 0.25.2 + resolution: "@anthropic-ai/sdk@npm:0.25.2" + dependencies: + "@types/node": ^18.11.18 + "@types/node-fetch": ^2.6.4 + abort-controller: ^3.0.0 + agentkeepalive: ^4.2.1 + form-data-encoder: 1.7.2 + formdata-node: ^4.3.2 + node-fetch: ^2.6.7 + checksum: b38f6a43f6f678e49f1b53226cc55654c23cf0fd1902cf3fcf98c0ae78f4c229518964f4cb31bc39da41925c806e7f4cc7ec14c511d387f07db3136b111bc744 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.24.1, @babel/code-frame@npm:^7.24.2": version: 7.24.2 resolution: "@babel/code-frame@npm:7.24.2" @@ -443,6 +458,13 @@ __metadata: languageName: node linkType: hard +"@cloudflare/ai@npm:1.0.47": + version: 1.0.47 + resolution: "@cloudflare/ai@npm:1.0.47" + checksum: a9e371f789256bb5795c0cb60d975749b07d6cb50141b997da6b5c94380ddf18e851cba518e246414fcab4239d8f4438c5fe29172e6f06c136be3be19a3b65e0 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.19.12": version: 0.19.12 resolution: "@esbuild/aix-ppc64@npm:0.19.12" @@ -1027,6 +1049,35 @@ __metadata: languageName: node linkType: hard +"@langchain/anthropic@npm:^0.2.15": + version: 0.2.15 + resolution: "@langchain/anthropic@npm:0.2.15" + dependencies: + "@anthropic-ai/sdk": ^0.25.2 + "@langchain/core": ">=0.2.21 <0.3.0" + fast-xml-parser: ^4.4.1 + zod: ^3.22.4 + zod-to-json-schema: ^3.22.4 + checksum: 06e36bee0451884be2e53c06942666f32701568fa83b329634169f4a1dd409f1659761c6255ac3b042f20f2ff5e63e7d4f04110fc1e908b00c3136c900474ba0 + languageName: node + linkType: hard + +"@langchain/cloudflare@npm:^0.0.7": + version: 0.0.7 + resolution: "@langchain/cloudflare@npm:0.0.7" + dependencies: + "@cloudflare/ai": 1.0.47 + "@langchain/core": ">0.1.0 <0.3.0" + uuid: ^10.0.0 + peerDependencies: + "@langchain/langgraph": <0.1.0 + peerDependenciesMeta: + "@langchain/langgraph": + optional: true + checksum: 7bc5d07a49a9b81d16e3dd8ca17c84b675dae9dfcc3e1cc74118af2ca95d985bb104b98a31035b7b14319efb127279383d308b456f60c1632df239a7f016d818 + languageName: node + linkType: hard + "@langchain/community@npm:^0.2.25": version: 0.2.25 resolution: "@langchain/community@npm:0.2.25" @@ -1408,6 +1459,387 @@ __metadata: languageName: node linkType: hard +"@langchain/community@npm:^0.2.27": + version: 0.2.27 + resolution: "@langchain/community@npm:0.2.27" + dependencies: + "@langchain/core": ">=0.2.21 <0.3.0" + "@langchain/openai": ">=0.2.0 <0.3.0" + binary-extensions: ^2.2.0 + expr-eval: ^2.0.2 + flat: ^5.0.2 + js-yaml: ^4.1.0 + langchain: ~0.2.3 + langsmith: ~0.1.30 + uuid: ^10.0.0 + zod: ^3.22.3 + zod-to-json-schema: ^3.22.5 + peerDependencies: + "@aws-crypto/sha256-js": ^5.0.0 + "@aws-sdk/client-bedrock-agent-runtime": ^3.583.0 + "@aws-sdk/client-bedrock-runtime": ^3.422.0 + "@aws-sdk/client-dynamodb": ^3.310.0 + "@aws-sdk/client-kendra": ^3.352.0 + "@aws-sdk/client-lambda": ^3.310.0 + "@aws-sdk/client-s3": ^3.310.0 + "@aws-sdk/client-sagemaker-runtime": ^3.310.0 + "@aws-sdk/client-sfn": ^3.310.0 + "@aws-sdk/credential-provider-node": ^3.388.0 + "@azure/search-documents": ^12.0.0 + "@azure/storage-blob": ^12.15.0 + "@browserbasehq/sdk": "*" + "@clickhouse/client": ^0.2.5 + "@cloudflare/ai": "*" + "@datastax/astra-db-ts": ^1.0.0 + "@elastic/elasticsearch": ^8.4.0 + "@getmetal/metal-sdk": "*" + "@getzep/zep-cloud": ^1.0.6 + "@getzep/zep-js": ^0.9.0 + "@gomomento/sdk": ^1.51.1 + "@gomomento/sdk-core": ^1.51.1 + "@google-ai/generativelanguage": "*" + "@google-cloud/storage": ^6.10.1 || ^7.7.0 + "@gradientai/nodejs-sdk": ^1.2.0 + "@huggingface/inference": ^2.6.4 + "@langchain/langgraph": ~0.0.26 + "@layerup/layerup-security": ^1.5.12 + "@mendable/firecrawl-js": ^0.0.13 + "@mlc-ai/web-llm": 0.2.46 + "@mozilla/readability": "*" + "@neondatabase/serverless": "*" + "@notionhq/client": ^2.2.10 + "@opensearch-project/opensearch": "*" + "@pinecone-database/pinecone": "*" + "@planetscale/database": ^1.8.0 + "@premai/prem-sdk": ^0.3.25 + "@qdrant/js-client-rest": ^1.8.2 + "@raycast/api": ^1.55.2 + "@rockset/client": ^0.9.1 + "@smithy/eventstream-codec": ^2.0.5 + "@smithy/protocol-http": ^3.0.6 + "@smithy/signature-v4": ^2.0.10 + "@smithy/util-utf8": ^2.0.0 + "@spider-cloud/spider-client": ^0.0.21 + "@supabase/supabase-js": ^2.45.0 + "@tensorflow-models/universal-sentence-encoder": "*" + "@tensorflow/tfjs-converter": "*" + "@tensorflow/tfjs-core": "*" + "@upstash/ratelimit": ^1.1.3 + "@upstash/redis": ^1.20.6 + "@upstash/vector": ^1.1.1 + "@vercel/kv": ^0.2.3 + "@vercel/postgres": ^0.5.0 + "@writerai/writer-sdk": ^0.40.2 + "@xata.io/client": ^0.28.0 + "@xenova/transformers": ^2.17.2 + "@zilliz/milvus2-sdk-node": ">=2.3.5" + apify-client: ^2.7.1 + assemblyai: ^4.6.0 + better-sqlite3: ">=9.4.0 <12.0.0" + cassandra-driver: ^4.7.2 + cborg: ^4.1.1 + cheerio: ^1.0.0-rc.12 + chromadb: "*" + closevector-common: 0.1.3 + closevector-node: 0.1.6 + closevector-web: 0.1.6 + cohere-ai: "*" + convex: ^1.3.1 + couchbase: ^4.3.0 + crypto-js: ^4.2.0 + d3-dsv: ^2.0.0 + discord.js: ^14.14.1 + dria: ^0.0.3 + duck-duck-scrape: ^2.2.5 + epub2: ^3.0.1 + faiss-node: ^0.5.1 + firebase-admin: ^11.9.0 || ^12.0.0 + google-auth-library: "*" + googleapis: ^126.0.1 + hnswlib-node: ^3.0.0 + html-to-text: ^9.0.5 + ignore: ^5.2.0 + interface-datastore: ^8.2.11 + ioredis: ^5.3.2 + it-all: ^3.0.4 + jsdom: "*" + jsonwebtoken: ^9.0.2 + llmonitor: ^0.5.9 + lodash: ^4.17.21 + lunary: ^0.7.10 + mammoth: ^1.6.0 + mongodb: ">=5.2.0" + mysql2: ^3.9.8 + neo4j-driver: "*" + node-llama-cpp: "*" + notion-to-md: ^3.1.0 + officeparser: ^4.0.4 + pdf-parse: 1.1.1 + pg: ^8.11.0 + pg-copy-streams: ^6.0.5 + pickleparser: ^0.2.1 + playwright: ^1.32.1 + portkey-ai: ^0.1.11 + puppeteer: "*" + redis: "*" + replicate: ^0.29.4 + sonix-speech-recognition: ^2.1.1 + srt-parser-2: ^1.2.3 + typeorm: ^0.3.20 + typesense: ^1.5.3 + usearch: ^1.1.1 + vectordb: ^0.1.4 + voy-search: 0.6.2 + weaviate-ts-client: "*" + web-auth-library: ^1.0.3 + ws: ^8.14.2 + youtube-transcript: ^1.0.6 + youtubei.js: ^9.1.0 + peerDependenciesMeta: + "@aws-crypto/sha256-js": + optional: true + "@aws-sdk/client-bedrock-agent-runtime": + optional: true + "@aws-sdk/client-bedrock-runtime": + optional: true + "@aws-sdk/client-dynamodb": + optional: true + "@aws-sdk/client-kendra": + optional: true + "@aws-sdk/client-lambda": + optional: true + "@aws-sdk/client-s3": + optional: true + "@aws-sdk/client-sagemaker-runtime": + optional: true + "@aws-sdk/client-sfn": + optional: true + "@aws-sdk/credential-provider-node": + optional: true + "@azure/search-documents": + optional: true + "@azure/storage-blob": + optional: true + "@browserbasehq/sdk": + optional: true + "@clickhouse/client": + optional: true + "@cloudflare/ai": + optional: true + "@datastax/astra-db-ts": + optional: true + "@elastic/elasticsearch": + optional: true + "@getmetal/metal-sdk": + optional: true + "@getzep/zep-cloud": + optional: true + "@getzep/zep-js": + optional: true + "@gomomento/sdk": + optional: true + "@gomomento/sdk-core": + optional: true + "@google-ai/generativelanguage": + optional: true + "@google-cloud/storage": + optional: true + "@gradientai/nodejs-sdk": + optional: true + "@huggingface/inference": + optional: true + "@langchain/langgraph": + optional: true + "@layerup/layerup-security": + optional: true + "@mendable/firecrawl-js": + optional: true + "@mlc-ai/web-llm": + optional: true + "@mozilla/readability": + optional: true + "@neondatabase/serverless": + optional: true + "@notionhq/client": + optional: true + "@opensearch-project/opensearch": + optional: true + "@pinecone-database/pinecone": + optional: true + "@planetscale/database": + optional: true + "@premai/prem-sdk": + optional: true + "@qdrant/js-client-rest": + optional: true + "@raycast/api": + optional: true + "@rockset/client": + optional: true + "@smithy/eventstream-codec": + optional: true + "@smithy/protocol-http": + optional: true + "@smithy/signature-v4": + optional: true + "@smithy/util-utf8": + optional: true + "@spider-cloud/spider-client": + optional: true + "@supabase/supabase-js": + optional: true + "@tensorflow-models/universal-sentence-encoder": + optional: true + "@tensorflow/tfjs-converter": + optional: true + "@tensorflow/tfjs-core": + optional: true + "@upstash/ratelimit": + optional: true + "@upstash/redis": + optional: true + "@upstash/vector": + optional: true + "@vercel/kv": + optional: true + "@vercel/postgres": + optional: true + "@writerai/writer-sdk": + optional: true + "@xata.io/client": + optional: true + "@xenova/transformers": + optional: true + "@zilliz/milvus2-sdk-node": + optional: true + apify-client: + optional: true + assemblyai: + optional: true + better-sqlite3: + optional: true + cassandra-driver: + optional: true + cborg: + optional: true + cheerio: + optional: true + chromadb: + optional: true + closevector-common: + optional: true + closevector-node: + optional: true + closevector-web: + optional: true + cohere-ai: + optional: true + convex: + optional: true + couchbase: + optional: true + crypto-js: + optional: true + d3-dsv: + optional: true + discord.js: + optional: true + dria: + optional: true + duck-duck-scrape: + optional: true + epub2: + optional: true + faiss-node: + optional: true + firebase-admin: + optional: true + google-auth-library: + optional: true + googleapis: + optional: true + hnswlib-node: + optional: true + html-to-text: + optional: true + ignore: + optional: true + interface-datastore: + optional: true + ioredis: + optional: true + it-all: + optional: true + jsdom: + optional: true + jsonwebtoken: + optional: true + llmonitor: + optional: true + lodash: + optional: true + lunary: + optional: true + mammoth: + optional: true + mongodb: + optional: true + mysql2: + optional: true + neo4j-driver: + optional: true + node-llama-cpp: + optional: true + notion-to-md: + optional: true + officeparser: + optional: true + pdf-parse: + optional: true + pg: + optional: true + pg-copy-streams: + optional: true + pickleparser: + optional: true + playwright: + optional: true + portkey-ai: + optional: true + puppeteer: + optional: true + redis: + optional: true + replicate: + optional: true + sonix-speech-recognition: + optional: true + srt-parser-2: + optional: true + typeorm: + optional: true + typesense: + optional: true + usearch: + optional: true + vectordb: + optional: true + voy-search: + optional: true + weaviate-ts-client: + optional: true + web-auth-library: + optional: true + ws: + optional: true + youtube-transcript: + optional: true + youtubei.js: + optional: true + checksum: 8dd1a0c249dd7f9d8f667b4f3af2930a3be59c6dfb5ce22eed3c4425e7c3cacc791894bf3e3131c2c18a92f3c6809d613ec354ab4a68edb6228ca9b06e91dba1 + languageName: node + linkType: hard + "@langchain/core@npm:>0.1.0 <0.3.0, @langchain/core@npm:>=0.2.11 <0.3.0, @langchain/core@npm:>=0.2.16 <0.3.0, @langchain/core@npm:>=0.2.20 <0.3.0, @langchain/core@npm:>=0.2.21 <0.3.0": version: 0.2.21 resolution: "@langchain/core@npm:0.2.21" @@ -1427,7 +1859,39 @@ __metadata: languageName: node linkType: hard -"@langchain/langgraph@workspace:langgraph": +"@langchain/core@npm:>0.1.0 <0.3.0, @langchain/core@npm:^0.2.24": + version: 0.2.24 + resolution: "@langchain/core@npm:0.2.24" + dependencies: + ansi-styles: ^5.0.0 + camelcase: 6 + decamelize: 1.2.0 + js-tiktoken: ^1.0.12 + langsmith: ~0.1.39 + mustache: ^4.2.0 + p-queue: ^6.6.2 + p-retry: 4 + uuid: ^10.0.0 + zod: ^3.22.4 + zod-to-json-schema: ^3.22.3 + checksum: 8f9dfa746750653a4fbfcdeca3d662c4461c085d97cfea7b68b310551e256c42496e5d364aabd10a641b53f96876ee0ffd072ee6ac844b0e89e21ad3dc4906b0 + languageName: node + linkType: hard + +"@langchain/groq@npm:^0.0.16": + version: 0.0.16 + resolution: "@langchain/groq@npm:0.0.16" + dependencies: + "@langchain/core": ">=0.2.21 <0.3.0" + "@langchain/openai": ~0.2.6 + groq-sdk: ^0.5.0 + zod: ^3.22.4 + zod-to-json-schema: ^3.22.5 + checksum: 734b933608ac6f6eaaddb3e0587f4345d8f49053b2a54a0cc2cc673952187b8b491c5e2fbe423a319ee9c5e5844654504d54e28e6885024630f0a8498c1e76ec + languageName: node + linkType: hard + +"@langchain/langgraph@workspace:*, @langchain/langgraph@workspace:langgraph": version: 0.0.0-use.local resolution: "@langchain/langgraph@workspace:langgraph" dependencies: @@ -1436,7 +1900,7 @@ __metadata: "@langchain/community": ^0.2.25 "@langchain/core": ">=0.2.20 <0.3.0" "@langchain/openai": ^0.2.4 - "@langchain/scripts": ^0.0.19 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -1476,6 +1940,19 @@ __metadata: languageName: unknown linkType: soft +"@langchain/mistralai@npm:^0.0.28": + version: 0.0.28 + resolution: "@langchain/mistralai@npm:0.0.28" + dependencies: + "@langchain/core": ">=0.2.21 <0.3.0" + "@mistralai/mistralai": ^0.4.0 + uuid: ^10.0.0 + zod: ^3.22.4 + zod-to-json-schema: ^3.22.4 + checksum: 90b10fa6d73f47ec616a2135fd78a5fc619ad0d9ac79c9e371d1b5160cd284a3efd4d6fd5beeb74a33220ee15db436b91c1f60ac722b3e52a2798dbd7ebb0711 + languageName: node + linkType: hard + "@langchain/openai@npm:>=0.1.0 <0.3.0, @langchain/openai@npm:^0.2.4": version: 0.2.4 resolution: "@langchain/openai@npm:0.2.4" @@ -1489,22 +1966,38 @@ __metadata: languageName: node linkType: hard -"@langchain/scripts@npm:^0.0.19": - version: 0.0.19 - resolution: "@langchain/scripts@npm:0.0.19" +"@langchain/openai@npm:>=0.2.0 <0.3.0, @langchain/openai@npm:^0.2.6, @langchain/openai@npm:~0.2.6": + version: 0.2.6 + resolution: "@langchain/openai@npm:0.2.6" + dependencies: + "@langchain/core": ">=0.2.21 <0.3.0" + js-tiktoken: ^1.0.12 + openai: ^4.55.0 + zod: ^3.22.4 + zod-to-json-schema: ^3.22.3 + checksum: 9b216cbc289f87e90dcb58d494b070b30e8bb0bb1c819b0cb1b2f5351a31a9af879895fe011bc0c3c89922cf93be1bbf700cee97590339202adedd65521bff6a + languageName: node + linkType: hard + +"@langchain/scripts@npm:^0.0.21": + version: 0.0.21 + resolution: "@langchain/scripts@npm:0.0.21" dependencies: "@rollup/wasm-node": ^4.19.0 axios: ^1.6.7 commander: ^11.1.0 glob: ^10.3.10 - rimraf: ^6.0.1 + lodash: ^4.17.21 + readline: ^1.3.0 + rimraf: ^5.0.1 rollup: ^4.5.2 ts-morph: ^21.0.1 typescript: ^5.4.5 bin: lc-build: bin/build.js lc_build_v2: bin/build_v2.js - checksum: ef58af8139ee701475bc602353ae132655ee00f0c3366a7d54dafad52195026de8ee2c378a66a6ab5b2c4e83494b553c6ab5f98353d3bd48502bf6e50b097729 + notebook_validate: bin/validate_notebook.js + checksum: 4653f3e5866d5c206013eb3d556bd11cc410e5ad118ed62f3794a3a63de8fc56bab362c537700387a03cf5aee018fc117413c3d6048d50ed9a54adc07006d13c languageName: node linkType: hard @@ -1518,6 +2011,15 @@ __metadata: languageName: node linkType: hard +"@mistralai/mistralai@npm:^0.4.0": + version: 0.4.0 + resolution: "@mistralai/mistralai@npm:0.4.0" + dependencies: + node-fetch: ^2.6.7 + checksum: 1b03fc0b55164c02e5fb29fb2d09ebe4ad44346fc313f7fb3ab09e48f73f975763d1ac9654098d433ea17d7caa20654b2b15510822276acc9fa46db461a254a6 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -2914,7 +3416,7 @@ __metadata: languageName: node linkType: hard -"@xenova/transformers@npm:^2.17.2": +"@xenova/transformers@npm:2.17.2, @xenova/transformers@npm:^2.17.2": version: 2.17.2 resolution: "@xenova/transformers@npm:2.17.2" dependencies: @@ -3727,6 +4229,27 @@ __metadata: languageName: node linkType: hard +"chromadb@npm:^1.8.1": + version: 1.9.1 + resolution: "chromadb@npm:1.9.1" + dependencies: + cliui: ^8.0.1 + isomorphic-fetch: ^3.0.0 + peerDependencies: + "@google/generative-ai": ^0.1.1 + cohere-ai: ^5.0.0 || ^6.0.0 || ^7.0.0 + openai: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + "@google/generative-ai": + optional: true + cohere-ai: + optional: true + openai: + optional: true + checksum: 9903891ee5811ec5a50d9c94057ea54d8f09d528749478f6ce1cbed4831544788f96d2c1301f7c3453f4ac5a2dd38380f1383686003d80a09e4e7dbc0d8b11f5 + languageName: node + linkType: hard + "ci-info@npm:^3.2.0": version: 3.9.0 resolution: "ci-info@npm:3.9.0" @@ -4671,7 +5194,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.3.1": +"dotenv@npm:^16.3.1, dotenv@npm:^16.4.5": version: 16.4.5 resolution: "dotenv@npm:16.4.5" checksum: 301a12c3d44fd49888b74eb9ccf9f07a1f5df43f489e7fcb89647a2edcd84c42d6bc349dc8df099cd18f07c35c7b04685c1a4f3e6a6a9e6b30f8d48c15b7f49c @@ -5506,6 +6029,31 @@ __metadata: languageName: node linkType: hard +"examples@workspace:examples": + version: 0.0.0-use.local + resolution: "examples@workspace:examples" + dependencies: + "@langchain/anthropic": ^0.2.15 + "@langchain/cloudflare": ^0.0.7 + "@langchain/community": ^0.2.27 + "@langchain/core": ^0.2.24 + "@langchain/groq": ^0.0.16 + "@langchain/langgraph": "workspace:*" + "@langchain/mistralai": ^0.0.28 + "@langchain/openai": ^0.2.6 + "@xenova/transformers": 2.17.2 + cheerio: ^1.0.0 + chromadb: ^1.8.1 + d3: ^7.9.0 + dotenv: ^16.4.5 + langchain: ^0.2.16 + tslab: ^1.0.22 + uuid: ^10.0.0 + zod: ^3.23.8 + zod-to-json-schema: ^3.23.2 + languageName: unknown + linkType: soft + "execa@npm:8.0.1": version: 8.0.1 resolution: "execa@npm:8.0.1" @@ -5640,7 +6188,7 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.3.5": +"fast-xml-parser@npm:^4.3.5, fast-xml-parser@npm:^4.4.1": version: 4.4.1 resolution: "fast-xml-parser@npm:4.4.1" dependencies: @@ -6065,19 +6613,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:^11.0.0": - version: 11.0.0 - resolution: "glob@npm:11.0.0" +"glob@npm:^10.3.7": + version: 10.4.5 + resolution: "glob@npm:10.4.5" dependencies: foreground-child: ^3.1.0 - jackspeak: ^4.0.1 - minimatch: ^10.0.0 + jackspeak: ^3.1.2 + minimatch: ^9.0.4 minipass: ^7.1.2 package-json-from-dist: ^1.0.0 - path-scurry: ^2.0.0 + path-scurry: ^1.11.1 bin: glob: dist/esm/bin.mjs - checksum: 8a2dd914d5776987be5244624d9491bbcaf19f2387e06783737003ff696ebfd2264190c47014f8709c1c02d8bc892f17660cf986c587b107e194c0a3151ab333 + checksum: 0bc725de5e4862f9f387fd0f2b274baf16850dcd2714502ccf471ee401803997983e2c05590cb65f9675a3c6f2a58e7a53f9e365704108c6ad3cbf1d60934c4a languageName: node linkType: hard @@ -6206,6 +6754,22 @@ __metadata: languageName: node linkType: hard +"groq-sdk@npm:^0.5.0": + version: 0.5.0 + resolution: "groq-sdk@npm:0.5.0" + dependencies: + "@types/node": ^18.11.18 + "@types/node-fetch": ^2.6.4 + abort-controller: ^3.0.0 + agentkeepalive: ^4.2.1 + form-data-encoder: 1.7.2 + formdata-node: ^4.3.2 + node-fetch: ^2.6.7 + web-streams-polyfill: ^3.2.1 + checksum: 051ca56e99e4a2440080943c831b109687dd346b24155d3f085113df1ad0639cb95724c14a05611f7314d340db8bf342af425eb11905c97bc6a6948cd7262f04 + languageName: node + linkType: hard + "guid-typescript@npm:^1.0.9": version: 1.0.9 resolution: "guid-typescript@npm:1.0.9" @@ -6913,6 +7477,16 @@ __metadata: languageName: node linkType: hard +"isomorphic-fetch@npm:^3.0.0": + version: 3.0.0 + resolution: "isomorphic-fetch@npm:3.0.0" + dependencies: + node-fetch: ^2.6.1 + whatwg-fetch: ^3.4.1 + checksum: e5ab79a56ce5af6ddd21265f59312ad9a4bc5a72cebc98b54797b42cb30441d5c5f8d17c5cd84a99e18101c8af6f90c081ecb8d12fd79e332be1778d58486d75 + languageName: node + linkType: hard + "issue-parser@npm:7.0.1": version: 7.0.1 resolution: "issue-parser@npm:7.0.1" @@ -7004,16 +7578,16 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^4.0.1": - version: 4.0.1 - resolution: "jackspeak@npm:4.0.1" +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" dependencies: "@isaacs/cliui": ^8.0.2 "@pkgjs/parseargs": ^0.11.0 dependenciesMeta: "@pkgjs/parseargs": optional: true - checksum: 7989d19eddeff0631ef653df413e26290db77dc3791438bd12b56bed1c0b24d5d535fdfec13cf35775cd5b47f8ee57d36fd0bceaf2df672b1f523533fd4184cc + checksum: be31027fc72e7cc726206b9f560395604b82e0fddb46c4cbf9f97d049bcef607491a5afc0699612eaa4213ca5be8fd3e1e7cd187b3040988b65c9489838a7c00 languageName: node linkType: hard @@ -7784,6 +8358,206 @@ __metadata: languageName: node linkType: hard +"langchain@npm:^0.2.16": + version: 0.2.16 + resolution: "langchain@npm:0.2.16" + dependencies: + "@langchain/core": ">=0.2.21 <0.3.0" + "@langchain/openai": ">=0.1.0 <0.3.0" + "@langchain/textsplitters": ~0.0.0 + binary-extensions: ^2.2.0 + js-tiktoken: ^1.0.12 + js-yaml: ^4.1.0 + jsonpointer: ^5.0.1 + langsmith: ~0.1.40 + openapi-types: ^12.1.3 + p-retry: 4 + uuid: ^10.0.0 + yaml: ^2.2.1 + zod: ^3.22.4 + zod-to-json-schema: ^3.22.3 + peerDependencies: + "@aws-sdk/client-s3": "*" + "@aws-sdk/client-sagemaker-runtime": "*" + "@aws-sdk/client-sfn": "*" + "@aws-sdk/credential-provider-node": "*" + "@azure/storage-blob": "*" + "@browserbasehq/sdk": "*" + "@gomomento/sdk": "*" + "@gomomento/sdk-core": "*" + "@gomomento/sdk-web": ^1.51.1 + "@langchain/anthropic": "*" + "@langchain/aws": "*" + "@langchain/cohere": "*" + "@langchain/community": "*" + "@langchain/google-genai": "*" + "@langchain/google-vertexai": "*" + "@langchain/groq": "*" + "@langchain/mistralai": "*" + "@langchain/ollama": "*" + "@mendable/firecrawl-js": "*" + "@notionhq/client": "*" + "@pinecone-database/pinecone": "*" + "@supabase/supabase-js": "*" + "@vercel/kv": "*" + "@xata.io/client": "*" + apify-client: "*" + assemblyai: "*" + axios: "*" + cheerio: "*" + chromadb: "*" + convex: "*" + couchbase: "*" + d3-dsv: "*" + epub2: "*" + fast-xml-parser: "*" + handlebars: ^4.7.8 + html-to-text: "*" + ignore: "*" + ioredis: "*" + jsdom: "*" + mammoth: "*" + mongodb: "*" + node-llama-cpp: "*" + notion-to-md: "*" + officeparser: "*" + pdf-parse: "*" + peggy: ^3.0.2 + playwright: "*" + puppeteer: "*" + pyodide: ^0.24.1 + redis: "*" + sonix-speech-recognition: "*" + srt-parser-2: "*" + typeorm: "*" + weaviate-ts-client: "*" + web-auth-library: "*" + ws: "*" + youtube-transcript: "*" + youtubei.js: "*" + peerDependenciesMeta: + "@aws-sdk/client-s3": + optional: true + "@aws-sdk/client-sagemaker-runtime": + optional: true + "@aws-sdk/client-sfn": + optional: true + "@aws-sdk/credential-provider-node": + optional: true + "@azure/storage-blob": + optional: true + "@browserbasehq/sdk": + optional: true + "@gomomento/sdk": + optional: true + "@gomomento/sdk-core": + optional: true + "@gomomento/sdk-web": + optional: true + "@langchain/anthropic": + optional: true + "@langchain/aws": + optional: true + "@langchain/cohere": + optional: true + "@langchain/community": + optional: true + "@langchain/google-genai": + optional: true + "@langchain/google-vertexai": + optional: true + "@langchain/groq": + optional: true + "@langchain/mistralai": + optional: true + "@langchain/ollama": + optional: true + "@mendable/firecrawl-js": + optional: true + "@notionhq/client": + optional: true + "@pinecone-database/pinecone": + optional: true + "@supabase/supabase-js": + optional: true + "@vercel/kv": + optional: true + "@xata.io/client": + optional: true + apify-client: + optional: true + assemblyai: + optional: true + axios: + optional: true + cheerio: + optional: true + chromadb: + optional: true + convex: + optional: true + couchbase: + optional: true + d3-dsv: + optional: true + epub2: + optional: true + faiss-node: + optional: true + fast-xml-parser: + optional: true + handlebars: + optional: true + html-to-text: + optional: true + ignore: + optional: true + ioredis: + optional: true + jsdom: + optional: true + mammoth: + optional: true + mongodb: + optional: true + node-llama-cpp: + optional: true + notion-to-md: + optional: true + officeparser: + optional: true + pdf-parse: + optional: true + peggy: + optional: true + playwright: + optional: true + puppeteer: + optional: true + pyodide: + optional: true + redis: + optional: true + sonix-speech-recognition: + optional: true + srt-parser-2: + optional: true + typeorm: + optional: true + weaviate-ts-client: + optional: true + web-auth-library: + optional: true + ws: + optional: true + youtube-transcript: + optional: true + youtubei.js: + optional: true + checksum: ff3f0df688fc89f76bcbfc565ba96758462207438fc54ec7b7142c7cb6f70dada6fb9ec61844ca9c161ec520eb4e1e489c20687054174125ebda978217bf3372 + languageName: node + linkType: hard + "langchain@npm:~0.2.3": version: 0.2.13 resolution: "langchain@npm:0.2.13" @@ -8163,7 +8937,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21": +"lodash@npm:4.17.21, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -8211,13 +8985,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.0.0": - version: 11.0.0 - resolution: "lru-cache@npm:11.0.0" - checksum: c29385f9369b1a566e1db9eda9a4b12f6507de906e5720ca12844dd775b7139c42b8e5837e7d5162bcc292ce4d3eecfa74ec2856c6afcc0caa2e3c9ea3a17f27 - languageName: node - linkType: hard - "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -8387,15 +9154,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.0": - version: 10.0.1 - resolution: "minimatch@npm:10.0.1" - dependencies: - brace-expansion: ^2.0.1 - checksum: f5b63c2f30606091a057c5f679b067f84a2cd0ffbd2dbc9143bda850afd353c7be81949ff11ae0c86988f07390eeca64efd7143ee05a0dab37f6c6b38a2ebb6c - languageName: node - linkType: hard - "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -8704,7 +9462,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -8978,6 +9736,28 @@ __metadata: languageName: node linkType: hard +"openai@npm:^4.55.0": + version: 4.55.9 + resolution: "openai@npm:4.55.9" + dependencies: + "@types/node": ^18.11.18 + "@types/node-fetch": ^2.6.4 + abort-controller: ^3.0.0 + agentkeepalive: ^4.2.1 + form-data-encoder: 1.7.2 + formdata-node: ^4.3.2 + node-fetch: ^2.6.7 + peerDependencies: + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + bin: + openai: bin/cli + checksum: 2d9e4bc33031b843a9bb20360217ec0aff5615b8ae52de1f9073b09ca955e4908054a9498b9d995038f27032568fbfef02c2d3f94a6ce39ee85d2ed0984ec940 + languageName: node + linkType: hard + "openapi-types@npm:^12.1.3": version: 12.1.3 resolution: "openapi-types@npm:12.1.3" @@ -9300,13 +10080,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.0": - version: 2.0.0 - resolution: "path-scurry@npm:2.0.0" +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" dependencies: - lru-cache: ^11.0.0 - minipass: ^7.1.2 - checksum: 9953ce3857f7e0796b187a7066eede63864b7e1dfc14bf0484249801a5ab9afb90d9a58fc533ebb1b552d23767df8aa6a2c6c62caf3f8a65f6ce336a97bbb484 + lru-cache: ^10.2.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: 890d5abcd593a7912dcce7cf7c6bf7a0b5648e3dee6caf0712c126ca0a65c7f3d7b9d769072a4d1baf370f61ce493ab5b038d59988688e0c5f3f646ee3c69023 languageName: node linkType: hard @@ -9820,15 +10600,14 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^6.0.1": - version: 6.0.1 - resolution: "rimraf@npm:6.0.1" +"rimraf@npm:^5.0.1": + version: 5.0.10 + resolution: "rimraf@npm:5.0.10" dependencies: - glob: ^11.0.0 - package-json-from-dist: ^1.0.0 + glob: ^10.3.7 bin: rimraf: dist/esm/bin.mjs - checksum: 8ba5b84131c1344e9417cb7e8c05d8368bb73cbe5dd4c1d5eb49fc0b558209781658d18c450460e30607d0b7865bb067482839a2f343b186b07ae87715837e66 + checksum: 50e27388dd2b3fa6677385fc1e2966e9157c89c86853b96d02e6915663a96b7ff4d590e14f6f70e90f9b554093aa5dbc05ac3012876be558c06a65437337bc05 languageName: node linkType: hard @@ -10700,6 +11479,25 @@ __metadata: languageName: node linkType: hard +"tslab@npm:^1.0.22": + version: 1.0.22 + resolution: "tslab@npm:1.0.22" + dependencies: + "@rollup/plugin-commonjs": ^13.0.0 + "@rollup/plugin-node-resolve": ^11.0.0 + "@rollup/plugin-replace": ^2.3.0 + "@tslab/typescript-for-tslab": 5.0.4 + "@types/node": ^18.16.2 + commander: ^10.0.0 + rollup: ^2.34.2 + semver: ^7.3.8 + zeromq: ^6.0.0-beta.16 + bin: + tslab: bin/tslab + checksum: 023a14f76f7fcf04710242e409747778a7a326f69e66ba767147abd28b5c4b3520122dbf575681a16b08aacecb2e18fc7a9d90aba4ad26883219fbf9a0b73b12 + languageName: node + linkType: hard + "tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" @@ -11150,6 +11948,13 @@ __metadata: languageName: node linkType: hard +"whatwg-fetch@npm:^3.4.1": + version: 3.6.20 + resolution: "whatwg-fetch@npm:3.6.20" + checksum: c58851ea2c4efe5c2235f13450f426824cf0253c1d45da28f45900290ae602a20aff2ab43346f16ec58917d5562e159cd691efa368354b2e82918c2146a519c5 + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -11391,6 +12196,15 @@ __metadata: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.23.2": + version: 3.23.2 + resolution: "zod-to-json-schema@npm:3.23.2" + peerDependencies: + zod: ^3.23.3 + checksum: 6dc87a6045f5dcca23d009f2e212f3f5dbb790b1e80488162560359f30aa2babb6a2ea8d44953e9193c6c923300c58e5ae157a9dc089d0bcf5e2437b3158ca1b + languageName: node + linkType: hard + "zod@npm:^3.22.3, zod@npm:^3.22.4, zod@npm:^3.23.8": version: 3.23.8 resolution: "zod@npm:3.23.8" From fd6233e010857e25340b01ada487f6b3616ccc3c Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 16 Aug 2024 15:20:37 -0700 Subject: [PATCH 2/3] ci[minor]: Add script & GH action to validate deno and node deps in sync (#327) * ci[minor]: Add script & GH action to validate deno and node deps in sync * cr * cr --- .github/workflows/validate-new-notebooks.yml | 20 ++++++ package.json | 2 +- scripts/validate_deps_sync.ts | 64 ++++++++++++++++++++ yarn.lock | 2 +- 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 scripts/validate_deps_sync.ts diff --git a/.github/workflows/validate-new-notebooks.yml b/.github/workflows/validate-new-notebooks.yml index b1dc31f0..f551a178 100644 --- a/.github/workflows/validate-new-notebooks.yml +++ b/.github/workflows/validate-new-notebooks.yml @@ -20,8 +20,28 @@ on: paths: - 'docs/docs/**' - 'examples/**' + - 'deno.json' workflow_dispatch: +env: + NODE_VERSION: "22.4.1" + +jobs: + validate-dep-files: + name: Validate Deno and Node dependencies in sync + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'yarn' + - name: Install dependencies + run: yarn install --immutable + - name: Validate + run: yarn tsx --experimental-wasm-modules ./scripts/validate_deps_sync.ts + jobs: validate-new-notebooks: runs-on: ubuntu-latest diff --git a/package.json b/package.json index 56fdbb92..729149ce 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "esm-hook": "^0.1.4", "readline": "^1.3.0", "release-it": "^17.6.0", - "semver": "^7.5.4", + "semver": "^7.6.3", "tslab": "^1.0.21", "tsx": "^4.7.0", "turbo": "canary", diff --git a/scripts/validate_deps_sync.ts b/scripts/validate_deps_sync.ts new file mode 100644 index 00000000..bb41e9e8 --- /dev/null +++ b/scripts/validate_deps_sync.ts @@ -0,0 +1,64 @@ +import fs from "fs"; +import semver from "semver"; + +type DenoJson = { + imports: Record; +}; + +type PackageJson = { + // Only adding the fields we care about + devDependencies: Record; +}; + +function main() { + const denoJson: DenoJson = JSON.parse(fs.readFileSync("deno.json", "utf-8")); + const packageJson: PackageJson = JSON.parse( + fs.readFileSync("examples/package.json", "utf-8") + ); + + // Parse the dependency names and versions from the deno.json file + const denoDeps = Object.entries(denoJson.imports).map(([name, version]) => { + let depName = name.endsWith("/") ? name.slice(0, -1) : name; + let depVersion = version + .replace(/^npm:\/?(.*)/g, "$1") + .replace(depName, ""); + depVersion = depVersion.replace(/@.*$/, "").replace(/\/$/, ""); + if (!depVersion || depVersion === "") { + depVersion = "latest"; + } + + // `latest` is not a valid semver, do not validate it + if (depVersion !== "latest" && !semver.valid(depVersion)) { + throw new Error(`Invalid version for ${depName}: ${depVersion}`); + } + return { name: depName, version: depVersion }; + }); + + // Match the dependencies to those in the `package.json` file, and + // use the `semver` package to verify the versions are compatible + denoDeps.forEach((denoDep) => { + if (!(denoDep.name in packageJson.devDependencies)) { + throw new Error( + `Dependency ${denoDep.name} is not in the package.json file` + ); + } + + const packageVersion = packageJson.devDependencies[denoDep.name]; + if (denoDep.version === "latest") { + // If the deno version is latest, we can not validate it. Assume it is correct + return; + } + const cleanedPackageJsonVersion = + semver.clean(packageVersion) ?? packageVersion; + if ( + cleanedPackageJsonVersion !== denoDep.version && + !semver.gte(cleanedPackageJsonVersion, denoDep.version) + ) { + throw new Error( + `Version mismatch for ${denoDep.name}: package.json version ${cleanedPackageJsonVersion} is less than deno.json version ${denoDep.version}` + ); + } + }); +} + +main(); diff --git a/yarn.lock b/yarn.lock index d7e28453..90e44576 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8779,7 +8779,7 @@ __metadata: esm-hook: ^0.1.4 readline: ^1.3.0 release-it: ^17.6.0 - semver: ^7.5.4 + semver: ^7.6.3 tslab: ^1.0.21 tsx: ^4.7.0 turbo: canary From b4ccbe29193939fcba807ae17f474965d50097d0 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 19 Aug 2024 18:11:22 -0700 Subject: [PATCH 3/3] docs[minor]: Update init syntax docs (#328) * docs[minor]: Update init syntax docs * start porting chat agent executor w func calling doc * finished porting * validated * agent executor refactor * nits * update branching how to * updated breakpoints doc * updated configuration doc * updated dynamically returning directly ntbk * updated edit graph state doc * update force calling a tool doc * updated human in the loop doc * Updated manage conversation history doc * Updated managing agent steps doc * update postgres notebook * update persistence notebook * updated stream tokens doc * updated stream updates doc * updated stream values doc * updated streaming tokens without langchain doc * updated subgraph docs * updated time travel guide * updated dealing with tool calling errors doc * updated use in web env guide * fix wait user input guide --- examples/agent_executor/base.ipynb | 424 +++++---- .../base.ipynb | 438 ++++----- examples/how-tos/branching.ipynb | 120 +-- examples/how-tos/breakpoints.ipynb | 121 ++- examples/how-tos/configuration.ipynb | 71 +- .../dynamically-returning-directly.ipynb | 40 +- examples/how-tos/edit-graph-state.ipynb | 324 +++---- .../how-tos/force-calling-a-tool-first.ipynb | 122 ++- examples/how-tos/human-in-the-loop.ipynb | 66 +- .../how-tos/manage-conversation-history.ipynb | 168 ++-- examples/how-tos/managing-agent-steps.ipynb | 68 +- examples/how-tos/persistence-postgres.ipynb | 46 +- examples/how-tos/persistence.ipynb | 123 +-- examples/how-tos/respond-in-format.ipynb | 36 +- examples/how-tos/stream-tokens.ipynb | 179 ++-- examples/how-tos/stream-updates.ipynb | 131 +-- examples/how-tos/stream-values.ipynb | 269 +++--- .../streaming-tokens-without-langchain.ipynb | 50 +- examples/how-tos/subgraph.ipynb | 121 ++- examples/how-tos/time-travel.ipynb | 460 ++++++---- examples/how-tos/tool-calling-errors.ipynb | 193 ++-- .../how-tos/use-in-web-environments.ipynb | 137 ++- examples/how-tos/wait-user-input.ipynb | 839 ++++++++++++------ examples/package.json | 6 +- package.json | 3 +- yarn.lock | 155 +++- 26 files changed, 2469 insertions(+), 2241 deletions(-) diff --git a/examples/agent_executor/base.ipynb b/examples/agent_executor/base.ipynb index 4b90b8d8..7eeddb4d 100644 --- a/examples/agent_executor/base.ipynb +++ b/examples/agent_executor/base.ipynb @@ -18,12 +18,12 @@ "id": "c0860511-03c2-49bb-937b-035f84142b7e", "metadata": {}, "source": [ - "## Setup¶\n", + "## Setup\n", "\n", "First we need to install the packages required\n", "\n", "```bash\n", - "yarn add langchain @langchain/openai @langchain/langgraph\n", + "yarn add @langchain/community @langchain/anthropic @langchain/langgraph @langchain/core\n", "```" ] }, @@ -32,11 +32,11 @@ "id": "5f4179ce-48fa-4aaf-a5a1-027b5229be1a", "metadata": {}, "source": [ - "Next, we need to set API keys for OpenAI (the LLM we will use) and Tavily (the\n", + "Next, we need to set API keys for Anthropic (the LLM we will use) and Tavily (the\n", "search tool we will use)\n", "\n", "```bash\n", - "export OPENAI_API_KEY=\n", + "export ANTHROPIC_API_KEY=\n", "export TAVILY_API_KEY=\n", "```" ] @@ -58,98 +58,56 @@ }, { "cell_type": "markdown", - "id": "5dace4a9-7c9e-4da2-bf7b-e58d0d05581e", + "id": "972e58b3-fe3c-449d-b3c4-8fa2217afd07", "metadata": {}, "source": [ - "## Create the LangChain agent\n", + "## Define the graph schema\n", "\n", - "First, we will create the LangChain agent. For more information on LangChain\n", - "agents, see [this documentation](https://js.langchain.com/docs/modules/agents/)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "fd5db1ef", - "metadata": {}, - "outputs": [], - "source": [ - "// Deno.env.set(\"OPENAI_API_KEY\", \"YOUR_API_KEY\")\n", - "// Deno.env.set(\"TAVILY_API_KEY\", \"YOUR_API_KEY\")" + "We first need to define the graph state. The state for a traditional LangGraph agent has a single attribute, `messages` which holds the original input, as well as the conversation history." ] }, { "cell_type": "code", - "execution_count": 3, - "id": "4499eb16-bca8-4a60-9a3a-2f34ae3f7078", + "execution_count": 21, + "id": "c941fb10-dbe5-4d6a-ab7d-133d01c33cc4", "metadata": {}, "outputs": [], "source": [ - "import { pull } from \"langchain/hub\";\n", - "import { createOpenAIFunctionsAgent } from \"langchain/agents\";\n", - "import { ChatOpenAI } from \"@langchain/openai\";\n", - "import { TavilySearchResults } from \"@langchain/community/tools/tavily_search\";\n", - "import { ChatPromptTemplate } from \"@langchain/core/prompts\";\n", - "\n", - "const tools = [new TavilySearchResults({ maxResults: 1 })];\n", - "\n", - "// Get the prompt to use - you can modify this!\n", - "const prompt = await pull(\n", - " \"hwchase17/openai-functions-agent\",\n", - ");\n", - "\n", - "// Choose the LLM that will drive the agent\n", - "const llm = new ChatOpenAI({\n", - " modelName: \"gpt-4o\",\n", - " temperature: 0,\n", - "});\n", + "import { Annotation } from \"@langchain/langgraph\";\n", + "import { BaseMessage } from \"@langchain/core/messages\";\n", "\n", - "// Construct the OpenAI Functions agent\n", - "const agentRunnable = await createOpenAIFunctionsAgent({\n", - " llm,\n", - " tools,\n", - " prompt,\n", + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", "});" ] }, { "cell_type": "markdown", - "id": "972e58b3-fe3c-449d-b3c4-8fa2217afd07", + "id": "ec3fab3b", "metadata": {}, "source": [ - "## Define the graph schema\n", + "## Define tools\n", "\n", - "We now define the graph state. The state for the traditional LangChain agent has\n", - "a few attributes:\n", + "Next, we will define the tools we'll use in this LangGraph graph. For this example, we'll use the pre-build Tavily Search tool, however you can use any pre-built, or custom tool you'd like. See [this guide](https://js.langchain.com/docs/how_to/custom_tools/) on how to create custom LangChain tools.\n", "\n", - "1. `input`: This is the input string representing the main ask from the user,\n", - " passed in as input.\n", - "2. `steps`: This is list of actions and corresponding observations that the\n", - " agent takes over time. This is updated each iteration of the agent.\n", - "3. `agentOutcome`: This is the response from the agent, either an AgentAction or\n", - " AgentFinish. The AgentExecutor should finish when this is an AgentFinish,\n", - " otherwise it should call the requested tools." + "We'll want to pass our tools to the `ToolNode`, which is the way LangGraph executes tools." ] }, { "cell_type": "code", - "execution_count": 9, - "id": "c941fb10-dbe5-4d6a-ab7d-133d01c33cc4", + "execution_count": 22, + "id": "25789946", "metadata": {}, "outputs": [], "source": [ - "const agentState = {\n", - " input: {\n", - " value: null,\n", - " },\n", - " steps: {\n", - " value: (x, y) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - " agentOutcome: {\n", - " value: null,\n", - " },\n", - "};" + "import { TavilySearchResults } from \"@langchain/community/tools/tavily_search\";\n", + "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", + "\n", + "const tools = [new TavilySearchResults({ maxResults: 1 })];\n", + "\n", + "const toolNode = new ToolNode(tools);" ] }, { @@ -185,55 +143,41 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 23, "id": "d61a970d-edf4-4eef-9678-28bab7c72331", "metadata": {}, "outputs": [], "source": [ - "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { AgentAction, AgentFinish, AgentStep } from \"@langchain/core/agents\";\n", - "import { ToolExecutor } from \"@langchain/langgraph/prebuilt\";\n", "import type { RunnableConfig } from \"@langchain/core/runnables\";\n", + "import { ChatAnthropic } from \"@langchain/anthropic\";\n", + "import { END } from \"@langchain/langgraph\";\n", "\n", - "interface AgentStateBase {\n", - " agentOutcome?: AgentAction | AgentFinish;\n", - " steps: Array;\n", - "}\n", - "\n", - "interface AgentState extends AgentStateBase {\n", - " input: string;\n", - " chatHistory?: BaseMessage[];\n", - "}\n", - "\n", - "const toolExecutor = new ToolExecutor({\n", - " tools,\n", - "});\n", + "// Define the LLM to be used in the agent\n", + "const llm = new ChatAnthropic({\n", + " model: \"claude-3-5-sonnet-20240620\",\n", + " temperature: 0,\n", + "}).bindTools(tools); // Ensure you bind the same tools passed to the ToolExecutor to the LLM, so these tools can be used in the agent\n", "\n", "// Define logic that will be used to determine which conditional edge to go down\n", - "const shouldContinue = (data: AgentState) => {\n", - " if (data.agentOutcome && \"returnValues\" in data.agentOutcome) {\n", - " return \"end\";\n", + "const shouldContinue = (data: typeof AgentState.State): \"executeTools\" | typeof END => {\n", + " const { messages } = data;\n", + " const lastMsg = messages[messages.length - 1];\n", + " // If the agent called a tool, we should continue. If not, we can end.\n", + " if (!(\"tool_calls\" in lastMsg) || !Array.isArray(lastMsg.tool_calls) || !lastMsg?.tool_calls?.length) {\n", + " return END;\n", " }\n", - " return \"continue\";\n", - "};\n", - "\n", - "const runAgent = async (data: AgentState, config?: RunnableConfig) => {\n", - " const agentOutcome = await agentRunnable.invoke(data, config);\n", - " return {\n", - " agentOutcome,\n", - " };\n", + " // By returning the name of the next node we want to go to\n", + " // LangGraph will automatically route to that node\n", + " return \"executeTools\";\n", "};\n", "\n", - "const executeTools = async (data: AgentState, config?: RunnableConfig) => {\n", - " const agentAction = data.agentOutcome;\n", - " if (!agentAction || \"returnValues\" in agentAction) {\n", - " throw new Error(\"Agent has not been run yet\");\n", - " }\n", - " const output = await toolExecutor.invoke(agentAction, config);\n", + "const callModel = async (data: typeof AgentState.State, config?: RunnableConfig): Promise> => {\n", + " const { messages } = data;\n", + " const result = await llm.invoke(messages, config);\n", " return {\n", - " steps: [{ action: agentAction, observation: JSON.stringify(output) }],\n", + " messages: [result],\n", " };\n", - "};" + "};\n" ] }, { @@ -248,58 +192,39 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 24, "id": "c4054dde-4618-49b7-998a-daa0c1d6d6c0", "metadata": {}, "outputs": [], "source": [ - "import { RunnableLambda } from \"@langchain/core/runnables\";\n", - "import { END, START, StateGraph } from \"@langchain/langgraph\";\n", + "import { START, StateGraph } from \"@langchain/langgraph\";\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: agentState,\n", - "});\n", - "\n", - "// Define the two nodes we will cycle between\n", - "workflow.addNode(\"agent\", new RunnableLambda({ func: runAgent }));\n", - "workflow.addNode(\"action\", new RunnableLambda({ func: executeTools }));\n", - "\n", - "// Set the entrypoint as `agent`\n", - "// This means that this node is the first one called\n", - "workflow.addEdge(START, \"agent\");\n", - "\n", - "// We now add a conditional edge\n", - "workflow.addConditionalEdges(\n", - " // First, we define the start node. We use `agent`.\n", - " // This means these are the edges taken after the `agent` node is called.\n", - " \"agent\",\n", - " // Next, we pass in the function that will determine which node is called next.\n", - " shouldContinue,\n", - " // Finally we pass in a mapping.\n", - " // The keys are strings, and the values are other nodes.\n", - " // END is a special node marking that the graph should finish.\n", - " // What will happen is we will call `should_continue`, and then the output of that\n", - " // will be matched against the keys in this mapping.\n", - " // Based on which one it matches, that node will then be called.\n", - " {\n", - " // If `tools`, then we call the tool node.\n", - " continue: \"action\",\n", - " // Otherwise we finish.\n", - " end: END,\n", - " },\n", - ");\n", - "\n", - "// We now add a normal edge from `tools` to `agent`.\n", - "// This means that after `tools` is called, `agent` node is called next.\n", - "workflow.addEdge(\"action\", \"agent\");\n", + "const workflow = new StateGraph(AgentState)\n", + " // Define the two nodes we will cycle between\n", + " .addNode(\"callModel\", callModel)\n", + " .addNode(\"executeTools\", toolNode)\n", + " // Set the entrypoint as `callModel`\n", + " // This means that this node is the first one called\n", + " .addEdge(START, \"callModel\")\n", + " // We now add a conditional edge\n", + " .addConditionalEdges(\n", + " // First, we define the start node. We use `callModel`.\n", + " // This means these are the edges taken after the `agent` node is called.\n", + " \"callModel\",\n", + " // Next, we pass in the function that will determine which node is called next.\n", + " shouldContinue,\n", + " )\n", + " // We now add a normal edge from `tools` to `agent`.\n", + " // This means that after `tools` is called, `agent` node is called next.\n", + " .addEdge(\"executeTools\", \"callModel\");\n", "\n", "const app = workflow.compile();" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 29, "id": "214ae46e-c297-465d-86db-2b0312ed3530", "metadata": {}, "outputs": [ @@ -307,105 +232,172 @@ "name": "stdout", "output_type": "stream", "text": [ - "{\n", - " agent: {\n", - " agentOutcome: {\n", - " tool: \"tavily_search_results_json\",\n", - " toolInput: { input: \"current weather in San Francisco\" },\n", - " log: 'Invoking \"tavily_search_results_json\" with {\"input\":\"current weather in San Francisco\"}\\n',\n", - " messageLog: [\n", - " AIMessage {\n", - " lc_serializable: true,\n", - " lc_kwargs: [Object],\n", - " lc_namespace: [Array],\n", - " content: \"\",\n", - " name: undefined,\n", - " additional_kwargs: [Object]\n", - " }\n", - " ]\n", + "(node) callModel: AIMessage {\n", + " \"id\": \"msg_01SwpMu2zu8W4NZeLEg5DHKj\",\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Certainly! I'll use the Tavily search engine to find current weather information for San Francisco (SF). Let me search for that information for you.\"\n", + " },\n", + " {\n", + " \"type\": \"tool_use\",\n", + " \"id\": \"toolu_013Asn4HooNPMtYYapQPcewB\",\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"input\": {\n", + " \"input\": \"current weather in San Francisco\"\n", + " }\n", " }\n", + " ],\n", + " \"additional_kwargs\": {\n", + " \"id\": \"msg_01SwpMu2zu8W4NZeLEg5DHKj\",\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 416,\n", + " \"output_tokens\": 94\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"id\": \"msg_01SwpMu2zu8W4NZeLEg5DHKj\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 416,\n", + " \"output_tokens\": 94\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"args\": {\n", + " \"input\": \"current weather in San Francisco\"\n", + " },\n", + " \"id\": \"toolu_013Asn4HooNPMtYYapQPcewB\",\n", + " \"type\": \"tool_call\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 416,\n", + " \"output_tokens\": 94,\n", + " \"total_tokens\": 510\n", " }\n", "}\n", "----\n", "\n", - "{\n", - " action: {\n", - " steps: [\n", - " {\n", - " action: {\n", - " tool: \"tavily_search_results_json\",\n", - " toolInput: [Object],\n", - " log: 'Invoking \"tavily_search_results_json\" with {\"input\":\"current weather in San Francisco\"}\\n',\n", - " messageLog: [Array]\n", - " },\n", - " observation: '\"[{\\\\\"title\\\\\":\\\\\"Weather in San Francisco\\\\\",\\\\\"url\\\\\":\\\\\"https://www.weatherapi.com/\\\\\",\\\\\"content\\\\\":\\\\\"Weat'... 839 more characters\n", - " }\n", - " ]\n", - " }\n", + "(node) executeTools: ToolMessage {\n", + " \"content\": \"[{\\\"title\\\":\\\"Weather in San Francisco\\\",\\\"url\\\":\\\"https://www.weatherapi.com/\\\",\\\"content\\\":\\\"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1724101106, 'localtime': '2024-08-19 13:58'}, 'current': {'last_updated_epoch': 1724100300, 'last_updated': '2024-08-19 13:45', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 9.4, 'wind_kph': 15.1, 'wind_degree': 250, 'wind_dir': 'WSW', 'pressure_mb': 1019.0, 'pressure_in': 30.08, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 64, 'cloud': 0, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 17.7, 'windchill_f': 63.8, 'heatindex_c': 17.7, 'heatindex_f': 63.8, 'dewpoint_c': 10.7, 'dewpoint_f': 51.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 13.2, 'gust_kph': 21.2}}\\\",\\\"score\\\":0.9953496,\\\"raw_content\\\":null}]\",\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"toolu_013Asn4HooNPMtYYapQPcewB\"\n", "}\n", "----\n", "\n", - "{\n", - " agent: {\n", - " agentOutcome: {\n", - " returnValues: {\n", - " output: \"The current weather in San Francisco is partly cloudy with a temperature of 11.7°C (53.1°F). The win\"... 278 more characters\n", - " },\n", - " log: \"The current weather in San Francisco is partly cloudy with a temperature of 11.7°C (53.1°F). The win\"... 278 more characters\n", + "(node) callModel: AIMessage {\n", + " \"id\": \"msg_01PwNrBEDix98RpZSdWwzjjn\",\n", + " \"content\": \"Based on the search results, I can provide you with the current weather information for San Francisco (SF). Here's a summary of the weather conditions:\\n\\n1. Temperature: 21.1°C (70.0°F)\\n2. Condition: Sunny\\n3. Wind: 15.1 km/h (9.4 mph), direction WSW (West-Southwest)\\n4. Humidity: 64%\\n5. Precipitation: 0 mm (0 inches)\\n6. Cloud cover: 0% (clear sky)\\n7. Visibility: 16 km (9 miles)\\n8. UV Index: 5.0 (moderate)\\n\\nThe weather in San Francisco is currently pleasant and sunny. It's a comfortable 21.1°C (70.0°F) with no cloud cover, making it a clear and bright day. The wind is light to moderate, coming from the west-southwest direction. There's no precipitation, and visibility is good.\\n\\nIt's worth noting that San Francisco's weather can change quickly due to its unique microclimate, so if you're planning activities, it's always good to check for updates closer to the time.\\n\\nIs there anything specific about the weather in San Francisco that you'd like to know more about?\",\n", + " \"additional_kwargs\": {\n", + " \"id\": \"msg_01PwNrBEDix98RpZSdWwzjjn\",\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"end_turn\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 1028,\n", + " \"output_tokens\": 282\n", " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"id\": \"msg_01PwNrBEDix98RpZSdWwzjjn\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"end_turn\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 1028,\n", + " \"output_tokens\": 282\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", + " },\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 1028,\n", + " \"output_tokens\": 282,\n", + " \"total_tokens\": 1310\n", " }\n", "}\n", "----\n", "\n", - "{\n", - " __end__: {\n", - " input: \"what is the weather in sf\",\n", - " steps: [\n", - " {\n", - " action: {\n", - " tool: \"tavily_search_results_json\",\n", - " toolInput: [Object],\n", - " log: 'Invoking \"tavily_search_results_json\" with {\"input\":\"current weather in San Francisco\"}\\n',\n", - " messageLog: [Array]\n", - " },\n", - " observation: '\"[{\\\\\"title\\\\\":\\\\\"Weather in San Francisco\\\\\",\\\\\"url\\\\\":\\\\\"https://www.weatherapi.com/\\\\\",\\\\\"content\\\\\":\\\\\"Weat'... 839 more characters\n", - " }\n", - " ],\n", - " agentOutcome: {\n", - " returnValues: {\n", - " output: \"The current weather in San Francisco is partly cloudy with a temperature of 11.7°C (53.1°F). The win\"... 278 more characters\n", - " },\n", - " log: \"The current weather in San Francisco is partly cloudy with a temperature of 11.7°C (53.1°F). The win\"... 278 more characters\n", - " }\n", - " }\n", - "}\n", - "----\n", - "\n" + "Final Result: Based on the search results, I can provide you with the current weather information for San Francisco (SF). Here's a summary of the weather conditions:\n", + "\n", + "1. Temperature: 21.1°C (70.0°F)\n", + "2. Condition: Sunny\n", + "3. Wind: 15.1 km/h (9.4 mph), direction WSW (West-Southwest)\n", + "4. Humidity: 64%\n", + "5. Precipitation: 0 mm (0 inches)\n", + "6. Cloud cover: 0% (clear sky)\n", + "7. Visibility: 16 km (9 miles)\n", + "8. UV Index: 5.0 (moderate)\n", + "\n", + "The weather in San Francisco is currently pleasant and sunny. It's a comfortable 21.1°C (70.0°F) with no cloud cover, making it a clear and bright day. The wind is light to moderate, coming from the west-southwest direction. There's no precipitation, and visibility is good.\n", + "\n", + "It's worth noting that San Francisco's weather can change quickly due to its unique microclimate, so if you're planning activities, it's always good to check for updates closer to the time.\n", + "\n", + "Is there anything specific about the weather in San Francisco that you'd like to know more about?\n" ] } ], "source": [ - "const inputs = { input: \"what is the weather in sf\" };\n", + "import { HumanMessage, BaseMessage } from \"@langchain/core/messages\";\n", + "\n", + "let finalResult: BaseMessage | undefined;\n", + "\n", + "const prettyLogOutput = (output: Record) => {\n", + " const keys = Object.keys(output);\n", + " const firstItem = output[keys[0]];\n", + " if (\"messages\" in firstItem) {\n", + " console.log(`(node) ${keys[0]}:`, firstItem.messages[0]);\n", + " console.log(\"----\\n\");\n", + " }\n", + "}\n", + "\n", + "const inputs = { messages: [new HumanMessage(\"Search the web for the weather in sf\")] };\n", "for await (const s of await app.stream(inputs)) {\n", - " console.log(s);\n", - " console.log(\"----\\n\");\n", - "}" + " prettyLogOutput(s);\n", + " if (\"callModel\" in s && s.callModel.messages?.length) {\n", + " finalResult = s.callModel.messages[0];\n", + " }\n", + "}\n", + "\n", + "console.log(\"Final Result: \", finalResult.content)" ] } ], "metadata": { "kernelspec": { - "display_name": "Deno", + "display_name": "TypeScript", "language": "typescript", - "name": "deno" + "name": "tslab" }, "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, "file_extension": ".ts", - "mimetype": "text/x.typescript", + "mimetype": "text/typescript", "name": "typescript", - "nb_converter": "script", - "pygments_lexer": "typescript", - "version": "5.3.3" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/examples/chat_agent_executor_with_function_calling/base.ipynb b/examples/chat_agent_executor_with_function_calling/base.ipynb index 51852e21..28e195e2 100644 --- a/examples/chat_agent_executor_with_function_calling/base.ipynb +++ b/examples/chat_agent_executor_with_function_calling/base.ipynb @@ -61,65 +61,25 @@ "source": [ "## Set up the tools\n", "\n", - "We will first define the tools we want to use. For this simple example, we will\n", - "use a built-in search tool via Tavily. However, it is really easy to create your\n", - "own tools - see documentation\n", - "[here](https://js.langchain.com/docs/modules/agents/tools/dynamic) on how to do\n", - "that." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "c60720fd", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Module: null prototype] { default: {} }" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import \"dotenv/config\";" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e", - "metadata": {}, - "outputs": [], - "source": [ - "import { TavilySearchResults } from \"@langchain/community/tools/tavily_search\";\n", + "We will first define the tools we want to use. For this simple example, we will use a built-in search tool via Tavily. However, it is really easy to create your own tools - see documentation [here](https://js.langchain.com/docs/how_to/custom_tools/) on how to do that.\n", "\n", - "const tools = [new TavilySearchResults({ maxResults: 1 })];" - ] - }, - { - "cell_type": "markdown", - "id": "01885785-b71a-44d1-b1d6-7b5b14d53b58", - "metadata": {}, - "source": [ - "We can now wrap these tools in a simple ToolExecutor. This is a real simple\n", - "class that takes in a ToolInvocation and calls that tool, returning the output.\n", + "After defining our tools, we can wrap them in a simple `ToolExecutor`. This is a real simple\n", + "class that takes in a `ToolInvocation` and calls that tool, returning the output.\n", "\n", "A ToolInvocation is any type with `tool` and `toolInput` attribute." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7", "metadata": {}, "outputs": [], "source": [ "import { ToolExecutor } from \"@langchain/langgraph/prebuilt\";\n", + "import { TavilySearchResults } from \"@langchain/community/tools/tavily_search\";\n", + "\n", + "const tools = [new TavilySearchResults({ maxResults: 1 })];\n", "\n", "const toolExecutor = new ToolExecutor({\n", " tools,\n", @@ -147,7 +107,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "892b54b9-75f0-4804-9ed0-88b5e5532989", "metadata": {}, "outputs": [], @@ -174,7 +134,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107", "metadata": {}, "outputs": [], @@ -204,29 +164,24 @@ "object you construct the graph with.\n", "\n", "For this example, the state we will track will just be a list of messages. We\n", - "want each node to just add messages to that list. Therefore, we will use an\n", - "object with one key (`messages`) with the value as an object:\n", - "`{ value: Function, default?: () => any }`\n", - "\n", - "The `default` key must be a factory that returns the default value for that\n", - "attribute." + "want each node to just add messages to that list. To do this, we define our state via the `Annotation` function, with a single attribute `messages` that is a list of `BaseMessage`s. The value of our attribute `messages` is another `Annotation` which has two sub-attributes: `reducer` and `default`. The `reducer` key must be a factory that returns a function that takes the current value of the attribute and the new value to add, and returns the new value of the attribute." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "id": "ea793afa-2eab-4901-910d-6eed90cd6564", "metadata": {}, "outputs": [], "source": [ "import { BaseMessage } from \"@langchain/core/messages\";\n", + "import { Annotation } from \"@langchain/langgraph\";\n", "\n", - "const agentState = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " })\n", + "})" ] }, { @@ -262,17 +217,16 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 12, "id": "3b541bb9-900c-40d0-964d-7b5dfee30667", "metadata": {}, "outputs": [], "source": [ "import { FunctionMessage } from \"@langchain/core/messages\";\n", "import { AgentAction } from \"@langchain/core/agents\";\n", - "import type { RunnableConfig } from \"@langchain/core/runnables\";\n", "\n", "// Define the function that determines whether to continue or not\n", - "const shouldContinue = (state: { messages: Array }) => {\n", + "const shouldContinue = (state: typeof AgentState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1];\n", " // If there is no function call, then we finish\n", @@ -287,7 +241,7 @@ "};\n", "\n", "// Define the function to execute tools\n", - "const _getAction = (state: { messages: Array }): AgentAction => {\n", + "const _getAction = (state: typeof AgentState.State): AgentAction => {\n", " const { messages } = state;\n", " // Based on the continue condition\n", " // we know the last message involves a function call\n", @@ -309,9 +263,7 @@ "};\n", "\n", "// Define the function that calls the model\n", - "const callModel = async (\n", - " state: { messages: Array },\n", - ") => {\n", + "const callModel = async (state: typeof AgentState.State) => {\n", " const { messages } = state;\n", " const response = await newModel.invoke(messages);\n", " // We return a list, because this will get added to the existing list\n", @@ -320,9 +272,7 @@ " };\n", "};\n", "\n", - "const callTool = async (\n", - " state: { messages: Array },\n", - ") => {\n", + "const callTool = async (state: typeof AgentState.State) => {\n", " const action = _getAction(state);\n", " // We call the tool_executor and get back a response\n", " const response = await toolExecutor.invoke(action);\n", @@ -348,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 13, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], @@ -356,42 +306,36 @@ "import { END, START, StateGraph } from \"@langchain/langgraph\";\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: agentState,\n", - "});\n", - "\n", - "// Define the two nodes we will cycle between\n", - "workflow.addNode(\"agent\", callModel);\n", - "workflow.addNode(\"action\", callTool);\n", - "\n", - "// Set the entrypoint as `agent`\n", - "// This means that this node is the first one called\n", - "workflow.addEdge(START, \"agent\");\n", - "\n", - "// We now add a conditional edge\n", - "workflow.addConditionalEdges(\n", - " // First, we define the start node. We use `agent`.\n", - " // This means these are the edges taken after the `agent` node is called.\n", - " \"agent\",\n", - " // Next, we pass in the function that will determine which node is called next.\n", - " shouldContinue,\n", - " // Finally we pass in a mapping.\n", - " // The keys are strings, and the values are other nodes.\n", - " // END is a special node marking that the graph should finish.\n", - " // What will happen is we will call `should_continue`, and then the output of that\n", - " // will be matched against the keys in this mapping.\n", - " // Based on which one it matches, that node will then be called.\n", - " {\n", - " // If `tools`, then we call the tool node.\n", - " continue: \"action\",\n", - " // Otherwise we finish.\n", - " end: END,\n", - " },\n", - ");\n", - "\n", - "// We now add a normal edge from `tools` to `agent`.\n", - "// This means that after `tools` is called, `agent` node is called next.\n", - "workflow.addEdge(\"action\", \"agent\");\n", + "const workflow = new StateGraph(AgentState)\n", + " // Define the two nodes we will cycle between\n", + " .addNode(\"agent\", callModel)\n", + " .addNode(\"action\", callTool)\n", + " // Set the entrypoint as `agent`\n", + " // This means that this node is the first one called\n", + " .addEdge(START, \"agent\")\n", + " // We now add a conditional edge\n", + " .addConditionalEdges(\n", + " // First, we define the start node. We use `agent`.\n", + " // This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " // Next, we pass in the function that will determine which node is called next.\n", + " shouldContinue,\n", + " // Finally we pass in a mapping.\n", + " // The keys are strings, and the values are other nodes.\n", + " // END is a special node marking that the graph should finish.\n", + " // What will happen is we will call `should_continue`, and then the output of that\n", + " // will be matched against the keys in this mapping.\n", + " // Based on which one it matches, that node will then be called.\n", + " {\n", + " // If `tools`, then we call the tool node.\n", + " continue: \"action\",\n", + " // Otherwise we finish.\n", + " end: END,\n", + " },\n", + " )\n", + " // We now add a normal edge from `tools` to `agent`.\n", + " // This means that after `tools` is called, `agent` node is called next.\n", + " .addEdge(\"action\", \"agent\");\n", "\n", "// Finally, we compile it!\n", "// This compiles it into a LangChain Runnable,\n", @@ -413,66 +357,83 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 14, "id": "8edb04b9-40b6-46f1-a7a8-4b2d8aba7752", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "{\n", - " messages: [\n", - " HumanMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: { content: \u001b[32m\"what is the weather in sf\"\u001b[39m, additional_kwargs: {} },\n", - " lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n", - " content: \u001b[32m\"what is the weather in sf\"\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: {}\n", - " },\n", - " AIMessageChunk {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: { content: \u001b[32m\"\"\u001b[39m, additional_kwargs: { function_call: \u001b[36m[Object]\u001b[39m } },\n", - " lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n", - " content: \u001b[32m\"\"\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: {\n", - " function_call: {\n", - " name: \u001b[32m\"tavily_search_results_json\"\u001b[39m,\n", - " arguments: \u001b[32m'{\"input\":\"weather in San Francisco\"}'\u001b[39m\n", - " }\n", - " }\n", - " },\n", - " FunctionMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m\"[]\"\u001b[39m,\n", - " name: \u001b[32m\"tavily_search_results_json\"\u001b[39m,\n", - " additional_kwargs: {}\n", - " },\n", - " lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n", - " content: \u001b[32m\"[]\"\u001b[39m,\n", - " name: \u001b[32m\"tavily_search_results_json\"\u001b[39m,\n", - " additional_kwargs: {}\n", - " },\n", - " AIMessageChunk {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m\"I'm sorry, but I couldn't find the current weather in San Francisco. You may want to check a reliabl\"\u001b[39m... 61 more characters,\n", - " additional_kwargs: {}\n", - " },\n", - " lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n", - " content: \u001b[32m\"I'm sorry, but I couldn't find the current weather in San Francisco. You may want to check a reliabl\"\u001b[39m... 61 more characters,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: {}\n", - " }\n", - " ]\n", - "}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " messages: [\n", + " HumanMessage {\n", + " \"content\": \"what is the weather in sf\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {}\n", + " },\n", + " AIMessageChunk {\n", + " \"id\": \"chatcmpl-9y3695OyrZqhRmcbVkioOxXzjXp32\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"function_call\": {\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"arguments\": \"{\\\"input\\\":\\\"weather in San Francisco\\\"}\"\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"estimatedTokenUsage\": {\n", + " \"promptTokens\": 80,\n", + " \"completionTokens\": 21,\n", + " \"totalTokens\": 101\n", + " },\n", + " \"prompt\": 0,\n", + " \"completion\": 0,\n", + " \"finish_reason\": \"function_call\",\n", + " \"system_fingerprint\": null\n", + " },\n", + " \"tool_calls\": [],\n", + " \"tool_call_chunks\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 79,\n", + " \"output_tokens\": 21,\n", + " \"total_tokens\": 100\n", + " }\n", + " },\n", + " FunctionMessage {\n", + " \"content\": \"[{\\\"title\\\":\\\"Weather in San Francisco\\\",\\\"url\\\":\\\"https://www.weatherapi.com/\\\",\\\"content\\\":\\\"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1724098493, 'localtime': '2024-08-19 13:14'}, 'current': {'last_updated_epoch': 1724097600, 'last_updated': '2024-08-19 13:00', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 9.4, 'wind_kph': 15.1, 'wind_degree': 250, 'wind_dir': 'WSW', 'pressure_mb': 1019.0, 'pressure_in': 30.08, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 64, 'cloud': 0, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 17.7, 'windchill_f': 63.8, 'heatindex_c': 17.7, 'heatindex_f': 63.8, 'dewpoint_c': 10.7, 'dewpoint_f': 51.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 13.2, 'gust_kph': 21.2}}\\\",\\\"score\\\":0.9991679,\\\"raw_content\\\":null}]\",\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {}\n", + " },\n", + " AIMessageChunk {\n", + " \"id\": \"chatcmpl-9y36FtclcqetcawFZmYH7rDvajQ7A\",\n", + " \"content\": \"The current weather in San Francisco is sunny with a temperature of 70.0°F (21.1°C). The wind is blowing at 15.1 kph from the WSW direction. The humidity is at 64%, and there is no precipitation.\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"estimatedTokenUsage\": {\n", + " \"promptTokens\": 536,\n", + " \"completionTokens\": 53,\n", + " \"totalTokens\": 589\n", + " },\n", + " \"prompt\": 0,\n", + " \"completion\": 0,\n", + " \"finish_reason\": \"stop\",\n", + " \"system_fingerprint\": null\n", + " },\n", + " \"tool_calls\": [],\n", + " \"tool_call_chunks\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 538,\n", + " \"output_tokens\": 54,\n", + " \"total_tokens\": 592\n", + " }\n", + " }\n", + " ]\n", + "}\n" + ] } ], "source": [ @@ -500,12 +461,14 @@ "### Streaming Node Output\n", "\n", "One of the benefits of using LangGraph is that it is easy to stream output as\n", - "it's produced by each node." + "it's produced by each node.\n", + "\n", + "In this example we'll re-use the `inputs` object from above." ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 15, "id": "f544977e-31f7-41f0-88c4-ec9c27b8cecb", "metadata": {}, "outputs": [ @@ -517,12 +480,33 @@ " agent: {\n", " messages: [\n", " AIMessageChunk {\n", - " lc_serializable: true,\n", - " lc_kwargs: { content: \"\", additional_kwargs: [Object] },\n", - " lc_namespace: [ \"langchain_core\", \"messages\" ],\n", - " content: \"\",\n", - " name: undefined,\n", - " additional_kwargs: { function_call: [Object] }\n", + " \"id\": \"chatcmpl-9y36G4P6hDihhZkOCW5T5bJWWNl0Z\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"function_call\": {\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"arguments\": \"{\\\"input\\\":\\\"weather in San Francisco\\\"}\"\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"estimatedTokenUsage\": {\n", + " \"promptTokens\": 80,\n", + " \"completionTokens\": 21,\n", + " \"totalTokens\": 101\n", + " },\n", + " \"prompt\": 0,\n", + " \"completion\": 0,\n", + " \"finish_reason\": \"function_call\",\n", + " \"system_fingerprint\": null\n", + " },\n", + " \"tool_calls\": [],\n", + " \"tool_call_chunks\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 79,\n", + " \"output_tokens\": 21,\n", + " \"total_tokens\": 100\n", + " }\n", " }\n", " ]\n", " }\n", @@ -533,16 +517,10 @@ " action: {\n", " messages: [\n", " FunctionMessage {\n", - " lc_serializable: true,\n", - " lc_kwargs: {\n", - " content: \"[]\",\n", - " name: \"tavily_search_results_json\",\n", - " additional_kwargs: {}\n", - " },\n", - " lc_namespace: [ \"langchain_core\", \"messages\" ],\n", - " content: \"[]\",\n", - " name: \"tavily_search_results_json\",\n", - " additional_kwargs: {}\n", + " \"content\": \"[{\\\"title\\\":\\\"Weather in San Francisco\\\",\\\"url\\\":\\\"https://www.weatherapi.com/\\\",\\\"content\\\":\\\"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1724098493, 'localtime': '2024-08-19 13:14'}, 'current': {'last_updated_epoch': 1724097600, 'last_updated': '2024-08-19 13:00', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 9.4, 'wind_kph': 15.1, 'wind_degree': 250, 'wind_dir': 'WSW', 'pressure_mb': 1019.0, 'pressure_in': 30.08, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 64, 'cloud': 0, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 17.7, 'windchill_f': 63.8, 'heatindex_c': 17.7, 'heatindex_f': 63.8, 'dewpoint_c': 10.7, 'dewpoint_f': 51.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 13.2, 'gust_kph': 21.2}}\\\",\\\"score\\\":0.9991846,\\\"raw_content\\\":null}]\",\n", + " \"name\": \"tavily_search_results_json\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {}\n", " }\n", " ]\n", " }\n", @@ -553,62 +531,28 @@ " agent: {\n", " messages: [\n", " AIMessageChunk {\n", - " lc_serializable: true,\n", - " lc_kwargs: {\n", - " content: \"I couldn't find the current weather in San Francisco. You may want to check a reliable weather websi\"... 46 more characters,\n", - " additional_kwargs: {}\n", + " \"id\": \"chatcmpl-9y36KCh1ZZbMNXVbLxTzz8jZb4t4T\",\n", + " \"content\": \"The current weather in San Francisco is sunny with a temperature of 70.0°F (21.1°C). The wind is blowing at 15.1 kph from the WSW direction. The humidity is at 64% and there is no precipitation.\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"estimatedTokenUsage\": {\n", + " \"promptTokens\": 536,\n", + " \"completionTokens\": 53,\n", + " \"totalTokens\": 589\n", + " },\n", + " \"prompt\": 0,\n", + " \"completion\": 0,\n", + " \"finish_reason\": \"stop\",\n", + " \"system_fingerprint\": null\n", " },\n", - " lc_namespace: [ \"langchain_core\", \"messages\" ],\n", - " content: \"I couldn't find the current weather in San Francisco. You may want to check a reliable weather websi\"... 46 more characters,\n", - " name: undefined,\n", - " additional_kwargs: {}\n", - " }\n", - " ]\n", - " }\n", - "}\n", - "-----\n", - "\n", - "output {\n", - " __end__: {\n", - " messages: [\n", - " HumanMessage {\n", - " lc_serializable: true,\n", - " lc_kwargs: { content: \"what is the weather in sf\", additional_kwargs: {} },\n", - " lc_namespace: [ \"langchain_core\", \"messages\" ],\n", - " content: \"what is the weather in sf\",\n", - " name: undefined,\n", - " additional_kwargs: {}\n", - " },\n", - " AIMessageChunk {\n", - " lc_serializable: true,\n", - " lc_kwargs: { content: \"\", additional_kwargs: [Object] },\n", - " lc_namespace: [ \"langchain_core\", \"messages\" ],\n", - " content: \"\",\n", - " name: undefined,\n", - " additional_kwargs: { function_call: [Object] }\n", - " },\n", - " FunctionMessage {\n", - " lc_serializable: true,\n", - " lc_kwargs: {\n", - " content: \"[]\",\n", - " name: \"tavily_search_results_json\",\n", - " additional_kwargs: {}\n", - " },\n", - " lc_namespace: [ \"langchain_core\", \"messages\" ],\n", - " content: \"[]\",\n", - " name: \"tavily_search_results_json\",\n", - " additional_kwargs: {}\n", - " },\n", - " AIMessageChunk {\n", - " lc_serializable: true,\n", - " lc_kwargs: {\n", - " content: \"I couldn't find the current weather in San Francisco. You may want to check a reliable weather websi\"... 46 more characters,\n", - " additional_kwargs: {}\n", - " },\n", - " lc_namespace: [ \"langchain_core\", \"messages\" ],\n", - " content: \"I couldn't find the current weather in San Francisco. You may want to check a reliable weather websi\"... 46 more characters,\n", - " name: undefined,\n", - " additional_kwargs: {}\n", + " \"tool_calls\": [],\n", + " \"tool_call_chunks\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 538,\n", + " \"output_tokens\": 54,\n", + " \"total_tokens\": 592\n", + " }\n", " }\n", " ]\n", " }\n", @@ -619,9 +563,6 @@ } ], "source": [ - "const inputs = {\n", - " messages: [new HumanMessage(\"what is the weather in sf\")],\n", - "};\n", "for await (const output of await app.stream(inputs)) {\n", " console.log(\"output\", output);\n", " console.log(\"-----\\n\");\n", @@ -631,17 +572,20 @@ ], "metadata": { "kernelspec": { - "display_name": "Deno", + "display_name": "TypeScript", "language": "typescript", - "name": "deno" + "name": "tslab" }, "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, "file_extension": ".ts", - "mimetype": "text/x.typescript", + "mimetype": "text/typescript", "name": "typescript", - "nb_converter": "script", - "pygments_lexer": "typescript", - "version": "5.3.3" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/examples/how-tos/branching.ipynb b/examples/how-tos/branching.ipynb index fb04ae45..bdfc2504 100644 --- a/examples/how-tos/branching.ipynb +++ b/examples/how-tos/branching.ipynb @@ -64,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -80,20 +80,13 @@ } ], "source": [ - "import { END, START, StateGraph } from \"@langchain/langgraph\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", - "\n", - "// Define the state type\n", - "interface IState {\n", - " aggregate: string[];\n", - "}\n", + "import { END, START, StateGraph, Annotation } from \"@langchain/langgraph\";\n", "\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " aggregate: {\n", - " value: (x: string[], y: string[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};\n", + "const GraphState = Annotation.Root({\n", + " aggregate: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " })\n", + "})\n", "\n", "// Define the ReturnNodeValue class\n", "class ReturnNodeValue {\n", @@ -103,7 +96,7 @@ " this._value = value;\n", " }\n", "\n", - " public call(state: IState) {\n", + " public call(state: typeof GraphState.State) {\n", " console.log(`Adding ${this._value} to ${state.aggregate}`);\n", " return { aggregate: [this._value] };\n", " }\n", @@ -115,7 +108,7 @@ "const nodeC = new ReturnNodeValue(\"I'm C\");\n", "const nodeD = new ReturnNodeValue(\"I'm D\");\n", "\n", - "const builder = new StateGraph({ channels: graphState })\n", + "const builder = new StateGraph(GraphState)\n", " .addNode(\"a\", nodeA.call.bind(nodeA))\n", " .addEdge(START, \"a\")\n", " .addNode(\"b\", nodeB.call.bind(nodeB))\n", @@ -151,7 +144,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -167,23 +160,14 @@ } ], "source": [ - "// Define the state type\n", - "interface IState2 {\n", - " // The operator.add reducer function makes this append-only\n", - " aggregate: string[];\n", - " which: string;\n", - "}\n", - "\n", - "const graphState2: StateGraphArgs[\"channels\"] = {\n", - " aggregate: {\n", - " value: (x: string[], y: string[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - " which: {\n", - " value: (x: string, y: string) => (y ? y : x),\n", - " default: () => \"bc\",\n", - " },\n", - "};\n", + "const GraphStateConditionalBranching = Annotation.Root({\n", + " aggregate: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + " which: Annotation({\n", + " reducer: (x: string, y: string) => (y ?? x),\n", + " })\n", + "})\n", "\n", "// Create the graph\n", "const nodeA2 = new ReturnNodeValue(\"I'm A\");\n", @@ -192,14 +176,14 @@ "const nodeD2 = new ReturnNodeValue(\"I'm D\");\n", "const nodeE2 = new ReturnNodeValue(\"I'm E\");\n", "// Define the route function\n", - "function routeCDorBC(state: IState2): string[] {\n", + "function routeCDorBC(state: typeof GraphStateConditionalBranching.State): string[] {\n", " if (state.which === \"cd\") {\n", " return [\"c\", \"d\"];\n", " }\n", " return [\"b\", \"c\"];\n", "}\n", "\n", - "const builder2 = new StateGraph({ channels: graphState2 })\n", + "const builder2 = new StateGraph(GraphStateConditionalBranching)\n", " .addNode(\"a\", nodeA2.call.bind(nodeA2))\n", " .addEdge(START, \"a\")\n", " .addNode(\"b\", nodeB2.call.bind(nodeB2))\n", @@ -222,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -263,7 +247,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -282,6 +266,11 @@ } ], "source": [ + "type ScoredValue = {\n", + " value: string;\n", + " score: number;\n", + "};\n", + "\n", "const reduceFanouts = (left?: ScoredValue[], right?: ScoredValue[]) => {\n", " if (!left) {\n", " left = [];\n", @@ -293,34 +282,18 @@ " return left.concat(right);\n", "};\n", "\n", - "type ScoredValue = {\n", - " value: string;\n", - " score: number;\n", - "};\n", - "\n", - "// Define the state type\n", - "// 'value' defines the 'reducer', which determines how updates are applied\n", - "// 'default' defines the default value for the state\n", - "interface IState3 {\n", - " aggregate: string[];\n", - " which: string;\n", - " fanoutValues: ScoredValue[];\n", - "}\n", + "const GraphStateStableSorting = Annotation.Root({\n", + " aggregate: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + " which: Annotation({\n", + " reducer: (x: string, y: string) => (y ?? x),\n", + " }),\n", + " fanoutValues: Annotation({\n", + " reducer: reduceFanouts,\n", + " }),\n", + "})\n", "\n", - "const graphState3: StateGraphArgs[\"channels\"] = {\n", - " aggregate: {\n", - " value: (x: string[], y: string[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - " which: {\n", - " value: (x: string, y: string) => (y ? y : x),\n", - " default: () => \"\",\n", - " },\n", - " fanoutValues: {\n", - " value: reduceFanouts,\n", - " default: () => [],\n", - " },\n", - "};\n", "\n", "class ParallelReturnNodeValue {\n", " private _value: string;\n", @@ -331,7 +304,7 @@ " this._score = score;\n", " }\n", "\n", - " public call(state: IState3) {\n", + " public call(state: typeof GraphStateStableSorting.State) {\n", " console.log(`Adding ${this._value} to ${state.aggregate}`);\n", " return { fanoutValues: [{ value: this._value, score: this._score }] };\n", " }\n", @@ -345,7 +318,7 @@ "const nodeC3 = new ParallelReturnNodeValue(\"I'm C\", 0.9);\n", "const nodeD3 = new ParallelReturnNodeValue(\"I'm D\", 0.3);\n", "\n", - "const aggregateFanouts = (state: { fanoutValues: ScoredValue[] }) => {\n", + "const aggregateFanouts = (state: typeof GraphStateStableSorting.State) => {\n", " // Sort by score (reversed)\n", " state.fanoutValues.sort((a, b) => b.score - a.score);\n", " return {\n", @@ -355,14 +328,14 @@ "};\n", "\n", "// Define the route function\n", - "function routeBCOrCD(state: { which: string }): string[] {\n", + "function routeBCOrCD(state: typeof GraphStateStableSorting.State): string[] {\n", " if (state.which === \"cd\") {\n", " return [\"c\", \"d\"];\n", " }\n", " return [\"b\", \"c\"];\n", "}\n", "\n", - "const builder3 = new StateGraph({ channels: graphState3 })\n", + "const builder3 = new StateGraph(GraphStateStableSorting)\n", " .addNode(\"a\", nodeA3.call.bind(nodeA3))\n", " .addEdge(START, \"a\")\n", " .addNode(\"b\", nodeB3.call.bind(nodeB3))\n", @@ -394,7 +367,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -416,13 +389,6 @@ "let g3result2 = await graph3.invoke({ aggregate: [], which: \"cd\" });\n", "console.log(\"Result 2: \", g3result2);\n" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/examples/how-tos/breakpoints.ipynb b/examples/how-tos/breakpoints.ipynb index ea7b11f4..791bc81f 100644 --- a/examples/how-tos/breakpoints.ipynb +++ b/examples/how-tos/breakpoints.ipynb @@ -65,61 +65,56 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "id": "9b53f191-1e86-4881-a667-d46a3d66958b", "metadata": {}, "outputs": [], "source": [ - "import { StateGraph, START, END } from \"@langchain/langgraph\";\n", + "import { StateGraph, START, END, Annotation } from \"@langchain/langgraph\";\n", "import { MemorySaver } from \"@langchain/langgraph\";\n", "\n", - "type GraphState = {\n", - " input: string;\n", - "}\n", + "const GraphState = Annotation.Root({\n", + " input: Annotation\n", + "});\n", "\n", - "const step1 = (state: GraphState): Partial => {\n", - " console.log(\"---Step 1---\");\n", - " return state;\n", + "const step1 = (state: typeof GraphState.State) => {\n", + " console.log(\"---Step 1---\");\n", + " return state;\n", "}\n", "\n", - "const step2 = (state: GraphState): Partial => {\n", - " console.log(\"---Step 2---\");\n", - " return state;\n", + "const step2 = (state: typeof GraphState.State) => {\n", + " console.log(\"---Step 2---\");\n", + " return state;\n", "}\n", "\n", - "const step3 = (state: GraphState): Partial => {\n", - " console.log(\"---Step 3---\");\n", - " return state;\n", + "const step3 = (state: typeof GraphState.State) => {\n", + " console.log(\"---Step 3---\");\n", + " return state;\n", "}\n", "\n", "\n", - "const builder = new StateGraph({\n", - " channels: {\n", - " input: null,\n", - " }\n", - "})\n", - " .addNode(\"step1\", step1)\n", - " .addNode(\"step2\", step2)\n", - " .addNode(\"step3\", step3)\n", - " .addEdge(START, \"step1\")\n", - " .addEdge(\"step1\", \"step2\")\n", - " .addEdge(\"step2\", \"step3\")\n", - " .addEdge(\"step3\", END);\n", + "const builder = new StateGraph(GraphState)\n", + " .addNode(\"step1\", step1)\n", + " .addNode(\"step2\", step2)\n", + " .addNode(\"step3\", step3)\n", + " .addEdge(START, \"step1\")\n", + " .addEdge(\"step1\", \"step2\")\n", + " .addEdge(\"step2\", \"step3\")\n", + " .addEdge(\"step3\", END);\n", "\n", "\n", "// Set up memory\n", - "const memory = new MemorySaver()\n", + "const graphStateMemory = new MemorySaver()\n", "\n", - "// Add \n", "const graph = builder.compile({\n", - " checkpointer: memory,\n", - " interruptBefore: [\"step3\"]\n", + " checkpointer: graphStateMemory,\n", + " interruptBefore: [\"step3\"]\n", "});" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "c2dd360b", "metadata": {}, "outputs": [ @@ -134,11 +129,11 @@ "source": [ "import * as tslab from \"tslab\";\n", "\n", - "const drawableGraph = graph.getGraph();\n", - "const image = await drawableGraph.drawMermaidPng();\n", - "const arrayBuffer = await image.arrayBuffer();\n", + "const drawableGraphGraphState = graph.getGraph();\n", + "const graphStateImage = await drawableGraphGraphState.drawMermaidPng();\n", + "const graphStateArrayBuffer = await graphStateImage.arrayBuffer();\n", "\n", - "await tslab.display.png(new Uint8Array(arrayBuffer));" + "await tslab.display.png(new Uint8Array(graphStateArrayBuffer));" ] }, { @@ -155,7 +150,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 7, "id": "dfe04a7f-988e-4a36-8ce8-2c49fab0130a", "metadata": {}, "outputs": [ @@ -179,11 +174,11 @@ "const initialInput = { input: \"hello world\" };\n", "\n", "// Thread\n", - "const config = { configurable: { thread_id: \"1\" }, streamMode: \"values\" as const };\n", + "const graphStateConfig = { configurable: { thread_id: \"1\" }, streamMode: \"values\" as const };\n", "\n", "// Run the graph until the first interruption\n", - "for await (const event of await graph.stream(initialInput, config)) {\n", - " console.log(`--- ${event.input} ---`);\n", + "for await (const event of await graph.stream(initialInput, graphStateConfig)) {\n", + " console.log(`--- ${event.input} ---`);\n", "}\n", "\n", "// Will log when the graph is interrupted, after step 2.\n", @@ -191,8 +186,8 @@ "\n", "// If approved, continue the graph execution. We must pass `null` as\n", "// the input here, or the graph will\n", - "for await (const event of await graph.stream(null, config)) {\n", - " console.log(`--- ${event.input} ---`);\n", + "for await (const event of await graph.stream(null, graphStateConfig)) {\n", + " console.log(`--- ${event.input} ---`);\n", "}\n" ] }, @@ -212,7 +207,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 8, "id": "6098e5cb", "metadata": {}, "outputs": [], @@ -221,16 +216,18 @@ "import { ChatAnthropic } from \"@langchain/anthropic\";\n", "import { tool } from \"@langchain/core/tools\";\n", "import { StateGraph, START, END } from \"@langchain/langgraph\";\n", - "import { MemorySaver } from \"@langchain/langgraph\";\n", + "import { MemorySaver, Annotation } from \"@langchain/langgraph\";\n", "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "import { BaseMessage, AIMessage } from \"@langchain/core/messages\";\n", "import { z } from \"zod\";\n", "\n", - "interface MessagesState {\n", - " messages: BaseMessage[];\n", - "}\n", + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});\n", "\n", - "const search = tool((input) => {\n", + "const search = tool((_) => {\n", " return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\";\n", "}, {\n", " name: \"search\",\n", @@ -239,7 +236,7 @@ "})\n", "\n", "const tools = [search]\n", - "const toolNode = new ToolNode(tools)\n", + "const toolNode = new ToolNode(tools)\n", "\n", "// Set up the model\n", "const model = new ChatAnthropic({ model: \"claude-3-5-sonnet-20240620\" })\n", @@ -249,7 +246,7 @@ "// Define nodes and conditional edges\n", "\n", "// Define the function that determines whether to continue or not\n", - "function shouldContinue(state: MessagesState): \"action\" | typeof END {\n", + "function shouldContinue(state: typeof AgentState.State): \"action\" | typeof END {\n", " const lastMessage = state.messages[state.messages.length - 1];\n", " // If there is no function call, then we finish\n", " if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n", @@ -260,7 +257,7 @@ "}\n", "\n", "// Define the function that calls the model\n", - "async function callModel(state: MessagesState): Promise> {\n", + "async function callModel(state: typeof AgentState.State): Promise> {\n", " const messages = state.messages;\n", " const response = await modelWithTools.invoke(messages);\n", " // We return an object with a messages property, because this will get added to the existing list\n", @@ -268,13 +265,7 @@ "}\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: (a: any, b: any) => a.concat(b)\n", - " },\n", - " }\n", - "})\n", + "const workflow = new StateGraph(AgentState)\n", " // Define the two nodes we will cycle between\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"action\", toolNode)\n", @@ -308,7 +299,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 9, "id": "4476aef1", "metadata": {}, "outputs": [ @@ -344,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ @@ -358,11 +349,11 @@ "[\n", " {\n", " type: 'text',\n", - " text: \"Certainly! I'll search for the current weather in San Francisco for you. Let me use the search function to find this information.\"\n", + " text: \"Certainly! I'll search for the current weather in San Francisco for you using the search function. Let me do that right away.\"\n", " },\n", " {\n", " type: 'tool_use',\n", - " id: 'toolu_01Mo7noa5MEewbwAKyFDK8mg',\n", + " id: 'toolu_01XsoaWr4EFgzEQ2x3EgVMdJ',\n", " name: 'search',\n", " input: { input: 'current weather in San Francisco' }\n", " }\n", @@ -403,7 +394,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 11, "id": "51923913-20f7-4ee1-b9ba-d01f5fb2869b", "metadata": {}, "outputs": [ @@ -416,11 +407,11 @@ "================================ ai Message (1) =================================\n", "Based on the search results, I can provide you with information about the current weather in San Francisco:\n", "\n", - "The weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. It's a great day to be outdoors or enjoy activities in the city.\n", + "The weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. It's a great day to be outdoors if you have any plans!\n", "\n", - "However, the search result also includes an unusual comment about Geminis. This appears to be unrelated to the weather and might be a joke or reference from the source. For accurate and detailed weather information, it would be best to check a reliable weather service or website.\n", + "However, the search result also includes an unusual statement about Geminis. This appears to be unrelated to the weather and might be a joke or a reference to an astrological forecast that was mixed in with the weather information. I would advise focusing on the weather information, which is what you asked about.\n", "\n", - "Is there anything else you'd like to know about the weather in San Francisco or any other information you need?\n" + "Is there anything else you'd like to know about the weather in San Francisco or any other location?\n" ] } ], diff --git a/examples/how-tos/configuration.ipynb b/examples/how-tos/configuration.ipynb index 66b3a247..28159963 100644 --- a/examples/how-tos/configuration.ipynb +++ b/examples/how-tos/configuration.ipynb @@ -68,22 +68,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "bdf2fe0f", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[WARN]: You have enabled LangSmith tracing without backgrounding callbacks.\n", - "[WARN]: If you are not using a serverless environment where you must wait for tracing calls to finish,\n", - "[WARN]: we suggest setting \"process.env.LANGCHAIN_CALLBACKS_BACKGROUND=true\" to avoid additional latency.\n" - ] - } - ], + "outputs": [], "source": [ - "import { BaseMessage, HumanMessage } from \"@langchain/core/messages\";\n", + "import { BaseMessage } from \"@langchain/core/messages\";\n", "import { ChatOpenAI } from \"@langchain/openai\";\n", "import { ChatAnthropic } from \"@langchain/anthropic\";\n", "import { ChatPromptTemplate } from \"@langchain/core/prompts\";\n", @@ -93,27 +83,20 @@ " MemorySaver,\n", " START,\n", " StateGraph,\n", - " StateGraphArgs,\n", + " Annotation,\n", "} from \"@langchain/langgraph\";\n", "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - " userInfo: string;\n", - "}\n", - "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - " userInfo: {\n", - " value: (x?: string, y?: string) => {\n", + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + " userInfo: Annotation({\n", + " reducer: (x, y) => {\n", " return y ? y : x ? x : \"N/A\";\n", " },\n", " default: () => \"N/A\",\n", - " },\n", - "};\n", + " })\n", + "});\n", "\n", "const promptTemplate = ChatPromptTemplate.fromMessages([\n", " [\"system\", \"You are a helpful assistant.\\n\\n## User Info:\\n{userInfo}\"],\n", @@ -121,7 +104,7 @@ "]);\n", "\n", "const callModel = async (\n", - " state: { messages: BaseMessage[]; userInfo: string },\n", + " state: typeof AgentState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " const { messages, userInfo } = state;\n", @@ -141,7 +124,7 @@ "};\n", "\n", "const fetchUserInformation = async (\n", - " _: { messages: BaseMessage[] },\n", + " _: typeof AgentState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " const userDB = {\n", @@ -169,9 +152,7 @@ " return { userInfo: \"N/A\" };\n", "};\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(AgentState)\n", " .addNode(\"fetchUserInfo\", fetchUserInformation)\n", " .addNode(\"agent\", callModel)\n", " .addEdge(START, \"fetchUserInfo\")\n", @@ -207,13 +188,15 @@ "Could you remind me of my email??\n", "-----\n", "\n", - "Of course, John. Your email is jod@langchain.ai.\n", + "Sure, John. Your email is jod@langchain.ai.\n", "-----\n", "\n" ] } ], "source": [ + "import { HumanMessage } from \"@langchain/core/messages\";\n", + "\n", "const config = {\n", " configurable: {\n", " model: \"openai\",\n", @@ -266,14 +249,8 @@ "\n", "Could you remind me of my email??\n", "-----\n", - "\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Certainly, Jane. Your email is jad@langchain.ai.\n", + "\n", + "Sure, Jane. Your email is jad@langchain.ai.\n", "-----\n", "\n" ] @@ -307,14 +284,6 @@ "}" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "88d8db9c", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "f000b97c", diff --git a/examples/how-tos/dynamically-returning-directly.ipynb b/examples/how-tos/dynamically-returning-directly.ipynb index 99de03a8..defa341f 100644 --- a/examples/how-tos/dynamically-returning-directly.ipynb +++ b/examples/how-tos/dynamically-returning-directly.ipynb @@ -207,18 +207,14 @@ }, "outputs": [], "source": [ - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", - "\n", - "interface AgentState {\n", - " messages: BaseMessage[];\n", - "}\n", + "import { Annotation } from \"@langchain/langgraph\";\n", + "import { BaseMessage } from \"@langchain/core/messages\";\n", "\n", - "const agentState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -266,7 +262,7 @@ "import { AIMessage } from \"@langchain/core/messages\";\n", "\n", "// Define the function that determines whether to continue or not\n", - "const shouldContinue = (state: AgentState) => {\n", + "const shouldContinue = (state: typeof AgentState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If there is no function call, then we finish\n", @@ -284,7 +280,7 @@ "};\n", "\n", "// Define the function that calls the model\n", - "const callModel = async (state: AgentState, config?: RunnableConfig) => {\n", + "const callModel = async (state: typeof AgentState.State, config?: RunnableConfig) => {\n", " const messages = state.messages;\n", " const response = await boundModel.invoke(messages, config);\n", " // We return an object, because this will get added to the existing list\n", @@ -312,7 +308,7 @@ "import { START, StateGraph } from \"@langchain/langgraph\";\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({ channels: agentState })\n", + "const workflow = new StateGraph(AgentState)\n", " // Define the two nodes we will cycle between\n", " .addNode(\"agent\", callModel)\n", " // Note the \"action\" and \"final\" nodes are identical!\n", @@ -359,13 +355,7 @@ "text": [ "[human]: what is the weather in sf\n", "-----\n", - "\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "\n", "[ai]: \n", "Tools: \n", "- search({\"query\":\"weather in San Francisco\"})\n", @@ -455,14 +445,6 @@ "```\n", "```" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e7f7cbc2-6b5d-4708-bed9-4a977fd72476", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/examples/how-tos/edit-graph-state.ipynb b/examples/how-tos/edit-graph-state.ipynb index b300761f..b26d70ac 100644 --- a/examples/how-tos/edit-graph-state.ipynb +++ b/examples/how-tos/edit-graph-state.ipynb @@ -52,7 +52,7 @@ "\n", "Below, we do two things:\n", "\n", - "1) We specify the [breakpoint](https://langchain-ai.github.io/langgraph/concepts/low_level/#breakpoints) using `interrupt_before` a specified step (node).\n", + "1) We specify the [breakpoint](https://langchain-ai.github.io/langgraph/concepts/low_level/#breakpoints) using `interruptBefore` a specified step (node).\n", "\n", "2) We set up a [checkpointer](https://langchain-ai.github.io/langgraphjs/concepts/#checkpoints) to save the state of the graph up until this node.\n", "\n", @@ -66,50 +66,45 @@ "metadata": {}, "outputs": [], "source": [ - "import { StateGraph, START, END } from \"@langchain/langgraph\";\n", + "import { StateGraph, START, END, Annotation } from \"@langchain/langgraph\";\n", "import { MemorySaver } from \"@langchain/langgraph\";\n", "\n", - "type GraphState = {\n", - " input: string;\n", - "}\n", + "const GraphState = Annotation.Root({\n", + " input: Annotation\n", + "});\n", "\n", - "const step1 = (state: GraphState): Partial => {\n", - " console.log(\"--- Step 1 ---\");\n", - " return state;\n", + "const step1 = (state: typeof GraphState.State) => {\n", + " console.log(\"---Step 1---\");\n", + " return state;\n", "}\n", "\n", - "const step2 = (state: GraphState): Partial => {\n", - " console.log(\"--- Step 2 ---\");\n", - " return state;\n", + "const step2 = (state: typeof GraphState.State) => {\n", + " console.log(\"---Step 2---\");\n", + " return state;\n", "}\n", "\n", - "const step3 = (state: GraphState): Partial => {\n", - " console.log(\"--- Step 3 ---\");\n", - " return state;\n", + "const step3 = (state: typeof GraphState.State) => {\n", + " console.log(\"---Step 3---\");\n", + " return state;\n", "}\n", "\n", "\n", - "const builder = new StateGraph({\n", - " channels: {\n", - " input: null,\n", - " }\n", - "})\n", - " .addNode(\"step1\", step1)\n", - " .addNode(\"step2\", step2)\n", - " .addNode(\"step3\", step3)\n", - " .addEdge(START, \"step1\")\n", - " .addEdge(\"step1\", \"step2\")\n", - " .addEdge(\"step2\", \"step3\")\n", - " .addEdge(\"step3\", END);\n", + "const builder = new StateGraph(GraphState)\n", + " .addNode(\"step1\", step1)\n", + " .addNode(\"step2\", step2)\n", + " .addNode(\"step3\", step3)\n", + " .addEdge(START, \"step1\")\n", + " .addEdge(\"step1\", \"step2\")\n", + " .addEdge(\"step2\", \"step3\")\n", + " .addEdge(\"step3\", END);\n", "\n", "\n", "// Set up memory\n", - "const memory = new MemorySaver()\n", + "const graphStateMemory = new MemorySaver()\n", "\n", - "// Add \n", "const graph = builder.compile({\n", - " checkpointer: memory,\n", - " interruptBefore: [\"step2\"]\n", + " checkpointer: graphStateMemory,\n", + " interruptBefore: [\"step2\"]\n", "});" ] }, @@ -130,11 +125,11 @@ "source": [ "import * as tslab from \"tslab\";\n", "\n", - "const drawableGraph = graph.getGraph();\n", - "const image = await drawableGraph.drawMermaidPng();\n", - "const arrayBuffer = await image.arrayBuffer();\n", + "const drawableGraphGraphState = graph.getGraph();\n", + "const graphStateImage = await drawableGraphGraphState.drawMermaidPng();\n", + "const graphStateArrayBuffer = await graphStateImage.arrayBuffer();\n", "\n", - "await tslab.display.png(new Uint8Array(arrayBuffer));" + "await tslab.display.png(new Uint8Array(graphStateArrayBuffer));" ] }, { @@ -148,7 +143,7 @@ "output_type": "stream", "text": [ "--- hello world ---\n", - "--- Step 1 ---\n", + "---Step 1---\n", "--- hello world ---\n", "--- GRAPH INTERRUPTED ---\n" ] @@ -159,10 +154,10 @@ "const initialInput = { input: \"hello world\" };\n", "\n", "// Thread\n", - "const config = { configurable: { thread_id: \"1\" }, streamMode: \"values\" as const };\n", + "const graphStateConfig = { configurable: { thread_id: \"1\" }, streamMode: \"values\" as const };\n", "\n", "// Run the graph until the first interruption\n", - "for await (const event of await graph.stream(initialInput, config)) {\n", + "for await (const event of await graph.stream(initialInput, graphStateConfig)) {\n", " console.log(`--- ${event.input} ---`);\n", "}\n", "\n", @@ -199,19 +194,19 @@ ], "source": [ "console.log(\"Current state!\")\n", - "const currState = await graph.getState(config);\n", + "const currState = await graph.getState(graphStateConfig);\n", "console.log(currState.values)\n", "\n", - "await graph.updateState(config, { input: \"hello universe!\" })\n", + "await graph.updateState(graphStateConfig, { input: \"hello universe!\" })\n", "\n", "console.log(\"---\\n---\\nUpdated state!\")\n", - "const updatedState = await graph.getState(config);\n", + "const updatedState = await graph.getState(graphStateConfig);\n", "console.log(updatedState.values)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "cf77f6eb-4cc0-4615-a095-eb5ae7027b7a", "metadata": {}, "outputs": [ @@ -219,16 +214,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "--- Step 2 ---\n", - "--- hello world ---\n", - "--- Step 3 ---\n", - "--- hello world ---\n" + "---Step 2---\n", + "--- hello universe! ---\n", + "---Step 3---\n", + "--- hello universe! ---\n" ] } ], "source": [ "// Continue the graph execution\n", - "for await (const event of await graph.stream(null, config)) {\n", + "for await (const event of await graph.stream(null, graphStateConfig)) {\n", " console.log(`--- ${event.input} ---`);\n", "}" ] @@ -249,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "6098e5cb", "metadata": {}, "outputs": [], @@ -258,26 +253,28 @@ "import { ChatAnthropic } from \"@langchain/anthropic\";\n", "import { tool } from \"@langchain/core/tools\";\n", "import { StateGraph, START, END } from \"@langchain/langgraph\";\n", - "import { MemorySaver } from \"@langchain/langgraph\";\n", + "import { MemorySaver, messagesStateReducer } from \"@langchain/langgraph\";\n", "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "import { BaseMessage, AIMessage } from \"@langchain/core/messages\";\n", "import { z } from \"zod\";\n", "import { v4 as uuidv4 } from \"uuid\";\n", "\n", - "interface MessagesState {\n", - " messages: BaseMessage[];\n", - "}\n", + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: messagesStateReducer\n", + " }),\n", + "});\n", "\n", - "const search = tool((input) => {\n", - " return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\";\n", + "const search = tool((_) => {\n", + " return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\";\n", "}, {\n", - " name: \"search\",\n", - " description: \"Call to surf the web.\",\n", - " schema: z.string(),\n", + " name: \"search\",\n", + " description: \"Call to surf the web.\",\n", + " schema: z.string(),\n", "})\n", "\n", "const tools = [search]\n", - "const toolNode = new ToolNode(tools)\n", + "const toolNode = new ToolNode(tools)\n", "\n", "// Set up the model\n", "const model = new ChatAnthropic({ model: \"claude-3-5-sonnet-20240620\" })\n", @@ -287,90 +284,43 @@ "// Define nodes and conditional edges\n", "\n", "// Define the function that determines whether to continue or not\n", - "function shouldContinue(state: MessagesState): \"action\" | typeof END {\n", - " const lastMessage = state.messages[state.messages.length - 1];\n", - " // If there is no function call, then we finish\n", - " if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n", - " return END;\n", - " }\n", - " // Otherwise if there is, we continue\n", - " return \"action\";\n", + "function shouldContinue(state: typeof AgentState.State): \"action\" | typeof END {\n", + " const lastMessage = state.messages[state.messages.length - 1];\n", + " // If there is no function call, then we finish\n", + " if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n", + " return END;\n", + " }\n", + " // Otherwise if there is, we continue\n", + " return \"action\";\n", "}\n", "\n", "// Define the function that calls the model\n", - "async function callModel(state: MessagesState): Promise> {\n", - " const messages = state.messages;\n", - " const response = await modelWithTools.invoke(messages);\n", - " // We return an object with a messages property, because this will get added to the existing list\n", - " return { messages: [response] };\n", - "}\n", - "\n", - "type Messages = BaseMessage | BaseMessage[]\n", - "\n", - "// Define our addMessages function which will add or merge messages to state\n", - "function addMessages(left: Messages, right: Messages): BaseMessage[] {\n", - " // Coerce to list\n", - " const leftList = Array.isArray(left) ? left : [left];\n", - " const rightList = Array.isArray(right) ? right : [right];\n", - "\n", - " // Assign missing ids\n", - " leftList.forEach(m => {\n", - " if (!m.id) m.id = uuidv4();\n", - " });\n", - " rightList.forEach(m => {\n", - " if (!m.id) m.id = uuidv4();\n", - " });\n", - "\n", - " // Merge\n", - " const leftIdxById = new Map(leftList.map((m, i) => [m.id, i]));\n", - " const merged = [...leftList];\n", - " const idsToRemove = new Set();\n", - "\n", - " for (const m of rightList) {\n", - " const existingIdx = leftIdxById.get(m.id!);\n", - " if (existingIdx !== undefined) {\n", - " if (m._getType() === 'remove' && m.id) {\n", - " idsToRemove.add(m.id);\n", - " } else {\n", - " merged[existingIdx] = m;\n", - " }\n", - " } else {\n", - " if (m._getType() === 'remove') {\n", - " throw new Error(`Attempting to delete a message with an ID that doesn't exist ('${m.id}')`);\n", - " }\n", - " merged.push(m);\n", - " }\n", - " }\n", - "\n", - " return merged.filter(m => !idsToRemove.has(m.id!));\n", + "async function callModel(state: typeof AgentState.State): Promise> {\n", + " const messages = state.messages;\n", + " const response = await modelWithTools.invoke(messages);\n", + " // We return an object with a messages property, because this will get added to the existing list\n", + " return { messages: [response] };\n", "}\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: addMessages\n", - " },\n", - " }\n", - "})\n", - " // Define the two nodes we will cycle between\n", - " .addNode(\"agent\", callModel)\n", - " .addNode(\"action\", toolNode)\n", - " // We now add a conditional edge\n", - " .addConditionalEdges(\n", - " // First, we define the start node. We use `agent`.\n", - " // This means these are the edges taken after the `agent` node is called.\n", - " \"agent\",\n", - " // Next, we pass in the function that will determine which node is called next.\n", - " shouldContinue\n", - " )\n", - " // We now add a normal edge from `action` to `agent`.\n", - " // This means that after `action` is called, `agent` node is called next.\n", - " .addEdge(\"action\", \"agent\")\n", - " // Set the entrypoint as `agent`\n", - " // This means that this node is the first one called\n", - " .addEdge(START, \"agent\");\n", - "\n", + "const workflow = new StateGraph(AgentState)\n", + " // Define the two nodes we will cycle between\n", + " .addNode(\"agent\", callModel)\n", + " .addNode(\"action\", toolNode)\n", + " // We now add a conditional edge\n", + " .addConditionalEdges(\n", + " // First, we define the start node. We use `agent`.\n", + " // This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " // Next, we pass in the function that will determine which node is called next.\n", + " shouldContinue\n", + " )\n", + " // We now add a normal edge from `action` to `agent`.\n", + " // This means that after `action` is called, `agent` node is called next.\n", + " .addEdge(\"action\", \"agent\")\n", + " // Set the entrypoint as `agent`\n", + " // This means that this node is the first one called\n", + " .addEdge(START, \"agent\");\n", "\n", "// Setup memory\n", "const memory = new MemorySaver();\n", @@ -379,14 +329,14 @@ "// This compiles it into a LangChain Runnable,\n", "// meaning you can use it as you would any other runnable\n", "const app = workflow.compile({\n", - " checkpointer: memory,\n", - " interruptBefore: [\"action\"]\n", + " checkpointer: memory,\n", + " interruptBefore: [\"action\"]\n", "});" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "9b011246", "metadata": {}, "outputs": [ @@ -420,7 +370,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ @@ -434,11 +384,11 @@ "[\n", " {\n", " type: 'text',\n", - " text: \"Certainly! I'll search for the current weather in San Francisco for you. Let me use the search function to find this information.\"\n", + " text: 'Certainly! I can help you search for the current weather in San Francisco. Let me use the search function to find that information for you.'\n", " },\n", " {\n", " type: 'tool_use',\n", - " id: 'toolu_019m2mL3asxj6BW4S5wSXCWL',\n", + " id: 'toolu_0141zTpknasyWkrjTV6eKeT6',\n", " name: 'search',\n", " input: { input: 'current weather in San Francisco' }\n", " }\n", @@ -475,7 +425,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "id": "1aa7b1b9-9322-4815-bc0d-eb083870ac15", "metadata": {}, "outputs": [ @@ -483,8 +433,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "currentState.messages 2\n", - "currentState2.messages 2\n" + "{\n", + " configurable: {\n", + " thread_id: '3',\n", + " checkpoint_id: '1ef5e785-4298-6b71-8002-4a6ceca964db'\n", + " }\n", + "}\n" ] } ], @@ -518,7 +472,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 10, "id": "a3fcf2bd-f881-49fe-b20e-ad16e6819bc6", "metadata": {}, "outputs": [ @@ -530,7 +484,7 @@ " {\n", " name: 'search',\n", " args: { query: 'current weather in SF' },\n", - " id: 'toolu_01AnDhM9nYbBi5zqhHBSxqbo',\n", + " id: 'toolu_0141zTpknasyWkrjTV6eKeT6',\n", " type: 'tool_call'\n", " }\n", "]\n" @@ -538,8 +492,8 @@ } ], "source": [ - "const currentState = await app.getState(config);\n", - "const updatedStateToolCalls = currentState.values.messages[currentState.values.messages.length -1 ].tool_calls\n", + "const newState = await app.getState(config);\n", + "const updatedStateToolCalls = newState.values.messages[newState.values.messages.length -1 ].tool_calls\n", "console.log(updatedStateToolCalls)" ] }, @@ -555,7 +509,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 11, "id": "51923913-20f7-4ee1-b9ba-d01f5fb2869b", "metadata": {}, "outputs": [ @@ -566,21 +520,21 @@ "{\n", " messages: [\n", " HumanMessage {\n", - " \"id\": \"510e408e-aaba-46a4-8218-c16da3d755bb\",\n", + " \"id\": \"7c69c1f3-914b-4236-b2ca-ef250e72cb7a\",\n", " \"content\": \"search for the weather in sf now\",\n", " \"additional_kwargs\": {},\n", " \"response_metadata\": {}\n", " },\n", " AIMessage {\n", - " \"id\": \"msg_018u4DL6FmVizEeQA8wzVchP\",\n", + " \"id\": \"msg_0152mx7AweoRWa67HFsfyaif\",\n", " \"content\": [\n", " {\n", " \"type\": \"text\",\n", - " \"text\": \"Certainly! I'll search for the current weather in San Francisco for you. Let me use the search tool to find this information.\"\n", + " \"text\": \"Certainly! I can help you search for the current weather in San Francisco. Let me use the search function to find that information for you.\"\n", " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01AnDhM9nYbBi5zqhHBSxqbo\",\n", + " \"id\": \"toolu_0141zTpknasyWkrjTV6eKeT6\",\n", " \"name\": \"search\",\n", " \"input\": {\n", " \"input\": \"current weather in San Francisco\"\n", @@ -588,7 +542,7 @@ " }\n", " ],\n", " \"additional_kwargs\": {\n", - " \"id\": \"msg_018u4DL6FmVizEeQA8wzVchP\",\n", + " \"id\": \"msg_0152mx7AweoRWa67HFsfyaif\",\n", " \"type\": \"message\",\n", " \"role\": \"assistant\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", @@ -596,17 +550,17 @@ " \"stop_sequence\": null,\n", " \"usage\": {\n", " \"input_tokens\": 380,\n", - " \"output_tokens\": 82\n", + " \"output_tokens\": 84\n", " }\n", " },\n", " \"response_metadata\": {\n", - " \"id\": \"msg_018u4DL6FmVizEeQA8wzVchP\",\n", + " \"id\": \"msg_0152mx7AweoRWa67HFsfyaif\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", " \"stop_reason\": \"tool_use\",\n", " \"stop_sequence\": null,\n", " \"usage\": {\n", " \"input_tokens\": 380,\n", - " \"output_tokens\": 82\n", + " \"output_tokens\": 84\n", " },\n", " \"type\": \"message\",\n", " \"role\": \"assistant\"\n", @@ -617,19 +571,19 @@ " \"args\": {\n", " \"query\": \"current weather in SF\"\n", " },\n", - " \"id\": \"toolu_01AnDhM9nYbBi5zqhHBSxqbo\",\n", + " \"id\": \"toolu_0141zTpknasyWkrjTV6eKeT6\",\n", " \"type\": \"tool_call\"\n", " }\n", " ],\n", " \"invalid_tool_calls\": []\n", " },\n", " ToolMessage {\n", - " \"id\": \"543ed4a4-b75f-4603-b34f-198bdc6948aa\",\n", + " \"id\": \"ccf0d56f-477f-408a-b809-6900a48379e0\",\n", " \"content\": \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\",\n", " \"name\": \"search\",\n", " \"additional_kwargs\": {},\n", " \"response_metadata\": {},\n", - " \"tool_call_id\": \"toolu_01AnDhM9nYbBi5zqhHBSxqbo\"\n", + " \"tool_call_id\": \"toolu_0141zTpknasyWkrjTV6eKeT6\"\n", " }\n", " ]\n", "}\n", @@ -641,21 +595,21 @@ "{\n", " messages: [\n", " HumanMessage {\n", - " \"id\": \"510e408e-aaba-46a4-8218-c16da3d755bb\",\n", + " \"id\": \"7c69c1f3-914b-4236-b2ca-ef250e72cb7a\",\n", " \"content\": \"search for the weather in sf now\",\n", " \"additional_kwargs\": {},\n", " \"response_metadata\": {}\n", " },\n", " AIMessage {\n", - " \"id\": \"msg_018u4DL6FmVizEeQA8wzVchP\",\n", + " \"id\": \"msg_0152mx7AweoRWa67HFsfyaif\",\n", " \"content\": [\n", " {\n", " \"type\": \"text\",\n", - " \"text\": \"Certainly! I'll search for the current weather in San Francisco for you. Let me use the search tool to find this information.\"\n", + " \"text\": \"Certainly! I can help you search for the current weather in San Francisco. Let me use the search function to find that information for you.\"\n", " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01AnDhM9nYbBi5zqhHBSxqbo\",\n", + " \"id\": \"toolu_0141zTpknasyWkrjTV6eKeT6\",\n", " \"name\": \"search\",\n", " \"input\": {\n", " \"input\": \"current weather in San Francisco\"\n", @@ -663,7 +617,7 @@ " }\n", " ],\n", " \"additional_kwargs\": {\n", - " \"id\": \"msg_018u4DL6FmVizEeQA8wzVchP\",\n", + " \"id\": \"msg_0152mx7AweoRWa67HFsfyaif\",\n", " \"type\": \"message\",\n", " \"role\": \"assistant\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", @@ -671,17 +625,17 @@ " \"stop_sequence\": null,\n", " \"usage\": {\n", " \"input_tokens\": 380,\n", - " \"output_tokens\": 82\n", + " \"output_tokens\": 84\n", " }\n", " },\n", " \"response_metadata\": {\n", - " \"id\": \"msg_018u4DL6FmVizEeQA8wzVchP\",\n", + " \"id\": \"msg_0152mx7AweoRWa67HFsfyaif\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", " \"stop_reason\": \"tool_use\",\n", " \"stop_sequence\": null,\n", " \"usage\": {\n", " \"input_tokens\": 380,\n", - " \"output_tokens\": 82\n", + " \"output_tokens\": 84\n", " },\n", " \"type\": \"message\",\n", " \"role\": \"assistant\"\n", @@ -692,43 +646,43 @@ " \"args\": {\n", " \"query\": \"current weather in SF\"\n", " },\n", - " \"id\": \"toolu_01AnDhM9nYbBi5zqhHBSxqbo\",\n", + " \"id\": \"toolu_0141zTpknasyWkrjTV6eKeT6\",\n", " \"type\": \"tool_call\"\n", " }\n", " ],\n", " \"invalid_tool_calls\": []\n", " },\n", " ToolMessage {\n", - " \"id\": \"543ed4a4-b75f-4603-b34f-198bdc6948aa\",\n", + " \"id\": \"ccf0d56f-477f-408a-b809-6900a48379e0\",\n", " \"content\": \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\",\n", " \"name\": \"search\",\n", " \"additional_kwargs\": {},\n", " \"response_metadata\": {},\n", - " \"tool_call_id\": \"toolu_01AnDhM9nYbBi5zqhHBSxqbo\"\n", + " \"tool_call_id\": \"toolu_0141zTpknasyWkrjTV6eKeT6\"\n", " },\n", " AIMessage {\n", - " \"id\": \"msg_01M3UZHNs4MJKQ84BccxvVCe\",\n", - " \"content\": \"Based on the search results, I can provide you with information about the current weather in San Francisco:\\n\\nThe weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. \\n\\nHowever, the search result includes an unusual additional comment that doesn't seem directly related to the weather. It mentions something about Geminis, which is likely not relevant to the weather information you're seeking. \\n\\nIs there any specific aspect of the weather you'd like to know more about, such as temperature, wind conditions, or forecast for the rest of the day? I'd be happy to search for more detailed information if you need it.\",\n", + " \"id\": \"msg_01YJXesUpaB5PfhgmRBCwnnb\",\n", + " \"content\": \"Based on the search results, I can provide you with information about the current weather in San Francisco:\\n\\nThe weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. It's a great day to be outdoors or engage in activities that benefit from good weather.\\n\\nHowever, I should note that the search result included an unusual comment about Gemini zodiac signs. This appears to be unrelated to the weather and might be part of a joke or a reference to something else. For accurate and detailed weather information, I would recommend checking a reliable weather service or website for San Francisco.\\n\\nIs there anything else you'd like to know about the weather in San Francisco or any other information you need?\",\n", " \"additional_kwargs\": {\n", - " \"id\": \"msg_01M3UZHNs4MJKQ84BccxvVCe\",\n", + " \"id\": \"msg_01YJXesUpaB5PfhgmRBCwnnb\",\n", " \"type\": \"message\",\n", " \"role\": \"assistant\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", " \"stop_reason\": \"end_turn\",\n", " \"stop_sequence\": null,\n", " \"usage\": {\n", - " \"input_tokens\": 496,\n", - " \"output_tokens\": 138\n", + " \"input_tokens\": 498,\n", + " \"output_tokens\": 154\n", " }\n", " },\n", " \"response_metadata\": {\n", - " \"id\": \"msg_01M3UZHNs4MJKQ84BccxvVCe\",\n", + " \"id\": \"msg_01YJXesUpaB5PfhgmRBCwnnb\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", " \"stop_reason\": \"end_turn\",\n", " \"stop_sequence\": null,\n", " \"usage\": {\n", - " \"input_tokens\": 496,\n", - " \"output_tokens\": 138\n", + " \"input_tokens\": 498,\n", + " \"output_tokens\": 154\n", " },\n", " \"type\": \"message\",\n", " \"role\": \"assistant\"\n", @@ -736,9 +690,9 @@ " \"tool_calls\": [],\n", " \"invalid_tool_calls\": [],\n", " \"usage_metadata\": {\n", - " \"input_tokens\": 496,\n", - " \"output_tokens\": 138,\n", - " \"total_tokens\": 634\n", + " \"input_tokens\": 498,\n", + " \"output_tokens\": 154,\n", + " \"total_tokens\": 652\n", " }\n", " }\n", " ]\n", @@ -746,11 +700,11 @@ "================================ ai Message (1) =================================\n", "Based on the search results, I can provide you with information about the current weather in San Francisco:\n", "\n", - "The weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. \n", + "The weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. It's a great day to be outdoors or engage in activities that benefit from good weather.\n", "\n", - "However, the search result includes an unusual additional comment that doesn't seem directly related to the weather. It mentions something about Geminis, which is likely not relevant to the weather information you're seeking. \n", + "However, I should note that the search result included an unusual comment about Gemini zodiac signs. This appears to be unrelated to the weather and might be part of a joke or a reference to something else. For accurate and detailed weather information, I would recommend checking a reliable weather service or website for San Francisco.\n", "\n", - "Is there any specific aspect of the weather you'd like to know more about, such as temperature, wind conditions, or forecast for the rest of the day? I'd be happy to search for more detailed information if you need it.\n" + "Is there anything else you'd like to know about the weather in San Francisco or any other information you need?\n" ] } ], diff --git a/examples/how-tos/force-calling-a-tool-first.ipynb b/examples/how-tos/force-calling-a-tool-first.ipynb index 269d06c1..c7a97065 100644 --- a/examples/how-tos/force-calling-a-tool-first.ipynb +++ b/examples/how-tos/force-calling-a-tool-first.ipynb @@ -81,17 +81,7 @@ "metadata": { "lines_to_next_cell": 2 }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[WARN]: You have enabled LangSmith tracing without backgrounding callbacks.\n", - "[WARN]: If you are not using a serverless environment where you must wait for tracing calls to finish,\n", - "[WARN]: we suggest setting \"process.env.LANGCHAIN_CALLBACKS_BACKGROUND=true\" to avoid additional latency.\n" - ] - } - ], + "outputs": [], "source": [ "import { DynamicStructuredTool } from \"@langchain/core/tools\";\n", "import { z } from \"zod\";\n", @@ -216,25 +206,21 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "3db3dd6e", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", - "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", + "import { Annotation } from \"@langchain/langgraph\";\n", + "import { BaseMessage } from \"@langchain/core/messages\";\n", "\n", - "const agentState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -271,7 +257,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "eee5adc0", "metadata": {}, "outputs": [], @@ -281,7 +267,7 @@ "import { concat } from \"@langchain/core/utils/stream\";\n", "\n", "// Define logic that will be used to determine which conditional edge to go down\n", - "const shouldContinue = (state: IState) => {\n", + "const shouldContinue = (state: typeof AgentState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If there is no function call, then we finish\n", @@ -294,7 +280,7 @@ "\n", "// Define the function that calls the model\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof AgentState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " const { messages } = state;\n", @@ -326,7 +312,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "e9104fc7", "metadata": { "lines_to_next_cell": 2 @@ -334,7 +320,7 @@ "outputs": [], "source": [ "// This is the new first - the first call of the model we want to explicitly hard-code some action\n", - "const firstModel = async (state: IState) => {\n", + "const firstModel = async (state: typeof AgentState.State) => {\n", " const humanInput = state.messages[state.messages.length - 1].content || \"\";\n", " return {\n", " messages: [\n", @@ -371,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "de6da918", "metadata": { "lines_to_next_cell": 2 @@ -381,7 +367,7 @@ "import { END, START, StateGraph } from \"@langchain/langgraph\";\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({ channels: agentState })\n", + "const workflow = new StateGraph(AgentState)\n", " // Define the new entrypoint\n", " .addNode(\"first_agent\", firstModel)\n", " // Define the two nodes we will cycle between\n", @@ -436,7 +422,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "acaade41", "metadata": { "lines_to_next_cell": 2 @@ -446,13 +432,69 @@ "name": "stdout", "output_type": "stream", "text": [ - "{ first_agent: { messages: [ \u001b[36m[AIMessage]\u001b[39m ] } }\n", + "{\n", + " first_agent: {\n", + " messages: [\n", + " AIMessage {\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"what is the weather in sf\"\n", + " },\n", + " \"id\": \"tool_abcd123\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": []\n", + " }\n", + " ]\n", + " }\n", + "}\n", "-----\n", "\n", - "{ action: { messages: [ \u001b[36m[ToolMessage]\u001b[39m ] } }\n", + "{\n", + " action: {\n", + " messages: [\n", + " ToolMessage {\n", + " \"content\": \"Cold, with a low of 13 ℃\",\n", + " \"name\": \"search\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"tool_abcd123\"\n", + " }\n", + " ]\n", + " }\n", + "}\n", "-----\n", "\n", - "{ agent: { messages: [ \u001b[36m[AIMessageChunk]\u001b[39m ] } }\n", + "{\n", + " agent: {\n", + " messages: [\n", + " AIMessageChunk {\n", + " \"id\": \"chatcmpl-9y562g16z0MUNBJcS6nKMsDuFMRsS\",\n", + " \"content\": \"The current weather in San Francisco is cold, with a low of 13°C.\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"prompt\": 0,\n", + " \"completion\": 0,\n", + " \"finish_reason\": \"stop\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [],\n", + " \"tool_call_chunks\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 104,\n", + " \"output_tokens\": 18,\n", + " \"total_tokens\": 122\n", + " }\n", + " }\n", + " ]\n", + " }\n", + "}\n", "-----\n", "\n" ] @@ -470,14 +512,6 @@ " console.log(\"-----\\n\");\n", "}" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "241e2fc9-9bee-4f2c-a077-09472a7b5613", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -485,9 +519,9 @@ "encoding": "# -*- coding: utf-8 -*-" }, "kernelspec": { - "display_name": "Deno", + "display_name": "TypeScript", "language": "typescript", - "name": "deno" + "name": "tslab" }, "language_info": { "codemirror_mode": { diff --git a/examples/how-tos/human-in-the-loop.ipynb b/examples/how-tos/human-in-the-loop.ipynb index 40ded78e..1e7cba87 100644 --- a/examples/how-tos/human-in-the-loop.ipynb +++ b/examples/how-tos/human-in-the-loop.ipynb @@ -80,20 +80,14 @@ }, "outputs": [], "source": [ + "import { Annotation } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", - "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -117,17 +111,7 @@ "metadata": { "lines_to_next_cell": 2 }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[WARN]: You have enabled LangSmith tracing without backgrounding callbacks.\n", - "[WARN]: If you are not using a serverless environment where you must wait for tracing calls to finish,\n", - "[WARN]: we suggest setting \"process.env.LANGCHAIN_CALLBACKS_BACKGROUND=true\" to avoid additional latency.\n" - ] - } - ], + "outputs": [], "source": [ "import { DynamicStructuredTool } from \"@langchain/core/tools\";\n", "import { z } from \"zod\";\n", @@ -173,7 +157,7 @@ "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -254,7 +238,7 @@ "import { AIMessage } from \"@langchain/core/messages\";\n", "import { END } from \"@langchain/langgraph\";\n", "\n", - "const routeMessage = (state: IState) => {\n", + "const routeMessage = (state: typeof AgentState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If no tools are called, we can finish (respond to the user)\n", @@ -266,7 +250,7 @@ "};\n", "\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof AgentState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " const { messages } = state;\n", @@ -295,9 +279,7 @@ "import { START, StateGraph } from \"@langchain/langgraph\";\n", "import { MemorySaver } from \"@langchain/langgraph\";\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(AgentState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(START, \"agent\")\n", @@ -379,7 +361,7 @@ "output_type": "stream", "text": [ "[human]: What did I tell you my name was?\n", - "[ai]: You mentioned that your name is Bob. How can I help you, Bob?\n" + "[ai]: You mentioned that your name is Bob. How can I assist you, Bob?\n" ] } ], @@ -405,13 +387,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[human]: what's the weather in sf now?\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "[human]: what's the weather in sf now?\n", "[ai]: \n", "Tools: \n", "- search({\"query\":\"weather in San Francisco\"})\n" @@ -454,13 +430,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[tool]: It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "[tool]: It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\n", "[ai]: It seems like it's sunny in San Francisco at the moment. If you need more detailed weather information, feel free to ask!\n" ] } @@ -475,14 +445,6 @@ " prettyPrint(messages[messages.length - 1]);\n", "}" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8185b577-064b-4b5c-8198-4c4d4b6d6cee", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/examples/how-tos/manage-conversation-history.ipynb b/examples/how-tos/manage-conversation-history.ipynb index 3d6d8b61..d717e445 100644 --- a/examples/how-tos/manage-conversation-history.ipynb +++ b/examples/how-tos/manage-conversation-history.ipynb @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 1, "id": "2ca8a0a7", "metadata": {}, "outputs": [], @@ -72,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "378899a9-3b9a-4748-95b6-eb00e0828677", "metadata": {}, "outputs": [], @@ -80,14 +80,16 @@ "import { ChatAnthropic } from \"@langchain/anthropic\";\n", "import { tool } from \"@langchain/core/tools\";\n", "import { BaseMessage, AIMessage } from \"@langchain/core/messages\";\n", - "import { StateGraph, START, END } from \"@langchain/langgraph\";\n", + "import { StateGraph, Annotation, START, END } from \"@langchain/langgraph\";\n", "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "import { MemorySaver } from \"@langchain/langgraph\";\n", "import { z } from \"zod\";\n", "\n", - "interface MessagesState {\n", - " messages: BaseMessage[];\n", - "}\n", + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});\n", "\n", "const memory = new MemorySaver();\n", "\n", @@ -105,35 +107,29 @@ "\n", "\n", "const tools = [searchTool]\n", - "const toolNode = new ToolNode(tools)\n", + "const toolNode = new ToolNode(tools)\n", "const model = new ChatAnthropic({ model: \"claude-3-haiku-20240307\" })\n", "const boundModel = model.bindTools(tools)\n", "\n", - "async function shouldContinue(state: MessagesState): Promise<\"action\" | typeof END> {\n", - " const lastMessage = state.messages[state.messages.length - 1];\n", - " // If there is no function call, then we finish\n", - " if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n", - " return END;\n", - " }\n", - " // Otherwise if there is, we continue\n", - " return \"action\";\n", + "function shouldContinue(state: typeof AgentState.State): \"action\" | typeof END {\n", + " const lastMessage = state.messages[state.messages.length - 1];\n", + " // If there is no function call, then we finish\n", + " if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n", + " return END;\n", + " }\n", + " // Otherwise if there is, we continue\n", + " return \"action\";\n", "}\n", "\n", "// Define the function that calls the model\n", - "async function callModel(state: MessagesState) {\n", - " const response = await model.invoke(state.messages);\n", - " // We return an object, because this will get merged with the existing state\n", - " return { messages: [response] };\n", + "async function callModel(state: typeof AgentState.State) {\n", + " const response = await model.invoke(state.messages);\n", + " // We return an object, because this will get merged with the existing state\n", + " return { messages: [response] };\n", "}\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: (a: any, b: any) => a.concat(b)\n", - " },\n", - " }\n", - "})\n", + "const workflow = new StateGraph(AgentState)\n", " // Define the two nodes we will cycle between\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"action\", toolNode)\n", @@ -162,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "57b27553-21be-43e5-ac48-d1d0a3aa0dca", "metadata": {}, "outputs": [ @@ -173,7 +169,7 @@ "================================ human Message (1) =================================\n", "hi! I'm bob\n", "================================ ai Message (1) =================================\n", - "It's nice to meet you, Bob! As an AI assistant, I'm here to help out however I can. Feel free to ask me anything, and I'll do my best to provide useful information or assistance. How can I help you today?\n", + "Hello Bob! It's nice to meet you. I'm an AI assistant created by Anthropic. I'm here to help with any questions or tasks you may have. Please let me know if there's anything I can assist you with.\n", "\n", "\n", "================================= END =================================\n", @@ -182,7 +178,7 @@ "================================ human Message (2) =================================\n", "what's my name?\n", "================================ ai Message (2) =================================\n", - "You said your name is Bob, so your name is Bob.\n" + "Your name is Bob, as you introduced yourself earlier.\n" ] } ], @@ -224,7 +220,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "eb20430f", "metadata": {}, "outputs": [], @@ -232,18 +228,20 @@ "import { ChatAnthropic } from \"@langchain/anthropic\";\n", "import { tool } from \"@langchain/core/tools\";\n", "import { BaseMessage, AIMessage } from \"@langchain/core/messages\";\n", - "import { StateGraph, START, END } from \"@langchain/langgraph\";\n", + "import { StateGraph, Annotation, START, END } from \"@langchain/langgraph\";\n", "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "import { MemorySaver } from \"@langchain/langgraph\";\n", "import { z } from \"zod\";\n", "\n", - "interface MessagesState {\n", - " messages: BaseMessage[];\n", - "}\n", + "const MessageFilteringAgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});\n", "\n", - "const memory = new MemorySaver();\n", + "const messageFilteringMemory = new MemorySaver();\n", "\n", - "const searchTool = tool((_): string => {\n", + "const messageFilteringSearchTool = tool((_): string => {\n", " // This is a placeholder for the actual implementation\n", " // Don't let the LLM know this though 😊\n", " return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n", @@ -255,14 +253,14 @@ " })\n", "})\n", "\n", + "// We can re-use the same search tool as above as we don't need to change it for this example.\n", + "const messageFilteringTools = [messageFilteringSearchTool]\n", + "const messageFilteringToolNode = new ToolNode(messageFilteringTools)\n", + "const messageFilteringModel = new ChatAnthropic({ model: \"claude-3-haiku-20240307\" })\n", + "const boundMessageFilteringModel = messageFilteringModel.bindTools(messageFilteringTools)\n", "\n", - "const tools = [searchTool]\n", - "const toolNode = new ToolNode(tools)\n", - "const model = new ChatAnthropic({ model: \"claude-3-haiku-20240307\" })\n", - "const boundModel = model.bindTools(tools)\n", "\n", - "\n", - "async function shouldContinue(state: MessagesState): Promise<\"action\" | typeof END> {\n", + "async function shouldContinueMessageFiltering(state: typeof MessageFilteringAgentState.State): Promise<\"action\" | typeof END> {\n", " const lastMessage = state.messages[state.messages.length - 1];\n", " // If there is no function call, then we finish\n", " if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n", @@ -273,56 +271,49 @@ "}\n", "\n", "const filterMessages = (messages: BaseMessage[]): BaseMessage[] => {\n", - " // This is very simple helper function which only ever uses the last message\n", - " return messages.slice(-1);\n", + " // This is very simple helper function which only ever uses the last message\n", + " return messages.slice(-1);\n", "}\n", "\n", - "\n", "// Define the function that calls the model\n", - "async function callModel(state: MessagesState) {\n", - " const response = await model.invoke(filterMessages(state.messages));\n", - " // We return an object, because this will get merged with the existing state\n", - " return { messages: [response] };\n", + "async function callModelMessageFiltering(state: typeof MessageFilteringAgentState.State) {\n", + " const response = await boundMessageFilteringModel.invoke(filterMessages(state.messages));\n", + " // We return an object, because this will get merged with the existing state\n", + " return { messages: [response] };\n", "}\n", "\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: (a: any, b: any) => a.concat(b)\n", - " },\n", - " }\n", - "})\n", - " // Define the two nodes we will cycle between\n", - " .addNode(\"agent\", callModel)\n", - " .addNode(\"action\", toolNode)\n", - " // We now add a conditional edge\n", - " .addConditionalEdges(\n", - " // First, we define the start node. We use `agent`.\n", - " // This means these are the edges taken after the `agent` node is called.\n", - " \"agent\",\n", - " // Next, we pass in the function that will determine which node is called next.\n", - " shouldContinue\n", - " )\n", - " // We now add a normal edge from `action` to `agent`.\n", - " // This means that after `action` is called, `agent` node is called next.\n", - " .addEdge(\"action\", \"agent\")\n", - " // Set the entrypoint as `agent`\n", - " // This means that this node is the first one called\n", - " .addEdge(START, \"agent\");\n", + "const messageFilteringWorkflow = new StateGraph(MessageFilteringAgentState)\n", + " // Define the two nodes we will cycle between\n", + " .addNode(\"agent\", callModelMessageFiltering)\n", + " .addNode(\"action\", messageFilteringToolNode)\n", + " // We now add a conditional edge\n", + " .addConditionalEdges(\n", + " // First, we define the start node. We use `agent`.\n", + " // This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " // Next, we pass in the function that will determine which node is called next.\n", + " shouldContinueMessageFiltering\n", + " )\n", + " // We now add a normal edge from `action` to `agent`.\n", + " // This means that after `action` is called, `agent` node is called next.\n", + " .addEdge(\"action\", \"agent\")\n", + " // Set the entrypoint as `agent`\n", + " // This means that this node is the first one called\n", + " .addEdge(START, \"agent\");\n", "\n", "// Finally, we compile it!\n", "// This compiles it into a LangChain Runnable,\n", "// meaning you can use it as you would any other runnable\n", - "const app = workflow.compile({\n", - " checkpointer: memory,\n", + "const messageFilteringApp = messageFilteringWorkflow.compile({\n", + " checkpointer: messageFilteringMemory,\n", "});" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "52468ebb-4b23-45ac-a98e-b4439f37740a", "metadata": {}, "outputs": [ @@ -333,7 +324,7 @@ "================================ human Message (1) =================================\n", "hi! I'm bob\n", "================================ ai Message (1) =================================\n", - "Hello Bob! It's nice to meet you. As an AI assistant, I'm here to help with any questions or tasks you might have. Please feel free to ask me anything, and I'll do my best to assist you.\n", + "Hello, nice to meet you Bob! I'm an AI assistant here to help out. Feel free to let me know if you have any questions or if there's anything I can assist with.\n", "\n", "\n", "================================= END =================================\n", @@ -342,19 +333,19 @@ "================================ human Message (2) =================================\n", "what's my name?\n", "================================ ai Message (2) =================================\n", - "I'm afraid I don't actually know your name. As an AI assistant, I don't have personal information about you unless you choose to provide it to me. Could you please tell me your name?\n" + "I'm afraid I don't actually know your name, since you haven't provided that information to me. As an AI assistant, I don't have access to personal details about you unless you share them with me directly. I'm happy to continue our conversation, but I don't have enough context to know your specific name. Please feel free to introduce yourself if you'd like me to address you by name.\n" ] } ], "source": [ "import { HumanMessage } from \"@langchain/core/messages\";\n", "\n", - "const config = { configurable: { thread_id: \"2\"}, streamMode: \"values\" as const }\n", + "const messageFilteringConfig = { configurable: { thread_id: \"2\"}, streamMode: \"values\" as const }\n", "\n", - "const inputMessage = new HumanMessage(\"hi! I'm bob\");\n", - "for await (const event of await app.stream({\n", - " messages: [inputMessage]\n", - "}, config)) {\n", + "const messageFilteringInput = new HumanMessage(\"hi! I'm bob\");\n", + "for await (const event of await messageFilteringApp.stream({\n", + " messages: [messageFilteringInput]\n", + "}, messageFilteringConfig)) {\n", " const recentMsg = event.messages[event.messages.length - 1];\n", " console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n", " console.log(recentMsg.content);\n", @@ -362,10 +353,13 @@ "\n", "console.log(\"\\n\\n================================= END =================================\\n\\n\")\n", "\n", - "const inputMessage2 = new HumanMessage(\"what's my name?\");\n", - "for await (const event of await app.stream({\n", - " messages: [inputMessage2]\n", - "}, config)) {\n", + "const messageFilteringInput2 = new HumanMessage(\"what's my name?\");\n", + "for await (const event of await messageFilteringApp.stream(\n", + " {\n", + " messages: [messageFilteringInput2]\n", + " },\n", + " messageFilteringConfig\n", + ")) {\n", " const recentMsg = event.messages[event.messages.length - 1];\n", " console.log(`================================ ${recentMsg._getType()} Message (2) =================================`)\n", " console.log(recentMsg.content);\n", diff --git a/examples/how-tos/managing-agent-steps.ipynb b/examples/how-tos/managing-agent-steps.ipynb index 6ad15d16..81b6109f 100644 --- a/examples/how-tos/managing-agent-steps.ipynb +++ b/examples/how-tos/managing-agent-steps.ipynb @@ -95,19 +95,14 @@ "metadata": {}, "outputs": [], "source": [ + "import { Annotation } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", - "\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -126,7 +121,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "ec9f73a5", "metadata": {}, "outputs": [], @@ -164,14 +159,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "7f4829c3", "metadata": {}, "outputs": [], "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -195,7 +190,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "id": "cf1fcc3f", "metadata": {}, "outputs": [], @@ -210,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "id": "a0903bb8", "metadata": {}, "outputs": [], @@ -254,7 +249,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 8, "id": "1249b1b3", "metadata": {}, "outputs": [], @@ -264,7 +259,7 @@ "import { RunnableConfig } from \"@langchain/core/runnables\";\n", "\n", "// Define the function that determines whether to continue or not\n", - "const shouldContinue = (state: IState) => {\n", + "const shouldContinue = (state: typeof AgentState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If there is no function call, then we finish\n", @@ -279,7 +274,7 @@ "//\n", "// Here we don't pass all messages to the model but rather only pass the `N` most recent. Note that this is a terribly simplistic way to handle messages meant as an illustration, and there may be other methods you may want to look into depending on your use case. We also have to make sure we don't truncate the chat history to include the tool message first, as this would cause an API error.\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof AgentState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " let modelMessages = [];\n", @@ -311,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 9, "id": "ff5f7b65", "metadata": {}, "outputs": [], @@ -319,9 +314,7 @@ "import { START, StateGraph } from \"@langchain/langgraph\";\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(AgentState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(START, \"agent\")\n", @@ -351,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 10, "id": "7bd7315e", "metadata": {}, "outputs": [ @@ -398,19 +391,8 @@ "Tools: \n", "- search({\"query\":\"current weather in San Francisco\"})\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "GraphRecursionError: Recursion limit of 10 reached without hitting a stop condition. You can increase the limit by setting the \"recursionLimit\" config key.\n", - " at CompiledStateGraph._transform (/Users/wfh/code/lc/langgraphjs/langgraph/dist/pregel/index.cjs:432:27)\n", - "\u001b[90m at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\u001b[39m\n", - " at async CompiledStateGraph._transformStreamWithConfig (/Users/wfh/code/lc/langgraphjs/node_modules/\u001b[4m@langchain\u001b[24m/core/dist/runnables/base.cjs:290:30)\n", - " at async CompiledStateGraph.transform (/Users/wfh/code/lc/langgraphjs/langgraph/dist/pregel/index.cjs:527:26)\n", - " at async Object.pull (/Users/wfh/code/lc/langgraphjs/node_modules/\u001b[4m@langchain\u001b[24m/core/dist/utils/stream.cjs:96:41)\n" + "\n", + "As expected, maximum steps reached. Exiting.\n" ] } ], @@ -462,21 +444,13 @@ " }\n", "}" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "763c79c8-71b8-441f-a52a-d03708170d12", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Deno", + "display_name": "TypeScript", "language": "typescript", - "name": "deno" + "name": "tslab" }, "language_info": { "codemirror_mode": { diff --git a/examples/how-tos/persistence-postgres.ipynb b/examples/how-tos/persistence-postgres.ipynb index 94c4d700..c6b7dc26 100644 --- a/examples/how-tos/persistence-postgres.ipynb +++ b/examples/how-tos/persistence-postgres.ipynb @@ -104,14 +104,14 @@ " const client = await this.pool.connect();\n", " try {\n", " await client.query(`\n", - " CREATE TABLE IF NOT EXISTS checkpoints (\n", - " thread_id TEXT NOT NULL,\n", - " checkpoint_id TEXT NOT NULL,\n", - " parent_id TEXT,\n", - " checkpoint BYTEA NOT NULL,\n", - " metadata BYTEA NOT NULL,\n", - " PRIMARY KEY (thread_id, checkpoint_id)\n", - " );\n", + "CREATE TABLE IF NOT EXISTS checkpoints (\n", + " thread_id TEXT NOT NULL,\n", + " checkpoint_id TEXT NOT NULL,\n", + " parent_id TEXT,\n", + " checkpoint BYTEA NOT NULL,\n", + " metadata BYTEA NOT NULL,\n", + " PRIMARY KEY (thread_id, checkpoint_id)\n", + ");\n", " `);\n", " this.isSetup = true;\n", " } catch (error) {\n", @@ -314,7 +314,7 @@ }, "outputs": [], "source": [ - "process.env.OPENAI_API_KEY = \"sk-...\";" + "// process.env.OPENAI_API_KEY = \"sk-...\";" ] }, { @@ -336,20 +336,14 @@ }, "outputs": [], "source": [ + "import { Annotation } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", - "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const AgentState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -418,7 +412,7 @@ "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -501,7 +495,7 @@ "import { AIMessage } from \"@langchain/core/messages\";\n", "import { RunnableConfig } from \"@langchain/core/runnables\";\n", "\n", - "const routeMessage = (state: IState) => {\n", + "const routeMessage = (state: typeof AgentState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If no tools are called, we can finish (respond to the user)\n", @@ -513,7 +507,7 @@ "};\n", "\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof AgentState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " // For versions of @langchain/core < 0.2.3, you must call `.stream()`\n", @@ -523,9 +517,7 @@ " return { messages: [responseMessage] };\n", "};\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(AgentState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(START, \"agent\")\n", diff --git a/examples/how-tos/persistence.ipynb b/examples/how-tos/persistence.ipynb index 37557b42..ed58047c 100644 --- a/examples/how-tos/persistence.ipynb +++ b/examples/how-tos/persistence.ipynb @@ -25,11 +25,11 @@ "Example:\n", "\n", "```javascript\n", - "import { MemorySaver } from \"@langchain/langgraph\";\n", + "import { MemorySaver, Annotation } from \"@langchain/langgraph\";\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "});\n", + "const GraphState = Annotation.Root({ ... });\n", + "\n", + "const workflow = new StateGraph(GraphState);\n", "\n", "/// ... Add nodes and edges\n", "// Initialize any compatible CheckPointSaver\n", @@ -103,20 +103,14 @@ }, "outputs": [], "source": [ + "import { Annotation } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", - "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -185,7 +179,7 @@ "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -269,7 +263,7 @@ "import { AIMessage } from \"@langchain/core/messages\";\n", "import { RunnableConfig } from \"@langchain/core/runnables\";\n", "\n", - "const routeMessage = (state: IState) => {\n", + "const routeMessage = (state: typeof GraphState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If no tools are called, we can finish (respond to the user)\n", @@ -281,7 +275,7 @@ "};\n", "\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof GraphState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " const { messages } = state;\n", @@ -289,9 +283,7 @@ " return { messages: [response] };\n", "};\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(GraphState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(START, \"agent\")\n", @@ -311,23 +303,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'user'\u001b[39m, \u001b[32m\"Hi I'm Yu, niced to meet you.\"\u001b[39m ]\n", + "[ 'user', \"Hi I'm Yu, niced to meet you.\" ]\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nice to meet you, Yu! How can I assist you today?\n", + "\n", + "Hi Yu, nice to meet you too! How can I assist you today?\n", "-----\n", "\n" ] @@ -362,23 +341,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'user'\u001b[39m, \u001b[32m'Remember my name?'\u001b[39m ]\n", + "[ 'user', 'Remember my name?' ]\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "I cannot remember personalized details, including names, from previous interactions. However, I'd be happy to help you with any inquiries you have! How can I assist you today?\n", + "\n", + "I don't have memory of previous interactions, so I don't remember your name. Can you please tell me again?\n", "-----\n", "\n" ] @@ -441,22 +407,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'user'\u001b[39m, \u001b[32m\"Hi I'm Jo, niced to meet you.\"\u001b[39m ]\n", + "[ 'user', \"Hi I'm Jo, niced to meet you.\" ]\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "\n", "Hi Jo, nice to meet you too! How can I assist you today?\n", "-----\n", "\n" @@ -494,23 +447,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'user'\u001b[39m, \u001b[32m'Remember my name?'\u001b[39m ]\n", + "[ 'user', 'Remember my name?' ]\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Yes, your name is Jo. How can I assist you today?\n", + "\n", + "Of course, Jo! How can I help you today?\n", "-----\n", "\n" ] @@ -560,7 +500,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{ configurable: { thread_id: \u001b[32m'conversation-2'\u001b[39m } }\n" + "{ configurable: { thread_id: 'conversation-2' } }\n" ] } ], @@ -578,23 +518,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'user'\u001b[39m, \u001b[32m'you forgot?'\u001b[39m ]\n", + "[ 'user', 'you forgot?' ]\n", "-----\n", "\n" ] }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "Could you please provide more context or clarify what you're referring to? Let me know how I can assist you further!\n", + "I'm sorry, it seems like I missed something. Could you remind me what you're referring to?\n", "-----\n", "\n" ] diff --git a/examples/how-tos/respond-in-format.ipynb b/examples/how-tos/respond-in-format.ipynb index 1ba10480..b74deccb 100644 --- a/examples/how-tos/respond-in-format.ipynb +++ b/examples/how-tos/respond-in-format.ipynb @@ -108,18 +108,14 @@ }, "outputs": [], "source": [ + "import { Annotation, messagesStateReducer } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { StateGraphArgs, messagesStateReducer } from \"@langchain/langgraph\";\n", "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", - "\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", " reducer: messagesStateReducer,\n", - " },\n", - "};" + " }),\n", + "});" ] }, { @@ -142,7 +138,7 @@ "import { tool } from \"@langchain/core/tools\";\n", "import { z } from \"zod\";\n", "\n", - "const searchTool = tool(async ({}: { query: string }) => {\n", + "const searchTool = tool((_) => {\n", " // This is a placeholder, but don't tell the LLM that...\n", " return \"67 degrees. Cloudy with a chance of rain.\";\n", "}, {\n", @@ -176,7 +172,7 @@ "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -270,7 +266,7 @@ "import { RunnableConfig } from \"@langchain/core/runnables\";\n", "\n", "// Define the function that determines whether to continue or not\n", - "const route = (state: IState) => {\n", + "const route = (state: typeof GraphState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If there is no function call, then we finish\n", @@ -287,7 +283,7 @@ "\n", "// Define the function that calls the model\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof GraphState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " const { messages } = state;\n", @@ -315,9 +311,7 @@ "import { StateGraph } from \"@langchain/langgraph\";\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(GraphState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(\"__start__\", \"agent\")\n", @@ -506,7 +500,7 @@ " {\n", " name: 'Response',\n", " args: { temperature: 67, other_notes: 'Cloudy with a chance of rain.' },\n", - " id: 'call_rW75pHMufPX9DOhOTNZYb9ag',\n", + " id: 'call_oOhNx2SdeelXn6tbenokDtkO',\n", " type: 'tool_call'\n", " }\n", "]\n" @@ -551,14 +545,6 @@ "// Final aggregated tool call\n", "console.log(aggregatedChunk.tool_calls);" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dc0e8d87", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/examples/how-tos/stream-tokens.ipynb b/examples/how-tos/stream-tokens.ipynb index 7f92c8fd..c2c0c6f0 100644 --- a/examples/how-tos/stream-tokens.ipynb +++ b/examples/how-tos/stream-tokens.ipynb @@ -83,27 +83,21 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "1648124b", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ + "import { Annotation } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", - "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -118,7 +112,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "a8f1ae1c", "metadata": { "lines_to_next_cell": 2 @@ -128,7 +122,7 @@ "import { tool } from \"@langchain/core/tools\";\n", "import { z } from \"zod\";\n", "\n", - "const searchTool = tool(async ({ query: _query }) => {\n", + "const searchTool = tool((_) => {\n", " // This is a placeholder for the actual implementation\n", " return \"Cold, with a low of 3℃\";\n", "}, {\n", @@ -158,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "f02278b1", "metadata": { "lines_to_next_cell": 2 @@ -167,7 +161,7 @@ "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -195,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "9c7210e7", "metadata": { "lines_to_next_cell": 2 @@ -219,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "b4ff23ee", "metadata": { "lines_to_next_cell": 2 @@ -241,15 +235,15 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "0ba603bb", "metadata": {}, "outputs": [], "source": [ - "import { StateGraph } from \"@langchain/langgraph\";\n", + "import { StateGraph, END } from \"@langchain/langgraph\";\n", "import { AIMessage } from \"@langchain/core/messages\";\n", "\n", - "const routeMessage = (state: IState) => {\n", + "const routeMessage = (state: typeof GraphState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If no tools are called, we can finish (respond to the user)\n", @@ -261,8 +255,8 @@ "};\n", "\n", "const callModel = async (\n", - " state: IState,\n", - ") => {\n", + " state: typeof GraphState.State,\n", + "): Promise> => {\n", " // For versions of @langchain/core < 0.2.3, you must call `.stream()`\n", " // and aggregate the message from chunks instead of calling `.invoke()`.\n", " const { messages } = state;\n", @@ -270,9 +264,7 @@ " return { messages: [responseMessage] };\n", "};\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(GraphState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(\"__start__\", \"agent\")\n", @@ -284,13 +276,13 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "a88cf20a", "metadata": {}, "outputs": [ { "data": { - "image/png": "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" + "image/png": "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" }, "metadata": {}, "output_type": "display_data" @@ -320,7 +312,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 9, "id": "c704d23c", "metadata": {}, "outputs": [ @@ -332,16 +324,65 @@ " {\n", " name: 'search',\n", " args: '',\n", - " id: 'call_zHJ0ikC0Odbogo30L0HvrWNf',\n", - " index: 0\n", + " id: 'call_ziGo5u8fYyqQ78SdLZTEC9Vg',\n", + " index: 0,\n", + " type: 'tool_call_chunk'\n", + " }\n", + "]\n", + "[\n", + " {\n", + " name: undefined,\n", + " args: '{\"',\n", + " id: undefined,\n", + " index: 0,\n", + " type: 'tool_call_chunk'\n", + " }\n", + "]\n", + "[\n", + " {\n", + " name: undefined,\n", + " args: 'query',\n", + " id: undefined,\n", + " index: 0,\n", + " type: 'tool_call_chunk'\n", + " }\n", + "]\n", + "[\n", + " {\n", + " name: undefined,\n", + " args: '\":\"',\n", + " id: undefined,\n", + " index: 0,\n", + " type: 'tool_call_chunk'\n", " }\n", "]\n", - "[ { name: undefined, args: '{\"', id: undefined, index: 0 } ]\n", - "[ { name: undefined, args: 'query', id: undefined, index: 0 } ]\n", - "[ { name: undefined, args: '\":\"', id: undefined, index: 0 } ]\n", - "[ { name: undefined, args: 'current', id: undefined, index: 0 } ]\n", - "[ { name: undefined, args: ' weather', id: undefined, index: 0 } ]\n", - "[ { name: undefined, args: '\"}', id: undefined, index: 0 } ]\n" + "[\n", + " {\n", + " name: undefined,\n", + " args: 'current',\n", + " id: undefined,\n", + " index: 0,\n", + " type: 'tool_call_chunk'\n", + " }\n", + "]\n", + "[\n", + " {\n", + " name: undefined,\n", + " args: ' weather',\n", + " id: undefined,\n", + " index: 0,\n", + " type: 'tool_call_chunk'\n", + " }\n", + "]\n", + "[\n", + " {\n", + " name: undefined,\n", + " args: '\"}',\n", + " id: undefined,\n", + " index: 0,\n", + " type: 'tool_call_chunk'\n", + " }\n", + "]\n" ] } ], @@ -381,7 +422,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 10, "id": "86f843bb", "metadata": {}, "outputs": [ @@ -410,11 +451,11 @@ } ], "source": [ - "const eventStream = await agent.streamEvents(\n", + "const eventStreamFinalRes = await agent.streamEvents(\n", " { messages: [[\"user\", \"What's the weather like today?\"]] },\n", " { version: \"v2\" });\n", "\n", - "for await (const { event, data } of eventStream) {\n", + "for await (const { event, data } of eventStreamFinalRes) {\n", " if (event === \"on_chat_model_stream\") {\n", " const msg = data.chunk as AIMessageChunk;\n", " if (!msg.tool_call_chunks?.length) {\n", @@ -436,32 +477,21 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 11, "id": "0fea2f20", "metadata": {}, "outputs": [], "source": [ - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", + "import { HumanMessage } from \"@langchain/core/messages\";\n", "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};\n", + "const OtherGraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});\n", "\n", - "const callModel = async (\n", - " state: IState,\n", - ") => {\n", + "const respond = async (state: typeof OtherGraphState.State): Promise> => {\n", " const { messages } = state;\n", - " const responseMessage = await boundModel.invoke(messages);\n", - " return { messages: [responseMessage] };\n", - "};\n", - "\n", - "const respond = async ({ messages }) => {\n", " const model = new ChatOpenAI({ model: \"gpt-4o\", temperature: 0 });\n", " const responseMessage = await model.invoke(messages);\n", " return {\n", @@ -469,13 +499,14 @@ " }\n", "};\n", "\n", - "const summarize = async ({ messages }) => {\n", + "const summarize = async (state: typeof OtherGraphState.State): Promise> => {\n", + " const { messages } = state;\n", " // Assign the final model call a run name\n", " const model = new ChatOpenAI({\n", " model: \"gpt-4o\",\n", " temperature: 0\n", " }).withConfig({ runName: \"Summarizer\" });\n", - " const userMessage = [\"user\", \"Now, summarize the above messages\"];\n", + " const userMessage = new HumanMessage(\"Now, summarize the above messages\")\n", " const responseMessage = await model.invoke([\n", " ...messages,\n", " userMessage,\n", @@ -485,21 +516,19 @@ " };\n", "}\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const otherWorkflow = new StateGraph(OtherGraphState)\n", " .addNode(\"respond\", respond)\n", " .addNode(\"summarize\", summarize)\n", " .addEdge(\"__start__\", \"respond\")\n", " .addEdge(\"respond\", \"summarize\")\n", " .addEdge(\"summarize\", \"__end__\");\n", "\n", - "const graph = workflow.compile();" + "const otherGraph = otherWorkflow.compile();" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 12, "id": "2149f527", "metadata": {}, "outputs": [ @@ -512,11 +541,11 @@ } ], "source": [ - "const runnableGraph = graph.getGraph();\n", - "const image = await runnableGraph.drawMermaidPng();\n", - "const arrayBuffer = await image.arrayBuffer();\n", + "const otherRunnableGraph = otherGraph.getGraph();\n", + "const otherImage = await otherRunnableGraph.drawMermaidPng();\n", + "const otherArrayBuffer = await otherImage.arrayBuffer();\n", "\n", - "await tslab.display.png(new Uint8Array(arrayBuffer));" + "await tslab.display.png(new Uint8Array(otherArrayBuffer));" ] }, { @@ -529,7 +558,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 13, "id": "51381303", "metadata": {}, "outputs": [ @@ -540,7 +569,7 @@ "\n", "You\n", " asked\n", - " for\n", + " about\n", " the\n", " capital\n", " of\n", @@ -559,13 +588,13 @@ } ], "source": [ - "const eventStream = await graph.streamEvents(\n", + "const otherEventStream = await otherGraph.streamEvents(\n", " { messages: [[\"user\", \"What's the capital of Nepal?\"]] },\n", " { version: \"v2\" },\n", " { includeNames: [\"Summarizer\"] }\n", ");\n", "\n", - "for await (const { event, data } of eventStream) {\n", + "for await (const { event, data } of otherEventStream) {\n", " if (event === \"on_chat_model_stream\") {\n", " console.log(data.chunk.content);\n", " }\n", diff --git a/examples/how-tos/stream-updates.ipynb b/examples/how-tos/stream-updates.ipynb index 3f78e8d2..fbeb2ba2 100644 --- a/examples/how-tos/stream-updates.ipynb +++ b/examples/how-tos/stream-updates.ipynb @@ -19,14 +19,14 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "8e76833b", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ - "process.env.OPENAI_API_KEY = \"sk-...\";" + "// process.env.OPENAI_API_KEY = \"sk-...\";" ] }, { @@ -48,20 +48,14 @@ }, "outputs": [], "source": [ + "import { Annotation } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", - "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -130,7 +124,7 @@ "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -214,7 +208,7 @@ "import { AIMessage } from \"@langchain/core/messages\";\n", "import { RunnableConfig } from \"@langchain/core/runnables\";\n", "\n", - "const routeMessage = (state: IState) => {\n", + "const routeMessage = (state: typeof GraphState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If no tools are called, we can finish (respond to the user)\n", @@ -226,7 +220,7 @@ "};\n", "\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof GraphState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " // For versions of @langchain/core < 0.2.3, you must call `.stream()`\n", @@ -236,9 +230,7 @@ " return { messages: [responseMessage] };\n", "};\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(GraphState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(START, \"agent\")\n", @@ -272,17 +264,42 @@ "{\n", " messages: [\n", " AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " lc_namespace: \u001b[36m[Array]\u001b[39m,\n", - " content: \u001b[32m''\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " response_metadata: \u001b[36m[Object]\u001b[39m,\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_calls: \u001b[36m[Array]\u001b[39m,\n", - " invalid_tool_calls: [],\n", - " usage_metadata: \u001b[36m[Object]\u001b[39m\n", + " \"id\": \"chatcmpl-9y654VypbD3kE1xM8v4xaAHzZEOXa\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"id\": \"call_OxlOhnROermwae2LPs9SanmD\",\n", + " \"type\": \"function\",\n", + " \"function\": \"[Object]\"\n", + " }\n", + " ]\n", + " },\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 17,\n", + " \"promptTokens\": 70,\n", + " \"totalTokens\": 87\n", + " },\n", + " \"finish_reason\": \"tool_calls\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"current weather in San Francisco\"\n", + " },\n", + " \"type\": \"tool_call\",\n", + " \"id\": \"call_OxlOhnROermwae2LPs9SanmD\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 70,\n", + " \"output_tokens\": 17,\n", + " \"total_tokens\": 87\n", + " }\n", " }\n", " ]\n", "}\n", @@ -293,15 +310,11 @@ "{\n", " messages: [\n", " ToolMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " lc_namespace: \u001b[36m[Array]\u001b[39m,\n", - " content: \u001b[32m'Cold, with a low of 3℃'\u001b[39m,\n", - " name: \u001b[32m'search'\u001b[39m,\n", - " additional_kwargs: {},\n", - " response_metadata: {},\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_call_id: \u001b[32m'call_htjQLLIUKKaxLDAU26QqPlJn'\u001b[39m\n", + " \"content\": \"Cold, with a low of 3℃\",\n", + " \"name\": \"search\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"call_OxlOhnROermwae2LPs9SanmD\"\n", " }\n", " ]\n", "}\n", @@ -312,17 +325,25 @@ "{\n", " messages: [\n", " AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " lc_namespace: \u001b[36m[Array]\u001b[39m,\n", - " content: \u001b[32m'The current weather in San Francisco is cold, with a low of 3°C (37.4°F).'\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " response_metadata: \u001b[36m[Object]\u001b[39m,\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_calls: [],\n", - " invalid_tool_calls: [],\n", - " usage_metadata: \u001b[36m[Object]\u001b[39m\n", + " \"id\": \"chatcmpl-9y654dZ0zzZhPYm6lb36FkG1Enr3p\",\n", + " \"content\": \"It looks like it's currently quite cold in San Francisco, with a low temperature of around 3°C. Make sure to dress warmly!\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 28,\n", + " \"promptTokens\": 103,\n", + " \"totalTokens\": 131\n", + " },\n", + " \"finish_reason\": \"stop\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 103,\n", + " \"output_tokens\": 28,\n", + " \"total_tokens\": 131\n", + " }\n", " }\n", " ]\n", "}\n", @@ -347,14 +368,6 @@ " }\n", "}" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8992a85-fac1-4550-9965-05664cec65ea", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/examples/how-tos/stream-values.ipynb b/examples/how-tos/stream-values.ipynb index 9eab6852..c92642f6 100644 --- a/examples/how-tos/stream-values.ipynb +++ b/examples/how-tos/stream-values.ipynb @@ -19,14 +19,14 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "8e76833b", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ - "process.env.OPENAI_API_KEY = \"sk-...\";" + "// process.env.OPENAI_API_KEY = \"sk-...\";" ] }, { @@ -48,20 +48,14 @@ }, "outputs": [], "source": [ + "import { Annotation } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", - "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};" + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -130,7 +124,7 @@ "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -214,7 +208,7 @@ "import { AIMessage } from \"@langchain/core/messages\";\n", "import { RunnableConfig } from \"@langchain/core/runnables\";\n", "\n", - "const routeMessage = (state: IState) => {\n", + "const routeMessage = (state: typeof GraphState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If no tools are called, we can finish (respond to the user)\n", @@ -226,7 +220,7 @@ "};\n", "\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof GraphState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " // For versions of @langchain/core < 0.2.3, you must call `.stream()`\n", @@ -236,9 +230,7 @@ " return { messages: [responseMessage] };\n", "};\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(GraphState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(START, \"agent\")\n", @@ -261,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "cbcf7c39", "metadata": {}, "outputs": [ @@ -269,133 +261,172 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ [ \u001b[32m'user'\u001b[39m, \u001b[32m\"what's the weather in sf\"\u001b[39m ] ]\n", + "[ [ 'user', \"what's the weather in sf\" ] ]\n", "\n", "====\n", "\n", "[\n", - " [ \u001b[32m'user'\u001b[39m, \u001b[32m\"what's the weather in sf\"\u001b[39m ],\n", + " [ 'user', \"what's the weather in sf\" ],\n", " AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m''\u001b[39m,\n", - " tool_calls: \u001b[36m[Array]\u001b[39m,\n", - " invalid_tool_calls: [],\n", - " additional_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " response_metadata: {}\n", + " \"id\": \"chatcmpl-9y660d49eLzT7DZeBk2ZmX8C5f0LU\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"id\": \"call_iD5Wk4vPsTckffDKJpEQaMkg\",\n", + " \"type\": \"function\",\n", + " \"function\": \"[Object]\"\n", + " }\n", + " ]\n", + " },\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 17,\n", + " \"promptTokens\": 70,\n", + " \"totalTokens\": 87\n", + " },\n", + " \"finish_reason\": \"tool_calls\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", " },\n", - " lc_namespace: [ \u001b[32m'langchain_core'\u001b[39m, \u001b[32m'messages'\u001b[39m ],\n", - " content: \u001b[32m''\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m, tool_calls: \u001b[36m[Array]\u001b[39m },\n", - " response_metadata: { tokenUsage: \u001b[36m[Object]\u001b[39m, finish_reason: \u001b[32m'tool_calls'\u001b[39m },\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_calls: [ \u001b[36m[Object]\u001b[39m ],\n", - " invalid_tool_calls: [],\n", - " usage_metadata: { input_tokens: \u001b[33m70\u001b[39m, output_tokens: \u001b[33m17\u001b[39m, total_tokens: \u001b[33m87\u001b[39m }\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"current weather in San Francisco\"\n", + " },\n", + " \"type\": \"tool_call\",\n", + " \"id\": \"call_iD5Wk4vPsTckffDKJpEQaMkg\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 70,\n", + " \"output_tokens\": 17,\n", + " \"total_tokens\": 87\n", + " }\n", " }\n", "]\n", "\n", "====\n", "\n", "[\n", - " [ \u001b[32m'user'\u001b[39m, \u001b[32m\"what's the weather in sf\"\u001b[39m ],\n", + " [ 'user', \"what's the weather in sf\" ],\n", " AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m''\u001b[39m,\n", - " tool_calls: \u001b[36m[Array]\u001b[39m,\n", - " invalid_tool_calls: [],\n", - " additional_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " response_metadata: {}\n", + " \"id\": \"chatcmpl-9y660d49eLzT7DZeBk2ZmX8C5f0LU\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"id\": \"call_iD5Wk4vPsTckffDKJpEQaMkg\",\n", + " \"type\": \"function\",\n", + " \"function\": \"[Object]\"\n", + " }\n", + " ]\n", " },\n", - " lc_namespace: [ \u001b[32m'langchain_core'\u001b[39m, \u001b[32m'messages'\u001b[39m ],\n", - " content: \u001b[32m''\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m, tool_calls: \u001b[36m[Array]\u001b[39m },\n", - " response_metadata: { tokenUsage: \u001b[36m[Object]\u001b[39m, finish_reason: \u001b[32m'tool_calls'\u001b[39m },\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_calls: [ \u001b[36m[Object]\u001b[39m ],\n", - " invalid_tool_calls: [],\n", - " usage_metadata: { input_tokens: \u001b[33m70\u001b[39m, output_tokens: \u001b[33m17\u001b[39m, total_tokens: \u001b[33m87\u001b[39m }\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 17,\n", + " \"promptTokens\": 70,\n", + " \"totalTokens\": 87\n", + " },\n", + " \"finish_reason\": \"tool_calls\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"current weather in San Francisco\"\n", + " },\n", + " \"type\": \"tool_call\",\n", + " \"id\": \"call_iD5Wk4vPsTckffDKJpEQaMkg\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 70,\n", + " \"output_tokens\": 17,\n", + " \"total_tokens\": 87\n", + " }\n", " },\n", " ToolMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " name: \u001b[32m'search'\u001b[39m,\n", - " content: \u001b[32m'Cold, with a low of 3℃'\u001b[39m,\n", - " tool_call_id: \u001b[32m'call_cQR8iZwXhIwkTuAsFhWjbPCf'\u001b[39m,\n", - " additional_kwargs: {},\n", - " response_metadata: {}\n", - " },\n", - " lc_namespace: [ \u001b[32m'langchain_core'\u001b[39m, \u001b[32m'messages'\u001b[39m ],\n", - " content: \u001b[32m'Cold, with a low of 3℃'\u001b[39m,\n", - " name: \u001b[32m'search'\u001b[39m,\n", - " additional_kwargs: {},\n", - " response_metadata: {},\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_call_id: \u001b[32m'call_cQR8iZwXhIwkTuAsFhWjbPCf'\u001b[39m\n", + " \"content\": \"Cold, with a low of 3℃\",\n", + " \"name\": \"search\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"call_iD5Wk4vPsTckffDKJpEQaMkg\"\n", " }\n", "]\n", "\n", "====\n", "\n", "[\n", - " [ \u001b[32m'user'\u001b[39m, \u001b[32m\"what's the weather in sf\"\u001b[39m ],\n", + " [ 'user', \"what's the weather in sf\" ],\n", " AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m''\u001b[39m,\n", - " tool_calls: \u001b[36m[Array]\u001b[39m,\n", - " invalid_tool_calls: [],\n", - " additional_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " response_metadata: {}\n", + " \"id\": \"chatcmpl-9y660d49eLzT7DZeBk2ZmX8C5f0LU\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"id\": \"call_iD5Wk4vPsTckffDKJpEQaMkg\",\n", + " \"type\": \"function\",\n", + " \"function\": \"[Object]\"\n", + " }\n", + " ]\n", " },\n", - " lc_namespace: [ \u001b[32m'langchain_core'\u001b[39m, \u001b[32m'messages'\u001b[39m ],\n", - " content: \u001b[32m''\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m, tool_calls: \u001b[36m[Array]\u001b[39m },\n", - " response_metadata: { tokenUsage: \u001b[36m[Object]\u001b[39m, finish_reason: \u001b[32m'tool_calls'\u001b[39m },\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_calls: [ \u001b[36m[Object]\u001b[39m ],\n", - " invalid_tool_calls: [],\n", - " usage_metadata: { input_tokens: \u001b[33m70\u001b[39m, output_tokens: \u001b[33m17\u001b[39m, total_tokens: \u001b[33m87\u001b[39m }\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 17,\n", + " \"promptTokens\": 70,\n", + " \"totalTokens\": 87\n", + " },\n", + " \"finish_reason\": \"tool_calls\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"current weather in San Francisco\"\n", + " },\n", + " \"type\": \"tool_call\",\n", + " \"id\": \"call_iD5Wk4vPsTckffDKJpEQaMkg\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 70,\n", + " \"output_tokens\": 17,\n", + " \"total_tokens\": 87\n", + " }\n", " },\n", " ToolMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " name: \u001b[32m'search'\u001b[39m,\n", - " content: \u001b[32m'Cold, with a low of 3℃'\u001b[39m,\n", - " tool_call_id: \u001b[32m'call_cQR8iZwXhIwkTuAsFhWjbPCf'\u001b[39m,\n", - " additional_kwargs: {},\n", - " response_metadata: {}\n", - " },\n", - " lc_namespace: [ \u001b[32m'langchain_core'\u001b[39m, \u001b[32m'messages'\u001b[39m ],\n", - " content: \u001b[32m'Cold, with a low of 3℃'\u001b[39m,\n", - " name: \u001b[32m'search'\u001b[39m,\n", - " additional_kwargs: {},\n", - " response_metadata: {},\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_call_id: \u001b[32m'call_cQR8iZwXhIwkTuAsFhWjbPCf'\u001b[39m\n", + " \"content\": \"Cold, with a low of 3℃\",\n", + " \"name\": \"search\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"call_iD5Wk4vPsTckffDKJpEQaMkg\"\n", " },\n", " AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m'The current weather in San Francisco is cold, with a low of 3°C.'\u001b[39m,\n", - " tool_calls: [],\n", - " invalid_tool_calls: [],\n", - " additional_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " response_metadata: {}\n", + " \"id\": \"chatcmpl-9y660ZKNXvziVJze0X5aTlZ5IoN35\",\n", + " \"content\": \"Currently, in San Francisco, it's cold with a temperature of around 3℃ (37.4°F).\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 23,\n", + " \"promptTokens\": 103,\n", + " \"totalTokens\": 126\n", + " },\n", + " \"finish_reason\": \"stop\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", " },\n", - " lc_namespace: [ \u001b[32m'langchain_core'\u001b[39m, \u001b[32m'messages'\u001b[39m ],\n", - " content: \u001b[32m'The current weather in San Francisco is cold, with a low of 3°C.'\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m, tool_calls: \u001b[90mundefined\u001b[39m },\n", - " response_metadata: { tokenUsage: \u001b[36m[Object]\u001b[39m, finish_reason: \u001b[32m'stop'\u001b[39m },\n", - " id: \u001b[90mundefined\u001b[39m,\n", - " tool_calls: [],\n", - " invalid_tool_calls: [],\n", - " usage_metadata: { input_tokens: \u001b[33m103\u001b[39m, output_tokens: \u001b[33m18\u001b[39m, total_tokens: \u001b[33m121\u001b[39m }\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 103,\n", + " \"output_tokens\": 23,\n", + " \"total_tokens\": 126\n", + " }\n", " }\n", "]\n", "\n", diff --git a/examples/how-tos/streaming-tokens-without-langchain.ipynb b/examples/how-tos/streaming-tokens-without-langchain.ipynb index 309fed9b..93955eb5 100644 --- a/examples/how-tos/streaming-tokens-without-langchain.ipynb +++ b/examples/how-tos/streaming-tokens-without-langchain.ipynb @@ -79,17 +79,21 @@ "source": [ "import { dispatchCustomEvent } from \"@langchain/core/callbacks/dispatch\";\n", "import { wrapOpenAI } from \"langsmith/wrappers/openai\";\n", + "import { Annotation } from \"@langchain/langgraph\";\n", "\n", - "type GraphState = {\n", - " messages: OpenAI.ChatCompletionMessageParam[];\n", - "};\n", + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});\n", "\n", "// If using LangSmith, use \"wrapOpenAI\" on the whole client or\n", "// \"traceable\" to wrap a single method for nicer tracing:\n", "// https://docs.smith.langchain.com/how_to_guides/tracing/annotate_code\n", "const wrappedClient = wrapOpenAI(openaiClient);\n", "\n", - "const callModel = async ({ messages }: GraphState) => {\n", + "const callModel = async (state: typeof GraphState.State): Promise> => {\n", + " const { messages } = state;\n", " const stream = await wrappedClient.chat.completions.create({\n", " messages,\n", " model: \"gpt-4o-mini\",\n", @@ -97,7 +101,7 @@ " stream: true,\n", " });\n", " let responseContent = \"\";\n", - " let role = \"assistant\";\n", + " let role: string = \"assistant\";\n", " let toolCallId: string | undefined;\n", " let toolCallName: string | undefined;\n", " let toolCallArgs = \"\";\n", @@ -133,11 +137,12 @@ " name: toolCallName,\n", " arguments: toolCallArgs\n", " },\n", - " type: \"function\",\n", + " type: \"function\" as const,\n", " }];\n", " }\n", + "\n", " const responseMessage = {\n", - " role,\n", + " role: role as any,\n", " content: responseContent,\n", " tool_calls: finalToolCalls,\n", " };\n", @@ -172,7 +177,8 @@ " }\n", "};\n", "\n", - "const callTools = async ({ messages }: GraphState) => {\n", + "const callTools = async (state: typeof GraphState.State): Promise> => {\n", + " const { messages } = state;\n", " const mostRecentMessage = messages[messages.length - 1];\n", " const toolCalls = (mostRecentMessage as OpenAI.ChatCompletionAssistantMessageParam).tool_calls;\n", " if (toolCalls === undefined || toolCalls.length === 0) {\n", @@ -186,7 +192,7 @@ " const response = await toolNameMap[functionName](functionArguments);\n", " const toolMessage = {\n", " tool_call_id: toolCalls[0].id,\n", - " role: \"tool\",\n", + " role: \"tool\" as const,\n", " name: functionName,\n", " content: response,\n", " }\n", @@ -210,13 +216,11 @@ "outputs": [], "source": [ "import { StateGraph } from \"@langchain/langgraph\";\n", - "\n", "import OpenAI from \"openai\";\n", - "type GraphState = {\n", - " messages: OpenAI.ChatCompletionMessageParam[];\n", - "};\n", "\n", - "const shouldContinue = ({ messages }: GraphState) => {\n", + "// We can reuse the same `GraphState` from above as it has not changed.\n", + "const shouldContinue = (state: typeof GraphState.State) => {\n", + " const { messages } = state;\n", " const lastMessage =\n", " messages[messages.length - 1] as OpenAI.ChatCompletionAssistantMessageParam;\n", " if (lastMessage?.tool_calls !== undefined && lastMessage?.tool_calls.length > 0) {\n", @@ -225,15 +229,7 @@ " return \"__end__\";\n", "}\n", "\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: (a: any, b: any) => a.concat(b),\n", - " }\n", - " }\n", - "});\n", - "\n", - "const graph = workflow\n", + "const graph = new StateGraph(GraphState)\n", " .addNode(\"model\", callModel)\n", " .addNode(\"tools\", callTools)\n", " .addEdge(\"__start__\", \"model\")\n", @@ -242,7 +238,8 @@ " __end__: \"__end__\",\n", " })\n", " .addEdge(\"tools\", \"model\")\n", - " .compile();" + " .compile();\n", + " " ] }, { @@ -288,7 +285,7 @@ "text": [ "streamed_tool_call_chunk {\n", " index: 0,\n", - " id: 'call_1Lt33wOgPfjXwvM9bcWoe1yZ',\n", + " id: 'call_v99reml4gZvvUypPgOpLgxM2',\n", " type: 'function',\n", " function: { name: 'get_items', arguments: '' }\n", "}\n", @@ -302,7 +299,8 @@ "streamed_token { content: ' the' }\n", "streamed_token { content: ' bedroom' }\n", "streamed_token { content: ',' }\n", - "streamed_token { content: \" you'll\" }\n", + "streamed_token { content: ' you' }\n", + "streamed_token { content: ' can' }\n", "streamed_token { content: ' find' }\n", "streamed_token { content: ' socks' }\n", "streamed_token { content: ',' }\n", diff --git a/examples/how-tos/subgraph.ipynb b/examples/how-tos/subgraph.ipynb index bd666035..c7213dc2 100644 --- a/examples/how-tos/subgraph.ipynb +++ b/examples/how-tos/subgraph.ipynb @@ -81,39 +81,29 @@ "id": "38d1f06f", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[WARN]: You have enabled LangSmith tracing without backgrounding callbacks.\n", - "[WARN]: If you are not using a serverless environment where you must wait for tracing calls to finish,\n", - "[WARN]: we suggest setting \"process.env.LANGCHAIN_CALLBACKS_BACKGROUND=true\" to avoid additional latency.\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ "{\n", - " name: \u001b[32m'test'\u001b[39m,\n", + " name: 'test',\n", " path: [\n", - " \u001b[32m'grandparent'\u001b[39m,\n", - " \u001b[32m'parent'\u001b[39m,\n", - " \u001b[32m'grandparent'\u001b[39m,\n", - " \u001b[32m'parent'\u001b[39m,\n", - " \u001b[32m'child_start'\u001b[39m,\n", - " \u001b[32m'child_middle'\u001b[39m,\n", - " \u001b[32m'child_end'\u001b[39m,\n", - " \u001b[32m'sibling'\u001b[39m,\n", - " \u001b[32m'fin'\u001b[39m\n", + " 'grandparent',\n", + " 'parent',\n", + " 'grandparent',\n", + " 'parent',\n", + " 'child_start',\n", + " 'child_middle',\n", + " 'child_end',\n", + " 'sibling',\n", + " 'fin'\n", " ]\n", "}\n" ] } ], "source": [ - "import { END, START, StateGraph } from \"@langchain/langgraph\";\n", - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", + "import { END, START, StateGraph, Annotation } from \"@langchain/langgraph\";\n", "\n", "function reduceList(\n", " left?: string[] | string,\n", @@ -132,27 +122,18 @@ " return [...left, ...right];\n", "}\n", "\n", - "// Define the state type\n", - "interface IState {\n", - " name: string;\n", - " path: string[];\n", - "}\n", - "\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " name: {\n", + "const GraphState = Annotation.Root({\n", + " name: Annotation({\n", " // Overwrite name if a new one is provided\n", - " value: (x: string, y?: string) => (y ? y : x),\n", + " reducer: (x, y) => y ?? x,\n", " default: () => \"default\",\n", - " },\n", - " path: {\n", - " // Concatenate paths\n", - " value: reduceList,\n", - " default: () => [],\n", - " },\n", - "};\n", + " }),\n", + " path: Annotation({\n", + " reducer: reduceList,\n", + " }),\n", + "});\n", "\n", - "const childBuilder = new StateGraph({ channels: graphState });\n", - "childBuilder\n", + "const childBuilder = new StateGraph(GraphState)\n", " .addNode(\"child_start\", (_state) => ({ path: [\"child_start\"] }))\n", " .addEdge(START, \"child_start\")\n", " .addNode(\"child_middle\", (_state) => ({ path: [\"child_middle\"] }))\n", @@ -161,11 +142,7 @@ " .addEdge(\"child_middle\", \"child_end\")\n", " .addEdge(\"child_end\", END);\n", "\n", - "const builder = new StateGraph({\n", - " channels: graphState,\n", - "});\n", - "\n", - "builder\n", + "const builder = new StateGraph(GraphState)\n", " .addNode(\"grandparent\", (_state) => ({ path: [\"grandparent\"] }))\n", " .addEdge(START, \"grandparent\")\n", " .addNode(\"parent\", (_state) => ({ path: [\"parent\"] }))\n", @@ -213,10 +190,29 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "34a51908", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " name: 'test',\n", + " path: [\n", + " { val: 'grandparent', id: 'd0cb05ae-85d7-4cf6-adf3-b78259509cb4' },\n", + " { val: 'parent', id: 'b5adf2c8-d70b-4f5a-b87c-c2ae09a5d046' },\n", + " { val: 'child_start', id: '57ac546a-a709-4a7d-bd17-90f1a8d4d338' },\n", + " { val: 'child_middle', id: '237b9419-dd07-4602-8abb-4b959228e2a2' },\n", + " { val: 'child_end', id: 'c02960fa-0aff-4f21-a35b-07eb8870bd90' },\n", + " { val: 'sibling', id: 'b097055f-530c-47c7-b704-245f2b2edfcc' },\n", + " { val: 'fin', id: '4ca6eae6-f265-4780-ae1c-c854bf7939dc' }\n", + " ]\n", + "}\n" + ] + } + ], "source": [ "import { v4 as uuidv4 } from \"uuid\";\n", "\n", @@ -266,29 +262,18 @@ " return merged;\n", "}\n", "\n", - "interface IStateWithIds {\n", - " name: string;\n", - " path: ValWithId[];\n", - "}\n", - "\n", - "const graphState2: StateGraphArgs[\"channels\"] = {\n", - " name: {\n", + "const GraphStateWithIds = Annotation.Root({\n", + " name: Annotation({\n", " // Overwrite name if a new one is provided\n", - " value: (x: string, y?: string) => (y ? y : x),\n", + " reducer: (x, y) => y ?? x,\n", " default: () => \"default\",\n", - " },\n", - " path: {\n", - " // Concatenate paths\n", - " value: reduceListWithIds,\n", - " default: () => [],\n", - " },\n", - "};\n", - "\n", - "const childBuilderWithIds = new StateGraph({\n", - " channels: graphState2,\n", + " }),\n", + " path: Annotation({\n", + " reducer: reduceListWithIds,\n", + " }),\n", "});\n", "\n", - "childBuilderWithIds\n", + "const childBuilderWithIds = new StateGraph(GraphStateWithIds)\n", " .addNode(\"child_start\", (_state) => ({\n", " path: [{ val: \"child_start\" }],\n", " }))\n", @@ -303,11 +288,7 @@ " .addEdge(\"child_middle\", \"child_end\")\n", " .addEdge(\"child_end\", END);\n", "\n", - "const builderWithIds = new StateGraph({\n", - " channels: graphState2,\n", - "});\n", - "\n", - "builderWithIds\n", + "const builderWithIds = new StateGraph(GraphStateWithIds)\n", " .addNode(\"grandparent\", (_state) => ({\n", " path: [{ val: \"grandparent\" }],\n", " }))\n", diff --git a/examples/how-tos/time-travel.ipynb b/examples/how-tos/time-travel.ipynb index f37b9b55..23ba27fa 100644 --- a/examples/how-tos/time-travel.ipynb +++ b/examples/how-tos/time-travel.ipynb @@ -91,27 +91,21 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "44968352", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ - "import { StateGraphArgs } from \"@langchain/langgraph\";\n", + "import { Annotation } from \"@langchain/langgraph\";\n", "import { BaseMessage } from \"@langchain/core/messages\";\n", "\n", - "interface IState {\n", - " messages: BaseMessage[];\n", - "}\n", - "\n", - "// This defines the agent state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};\n" + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});" ] }, { @@ -130,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "b22edfc4", "metadata": { "lines_to_next_cell": 2 @@ -147,7 +141,7 @@ " schema: z.object({\n", " query: z.string().describe(\"The query to use in your search.\"),\n", " }),\n", - " func: async ({}: { query: string }) => {\n", + " func: async (_) => {\n", " // This is a placeholder for the actual implementation\n", " return \"Cold, with a low of 13 ℃\";\n", " },\n", @@ -171,7 +165,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "id": "0cc63f1f", "metadata": { "lines_to_next_cell": 2 @@ -180,7 +174,7 @@ "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { @@ -209,7 +203,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "id": "dae9ab9c", "metadata": { "lines_to_next_cell": 2 @@ -233,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 5, "id": "ca438e74", "metadata": { "lines_to_next_cell": 2 @@ -259,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 6, "id": "1a29ec2a", "metadata": {}, "outputs": [], @@ -269,7 +263,7 @@ "import { RunnableConfig } from \"@langchain/core/runnables\";\n", "import { MemorySaver } from \"@langchain/langgraph\";\n", "\n", - "const routeMessage = (state: IState) => {\n", + "const routeMessage = (state: typeof GraphState.State) => {\n", " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " // If no tools are called, we can finish (respond to the user)\n", @@ -281,7 +275,7 @@ "};\n", "\n", "const callModel = async (\n", - " state: IState,\n", + " state: typeof GraphState.State,\n", " config?: RunnableConfig,\n", ") => {\n", " const { messages } = state;\n", @@ -289,9 +283,7 @@ " return { messages: [response] };\n", "};\n", "\n", - "const workflow = new StateGraph({\n", - " channels: graphState,\n", - "})\n", + "const workflow = new StateGraph(GraphState)\n", " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(START, \"agent\")\n", @@ -316,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 7, "id": "0749329a", "metadata": {}, "outputs": [ @@ -324,23 +316,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'user'\u001b[39m, \u001b[32m\"Hi I'm Jo.\"\u001b[39m ]\n", + "[ 'user', \"Hi I'm Jo.\" ]\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hello, Jo! How can I assist you today?\n", + "\n", + "Hello Jo! How can I assist you today?\n", "-----\n", "\n" ] @@ -383,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 8, "id": "6ff5468d", "metadata": { "lines_to_next_cell": 2 @@ -395,17 +374,22 @@ "text": [ "{\n", " messages: [\n", - " [ \u001b[32m'user'\u001b[39m, \u001b[32m\"Hi I'm Jo.\"\u001b[39m ],\n", + " [ 'user', \"Hi I'm Jo.\" ],\n", " AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " lc_namespace: \u001b[36m[Array]\u001b[39m,\n", - " content: \u001b[32m'Hello, Jo! How can I assist you today?'\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: {},\n", - " response_metadata: \u001b[36m[Object]\u001b[39m,\n", - " tool_calls: [],\n", - " invalid_tool_calls: []\n", + " \"id\": \"chatcmpl-9y6TlYVbfL3d3VonkF1b3iXwnbdFm\",\n", + " \"content\": \"Hello Jo! How can I assist you today?\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 11,\n", + " \"promptTokens\": 68,\n", + " \"totalTokens\": 79\n", + " },\n", + " \"finish_reason\": \"stop\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": []\n", " }\n", " ]\n", "}\n" @@ -431,7 +415,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "id": "22b25946", "metadata": { "lines_to_next_cell": 2 @@ -462,7 +446,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 10, "id": "873b3438", "metadata": {}, "outputs": [ @@ -470,35 +454,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'user'\u001b[39m, \u001b[32m\"What's the weather like in SF currently?\"\u001b[39m ]\n", + "[ 'user', \"What's the weather like in SF currently?\" ]\n", "-----\n", "\n", "[\n", " {\n", - " name: \u001b[32m'search'\u001b[39m,\n", - " args: { query: \u001b[32m'current weather in San Francisco'\u001b[39m },\n", - " id: \u001b[32m'call_3dj210cRFWwO6ZXbKskiXqn6'\u001b[39m\n", + " name: 'search',\n", + " args: { query: 'current weather in San Francisco' },\n", + " type: 'tool_call',\n", + " id: 'call_IBDK50kVnVq2RtDjbpq0UiTA'\n", " }\n", "]\n", "-----\n", "\n", "Cold, with a low of 13 ℃\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The current weather in San Francisco is 13°C and cold.\n", + "\n", + "The current weather in San Francisco is cold, with a low temperature of 13°C (55°F). Is there anything else you would like to know?\n", "-----\n", "\n" ] @@ -553,7 +525,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 11, "id": "736be42e", "metadata": {}, "outputs": [ @@ -561,14 +533,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'user'\u001b[39m, \u001b[32m\"What's the weather like in SF currently?\"\u001b[39m ]\n", + "[ 'user', \"What's the weather like in SF currently?\" ]\n", "-----\n", "\n", "[\n", " {\n", - " name: \u001b[32m'search'\u001b[39m,\n", - " args: { query: \u001b[32m'current weather in San Francisco'\u001b[39m },\n", - " id: \u001b[32m'call_WRrsB6evR9HRlKvTpeKdTeMA'\u001b[39m\n", + " name: 'search',\n", + " args: { query: 'current weather in San Francisco, CA' },\n", + " type: 'tool_call',\n", + " id: 'call_upim4LMd1U6JdWlsGGk772Pa'\n", " }\n", "]\n", "-----\n", @@ -615,7 +588,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 12, "id": "0f434f69", "metadata": { "lines_to_next_cell": 2 @@ -625,7 +598,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ \u001b[32m'tools'\u001b[39m ]\n" + "[ 'tools' ]\n" ] } ], @@ -647,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 13, "id": "fd4d7eff", "metadata": {}, "outputs": [ @@ -657,21 +630,8 @@ "text": [ "Cold, with a low of 13 ℃\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The current weather in San Francisco is cold, with a low of 13°C (55°F).\n", + "\n", + "Currently, it's cold in San Francisco, with a temperature around 13°C.\n", "-----\n", "\n" ] @@ -708,7 +668,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 14, "id": "bc7acb70", "metadata": { "lines_to_next_cell": 2 @@ -719,68 +679,214 @@ "output_type": "stream", "text": [ "{\n", - " values: { messages: [ \u001b[36m[Array]\u001b[39m, \u001b[36m[AIMessage]\u001b[39m, \u001b[36m[ToolMessage]\u001b[39m, \u001b[36m[AIMessage]\u001b[39m ] },\n", + " values: {\n", + " messages: [\n", + " [Array],\n", + " AIMessage {\n", + " \"id\": \"chatcmpl-9y6Tn0RGjUnVqxDHz5CxlGfldPS2E\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"id\": \"call_upim4LMd1U6JdWlsGGk772Pa\",\n", + " \"type\": \"function\",\n", + " \"function\": \"[Object]\"\n", + " }\n", + " ]\n", + " },\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 19,\n", + " \"promptTokens\": 72,\n", + " \"totalTokens\": 91\n", + " },\n", + " \"finish_reason\": \"tool_calls\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"current weather in San Francisco, CA\"\n", + " },\n", + " \"type\": \"tool_call\",\n", + " \"id\": \"call_upim4LMd1U6JdWlsGGk772Pa\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": []\n", + " },\n", + " ToolMessage {\n", + " \"content\": \"Cold, with a low of 13 ℃\",\n", + " \"name\": \"search\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"call_upim4LMd1U6JdWlsGGk772Pa\"\n", + " },\n", + " AIMessage {\n", + " \"id\": \"chatcmpl-9y6ToC6yczhz1hzn5XMPt6Fha4CLJ\",\n", + " \"content\": \"Currently, it's cold in San Francisco, with a temperature around 13°C.\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 17,\n", + " \"promptTokens\": 107,\n", + " \"totalTokens\": 124\n", + " },\n", + " \"finish_reason\": \"stop\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": []\n", + " }\n", + " ]\n", + " },\n", " next: [],\n", - " metadata: { source: \u001b[32m'loop'\u001b[39m, step: \u001b[33m3\u001b[39m, writes: { messages: \u001b[36m[Array]\u001b[39m } },\n", + " metadata: { source: 'loop', step: 3, writes: { agent: [Object] } },\n", " config: {\n", " configurable: {\n", - " thread_id: \u001b[32m'conversation-num-1'\u001b[39m,\n", - " checkpoint_id: \u001b[32m'1ef16645-dedc-6920-8003-05dd7bf5a619'\u001b[39m\n", + " thread_id: 'conversation-num-1',\n", + " checkpoint_id: '1ef5e864-0045-68b1-8003-3da747a708d6'\n", " }\n", " },\n", - " parentConfig: \u001b[90mundefined\u001b[39m\n", + " createdAt: '2024-08-19T23:53:36.443Z',\n", + " parentConfig: undefined\n", "}\n", "--\n", "{\n", - " values: { messages: [ \u001b[36m[Array]\u001b[39m, \u001b[36m[AIMessage]\u001b[39m, \u001b[36m[ToolMessage]\u001b[39m ] },\n", - " next: [ \u001b[32m'agent'\u001b[39m ],\n", - " metadata: { source: \u001b[32m'loop'\u001b[39m, step: \u001b[33m2\u001b[39m, writes: { messages: \u001b[36m[Array]\u001b[39m } },\n", + " values: {\n", + " messages: [\n", + " [Array],\n", + " AIMessage {\n", + " \"id\": \"chatcmpl-9y6Tn0RGjUnVqxDHz5CxlGfldPS2E\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"id\": \"call_upim4LMd1U6JdWlsGGk772Pa\",\n", + " \"type\": \"function\",\n", + " \"function\": \"[Object]\"\n", + " }\n", + " ]\n", + " },\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 19,\n", + " \"promptTokens\": 72,\n", + " \"totalTokens\": 91\n", + " },\n", + " \"finish_reason\": \"tool_calls\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"current weather in San Francisco, CA\"\n", + " },\n", + " \"type\": \"tool_call\",\n", + " \"id\": \"call_upim4LMd1U6JdWlsGGk772Pa\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": []\n", + " },\n", + " ToolMessage {\n", + " \"content\": \"Cold, with a low of 13 ℃\",\n", + " \"name\": \"search\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"call_upim4LMd1U6JdWlsGGk772Pa\"\n", + " }\n", + " ]\n", + " },\n", + " next: [ 'agent' ],\n", + " metadata: { source: 'loop', step: 2, writes: { tools: [Object] } },\n", " config: {\n", " configurable: {\n", - " thread_id: \u001b[32m'conversation-num-1'\u001b[39m,\n", - " checkpoint_id: \u001b[32m'1ef16645-d8d3-6290-8002-acd679142065'\u001b[39m\n", + " thread_id: 'conversation-num-1',\n", + " checkpoint_id: '1ef5e863-fa1c-6650-8002-bf4528305aac'\n", " }\n", " },\n", - " parentConfig: \u001b[90mundefined\u001b[39m\n", + " createdAt: '2024-08-19T23:53:35.797Z',\n", + " parentConfig: undefined\n", "}\n", "--\n", "{\n", - " values: { messages: [ \u001b[36m[Array]\u001b[39m, \u001b[36m[AIMessage]\u001b[39m ] },\n", - " next: [ \u001b[32m'tools'\u001b[39m ],\n", - " metadata: { source: \u001b[32m'loop'\u001b[39m, step: \u001b[33m1\u001b[39m, writes: { messages: \u001b[36m[Array]\u001b[39m } },\n", + " values: {\n", + " messages: [\n", + " [Array],\n", + " AIMessage {\n", + " \"id\": \"chatcmpl-9y6Tn0RGjUnVqxDHz5CxlGfldPS2E\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"id\": \"call_upim4LMd1U6JdWlsGGk772Pa\",\n", + " \"type\": \"function\",\n", + " \"function\": \"[Object]\"\n", + " }\n", + " ]\n", + " },\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 19,\n", + " \"promptTokens\": 72,\n", + " \"totalTokens\": 91\n", + " },\n", + " \"finish_reason\": \"tool_calls\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"current weather in San Francisco, CA\"\n", + " },\n", + " \"type\": \"tool_call\",\n", + " \"id\": \"call_upim4LMd1U6JdWlsGGk772Pa\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": []\n", + " }\n", + " ]\n", + " },\n", + " next: [ 'tools' ],\n", + " metadata: { source: 'loop', step: 1, writes: { agent: [Object] } },\n", " config: {\n", " configurable: {\n", - " thread_id: \u001b[32m'conversation-num-1'\u001b[39m,\n", - " checkpoint_id: \u001b[32m'1ef16645-c45e-6580-8001-3508bcab0211'\u001b[39m\n", + " thread_id: 'conversation-num-1',\n", + " checkpoint_id: '1ef5e863-f976-6611-8001-af242a92fef8'\n", " }\n", " },\n", - " parentConfig: \u001b[90mundefined\u001b[39m\n", + " createdAt: '2024-08-19T23:53:35.729Z',\n", + " parentConfig: undefined\n", "}\n", "--\n", "{\n", - " values: { messages: [ \u001b[36m[Array]\u001b[39m ] },\n", - " next: [ \u001b[32m'agent'\u001b[39m ],\n", - " metadata: { source: \u001b[32m'loop'\u001b[39m, step: \u001b[33m0\u001b[39m, writes: { messages: \u001b[36m[Array]\u001b[39m } },\n", + " values: { messages: [ [Array] ] },\n", + " next: [ 'agent' ],\n", + " metadata: { source: 'loop', step: 0, writes: null },\n", " config: {\n", " configurable: {\n", - " thread_id: \u001b[32m'conversation-num-1'\u001b[39m,\n", - " checkpoint_id: \u001b[32m'1ef16645-be57-6602-8000-da3eed993c02'\u001b[39m\n", + " thread_id: 'conversation-num-1',\n", + " checkpoint_id: '1ef5e863-f365-6a51-8000-6443aafd5477'\n", " }\n", " },\n", - " parentConfig: \u001b[90mundefined\u001b[39m\n", + " createdAt: '2024-08-19T23:53:35.093Z',\n", + " parentConfig: undefined\n", "}\n", "--\n", "{\n", - " values: { messages: [] },\n", - " next: [ \u001b[32m'__start__'\u001b[39m ],\n", - " metadata: { source: \u001b[32m'input'\u001b[39m, step: \u001b[33m-1\u001b[39m, writes: { __start__: \u001b[36m[Object]\u001b[39m } },\n", + " values: {},\n", + " next: [ '__start__' ],\n", + " metadata: { source: 'input', step: -1, writes: { __start__: [Object] } },\n", " config: {\n", " configurable: {\n", - " thread_id: \u001b[32m'conversation-num-1'\u001b[39m,\n", - " checkpoint_id: \u001b[32m'1ef16645-be57-6601-unde-finedff5c9266290795'\u001b[39m\n", + " thread_id: 'conversation-num-1',\n", + " checkpoint_id: '1ef5e863-f365-6a50-ffff-0ae60570513f'\n", " }\n", " },\n", - " parentConfig: \u001b[90mundefined\u001b[39m\n", + " createdAt: '2024-08-19T23:53:35.093Z',\n", + " parentConfig: undefined\n", "}\n", "--\n" ] @@ -813,7 +919,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 15, "id": "c1cefbfa", "metadata": {}, "outputs": [ @@ -823,21 +929,8 @@ "text": [ "Cold, with a low of 13 ℃\n", "-----\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The current weather in San Francisco is cold, with a low of 13°C.\n", + "\n", + "The current weather in San Francisco, CA is cold, with a temperature of 13°C (approximately 55°F).\n", "-----\n", "\n" ] @@ -881,7 +974,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 16, "id": "d7656840-3a4a-4a80-af74-214b35cfbadd", "metadata": {}, "outputs": [ @@ -891,31 +984,49 @@ "text": [ "{\n", " messages: [\n", - " [ \u001b[32m'user'\u001b[39m, \u001b[32m\"What's the weather like in SF currently?\"\u001b[39m ],\n", + " [ 'user', \"What's the weather like in SF currently?\" ],\n", " AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " lc_namespace: \u001b[36m[Array]\u001b[39m,\n", - " content: \u001b[32m''\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " response_metadata: \u001b[36m[Object]\u001b[39m,\n", - " tool_calls: \u001b[36m[Array]\u001b[39m,\n", - " invalid_tool_calls: []\n", + " \"id\": \"chatcmpl-9y6Tn0RGjUnVqxDHz5CxlGfldPS2E\",\n", + " \"content\": \"\",\n", + " \"additional_kwargs\": {\n", + " \"tool_calls\": [\n", + " {\n", + " \"id\": \"call_upim4LMd1U6JdWlsGGk772Pa\",\n", + " \"type\": \"function\",\n", + " \"function\": \"[Object]\"\n", + " }\n", + " ]\n", + " },\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 19,\n", + " \"promptTokens\": 72,\n", + " \"totalTokens\": 91\n", + " },\n", + " \"finish_reason\": \"tool_calls\",\n", + " \"system_fingerprint\": \"fp_3aa7262c27\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"query\": \"current weather in San Francisco, CA\"\n", + " },\n", + " \"type\": \"tool_call\",\n", + " \"id\": \"call_upim4LMd1U6JdWlsGGk772Pa\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": []\n", " },\n", " ToolMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: \u001b[36m[Object]\u001b[39m,\n", - " lc_namespace: \u001b[36m[Array]\u001b[39m,\n", - " content: \u001b[32m\"It's sunny out, with a high of 38 ℃.\"\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: {},\n", - " response_metadata: {},\n", - " tool_call_id: \u001b[32m'call_WRrsB6evR9HRlKvTpeKdTeMA'\u001b[39m\n", + " \"content\": \"It's sunny out, with a high of 38 ℃.\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"call_upim4LMd1U6JdWlsGGk772Pa\"\n", " }\n", " ]\n", "}\n", - "[ \u001b[32m'agent'\u001b[39m ]\n" + "[ 'agent' ]\n" ] } ], @@ -956,22 +1067,15 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 17, "id": "bb95930f-07e5-4e32-8e38-2170d36ab1a0", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel branch:agent:routeMessage:undefined which has no readers\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "The current weather in San Francisco is sunny, with a high of 38°C.\n", + "The current weather in San Francisco is sunny with a high of 38°C (100.4°F).\n", "-----\n", "\n" ] @@ -995,14 +1099,6 @@ " console.log(\"-----\\n\");\n", "}" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "67565ff9-8d4a-4960-952c-ac1eac5ca97c", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -1010,9 +1106,9 @@ "encoding": "# -*- coding: utf-8 -*-" }, "kernelspec": { - "display_name": "Deno", + "display_name": "TypeScript", "language": "typescript", - "name": "deno" + "name": "tslab" }, "language_info": { "codemirror_mode": { diff --git a/examples/how-tos/tool-calling-errors.ipynb b/examples/how-tos/tool-calling-errors.ipynb index 5c548b4b..c91766ad 100644 --- a/examples/how-tos/tool-calling-errors.ipynb +++ b/examples/how-tos/tool-calling-errors.ipynb @@ -65,23 +65,26 @@ "metadata": {}, "outputs": [], "source": [ - "import { StateGraph, messagesStateReducer } from \"@langchain/langgraph\";\n", + "import { StateGraph, Annotation, messagesStateReducer } from \"@langchain/langgraph\";\n", "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "import { ChatAnthropic } from \"@langchain/anthropic\";\n", - "import { type BaseMessage, isAIMessage } from \"@langchain/core/messages\";\n", + "import { BaseMessage, isAIMessage } from \"@langchain/core/messages\";\n", "\n", - "type MessagesState = {\n", - " messages: BaseMessage[];\n", - "}\n", + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: messagesStateReducer,\n", + " }),\n", + "});\n", "\n", - "const toolNode = new ToolNode([getWeather]);\n", + "const toolNode = new ToolNode([getWeather]);\n", "\n", "const modelWithTools = new ChatAnthropic({\n", " model: \"claude-3-haiku-20240307\",\n", " temperature: 0,\n", "}).bindTools([getWeather]);\n", "\n", - "const shouldContinue = async ({ messages }: MessagesState) => {\n", + "const shouldContinue = async (state: typeof GraphState.State) => {\n", + " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1];\n", " if (isAIMessage(lastMessage) && lastMessage.tool_calls?.length) {\n", " return \"tools\";\n", @@ -89,20 +92,14 @@ " return \"__end__\";\n", "}\n", "\n", - "const callModel = async ({ messages }: MessagesState) => {\n", + "const callModel = async (state: typeof GraphState.State) => {\n", + " const { messages } = state;\n", " const response = await modelWithTools.invoke(messages);\n", " return { messages: [response] };\n", "}\n", "\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: messagesStateReducer,\n", - " }\n", - " }\n", - "});\n", - "\n", - "const app = workflow.addNode(\"agent\", callModel)\n", + "const app = new StateGraph(GraphState)\n", + " .addNode(\"agent\", callModel)\n", " .addNode(\"tools\", toolNode)\n", " .addEdge(\"__start__\", \"agent\")\n", " .addEdge(\"tools\", \"agent\")\n", @@ -162,7 +159,7 @@ " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01KgUWuDyEz7byrPFF4Jaypo\",\n", + " \"id\": \"toolu_01TiQFrXDi5x4p7oEEZ99T5p\",\n", " \"name\": \"get_weather\",\n", " \"input\": {\n", " \"location\": \"San Francisco\"\n", @@ -177,7 +174,7 @@ " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01X3yFPDXvEXDxx2ndizpMSx\",\n", + " \"id\": \"toolu_01B1LiK5i74E52EwfHL3S3XJ\",\n", " \"name\": \"get_weather\",\n", " \"input\": {\n", " \"location\": \"SAN FRANCISCO\"\n", @@ -233,7 +230,7 @@ " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01D4mFR7MFXteZd9NXeEgxtH\",\n", + " \"id\": \"toolu_011kEBcJ3LnqwbBgTMgsED9C\",\n", " \"name\": \"master_haiku_generator\",\n", " \"input\": {\n", " \"topic\": [\n", @@ -250,7 +247,7 @@ " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01D9cZSk4GZziJbi482q7H54\",\n", + " \"id\": \"toolu_017uPCEEjQzaBAY9iy8mv1cU\",\n", " \"name\": \"master_haiku_generator\",\n", " \"input\": {\n", " \"topic\": [\n", @@ -268,10 +265,14 @@ ], "source": [ "import { StringOutputParser } from \"@langchain/core/output_parsers\";\n", + "import { Annotation } from \"@langchain/langgraph\";\n", + "import { BaseMessage } from \"@langchain/core/messages\";\n", "\n", - "type MessagesState = {\n", - " messages: BaseMessage[];\n", - "}\n", + "const CustomStrategyState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: messagesStateReducer,\n", + " }),\n", + "});\n", "\n", "const haikuRequestSchema = z.object({\n", " topic: z.array(z.string()).length(3),\n", @@ -292,15 +293,16 @@ " schema: haikuRequestSchema,\n", "});\n", "\n", - "const toolNode = new ToolNode([masterHaikuGenerator]);\n", + "const customStrategyToolNode = new ToolNode([masterHaikuGenerator]);\n", "\n", - "const model = new ChatAnthropic({\n", + "const customStrategyModel = new ChatAnthropic({\n", " model: \"claude-3-haiku-20240307\",\n", " temperature: 0,\n", "});\n", - "const modelWithTools = model.bindTools([masterHaikuGenerator]);\n", + "const customStrategyModelWithTools = customStrategyModel.bindTools([masterHaikuGenerator]);\n", "\n", - "const shouldContinue = async ({ messages }: MessagesState) => {\n", + "const customStrategyShouldContinue = async (state: typeof CustomStrategyState.State) => {\n", + " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1];\n", " if (isAIMessage(lastMessage) && lastMessage.tool_calls?.length) {\n", " return \"tools\";\n", @@ -308,24 +310,18 @@ " return \"__end__\";\n", "}\n", "\n", - "const callModel = async ({ messages }: MessagesState) => {\n", - " const response = await modelWithTools.invoke(messages);\n", + "const customStrategyCallModel = async (state: typeof CustomStrategyState.State) => {\n", + " const { messages } = state;\n", + " const response = await customStrategyModelWithTools.invoke(messages);\n", " return { messages: [response] };\n", "}\n", "\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: messagesStateReducer,\n", - " }\n", - " }\n", - "});\n", - "\n", - "const app = workflow.addNode(\"tools\", toolNode)\n", - " .addNode(\"agent\", callModel)\n", + "const customStrategyApp = new StateGraph(CustomStrategyState)\n", + " .addNode(\"tools\", customStrategyToolNode)\n", + " .addNode(\"agent\", customStrategyCallModel)\n", " .addEdge(\"__start__\", \"agent\")\n", " .addEdge(\"tools\", \"agent\")\n", - " .addConditionalEdges(\"agent\", shouldContinue, {\n", + " .addConditionalEdges(\"agent\", customStrategyShouldContinue, {\n", " // Explicitly list possible destinations so that\n", " // we can automatically draw the graph below.\n", " tools: \"tools\",\n", @@ -333,14 +329,14 @@ " })\n", " .compile();\n", "\n", - "const response = await app.invoke(\n", + "const response2 = await customStrategyApp.invoke(\n", " {\n", " messages: [new HumanMessage(\"Write me an incredible haiku about water.\")],\n", " },\n", " { recursionLimit: 10 }\n", ");\n", "\n", - "for (const message of response.messages) {\n", + "for (const message of response2.messages) {\n", " // Anthropic returns tool calls in content as well as in `AIMessage.tool_calls`\n", " const content = JSON.stringify(message.content, null, 2);\n", " console.log(`${message._getType().toUpperCase()}: ${content}`);\n", @@ -358,21 +354,24 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "import { AIMessage, ToolMessage, RemoveMessage } from \"@langchain/core/messages\";\n", + "import { AIMessage, ToolMessage, RemoveMessage, BaseMessage } from \"@langchain/core/messages\";\n", + "import { Annotation } from \"@langchain/langgraph\";\n", "\n", - "type MessagesState = {\n", - " messages: BaseMessage[];\n", - "}\n", + "const CustomStrategyState2 = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: messagesStateReducer,\n", + " }),\n", + "});\n", "\n", - "const haikuRequestSchema = z.object({\n", + "const haikuRequestSchema2 = z.object({\n", " topic: z.array(z.string()).length(3),\n", "});\n", "\n", - "const masterHaikuGenerator = tool(async ({ topic }) => {\n", + "const masterHaikuGenerator2 = tool(async ({ topic }) => {\n", " const model = new ChatAnthropic({\n", " model: \"claude-3-haiku-20240307\",\n", " temperature: 0,\n", @@ -384,10 +383,11 @@ "}, {\n", " name: \"master_haiku_generator\",\n", " description: \"Generates a haiku based on the provided topics.\",\n", - " schema: haikuRequestSchema,\n", + " schema: haikuRequestSchema2,\n", "});\n", "\n", - "const callTool = async ({ messages }: MessagesState) => {\n", + "const callTool2 = async (state: typeof CustomStrategyState2.State) => {\n", + " const { messages } = state;\n", " const toolsByName = { master_haiku_generator: masterHaikuGenerator };\n", " const lastMessage = messages[messages.length - 1] as AIMessage;\n", " const outputMessages: ToolMessage[] = [];\n", @@ -414,15 +414,16 @@ " model: \"claude-3-haiku-20240307\",\n", " temperature: 0,\n", "});\n", - "const modelWithTools = model.bindTools([masterHaikuGenerator]);\n", + "const modelWithTools2 = model.bindTools([masterHaikuGenerator2]);\n", "\n", "const betterModel = new ChatAnthropic({\n", " model: \"claude-3-5-sonnet-20240620\",\n", " temperature: 0,\n", "});\n", - "const betterModelWithTools = betterModel.bindTools([masterHaikuGenerator]);\n", + "const betterModelWithTools = betterModel.bindTools([masterHaikuGenerator2]);\n", "\n", - "const shouldContinue = async ({ messages }: MessagesState) => {\n", + "const shouldContinue2 = async (state: typeof CustomStrategyState2.State) => {\n", + " const { messages } = state;\n", " const lastMessage = messages[messages.length - 1];\n", " if (isAIMessage(lastMessage) && lastMessage.tool_calls?.length) {\n", " return \"tools\";\n", @@ -430,7 +431,8 @@ " return \"__end__\";\n", "}\n", "\n", - "const shouldFallback = async ({ messages }: MessagesState) => {\n", + "const shouldFallback = async (state: typeof CustomStrategyState2.State) => {\n", + " const { messages } = state;\n", " const failedToolMessages = messages.find((message) => {\n", " return message._getType() === \"tool\" && message.additional_kwargs.error !== undefined;\n", " });\n", @@ -440,12 +442,14 @@ " return \"agent\";\n", "}\n", "\n", - "const callModel = async ({ messages }: MessagesState) => {\n", - " const response = await modelWithTools.invoke(messages);\n", + "const callModel2 = async (state: typeof CustomStrategyState2.State) => {\n", + " const { messages } = state;\n", + " const response = await modelWithTools2.invoke(messages);\n", " return { messages: [response] };\n", "}\n", "\n", - "const removeFailedToolCallAttempt = async ({ messages }: MessagesState) => {\n", + "const removeFailedToolCallAttempt = async (state: typeof CustomStrategyState2.State) => {\n", + " const { messages } = state;\n", " // Remove all messages from the most recent\n", " // instance of AIMessage onwards.\n", " const lastAIMessageIndex = messages\n", @@ -456,26 +460,19 @@ " return { messages: messagesToRemove.map(m => new RemoveMessage({ id: m.id })) };\n", "}\n", "\n", - "const callFallbackModel = async ({ messages }: MessagesState) => {\n", + "const callFallbackModel = async (state: typeof CustomStrategyState2.State) => {\n", + " const { messages } = state;\n", " const response = await betterModelWithTools.invoke(messages);\n", " return { messages: [response] };\n", "}\n", "\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: messagesStateReducer,\n", - " }\n", - " }\n", - "});\n", - "\n", - "const app = workflow\n", - " .addNode(\"tools\", callTool)\n", - " .addNode(\"agent\", callModel)\n", + "const app2 = new StateGraph(CustomStrategyState2)\n", + " .addNode(\"tools\", callTool2)\n", + " .addNode(\"agent\", callModel2)\n", " .addNode(\"remove_failed_tool_call_attempt\", removeFailedToolCallAttempt)\n", " .addNode(\"fallback_agent\", callFallbackModel)\n", " .addEdge(\"__start__\", \"agent\")\n", - " .addConditionalEdges(\"agent\", shouldContinue, {\n", + " .addConditionalEdges(\"agent\", shouldContinue2, {\n", " // Explicitly list possible destinations so that\n", " // we can automatically draw the graph below.\n", " tools: \"tools\",\n", @@ -501,7 +498,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -515,11 +512,11 @@ "source": [ "import * as tslab from \"tslab\";\n", "\n", - "const graph = app.getGraph();\n", - "const image = await graph.drawMermaidPng();\n", - "const arrayBuffer = await image.arrayBuffer();\n", + "const graph2 = app2.getGraph();\n", + "const image2 = await graph2.drawMermaidPng();\n", + "const arrayBuffer2 = await image2.arrayBuffer();\n", "\n", - "await tslab.display.png(new Uint8Array(arrayBuffer));" + "await tslab.display.png(new Uint8Array(arrayBuffer2));" ] }, { @@ -531,7 +528,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -542,7 +539,7 @@ " agent: {\n", " messages: [\n", " AIMessage {\n", - " \"id\": \"msg_013hBkXecRH3SjkB9zBHsRAh\",\n", + " \"id\": \"msg_01GLo6bFLEpcKH1mZHkb2jhf\",\n", " \"content\": [\n", " {\n", " \"type\": \"text\",\n", @@ -550,7 +547,7 @@ " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01Xj165iEXzLxGHwXm3JNzc4\",\n", + " \"id\": \"toolu_01Lyy87dsq6ja6NBZp2P2bLj\",\n", " \"name\": \"master_haiku_generator\",\n", " \"input\": {\n", " \"topic\": \"[Array]\"\n", @@ -558,7 +555,7 @@ " }\n", " ],\n", " \"additional_kwargs\": {\n", - " \"id\": \"msg_013hBkXecRH3SjkB9zBHsRAh\",\n", + " \"id\": \"msg_01GLo6bFLEpcKH1mZHkb2jhf\",\n", " \"type\": \"message\",\n", " \"role\": \"assistant\",\n", " \"model\": \"claude-3-haiku-20240307\",\n", @@ -570,7 +567,7 @@ " }\n", " },\n", " \"response_metadata\": {\n", - " \"id\": \"msg_013hBkXecRH3SjkB9zBHsRAh\",\n", + " \"id\": \"msg_01GLo6bFLEpcKH1mZHkb2jhf\",\n", " \"model\": \"claude-3-haiku-20240307\",\n", " \"stop_reason\": \"tool_use\",\n", " \"stop_sequence\": null,\n", @@ -587,7 +584,7 @@ " \"args\": {\n", " \"topic\": \"[Array]\"\n", " },\n", - " \"id\": \"toolu_01Xj165iEXzLxGHwXm3JNzc4\",\n", + " \"id\": \"toolu_01Lyy87dsq6ja6NBZp2P2bLj\",\n", " \"type\": \"tool_call\"\n", " }\n", " ],\n", @@ -605,7 +602,7 @@ " tools: {\n", " messages: [\n", " ToolMessage {\n", - " \"id\": \"99f4925e-1afb-402a-b3ce-bbabe573ffa4\",\n", + " \"id\": \"7c551efd-03a1-4a3d-bc13-29f253ed64da\",\n", " \"content\": \"Received tool input did not match expected schema\",\n", " \"name\": \"master_haiku_generator\",\n", " \"additional_kwargs\": {\n", @@ -614,7 +611,7 @@ " }\n", " },\n", " \"response_metadata\": {},\n", - " \"tool_call_id\": \"toolu_01Xj165iEXzLxGHwXm3JNzc4\"\n", + " \"tool_call_id\": \"toolu_01Lyy87dsq6ja6NBZp2P2bLj\"\n", " }\n", " ]\n", " }\n", @@ -623,13 +620,13 @@ " remove_failed_tool_call_attempt: {\n", " messages: [\n", " BaseMessage {\n", - " \"id\": \"msg_013hBkXecRH3SjkB9zBHsRAh\",\n", + " \"id\": \"msg_01GLo6bFLEpcKH1mZHkb2jhf\",\n", " \"content\": \"\",\n", " \"additional_kwargs\": {},\n", " \"response_metadata\": {}\n", " },\n", " BaseMessage {\n", - " \"id\": \"99f4925e-1afb-402a-b3ce-bbabe573ffa4\",\n", + " \"id\": \"7c551efd-03a1-4a3d-bc13-29f253ed64da\",\n", " \"content\": \"\",\n", " \"additional_kwargs\": {},\n", " \"response_metadata\": {}\n", @@ -641,7 +638,7 @@ " fallback_agent: {\n", " messages: [\n", " AIMessage {\n", - " \"id\": \"msg_01DjJX1aBUAfLmWuR3TpoATG\",\n", + " \"id\": \"msg_01EAWJF5xxc2wpr2gtqzT4AK\",\n", " \"content\": [\n", " {\n", " \"type\": \"text\",\n", @@ -649,7 +646,7 @@ " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01PeHtXkhProbuBSszBi4n84\",\n", + " \"id\": \"toolu_015ktvv3AAxZiUtXoMhrfyw7\",\n", " \"name\": \"master_haiku_generator\",\n", " \"input\": {\n", " \"topic\": \"[Array]\"\n", @@ -657,7 +654,7 @@ " }\n", " ],\n", " \"additional_kwargs\": {\n", - " \"id\": \"msg_01DjJX1aBUAfLmWuR3TpoATG\",\n", + " \"id\": \"msg_01EAWJF5xxc2wpr2gtqzT4AK\",\n", " \"type\": \"message\",\n", " \"role\": \"assistant\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", @@ -669,7 +666,7 @@ " }\n", " },\n", " \"response_metadata\": {\n", - " \"id\": \"msg_01DjJX1aBUAfLmWuR3TpoATG\",\n", + " \"id\": \"msg_01EAWJF5xxc2wpr2gtqzT4AK\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", " \"stop_reason\": \"tool_use\",\n", " \"stop_sequence\": null,\n", @@ -686,7 +683,7 @@ " \"args\": {\n", " \"topic\": \"[Array]\"\n", " },\n", - " \"id\": \"toolu_01PeHtXkhProbuBSszBi4n84\",\n", + " \"id\": \"toolu_015ktvv3AAxZiUtXoMhrfyw7\",\n", " \"type\": \"tool_call\"\n", " }\n", " ],\n", @@ -704,12 +701,12 @@ " tools: {\n", " messages: [\n", " ToolMessage {\n", - " \"id\": \"3ceee01b-583e-4538-9960-fc6982dac95f\",\n", + " \"id\": \"5fa074e5-2af1-4da5-8260-c94ee8127fa6\",\n", " \"content\": \"Here is a haiku about water, flow, and reflection:\\n\\nRippling waters flow,\\nMirroring the sky above,\\nTranquil reflection.\",\n", " \"name\": \"master_haiku_generator\",\n", " \"additional_kwargs\": {},\n", " \"response_metadata\": {},\n", - " \"tool_call_id\": \"toolu_01PeHtXkhProbuBSszBi4n84\"\n", + " \"tool_call_id\": \"toolu_015ktvv3AAxZiUtXoMhrfyw7\"\n", " }\n", " ]\n", " }\n", @@ -718,10 +715,10 @@ " agent: {\n", " messages: [\n", " AIMessage {\n", - " \"id\": \"msg_013nHqwYg34aLh4Ha3MYsTsb\",\n", + " \"id\": \"msg_014EjX9Y5WTW7vP1RyWWURCW\",\n", " \"content\": \"I hope you enjoy this haiku about the beauty and serenity of water. Please let me know if you would like me to generate another one.\",\n", " \"additional_kwargs\": {\n", - " \"id\": \"msg_013nHqwYg34aLh4Ha3MYsTsb\",\n", + " \"id\": \"msg_014EjX9Y5WTW7vP1RyWWURCW\",\n", " \"type\": \"message\",\n", " \"role\": \"assistant\",\n", " \"model\": \"claude-3-haiku-20240307\",\n", @@ -733,7 +730,7 @@ " }\n", " },\n", " \"response_metadata\": {\n", - " \"id\": \"msg_013nHqwYg34aLh4Ha3MYsTsb\",\n", + " \"id\": \"msg_014EjX9Y5WTW7vP1RyWWURCW\",\n", " \"model\": \"claude-3-haiku-20240307\",\n", " \"stop_reason\": \"end_turn\",\n", " \"stop_sequence\": null,\n", @@ -759,7 +756,7 @@ } ], "source": [ - "const stream = await app.stream(\n", + "const stream = await app2.stream(\n", " { messages: [new HumanMessage(\"Write me an incredible haiku about water.\")] },\n", " { recursionLimit: 10 },\n", ")\n", diff --git a/examples/how-tos/use-in-web-environments.ipynb b/examples/how-tos/use-in-web-environments.ipynb index a4612e89..7df80dca 100644 --- a/examples/how-tos/use-in-web-environments.ipynb +++ b/examples/how-tos/use-in-web-environments.ipynb @@ -42,29 +42,22 @@ " END,\n", " START,\n", " StateGraph,\n", - " StateGraphArgs,\n", + " Annotation,\n", "} from \"@langchain/langgraph/web\";\n", - "import { HumanMessage } from \"@langchain/core/messages\";\n", + "import { BaseMessage, HumanMessage } from \"@langchain/core/messages\";\n", "\n", - "// Define the state interface\n", - "interface AgentState {\n", - " messages: HumanMessage[];\n", - "}\n", - "\n", - "// Define the graph state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: HumanMessage[], y: HumanMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};\n", + "const GraphState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});\n", "\n", - "const nodeFn = async (_state: AgentState) => {\n", + "const nodeFn = async (_state: typeof GraphState.State) => {\n", " return { messages: [new HumanMessage(\"Hello from the browser!\")] };\n", "};\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({ channels: graphState })\n", + "const workflow = new StateGraph(GraphState)\n", " .addNode(\"node\", nodeFn)\n", " .addEdge(START, \"node\")\n", " .addEdge(\"node\", END);\n", @@ -118,10 +111,23 @@ "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "Received 0 events from the nested function\n" + "evalmachine.:41\n", + "for await (const event of eventStream2) {\n", + " ^^^^^\n", + "\n", + "SyntaxError: Unexpected reserved word\n", + " at new Script (node:vm:116:7)\n", + " at createScript (node:vm:268:10)\n", + " at Object.runInThisContext (node:vm:316:10)\n", + " at Object.execute (/Users/bracesproul/code/lang-chain-ai/langgraphjs/examples/node_modules/tslab/dist/executor.js:160:38)\n", + " at JupyterHandlerImpl.handleExecuteImpl (/Users/bracesproul/code/lang-chain-ai/langgraphjs/examples/node_modules/tslab/dist/jupyter.js:250:38)\n", + " at /Users/bracesproul/code/lang-chain-ai/langgraphjs/examples/node_modules/tslab/dist/jupyter.js:208:57\n", + " at async JupyterHandlerImpl.handleExecute (/Users/bracesproul/code/lang-chain-ai/langgraphjs/examples/node_modules/tslab/dist/jupyter.js:208:21)\n", + " at async ZmqServer.handleExecute (/Users/bracesproul/code/lang-chain-ai/langgraphjs/examples/node_modules/tslab/dist/jupyter.js:406:25)\n", + " at async ZmqServer.handleShellMessage (/Users/bracesproul/code/lang-chain-ai/langgraphjs/examples/node_modules/tslab/dist/jupyter.js:351:21)\n" ] } ], @@ -131,26 +137,19 @@ " END,\n", " START,\n", " StateGraph,\n", - " StateGraphArgs,\n", + " Annotation,\n", "} from \"@langchain/langgraph/web\";\n", - "import { HumanMessage } from \"@langchain/core/messages\";\n", + "import { BaseMessage } from \"@langchain/core/messages\";\n", "import { RunnableLambda } from \"@langchain/core/runnables\";\n", "import { type StreamEvent } from \"@langchain/core/tracers/log_stream\";\n", "\n", - "// Define the state interface\n", - "interface AgentState {\n", - " messages: HumanMessage[];\n", - "}\n", - "\n", - "// Define the graph state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: HumanMessage[], y: HumanMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};\n", + "const GraphState2 = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});\n", "\n", - "const nodeFn = async (_state: AgentState) => {\n", + "const nodeFn2 = async (_state: typeof GraphState2.State) => {\n", " // Note that we do not pass any `config` through here\n", " const nestedFn = RunnableLambda.from(async (input: string) => {\n", " return new HumanMessage(`Hello from ${input}!`);\n", @@ -160,27 +159,27 @@ "};\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({ channels: graphState })\n", - " .addNode(\"node\", nodeFn)\n", + "const workflow2 = new StateGraph(GraphState2)\n", + " .addNode(\"node\", nodeFn2)\n", " .addEdge(START, \"node\")\n", " .addEdge(\"node\", END);\n", "\n", - "const app = workflow.compile({});\n", + "const app2 = workflow2.compile({});\n", "\n", "// Stream intermediate steps from the graph\n", - "const eventStream = await app.streamEvents(\n", + "const eventStream2 = app2.streamEvents(\n", " { messages: [] },\n", " { version: \"v2\" },\n", " { includeNames: [\"nested\"] },\n", ");\n", "\n", - "const events: StreamEvent[] = [];\n", - "for await (const event of eventStream) {\n", + "const events2: StreamEvent[] = [];\n", + "for await (const event of eventStream2) {\n", " console.log(event);\n", - " events.push(event);\n", + " events2.push(event);\n", "}\n", "\n", - "console.log(`Received ${events.length} events from the nested function`);" + "console.log(`Received ${events2.length} events from the nested function`);" ] }, { @@ -195,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -242,27 +241,20 @@ " END,\n", " START,\n", " StateGraph,\n", - " StateGraphArgs,\n", + " Annotation,\n", "} from \"@langchain/langgraph/web\";\n", - "import { HumanMessage } from \"@langchain/core/messages\";\n", + "import { BaseMessage } from \"@langchain/core/messages\";\n", "import { type RunnableConfig, RunnableLambda } from \"@langchain/core/runnables\";\n", "import { type StreamEvent } from \"@langchain/core/tracers/log_stream\";\n", "\n", - "// Define the state interface\n", - "interface AgentState {\n", - " messages: HumanMessage[];\n", - "}\n", - "\n", - "// Define the graph state\n", - "const graphState: StateGraphArgs[\"channels\"] = {\n", - " messages: {\n", - " value: (x: HumanMessage[], y: HumanMessage[]) => x.concat(y),\n", - " default: () => [],\n", - " },\n", - "};\n", + "const GraphState3 = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: (x, y) => x.concat(y),\n", + " }),\n", + "});\n", "\n", "// Note the second argument here.\n", - "const nodeFn = async (_state: AgentState, config?: RunnableConfig) => {\n", + "const nodeFn3 = async (_state: typeof GraphState3.State, config?: RunnableConfig) => {\n", " // If you need to nest deeper, remember to pass `_config` when invoking\n", " const nestedFn = RunnableLambda.from(\n", " async (input: string, _config?: RunnableConfig) => {\n", @@ -274,27 +266,27 @@ "};\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({ channels: graphState })\n", - " .addNode(\"node\", nodeFn)\n", + "const workflow3 = new StateGraph(GraphState3)\n", + " .addNode(\"node\", nodeFn3)\n", " .addEdge(START, \"node\")\n", " .addEdge(\"node\", END);\n", "\n", - "const app = workflow.compile({});\n", + "const app3 = workflow3.compile({});\n", "\n", "// Stream intermediate steps from the graph\n", - "const eventStream = await app.streamEvents(\n", + "const eventStream3 = app3.streamEvents(\n", " { messages: [] },\n", " { version: \"v2\" },\n", " { includeNames: [\"nested\"] },\n", ");\n", "\n", - "const events: StreamEvent[] = [];\n", - "for await (const event of eventStream) {\n", + "const events3: StreamEvent[] = [];\n", + "for await (const event of eventStream3) {\n", " console.log(event);\n", - " events.push(event);\n", + " events3.push(event);\n", "}\n", "\n", - "console.log(`Received ${events.length} events from the nested function`);" + "console.log(`Received ${events3.length} events from the nested function`);" ] }, { @@ -315,17 +307,20 @@ ], "metadata": { "kernelspec": { - "display_name": "Deno", + "display_name": "TypeScript", "language": "typescript", - "name": "deno" + "name": "tslab" }, "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, "file_extension": ".ts", - "mimetype": "text/x.typescript", + "mimetype": "text/typescript", "name": "typescript", - "nb_converter": "script", - "pygments_lexer": "typescript", - "version": "5.3.3" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/examples/how-tos/wait-user-input.ipynb b/examples/how-tos/wait-user-input.ipynb index 3f6cb900..09270d47 100644 --- a/examples/how-tos/wait-user-input.ipynb +++ b/examples/how-tos/wait-user-input.ipynb @@ -72,35 +72,30 @@ "metadata": {}, "outputs": [], "source": [ - "import { StateGraph, START, END } from \"@langchain/langgraph\";\n", + "import { StateGraph, Annotation, START, END } from \"@langchain/langgraph\";\n", "import { MemorySaver } from \"@langchain/langgraph\";\n", "\n", - "type GraphState = {\n", - " input: string;\n", - " userFeedback: string;\n", - "}\n", + "const GraphState = Annotation.Root({\n", + " input: Annotation,\n", + " userFeedback: Annotation\n", + "});\n", "\n", - "const step1 = (state: GraphState): Partial => {\n", - " console.log(\"--- Step 1 ---\");\n", - " return state;\n", + "const step1 = (state: typeof GraphState.State) => {\n", + " console.log(\"---Step 1---\");\n", + " return state;\n", "}\n", "\n", - "const humanFeedback = (state: GraphState): Partial => {\n", - " console.log(\"--- humanFeedback ---\");\n", - " return state;\n", + "const humanFeedback = (state: typeof GraphState.State) => {\n", + " console.log(\"--- humanFeedback ---\");\n", + " return state;\n", "}\n", "\n", - "const step3 = (state: GraphState): Partial => {\n", - " console.log(\"--- Step 3 ---\");\n", - " return state;\n", + "const step3 = (state: typeof GraphState.State) => {\n", + " console.log(\"---Step 3---\");\n", + " return state;\n", "}\n", "\n", - "const builder = new StateGraph({\n", - " channels: {\n", - " input: null,\n", - " userFeedback: null,\n", - " }\n", - "})\n", + "const builder = new StateGraph(GraphState)\n", " .addNode(\"step1\", step1)\n", " .addNode(\"humanFeedback\", humanFeedback)\n", " .addNode(\"step3\", step3)\n", @@ -115,17 +110,25 @@ "\n", "// Add \n", "const graph = builder.compile({\n", - " checkpointer: memory,\n", - " interruptBefore: [\"humanFeedback\"]\n", + " checkpointer: memory,\n", + " interruptBefore: [\"humanFeedback\"]\n", "});" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "9e990a56", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import * as tslab from \"tslab\";\n", "\n", @@ -146,7 +149,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "id": "eb8e7d47-e7c9-4217-b72c-08394a2c4d3e", "metadata": {}, "outputs": [ @@ -155,7 +158,7 @@ "output_type": "stream", "text": [ "--- hello world ---\n", - "--- Step 1 ---\n", + "---Step 1---\n", "--- hello world ---\n", "--- GRAPH INTERRUPTED ---\n" ] @@ -170,7 +173,7 @@ "\n", "// Run the graph until the first interruption\n", "for await (const event of await graph.stream(initialInput, config)) {\n", - " console.log(`--- ${event.input} ---`);\n", + " console.log(`--- ${event.input} ---`);\n", "}\n", "\n", "// Will log when the graph is interrupted, after step 2.\n", @@ -187,7 +190,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "id": "2165a1bc-1c5b-411f-9e9c-a2b9627e5d56", "metadata": {}, "outputs": [ @@ -197,35 +200,20 @@ "text": [ "--- State after update ---\n", "{\n", - " values: { input: \"hello world\", userFeedback: \"Go to step 3!!\" },\n", - " next: [ \"humanFeedback\" ],\n", - " metadata: {\n", - " source: \"update\",\n", - " step: 20,\n", - " writes: {\n", - " step1: { userFeedback: \"Go to step 3!!\", asNode: \"humanFeedback\" }\n", - " }\n", - " },\n", + " values: { input: 'hello world', userFeedback: 'Go to step 3!!' },\n", + " next: [ 'humanFeedback' ],\n", + " metadata: { source: 'update', step: 2, writes: { step1: [Object] } },\n", " config: {\n", " configurable: {\n", - " thread_id: \"1\",\n", - " checkpoint_id: \"1ef46202-3ded-6a80-8014-72aef304b6c8\"\n", + " thread_id: '1',\n", + " checkpoint_id: '1ef5e8fb-89dd-6360-8002-5ff9e3c15c57'\n", " }\n", " },\n", - " createdAt: \"2024-07-19T22:42:12.648Z\",\n", + " createdAt: '2024-08-20T01:01:24.246Z',\n", " parentConfig: undefined\n", - "}\n" + "}\n", + "[ 'humanFeedback' ]\n" ] - }, - { - "data": { - "text/plain": [ - "[ \u001b[32m\"humanFeedback\"\u001b[39m ]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -253,7 +241,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 5, "id": "3cca588f-e8d8-416b-aba7-0f3ae5e51598", "metadata": {}, "outputs": [ @@ -263,7 +251,7 @@ "text": [ "--- humanFeedback ---\n", "--- hello world ---\n", - "--- Step 3 ---\n", + "---Step 3---\n", "--- hello world ---\n" ] } @@ -271,7 +259,7 @@ "source": [ "// Continue the graph execution\n", "for await (const event of await graph.stream(null, config)) {\n", - " console.log(`--- ${event.input} ---`);\n", + " console.log(`--- ${event.input} ---`);\n", "}" ] }, @@ -285,19 +273,16 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 6, "id": "2b83e5ca-8497-43ca-bff7-7203e654c4d3", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "{ input: \u001b[32m\"hello world\"\u001b[39m, userFeedback: \u001b[32m\"Go to step 3!!\"\u001b[39m }" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "{ input: 'hello world', userFeedback: 'Go to step 3!!' }\n" + ] } ], "source": [ @@ -328,37 +313,38 @@ "// Set up the tool\n", "import { ChatAnthropic } from \"@langchain/anthropic\";\n", "import { tool } from \"@langchain/core/tools\";\n", - "import { StateGraph, START, END } from \"@langchain/langgraph\";\n", + "import { StateGraph, Annotation, START, END, messagesStateReducer } from \"@langchain/langgraph\";\n", "import { MemorySaver } from \"@langchain/langgraph\";\n", "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", "import { BaseMessage, AIMessage } from \"@langchain/core/messages\";\n", "import { z } from \"zod\";\n", - "import { v4 as uuidv4 } from \"uuid\";\n", "\n", - "interface MessagesState {\n", - " messages: BaseMessage[];\n", - "}\n", + "const GraphMessagesState = Annotation.Root({\n", + " messages: Annotation({\n", + " reducer: messagesStateReducer,\n", + " }),\n", + "});\n", "\n", - "const search = tool((input) => {\n", - " return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\";\n", + "const search = tool((_) => {\n", + " return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\";\n", "}, {\n", - " name: \"search\",\n", - " description: \"Call to surf the web.\",\n", - " schema: z.string(),\n", + " name: \"search\",\n", + " description: \"Call to surf the web.\",\n", + " schema: z.string(),\n", "})\n", "\n", "const tools = [search]\n", - "const toolNode = new ToolNode(tools)\n", + "const toolNode = new ToolNode(tools)\n", "\n", "// Set up the model\n", "const model = new ChatAnthropic({ model: \"claude-3-5-sonnet-20240620\" })\n", "\n", - "const askHumanTool = tool((input: string) => {\n", - " return \"The human said XYZ\";\n", + "const askHumanTool = tool((_) => {\n", + " return \"The human said XYZ\";\n", "}, {\n", - " name: \"askHuman\",\n", - " description: \"Ask the human for input.\",\n", - " schema: z.string(),\n", + " name: \"askHuman\",\n", + " description: \"Ask the human for input.\",\n", + " schema: z.string(),\n", "});\n", "\n", "\n", @@ -367,150 +353,97 @@ "// Define nodes and conditional edges\n", "\n", "// Define the function that determines whether to continue or not\n", - "function shouldContinue(state: MessagesState): \"action\" | \"askHuman\" | typeof END {\n", - " const lastMessage = state.messages[state.messages.length - 1];\n", - " const castLastMessage = lastMessage as AIMessage;\n", - " // If there is no function call, then we finish\n", - " if (castLastMessage && !castLastMessage.tool_calls?.length) {\n", - " return END;\n", - " }\n", - " // If tool call is askHuman, we return that node\n", - " // You could also add logic here to let some system know that there's something that requires Human input\n", - " // For example, send a slack message, etc\n", - " if (castLastMessage.tool_calls?.[0]?.name === \"askHuman\") {\n", - " console.log(\"--- ASKING HUMAN ---\")\n", - " return \"askHuman\";\n", - " }\n", - " // Otherwise if it isn't, we continue with the action node\n", - " return \"action\";\n", + "function shouldContinue(state: typeof GraphMessagesState.State): \"action\" | \"askHuman\" | typeof END {\n", + " const lastMessage = state.messages[state.messages.length - 1];\n", + " const castLastMessage = lastMessage as AIMessage;\n", + " // If there is no function call, then we finish\n", + " if (castLastMessage && !castLastMessage.tool_calls?.length) {\n", + " return END;\n", + " }\n", + " // If tool call is askHuman, we return that node\n", + " // You could also add logic here to let some system know that there's something that requires Human input\n", + " // For example, send a slack message, etc\n", + " if (castLastMessage.tool_calls?.[0]?.name === \"askHuman\") {\n", + " console.log(\"--- ASKING HUMAN ---\")\n", + " return \"askHuman\";\n", + " }\n", + " // Otherwise if it isn't, we continue with the action node\n", + " return \"action\";\n", "}\n", "\n", "\n", "// Define the function that calls the model\n", - "async function callModel(state: MessagesState): Promise> {\n", - " const messages = state.messages;\n", - " const response = await modelWithTools.invoke(messages);\n", - " // We return an object with a messages property, because this will get added to the existing list\n", - " return { messages: [response] };\n", + "async function callModel(state: typeof GraphMessagesState.State): Promise> {\n", + " const messages = state.messages;\n", + " const response = await modelWithTools.invoke(messages);\n", + " // We return an object with a messages property, because this will get added to the existing list\n", + " return { messages: [response] };\n", "}\n", "\n", "\n", "// We define a fake node to ask the human\n", - "function askHuman(state: MessagesState): Partial {\n", - " return state;\n", - "}\n", - "\n", - "\n", - "type Messages = BaseMessage | BaseMessage[]\n", - "\n", - "// Define our addMessages function which will add or merge messages to state\n", - "function addMessages(left: Messages, right: Messages): BaseMessage[] {\n", - " // Coerce to list\n", - " const leftList = Array.isArray(left) ? left : [left];\n", - " const rightList = Array.isArray(right) ? right : [right];\n", - "\n", - " // Assign missing ids\n", - " leftList.forEach(m => {\n", - " if (!m.id) m.id = uuidv4();\n", - " });\n", - " rightList.forEach(m => {\n", - " if (!m.id) m.id = uuidv4();\n", - " });\n", - "\n", - " // Merge\n", - " const leftIdxById = new Map(leftList.map((m, i) => [m.id, i]));\n", - " const merged = [...leftList];\n", - " const idsToRemove = new Set();\n", - "\n", - " for (const m of rightList) {\n", - " const existingIdx = leftIdxById.get(m.id!);\n", - " if (existingIdx !== undefined) {\n", - " if (m._getType() === 'remove' && m.id) {\n", - " idsToRemove.add(m.id);\n", - " } else {\n", - " merged[existingIdx] = m;\n", - " }\n", - " } else {\n", - " if (m._getType() === 'remove') {\n", - " throw new Error(`Attempting to delete a message with an ID that doesn't exist ('${m.id}')`);\n", - " }\n", - " merged.push(m);\n", - " }\n", - " }\n", - "\n", - " return merged.filter(m => !idsToRemove.has(m.id!));\n", + "function askHuman(state: typeof GraphMessagesState.State): Partial {\n", + " return state;\n", "}\n", "\n", "// Define a new graph\n", - "const workflow = new StateGraph({\n", - " channels: {\n", - " messages: {\n", - " reducer: addMessages\n", - " },\n", - " }\n", - "})\n", - " // Define the two nodes we will cycle between\n", - " .addNode(\"agent\", callModel)\n", - " .addNode(\"action\", toolNode)\n", - " .addNode(\"askHuman\", askHuman)\n", - " // We now add a conditional edge\n", - " .addConditionalEdges(\n", - " // First, we define the start node. We use `agent`.\n", - " // This means these are the edges taken after the `agent` node is called.\n", - " \"agent\",\n", - " // Next, we pass in the function that will determine which node is called next.\n", - " shouldContinue\n", - " )\n", - " // We now add a normal edge from `action` to `agent`.\n", - " // This means that after `action` is called, `agent` node is called next.\n", - " .addEdge(\"action\", \"agent\")\n", - " // After we get back the human response, we go back to the agent\n", - " .addEdge(\"askHuman\", \"agent\")\n", - " // Set the entrypoint as `agent`\n", - " // This means that this node is the first one called\n", - " .addEdge(START, \"agent\");\n", + "const messagesWorkflow = new StateGraph(GraphMessagesState)\n", + " // Define the two nodes we will cycle between\n", + " .addNode(\"agent\", callModel)\n", + " .addNode(\"action\", toolNode)\n", + " .addNode(\"askHuman\", askHuman)\n", + " // We now add a conditional edge\n", + " .addConditionalEdges(\n", + " // First, we define the start node. We use `agent`.\n", + " // This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " // Next, we pass in the function that will determine which node is called next.\n", + " shouldContinue\n", + " )\n", + " // We now add a normal edge from `action` to `agent`.\n", + " // This means that after `action` is called, `agent` node is called next.\n", + " .addEdge(\"action\", \"agent\")\n", + " // After we get back the human response, we go back to the agent\n", + " .addEdge(\"askHuman\", \"agent\")\n", + " // Set the entrypoint as `agent`\n", + " // This means that this node is the first one called\n", + " .addEdge(START, \"agent\");\n", "\n", "\n", "// Setup memory\n", - "const memory = new MemorySaver();\n", + "const messagesMemory = new MemorySaver();\n", "\n", "// Finally, we compile it!\n", "// This compiles it into a LangChain Runnable,\n", "// meaning you can use it as you would any other runnable\n", - "const app = workflow.compile({\n", - " checkpointer: memory,\n", + "const messagesApp = messagesWorkflow.compile({\n", + " checkpointer: messagesMemory,\n", " interruptBefore: [\"askHuman\"]\n", "});" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "4b816850", "metadata": {}, "outputs": [ { - "ename": "Error", - "evalue": "Not ready", - "output_type": "error", - "traceback": [ - "Stack trace:", - "Error: Not ready", - " at DisplayImpl.raw (file:///Users/bracesproul/Library/Caches/deno/npm/registry.npmjs.org/tslab/1.0.22/dist/public.js:83:19)", - " at DisplayImpl.png (file:///Users/bracesproul/Library/Caches/deno/npm/registry.npmjs.org/tslab/1.0.22/dist/public.js:67:14)", - " at :5:21", - " at eventLoopTick (ext:core/01_core.js:63:7)" - ] + "data": { + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ "import * as tslab from \"tslab\";\n", "\n", - "const drawableGraph = app.getGraph();\n", - "const image = await drawableGraph.drawMermaidPng();\n", - "const arrayBuffer = await image.arrayBuffer();\n", + "const drawableGraph2 = messagesApp.getGraph();\n", + "const image2 = await drawableGraph2.drawMermaidPng();\n", + "const arrayBuffer2 = await image2.arrayBuffer();\n", "\n", - "await tslab.display.png(new Uint8Array(arrayBuffer));" + "await tslab.display.png(new Uint8Array(arrayBuffer2));" ] }, { @@ -527,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ @@ -541,19 +474,19 @@ "================================ ai Message (1) =================================\n", "[\n", " {\n", - " type: \"text\",\n", - " text: \"Certainly! I'll use the askHuman tool to ask the user where they are, and then I'll use the search t\"... 96 more characters\n", + " type: 'text',\n", + " text: \"Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are.\"\n", " },\n", " {\n", - " type: \"tool_use\",\n", - " id: \"toolu_01VF5p3e6Qyi3i2YwoTVZBGc\",\n", - " name: \"askHuman\",\n", + " type: 'tool_use',\n", + " id: 'toolu_01RN181HAAL5BcnMXkexbA1r',\n", + " name: 'askHuman',\n", " input: {\n", - " input: \"Where are you located? Please provide your city and country.\"\n", + " input: 'Where are you located? Please provide your city and country.'\n", " }\n", " }\n", "]\n", - "next: [ \"askHuman\" ]\n" + "next: [ 'askHuman' ]\n" ] } ], @@ -563,17 +496,17 @@ "const inputs = new HumanMessage(\"Use the search tool to ask the user where they are, then look up the weather there\");\n", "\n", "// Thread\n", - "const config = { configurable: { thread_id: \"3\" }, streamMode: \"values\" as const };\n", - "\n", - "for await (const event of await app.stream({\n", - " messages: [inputs]\n", - "}, config)) {\n", - " const recentMsg = event.messages[event.messages.length - 1];\n", - " console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n", - " console.log(recentMsg.content);\n", + "const config2 = { configurable: { thread_id: \"3\" }, streamMode: \"values\" as const };\n", + "\n", + "for await (const event of await messagesApp.stream({\n", + " messages: [inputs]\n", + "}, config2)) {\n", + " const recentMsg = event.messages[event.messages.length - 1];\n", + " console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n", + " console.log(recentMsg.content);\n", "}\n", "\n", - "console.log(\"next: \", (await app.getState(config)).next)" + "console.log(\"next: \", (await messagesApp.getState(config2)).next)" ] }, { @@ -588,7 +521,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "63598092-d565-4170-9773-e092d345f8c1", "metadata": {}, "outputs": [ @@ -596,39 +529,39 @@ "name": "stdout", "output_type": "stream", "text": [ - "next before update state: [ \"askHuman\" ]\n", - "next AFTER update state: [ \"askHuman\" ]\n" + "next before update state: [ 'askHuman' ]\n", + "next AFTER update state: [ 'agent' ]\n" ] } ], "source": [ "import { ToolMessage } from \"@langchain/core/messages\";\n", "\n", - "const currentState = await app.getState(config);\n", + "const currentState = await messagesApp.getState(config2);\n", "\n", "const toolCallId = currentState.values.messages[currentState.values.messages.length - 1].tool_calls[0].id;\n", "\n", "// We now create the tool call with the id and the response we want\n", "const toolMessage = new ToolMessage({\n", - " tool_call_id: toolCallId,\n", - " content: \"san francisco\"\n", + " tool_call_id: toolCallId,\n", + " content: \"san francisco\"\n", "});\n", "\n", - "console.log(\"next before update state: \", (await app.getState(config)).next)\n", + "console.log(\"next before update state: \", (await messagesApp.getState(config2)).next)\n", "\n", "// We now update the state\n", "// Notice that we are also specifying `asNode: \"askHuman\"`\n", "// This will apply this update as this node,\n", "// which will make it so that afterwards it continues as normal\n", - "await app.updateState(config, { messages: [toolMessage] });\n", + "await messagesApp.updateState(config2, { messages: [toolMessage] }, \"askHuman\");\n", "\n", "// We can check the state\n", "// We can see that the state currently has the `agent` node next\n", "// This is based on how we define our graph,\n", "// where after the `askHuman` node goes (which we just triggered)\n", "// there is an edge to the `agent` node\n", - "console.log(\"next AFTER update state: \", (await app.getState(config)).next)\n", - "// await app.getState(config)" + "console.log(\"next AFTER update state: \", (await messagesApp.getState(config2)).next)\n", + "// await messagesApp.getState(config)" ] }, { @@ -641,7 +574,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "a9f599b5-1a55-406b-a76b-f52b3ca06975", "metadata": {}, "outputs": [ @@ -652,21 +585,21 @@ "{\n", " messages: [\n", " HumanMessage {\n", - " \"id\": \"f89d75ad-9702-4605-a5ab-b6152bfc8c05\",\n", + " \"id\": \"a80d5763-0f27-4a00-9e54-8a239b499ea1\",\n", " \"content\": \"Use the search tool to ask the user where they are, then look up the weather there\",\n", " \"additional_kwargs\": {},\n", " \"response_metadata\": {}\n", " },\n", " AIMessage {\n", - " \"id\": \"1a7e49e0-95e8-42af-9488-316907e8bfd2\",\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", " \"content\": [\n", " {\n", " \"type\": \"text\",\n", - " \"text\": \"Certainly! I'll use the askHuman tool to ask the user where they are, and then I'll use the search tool to look up the weather for that location. Let's start by asking the user for their location.\"\n", + " \"text\": \"Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are.\"\n", " },\n", " {\n", " \"type\": \"tool_use\",\n", - " \"id\": \"toolu_01VF5p3e6Qyi3i2YwoTVZBGc\",\n", + " \"id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\",\n", " \"name\": \"askHuman\",\n", " \"input\": {\n", " \"input\": \"Where are you located? Please provide your city and country.\"\n", @@ -674,7 +607,7 @@ " }\n", " ],\n", " \"additional_kwargs\": {\n", - " \"id\": \"msg_01BfBxARH5hF2dyP6VXzeLjt\",\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", " \"type\": \"message\",\n", " \"role\": \"assistant\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", @@ -682,18 +615,20 @@ " \"stop_sequence\": null,\n", " \"usage\": {\n", " \"input_tokens\": 465,\n", - " \"output_tokens\": 110\n", + " \"output_tokens\": 108\n", " }\n", " },\n", " \"response_metadata\": {\n", - " \"id\": \"msg_01BfBxARH5hF2dyP6VXzeLjt\",\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", " \"model\": \"claude-3-5-sonnet-20240620\",\n", " \"stop_reason\": \"tool_use\",\n", " \"stop_sequence\": null,\n", " \"usage\": {\n", " \"input_tokens\": 465,\n", - " \"output_tokens\": 110\n", - " }\n", + " \"output_tokens\": 108\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", " },\n", " \"tool_calls\": [\n", " {\n", @@ -701,75 +636,443 @@ " \"args\": {\n", " \"input\": \"Where are you located? Please provide your city and country.\"\n", " },\n", - " \"id\": \"toolu_01VF5p3e6Qyi3i2YwoTVZBGc\"\n", + " \"id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\",\n", + " \"type\": \"tool_call\"\n", " }\n", " ],\n", " \"invalid_tool_calls\": []\n", + " },\n", + " ToolMessage {\n", + " \"id\": \"9159f841-0e15-4366-96a9-cc5ee0662da0\",\n", + " \"content\": \"san francisco\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\"\n", + " },\n", + " AIMessage {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Thank you for providing your location. Now, I'll use the search tool to look up the weather in San Francisco.\"\n", + " },\n", + " {\n", + " \"type\": \"tool_use\",\n", + " \"id\": \"toolu_01QCcxzRjojWW5JqQp7WTN82\",\n", + " \"name\": \"search\",\n", + " \"input\": {\n", + " \"input\": \"current weather in San Francisco\"\n", + " }\n", + " }\n", + " ],\n", + " \"additional_kwargs\": {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"input\": \"current weather in San Francisco\"\n", + " },\n", + " \"id\": \"toolu_01QCcxzRjojWW5JqQp7WTN82\",\n", + " \"type\": \"tool_call\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81,\n", + " \"total_tokens\": 668\n", + " }\n", " }\n", " ]\n", "}\n", "================================ ai Message (1) =================================\n", "[\n", " {\n", - " type: \"text\",\n", - " text: \"Certainly! I'll use the askHuman tool to ask the user where they are, and then I'll use the search t\"... 96 more characters\n", + " type: 'text',\n", + " text: \"Thank you for providing your location. Now, I'll use the search tool to look up the weather in San Francisco.\"\n", " },\n", " {\n", - " type: \"tool_use\",\n", - " id: \"toolu_01VF5p3e6Qyi3i2YwoTVZBGc\",\n", - " name: \"askHuman\",\n", - " input: {\n", - " input: \"Where are you located? Please provide your city and country.\"\n", - " }\n", + " type: 'tool_use',\n", + " id: 'toolu_01QCcxzRjojWW5JqQp7WTN82',\n", + " name: 'search',\n", + " input: { input: 'current weather in San Francisco' }\n", " }\n", - "]\n" - ] - }, - { - "ename": "Error", - "evalue": "400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"Your API request included an `assistant` message in the final position, which would pre-fill the `assistant` response. When using tools, pre-filling the `assistant` response is not supported.\"}}", - "output_type": "error", - "traceback": [ - "Stack trace:", - "Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"Your API request included an `assistant` message in the final position, which would pre-fill the `assistant` response. When using tools, pre-filling the `assistant` response is not supported.\"}}", - " at Function.generate (file:///Users/bracesproul/Library/Caches/deno/npm/registry.npmjs.org/@anthropic-ai/sdk/0.21.0/error.mjs:36:20)", - " at Anthropic.makeStatusError (file:///Users/bracesproul/Library/Caches/deno/npm/registry.npmjs.org/@anthropic-ai/sdk/0.21.0/core.mjs:256:25)", - " at Anthropic.makeRequest (file:///Users/bracesproul/Library/Caches/deno/npm/registry.npmjs.org/@anthropic-ai/sdk/0.21.0/core.mjs:299:30)", - " at eventLoopTick (ext:core/01_core.js:63:7)", - " at async RetryOperation._fn (file:///Users/bracesproul/Library/Caches/deno/npm/registry.npmjs.org/p-retry/4.6.2/index.js:50:12)" + "]\n", + "{\n", + " messages: [\n", + " HumanMessage {\n", + " \"id\": \"a80d5763-0f27-4a00-9e54-8a239b499ea1\",\n", + " \"content\": \"Use the search tool to ask the user where they are, then look up the weather there\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {}\n", + " },\n", + " AIMessage {\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are.\"\n", + " },\n", + " {\n", + " \"type\": \"tool_use\",\n", + " \"id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\",\n", + " \"name\": \"askHuman\",\n", + " \"input\": {\n", + " \"input\": \"Where are you located? Please provide your city and country.\"\n", + " }\n", + " }\n", + " ],\n", + " \"additional_kwargs\": {\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 465,\n", + " \"output_tokens\": 108\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 465,\n", + " \"output_tokens\": 108\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"askHuman\",\n", + " \"args\": {\n", + " \"input\": \"Where are you located? Please provide your city and country.\"\n", + " },\n", + " \"id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\",\n", + " \"type\": \"tool_call\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": []\n", + " },\n", + " ToolMessage {\n", + " \"id\": \"9159f841-0e15-4366-96a9-cc5ee0662da0\",\n", + " \"content\": \"san francisco\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\"\n", + " },\n", + " AIMessage {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Thank you for providing your location. Now, I'll use the search tool to look up the weather in San Francisco.\"\n", + " },\n", + " {\n", + " \"type\": \"tool_use\",\n", + " \"id\": \"toolu_01QCcxzRjojWW5JqQp7WTN82\",\n", + " \"name\": \"search\",\n", + " \"input\": {\n", + " \"input\": \"current weather in San Francisco\"\n", + " }\n", + " }\n", + " ],\n", + " \"additional_kwargs\": {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"input\": \"current weather in San Francisco\"\n", + " },\n", + " \"id\": \"toolu_01QCcxzRjojWW5JqQp7WTN82\",\n", + " \"type\": \"tool_call\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81,\n", + " \"total_tokens\": 668\n", + " }\n", + " },\n", + " ToolMessage {\n", + " \"id\": \"0bf52bcd-ffbd-4f82-9ee1-7ba2108f0d27\",\n", + " \"content\": \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\",\n", + " \"name\": \"search\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"toolu_01QCcxzRjojWW5JqQp7WTN82\"\n", + " }\n", + " ]\n", + "}\n", + "================================ tool Message (1) =================================\n", + "{\n", + " name: 'search',\n", + " content: \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n", + "}\n", + "{\n", + " messages: [\n", + " HumanMessage {\n", + " \"id\": \"a80d5763-0f27-4a00-9e54-8a239b499ea1\",\n", + " \"content\": \"Use the search tool to ask the user where they are, then look up the weather there\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {}\n", + " },\n", + " AIMessage {\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are.\"\n", + " },\n", + " {\n", + " \"type\": \"tool_use\",\n", + " \"id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\",\n", + " \"name\": \"askHuman\",\n", + " \"input\": {\n", + " \"input\": \"Where are you located? Please provide your city and country.\"\n", + " }\n", + " }\n", + " ],\n", + " \"additional_kwargs\": {\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 465,\n", + " \"output_tokens\": 108\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"id\": \"msg_01CsrDn46VqNXrdkpVHbcMKA\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 465,\n", + " \"output_tokens\": 108\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"askHuman\",\n", + " \"args\": {\n", + " \"input\": \"Where are you located? Please provide your city and country.\"\n", + " },\n", + " \"id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\",\n", + " \"type\": \"tool_call\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": []\n", + " },\n", + " ToolMessage {\n", + " \"id\": \"9159f841-0e15-4366-96a9-cc5ee0662da0\",\n", + " \"content\": \"san francisco\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"toolu_01RN181HAAL5BcnMXkexbA1r\"\n", + " },\n", + " AIMessage {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Thank you for providing your location. Now, I'll use the search tool to look up the weather in San Francisco.\"\n", + " },\n", + " {\n", + " \"type\": \"tool_use\",\n", + " \"id\": \"toolu_01QCcxzRjojWW5JqQp7WTN82\",\n", + " \"name\": \"search\",\n", + " \"input\": {\n", + " \"input\": \"current weather in San Francisco\"\n", + " }\n", + " }\n", + " ],\n", + " \"additional_kwargs\": {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"id\": \"msg_017hfZ8kdhX5nKD97THKWpPx\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"tool_use\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", + " },\n", + " \"tool_calls\": [\n", + " {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"input\": \"current weather in San Francisco\"\n", + " },\n", + " \"id\": \"toolu_01QCcxzRjojWW5JqQp7WTN82\",\n", + " \"type\": \"tool_call\"\n", + " }\n", + " ],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 587,\n", + " \"output_tokens\": 81,\n", + " \"total_tokens\": 668\n", + " }\n", + " },\n", + " ToolMessage {\n", + " \"id\": \"0bf52bcd-ffbd-4f82-9ee1-7ba2108f0d27\",\n", + " \"content\": \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\",\n", + " \"name\": \"search\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {},\n", + " \"tool_call_id\": \"toolu_01QCcxzRjojWW5JqQp7WTN82\"\n", + " },\n", + " AIMessage {\n", + " \"id\": \"msg_01NuhYbiu36DSgW7brfoKMr8\",\n", + " \"content\": \"Based on the search results, I can provide you with information about the current weather in San Francisco:\\n\\nThe weather in San Francisco is currently sunny. \\n\\nIt's worth noting that the search result included an unusual comment about Geminis, which doesn't seem directly related to the weather. If you'd like more detailed weather information, such as temperature, humidity, or forecast, please let me know, and I can perform another search for more specific weather data.\\n\\nIs there anything else you'd like to know about the weather in San Francisco or any other information you need?\",\n", + " \"additional_kwargs\": {\n", + " \"id\": \"msg_01NuhYbiu36DSgW7brfoKMr8\",\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"end_turn\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 701,\n", + " \"output_tokens\": 121\n", + " }\n", + " },\n", + " \"response_metadata\": {\n", + " \"id\": \"msg_01NuhYbiu36DSgW7brfoKMr8\",\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"stop_reason\": \"end_turn\",\n", + " \"stop_sequence\": null,\n", + " \"usage\": {\n", + " \"input_tokens\": 701,\n", + " \"output_tokens\": 121\n", + " },\n", + " \"type\": \"message\",\n", + " \"role\": \"assistant\"\n", + " },\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 701,\n", + " \"output_tokens\": 121,\n", + " \"total_tokens\": 822\n", + " }\n", + " }\n", + " ]\n", + "}\n", + "================================ ai Message (1) =================================\n", + "Based on the search results, I can provide you with information about the current weather in San Francisco:\n", + "\n", + "The weather in San Francisco is currently sunny. \n", + "\n", + "It's worth noting that the search result included an unusual comment about Geminis, which doesn't seem directly related to the weather. If you'd like more detailed weather information, such as temperature, humidity, or forecast, please let me know, and I can perform another search for more specific weather data.\n", + "\n", + "Is there anything else you'd like to know about the weather in San Francisco or any other information you need?\n" ] } ], "source": [ - "for await (const event of await app.stream(null, config)) {\n", - " console.log(event)\n", - " const recentMsg = event.messages[event.messages.length - 1];\n", - " console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n", - " if (recentMsg._getType() === \"tool\") {\n", - " console.log({\n", - " name: recentMsg.name,\n", - " content: recentMsg.content\n", - " })\n", - " } else if (recentMsg._getType() === \"ai\") {\n", - " console.log(recentMsg.content)\n", - " }\n", + "for await (const event of await messagesApp.stream(null, config2)) {\n", + " console.log(event)\n", + " const recentMsg = event.messages[event.messages.length - 1];\n", + " console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n", + " if (recentMsg._getType() === \"tool\") {\n", + " console.log({\n", + " name: recentMsg.name,\n", + " content: recentMsg.content\n", + " })\n", + " } else if (recentMsg._getType() === \"ai\") {\n", + " console.log(recentMsg.content)\n", + " }\n", "}" ] } ], "metadata": { "kernelspec": { - "display_name": "Deno", + "display_name": "TypeScript", "language": "typescript", - "name": "deno" + "name": "tslab" }, "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, "file_extension": ".ts", - "mimetype": "text/x.typescript", + "mimetype": "text/typescript", "name": "typescript", - "nb_converter": "script", - "pygments_lexer": "typescript", - "version": "5.3.3" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/examples/package.json b/examples/package.json index c50e0be2..97c17504 100644 --- a/examples/package.json +++ b/examples/package.json @@ -5,7 +5,7 @@ "@langchain/anthropic": "^0.2.15", "@langchain/cloudflare": "^0.0.7", "@langchain/community": "^0.2.27", - "@langchain/core": "^0.2.24", + "@langchain/core": "^0.2.27", "@langchain/groq": "^0.0.16", "@langchain/langgraph": "workspace:*", "@langchain/mistralai": "^0.0.28", @@ -16,9 +16,13 @@ "d3": "^7.9.0", "dotenv": "^16.4.5", "langchain": "^0.2.16", + "pg": "^8.11.0", "tslab": "^1.0.22", "uuid": "^10.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.2" + }, + "resolutions": { + "@langchain/core": "0.2.27" } } diff --git a/package.json b/package.json index 729149ce..dde47760 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,8 @@ "resolutions": { "cheerio": "^1.0.0-rc.12", "typescript": "4.9.5", - "semver": "^7.0.0" + "semver": "^7.0.0", + "@langchain/core": "latest" }, "publishConfig": { "access": "public", diff --git a/yarn.lock b/yarn.lock index 90e44576..92ac7707 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1840,28 +1840,9 @@ __metadata: languageName: node linkType: hard -"@langchain/core@npm:>0.1.0 <0.3.0, @langchain/core@npm:>=0.2.11 <0.3.0, @langchain/core@npm:>=0.2.16 <0.3.0, @langchain/core@npm:>=0.2.20 <0.3.0, @langchain/core@npm:>=0.2.21 <0.3.0": - version: 0.2.21 - resolution: "@langchain/core@npm:0.2.21" - dependencies: - ansi-styles: ^5.0.0 - camelcase: 6 - decamelize: 1.2.0 - js-tiktoken: ^1.0.12 - langsmith: ~0.1.39 - mustache: ^4.2.0 - p-queue: ^6.6.2 - p-retry: 4 - uuid: ^10.0.0 - zod: ^3.22.4 - zod-to-json-schema: ^3.22.3 - checksum: 661e40388156820a05a342db5b3bb65eb27dc2d44306b0946383d578857b96900c2c8a0975463fa3576e6c16dc034a83aa9288b9472f3f0ae0e92968033214ce - languageName: node - linkType: hard - -"@langchain/core@npm:>0.1.0 <0.3.0, @langchain/core@npm:^0.2.24": - version: 0.2.24 - resolution: "@langchain/core@npm:0.2.24" +"@langchain/core@npm:latest": + version: 0.2.27 + resolution: "@langchain/core@npm:0.2.27" dependencies: ansi-styles: ^5.0.0 camelcase: 6 @@ -1874,7 +1855,7 @@ __metadata: uuid: ^10.0.0 zod: ^3.22.4 zod-to-json-schema: ^3.22.3 - checksum: 8f9dfa746750653a4fbfcdeca3d662c4461c085d97cfea7b68b310551e256c42496e5d364aabd10a641b53f96876ee0ffd072ee6ac844b0e89e21ad3dc4906b0 + checksum: 188ab834a0a9f4462b81d38b92be61df91d87392b2bd86fb21d5cfe253c080700d09ffd954c90726704fc5fe6c0f06cb2f5bd7705f17632819473c9d8ed57b39 languageName: node linkType: hard @@ -6036,7 +6017,7 @@ __metadata: "@langchain/anthropic": ^0.2.15 "@langchain/cloudflare": ^0.0.7 "@langchain/community": ^0.2.27 - "@langchain/core": ^0.2.24 + "@langchain/core": ^0.2.27 "@langchain/groq": ^0.0.16 "@langchain/langgraph": "workspace:*" "@langchain/mistralai": ^0.0.28 @@ -6047,6 +6028,7 @@ __metadata: d3: ^7.9.0 dotenv: ^16.4.5 langchain: ^0.2.16 + pg: ^8.11.0 tslab: ^1.0.22 uuid: ^10.0.0 zod: ^3.23.8 @@ -10104,6 +10086,87 @@ __metadata: languageName: node linkType: hard +"pg-cloudflare@npm:^1.1.1": + version: 1.1.1 + resolution: "pg-cloudflare@npm:1.1.1" + checksum: 32aac06b5dc4588bbf78801b6267781bc7e13be672009df949d08e9627ba9fdc26924916665d4de99d47f9b0495301930547488dad889d826856976c7b3f3731 + languageName: node + linkType: hard + +"pg-connection-string@npm:^2.6.4": + version: 2.6.4 + resolution: "pg-connection-string@npm:2.6.4" + checksum: 2c1d2ac1add1f93076f1594d217a0980f79add05dc48de6363e1c550827c78a6ee3e3b5420da9c54858f6b678cdb348aed49732ee68158b6cdb70f1d1c748cf9 + languageName: node + linkType: hard + +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: a1e3a05a69005ddb73e5f324b6b4e689868a447c5fa280b44cd4d04e6916a344ac289e0b8d2695d66e8e89a7fba023affb9e0e94778770ada5df43f003d664c9 + languageName: node + linkType: hard + +"pg-pool@npm:^3.6.2": + version: 3.6.2 + resolution: "pg-pool@npm:3.6.2" + peerDependencies: + pg: ">=8.0" + checksum: 5ceee4320a35fce08777d085d50a30a1253574257e1e7c5c56c915056d387d340f797115580c8d90a46691f83c39a9b4da1fd810d9ad168cc455c79c289116f4 + languageName: node + linkType: hard + +"pg-protocol@npm:^1.6.1": + version: 1.6.1 + resolution: "pg-protocol@npm:1.6.1" + checksum: cce3f72cc4bdc04db9ce3fa38b2c45b745f0a95a925847b349087f52c02c4d51b7c74d8867e40639699d0c7609accfaffb6b1d221b3268d2bdc4bb8d6a2995a3 + languageName: node + linkType: hard + +"pg-types@npm:^2.1.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: 1.0.1 + postgres-array: ~2.0.0 + postgres-bytea: ~1.0.0 + postgres-date: ~1.0.4 + postgres-interval: ^1.1.0 + checksum: bf4ec3f594743442857fb3a8dfe5d2478a04c98f96a0a47365014557cbc0b4b0cee01462c79adca863b93befbf88f876299b75b72c665b5fb84a2c94fbd10316 + languageName: node + linkType: hard + +"pg@npm:^8.11.0": + version: 8.12.0 + resolution: "pg@npm:8.12.0" + dependencies: + pg-cloudflare: ^1.1.1 + pg-connection-string: ^2.6.4 + pg-pool: ^3.6.2 + pg-protocol: ^1.6.1 + pg-types: ^2.1.0 + pgpass: 1.x + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 8450b61c787f360e22182aa853548f834f13622714868d0789a60f63743d66ae28930cdca0ef0251bfc89b04679e9074c1398f172c2937bf59b5a360337f4149 + languageName: node + linkType: hard + +"pgpass@npm:1.x": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" + dependencies: + split2: ^4.1.0 + checksum: 947ac096c031eebdf08d989de2e9f6f156b8133d6858c7c2c06c041e1e71dda6f5f3bad3c0ec1e96a09497bbc6ef89e762eefe703b5ef9cb2804392ec52ec400 + languageName: node + linkType: hard + "picocolors@npm:^1.0.0": version: 1.0.0 resolution: "picocolors@npm:1.0.0" @@ -10148,6 +10211,36 @@ __metadata: languageName: node linkType: hard +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 0e1e659888147c5de579d229a2d95c0d83ebdbffc2b9396d890a123557708c3b758a0a97ed305ce7f58edfa961fa9f0bbcd1ea9f08b6e5df73322e683883c464 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.0 + resolution: "postgres-bytea@npm:1.0.0" + checksum: d844ae4ca7a941b70e45cac1261a73ee8ed39d72d3d74ab1d645248185a1b7f0ac91a3c63d6159441020f4e1f7fe64689ac56536a307b31cef361e5187335090 + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 5745001d47e51cd767e46bcb1710649cd705d91a24d42fa661c454b6dcbb7353c066a5047983c90a626cd3bbfea9e626cc6fa84a35ec57e5bbb28b49f78e13ed + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: ^4.0.0 + checksum: 746b71f93805ae33b03528e429dc624706d1f9b20ee81bf743263efb6a0cd79ae02a642a8a480dbc0f09547b4315ab7df6ce5ec0be77ed700bac42730f5c76b2 + languageName: node + linkType: hard + "prebuild-install@npm:^7.1.1": version: 7.1.2 resolution: "prebuild-install@npm:7.1.2" @@ -11022,6 +11115,13 @@ __metadata: languageName: node linkType: hard +"split2@npm:^4.1.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 05d54102546549fe4d2455900699056580cca006c0275c334611420f854da30ac999230857a85fdd9914dc2109ae50f80fda43d2a445f2aa86eccdc1dfce779d + languageName: node + linkType: hard + "sprintf-js@npm:^1.1.3": version: 1.1.3 resolution: "sprintf-js@npm:1.1.3" @@ -12107,6 +12207,13 @@ __metadata: languageName: node linkType: hard +"xtend@npm:^4.0.0": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8"