Compare commits

...

13 Commits

Author SHA1 Message Date
Slavi Pantaleev
4a5966401c Release 1.3.2 2024-11-12 10:37:49 +02:00
Slavi Pantaleev
d92dfba2bf Upgrade Rust compiler in container image (1.81.0 -> 1.82.0) 2024-11-12 10:37:16 +02:00
Slavi Pantaleev
8538d6b2b8 Upgrade component services 2024-11-12 10:36:54 +02:00
Slavi Pantaleev
23f763ba72 Update dependencies 2024-11-12 10:22:26 +02:00
Slavi Pantaleev
a9e4ab1bdb Release 1.3.1 2024-10-03 16:30:58 +03:00
Slavi Pantaleev
d9a045a5e4 Make fallback user mentions support also match against the bot's room-specific username
It seems like Element iOS benefits from this.
2024-10-03 16:28:49 +03:00
Slavi Pantaleev
393be9be5a Remove strip_rich_reply_fallback_text in favor of remove_plain_reply_fallback from ruma events
No need to reinvent the wheel.
2024-10-03 16:02:06 +03:00
Slavi Pantaleev
a7b016a3d3 Release 1.3.0 2024-10-03 12:08:00 +03:00
Slavi Pantaleev
85e66406dc Allow for prompt caching to work by using baibot_conversation_start_time_utc instead of baibot_now_utc
This patch introduces a new `baibot_conversation_start_time_utc`
variable which indicates the time the conversation got started.

Using `baibot_now_utc` is still possible, but given that the current
time is a moving target, its use is in conflict with prompt caching.

Because the new `baibot_conversation_start_time_utc` prompt variable
is a more reasonable default, we're now using it in all sample configs.
2024-10-03 11:48:14 +03:00
Slavi Pantaleev
db9422740c Add support for OpenAI's o1 models by making max_response_tokens optional
The other prerequisite seems to be not using a `prompt` (`prompt: null`),
but we already supported this.

It'd be nice to add an optional `max_completion_tokens` parameter as
well, for the benefit of the o1 models, but this is not yet supported by
async-openai.
Possibly tracked here: https://github.com/64bit/async-openai/issues/272
2024-10-03 10:36:28 +03:00
Slavi Pantaleev
90fbad5b64 Update sample & default OpenAI provider configs to use gpt-4o (instead of gpt-4o-2024-08-06)
Since 2024-10-02, `gpt-4o` is actually the same as `gpt-4o-2024-08-06`.

We previously used `gpt-4o-2024-08-06`, because it was pointing to a
much better (longer context) model. Since they're both the same now,
we'd better stick to the unpinned model and make it easier for future
users to get upgrades.
2024-10-03 09:26:41 +03:00
Slavi Pantaleev
b40226826f Restore fallback support for user mentions
Fallback support was intentionally removed in 9908512968,
because it was deemed OK to do so.

It turns out that Element iOS still doesn't properly do user mentions
(and likely never will, until Element X replaces it), so we can't just
drop the fallback user mentions logic without affecting all these
clients. It's possible that the Element Android is no better (unverified claim).
2024-10-03 09:18:02 +03:00
Slavi Pantaleev
b89f0db71a Relocate "On-demand involvement" feature description section
[skip ci]
2024-10-02 09:12:12 +03:00
48 changed files with 420 additions and 215 deletions

View File

@@ -1,8 +1,31 @@
# (2024-11-12) Version 1.3.2
Dependency updates.
# (2024-10-03) Version 1.3.1
- (**Improvement**) Improves fallback user mentions support for old clients (like Element iOS) which use the bot's display name (not its full Matrix User ID). ([d9a045a5e4](https://github.com/etkecc/baibot/commit/d9a045a5e41d2b99694f92ec9e90f47529546d89))
# (2024-10-03) Version 1.3.0
**TLDR**: you can now use OpenAI's [o1](https://platform.openai.com/docs/models/o1) models, benefit from [prompt caching](https://platform.openai.com/docs/guides/prompt-caching) and mention the bot again from old clients lacking proper [user mentions support](https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions) (like Element iOS).
- (**Feature**) Introduces a new `baibot_conversation_start_time_utc` [prompt variable](./docs/configuration/text-generation.md#️-prompt-override) which is not a moving target (like the `baibot_now_utc` variable) and allows [prompt caching](https://platform.openai.com/docs/guides/prompt-caching) to work. All default/sample configs have been adjusted to make use of this new variable, but users need to adjust your existing dynamically-created agents to start using it. ([85e66406dc](https://github.com/etkecc/baibot/commit/85e66406dc6f430741c7819f420e2df4ae6e8d3b))
- (**Improvement**) Allows for the `max_response_tokens` configuration value for the [OpenAI provider](./docs/providers.md#openai) to be set to `null` to allow [o1](https://platform.openai.com/docs/models/o1) models (which do not support `max_response_tokens`) to be used. See the new o1 sample config [here](./docs/sample-provider-configs/openai-o1.yml). ([db9422740c](https://github.com/etkecc/baibot/commit/db9422740ceca32956d9628b6326b8be206344e2))
- (**Improvement**) Switches the sample configs for the [OpenAI provider](./docs/providers.md#openai) to point to the `gpt-4o` model, which since 2024-10-02 is the same as the `gpt-4o-2024-08-06` model. We previously explicitly pointed the bot to the `gpt-4o-2024-08-06` model, because it was much better (longer context window). Now that `gpt-4o` points to the same powerful model, we don't need to pin its version anymore. Existing users may wish to adjust their configuration to match. ([90fbad5b64](https://github.com/etkecc/baibot/commit/90fbad5b643cd06c23179f055a309ec6a7cba161))
- (**Bugfix**) Restores fallback user mentions support (via regular text, not via the [user mentions spec](https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions)) to allow certain old clients (like Element iOS) to be able to mention the bot again. Support for this was intentionally removed recently (in [v1.2.0](#2024-10-01-version-120)), but it turned out to be too early to do this. ([b40226826f](https://github.com/etkecc/baibot/commit/b40226826fe914d0d5d265230ebc5bac8058b6f7))
# (2024-10-01) Version 1.2.0 # (2024-10-01) Version 1.2.0
- (**Feature**) Adds support for [on-demand involvement](./docs/usage.md#on-demand-involvement) of the bot (via mention) in arbitrary threads and reply chains ([9908512968](https://github.com/etkecc/baibot/commit/990851296828168c2106eb3f4668833e9e5a7463)) - fixes [issue #15](https://github.com/etkecc/baibot/issues/15) - (**Feature**) Adds support for [on-demand involvement](./docs/features.md#on-demand-involvement) of the bot (via mention) in arbitrary threads and reply chains ([9908512968](https://github.com/etkecc/baibot/commit/990851296828168c2106eb3f4668833e9e5a7463)) - fixes [issue #15](https://github.com/etkecc/baibot/issues/15)
- (**Feature**) Simplifies [Transcribe-only mode](./docs/features.md#transcribe-only-mode) reply format (removing `> 🦻` prefixing) to allow easier forwarding, etc. ([e6aa956423](https://github.com/etkecc/baibot/commit/e6aa95642376ee7d87932d0e66dcfedf261b188b)) - fixes [issue #14](https://github.com/etkecc/baibot/issues/14) - (**Improvement**) Simplifies [Transcribe-only mode](./docs/features.md#transcribe-only-mode) reply format (removing `> 🦻` prefixing) to allow easier forwarding, etc. ([e6aa956423](https://github.com/etkecc/baibot/commit/e6aa95642376ee7d87932d0e66dcfedf261b188b)) - fixes [issue #14](https://github.com/etkecc/baibot/issues/14)
- (**Bugfix**) Fixes speech-to-text replies rendering incorrectly in certain clients, due to them confusing our old reply format with [fallback for rich replies](https://spec.matrix.org/v1.11/client-server-api/#fallbacks-for-rich-replies) ([e6aa956423](https://github.com/etkecc/baibot/commit/e6aa95642376ee7d87932d0e66dcfedf261b188b)) - fixes [issue #17](https://github.com/etkecc/baibot/issues/17) - (**Bugfix**) Fixes speech-to-text replies rendering incorrectly in certain clients, due to them confusing our old reply format with [fallback for rich replies](https://spec.matrix.org/v1.11/client-server-api/#fallbacks-for-rich-replies) ([e6aa956423](https://github.com/etkecc/baibot/commit/e6aa95642376ee7d87932d0e66dcfedf261b188b)) - fixes [issue #17](https://github.com/etkecc/baibot/issues/17)

125
Cargo.lock generated
View File

@@ -16,9 +16,9 @@ dependencies = [
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.24.1" version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [ dependencies = [
"gimli", "gimli",
] ]
@@ -191,9 +191,9 @@ dependencies = [
[[package]] [[package]]
name = "async-openai" name = "async-openai"
version = "0.24.1" version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6db3286b4f52b6556ac5208fb575d035eca61a2bf40d7e75d1db2733ffc599f" checksum = "0d226540b4ecf884b0fb4370008631ccbd9605cf82f98bb504ec8f2cee0810e8"
dependencies = [ dependencies = [
"async-convert", "async-convert",
"backoff", "backoff",
@@ -291,11 +291,11 @@ dependencies = [
[[package]] [[package]]
name = "baibot" name = "baibot"
version = "1.2.0" version = "1.3.2"
dependencies = [ dependencies = [
"anthropic-rs", "anthropic-rs",
"anyhow", "anyhow",
"async-openai 0.24.1", "async-openai 0.26.0",
"base64 0.22.1", "base64 0.22.1",
"chrono", "chrono",
"etke_openai_api_rust", "etke_openai_api_rust",
@@ -452,9 +452,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.24" version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@@ -1018,12 +1018,13 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]] [[package]]
name = "fancy-regex" name = "fancy-regex"
version = "0.12.0" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7493d4c459da9f84325ad297371a6b2b8a162800873a22e3b6b6512e61d18c05" checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"regex", "regex-automata 0.4.8",
"regex-syntax 0.8.5",
] ]
[[package]] [[package]]
@@ -1098,9 +1099,9 @@ dependencies = [
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -1113,9 +1114,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@@ -1123,15 +1124,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@@ -1140,15 +1141,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1157,15 +1158,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]] [[package]]
name = "futures-timer" name = "futures-timer"
@@ -1175,9 +1176,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -1216,9 +1217,9 @@ dependencies = [
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.31.0" version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]] [[package]]
name = "gloo-timers" name = "gloo-timers"
@@ -1293,13 +1294,19 @@ dependencies = [
"allocator-api2", "allocator-api2",
] ]
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]] [[package]]
name = "hashlink" name = "hashlink"
version = "0.8.4" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [ dependencies = [
"hashbrown", "hashbrown 0.14.5",
] ]
[[package]] [[package]]
@@ -1601,12 +1608,12 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.15.0",
"serde", "serde",
] ]
@@ -1726,9 +1733,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.159" version = "0.2.162"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
@@ -2192,21 +2199,18 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.4" version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.1" version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@@ -2365,12 +2369,6 @@ dependencies = [
"universal-hash", "universal-hash",
] ]
[[package]]
name = "portable-atomic"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.20"
@@ -2455,9 +2453,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -2504,7 +2502,7 @@ checksum = "7d7c94f8935a9df96bb6380e8592c70edf497a643f94bd23b2f76b399385dbf4"
dependencies = [ dependencies = [
"ahash", "ahash",
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.14.5",
"parking_lot", "parking_lot",
] ]
@@ -3001,9 +2999,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.37" version = "0.38.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"errno", "errno",
@@ -3462,9 +3460,9 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.13.0" version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
@@ -3505,9 +3503,9 @@ dependencies = [
[[package]] [[package]]
name = "tiktoken-rs" name = "tiktoken-rs"
version = "0.5.9" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c314e7ce51440f9e8f5a497394682a57b7c323d0f4d0a6b1b13c429056e0e234" checksum = "44075987ee2486402f0808505dd65692163d243a337fc54363d49afac41087f6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-openai 0.14.3", "async-openai 0.14.3",
@@ -3516,6 +3514,7 @@ dependencies = [
"fancy-regex", "fancy-regex",
"lazy_static", "lazy_static",
"parking_lot", "parking_lot",
"regex",
"rustc-hash 1.1.0", "rustc-hash 1.1.0",
] ]
@@ -3536,9 +3535,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.40.0" version = "1.41.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -3791,9 +3790,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.15" version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"

View File

@@ -7,7 +7,7 @@ license = "AGPL-3.0-or-later"
readme = "README.md" readme = "README.md"
keywords = ["matrix", "chat", "bot", "AI", "LLM"] keywords = ["matrix", "chat", "bot", "AI", "LLM"]
include = ["/etc/assets/baibot-torso-768.png", "/src", "/README.md", "/CHANGELOG.md", "/LICENSE"] include = ["/etc/assets/baibot-torso-768.png", "/src", "/README.md", "/CHANGELOG.md", "/LICENSE"]
version = "1.2.0" version = "1.3.2"
edition = "2021" edition = "2021"
[lib] [lib]
@@ -17,7 +17,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
anthropic-rs = "0.1.*" anthropic-rs = "0.1.*"
anyhow = "1.0.*" anyhow = "1.0.*"
async-openai = "0.24.*" async-openai = "0.26.*"
base64 = "0.22.*" base64 = "0.22.*"
chrono = { version = "0.4.*", default-features = false, features = ["std", "now"] } chrono = { version = "0.4.*", default-features = false, features = ["std", "now"] }
# We'd rather not depend on this, but we cannot use the ruma-events EventContent macro without it. # We'd rather not depend on this, but we cannot use the ruma-events EventContent macro without it.
@@ -30,9 +30,9 @@ regex = "1.11.*"
serde = { version = "1.0.*", features = ["derive"], default-features = false } serde = { version = "1.0.*", features = ["derive"], default-features = false }
serde_json = "1.0.*" serde_json = "1.0.*"
serde_yaml = "0.9.*" serde_yaml = "0.9.*"
tempfile = "3.13.*" tempfile = "3.14.*"
tiktoken-rs = { version = "0.5.*", features = ["async-openai"] } tiktoken-rs = { version = "0.6.*", features = ["async-openai"] }
tokio = { version = "1.40.*", features = ["rt", "rt-multi-thread", "macros"] } tokio = { version = "1.41.*", features = ["rt", "rt-multi-thread", "macros"] }
tracing = "0.1.*" tracing = "0.1.*"
tracing-subscriber = { version = "0.3.*", features = ["env-filter"] } tracing-subscriber = { version = "0.3.*", features = ["env-filter"] }
url = "2.5.*" url = "2.5.*"

View File

@@ -4,7 +4,7 @@
# # # #
####################################### #######################################
FROM docker.io/rust:1.81.0-slim-bookworm AS build FROM docker.io/rust:1.82.0-slim-bookworm AS build
RUN apt-get update && apt-get install -y build-essential pkg-config libssl-dev libsqlite3-dev RUN apt-get update && apt-get install -y build-essential pkg-config libssl-dev libsqlite3-dev

View File

@@ -4,7 +4,7 @@
# # # #
####################################### #######################################
FROM docker.io/rust:1.81.0-slim-bookworm AS build FROM docker.io/rust:1.82.0-slim-bookworm AS build
RUN apt-get update && apt-get install -y build-essential pkg-config libssl-dev libsqlite3-dev RUN apt-get update && apt-get install -y build-essential pkg-config libssl-dev libsqlite3-dev

View File

@@ -16,7 +16,7 @@ Users:
- ✅ can **invite the bot to rooms** - ✅ can **invite the bot to rooms**
- ✅ can **use all the bot's [features](./features.md)** ([💬 Text Generation](./features.md#-text-generation), [🦻 Speech-to-Text](./features.md#-speech-to-text), etc.) by sending room messages - ✅ can **use all the bot's [features](./features.md)** ([💬 Text Generation](./features.md#-text-generation), [🦻 Speech-to-Text](./features.md#-speech-to-text), etc.) by sending room messages
- ✅ can **mention the bot** in threads and reply chains to provoke it to respond to non-user messages (see [📖 Usage / 💬 Text Generation / On-demand involvement](./usage.md#on-demand-involvement)) - ✅ can **mention the bot** in threads and reply chains to provoke it to respond to non-user messages (see [🌟 Features / 💬 Text Generation / On-demand involvement](./features.md#on-demand-involvement))
- ✅ can **change the bot's configuration in a room** (e.g. `!bai config room ...` commands) - ✅ can **change the bot's configuration in a room** (e.g. `!bai config room ...` commands)
- ❌ cannot **change the bot's global configuration** (e.g. `!bai config global ...` commands) - ❌ cannot **change the bot's global configuration** (e.g. `!bai config global ...` commands)
- ❌ cannot **create new [🤖 Agents](./agents.md)** (neither in rooms, nor globally). See [💼 Room-local agent managers](#-room-local-agent-managers) for controlling which users can create agents. - ❌ cannot **create new [🤖 Agents](./agents.md)** (neither in rooms, nor globally). See [💼 Room-local agent managers](#-room-local-agent-managers) for controlling which users can create agents.

View File

@@ -77,11 +77,14 @@ Prompts may contain the following **placeholder variables** which will be replac
|---------------------------|-------------|---------| |---------------------------|-------------|---------|
| `{{ baibot_name }}` | Name of the bot as configured in the `user.name` field in the [Static configuration](./README.md#static-configuration) | `Baibot` | | `{{ baibot_name }}` | Name of the bot as configured in the `user.name` field in the [Static configuration](./README.md#static-configuration) | `Baibot` |
| `{{ baibot_model_id }}` | Text-Generation model ID as configured in the [🤖 agent](../agents.md)'s configuration | `gpt-4o` | | `{{ baibot_model_id }}` | Text-Generation model ID as configured in the [🤖 agent](../agents.md)'s configuration | `gpt-4o` |
| `{{ baibot_now_utc }}` | Current date and time in UTC | `2024-09-20 (Friday), 14:26:42 UTC` | | `{{ baibot_now_utc }}` | Current date and time in UTC (⚠️ usage may break prompt caching - see below) | `2024-09-20 (Friday), 14:26:42 UTC` |
| `{{ baibot_conversation_start_time_utc }}` | The date and time in UTC that the conversation started | `2024-09-20 (Friday), 14:26:42 UTC` |
💡 `{{ baibot_now_utc }}` changes as time goes on, which prevents [prompt caching](https://platform.openai.com/docs/guides/prompt-caching) from working. It's better to use `{{ baibot_conversation_start_time_utc }}` in prompts, as its value doesn't change yet still orients the bot to the current date/time.
Here's a prompt that combines some of the above variables: Here's a prompt that combines some of the above variables:
> You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." > You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
### 🌡️ Temperature Override ### 🌡️ Temperature Override

View File

@@ -28,7 +28,7 @@ Text Generation is the bot's ability to **respond to users' text messages with t
In multi-user (group) rooms, to avoid disturbing the normal conversation between people, the bot is auto-configured to only respond to messages starting with the command prefix (`!bai`) or direct mentions via the [💬 Text Generation / 🗟 Prefix Requirement Type](./configuration/text-generation.md#-prefix-requirement-type) setting. In multi-user (group) rooms, to avoid disturbing the normal conversation between people, the bot is auto-configured to only respond to messages starting with the command prefix (`!bai`) or direct mentions via the [💬 Text Generation / 🗟 Prefix Requirement Type](./configuration/text-generation.md#-prefix-requirement-type) setting.
Normally, the bot only responds to allowed [👥 Users](./access.md#-users). In certain cases, it's useful for an allowed user to provoke the bot to respond even in foreign threads or reply chains. You can learn more about this feature in the [📖 Usage / 💬 Text Generation / On-demand involvement](./usage.md#on-demand-involvement) section. Normally, the bot only responds to allowed [👥 Users](./access.md#-users). In certain cases, it's useful for an allowed user to provoke the bot to respond even in foreign threads or reply chains. You can learn more about this feature in the [On-demand involvement](./features.md#on-demand-involvement) section below.
A few other features (like [🗣️ Text-to-Speech](#️-text-to-speech) and [🦻 Speech-to-Text](#-speech-to-text)) combine well with Text Generation, so you **don't necessarily need to communicate with the bot via text** (with [Seamless voice interaction](#seamless-voice-interaction), you can communicate only with voice). A few other features (like [🗣️ Text-to-Speech](#️-text-to-speech) and [🦻 Speech-to-Text](#-speech-to-text)) combine well with Text Generation, so you **don't necessarily need to communicate with the bot via text** (with [Seamless voice interaction](#seamless-voice-interaction), you can communicate only with voice).
@@ -38,6 +38,22 @@ You may also wish to see:
- [📖 Usage / 💬 Text Generation](./usage.md#-text-generation) section for more details on how to use the bot for Text Generation in a room - [📖 Usage / 💬 Text Generation](./usage.md#-text-generation) section for more details on how to use the bot for Text Generation in a room
#### On-demand involvement
In the following 2 cases, it's useful to involve the bot in conversations on-demand:
1. In multi-user rooms (with the [🗟 Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting set to "required")
2. In rooms with foreign users (users that are not authorized bot [👥 users](./access.md#-users))
In these instances, an allowed [👥 user](./access.md#-users) can also provoke the bot to respond to **any** thread or reply chain by [mentioning](https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions) the bot (e.g. `@baibot Hello!`). The following screenshots demonstrate this behavior:
- [🖼️ On-demand involvement in the room](./screenshots/text-generation-prefix-requirement.webp)
- [🖼️ On-demand involvement in a thread](./screenshots/text-generation-on-demand-thread-involvement.webp) (the Alice user in this example is not an allowed user, yet her messages are still considered as part of the conversation context)
- [🖼️ On-demand involvement in a reply chain](./screenshots/text-generation-on-demand-reply-involvement.webp) (the Alice user in this example is not an allowed user, yet her messages are still considered as part of the conversation context)
💡 **NOTE**: Normally, the bot **only considers messages from allowed [👥 Users](./access.md#-users)** and ignores all other messages when responding. However, **when the bot is explicitly invoked (via mention)** in a thread or reply chain, **it will consider all messages** in the thread and reply chain (even those from foreign users) as part of the conversation context.
### 🗣️ Text-to-Speech ### 🗣️ Text-to-Speech
Text-to-Speech is the bot's ability to **turn text messages into voice messages**. Text-to-Speech is the bot's ability to **turn text messages into voice messages**.

View File

@@ -125,7 +125,10 @@ For services which are not fully compatible with the OpenAI API, consider using
- create a room-local agent: `!bai agent create-room-local openai my-openai-agent` - create a room-local agent: `!bai agent create-room-local openai my-openai-agent`
- create a global agent: `!bai agent create-global openai my-openai-agent` - create a global agent: `!bai agent create-global openai my-openai-agent`
💡 When creating an agent, the bot will show you an up-to-date sample configuration for this provider which looks [like this](./sample-provider-configs/openai.yml). 💡 When creating an agent, the bot will show you an up-to-date sample configuration for this provider which:
- in the general case looks [like this](./sample-provider-configs/openai.yml)
- for the [o1](https://platform.openai.com/docs/models/o1) models needs to look [like this](./sample-provider-configs/openai-o1.yml)
### OpenAI Compatible ### OpenAI Compatible

View File

@@ -2,7 +2,7 @@ base_url: https://api.anthropic.com/v1
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: claude-3-5-sonnet-20240620 model_id: claude-3-5-sonnet-20240620
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 8192 max_response_tokens: 8192
max_context_tokens: 204800 max_context_tokens: 204800

View File

@@ -2,7 +2,7 @@ base_url: https://api.groq.com/openai/v1
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: llama3-70b-8192 model_id: llama3-70b-8192
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 4096 max_response_tokens: 4096
max_context_tokens: 131072 max_context_tokens: 131072

View File

@@ -2,7 +2,7 @@ base_url: http://my-localai-self-hosted-service:8080/v1
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: gpt-4 model_id: gpt-4
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 4096 max_response_tokens: 4096
max_context_tokens: 128000 max_context_tokens: 128000

View File

@@ -2,7 +2,7 @@ base_url: https://api.mistral.ai/v1
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: mistral-large-latest model_id: mistral-large-latest
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 4096 max_response_tokens: 4096
max_context_tokens: 128000 max_context_tokens: 128000

View File

@@ -2,7 +2,7 @@ base_url: http://my-ollama-self-hosted-service:11434/v1
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: gemma2:2b model_id: gemma2:2b
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 4096 max_response_tokens: 4096
max_context_tokens: 128000 max_context_tokens: 128000

View File

@@ -2,7 +2,7 @@ base_url: ''
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: some-model model_id: some-model
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 4096 max_response_tokens: 4096
max_context_tokens: 128000 max_context_tokens: 128000

View File

@@ -0,0 +1,24 @@
base_url: https://api.openai.com/v1
api_key: YOUR_API_KEY_HERE
text_generation:
model_id: o1-mini
# o1 models do not support a system prompt
prompt: null
temperature: 1.0
# o1 models do not support max_response_tokens.
# They use `max_completion_tokens` as an alternative,
# but we don't support it yet (see https://github.com/64bit/async-openai/issues/272).
max_response_tokens: null
max_context_tokens: 128000
speech_to_text:
model_id: whisper-1
text_to_speech:
model_id: tts-1-hd
voice: onyx
speed: 1.0
response_format: opus
image_generation:
model_id: dall-e-3
style: vivid
size: 1024x1024
quality: standard

View File

@@ -1,8 +1,8 @@
base_url: https://api.openai.com/v1 base_url: https://api.openai.com/v1
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: gpt-4o-2024-08-06 model_id: gpt-4o
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 16384 max_response_tokens: 16384
max_context_tokens: 128000 max_context_tokens: 128000

View File

@@ -2,7 +2,7 @@ base_url: https://openrouter.ai/api/v1
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: mattshumer/reflection-70b:free model_id: mattshumer/reflection-70b:free
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 2048 max_response_tokens: 2048
max_context_tokens: 8192 max_context_tokens: 8192

View File

@@ -2,7 +2,7 @@ base_url: https://api.together.xyz/v1
api_key: YOUR_API_KEY_HERE api_key: YOUR_API_KEY_HERE
text_generation: text_generation:
model_id: meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo model_id: meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo
prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
temperature: 1.0 temperature: 1.0
max_response_tokens: 2048 max_response_tokens: 2048
max_context_tokens: 8192 max_context_tokens: 8192

View File

@@ -15,7 +15,7 @@ See screenshots of:
- 🖼️ [the default Text Generation flow](./screenshots/text-generation.webp) in 1:1 rooms - 🖼️ [the default Text Generation flow](./screenshots/text-generation.webp) in 1:1 rooms
- 🖼️ [the Text Generation flow in multi-user rooms](./screenshots/text-generation-prefix-requirement.webp) (where the [🗟 Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting is auto-configured to "required") - 🖼️ [the Text Generation flow in multi-user rooms](./screenshots/text-generation-prefix-requirement.webp) (where the [🗟 Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting is auto-configured to "required")
- [on-demand involvement](#on-demand-involvement) - the [on-demand involvement](./features.md#on-demand-involvement) feature
Whether the bot responds depends on: Whether the bot responds depends on:
@@ -25,27 +25,12 @@ Whether the bot responds depends on:
- (🎨 agent capabilities) whether the configured `text-generation` (or `catch-all`) handler agent actually supports text-generation. The provider may lack support for this feature or it may be disabled in the [🤖 agents](./agents.md) configuration - (🎨 agent capabilities) whether the configured `text-generation` (or `catch-all`) handler agent actually supports text-generation. The provider may lack support for this feature or it may be disabled in the [🤖 agents](./agents.md) configuration
- (the [🗟 Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting) whether a prefix (e.g. `!bai`) or user mention (e.g. `@baibot`) is required for messages sent to the room. For multi-user rooms, this setting defaults to "required". See [on-demand involvement](#on-demand-involvement) for details. - (the [🗟 Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting) whether a prefix (e.g. `!bai`) or user mention (e.g. `@baibot`) is required for messages sent to the room. For multi-user rooms, this setting defaults to "required". See [🌟 Features / 💬 Text Generation / On-demand involvement](./features.md#on-demand-involvement) for details.
Room messages start a threaded conversation where you can continue back-and-forth communication with the bot. Room messages start a threaded conversation where you can continue back-and-forth communication with the bot. Using [on-demand involvement](./features.md#on-demand-involvement), you can can also mention the bot to provoke it to get involved in any conversation thread or reply chain.
Unless you've enabled the [♻️ Context Management](./features.md#️-context-management) feature, all messages will be sent to the agent's API each time. If the context management feature is enabled, older messages may be dropped. Unless you've enabled the [♻️ Context Management](./features.md#️-context-management) feature, all messages will be sent to the agent's API each time. If the context management feature is enabled, older messages may be dropped.
#### On-demand involvement
In the following 2 cases, it's useful to involve the bot in conversations on-demand:
1. For multi-user rooms (with the [🗟 Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting set to "required")
2. In rooms with foreign users (users that are not authorized bot [👥 users](./access.md#-users))
In these instances, an allowed [👥 user](./access.md#-users) can also provoke the bot to respond to **any** thread or reply chain by [mentioning](https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions) the bot (e.g. `@baibot Hello!`). The following screenshots demonstrate this behavior:
- [🖼️ On-demand involvement in the room](./screenshots/text-generation-prefix-requirement.webp)
- [🖼️ On-demand involvement in a thread](./screenshots/text-generation-on-demand-thread-involvement.webp) (the Alice user in this example is not an allowed user, yet her messages are still considered as part of the conversation context)
- [🖼️ On-demand involvement in a reply chain](./screenshots/text-generation-on-demand-reply-involvement.webp) (the Alice user in this example is not an allowed user, yet her messages are still considered as part of the conversation context)
💡 **NOTE**: Normally, the bot **only considers messages from allowed [👥 Users](./access.md#-users)** and ignores all other messages when responding. However, **when the bot is explicitly invoked (via mention)** in a thread or reply chain, **it will consider all messages** in the thread and reply chain (even those from foreign users) as part of the conversation context.
### 🗣️ Text-to-Speech ### 🗣️ Text-to-Speech

View File

@@ -72,8 +72,8 @@ agents:
# base_url: https://api.openai.com/v1 # base_url: https://api.openai.com/v1
# api_key: "" # api_key: ""
# text_generation: # text_generation:
# model_id: gpt-4o-2024-08-06 # model_id: gpt-4o
# prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
# temperature: 1.0 # temperature: 1.0
# max_response_tokens: 16384 # max_response_tokens: 16384
# max_context_tokens: 128000 # max_context_tokens: 128000
@@ -97,7 +97,7 @@ agents:
# api_key: null # api_key: null
# text_generation: # text_generation:
# model_id: gpt-4 # model_id: gpt-4
# prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
# temperature: 1.0 # temperature: 1.0
# max_response_tokens: 16384 # max_response_tokens: 16384
# max_context_tokens: 128000 # max_context_tokens: 128000
@@ -122,7 +122,7 @@ agents:
# api_key: null # api_key: null
# text_generation: # text_generation:
# model_id: "gemma2:2b" # model_id: "gemma2:2b"
# prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
# temperature: 1.0 # temperature: 1.0
# max_response_tokens: 4096 # max_response_tokens: 4096
# max_context_tokens: 128000 # max_context_tokens: 128000

View File

@@ -13,7 +13,7 @@ services:
- /etc/passwd:/etc/passwd:ro - /etc/passwd:/etc/passwd:ro
synapse: synapse:
image: ghcr.io/element-hq/synapse:v1.116.0 image: ghcr.io/element-hq/synapse:v1.118.0
user: "${UID}:${GID}" user: "${UID}:${GID}"
restart: unless-stopped restart: unless-stopped
entrypoint: python entrypoint: python
@@ -26,7 +26,7 @@ services:
- ./synapse/media-store:/media-store - ./synapse/media-store:/media-store
element-web: element-web:
image: docker.io/vectorim/element-web:v1.11.79 image: docker.io/vectorim/element-web:v1.11.84
user: "${UID}:${GID}" user: "${UID}:${GID}"
restart: unless-stopped restart: unless-stopped
ports: ports:

View File

@@ -1,6 +1,6 @@
services: services:
ollama: ollama:
image: docker.io/ollama/ollama:0.3.11 image: docker.io/ollama/ollama:0.4.1
restart: unless-stopped restart: unless-stopped
ports: ports:
- "${SERVICE_OLLAMA_BIND_PORT_HTTP}:11434" - "${SERVICE_OLLAMA_BIND_PORT_HTTP}:11434"

View File

@@ -21,5 +21,5 @@ pub use provider::{AgentProvider, AgentProviderInfo, ControllerTrait};
pub use purpose::AgentPurpose; pub use purpose::AgentPurpose;
pub(super) fn default_prompt() -> &'static str { pub(super) fn default_prompt() -> &'static str {
"You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}."
} }

View File

@@ -72,6 +72,7 @@ impl ControllerTrait for Controller {
let messages = vec![LLMMessage { let messages = vec![LLMMessage {
author: LLMAuthor::User, author: LLMAuthor::User,
message_text: "Hello!".to_string(), message_text: "Hello!".to_string(),
timestamp: chrono::Utc::now(),
}]; }];
let conversation = LLMConversation { messages }; let conversation = LLMConversation { messages };
@@ -108,6 +109,7 @@ impl ControllerTrait for Controller {
Some(LLMMessage { Some(LLMMessage {
author: LLMAuthor::Prompt, author: LLMAuthor::Prompt,
message_text: prompt_text, message_text: prompt_text,
timestamp: chrono::Utc::now(),
}) })
}; };
@@ -129,7 +131,7 @@ impl ControllerTrait for Controller {
&text_generation_config.model_id, &text_generation_config.model_id,
&prompt_message, &prompt_message,
conversation_messages, conversation_messages,
text_generation_config.max_response_tokens, Some(text_generation_config.max_response_tokens),
text_generation_config.max_context_tokens, text_generation_config.max_context_tokens,
); );

View File

@@ -7,17 +7,33 @@ pub struct TextGenerationPromptVariables {
impl Default for TextGenerationPromptVariables { impl Default for TextGenerationPromptVariables {
fn default() -> Self { fn default() -> Self {
Self::new("unnamed", "unknown-model", Utc::now()) let now = Utc::now();
Self::new("unnamed", "unknown-model", now, Some(now))
} }
} }
impl TextGenerationPromptVariables { impl TextGenerationPromptVariables {
pub fn new(bot_name: &str, model_id: &str, utc_time: DateTime<Utc>) -> Self { pub fn new(
bot_name: &str,
model_id: &str,
now_time: DateTime<Utc>,
conversation_start_time: Option<DateTime<Utc>>,
) -> Self {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("baibot_name".to_string(), bot_name.to_string()); map.insert("baibot_name".to_string(), bot_name.to_string());
map.insert("baibot_model_id".to_string(), model_id.to_string()); map.insert("baibot_model_id".to_string(), model_id.to_string());
map.insert("baibot_now_utc".to_string(), format_utc_time(utc_time)); map.insert("baibot_now_utc".to_string(), format_utc_time(now_time));
let baibot_conversation_start_time_utc = match conversation_start_time {
Some(conversation_start_time) => format_utc_time(conversation_start_time),
None => "unknown".to_string(),
};
map.insert(
"baibot_conversation_start_time_utc".to_string(),
baibot_conversation_start_time_utc,
);
Self { map } Self { map }
} }
@@ -52,7 +68,18 @@ mod tests {
.with_nanosecond(250000000) .with_nanosecond(250000000)
.unwrap(); .unwrap();
let variables = TextGenerationPromptVariables::new("baibot", "gpt-4o", now_utc); let conversation_start_time_utc = Utc
.with_ymd_and_hms(2024, 9, 19, 18, 34, 15)
.unwrap()
.with_nanosecond(250000000)
.unwrap();
let variables = TextGenerationPromptVariables::new(
"baibot",
"gpt-4o",
now_utc,
Some(conversation_start_time_utc),
);
assert_eq!( assert_eq!(
variables.map.get("baibot_name"), variables.map.get("baibot_name"),
@@ -66,9 +93,13 @@ mod tests {
variables.map.get("baibot_now_utc"), variables.map.get("baibot_now_utc"),
Some(&format_utc_time(now_utc)) Some(&format_utc_time(now_utc))
); );
assert_eq!(
variables.map.get("baibot_conversation_start_time_utc"),
Some(&format_utc_time(conversation_start_time_utc))
);
let prompt = "Hello, I'm {{ baibot_name }} using {{ baibot_model_id }}. The date/time now is {{ baibot_now_utc }}."; let prompt = "Hello, I'm {{ baibot_name }} using {{ baibot_model_id }}. The date/time now is {{ baibot_now_utc }} and this conversation started at {{ baibot_conversation_start_time_utc }}.";
let expected = "Hello, I'm baibot using gpt-4o. The date/time now is 2024-09-20 (Friday), 18:34:15 UTC."; let expected = "Hello, I'm baibot using gpt-4o. The date/time now is 2024-09-20 (Friday), 18:34:15 UTC and this conversation started at 2024-09-19 (Thursday), 18:34:15 UTC.";
assert_eq!(variables.format(prompt), expected); assert_eq!(variables.format(prompt), expected);
} }

View File

@@ -15,7 +15,7 @@ pub fn default_config() -> Config {
if let Some(ref mut config) = config.text_generation.as_mut() { if let Some(ref mut config) = config.text_generation.as_mut() {
config.model_id = "llama3-70b-8192".to_owned(); config.model_id = "llama3-70b-8192".to_owned();
config.max_context_tokens = 131_072; config.max_context_tokens = 131_072;
config.max_response_tokens = 4096; config.max_response_tokens = Some(4096);
} }
if let Some(ref mut config) = config.speech_to_text.as_mut() { if let Some(ref mut config) = config.speech_to_text.as_mut() {

View File

@@ -13,7 +13,7 @@ pub fn default_config() -> Config {
if let Some(ref mut config) = config.text_generation.as_mut() { if let Some(ref mut config) = config.text_generation.as_mut() {
config.model_id = "gpt-4".to_owned(); config.model_id = "gpt-4".to_owned();
config.max_context_tokens = 128_000; config.max_context_tokens = 128_000;
config.max_response_tokens = 4096; config.max_response_tokens = Some(4096);
} }
if let Some(ref mut config) = config.text_to_speech.as_mut() { if let Some(ref mut config) = config.text_to_speech.as_mut() {

View File

@@ -17,7 +17,7 @@ pub fn default_config() -> Config {
if let Some(ref mut config) = config.text_generation.as_mut() { if let Some(ref mut config) = config.text_generation.as_mut() {
config.model_id = "gemma2:2b".to_owned(); config.model_id = "gemma2:2b".to_owned();
config.max_context_tokens = 128_000; config.max_context_tokens = 128_000;
config.max_response_tokens = 4096; config.max_response_tokens = Some(4096);
} }
config config

View File

@@ -56,7 +56,7 @@ pub struct TextGenerationConfig {
pub temperature: f32, pub temperature: f32,
#[serde(default)] #[serde(default)]
pub max_response_tokens: u32, pub max_response_tokens: Option<u32>,
#[serde(default)] #[serde(default)]
pub max_context_tokens: u32, pub max_context_tokens: u32,
@@ -68,14 +68,14 @@ impl Default for TextGenerationConfig {
model_id: default_text_model_id(), model_id: default_text_model_id(),
prompt: Some(default_prompt().to_owned()), prompt: Some(default_prompt().to_owned()),
temperature: super::super::default_temperature(), temperature: super::super::default_temperature(),
max_response_tokens: 16_384, max_response_tokens: Some(16_384),
max_context_tokens: 128_000, max_context_tokens: 128_000,
} }
} }
} }
fn default_text_model_id() -> String { fn default_text_model_id() -> String {
"gpt-4o-2024-08-06".to_owned() "gpt-4o".to_owned()
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@@ -63,6 +63,7 @@ impl ControllerTrait for Controller {
let messages = vec![LLMMessage { let messages = vec![LLMMessage {
author: LLMAuthor::User, author: LLMAuthor::User,
message_text: "Hello!".to_string(), message_text: "Hello!".to_string(),
timestamp: chrono::Utc::now(),
}]; }];
let conversation = LLMConversation { messages }; let conversation = LLMConversation { messages };
@@ -99,6 +100,7 @@ impl ControllerTrait for Controller {
Some(LLMMessage { Some(LLMMessage {
author: LLMAuthor::Prompt, author: LLMAuthor::Prompt,
message_text: prompt_text, message_text: prompt_text,
timestamp: chrono::Utc::now(),
}) })
}; };
@@ -131,12 +133,18 @@ impl ControllerTrait for Controller {
.temperature_override .temperature_override
.unwrap_or(text_generation_config.temperature); .unwrap_or(text_generation_config.temperature);
let request = CreateChatCompletionRequestArgs::default() let mut request_builder = CreateChatCompletionRequestArgs::default();
.max_tokens(text_generation_config.max_response_tokens)
request_builder
.model(&text_generation_config.model_id) .model(&text_generation_config.model_id)
.temperature(temperature) .temperature(temperature)
.messages(openai_conversation_messages) .messages(openai_conversation_messages);
.build()?;
if let Some(max_response_tokens) = text_generation_config.max_response_tokens {
request_builder.max_tokens(max_response_tokens);
}
let request = request_builder.build()?;
if let Ok(request_as_json) = serde_json::to_string(&request) { if let Ok(request_as_json) = serde_json::to_string(&request) {
tracing::trace!( tracing::trace!(

View File

@@ -66,7 +66,7 @@ pub struct TextGenerationConfig {
pub temperature: f32, pub temperature: f32,
#[serde(default)] #[serde(default)]
pub max_response_tokens: u32, pub max_response_tokens: Option<u32>,
#[serde(default)] #[serde(default)]
pub max_context_tokens: u32, pub max_context_tokens: u32,
@@ -78,7 +78,7 @@ impl Default for TextGenerationConfig {
model_id: default_text_model_id(), model_id: default_text_model_id(),
prompt: Some(default_prompt().to_owned()), prompt: Some(default_prompt().to_owned()),
temperature: super::super::default_temperature(), temperature: super::super::default_temperature(),
max_response_tokens: 4096, max_response_tokens: Some(4096),
max_context_tokens: 128_000, max_context_tokens: 128_000,
} }
} }

View File

@@ -61,6 +61,7 @@ impl ControllerTrait for Controller {
let messages = vec![LLMMessage { let messages = vec![LLMMessage {
author: LLMAuthor::User, author: LLMAuthor::User,
message_text: "Hello!".to_string(), message_text: "Hello!".to_string(),
timestamp: chrono::Utc::now(),
}]; }];
let conversation = LLMConversation { messages }; let conversation = LLMConversation { messages };
@@ -97,6 +98,7 @@ impl ControllerTrait for Controller {
Some(LLMMessage { Some(LLMMessage {
author: LLMAuthor::Prompt, author: LLMAuthor::Prompt,
message_text: prompt_text, message_text: prompt_text,
timestamp: chrono::Utc::now(),
}) })
}; };
@@ -131,12 +133,15 @@ impl ControllerTrait for Controller {
let max_tokens = text_generation_config let max_tokens = text_generation_config
.max_response_tokens .max_response_tokens
.try_into() .map(|max_response_tokens| {
.expect("Failed converting max_response_tokens from u32 to i32"); max_response_tokens
.try_into()
.expect("Failed converting max_response_tokens from u32 to i32")
});
let request = ChatBody { let request = ChatBody {
model: text_generation_config.model_id.clone(), model: text_generation_config.model_id.clone(),
max_tokens: Some(max_tokens), max_tokens,
temperature: Some(temperature), temperature: Some(temperature),
top_p: None, top_p: None,
n: Some(1), n: Some(1),

View File

@@ -56,7 +56,7 @@ pub fn default_config() -> Config {
if let Some(text_generation) = &mut config.text_generation { if let Some(text_generation) = &mut config.text_generation {
text_generation.model_id = "some-model".to_string(); text_generation.model_id = "some-model".to_string();
text_generation.max_response_tokens = 4096; text_generation.max_response_tokens = Some(4096);
text_generation.max_context_tokens = 128_000; text_generation.max_context_tokens = 128_000;
} }

View File

@@ -14,7 +14,7 @@ pub fn default_config() -> Config {
if let Some(ref mut config) = config.text_generation.as_mut() { if let Some(ref mut config) = config.text_generation.as_mut() {
config.model_id = "mattshumer/reflection-70b:free".to_owned(); config.model_id = "mattshumer/reflection-70b:free".to_owned();
config.max_context_tokens = 8192; config.max_context_tokens = 8192;
config.max_response_tokens = 2048; config.max_response_tokens = Some(2048);
} }
config config

View File

@@ -14,7 +14,7 @@ pub fn default_config() -> Config {
if let Some(ref mut config) = config.text_generation.as_mut() { if let Some(ref mut config) = config.text_generation.as_mut() {
config.model_id = "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo".to_owned(); config.model_id = "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo".to_owned();
config.max_context_tokens = 8192; config.max_context_tokens = 8192;
config.max_response_tokens = 2048; config.max_response_tokens = Some(2048);
} }
config config

View File

@@ -172,6 +172,24 @@ impl Bot {
self.matrix_link().user_id() self.matrix_link().user_id()
} }
pub(crate) async fn user_display_name_in_room(&self, room: &Room) -> Option<String> {
let bot_display_name = self
.room_display_name_fetcher()
.own_display_name_in_room(room)
.await;
match bot_display_name {
Ok(value) => value,
Err(err) => {
tracing::warn!(
?err,
"Failed to fetch bot display name. Proceeding without it"
);
None
}
}
}
pub(crate) fn reacting(&self) -> super::reacting::Reacting { pub(crate) fn reacting(&self) -> super::reacting::Reacting {
super::reacting::Reacting::new(self.clone()) super::reacting::Reacting::new(self.clone())
} }

View File

@@ -239,8 +239,11 @@ impl Messaging {
} }
}; };
let bot_display_name = self.bot.user_display_name_in_room(&room).await;
let interaction_context = determine_interaction_context_for_room_event( let interaction_context = determine_interaction_context_for_room_event(
self.bot.user_id(), self.bot.user_id(),
&bot_display_name,
&room, &room,
&event, &event,
&payload, &payload,
@@ -279,7 +282,8 @@ impl Messaging {
self.bot.admin_pattern_regexes().clone(), self.bot.admin_pattern_regexes().clone(),
trigger_event_info, trigger_event_info,
interaction_context.thread_info.clone(), interaction_context.thread_info.clone(),
); )
.with_bot_display_name(bot_display_name);
let controller_type = crate::controller::determine_controller( let controller_type = crate::controller::determine_controller(
self.bot.command_prefix(), self.bot.command_prefix(),

View File

@@ -383,24 +383,10 @@ async fn handle_stage_text_generation(
_ => vec![], _ => vec![],
}; };
let bot_display_name = bot let bot_user_prefixes_to_strip = create_list_of_bot_user_prefixes_to_strip(
.room_display_name_fetcher() bot.user_id(),
.own_display_name_in_room(message_context.room()) message_context.bot_display_name(),
.await; );
let bot_display_name = match bot_display_name {
Ok(value) => value,
Err(err) => {
tracing::warn!(
?err,
"Failed to fetch bot display name. Proceeding without it"
);
None
}
};
let bot_user_prefixes_to_strip =
create_list_of_bot_user_prefixes_to_strip(bot.user_id(), &bot_display_name);
let allowed_users = match controller_type { let allowed_users = match controller_type {
// Regular chat completion only operates on messages from allowed users. // Regular chat completion only operates on messages from allowed users.
@@ -492,6 +478,7 @@ async fn handle_stage_text_generation(
.text_generation_model_id() .text_generation_model_id()
.unwrap_or("unknown-model".to_owned()), .unwrap_or("unknown-model".to_owned()),
chrono::Utc::now(), chrono::Utc::now(),
conversation.start_time(),
); );
let params = TextGenerationParams { let params = TextGenerationParams {

View File

@@ -46,6 +46,8 @@ mod tests {
#[test] #[test]
fn test_build_prompt() { fn test_build_prompt() {
let timestamp = chrono::Utc::now();
let test_cases = vec![ let test_cases = vec![
// Simple case // Simple case
TestCase { TestCase {
@@ -59,6 +61,7 @@ mod tests {
messages: vec![Message { messages: vec![Message {
author: Author::User, author: Author::User,
message_text: "Must be blue".to_owned(), message_text: "Must be blue".to_owned(),
timestamp,
}], }],
expected_prompt: "Generate a picture of a dog\nOther criteria:\n- Must be blue", expected_prompt: "Generate a picture of a dog\nOther criteria:\n- Must be blue",
}, },
@@ -68,14 +71,17 @@ mod tests {
messages: vec![Message { messages: vec![Message {
author: Author::User, author: Author::User,
message_text: "Must be blue".to_owned(), message_text: "Must be blue".to_owned(),
timestamp,
}, },
Message { Message {
author: Author::Assistant, author: Author::Assistant,
message_text: "Whatever".to_owned(), message_text: "Whatever".to_owned(),
timestamp,
}, },
Message { Message {
author: Author::User, author: Author::User,
message_text: "Must be 3-legged.\nMust be flying.".to_owned(), message_text: "Must be 3-legged.\nMust be flying.".to_owned(),
timestamp,
}], }],
expected_prompt: "Generate a picture of an elephant\nOther criteria:\n- Must be blue\n- Must be 3-legged.. Must be flying.", expected_prompt: "Generate a picture of an elephant\nOther criteria:\n- Must be blue\n- Must be 3-legged.. Must be flying.",
}, },
@@ -85,18 +91,22 @@ mod tests {
messages: vec![Message { messages: vec![Message {
author: Author::User, author: Author::User,
message_text: "Must be blue".to_owned(), message_text: "Must be blue".to_owned(),
timestamp,
}, },
Message { Message {
author: Author::Assistant, author: Author::Assistant,
message_text: "Whatever".to_owned(), message_text: "Whatever".to_owned(),
timestamp,
}, },
Message { Message {
author: Author::User, author: Author::User,
message_text: "Again".to_owned(), message_text: "Again".to_owned(),
timestamp,
}, },
Message { Message {
author: Author::User, author: Author::User,
message_text: "again".to_owned(), message_text: "again".to_owned(),
timestamp,
}], }],
expected_prompt: "Generate a picture of a grizzly bear\nOther criteria:\n- Must be blue", expected_prompt: "Generate a picture of a grizzly bear\nOther criteria:\n- Must be blue",
}, },

View File

@@ -1,3 +1,5 @@
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Author { pub enum Author {
Prompt, Prompt,
@@ -9,6 +11,7 @@ pub enum Author {
pub struct Message { pub struct Message {
pub author: Author, pub author: Author,
pub message_text: String, pub message_text: String,
pub timestamp: DateTime<Utc>,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -52,39 +55,59 @@ impl Conversation {
messages: new_messages, messages: new_messages,
} }
} }
pub fn start_time(&self) -> Option<DateTime<Utc>> {
self.messages.first().map(|message| message.timestamp)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use chrono::{TimeZone, Utc};
#[test] #[test]
fn combine_consecutive_messages() { fn combine_consecutive_messages() {
let timestamp_1 = Utc.with_ymd_and_hms(2024, 9, 20, 18, 34, 15).unwrap();
let timestamp_2 = Utc.with_ymd_and_hms(2024, 9, 21, 18, 34, 15).unwrap();
let timestamp_3 = Utc.with_ymd_and_hms(2024, 9, 22, 18, 34, 15).unwrap();
let conversation = Conversation { let conversation = Conversation {
messages: vec![ messages: vec![
// User's turn
Message { Message {
author: Author::User, author: Author::User,
message_text: "Hello".to_string(), message_text: "Hello".to_string(),
timestamp: timestamp_1,
}, },
Message { Message {
author: Author::User, author: Author::User,
message_text: "How are you?".to_string(), message_text: "How are you?".to_string(),
timestamp: timestamp_2,
}, },
Message { Message {
author: Author::User, author: Author::User,
message_text: "I'm OK, btw.".to_string(), message_text: "I'm OK, btw.".to_string(),
timestamp: timestamp_3,
}, },
// Assistant's turn
Message { Message {
author: Author::Assistant, author: Author::Assistant,
message_text: "Hi there!".to_string(), message_text: "Hi there!".to_string(),
timestamp: timestamp_2,
}, },
Message { Message {
author: Author::Assistant, author: Author::Assistant,
message_text: "I'm doing well, thank you.".to_string(), message_text: "I'm doing well, thank you.".to_string(),
timestamp: timestamp_3,
}, },
// User's turn
Message { Message {
author: Author::User, author: Author::User,
message_text: "That's great!".to_string(), message_text: "That's great!".to_string(),
timestamp: timestamp_3,
}, },
], ],
}; };
@@ -97,12 +120,17 @@ mod tests {
conversation.messages[0].message_text, conversation.messages[0].message_text,
"Hello\nHow are you?\nI'm OK, btw." "Hello\nHow are you?\nI'm OK, btw."
); );
assert_eq!(conversation.messages[0].timestamp, timestamp_1);
assert_eq!(conversation.messages[1].author, Author::Assistant); assert_eq!(conversation.messages[1].author, Author::Assistant);
assert_eq!( assert_eq!(
conversation.messages[1].message_text, conversation.messages[1].message_text,
"Hi there!\nI'm doing well, thank you." "Hi there!\nI'm doing well, thank you."
); );
assert_eq!(conversation.messages[1].timestamp, timestamp_2);
assert_eq!(conversation.messages[2].author, Author::User); assert_eq!(conversation.messages[2].author, Author::User);
assert_eq!(conversation.messages[2].message_text, "That's great!"); assert_eq!(conversation.messages[2].message_text, "That's great!");
assert_eq!(conversation.messages[2].timestamp, timestamp_3);
} }
} }

View File

@@ -15,6 +15,7 @@ fn test_messages_by_the_bot_are_identified_correctly() {
message_type: super::super::matrix::MatrixMessageType::Text, message_type: super::super::matrix::MatrixMessageType::Text,
message_text: "Hello!".to_owned(), message_text: "Hello!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp: chrono::Utc::now(),
}; };
let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id).unwrap(); let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id).unwrap();
@@ -39,6 +40,7 @@ fn test_notice_messages_by_bot_with_speech_to_text_prefix_are_cleaned_up_and_con
message_type: super::super::matrix::MatrixMessageType::Notice, message_type: super::super::matrix::MatrixMessageType::Notice,
message_text, message_text,
mentioned_users: vec![], mentioned_users: vec![],
timestamp: chrono::Utc::now(),
}; };
let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id).unwrap(); let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id).unwrap();
@@ -62,6 +64,7 @@ fn test_notice_error_messages_by_bot_are_ignored() {
message_type: super::super::matrix::MatrixMessageType::Notice, message_type: super::super::matrix::MatrixMessageType::Notice,
message_text, message_text,
mentioned_users: vec![], mentioned_users: vec![],
timestamp: chrono::Utc::now(),
}; };
let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id); let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id);
@@ -86,6 +89,7 @@ fn test_other_notice_messages_by_the_bot_are_ignored() {
message_type: super::super::matrix::MatrixMessageType::Notice, message_type: super::super::matrix::MatrixMessageType::Notice,
message_text: message_text.to_owned(), message_text: message_text.to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp: chrono::Utc::now(),
}; };
let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id); let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id);

View File

@@ -16,7 +16,7 @@ pub fn shorten_messages_list_to_context_size(
model: &str, model: &str,
prompt_message: &Option<Message>, prompt_message: &Option<Message>,
mut messages: Vec<Message>, mut messages: Vec<Message>,
max_response_tokens: u32, max_response_tokens: Option<u32>,
max_context_tokens: u32, max_context_tokens: u32,
) -> Vec<Message> { ) -> Vec<Message> {
// Loading the tokenization data is an expensive process, so // Loading the tokenization data is an expensive process, so
@@ -26,7 +26,8 @@ pub fn shorten_messages_list_to_context_size(
// We want to retain the prompt in all cases, so we always count it first. // We want to retain the prompt in all cases, so we always count it first.
// We also always reserve enough tokens for the maximum response we expect. // We also always reserve enough tokens for the maximum response we expect.
let mut current_context_length: u32 = if let Some(prompt_message) = prompt_message { let mut current_context_length: u32 = if let Some(prompt_message) = prompt_message {
calculate_token_size_for_message(&bpe, model, prompt_message) + max_response_tokens calculate_token_size_for_message(&bpe, model, prompt_message)
+ max_response_tokens.unwrap_or(0)
} else { } else {
0 0
}; };
@@ -85,6 +86,7 @@ pub mod test {
let message = super::Message { let message = super::Message {
author: super::Author::User, author: super::Author::User,
message_text: "Hello there!".to_owned(), message_text: "Hello there!".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let tokens = super::calculate_token_size_for_message(&bpe, model, &message); let tokens = super::calculate_token_size_for_message(&bpe, model, &message);
@@ -98,11 +100,12 @@ pub mod test {
let bpe = super::get_bpe_for_model(model); let bpe = super::get_bpe_for_model(model);
let max_response_tokens: u32 = 5; let max_response_tokens: Option<u32> = Some(5);
let prompt = super::Message { let prompt = super::Message {
author: super::Author::Prompt, author: super::Author::Prompt,
message_text: "You are a bot!".to_owned(), message_text: "You are a bot!".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let prompt_length = 10; let prompt_length = 10;
@@ -116,6 +119,7 @@ pub mod test {
let first = super::Message { let first = super::Message {
author: super::Author::User, author: super::Author::User,
message_text: "Hello there!".to_owned(), message_text: "Hello there!".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let first_length = 8; let first_length = 8;
@@ -129,6 +133,7 @@ pub mod test {
let second = super::Message { let second = super::Message {
author: super::Author::Assistant, author: super::Author::Assistant,
message_text: "Hello!".to_owned(), message_text: "Hello!".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let second_length = 7; let second_length = 7;
@@ -143,6 +148,7 @@ pub mod test {
author: super::Author::User, author: super::Author::User,
message_text: "This is the 3rd message in this conversation. It shall be preserved." message_text: "This is the 3rd message in this conversation. It shall be preserved."
.to_owned(), .to_owned(),
timestamp: chrono::Utc::now(),
}; };
let third_length = 21; let third_length = 21;
@@ -156,6 +162,7 @@ pub mod test {
let forth = super::Message { let forth = super::Message {
author: super::Author::Assistant, author: super::Author::Assistant,
message_text: "This is yet another message that shall be preserved.".to_owned(), message_text: "This is yet another message that shall be preserved.".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let forth_length = 15; let forth_length = 15;
@@ -173,7 +180,7 @@ pub mod test {
&Some(prompt), &Some(prompt),
conversation_messages, conversation_messages,
max_response_tokens, max_response_tokens,
prompt_length + max_response_tokens + forth_length + third_length, prompt_length + max_response_tokens.unwrap_or(0) + forth_length + third_length,
); );
assert_eq!(2, new_conversation_messages.len()); assert_eq!(2, new_conversation_messages.len());
@@ -195,11 +202,12 @@ pub mod test {
let bpe = super::get_bpe_for_model(model); let bpe = super::get_bpe_for_model(model);
let max_response_tokens: u32 = 5; let max_response_tokens: Option<u32> = Some(5);
let prompt = super::Message { let prompt = super::Message {
author: super::Author::User, author: super::Author::User,
message_text: "あなたはボットです。".to_owned(), message_text: "あなたはボットです。".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let prompt_length = 14; let prompt_length = 14;
@@ -213,6 +221,7 @@ pub mod test {
let first = super::Message { let first = super::Message {
author: super::Author::User, author: super::Author::User,
message_text: "こんにちは!".to_owned(), message_text: "こんにちは!".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let first_length = 7; let first_length = 7;
@@ -226,6 +235,7 @@ pub mod test {
let second = super::Message { let second = super::Message {
author: super::Author::Assistant, author: super::Author::Assistant,
message_text: "こんにちは。今日は元気ですか。".to_owned(), message_text: "こんにちは。今日は元気ですか。".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let second_length = 15; let second_length = 15;
@@ -239,6 +249,7 @@ pub mod test {
let third = super::Message { let third = super::Message {
author: super::Author::User, author: super::Author::User,
message_text: "これは第3のメッセージなので、保存されます。".to_owned(), message_text: "これは第3のメッセージなので、保存されます。".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let third_length = 22; let third_length = 22;
@@ -252,6 +263,7 @@ pub mod test {
let forth = super::Message { let forth = super::Message {
author: super::Author::Assistant, author: super::Author::Assistant,
message_text: "これはもう一つの保存されますメッセージです。".to_owned(), message_text: "これはもう一つの保存されますメッセージです。".to_owned(),
timestamp: chrono::Utc::now(),
}; };
let forth_length = 21; let forth_length = 21;
@@ -269,7 +281,7 @@ pub mod test {
&Some(prompt), &Some(prompt),
conversation_messages, conversation_messages,
max_response_tokens, max_response_tokens,
prompt_length + max_response_tokens + forth_length + third_length, prompt_length + max_response_tokens.unwrap_or(0) + forth_length + third_length,
); );
assert_eq!(2, new_conversation_messages.len()); assert_eq!(2, new_conversation_messages.len());

View File

@@ -17,19 +17,30 @@ pub fn convert_matrix_message_to_llm_message(
fn convert_bot_message(matrix_message: &MatrixMessage) -> Option<Message> { fn convert_bot_message(matrix_message: &MatrixMessage) -> Option<Message> {
match matrix_message.message_type { match matrix_message.message_type {
MatrixMessageType::Text => convert_bot_text_message(&matrix_message.message_text), MatrixMessageType::Text => {
MatrixMessageType::Notice => convert_bot_notice_message(&matrix_message.message_text), convert_bot_text_message(&matrix_message.message_text, &matrix_message.timestamp)
}
MatrixMessageType::Notice => {
convert_bot_notice_message(&matrix_message.message_text, &matrix_message.timestamp)
}
} }
} }
fn convert_bot_text_message(text: &str) -> Option<Message> { fn convert_bot_text_message(
text: &str,
timestamp: &chrono::DateTime<chrono::Utc>,
) -> Option<Message> {
Some(Message { Some(Message {
author: Author::Assistant, author: Author::Assistant,
message_text: text.to_owned(), message_text: text.to_owned(),
timestamp: timestamp.to_owned(),
}) })
} }
fn convert_bot_notice_message(text: &str) -> Option<Message> { fn convert_bot_notice_message(
text: &str,
timestamp: &chrono::DateTime<chrono::Utc>,
) -> Option<Message> {
// Notice messages sent by the bot are usually transcriptions of previous messages sent by the user. // Notice messages sent by the bot are usually transcriptions of previous messages sent by the user.
// Such transcriptions are prefixed with an emoji and blockquoted. // Such transcriptions are prefixed with an emoji and blockquoted.
// If we find a notice that doesn't match this pattern, we skip it. // If we find a notice that doesn't match this pattern, we skip it.
@@ -42,6 +53,7 @@ fn convert_bot_notice_message(text: &str) -> Option<Message> {
return Some(Message { return Some(Message {
author: Author::User, author: Author::User,
message_text: text.to_owned(), message_text: text.to_owned(),
timestamp: timestamp.to_owned(),
}); });
} }
@@ -52,5 +64,6 @@ fn convert_user_message(matrix_message: &MatrixMessage) -> Option<Message> {
Some(Message { Some(Message {
author: Author::User, author: Author::User,
message_text: matrix_message.message_text.clone(), message_text: matrix_message.message_text.clone(),
timestamp: matrix_message.timestamp.to_owned(),
}) })
} }

View File

@@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use regex::Regex; use regex::Regex;
use mxlink::matrix_sdk::ruma::OwnedUserId; use mxlink::matrix_sdk::ruma::OwnedUserId;
@@ -8,6 +9,7 @@ pub struct MatrixMessage {
pub message_type: MatrixMessageType, pub message_type: MatrixMessageType,
pub message_text: String, pub message_text: String,
pub mentioned_users: Vec<OwnedUserId>, pub mentioned_users: Vec<OwnedUserId>,
pub timestamp: DateTime<Utc>,
} }
#[derive(Clone)] #[derive(Clone)]

View File

@@ -9,7 +9,8 @@ use mxlink::matrix_sdk::{
ruma::events::{ ruma::events::{
relation::Thread, relation::Thread,
room::message::{ room::message::{
MessageType, OriginalSyncRoomMessageEvent, Relation, RoomMessageEventContent, sanitize::remove_plain_reply_fallback, MessageType, OriginalSyncRoomMessageEvent,
Relation, RoomMessageEventContent,
}, },
AnyMessageLikeEvent, AnyMessageLikeEventContent, AnyTimelineEvent, MessageLikeEvent, AnyMessageLikeEvent, AnyMessageLikeEventContent, AnyTimelineEvent, MessageLikeEvent,
}, },
@@ -229,11 +230,18 @@ pub fn convert_matrix_native_event_to_matrix_message(
let text = if is_reply { let text = if is_reply {
// For regular replies, we need to strip the fallback-for-rich replies part. // For regular replies, we need to strip the fallback-for-rich replies part.
// See: https://spec.matrix.org/v1.11/client-server-api/#fallbacks-for-rich-replies // See: https://spec.matrix.org/v1.11/client-server-api/#fallbacks-for-rich-replies
strip_rich_reply_fallback_text(&text) remove_plain_reply_fallback(&text).to_owned()
} else { } else {
text text
}; };
let timestamp = chrono::DateTime::<chrono::Utc>::from(
matrix_native_event
.origin_server_ts()
.to_system_time()
.unwrap_or_else(std::time::SystemTime::now),
);
let mentioned_users = room_message let mentioned_users = room_message
.mentions .mentions
.map(|m| m.user_ids.iter().map(|u| u.to_owned()).collect()) .map(|m| m.user_ids.iter().map(|u| u.to_owned()).collect())
@@ -248,6 +256,7 @@ pub fn convert_matrix_native_event_to_matrix_message(
}, },
message_text: text, message_text: text,
mentioned_users, mentioned_users,
timestamp,
}) })
} }
@@ -268,13 +277,14 @@ pub fn convert_matrix_native_event_to_matrix_message(
#[tracing::instrument(name = "determine_interaction_context_for_room_event", skip_all, fields(room_id = room.room_id().as_str(), event_id = current_event.event_id.as_str()))] #[tracing::instrument(name = "determine_interaction_context_for_room_event", skip_all, fields(room_id = room.room_id().as_str(), event_id = current_event.event_id.as_str()))]
pub async fn determine_interaction_context_for_room_event( pub async fn determine_interaction_context_for_room_event(
bot_user_id: &OwnedUserId, bot_user_id: &OwnedUserId,
bot_display_name: &Option<String>,
room: &Room, room: &Room,
current_event: &OriginalSyncRoomMessageEvent, current_event: &OriginalSyncRoomMessageEvent,
current_event_payload: &MessagePayload, current_event_payload: &MessagePayload,
event_fetcher: &Arc<RoomEventFetcher>, event_fetcher: &Arc<RoomEventFetcher>,
) -> anyhow::Result<Option<InteractionContext>> { ) -> anyhow::Result<Option<InteractionContext>> {
let current_event_is_mentioning_bot = let current_event_is_mentioning_bot =
is_event_mentioning_bot(&current_event.content, bot_user_id); is_event_mentioning_bot(&current_event.content, bot_user_id, bot_display_name);
let Some(relation) = &current_event.content.relates_to else { let Some(relation) = &current_event.content.relates_to else {
// This is a top-level message. We consider it the start of the thread. // This is a top-level message. We consider it the start of the thread.
@@ -296,6 +306,7 @@ pub async fn determine_interaction_context_for_room_event(
Relation::Thread(thread) => { Relation::Thread(thread) => {
determine_interaction_context_for_room_event_related_to_thread( determine_interaction_context_for_room_event_related_to_thread(
bot_user_id, bot_user_id,
bot_display_name,
room, room,
current_event, current_event,
event_fetcher, event_fetcher,
@@ -320,6 +331,7 @@ pub async fn determine_interaction_context_for_room_event(
async fn determine_interaction_context_for_room_event_related_to_thread( async fn determine_interaction_context_for_room_event_related_to_thread(
bot_user_id: &OwnedUserId, bot_user_id: &OwnedUserId,
bot_display_name: &Option<String>,
room: &Room, room: &Room,
current_event: &OriginalSyncRoomMessageEvent, current_event: &OriginalSyncRoomMessageEvent,
event_fetcher: &Arc<RoomEventFetcher>, event_fetcher: &Arc<RoomEventFetcher>,
@@ -380,6 +392,7 @@ async fn determine_interaction_context_for_room_event_related_to_thread(
thread_start_timeline_event, thread_start_timeline_event,
thread_info.clone(), thread_info.clone(),
bot_user_id, bot_user_id,
bot_display_name,
)?; )?;
let Some(detailed_message_payload) = thread_start_detailed_message_payload else { let Some(detailed_message_payload) = thread_start_detailed_message_payload else {
@@ -422,45 +435,33 @@ async fn determine_interaction_context_for_room_event_related_to_reply(
fn is_event_mentioning_bot( fn is_event_mentioning_bot(
event_content: &RoomMessageEventContent, event_content: &RoomMessageEventContent,
bot_user_id: &OwnedUserId, bot_user_id: &OwnedUserId,
bot_display_name: &Option<String>,
) -> bool { ) -> bool {
// As a fallback, we used to do string matching (`event_content.body().contains(bot_user_id.as_str())`) here as well. if let Some(mentions) = &event_content.mentions {
// However, this is unreliable. In 2024+, clients that do not have proper mentions support should get fixed, mentions
// instead of us having to deal with the possibility of false positives. .user_ids
// .iter()
let Some(mentions) = &event_content.mentions else { .any(|user_id| user_id == bot_user_id)
return false; } else {
}; // For compatibility with clients that do not support the new Mentions specification
// (see https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions),
mentions // we also do string matching here.
.user_ids //
.iter() // As of 2024-10-03, at least Element iOS does not support the new Mentions specification
.any(|user_id| user_id == bot_user_id) // and is still quite widespread.
} //
// We may consider dropping this string-matching behavior altogether in the future,
/// Strips the rich reply fallback text from the given text. // so improving this compatibility block is not a high priority.
/// See: https://spec.matrix.org/v1.11/client-server-api/#fallbacks-for-rich-replies if event_content.body().contains(bot_user_id.as_str()) {
/// return true;
/// Example:
/// ```rust,ignore
/// let text = "> <@admin:example.com> What's the difference between Matrix and XMPP?\n\nAnswer me";
/// let stripped_text = strip_rich_reply_fallback_text(text);
/// assert_eq!(stripped_text, "Answer me");
/// ```
fn strip_rich_reply_fallback_text(text: &str) -> String {
let lines = text.lines();
let mut stripped_lines = Vec::new();
let mut encountered_non_prefix = false;
for line in lines {
if !encountered_non_prefix && line.starts_with("> ") {
continue;
} else {
encountered_non_prefix = true;
stripped_lines.push(line);
} }
}
stripped_lines.join("\n").trim().to_owned() if let Some(bot_display_name) = bot_display_name {
return event_content.body().contains(bot_display_name);
}
false
}
} }
fn timeline_event_to_detailed_message_payload( fn timeline_event_to_detailed_message_payload(
@@ -468,6 +469,7 @@ fn timeline_event_to_detailed_message_payload(
timeline_event: TimelineEvent, timeline_event: TimelineEvent,
thread_info: ThreadInfo, thread_info: ThreadInfo,
bot_user_id: &OwnedUserId, bot_user_id: &OwnedUserId,
bot_display_name: &Option<String>,
) -> anyhow::Result<Option<DetailedMessagePayload>> { ) -> anyhow::Result<Option<DetailedMessagePayload>> {
let timeline_event_deserialized = match timeline_event.event.deserialize() { let timeline_event_deserialized = match timeline_event.event.deserialize() {
Ok(value) => value, Ok(value) => value,
@@ -519,8 +521,11 @@ fn timeline_event_to_detailed_message_payload(
return Ok(None); return Ok(None);
}; };
let is_mentioning_bot = let is_mentioning_bot = is_event_mentioning_bot(
is_event_mentioning_bot(&room_message_original.content, bot_user_id); &room_message_original.content,
bot_user_id,
bot_display_name,
);
(is_mentioning_bot, room_message_payload) (is_mentioning_bot, room_message_payload)
} else { } else {

View File

@@ -1,3 +1,5 @@
use chrono::{TimeZone, Utc};
use mxlink::matrix_sdk::ruma::OwnedUserId; use mxlink::matrix_sdk::ruma::OwnedUserId;
use crate::conversation::matrix::{ use crate::conversation::matrix::{
@@ -11,11 +13,14 @@ fn is_message_from_allowed_sender() {
let allowed_user_id = OwnedUserId::try_from("@user.someone:example.com").unwrap(); let allowed_user_id = OwnedUserId::try_from("@user.someone:example.com").unwrap();
let unallowed_user_id = OwnedUserId::try_from("@another:example.com").unwrap(); let unallowed_user_id = OwnedUserId::try_from("@another:example.com").unwrap();
let timestamp = Utc.with_ymd_and_hms(2024, 9, 20, 18, 34, 15).unwrap();
let bot_message = MatrixMessage { let bot_message = MatrixMessage {
sender_id: bot_user_id.to_owned(), sender_id: bot_user_id.to_owned(),
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "Hello!".to_owned(), message_text: "Hello!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let allowed_user_message = MatrixMessage { let allowed_user_message = MatrixMessage {
@@ -23,6 +28,7 @@ fn is_message_from_allowed_sender() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "Hello!".to_owned(), message_text: "Hello!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let unallowed_user_message = MatrixMessage { let unallowed_user_message = MatrixMessage {
@@ -30,6 +36,7 @@ fn is_message_from_allowed_sender() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "Hello!".to_owned(), message_text: "Hello!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let parsed_regex = match mxidwc::parse_pattern("@user.*:example.com") { let parsed_regex = match mxidwc::parse_pattern("@user.*:example.com") {
@@ -77,11 +84,14 @@ async fn process_matrix_messages() {
let allowed_user_id = OwnedUserId::try_from("@user.someone:example.com").unwrap(); let allowed_user_id = OwnedUserId::try_from("@user.someone:example.com").unwrap();
let unallowed_user_id = OwnedUserId::try_from("@another:example.com").unwrap(); let unallowed_user_id = OwnedUserId::try_from("@another:example.com").unwrap();
let timestamp = Utc.with_ymd_and_hms(2024, 9, 20, 18, 34, 15).unwrap();
let allowed_user_message = MatrixMessage { let allowed_user_message = MatrixMessage {
sender_id: allowed_user_id.to_owned(), sender_id: allowed_user_id.to_owned(),
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "Hello from the user!".to_owned(), message_text: "Hello from the user!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let allowed_user_message_with_prefix = MatrixMessage { let allowed_user_message_with_prefix = MatrixMessage {
@@ -89,6 +99,7 @@ async fn process_matrix_messages() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "!bai Hello from the user!".to_owned(), message_text: "!bai Hello from the user!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let allowed_user_message_with_prefix_no_space = MatrixMessage { let allowed_user_message_with_prefix_no_space = MatrixMessage {
@@ -96,6 +107,7 @@ async fn process_matrix_messages() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "!baiHello from the user!".to_owned(), message_text: "!baiHello from the user!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let allowed_user_message_with_prefix_full_width_space = MatrixMessage { let allowed_user_message_with_prefix_full_width_space = MatrixMessage {
@@ -103,6 +115,7 @@ async fn process_matrix_messages() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "!bai Hello from the user!".to_owned(), message_text: "!bai Hello from the user!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let bot_message = MatrixMessage { let bot_message = MatrixMessage {
@@ -110,6 +123,7 @@ async fn process_matrix_messages() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "Hello from the bot!".to_owned(), message_text: "Hello from the bot!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let allowed_user_message_with_bot_mention = MatrixMessage { let allowed_user_message_with_bot_mention = MatrixMessage {
@@ -117,6 +131,7 @@ async fn process_matrix_messages() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "@baibot: Hello from the user!".to_owned(), message_text: "@baibot: Hello from the user!".to_owned(),
mentioned_users: vec![bot_user_id.to_owned()], mentioned_users: vec![bot_user_id.to_owned()],
timestamp,
}; };
// The message text is the same as above - it mentions the bot, but the actually-mentioned user is another user. // The message text is the same as above - it mentions the bot, but the actually-mentioned user is another user.
@@ -125,6 +140,7 @@ async fn process_matrix_messages() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: allowed_user_message_with_bot_mention.message_text.clone(), message_text: allowed_user_message_with_bot_mention.message_text.clone(),
mentioned_users: vec![allowed_user_id.to_owned()], mentioned_users: vec![allowed_user_id.to_owned()],
timestamp,
}; };
let unallowed_user_message = MatrixMessage { let unallowed_user_message = MatrixMessage {
@@ -132,6 +148,7 @@ async fn process_matrix_messages() {
message_type: MatrixMessageType::Text, message_type: MatrixMessageType::Text,
message_text: "Hello from an unallowed user!".to_owned(), message_text: "Hello from an unallowed user!".to_owned(),
mentioned_users: vec![], mentioned_users: vec![],
timestamp,
}; };
let parsed_regex = match mxidwc::parse_pattern("@user.*:example.com") { let parsed_regex = match mxidwc::parse_pattern("@user.*:example.com") {
@@ -279,13 +296,6 @@ async fn process_matrix_messages() {
} }
} }
#[test]
fn strip_rich_reply_fallback_text() {
let text = "> <@admin:example.com> What's the difference between Matrix and XMPP?\n\nAnswer me";
let stripped_text = super::strip_rich_reply_fallback_text(text);
assert_eq!(stripped_text, "Answer me");
}
#[test] #[test]
fn create_list_of_bot_user_prefixes_to_strip() { fn create_list_of_bot_user_prefixes_to_strip() {
let bot_user_id = let bot_user_id =

View File

@@ -15,6 +15,8 @@ pub struct MessageContext {
admin_whitelist_regexes: Vec<regex::Regex>, admin_whitelist_regexes: Vec<regex::Regex>,
trigger_event_info: TriggerEventInfo, trigger_event_info: TriggerEventInfo,
thread_info: ThreadInfo, thread_info: ThreadInfo,
bot_display_name: Option<String>,
} }
impl MessageContext { impl MessageContext {
@@ -31,9 +33,20 @@ impl MessageContext {
admin_whitelist_regexes, admin_whitelist_regexes,
trigger_event_info, trigger_event_info,
thread_info, thread_info,
bot_display_name: None,
} }
} }
pub fn with_bot_display_name(mut self, value: Option<String>) -> Self {
self.bot_display_name = value;
self
}
pub fn bot_display_name(&self) -> &Option<String> {
&self.bot_display_name
}
pub fn room(&self) -> &Room { pub fn room(&self) -> &Room {
&self.room &self.room
} }