From c1ce804d14cca5fad0be1d2a3377fe109a6f7c88 Mon Sep 17 00:00:00 2001 From: lusixing <32328454+lusixing@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:37:41 -0800 Subject: [PATCH] langgraph_used --- metro.config.js | 4 +- package-lock.json | 389 ++++++++++++++++++++++++++++++ package.json | 2 + src/screens/FlowScreen.tsx | 21 +- src/services/ai.service.ts | 8 +- src/services/langgraph.service.ts | 96 ++++++++ src/utils/async_hooks_mock.ts | 22 ++ src/utils/token_utils.ts | 76 ++++++ 8 files changed, 613 insertions(+), 5 deletions(-) create mode 100644 src/services/langgraph.service.ts create mode 100644 src/utils/async_hooks_mock.ts create mode 100644 src/utils/token_utils.ts diff --git a/metro.config.js b/metro.config.js index 37c7faa..2e4afc6 100644 --- a/metro.config.js +++ b/metro.config.js @@ -6,8 +6,10 @@ const config = getDefaultConfig(__dirname); config.resolver.extraNodeModules = { ...config.resolver.extraNodeModules, crypto: path.resolve(__dirname, 'src/utils/crypto_polyfill.ts'), - stream: require.resolve('readable-stream'), // Just in case + stream: require.resolve('readable-stream'), vm: require.resolve('vm-browserify'), + async_hooks: path.resolve(__dirname, 'src/utils/async_hooks_mock.ts'), + 'node:async_hooks': path.resolve(__dirname, 'src/utils/async_hooks_mock.ts'), }; module.exports = config; diff --git a/package-lock.json b/package-lock.json index 4e72b9e..71a51ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@expo/metro-runtime": "~4.0.1", "@expo/vector-icons": "~14.0.4", + "@langchain/core": "^1.1.18", + "@langchain/langgraph": "^1.1.3", "@noble/ciphers": "^1.3.0", "@noble/hashes": "^1.8.0", "@react-native-async-storage/async-storage": "^2.2.0", @@ -2203,6 +2205,12 @@ "node": ">=6.9.0" } }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT" + }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -3216,6 +3224,204 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@langchain/core": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/@langchain/core/-/core-1.1.18.tgz", + "integrity": "sha512-vwzbtHUSZaJONBA1n9uQedZPfyFFZ6XzTggTpR28n8tiIg7e1NC/5dvGW/lGtR1Du1VwV9DvDHA5/bOrLe6cVg==", + "license": "MIT", + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": ">=0.4.0 <1.0.0", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "uuid": "^10.0.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@langchain/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@langchain/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/core/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/langgraph": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@langchain/langgraph/-/langgraph-1.1.3.tgz", + "integrity": "sha512-o/cEWeocDDSpyBI2MfX07LkNG4LzdRKxwcgUcbR4PyRzhxxCkeIZRCCYkXVQoDbdKqAczJa0D7+yjU9rmA5iHQ==", + "license": "MIT", + "dependencies": { + "@langchain/langgraph-checkpoint": "^1.0.0", + "@langchain/langgraph-sdk": "~1.5.5", + "@standard-schema/spec": "1.1.0", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.0.1", + "zod": "^3.25.32 || ^4.2.0", + "zod-to-json-schema": "^3.x" + }, + "peerDependenciesMeta": { + "zod-to-json-schema": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-checkpoint": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.0.tgz", + "integrity": "sha512-xrclBGvNCXDmi0Nz28t3vjpxSH6UYx6w5XAXSiiB1WEdc2xD2iY/a913I3x3a31XpInUW/GGfXXfePfaghV54A==", + "license": "MIT", + "dependencies": { + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.0.1" + } + }, + "node_modules/@langchain/langgraph-checkpoint/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/langgraph-sdk": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.5.5.tgz", + "integrity": "sha512-SyiAs6TVXPWlt/8cI9pj/43nbIvclY3ytKqUFbL5MplCUnItetEyqvH87EncxyVF5D7iJKRZRfSVYBMmOZbjbQ==", + "license": "MIT", + "dependencies": { + "p-queue": "^9.0.1", + "p-retry": "^7.1.1", + "uuid": "^13.0.0" + }, + "peerDependencies": { + "@langchain/core": "^1.1.15", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@langchain/core": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/@langchain/langgraph-sdk/node_modules/p-queue": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/p-queue/-/p-queue-9.1.0.tgz", + "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/@langchain/langgraph/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@noble/ciphers": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/@noble/ciphers/-/ciphers-1.3.0.tgz", @@ -3818,6 +4024,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3940,6 +4152,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -5099,6 +5317,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/console-table-printer": { + "version": "2.15.0", + "resolved": "https://registry.npmmirror.com/console-table-printer/-/console-table-printer-2.15.0.tgz", + "integrity": "sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==", + "license": "MIT", + "dependencies": { + "simple-wcswidth": "^1.1.2" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5238,6 +5465,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -5596,6 +5832,12 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", @@ -6813,6 +7055,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7131,6 +7385,15 @@ "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==", "license": "MIT" }, + "node_modules/js-tiktoken": { + "version": "1.0.21", + "resolved": "https://registry.npmmirror.com/js-tiktoken/-/js-tiktoken-1.0.21.tgz", + "integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7251,6 +7514,65 @@ "node": ">=6" } }, + "node_modules/langsmith": { + "version": "0.4.12", + "resolved": "https://registry.npmmirror.com/langsmith/-/langsmith-0.4.12.tgz", + "integrity": "sha512-YWt0jcGvKqjUgIvd78rd4QcdMss0lUkeUaqp0UpVRq7H2yNDx8H5jOUO/laWUmaPtWGgcip0qturykXe1g9Gqw==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/langsmith/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/langsmith/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -8337,6 +8659,15 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -8751,6 +9082,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmmirror.com/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/p-retry/-/p-retry-7.1.1.tgz", + "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==", + "license": "MIT", + "dependencies": { + "is-network-error": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -10143,6 +10517,12 @@ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "license": "MIT" }, + "node_modules/simple-wcswidth": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz", + "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", + "license": "MIT" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -11493,6 +11873,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 59217e6..93bda3a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "dependencies": { "@expo/metro-runtime": "~4.0.1", "@expo/vector-icons": "~14.0.4", + "@langchain/core": "^1.1.18", + "@langchain/langgraph": "^1.1.3", "@noble/ciphers": "^1.3.0", "@noble/hashes": "^1.8.0", "@react-native-async-storage/async-storage": "^2.2.0", diff --git a/src/screens/FlowScreen.tsx b/src/screens/FlowScreen.tsx index a9d2271..5a890db 100644 --- a/src/screens/FlowScreen.tsx +++ b/src/screens/FlowScreen.tsx @@ -31,6 +31,8 @@ import * as ImagePicker from 'expo-image-picker'; import { AIRole } from '../types'; import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors'; import { aiService, AIMessage } from '../services/ai.service'; +import { langGraphService } from '../services/langgraph.service'; +import { HumanMessage, AIMessage as LangChainAIMessage, SystemMessage } from "@langchain/core/messages"; import { assetsService } from '../services/assets.service'; import { useAuth } from '../context/AuthContext'; import { AI_CONFIG, getVaultStorageKeys } from '../config'; @@ -280,8 +282,23 @@ export default function FlowScreen() { setMessages(prev => [...prev, userMsg]); try { - // Call AI proxy with selected role's system prompt - const aiResponse = await aiService.sendMessage(userMessage, token, selectedRole?.systemPrompt || ''); + // 1. Convert current messages history to LangChain format + const history: (HumanMessage | LangChainAIMessage | SystemMessage)[] = messages.map(msg => { + if (msg.role === 'user') return new HumanMessage(msg.content); + return new LangChainAIMessage(msg.content); + }); + + // 2. Add system prompt + const systemPrompt = new SystemMessage(selectedRole?.systemPrompt || ''); + + // 3. Add current new message + const currentMsg = new HumanMessage(userMessage); + + // 4. Combine all messages for LangGraph processing + const fullMessages = [systemPrompt, ...history, currentMsg]; + + // 5. Execute via LangGraph service (handles token limits and context) + const aiResponse = await langGraphService.execute(fullMessages, token); // Add AI response const aiMsg: ChatMessage = { diff --git a/src/services/ai.service.ts b/src/services/ai.service.ts index b6c0149..43ecf3c 100644 --- a/src/services/ai.service.ts +++ b/src/services/ai.service.ts @@ -13,6 +13,7 @@ import { logApiDebug, } from '../config'; import { AIRole } from '../types'; +import { trimInternalMessages } from '../utils/token_utils'; // ============================================================================= // Type Definitions @@ -259,14 +260,17 @@ export const aiService = { }); } - const historicalMessages = messages.map(msg => ({ + // Enforce token limit (10,000 tokens) + const trimmedMessages = trimInternalMessages(messages); + + const historicalMessages = trimmedMessages.map(msg => ({ role: msg.role, content: msg.content, })); const summaryPrompt: AIMessage = { role: 'user', - content: 'Please provide a concise summary of the conversation above in Chinese (since the user request was in Chinese). Focus on the main topics discussed and any key conclusions or actions mentioned.', + content: 'Please provide a concise summary of the conversation above in English. Focus on the main topics discussed and any key conclusions or actions mentioned.', }; const response = await this.chat([...historicalMessages, summaryPrompt], token); diff --git a/src/services/langgraph.service.ts b/src/services/langgraph.service.ts new file mode 100644 index 0000000..20d7d30 --- /dev/null +++ b/src/services/langgraph.service.ts @@ -0,0 +1,96 @@ +/** + * LangGraph Service + * + * Implements AI chat logic using LangGraph.js for state management + * and context handling. + */ + +import { StateGraph, START, END, Annotation } from "@langchain/langgraph"; +import { BaseMessage, HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages"; +import { aiService } from "./ai.service"; +import { trimLangChainMessages } from "../utils/token_utils"; + +// ============================================================================= +// Settings +// ============================================================================= + +/** + * Define the State using Annotation (Standard for latest LangGraph.js) + */ +const GraphAnnotation = Annotation.Root({ + messages: Annotation({ + reducer: (x, y) => x.concat(y), + default: () => [], + }), +}); + +// ============================================================================= +// Graph Definition +// ============================================================================= + +/** + * The main node that calls our existing AI API + */ +async function callModel(state: typeof GraphAnnotation.State, config: any) { + const { messages } = state; + const { token } = config.configurable || {}; + + // 1. Trim messages to stay under token limit + const trimmedMessages = trimLangChainMessages(messages); + + // 2. Convert LangChain messages to our internal AIMessage format for the API + const apiMessages = trimmedMessages.map(m => { + let role: 'system' | 'user' | 'assistant' = 'user'; + const type = (m as any)._getType?.() || (m instanceof SystemMessage ? 'system' : m instanceof HumanMessage ? 'human' : m instanceof AIMessage ? 'ai' : 'user'); + + if (type === 'system') role = 'system'; + else if (type === 'human') role = 'user'; + else if (type === 'ai') role = 'assistant'; + + return { + role, + content: m.content.toString() + }; + }); + + // 3. Call the proxy service + const response = await aiService.chat(apiMessages, token); + const content = response.choices[0]?.message?.content || "No response generated"; + + // 4. Return the new message to satisfy the Graph (it will be appended due to reducer) + return { + messages: [new AIMessage(content)] + }; +} + +// ============================================================================= +// Service Export +// ============================================================================= + +export const langGraphService = { + /** + * Run the chat graph with history + */ + async execute( + currentMessages: BaseMessage[], + userToken: string, + ): Promise { + // Define the graph + const workflow = new StateGraph(GraphAnnotation) + .addNode("agent", callModel) + .addEdge(START, "agent") + .addEdge("agent", END); + + const app = workflow.compile(); + + // Execute the graph + const result = await app.invoke( + { messages: currentMessages }, + { configurable: { token: userToken } } + ); + + // Return the content of the last message (the AI response) + const lastMsg = result.messages[result.messages.length - 1]; + return lastMsg.content.toString(); + } +}; diff --git a/src/utils/async_hooks_mock.ts b/src/utils/async_hooks_mock.ts new file mode 100644 index 0000000..9453247 --- /dev/null +++ b/src/utils/async_hooks_mock.ts @@ -0,0 +1,22 @@ +/** + * Mock for Node.js async_hooks + * Used to fix LangGraph.js compatibility with React Native + */ + +export class AsyncLocalStorage { + disable() { } + getStore() { + return undefined; + } + run(store: any, callback: (...args: any[]) => any, ...args: any[]) { + return callback(...args); + } + exit(callback: (...args: any[]) => any, ...args: any[]) { + return callback(...args); + } + enterWith(store: any) { } +} + +export default { + AsyncLocalStorage, +}; diff --git a/src/utils/token_utils.ts b/src/utils/token_utils.ts new file mode 100644 index 0000000..2b5242e --- /dev/null +++ b/src/utils/token_utils.ts @@ -0,0 +1,76 @@ +/** + * Token Utilities + * + * Shared logic for trimming messages to stay within token limits. + */ + +import { BaseMessage, SystemMessage } from "@langchain/core/messages"; +import { AIMessage as ServiceAIMessage } from "../services/ai.service"; + +export const TOKEN_LIMIT = 10000; +const CHARS_PER_TOKEN = 3; // Conservative estimate: 1 token ≈ 3 chars +export const MAX_CHARS = TOKEN_LIMIT * CHARS_PER_TOKEN; + +/** + * Trims LangChain messages to fit within token limit + */ +export function trimLangChainMessages(messages: BaseMessage[]): BaseMessage[] { + let totalLength = 0; + const trimmed: BaseMessage[] = []; + + // Always keep the system message if it's at the start + let systemMsg: BaseMessage | null = null; + if (messages.length > 0 && (messages[0] instanceof SystemMessage || (messages[0] as any)._getType?.() === 'system')) { + systemMsg = messages[0]; + totalLength += systemMsg.content.toString().length; + } + + // Iterate backwards and add messages until we hit the char limit + for (let i = messages.length - 1; i >= (systemMsg ? 1 : 0); i--) { + const msg = messages[i]; + const len = msg.content.toString().length; + + if (totalLength + len > MAX_CHARS) break; + + trimmed.unshift(msg); + totalLength += len; + } + + if (systemMsg) { + trimmed.unshift(systemMsg); + } + + return trimmed; +} + +/** + * Trims internal AIMessage format messages to fit within token limit + */ +export function trimInternalMessages(messages: ServiceAIMessage[]): ServiceAIMessage[] { + let totalLength = 0; + const trimmed: ServiceAIMessage[] = []; + + // Always keep the system message if it's at the start + let systemMsg: ServiceAIMessage | null = null; + if (messages.length > 0 && messages[0].role === 'system') { + systemMsg = messages[0]; + totalLength += systemMsg.content.length; + } + + // Iterate backwards and add messages until we hit the char limit + for (let i = messages.length - 1; i >= (systemMsg ? 1 : 0); i--) { + const msg = messages[i]; + const len = msg.content.length; + + if (totalLength + len > MAX_CHARS) break; + + trimmed.unshift(msg); + totalLength += len; + } + + if (systemMsg) { + trimmed.unshift(systemMsg); + } + + return trimmed; +}