From a7d486a6237f2f170837b9d8887ece77ee0ce9a3 Mon Sep 17 00:00:00 2001 From: Sebastien Tosello Date: Thu, 10 Sep 2026 17:02:46 +0200 Subject: [PATCH] SEARCH-3102 : Add semantic search activity example Demonstrates MessageService.search_messages_semantic (SEARCH-3102) wired up as a real activity, handling a free-text "/search " command. Written as a plain CommandActivity rather than activities.slash("/search {query}") because a slash-command {argument} only binds a single whitespace-delimited token and can't capture a multi-word query. Co-Authored-By: Claude Sonnet 5 --- .../activities/semantic_search_activity.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 examples/activities/semantic_search_activity.py diff --git a/examples/activities/semantic_search_activity.py b/examples/activities/semantic_search_activity.py new file mode 100644 index 00000000..d60e6777 --- /dev/null +++ b/examples/activities/semantic_search_activity.py @@ -0,0 +1,63 @@ +import asyncio +import html +import logging.config +from pathlib import Path + +from symphony.bdk.core.activity.command import CommandActivity, CommandContext +from symphony.bdk.core.config.loader import BdkConfigLoader +from symphony.bdk.core.service.message.message_service import MessageService +from symphony.bdk.core.symphony_bdk import SymphonyBdk + + +async def run(): + async with SymphonyBdk(BdkConfigLoader.load_from_symphony_dir("config.yaml")) as bdk: + bdk.activities().register(SemanticSearchCommandActivity(bdk.messages())) + await bdk.datafeed().start() + + +class SemanticSearchCommandActivity(CommandActivity): + """Handles "/search " and replies with the matching messages found by + :func:`MessageService.search_messages_semantic`. + + This is written as a plain CommandActivity rather than with the activities.slash decorator + because a slash-command {argument} only ever binds a single whitespace-delimited token - it + can't capture a free-text, multi-word query like "/search green investments". + """ + + def __init__(self, messages: MessageService): + self._messages = messages + super().__init__() + + def matches(self, context: CommandContext) -> bool: + return context.text_content.startswith("/search ") + + async def on_activity(self, context: CommandContext): + query = context.text_content[len("/search ") :].strip() + if not query: + return + + results = await self._messages.search_messages_semantic(query, limit=5) + + if not results: + await self._messages.send_message( + context.stream_id, "No matching messages found." + ) + return + + rows = "".join( + f"{r.message_id}{html.escape(r.message or '')}" + for r in results + ) + table = f"{rows}
Message IDContent
" + await self._messages.send_message(context.stream_id, f"{table}") + + +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) + +try: + logging.info("Running activity example...") + asyncio.run(run()) +except KeyboardInterrupt: + logging.info("Ending activity example")