from __future__ import annotations import argparse from pathlib import Path from agent import CoreAgent from core_agent.config import apply_compat_env_aliases, build_core_agent_config, load_core_agent_env, require_model_config from providers.openai_compatible import OpenAICompatibleProvider from providers.rule_based import RuleBasedMeetingProvider from tools.default_tools import build_default_registry class TerminalRenderer: def __init__(self, assistant_name: str) -> None: self.assistant_name = assistant_name def render_event(self, event) -> None: if event.type == "round_start": print(f"\n[step {event.iteration}/{event.max_iterations}]", end="") elif event.type == "tool_call": print(f"\n[tool] {event.tool_name} {event.tool_args}") elif event.type == "tool_result": print(f"[tool-result] {_compact(event.tool_result)}") elif event.type == "final": print(f"\n{self.assistant_name}> {event.final_response}") def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Meeting memory agent CLI.") parser.add_argument("--data-dir", default="data", help="会议记忆与原文归档保存目录。") parser.add_argument("--once", default="", help="只执行一条消息,适合测试。") parser.add_argument("--offline", action="store_true", help="只用于本地演示:强制使用离线规则 provider,不调用大模型。") parser.add_argument("--list-models", action="store_true", help="列出当前 OpenAI-compatible 服务暴露的模型名。") return parser def main() -> None: args = build_parser().parse_args() workspace = Path.cwd() load_core_agent_env(workspace) apply_compat_env_aliases() config = build_core_agent_config() if args.list_models: _list_models(config) return try: provider = _build_provider(config, force_offline=args.offline) except RuntimeError as exc: print(f"配置错误:{exc}") return registry = build_default_registry(workspace / args.data_dir) agent = CoreAgent( provider=provider, workspace=workspace, skill_dirs=[workspace / "skills"], tool_registry=registry, max_iterations=config.max_iterations, ) session = agent.new_session(session_id="cli_default") renderer = TerminalRenderer(config.agent_name) if args.once: for event in session.stream_ask(args.once): renderer.render_event(event) return print("Meeting Memory Interactive Agent") print(r"示例:导入 D:\github_project\my_code\meeting_agent\examples\huiyi.txt") print("示例:最近一次会议提到了哪些待办?") print("输入 exit / quit 退出。") while True: try: user_message = input("\n你> ").strip() except (EOFError, KeyboardInterrupt): print() break if user_message.lower() in {"exit", "quit", "q"}: break if not user_message: continue for event in session.stream_ask(user_message): renderer.render_event(event) def _build_provider(config, *, force_offline: bool): if force_offline: return RuleBasedMeetingProvider() require_model_config(config) return OpenAICompatibleProvider( model=config.model, api_key=config.api_key, base_url=config.base_url, timeout=config.timeout, temperature=config.temperature, ) def _list_models(config) -> None: from openai import OpenAI if not config.api_key or not config.base_url: print("配置错误:列出模型需要 OPENAI_API_KEY 和 OPENAI_BASE_URL。") return try: client = OpenAI(api_key=config.api_key, base_url=config.base_url, timeout=config.timeout) models = client.models.list() except Exception as exc: print(f"获取模型列表失败:{exc}") return print(f"OPENAI_BASE_URL={config.base_url}") print("可用模型:") for model in models.data: print(f"- {model.id}") def _compact(text: str, limit: int = 240) -> str: text = " ".join(text.split()) return text if len(text) <= limit else text[:limit] + "..." if __name__ == "__main__": main()