"""Command line entry point. python -m store_assistant init-db [--reset] create the database from the catalog file python -m store_assistant chat talk to the assistant in the terminal python -m store_assistant bot run the Telegram bot """ from __future__ import annotations import argparse import asyncio import logging import sys from openai import AsyncOpenAI from .assistant import StoreAssistant from .config import ConfigError, Settings from .memory import ConversationMemory from .prompts import build_system_prompt from .store import Store, open_store from .tools import ToolExecutor CLI_CHAT_ID = 0 def build_assistant(settings: Settings, store: Store) -> tuple[StoreAssistant, str]: store_name = store.store_info().get("name", "") client = AsyncOpenAI( api_key=settings.devup_api_key, base_url=settings.devup_base_url, timeout=settings.request_timeout_s, max_retries=2, ) assistant = StoreAssistant( client=client, model=settings.model, executor=ToolExecutor(store), memory=ConversationMemory(settings.history_messages), system_prompt=build_system_prompt(store_name), max_tool_rounds=settings.max_tool_rounds, max_output_tokens=settings.max_output_tokens, ) return assistant, store_name def cmd_init_db(settings: Settings, reset: bool) -> int: settings.db_path.parent.mkdir(parents=True, exist_ok=True) store = Store(settings.db_path) try: if reset or store.is_empty(): store.load_catalog(settings.catalog_path, replace=reset) print(f"Database ready: {settings.db_path}") else: print(f"Database already has data: {settings.db_path} (use --reset to reload the catalog)") finally: store.close() return 0 async def _chat_loop(assistant: StoreAssistant, store_name: str, show_tools: bool) -> None: print(f"{store_name or 'Store'} assistant. Type /reset for a new conversation, /quit to exit.\n") while True: try: text = (await asyncio.to_thread(input, "you> ")).strip() except (EOFError, KeyboardInterrupt): print() return if not text: continue if text in {"/quit", "/exit"}: return if text == "/reset": assistant.reset(CLI_CHAT_ID) print("(new conversation)\n") continue reply = await assistant.reply(CLI_CHAT_ID, text, username="terminal") if show_tools and reply.tools_used: print(f" [tools: {', '.join(reply.tools_used)}]") print(f"bot> {reply.text}\n") def cmd_chat(settings: Settings, show_tools: bool) -> int: store = open_store(settings.db_path, settings.catalog_path) try: assistant, store_name = build_assistant(settings, store) asyncio.run(_chat_loop(assistant, store_name, show_tools)) finally: store.close() return 0 def cmd_bot(settings: Settings) -> int: from .bot import SupportBot # imported here so the other commands work without Telegram setup store = open_store(settings.db_path, settings.catalog_path) try: assistant, store_name = build_assistant(settings, store) app = SupportBot(settings, assistant, store_name).build_application() logging.getLogger(__name__).info("bot started with model %s", settings.model) app.run_polling(allowed_updates=["message"], drop_pending_updates=True) finally: store.close() return 0 def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(prog="store_assistant", description="AI customer support assistant") parser.add_argument("-v", "--verbose", action="store_true", help="debug logging") sub = parser.add_subparsers(dest="command", required=True) init_db = sub.add_parser("init-db", help="create the database from the catalog file") init_db.add_argument("--reset", action="store_true", help="delete existing data and reload the catalog") chat = sub.add_parser("chat", help="talk to the assistant in the terminal") chat.add_argument("--show-tools", action="store_true", help="print which tools each reply used") sub.add_parser("bot", help="run the Telegram bot") args = parser.parse_args(argv) logging.basicConfig( level=logging.DEBUG if args.verbose else logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) # The HTTP clients log every request URL at INFO, and Telegram URLs contain the bot token. for noisy in ("httpx", "httpx2"): logging.getLogger(noisy).setLevel(logging.WARNING) if args.command == "chat" and not args.verbose: logging.getLogger().setLevel(logging.WARNING) try: if args.command == "init-db": return cmd_init_db(Settings.from_env(require_api_key=False), args.reset) settings = Settings.from_env(require_telegram=args.command == "bot") except ConfigError as exc: print(f"Configuration error: {exc}", file=sys.stderr) return 2 if args.command == "chat": return cmd_chat(settings, args.show_tools) return cmd_bot(settings) if __name__ == "__main__": sys.exit(main())