diff --git a/.github/workflows/ci-model-regression.yml b/.github/workflows/ci-model-regression.yml index 84f6d34a5392..0669f1104bad 100644 --- a/.github/workflows/ci-model-regression.yml +++ b/.github/workflows/ci-model-regression.yml @@ -865,7 +865,7 @@ jobs: echo "to_ts=$TIME_UNIX_NOW" >> $GITHUB_OUTPUT - name: Publish results as a PR comment - uses: marocchino/sticky-pull-request-comment@f61b6cf21ef2fcc468f4345cdfcc9bda741d2343 # v2.6.2 + uses: marocchino/sticky-pull-request-comment@f6a2580ed520ae15da6076e7410b088d1c5dddd9 # v2.7.0 if: ${{ always() }} with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/changelog/12696.improvement.md b/changelog/12696.improvement.md new file mode 100644 index 000000000000..ebe9ffd16b58 --- /dev/null +++ b/changelog/12696.improvement.md @@ -0,0 +1 @@ +Use the same session across requests in `RasaNLUHttpInterpreter` \ No newline at end of file diff --git a/changelog/12703.doc.md b/changelog/12703.doc.md new file mode 100644 index 000000000000..c18c15aa4419 --- /dev/null +++ b/changelog/12703.doc.md @@ -0,0 +1 @@ +Document support for Vault namespaces. diff --git a/changelog/12704.improvement.md b/changelog/12704.improvement.md new file mode 100644 index 000000000000..f089d3d514fc --- /dev/null +++ b/changelog/12704.improvement.md @@ -0,0 +1 @@ +Passed request headers from REST channel. \ No newline at end of file diff --git a/changelog/12712.doc.md b/changelog/12712.doc.md new file mode 100644 index 000000000000..012693eda568 --- /dev/null +++ b/changelog/12712.doc.md @@ -0,0 +1,2 @@ +Updated PII docs with new section on how to use Rasa X/Enterprise with PII management solution, and a new note on debug +logs being displayed after the bot message with `rasa shell`. diff --git a/docs/docs/pii-management.mdx b/docs/docs/pii-management.mdx index 1791226b4752..e65e5ecc90cf 100644 --- a/docs/docs/pii-management.mdx +++ b/docs/docs/pii-management.mdx @@ -240,6 +240,34 @@ The `anonymization_topics` section contains a list of Kafka topics to which the Each Kafka topic must have a `name` field and an `anonymization_rules` field. The `name` field specifies the name of the Kafka topic. The `anonymization_rules` field specifies the `id` of the anonymization rule list to be used for the Kafka topic. +### Streaming anonymized events to Rasa X/Enterprise with Kafka + +Streaming anonymized events to Rasa X/Enterprise is only supported for Rasa X/Enterprise versions `1.3.0` and above. +In addition, you must use the Kafka event broker, other event broker types are not supported. + +You can stream anonymized events to Rasa X/Enterprise via Kafka by adding the `rasa_x_consumer: true` key-value pair to +the `anonymization_topics` section: + +```yaml +event_broker: + type: kafka + partition_by_sender: True + url: localhost + anonymization_topics: + - name: topic_1 + anonymization_rules: rules_1 + rasa_x_consumer: true + - name: topic_2 + anonymization_rules: rules_2 +``` + +If multiple Kafka anonymization topics contain the `rasa_x_consumer` key-value pair, the anonymized events will be streamed +to the Kafka topic that is mapped to the first topic in the `anonymization_topics` list that contains the `rasa_x_consumer` +key-value pair. + +Note that the `rasa_x_consumer` key-value pair is optional. If it is not specified, the anonymized events will be published +to the Kafka topic, but they will not be streamed to Rasa X/Enterprise. + ## How to enable anonymization of PII in logs You can enable anonymization of PII in logs by filling the `logger` section in the `endpoints.yml` file. @@ -257,3 +285,8 @@ The `anonymization_rules` field specifies the `id` of the anonymization rule lis We strongly recommend to run with log level INFO in production. Running with log level DEBUG will increase the assistant's response latency because of processing delays. ::: + +Note that running `rasa shell` in debug mode with a Kafka event broker might result in logs related to the event publishing +to be printed to console **after** the bot message. This behaviour is expected because the event anonymization and publishing +is done asynchronously as a background task, so it will complete after the assistant has already predicted and executed the +bot response. diff --git a/docs/docs/secrets-managers.mdx b/docs/docs/secrets-managers.mdx index d24184886481..90629ecd1a70 100644 --- a/docs/docs/secrets-managers.mdx +++ b/docs/docs/secrets-managers.mdx @@ -66,6 +66,15 @@ and through `endpoints.yml` configuration file. Environment variables and `endpoints.yml` configuration file are merged together and **the values from the environment variables take precedence**. +:::info New in 3.7 +Vault namespaces can be used to isolate secrets. You can +configure a namespace with the `VAULT_NAMESPACE` environment variable or the `namespace` key in secrets_manager +section of the `endpoints.yml` file. +To learn more about namespaces, +check out the [Vault namespaces docs](https://developer.hashicorp.com/vault/docs/enterprise/namespaces). +::: + + The following environment variables are available: | Environment Variable | Description | Default | @@ -75,6 +84,7 @@ The following environment variables are available: | `VAULT_TOKEN` | **Required**. token to authenticate to the vault server | | | `VAULT_RASA_SECRETS_PATH` | Path to the secrets in the vault server | `rasa-secrets` | | `VAULT_TRANSIT_MOUNT_POINT` | If transit secrets engine is enabled, set this to mount point of the transit engine | | +| `VAULT_NAMESPACE` | If namespaces are used, set this to the path of the namespace | | To configure the Vault secrets manager, you can fill the following section in `endpoints.yml` file: ```yaml-rasa title="endpoints.yml @@ -84,6 +94,7 @@ secrets_manager: url: "http://localhost:1234" # required - the address of the vault server secrets_path: rasa-secrets # path to the secrets in the vault server if not set it defaults to `rasa-secrets` transit_mount_point: transit # if transit secrets engine is enabled, set this to mount point of the transit engine + namespace: my-namespace # if namespaces are used, set this to the path of the namespace ``` #### Store access credentials in environment variables @@ -103,6 +114,7 @@ secrets_manager: url: "http://localhost:1234" secrets_path: rasa-secrets # if not set it defaults to `rasa-secrets` transit_mount_point: transit # if you have enabled transit secrets engine, and you want to use it + namespace: my-namespace # if namespaces are used, set this to the path of the namespace ``` ### How to configure Tracker Store with Vault Secrets Manager diff --git a/docs/yarn.lock b/docs/yarn.lock index ac7e79bfa8e1..e9b5e167147c 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -3096,6 +3096,11 @@ "@babel/runtime" "^7.7.2" core-js "^3.4.1" +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + "@lunelson/sass-calc@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@lunelson/sass-calc/-/sass-calc-1.2.0.tgz#7880a17cea6631f7e5c63315617dd2708809b2c5" @@ -5634,15 +5639,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001124, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001173: - version "1.0.30001214" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001214.tgz" - integrity sha512-O2/SCpuaU3eASWVaesQirZv1MSjUNOvmugaD8zNSJqw6Vv5SGwoOpA9LJs3pNPfM745nxqPvfZY3MQKY4AKHYg== - -caniuse-lite@^1.0.30001219: - version "1.0.30001240" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001240.tgz#ec15d125b590602c8731545c5351ff054ad2d52f" - integrity sha512-nb8mDzfMdxBDN7ZKx8chWafAdBp5DAAlpWvNyUGe5tcDWd838zpzDN3Rah9cjCqhfOKkrvx40G2SDtP0qiWX/w== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001124, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001173, caniuse-lite@^1.0.30001219: + version "1.0.30001519" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz" + integrity sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg== cardinal@^2.1.1: version "2.1.1" @@ -7294,19 +7294,19 @@ dns-equal@^1.0.0: integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== dependencies: ip "^1.1.0" safe-buffer "^5.0.1" dns-packet@^5.1.2: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.2.1.tgz#26cec0be92252a1b97ed106482921192a7e08f72" - integrity sha512-JHj2yJeKOqlxzeuYpN1d56GfhzivAxavNwHj9co3qptECel27B1rLY5PifJAvubsInX5pGLDjAHuCfCUc2Zv/w== + version "5.4.0" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b" + integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== dependencies: - ip "^1.1.5" + "@leichtgewicht/ip-codec" "^2.0.1" dns-socket@^4.2.1: version "4.2.1" @@ -11494,9 +11494,9 @@ minimatch@3.0.4, minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== minipass-collect@^1.0.2: version "1.0.2" @@ -17455,9 +17455,9 @@ winston@^3.2.1: winston-transport "^4.4.0" word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== worker-farm@^1.7.0: version "1.7.0" diff --git a/poetry.lock b/poetry.lock index c50305dd469a..f8a0ac9d9a00 100644 --- a/poetry.lock +++ b/poetry.lock @@ -431,7 +431,7 @@ pytz = ">=2015.7" name = "backoff" version = "1.10.0" description = "Function decoration for backoff and retry" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1365,7 +1365,7 @@ zstandard = ["zstandard"] name = "deprecated" version = "1.2.14" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1853,26 +1853,26 @@ gitdb = ">=4.0.1,<5" [[package]] name = "google-api-core" -version = "2.11.1" +version = "2.8.0" description = "Google API client core library" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" files = [ - {file = "google-api-core-2.11.1.tar.gz", hash = "sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a"}, - {file = "google_api_core-2.11.1-py3-none-any.whl", hash = "sha256:d92a5a92dc36dd4f4b9ee4e55528a90e432b059f93aee6ad857f9de8cc7ae94a"}, + {file = "google-api-core-2.8.0.tar.gz", hash = "sha256:065bb8e11c605fd232707ae50963dc1c8af5b3c95b4568887515985e6c1156b3"}, + {file = "google_api_core-2.8.0-py3-none-any.whl", hash = "sha256:1b9f59236ce1bae9a687c1d4f22957e79a2669e53d032893f6bf0fca54f6931d"}, ] [package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" +google-auth = ">=1.25.0,<3.0dev" +googleapis-common-protos = ">=1.52.0,<2.0dev" +protobuf = ">=3.12.0" +requests = ">=2.18.0,<3.0.0dev" [package.extras] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"] +grpcgcp = ["grpcio-gcp (>=0.2.2)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] [[package]] name = "google-auth" @@ -2078,21 +2078,21 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] [[package]] name = "googleapis-common-protos" -version = "1.59.1" +version = "1.56.1" description = "Common protobufs used in Google APIs" -category = "dev" +category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" files = [ - {file = "googleapis-common-protos-1.59.1.tar.gz", hash = "sha256:b35d530fe825fb4227857bc47ad84c33c809ac96f312e13182bdeaa2abe1178a"}, - {file = "googleapis_common_protos-1.59.1-py2.py3-none-any.whl", hash = "sha256:0cbedb6fb68f1c07e18eb4c48256320777707e7d0c55063ae56c15db3224a61e"}, + {file = "googleapis-common-protos-1.56.1.tar.gz", hash = "sha256:6b5ee59dc646eb61a8eb65ee1db186d3df6687c8804830024f32573298bca19b"}, + {file = "googleapis_common_protos-1.56.1-py2.py3-none-any.whl", hash = "sha256:ddcd955b5bb6589368f659fa475373faa1ed7d09cde5ba25e88513d87007e174"}, ] [package.dependencies] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.15.0" [package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +grpc = ["grpcio (>=1.0.0)"] [[package]] name = "greenlet" @@ -3354,22 +3354,22 @@ files = [ [[package]] name = "networkx" -version = "2.6.3" +version = "3.1" description = "Python package for creating and manipulating graphs and networks" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "networkx-2.6.3-py3-none-any.whl", hash = "sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef"}, - {file = "networkx-2.6.3.tar.gz", hash = "sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51"}, + {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, + {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, ] [package.extras] -default = ["matplotlib (>=3.3)", "numpy (>=1.19)", "pandas (>=1.1)", "scipy (>=1.5,!=1.6.1)"] -developer = ["black (==21.5b1)", "pre-commit (>=2.12)"] -doc = ["nb2plots (>=0.6)", "numpydoc (>=1.1)", "pillow (>=8.2)", "pydata-sphinx-theme (>=0.6,<1.0)", "sphinx (>=4.0,<5.0)", "sphinx-gallery (>=0.9,<1.0)", "texext (>=0.6.6)"] -extra = ["lxml (>=4.5)", "pydot (>=1.4.1)", "pygraphviz (>=1.7)"] -test = ["codecov (>=2.1)", "pytest (>=6.2)", "pytest-cov (>=2.12)"] +default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] +developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] +test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "nr-util" @@ -3472,6 +3472,180 @@ rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] +[[package]] +name = "opentelemetry-api" +version = "1.15.0" +description = "OpenTelemetry Python API" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_api-1.15.0-py3-none-any.whl", hash = "sha256:e6c2d2e42140fd396e96edf75a7ceb11073f4efb4db87565a431cc9d0f93f2e0"}, + {file = "opentelemetry_api-1.15.0.tar.gz", hash = "sha256:79ab791b4aaad27acc3dc3ba01596db5b5aac2ef75c70622c6038051d6c2cded"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +setuptools = ">=16.0" + +[[package]] +name = "opentelemetry-exporter-jaeger" +version = "1.15.0" +description = "Jaeger Exporters for OpenTelemetry" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_jaeger-1.15.0-py3-none-any.whl", hash = "sha256:e8d1b8b95095736507fbef46eea4ee9472e9e7f415ee4461f9414d9d1590ac37"}, + {file = "opentelemetry_exporter_jaeger-1.15.0.tar.gz", hash = "sha256:5d0e5a1b37589a4d7eb67be90aa1fec45431565f8e84ae4960437e77b779002e"}, +] + +[package.dependencies] +opentelemetry-exporter-jaeger-proto-grpc = "1.15.0" +opentelemetry-exporter-jaeger-thrift = "1.15.0" + +[[package]] +name = "opentelemetry-exporter-jaeger-proto-grpc" +version = "1.15.0" +description = "Jaeger Protobuf Exporter for OpenTelemetry" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_jaeger_proto_grpc-1.15.0-py3-none-any.whl", hash = "sha256:78c46b8b8c9ceabd1107cc85a85b463bd50a049e980c370483d0c3c577632991"}, + {file = "opentelemetry_exporter_jaeger_proto_grpc-1.15.0.tar.gz", hash = "sha256:ff650cc786932cf0fce9809d18f680df7fb49955511009067322470a25b27c5c"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.52,<1.56.3" +grpcio = ">=1.0.0,<2.0.0" +opentelemetry-api = ">=1.3,<2.0" +opentelemetry-sdk = ">=1.11,<2.0" + +[[package]] +name = "opentelemetry-exporter-jaeger-thrift" +version = "1.15.0" +description = "Jaeger Thrift Exporter for OpenTelemetry" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_jaeger_thrift-1.15.0-py3-none-any.whl", hash = "sha256:a9d6dcdb203d10d6b0f72bfaeebf1e4822e2636d7d35ff67ed5a9fc672d76fc5"}, + {file = "opentelemetry_exporter_jaeger_thrift-1.15.0.tar.gz", hash = "sha256:2d85ad991c49f63f2397bcbae3881b9d58e51797d2f9c6fe4e02d6372e92b3ec"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.3,<2.0" +opentelemetry-sdk = ">=1.11,<2.0" +thrift = ">=0.10.0" + +[[package]] +name = "opentelemetry-exporter-otlp" +version = "1.15.0" +description = "OpenTelemetry Collector Exporters" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_otlp-1.15.0-py3-none-any.whl", hash = "sha256:79f22748b6a54808a0448093dfa189c8490e729f67c134d4c992533d9393b33e"}, + {file = "opentelemetry_exporter_otlp-1.15.0.tar.gz", hash = "sha256:4f7c49751d9720e2e726e13b0bb958ccade4e29122c305d92c033da432c8d2c5"}, +] + +[package.dependencies] +opentelemetry-exporter-otlp-proto-grpc = "1.15.0" +opentelemetry-exporter-otlp-proto-http = "1.15.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.15.0" +description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_otlp_proto_grpc-1.15.0-py3-none-any.whl", hash = "sha256:c2a5492ba7d140109968135d641d06ce3c5bd73c50665f787526065d57d7fd1d"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.15.0.tar.gz", hash = "sha256:844f2a4bb9bcda34e4eb6fe36765e5031aacb36dc60ed88c90fc246942ea26e7"}, +] + +[package.dependencies] +backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} +googleapis-common-protos = ">=1.52,<2.0" +grpcio = ">=1.0.0,<2.0.0" +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-proto = "1.15.0" +opentelemetry-sdk = ">=1.12,<2.0" + +[package.extras] +test = ["pytest-grpc"] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.15.0" +description = "OpenTelemetry Collector Protobuf over HTTP Exporter" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_otlp_proto_http-1.15.0-py3-none-any.whl", hash = "sha256:3ec2a02196c8a54bf5cbf7fe623a5238625638e83b6047a983bdf96e2bbb74c0"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.15.0.tar.gz", hash = "sha256:11b2c814249a49b22f6cca7a06b05701f561d577b747f3660dfd67b6eb9daf9c"}, +] + +[package.dependencies] +backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} +googleapis-common-protos = ">=1.52,<2.0" +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-proto = "1.15.0" +opentelemetry-sdk = ">=1.12,<2.0" +requests = ">=2.7,<3.0" + +[package.extras] +test = ["responses (==0.22.0)"] + +[[package]] +name = "opentelemetry-proto" +version = "1.15.0" +description = "OpenTelemetry Python Proto" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_proto-1.15.0-py3-none-any.whl", hash = "sha256:044b6d044b4d10530f250856f933442b8753a17f94ae37c207607f733fb9a844"}, + {file = "opentelemetry_proto-1.15.0.tar.gz", hash = "sha256:9c4008e40ac8cab359daac283fbe7002c5c29c77ea2674ad5626a249e64e0101"}, +] + +[package.dependencies] +protobuf = ">=3.19,<5.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.15.0" +description = "OpenTelemetry Python SDK" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_sdk-1.15.0-py3-none-any.whl", hash = "sha256:555c533e9837766119bbccc7a80458c9971d853a6f1da683a2246cd5e53b4645"}, + {file = "opentelemetry_sdk-1.15.0.tar.gz", hash = "sha256:98dbffcfeebcbff12c0c974292d6ea603180a145904cf838b1fe4d5c99078425"}, +] + +[package.dependencies] +opentelemetry-api = "1.15.0" +opentelemetry-semantic-conventions = "0.36b0" +setuptools = ">=16.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.36b0" +description = "OpenTelemetry Semantic Conventions" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_semantic_conventions-0.36b0-py3-none-any.whl", hash = "sha256:adc05635e87b9d3e007c9f530eed487fc3ef2177d02f82f674f28ebf9aff8243"}, + {file = "opentelemetry_semantic_conventions-0.36b0.tar.gz", hash = "sha256:829dc221795467d98b773c04096e29be038d77526dc8d6ac76f546fb6279bf01"}, +] + [[package]] name = "opt-einsum" version = "3.3.0" @@ -4604,25 +4778,29 @@ fire = "*" [[package]] name = "rasa-sdk" -version = "3.6.1" +version = "3.7.0a1" description = "Open source machine learning framework to automate text- and voice-based conversations: NLU, dialogue management, connect to Slack, Facebook, and more - Create chatbots and voice assistants" category = "main" optional = false python-versions = ">=3.8,<3.11" files = [ - {file = "rasa_sdk-3.6.1-py3-none-any.whl", hash = "sha256:fb4d8c9ac0a6266931bc1dda108ef965d7f1c3fbe10ff2330dffef7add987b29"}, - {file = "rasa_sdk-3.6.1.tar.gz", hash = "sha256:f5dbd776dcbe9eea4308c1dcf32f14765297af32f71f76a0f43e29d6d030810c"}, + {file = "rasa_sdk-3.7.0a1-py3-none-any.whl", hash = "sha256:1d5a2613c1e2e03dd5307dc86a59ce9a704bc7e8047076d993a2c38d5c9ad0bc"}, + {file = "rasa_sdk-3.7.0a1.tar.gz", hash = "sha256:9fda99c2bb3a609b93c352844ef390512e77ccc4d25d521feb128b796e11053b"}, ] [package.dependencies] coloredlogs = ">=10,<16" +opentelemetry-api = ">=1.15.0,<1.16.0" +opentelemetry-exporter-jaeger = ">=1.15.0,<1.16.0" +opentelemetry-exporter-otlp = ">=1.15.0,<1.16.0" +opentelemetry-sdk = ">=1.15.0,<1.16.0" pluggy = ">=1.0.0,<2.0.0" prompt-toolkit = ">=3.0,<3.0.29" "ruamel.yaml" = ">=0.16.5,<0.18.0" sanic = ">=21.12.0,<22.0.0" Sanic-Cors = ">=2.0.0,<3.0.0" typing-extensions = ">=4.1.1,<5.0.0" -websockets = ">=10.0,<11.0" +websockets = ">=10.0,<12.0" [[package]] name = "redis" @@ -4921,7 +5099,8 @@ files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, @@ -6244,6 +6423,25 @@ files = [ {file = "threadpoolctl-3.1.0.tar.gz", hash = "sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380"}, ] +[[package]] +name = "thrift" +version = "0.16.0" +description = "Python bindings for the Apache Thrift RPC system" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "thrift-0.16.0.tar.gz", hash = "sha256:2b5b6488fcded21f9d312aa23c9ff6a0195d0f6ae26ddbd5ad9e3e25dfc14408"}, +] + +[package.dependencies] +six = ">=1.7.2" + +[package.extras] +all = ["tornado (>=4.0)", "twisted"] +tornado = ["tornado (>=4.0)"] +twisted = ["twisted"] + [[package]] name = "tokenizers" version = "0.13.3" @@ -7270,4 +7468,4 @@ transformers = ["sentencepiece", "transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.11" -content-hash = "50bc9c769d9b3cfc9a614c1fe8d79826b009c4d103d3c1583f4be8c98780dee2" +content-hash = "d6b5c6722c75a798933b57e6f3c2e1976bff70fef9ba18b0285f88e75ea35438" diff --git a/pyproject.toml b/pyproject.toml index 7c630fefc570..5344a4e5ee71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ exclude = "((.eggs | .git | .pytest_cache | build | dist))" [tool.poetry] name = "rasa" -version = "3.6.4" +version = "3.7.0a1" description = "Open source machine learning framework to automate text- and voice-based conversations: NLU, dialogue management, connect to Slack, Facebook, and more - Create chatbots and voice assistants" authors = [ "Rasa Technologies GmbH ",] maintainers = [ "Tom Bocklisch ",] @@ -97,7 +97,7 @@ redis = ">=4.5.3, <5.0" absl-py = ">=0.9,<1.5" apscheduler = ">=3.6,<3.10" tqdm = "^4.31" -networkx = ">=2.4,<2.7" +networkx = ">=2.4,<3.2" fbmessenger = "~6.0.0" pykwalify = ">=1.7,<1.9" coloredlogs = ">=10,<16" @@ -111,7 +111,7 @@ colorhash = ">=1.0.2,<1.3.0" jsonschema = ">=3.2,<4.18" packaging = ">=20.0,<21.0" pytz = ">=2019.1,<2023.0" -rasa-sdk = "~3.6.1" +rasa-sdk = "~3.7.0a1" colorclass = "~2.2" terminaltables = "~3.1.0" sanic = "~21.12" @@ -320,7 +320,7 @@ coveralls = "^3.0.1" towncrier = "^22.8.0" toml = "^0.10.0" pep440-version-utils = "^0.3.0" -pydoc-markdown = "^4.5.1" +pydoc-markdown = "^4.7.0" pytest-timeout = "^2.1.0" mypy = "^1.0.0" bandit = "^1.6.3" diff --git a/rasa/core/brokers/kafka.py b/rasa/core/brokers/kafka.py index 7183be12746a..3e6d86797417 100644 --- a/rasa/core/brokers/kafka.py +++ b/rasa/core/brokers/kafka.py @@ -63,13 +63,17 @@ def __init__( SCRAM-SHA-512. Default: `PLAIN` ssl_cafile: Optional filename of ca file to use in certificate verification. - ssl_certfile: Optional filename of file in pem format containing + + ssl_certfile : Optional filename of file in pem format containing the client certificate, as well as any ca certificates needed to establish the certificate's authenticity. - ssl_keyfile: Optional filename containing the client private key. - ssl_check_hostname: Flag to configure whether ssl handshake + + ssl_keyfile : Optional filename containing the client private key. + + ssl_check_hostname : Flag to configure whether ssl handshake should verify that the certificate matches the broker's hostname. - security_protocol: Protocol used to communicate with brokers. + + security_protocol : Protocol used to communicate with brokers. Valid values are: PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL. """ self.producer: Optional[Producer] = None diff --git a/rasa/core/channels/channel.py b/rasa/core/channels/channel.py index 2efd680b183f..b4f168237668 100644 --- a/rasa/core/channels/channel.py +++ b/rasa/core/channels/channel.py @@ -32,7 +32,8 @@ class UserMessage: """Represents an incoming message. - Includes the channel the responses should be sent to.""" + Includes the channel the responses should be sent to. + """ def __init__( self, @@ -43,6 +44,7 @@ def __init__( input_channel: Optional[Text] = None, message_id: Optional[Text] = None, metadata: Optional[Dict] = None, + **kwargs: Any, ) -> None: """Creates a ``UserMessage`` object. @@ -78,6 +80,7 @@ def __init__( self.parse_data = parse_data self.metadata = metadata + self.headers = kwargs.get("headers", None) def register( @@ -119,7 +122,8 @@ def blueprint( """Defines a Sanic blueprint. The blueprint will be attached to a running sanic server and handle - incoming routes it registered for.""" + incoming routes it registered for. + """ raise NotImplementedError("Component listener needs to provide blueprint.") @classmethod @@ -217,7 +221,6 @@ def name(cls) -> Text: async def send_response(self, recipient_id: Text, message: Dict[Text, Any]) -> None: """Send a message to the client.""" - if message.get("quick_replies"): await self.send_quick_replies( recipient_id, @@ -251,7 +254,6 @@ async def send_text_message( self, recipient_id: Text, text: Text, **kwargs: Any ) -> None: """Send a message through this channel.""" - raise NotImplementedError( "Output channel needs to implement a send message for simple texts." ) @@ -260,14 +262,12 @@ async def send_image_url( self, recipient_id: Text, image: Text, **kwargs: Any ) -> None: """Sends an image. Default will just post the url as a string.""" - await self.send_text_message(recipient_id, f"Image: {image}") async def send_attachment( self, recipient_id: Text, attachment: Text, **kwargs: Any ) -> None: """Sends an attachment. Default will just post as a string.""" - await self.send_text_message(recipient_id, f"Attachment: {attachment}") async def send_text_with_buttons( @@ -279,8 +279,8 @@ async def send_text_with_buttons( ) -> None: """Sends buttons to the output. - Default implementation will just post the buttons as a string.""" - + Default implementation will just post the buttons as a string. + """ await self.send_text_message(recipient_id, text) for idx, button in enumerate(buttons): button_msg = cli_utils.button_to_string(button, idx) @@ -295,17 +295,16 @@ async def send_quick_replies( ) -> None: """Sends quick replies to the output. - Default implementation will just send as buttons.""" - + Default implementation will just send as buttons. + """ await self.send_text_with_buttons(recipient_id, text, quick_replies) async def send_elements( self, recipient_id: Text, elements: Iterable[Dict[Text, Any]], **kwargs: Any ) -> None: """Sends elements to the output. - - Default implementation will just post the elements as a string.""" - + Default implementation will just post the elements as a string. + """ for element in elements: element_msg = "{title} : {subtitle}".format( title=element.get("title", ""), subtitle=element.get("subtitle", "") @@ -318,16 +317,16 @@ async def send_custom_json( self, recipient_id: Text, json_message: Dict[Text, Any], **kwargs: Any ) -> None: """Sends json dict to the output channel. - - Default implementation will just post the json contents as a string.""" - + Default implementation will just post the json contents as a string. + """ await self.send_text_message(recipient_id, json.dumps(json_message)) class CollectingOutputChannel(OutputChannel): - """Output channel that collects send messages in a list + """Output channel that collects send messages in a list. - (doesn't send them anywhere, just collects them).""" + (doesn't send them anywhere, just collects them). + """ def __init__(self) -> None: """Initialise list to collect messages.""" @@ -348,7 +347,6 @@ def _message( custom: Dict[Text, Any] = None, ) -> Dict: """Create a message object that will be stored.""" - obj = { "recipient_id": recipient_id, "text": text, @@ -380,14 +378,12 @@ async def send_image_url( self, recipient_id: Text, image: Text, **kwargs: Any ) -> None: """Sends an image. Default will just post the url as a string.""" - await self._persist_message(self._message(recipient_id, image=image)) async def send_attachment( self, recipient_id: Text, attachment: Text, **kwargs: Any ) -> None: """Sends an attachment. Default will just post as a string.""" - await self._persist_message(self._message(recipient_id, attachment=attachment)) async def send_text_with_buttons( diff --git a/rasa/core/channels/rest.py b/rasa/core/channels/rest.py index 764e920b5f5e..9cbfd6e10aca 100644 --- a/rasa/core/channels/rest.py +++ b/rasa/core/channels/rest.py @@ -166,6 +166,7 @@ async def receive(request: Request) -> Union[ResponseStream, HTTPResponse]: sender_id, input_channel=input_channel, metadata=metadata, + headers=request.headers, ) ) except CancelledError: diff --git a/rasa/core/http_interpreter.py b/rasa/core/http_interpreter.py index 9888dede258d..375dc7d1c9db 100644 --- a/rasa/core/http_interpreter.py +++ b/rasa/core/http_interpreter.py @@ -20,6 +20,7 @@ class RasaNLUHttpInterpreter: def __init__(self, endpoint_config: Optional[EndpointConfig] = None) -> None: """Initializes a `RasaNLUHttpInterpreter`.""" + self.session = aiohttp.ClientSession() if endpoint_config: self.endpoint_config = endpoint_config else: @@ -67,18 +68,17 @@ async def _rasa_http_parse( # noinspection PyBroadException try: - async with aiohttp.ClientSession() as session: - async with session.post(url, json=params) as resp: - if resp.status == 200: - return await resp.json() - else: - response_text = await resp.text() - structlogger.error( - "http.parse.text.failure", - text=copy.deepcopy(text), - response_text=copy.deepcopy(response_text), - ) - return None + async with self.session.post(url, json=params) as resp: + if resp.status == 200: + return await resp.json() + else: + response_text = await resp.text() + structlogger.error( + "http.parse.text.failure", + text=copy.deepcopy(text), + response_text=copy.deepcopy(response_text), + ) + return None except Exception: # skipcq: PYL-W0703 # need to catch all possible exceptions when doing http requests # (timeouts, value errors, parser errors, ...) diff --git a/rasa/shared/core/training_data/visualization.py b/rasa/shared/core/training_data/visualization.py index 21176c67b4b7..3e7049cbc9fb 100644 --- a/rasa/shared/core/training_data/visualization.py +++ b/rasa/shared/core/training_data/visualization.py @@ -533,17 +533,17 @@ def _remove_auxiliary_nodes( graph.remove_node(TMP_NODE_ID) - if not len(list(graph.predecessors(END_NODE_ID))): + if not graph.predecessors(END_NODE_ID): graph.remove_node(END_NODE_ID) # remove duplicated "..." nodes after merging - ps = set() + predecessors_seen = set() for i in range(special_node_idx + 1, TMP_NODE_ID): - for pred in list(graph.predecessors(i)): - if pred in ps: + predecessors = graph.predecessors(i) + for pred in predecessors: + if pred in predecessors_seen: graph.remove_node(i) - else: - ps.add(pred) + predecessors_seen.update(predecessors) def visualize_stories( diff --git a/rasa/version.py b/rasa/version.py index 671fdd543a16..45324effe832 100644 --- a/rasa/version.py +++ b/rasa/version.py @@ -1,3 +1,3 @@ # this file will automatically be changed, # do not add anything but the version number here! -__version__ = "3.6.4" +__version__ = "3.7.0a1" diff --git a/tests/core/test_http_interpreter.py b/tests/core/test_http_interpreter.py index 7580b51576b6..373277ff3dac 100644 --- a/tests/core/test_http_interpreter.py +++ b/tests/core/test_http_interpreter.py @@ -1,10 +1,11 @@ +from unittest.mock import patch + import pytest from aioresponses import aioresponses - from rasa.core.channels import UserMessage from rasa.core.http_interpreter import RasaNLUHttpInterpreter from rasa.utils.endpoints import EndpointConfig -from tests.utilities import latest_request, json_of_latest_request +from tests.utilities import json_of_latest_request, latest_request @pytest.mark.parametrize( @@ -16,6 +17,11 @@ ], ) async def test_http_interpreter(endpoint_url, joined_url): + """ + GIVEN an endpoint url + WHEN a RasaNLUHttpInterpreter is created using the endpoint url + THEN the parse method sends a request to the joined url. + """ with aioresponses() as mocked: mocked.post(joined_url) @@ -30,3 +36,37 @@ async def test_http_interpreter(endpoint_url, joined_url): response = {"text": "message_text", "token": None, "message_id": "message_id"} assert query == response + + +@pytest.fixture +def nlu_interpreter(): + with patch("aiohttp.ClientSession") as mock_session: + endpoint = EndpointConfig("https://example.com/a/") + yield RasaNLUHttpInterpreter(endpoint_config=endpoint) + + # Assert that the session object is initialized correctly + assert mock_session.called + + +async def test_same_session_object_used(nlu_interpreter): + """ + GIVEN a RasaNLUHttpInterpreter + WHEN the parse() method is called multiple times + THEN the same session object is used for all requests. + """ + # Call the parse() method multiple times + session = nlu_interpreter.session + + result1 = await nlu_interpreter.parse( + UserMessage(text="message_text_1", sender_id="message_id_1") + ) + assert nlu_interpreter.session == session + + result2 = await nlu_interpreter.parse( + UserMessage(text="message_text_2", sender_id="message_id_2") + ) + assert nlu_interpreter.session == session + + # Assert that the same session object is used for all requests + assert result1 is not None + assert result2 is not None diff --git a/tests/shared/core/training_data/test_visualization.py b/tests/shared/core/training_data/test_visualization.py index 3bb4f4f4ad27..63604b5ef354 100644 --- a/tests/shared/core/training_data/test_visualization.py +++ b/tests/shared/core/training_data/test_visualization.py @@ -10,6 +10,8 @@ from rasa.shared.nlu.training_data.message import Message from rasa.shared.nlu.training_data.training_data import TrainingData +import pytest + def test_style_transfer(): r = visualization._transfer_style({"class": "dashed great"}, {"class": "myclass"}) @@ -188,3 +190,42 @@ def test_story_visualization_with_merging(domain: Domain): assert 15 < len(generated_graph.nodes()) < 33 assert 20 < len(generated_graph.edges()) < 33 + + +@pytest.mark.parametrize( + "input_nodes, input_edges, remove_count, expected_nodes, expected_edges", + [ + ( + [-2, -1, 0, 1, 2, 3, 4, 5], + [(-2, 0), (-1, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5)], + 3, + set([0, 1, 2, 3, 4, 5, -1]), + [(-1, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5)], + ), + ( + [-3, -2, -1, 0, 1, 2, 3, 4, 5], + [(-3, -2), (-2, -1), (-1, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5)], + 4, + set([-3, -1, 0, 1, 2, 3, 4, 5]), + [(-1, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5)], + ), + ], +) +def test_remove_auxiliary_nodes( + input_nodes, input_edges, remove_count, expected_nodes, expected_edges +): + import networkx as nx + + # Create a sample graph + graph = nx.MultiDiGraph() + graph.add_nodes_from(input_nodes) + graph.add_edges_from(input_edges) + + # Call the method to remove auxiliary nodes + visualization._remove_auxiliary_nodes(graph, remove_count) + + # Check if the expected nodes are removed + assert set(graph.nodes()) == expected_nodes, "Nodes mismatch" + + # Check if the edges are updated correctly + assert list(graph.edges()) == expected_edges, "Edges mismatch"