{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 映前 (YingQian) — 多智能体电影推荐演示\n", "\n", "基于 **HelloAgents** 的 Pipeline + Tool-use:\n", "\n", "1. **画像 Agent**(无工具)→ TasteProfile \n", "2. **检索 Agent**(TMDB Tool)→ 真实候选片 \n", "3. **推荐 Agent**(无工具)→ 白名单内精选 + 理由 \n", "\n", "## 使用说明\n", "\n", "1. 先配置 `backend/.env`(可从 `.env.example` 复制) \n", "2. 在项目根目录启动 Jupyter,按顺序运行本 Notebook \n", "3. 需能访问 TMDB 与你的 LLM API" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## 第 1 部分:环境准备" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import sys\n", "from pathlib import Path\n", "\n", "# 项目根目录 = 本 notebook 所在目录\n", "ROOT = Path.cwd().resolve()\n", "BACKEND = ROOT / \"backend\"\n", "assert (BACKEND / \"app\").exists(), f\"找不到 backend/app,请在项目根目录打开 notebook(当前: {ROOT}\"\n", "\n", "sys.path.insert(0, str(BACKEND))\n", "\n", "# 优先加载 backend/.env\n", "from dotenv import load_dotenv\n", "\n", "env_path = BACKEND / \".env\"\n", "if not env_path.exists():\n", " raise FileNotFoundError(\n", " f\"未找到 {env_path}\\n\"\n", " \"请执行: copy .env.example backend\\\\.env 并填入 TMDB / LLM 密钥\"\n", " )\n", "load_dotenv(env_path)\n", "\n", "print(\"ROOT :\", ROOT)\n", "print(\"BACKEND:\", BACKEND)\n", "print(\"TMDB :\", \"已配置\" if (os.getenv(\"TMDB_ACCESS_TOKEN\") or os.getenv(\"TMDB_API_KEY\")) else \"缺失\")\n", "print(\"LLM :\", \"已配置\" if os.getenv(\"LLM_API_KEY\") else \"缺失\")\n", "print(\"MODEL :\", os.getenv(\"LLM_MODEL_ID\") or \"(未设置)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## 第 2 部分:构造推荐请求" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from app.models.schemas import RecommendRequest\n", "\n", "request = RecommendRequest(\n", " mood=\"放松\",\n", " party_type=\"独自\",\n", " genres=[\"剧情\", \"喜剧\"],\n", " max_runtime_minutes=120,\n", " region_preference=\"不限\",\n", " year_preference=\"近10年\",\n", " exclude_titles=[],\n", " spoilers_ok=False,\n", " free_text=\"不要太沉重,适合周末晚上\",\n", " exclude_ids=[],\n", ")\n", "\n", "print(request.model_dump_json(indent=2, ensure_ascii=False))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## 第 3 部分:运行多智能体推荐流水线\n", "\n", "> 完整一次大约 40–60 秒,请耐心等待。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from app.agents.movie_recommender_agent import MultiAgentMovieRecommender\n", "\n", "recommender = MultiAgentMovieRecommender()\n", "result, trace_id = recommender.recommend(request)\n", "\n", "print(\"trace_id:\", trace_id)\n", "print(\"fallback:\", result.fallback)\n", "if result.taste_profile:\n", " print(\"画像摘要:\", result.taste_profile.summary)\n", " print(\"类型线索:\", result.taste_profile.genre_hints)\n", "print(f\"推荐数量: {len(result.movies)}\")\n", "print(\"=\" * 50)\n", "for i, m in enumerate(result.movies, 1):\n", " print(f\"{i}. {m.title} ({m.year or '?'}) 评分={m.rating}\")\n", " print(f\" 理由: {m.reason}\")\n", " print(f\" 海报: {m.poster_url}\")\n", " print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## 第 4 部分(可选):仅测 TMDB 连通性" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from app.services.movie_service import get_movie_service\n", "\n", "svc = get_movie_service()\n", "movies = svc.discover(with_genres=\"喜剧\", sort_by=\"popularity.desc\", page=1)\n", "print(f\"discover 返回 {len(movies)} 部,前 5 部:\")\n", "for m in movies[:5]:\n", " print(f\"- {m.id} | {m.title} | {m.year} | {m.rating}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## 总结\n", "\n", "- 流水线:画像 → 检索(TMDB Tool) → 推荐(id 白名单) \n", "- Web 形态:`backend` FastAPI + `frontend` React \n", "- 若 TMDB 连接超时(WinError 10060),请检查代理/VPN 后重试" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 }