Laura Wagner commited on
Commit
0b5c83f
·
1 Parent(s): 8f0bbd9

corrected llm code

Browse files
jupyter_notebooks/Section_2-3-4_Figure_8_deepfake_adapters.ipynb CHANGED
@@ -954,7 +954,168 @@
954
  "id": "qwen_code",
955
  "metadata": {},
956
  "outputs": [],
957
- "source": "import pandas as pd\nimport json\nimport time\nimport re\nfrom pathlib import Path\nfrom tqdm import tqdm\nfrom openai import OpenAI\n\ncurrent_dir = Path.cwd()\ninput_file = current_dir.parent / \"data/CSV/NER_POI_step02_annotated.csv\"\napi_key_file = current_dir.parent / \"misc/credentials/qwen_api_key.txt\"\n\n# === CONFIGURATION ===\nTEST_MODE = True\nTEST_SIZE = 10\nMAX_ROWS = 20000\nSAVE_INTERVAL = 10\n\nPROFESSION_CATEGORIES = [\n \"actor\", \"adult performer\", \"singer/musician\", \"model\",\n \"online personality\", \"public figure\", \"voice actor/ASMR\",\n \"sports professional\", \"tv personality\"\n]\n\n# === LOAD API KEY ===\ntry:\n with open(api_key_file) as f:\n api_key = f.read().strip()\n print(\"✅ Qwen API key loaded\")\n print(f\" Key starts with: {api_key[:10]}...\")\nexcept FileNotFoundError:\n print(\"❌ Error: qwen_api_key.txt not found\")\n raise\n\n# Initialize OpenAI client for Qwen (matching dashboard example)\nclient = OpenAI(\n api_key=api_key,\n base_url=\"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\"\n)\n\n# === LOAD DATA ===\ndf = pd.read_csv(input_file)\nprint(f\"Loaded {len(df)} rows\")\n\nif TEST_MODE:\n print(f\"Running in TEST MODE with {TEST_SIZE} samples\")\n df = df.head(TEST_SIZE).copy()\nelif MAX_ROWS:\n df = df.head(MAX_ROWS).copy()\n\n# === CREATE PROMPT ===\ndef create_prompt(name: str, tags: list, country: str) -> str:\n tags_str = \", \".join(tags[:5]) if tags else \"none\"\n country_str = country if country else \"unknown\"\n \n return f\"\"\"You are an expert at identifying public figures.\n\nGiven: Name: {name}, Tags: {tags_str}, Country: {country_str}\n\nProvide in JSON format:\n{{\n \"full_name\": \"<full name>\",\n \"gender\": \"<Male/Female/Other/Unknown>\",\n \"professions\": [\"<profession 1>\", \"<profession 2>\", \"<profession 3>\"],\n \"country\": \"<country>\"\n}}\n\nChoose professions ONLY from: {\", \".join(PROFESSION_CATEGORIES)}\n- Max 3 professions, ordered by relevance\n- \"online personality\" includes streamers, cosplayers, influencers\n- Return \"Unknown\" if uncertain\n\nReturn ONLY JSON, no extra text.\"\"\"\n\n# === QUERY QWEN ===\ndef query_qwen(prompt: str) -> dict:\n \"\"\"Query Qwen using OpenAI-compatible API (matching dashboard example).\"\"\"\n for attempt in range(3):\n try:\n completion = client.chat.completions.create(\n model=\"qwen-max\", # Note: dashboard shows \"qwen3-max\" but \"qwen-max\" works too\n messages=[\n {\"role\": \"user\", \"content\": prompt}\n ],\n temperature=0.1,\n max_tokens=500,\n stream=False # Disable streaming for easier parsing\n )\n \n content = completion.choices[0].message.content\n \n # Parse JSON from response\n json_match = re.search(r'\\{[^}]+\\}', content, re.DOTALL)\n if json_match:\n return json.loads(json_match.group())\n \n return None\n \n except Exception as e:\n print(f\"Qwen error (attempt {attempt+1}): {e}\")\n if attempt < 2:\n time.sleep(2 ** attempt)\n \n return None\n\n# === PROCESS ===\noutput_file = current_dir.parent / f\"data/CSV/qwen_annotated_POI{'_test' if TEST_MODE else ''}.csv\"\nindex_file = current_dir.parent / \"misc/qwen_query_index.txt\"\n\ncurrent_index = 0\nif index_file.exists():\n with open(index_file) as f:\n current_index = int(f.read().strip())\n\nfor col in ['full_name', 'gender', 'profession_llm', 'country']:\n if col not in df.columns:\n df[col] = 'Unknown'\n\nprint(f\"\\nAnnotating with QWEN-MAX (OpenAI-compatible API) - rows {current_index} to {len(df)}...\")\n\ntry:\n for i in tqdm(range(current_index, len(df)), desc=\"Qwen\"):\n row = df.iloc[i]\n \n tags = [str(row[f'tag_{j}']).lower() for j in range(1, 8)\n if f'tag_{j}' in row and pd.notna(row[f'tag_{j}'])]\n \n prompt = create_prompt(\n row.get('real_name', row.get('name', '')),\n tags,\n row.get('likely_country', '')\n )\n \n result = query_qwen(prompt)\n \n if result:\n df.at[i, 'full_name'] = result.get('full_name', 'Unknown')\n df.at[i, 'gender'] = result.get('gender', 'Unknown')\n profs = result.get('professions', [])\n df.at[i, 'profession_llm'] = \", \".join(profs[:3]) if isinstance(profs, list) else str(profs)\n df.at[i, 'country'] = result.get('country', 'Unknown')\n \n current_index = i + 1\n \n if (i + 1) % SAVE_INTERVAL == 0 or (i + 1) == len(df):\n df.to_csv(output_file, index=False)\n with open(index_file, 'w') as f:\n f.write(str(current_index))\n print(f\"✅ Saved after {i+1} rows\")\n \n time.sleep(0.5)\n \n print(f\"\\n✅ Done! Results: {output_file}\")\n print(f\"\\nTop professions:\\n{df['profession_llm'].value_counts().head(10)}\")\n \nexcept Exception as e:\n print(f\"⚠️ Error: {e}\")\n df.to_csv(output_file, index=False)\n with open(index_file, 'w') as f:\n f.write(str(current_index))"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
958
  },
959
  {
960
  "cell_type": "markdown",
@@ -963,47 +1124,361 @@
963
  "source": [
964
  "## Llama Annotation (Together AI)\n",
965
  "\n",
966
- "Uses **Llama-3.1-70B-Instruct-Turbo** via Together AI.\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
967
  "\n",
968
- "**Requirements:**\n",
969
- "- API Key: `misc/credentials/together_api_key.txt`\n",
970
- "- Sign up: https://www.together.ai/\n",
971
- "- Model: meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo\n",
972
- "- Cost: ~$0.90 per 1M tokens (~$5-10 for 10k entries)"
973
- ]
974
- },
975
- {
976
- "cell_type": "code",
977
- "execution_count": null,
978
- "id": "llama_code",
979
- "metadata": {},
980
- "outputs": [],
981
- "source": "import pandas as pd\nimport json\nimport time\nimport requests\nimport re\nfrom pathlib import Path\nfrom tqdm import tqdm\n\ncurrent_dir = Path.cwd()\ninput_file = current_dir.parent / \"data/CSV/NER_POI_step02_annotated.csv\"\napi_key_file = current_dir.parent / \"misc/credentials/together_api_key.txt\"\n\n# === CONFIGURATION ===\nTEST_MODE = True\nTEST_SIZE = 10\nMAX_ROWS = 20000\nSAVE_INTERVAL = 10\n\nPROFESSION_CATEGORIES = [\n \"actor\", \"adult performer\", \"singer/musician\", \"model\",\n \"online personality\", \"public figure\", \"voice actor/ASMR\",\n \"sports professional\", \"tv personality\"\n]\n\n# === LOAD API KEY ===\ntry:\n with open(api_key_file) as f:\n api_key = f.read().strip()\n print(\"✅ Llama API key loaded\")\nexcept FileNotFoundError:\n print(\"❌ Error: together_api_key.txt not found\")\n raise\n\n# === LOAD DATA ===\ndf = pd.read_csv(input_file)\nprint(f\"Loaded {len(df)} rows\")\n\nif TEST_MODE:\n print(f\"Running in TEST MODE with {TEST_SIZE} samples\")\n df = df.head(TEST_SIZE).copy()\nelif MAX_ROWS:\n df = df.head(MAX_ROWS).copy()\n\n# === CREATE PROMPT ===\ndef create_prompt(name: str, tags: list, country: str) -> str:\n tags_str = \", \".join(tags[:5]) if tags else \"none\"\n country_str = country if country else \"unknown\"\n \n return f\"\"\"You are an expert at identifying public figures.\n\nGiven: Name: {name}, Tags: {tags_str}, Country: {country_str}\n\nProvide in JSON format:\n{{\n \"full_name\": \"<full name>\",\n \"gender\": \"<Male/Female/Other/Unknown>\",\n \"professions\": [\"<profession 1>\", \"<profession 2>\", \"<profession 3>\"],\n \"country\": \"<country>\"\n}}\n\nChoose professions ONLY from: {\", \".join(PROFESSION_CATEGORIES)}\n- Max 3 professions, ordered by relevance\n- \"online personality\" includes streamers, cosplayers, influencers\n- Return \"Unknown\" if uncertain\n\nReturn ONLY JSON, no extra text.\"\"\"\n\n# === QUERY LLAMA ===\ndef query_llama(prompt: str) -> dict:\n url = \"https://api.together.xyz/v1/chat/completions\"\n \n headers = {\n \"Authorization\": f\"Bearer {api_key}\",\n \"Content-Type\": \"application/json\"\n }\n \n data = {\n \"model\": \"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo\",\n \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n \"temperature\": 0.1,\n \"max_tokens\": 500\n }\n \n for attempt in range(3):\n try:\n response = requests.post(url, headers=headers, json=data, timeout=30)\n response.raise_for_status()\n result = response.json()\n \n if 'choices' in result:\n content = result['choices'][0]['message']['content']\n json_match = re.search(r'\\{[^}]+\\}', content, re.DOTALL)\n if json_match:\n return json.loads(json_match.group())\n return None\n except Exception as e:\n print(f\"Llama error (attempt {attempt+1}): {e}\")\n if attempt < 2:\n time.sleep(2 ** attempt)\n return None\n\n# === PROCESS ===\noutput_file = current_dir.parent / f\"data/CSV/llama_annotated_POI{'_test' if TEST_MODE else ''}.csv\"\nindex_file = current_dir.parent / \"misc/llama_query_index.txt\"\n\ncurrent_index = 0\nif index_file.exists():\n with open(index_file) as f:\n current_index = int(f.read().strip())\n\nfor col in ['full_name', 'gender', 'profession_llm', 'country']:\n if col not in df.columns:\n df[col] = 'Unknown'\n\nprint(f\"\\nAnnotating with LLAMA 3.1 70B - rows {current_index} to {len(df)}...\")\n\ntry:\n for i in tqdm(range(current_index, len(df)), desc=\"Llama\"):\n row = df.iloc[i]\n \n tags = [str(row[f'tag_{j}']).lower() for j in range(1, 8)\n if f'tag_{j}' in row and pd.notna(row[f'tag_{j}'])]\n \n prompt = create_prompt(\n row.get('real_name', row.get('name', '')),\n tags,\n row.get('likely_country', '')\n )\n \n result = query_llama(prompt)\n \n if result:\n df.at[i, 'full_name'] = result.get('full_name', 'Unknown')\n df.at[i, 'gender'] = result.get('gender', 'Unknown')\n profs = result.get('professions', [])\n df.at[i, 'profession_llm'] = \", \".join(profs[:3]) if isinstance(profs, list) else str(profs)\n df.at[i, 'country'] = result.get('country', 'Unknown')\n \n current_index = i + 1\n \n if (i + 1) % SAVE_INTERVAL == 0 or (i + 1) == len(df):\n df.to_csv(output_file, index=False)\n with open(index_file, 'w') as f:\n f.write(str(current_index))\n print(f\"✅ Saved after {i+1} rows\")\n \n time.sleep(0.5)\n \n print(f\"\\n✅ Done! Results: {output_file}\")\n print(f\"\\nTop professions:\\n{df['profession_llm'].value_counts().head(10)}\")\n \nexcept Exception as e:\n print(f\"⚠️ Error: {e}\")\n df.to_csv(output_file, index=False)\n with open(index_file, 'w') as f:\n f.write(str(current_index))"
982
- },
983
- {
984
- "cell_type": "markdown",
985
- "id": "mistral_header",
986
- "metadata": {},
987
- "source": [
988
- "## Mixtral Annotation (Mistral AI)\n",
989
  "\n",
990
- "Uses **Open-Mixtral-8x22B** via Mistral AI API.\n",
991
  "\n",
992
- "**Requirements:**\n",
993
- "- API Key: `misc/credentials/mistral_api_key.txt`\n",
994
- "- Sign up: https://mistral.ai/\n",
995
- "- Model: open-mixtral-8x22b\n",
996
- "- Cost: ~$2 per 1M tokens (~$10-20 for 10k entries)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
997
  ]
998
  },
999
- {
1000
- "cell_type": "code",
1001
- "execution_count": null,
1002
- "id": "mixtral_code",
1003
- "metadata": {},
1004
- "outputs": [],
1005
- "source": "import pandas as pd\nimport json\nimport time\nimport requests\nimport re\nfrom pathlib import Path\nfrom tqdm import tqdm\n\ncurrent_dir = Path.cwd()\ninput_file = current_dir.parent / \"data/CSV/NER_POI_step02_annotated.csv\"\napi_key_file = current_dir.parent / \"misc/credentials/mistral_api_key.txt\"\n\n# === CONFIGURATION ===\nTEST_MODE = True\nTEST_SIZE = 10\nMAX_ROWS = 20000\nSAVE_INTERVAL = 10\n\nPROFESSION_CATEGORIES = [\n \"actor\", \"adult performer\", \"singer/musician\", \"model\",\n \"online personality\", \"public figure\", \"voice actor/ASMR\",\n \"sports professional\", \"tv personality\"\n]\n\n# === LOAD API KEY ===\ntry:\n with open(api_key_file) as f:\n api_key = f.read().strip()\n print(\"✅ Mixtral API key loaded\")\nexcept FileNotFoundError:\n print(\"❌ Error: mistral_api_key.txt not found\")\n raise\n\n# === LOAD DATA ===\ndf = pd.read_csv(input_file)\nprint(f\"Loaded {len(df)} rows\")\n\nif TEST_MODE:\n print(f\"Running in TEST MODE with {TEST_SIZE} samples\")\n df = df.head(TEST_SIZE).copy()\nelif MAX_ROWS:\n df = df.head(MAX_ROWS).copy()\n\n# === CREATE PROMPT ===\ndef create_prompt(name: str, tags: list, country: str) -> str:\n tags_str = \", \".join(tags[:5]) if tags else \"none\"\n country_str = country if country else \"unknown\"\n \n return f\"\"\"You are an expert at identifying public figures.\n\nGiven: Name: {name}, Tags: {tags_str}, Country: {country_str}\n\nProvide in JSON format:\n{{\n \"full_name\": \"<full name>\",\n \"gender\": \"<Male/Female/Other/Unknown>\",\n \"professions\": [\"<profession 1>\", \"<profession 2>\", \"<profession 3>\"],\n \"country\": \"<country>\"\n}}\n\nChoose professions ONLY from: {\", \".join(PROFESSION_CATEGORIES)}\n- Max 3 professions, ordered by relevance\n- \"online personality\" includes streamers, cosplayers, influencers\n- Return \"Unknown\" if uncertain\n\nReturn ONLY JSON, no extra text.\"\"\"\n\n# === QUERY MIXTRAL ===\ndef query_mixtral(prompt: str) -> dict:\n url = \"https://api.mistral.ai/v1/chat/completions\"\n \n headers = {\n \"Authorization\": f\"Bearer {api_key}\",\n \"Content-Type\": \"application/json\"\n }\n \n data = {\n \"model\": \"open-mixtral-8x22b\",\n \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n \"temperature\": 0.1,\n \"max_tokens\": 500\n }\n \n for attempt in range(3):\n try:\n response = requests.post(url, headers=headers, json=data, timeout=30)\n response.raise_for_status()\n result = response.json()\n \n if 'choices' in result:\n content = result['choices'][0]['message']['content']\n json_match = re.search(r'\\{[^}]+\\}', content, re.DOTALL)\n if json_match:\n return json.loads(json_match.group())\n return None\n except Exception as e:\n print(f\"Mixtral error (attempt {attempt+1}): {e}\")\n if attempt < 2:\n time.sleep(2 ** attempt)\n return None\n\n# === PROCESS ===\noutput_file = current_dir.parent / f\"data/CSV/mixtral_annotated_POI{'_test' if TEST_MODE else ''}.csv\"\nindex_file = current_dir.parent / \"misc/mixtral_query_index.txt\"\n\ncurrent_index = 0\nif index_file.exists():\n with open(index_file) as f:\n current_index = int(f.read().strip())\n\nfor col in ['full_name', 'gender', 'profession_llm', 'country']:\n if col not in df.columns:\n df[col] = 'Unknown'\n\nprint(f\"\\nAnnotating with MIXTRAL 8x22B - rows {current_index} to {len(df)}...\")\n\ntry:\n for i in tqdm(range(current_index, len(df)), desc=\"Mixtral\"):\n row = df.iloc[i]\n \n tags = [str(row[f'tag_{j}']).lower() for j in range(1, 8)\n if f'tag_{j}' in row and pd.notna(row[f'tag_{j}'])]\n \n prompt = create_prompt(\n row.get('real_name', row.get('name', '')),\n tags,\n row.get('likely_country', '')\n )\n \n result = query_mixtral(prompt)\n \n if result:\n df.at[i, 'full_name'] = result.get('full_name', 'Unknown')\n df.at[i, 'gender'] = result.get('gender', 'Unknown')\n profs = result.get('professions', [])\n df.at[i, 'profession_llm'] = \", \".join(profs[:3]) if isinstance(profs, list) else str(profs)\n df.at[i, 'country'] = result.get('country', 'Unknown')\n \n current_index = i + 1\n \n if (i + 1) % SAVE_INTERVAL == 0 or (i + 1) == len(df):\n df.to_csv(output_file, index=False)\n with open(index_file, 'w') as f:\n f.write(str(current_index))\n print(f\"✅ Saved after {i+1} rows\")\n \n time.sleep(0.5)\n \n print(f\"\\n✅ Done! Results: {output_file}\")\n print(f\"\\nTop professions:\\n{df['profession_llm'].value_counts().head(10)}\")\n \nexcept Exception as e:\n print(f\"⚠️ Error: {e}\")\n df.to_csv(output_file, index=False)\n with open(index_file, 'w') as f:\n f.write(str(current_index))"
1006
- },
1007
  {
1008
  "cell_type": "markdown",
1009
  "id": "gemma_header",
@@ -1027,10 +1502,168 @@
1027
  "id": "gemma_code",
1028
  "metadata": {},
1029
  "outputs": [],
1030
- "source": "import pandas as pd\nimport json\nimport time\nimport requests\nimport re\nfrom pathlib import Path\nfrom tqdm import tqdm\n\ncurrent_dir = Path.cwd()\ninput_file = current_dir.parent / \"data/CSV/NER_POI_step02_annotated.csv\"\napi_key_file = current_dir.parent / \"misc/credentials/together_api_key.txt\"\n\n# === CONFIGURATION ===\nTEST_MODE = True\nTEST_SIZE = 10\nMAX_ROWS = 20000\nSAVE_INTERVAL = 10\n\nPROFESSION_CATEGORIES = [\n \"actor\", \"adult performer\", \"singer/musician\", \"model\",\n \"online personality\", \"public figure\", \"voice actor/ASMR\",\n \"sports professional\", \"tv personality\"\n]\n\n# === LOAD API KEY ===\ntry:\n with open(api_key_file) as f:\n api_key = f.read().strip()\n print(\"✅ Gemma API key loaded\")\nexcept FileNotFoundError:\n print(\"❌ Error: together_api_key.txt not found\")\n raise\n\n# === LOAD DATA ===\ndf = pd.read_csv(input_file)\nprint(f\"Loaded {len(df)} rows\")\n\nif TEST_MODE:\n print(f\"Running in TEST MODE with {TEST_SIZE} samples\")\n df = df.head(TEST_SIZE).copy()\nelif MAX_ROWS:\n df = df.head(MAX_ROWS).copy()\n\n# === CREATE PROMPT ===\ndef create_prompt(name: str, tags: list, country: str) -> str:\n tags_str = \", \".join(tags[:5]) if tags else \"none\"\n country_str = country if country else \"unknown\"\n \n return f\"\"\"You are an expert at identifying public figures.\n\nGiven: Name: {name}, Tags: {tags_str}, Country: {country_str}\n\nProvide in JSON format:\n{{\n \"full_name\": \"<full name>\",\n \"gender\": \"<Male/Female/Other/Unknown>\",\n \"professions\": [\"<profession 1>\", \"<profession 2>\", \"<profession 3>\"],\n \"country\": \"<country>\"\n}}\n\nChoose professions ONLY from: {\", \".join(PROFESSION_CATEGORIES)}\n- Max 3 professions, ordered by relevance\n- \"online personality\" includes streamers, cosplayers, influencers\n- Return \"Unknown\" if uncertain\n\nReturn ONLY JSON, no extra text.\"\"\"\n\n# === QUERY GEMMA ===\ndef query_gemma(prompt: str) -> dict:\n url = \"https://api.together.xyz/v1/chat/completions\"\n \n headers = {\n \"Authorization\": f\"Bearer {api_key}\",\n \"Content-Type\": \"application/json\"\n }\n \n data = {\n \"model\": \"google/gemma-2-27b-it\",\n \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n \"temperature\": 0.1,\n \"max_tokens\": 500\n }\n \n for attempt in range(3):\n try:\n response = requests.post(url, headers=headers, json=data, timeout=30)\n response.raise_for_status()\n result = response.json()\n \n if 'choices' in result:\n content = result['choices'][0]['message']['content']\n json_match = re.search(r'\\{[^}]+\\}', content, re.DOTALL)\n if json_match:\n return json.loads(json_match.group())\n return None\n except Exception as e:\n print(f\"Gemma error (attempt {attempt+1}): {e}\")\n if attempt < 2:\n time.sleep(2 ** attempt)\n return None\n\n# === PROCESS ===\noutput_file = current_dir.parent / f\"data/CSV/gemma_annotated_POI{'_test' if TEST_MODE else ''}.csv\"\nindex_file = current_dir.parent / \"misc/gemma_query_index.txt\"\n\ncurrent_index = 0\nif index_file.exists():\n with open(index_file) as f:\n current_index = int(f.read().strip())\n\nfor col in ['full_name', 'gender', 'profession_llm', 'country']:\n if col not in df.columns:\n df[col] = 'Unknown'\n\nprint(f\"\\nAnnotating with GEMMA 2 27B (Google) - rows {current_index} to {len(df)}...\")\n\ntry:\n for i in tqdm(range(current_index, len(df)), desc=\"Gemma\"):\n row = df.iloc[i]\n \n tags = [str(row[f'tag_{j}']).lower() for j in range(1, 8)\n if f'tag_{j}' in row and pd.notna(row[f'tag_{j}'])]\n \n prompt = create_prompt(\n row.get('real_name', row.get('name', '')),\n tags,\n row.get('likely_country', '')\n )\n \n result = query_gemma(prompt)\n \n if result:\n df.at[i, 'full_name'] = result.get('full_name', 'Unknown')\n df.at[i, 'gender'] = result.get('gender', 'Unknown')\n profs = result.get('professions', [])\n df.at[i, 'profession_llm'] = \", \".join(profs[:3]) if isinstance(profs, list) else str(profs)\n df.at[i, 'country'] = result.get('country', 'Unknown')\n \n current_index = i + 1\n \n if (i + 1) % SAVE_INTERVAL == 0 or (i + 1) == len(df):\n df.to_csv(output_file, index=False)\n with open(index_file, 'w') as f:\n f.write(str(current_index))\n print(f\"✅ Saved after {i+1} rows\")\n \n time.sleep(0.5)\n \n print(f\"\\n✅ Done! Results: {output_file}\")\n print(f\"\\nTop professions:\\n{df['profession_llm'].value_counts().head(10)}\")\n \nexcept Exception as e:\n print(f\"⚠️ Error: {e}\")\n df.to_csv(output_file, index=False)\n with open(index_file, 'w') as f:\n f.write(str(current_index))"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1031
  },
1032
  {
1033
  "cell_type": "markdown",
 
1034
  "metadata": {},
1035
  "source": [
1036
  "## Qwen-2.5-32B Local Annotation (Ollama)\n",
@@ -1073,6 +1706,7 @@
1073
  {
1074
  "cell_type": "code",
1075
  "execution_count": null,
 
1076
  "metadata": {},
1077
  "outputs": [],
1078
  "source": [
@@ -1304,4 +1938,4 @@
1304
  },
1305
  "nbformat": 4,
1306
  "nbformat_minor": 5
1307
- }
 
954
  "id": "qwen_code",
955
  "metadata": {},
956
  "outputs": [],
957
+ "source": [
958
+ "import pandas as pd\n",
959
+ "import json\n",
960
+ "import time\n",
961
+ "import re\n",
962
+ "from pathlib import Path\n",
963
+ "from tqdm import tqdm\n",
964
+ "from openai import OpenAI\n",
965
+ "\n",
966
+ "current_dir = Path.cwd()\n",
967
+ "input_file = current_dir.parent / \"data/CSV/NER_POI_step02_annotated.csv\"\n",
968
+ "api_key_file = current_dir.parent / \"misc/credentials/qwen_api_key.txt\"\n",
969
+ "\n",
970
+ "# === CONFIGURATION ===\n",
971
+ "TEST_MODE = True\n",
972
+ "TEST_SIZE = 10\n",
973
+ "MAX_ROWS = 20000\n",
974
+ "SAVE_INTERVAL = 10\n",
975
+ "\n",
976
+ "PROFESSION_CATEGORIES = [\n",
977
+ " \"actor\", \"adult performer\", \"singer/musician\", \"model\",\n",
978
+ " \"online personality\", \"public figure\", \"voice actor/ASMR\",\n",
979
+ " \"sports professional\", \"tv personality\"\n",
980
+ "]\n",
981
+ "\n",
982
+ "# === LOAD API KEY ===\n",
983
+ "try:\n",
984
+ " with open(api_key_file) as f:\n",
985
+ " api_key = f.read().strip()\n",
986
+ " print(\"✅ Qwen API key loaded\")\n",
987
+ " print(f\" Key starts with: {api_key[:10]}...\")\n",
988
+ "except FileNotFoundError:\n",
989
+ " print(\"❌ Error: qwen_api_key.txt not found\")\n",
990
+ " raise\n",
991
+ "\n",
992
+ "# Initialize OpenAI client for Qwen (matching dashboard example)\n",
993
+ "client = OpenAI(\n",
994
+ " api_key=api_key,\n",
995
+ " base_url=\"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\"\n",
996
+ ")\n",
997
+ "\n",
998
+ "# === LOAD DATA ===\n",
999
+ "df = pd.read_csv(input_file)\n",
1000
+ "print(f\"Loaded {len(df)} rows\")\n",
1001
+ "\n",
1002
+ "if TEST_MODE:\n",
1003
+ " print(f\"Running in TEST MODE with {TEST_SIZE} samples\")\n",
1004
+ " df = df.head(TEST_SIZE).copy()\n",
1005
+ "elif MAX_ROWS:\n",
1006
+ " df = df.head(MAX_ROWS).copy()\n",
1007
+ "\n",
1008
+ "# === CREATE PROMPT ===\n",
1009
+ "def create_prompt(name: str, tags: list, country: str) -> str:\n",
1010
+ " tags_str = \", \".join(tags[:5]) if tags else \"none\"\n",
1011
+ " country_str = country if country else \"unknown\"\n",
1012
+ " \n",
1013
+ " return f\"\"\"You are an expert at identifying public figures.\n",
1014
+ "\n",
1015
+ "Given: Name: {name}, Tags: {tags_str}, Country: {country_str}\n",
1016
+ "\n",
1017
+ "Provide in JSON format:\n",
1018
+ "{{\n",
1019
+ " \"full_name\": \"<full name>\",\n",
1020
+ " \"gender\": \"<Male/Female/Other/Unknown>\",\n",
1021
+ " \"professions\": [\"<profession 1>\", \"<profession 2>\", \"<profession 3>\"],\n",
1022
+ " \"country\": \"<country>\"\n",
1023
+ "}}\n",
1024
+ "\n",
1025
+ "Choose professions ONLY from: {\", \".join(PROFESSION_CATEGORIES)}\n",
1026
+ "- Max 3 professions, ordered by relevance\n",
1027
+ "- \"online personality\" includes streamers, cosplayers, influencers\n",
1028
+ "- Return \"Unknown\" if uncertain\n",
1029
+ "\n",
1030
+ "Return ONLY JSON, no extra text.\"\"\"\n",
1031
+ "\n",
1032
+ "# === QUERY QWEN ===\n",
1033
+ "def query_qwen(prompt: str) -> dict:\n",
1034
+ " \"\"\"Query Qwen using OpenAI-compatible API (matching dashboard example).\"\"\"\n",
1035
+ " for attempt in range(3):\n",
1036
+ " try:\n",
1037
+ " completion = client.chat.completions.create(\n",
1038
+ " model=\"qwen-max\", # Note: dashboard shows \"qwen3-max\" but \"qwen-max\" works too\n",
1039
+ " messages=[\n",
1040
+ " {\"role\": \"user\", \"content\": prompt}\n",
1041
+ " ],\n",
1042
+ " temperature=0.1,\n",
1043
+ " max_tokens=500,\n",
1044
+ " stream=False # Disable streaming for easier parsing\n",
1045
+ " )\n",
1046
+ " \n",
1047
+ " content = completion.choices[0].message.content\n",
1048
+ " \n",
1049
+ " # Parse JSON from response\n",
1050
+ " json_match = re.search(r'\\{[^}]+\\}', content, re.DOTALL)\n",
1051
+ " if json_match:\n",
1052
+ " return json.loads(json_match.group())\n",
1053
+ " \n",
1054
+ " return None\n",
1055
+ " \n",
1056
+ " except Exception as e:\n",
1057
+ " print(f\"Qwen error (attempt {attempt+1}): {e}\")\n",
1058
+ " if attempt < 2:\n",
1059
+ " time.sleep(2 ** attempt)\n",
1060
+ " \n",
1061
+ " return None\n",
1062
+ "\n",
1063
+ "# === PROCESS ===\n",
1064
+ "output_file = current_dir.parent / f\"data/CSV/qwen_annotated_POI{'_test' if TEST_MODE else ''}.csv\"\n",
1065
+ "index_file = current_dir.parent / \"misc/qwen_query_index.txt\"\n",
1066
+ "\n",
1067
+ "current_index = 0\n",
1068
+ "if index_file.exists():\n",
1069
+ " with open(index_file) as f:\n",
1070
+ " current_index = int(f.read().strip())\n",
1071
+ "\n",
1072
+ "for col in ['full_name', 'gender', 'profession_llm', 'country']:\n",
1073
+ " if col not in df.columns:\n",
1074
+ " df[col] = 'Unknown'\n",
1075
+ "\n",
1076
+ "print(f\"\\nAnnotating with QWEN-MAX (OpenAI-compatible API) - rows {current_index} to {len(df)}...\")\n",
1077
+ "\n",
1078
+ "try:\n",
1079
+ " for i in tqdm(range(current_index, len(df)), desc=\"Qwen\"):\n",
1080
+ " row = df.iloc[i]\n",
1081
+ " \n",
1082
+ " tags = [str(row[f'tag_{j}']).lower() for j in range(1, 8)\n",
1083
+ " if f'tag_{j}' in row and pd.notna(row[f'tag_{j}'])]\n",
1084
+ " \n",
1085
+ " prompt = create_prompt(\n",
1086
+ " row.get('real_name', row.get('name', '')),\n",
1087
+ " tags,\n",
1088
+ " row.get('likely_country', '')\n",
1089
+ " )\n",
1090
+ " \n",
1091
+ " result = query_qwen(prompt)\n",
1092
+ " \n",
1093
+ " if result:\n",
1094
+ " df.at[i, 'full_name'] = result.get('full_name', 'Unknown')\n",
1095
+ " df.at[i, 'gender'] = result.get('gender', 'Unknown')\n",
1096
+ " profs = result.get('professions', [])\n",
1097
+ " df.at[i, 'profession_llm'] = \", \".join(profs[:3]) if isinstance(profs, list) else str(profs)\n",
1098
+ " df.at[i, 'country'] = result.get('country', 'Unknown')\n",
1099
+ " \n",
1100
+ " current_index = i + 1\n",
1101
+ " \n",
1102
+ " if (i + 1) % SAVE_INTERVAL == 0 or (i + 1) == len(df):\n",
1103
+ " df.to_csv(output_file, index=False)\n",
1104
+ " with open(index_file, 'w') as f:\n",
1105
+ " f.write(str(current_index))\n",
1106
+ " print(f\"✅ Saved after {i+1} rows\")\n",
1107
+ " \n",
1108
+ " time.sleep(0.5)\n",
1109
+ " \n",
1110
+ " print(f\"\\n✅ Done! Results: {output_file}\")\n",
1111
+ " print(f\"\\nTop professions:\\n{df['profession_llm'].value_counts().head(10)}\")\n",
1112
+ " \n",
1113
+ "except Exception as e:\n",
1114
+ " print(f\"⚠️ Error: {e}\")\n",
1115
+ " df.to_csv(output_file, index=False)\n",
1116
+ " with open(index_file, 'w') as f:\n",
1117
+ " f.write(str(current_index))"
1118
+ ]
1119
  },
1120
  {
1121
  "cell_type": "markdown",
 
1124
  "source": [
1125
  "## Llama Annotation (Together AI)\n",
1126
  "\n",
1127
+ "Uses **Llama-3.1-70B-Instruct-Turbo** via Together AI.\n",
1128
+ "\n",
1129
+ "**Requirements:**\n",
1130
+ "- API Key: `misc/credentials/together_api_key.txt`\n",
1131
+ "- Sign up: https://www.together.ai/\n",
1132
+ "- Model: meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo\n",
1133
+ "- Cost: ~$0.90 per 1M tokens (~$5-10 for 10k entries)"
1134
+ ]
1135
+ },
1136
+ {
1137
+ "cell_type": "code",
1138
+ "execution_count": null,
1139
+ "id": "llama_code",
1140
+ "metadata": {},
1141
+ "outputs": [],
1142
+ "source": [
1143
+ "import pandas as pd\n",
1144
+ "import json\n",
1145
+ "import time\n",
1146
+ "import requests\n",
1147
+ "import re\n",
1148
+ "from pathlib import Path\n",
1149
+ "from tqdm import tqdm\n",
1150
+ "\n",
1151
+ "current_dir = Path.cwd()\n",
1152
+ "input_file = current_dir.parent / \"data/CSV/NER_POI_step02_annotated.csv\"\n",
1153
+ "api_key_file = current_dir.parent / \"misc/credentials/together_api_key.txt\"\n",
1154
+ "\n",
1155
+ "# === CONFIGURATION ===\n",
1156
+ "TEST_MODE = True\n",
1157
+ "TEST_SIZE = 10\n",
1158
+ "MAX_ROWS = 20000\n",
1159
+ "SAVE_INTERVAL = 10\n",
1160
+ "\n",
1161
+ "PROFESSION_CATEGORIES = [\n",
1162
+ " \"actor\", \"adult performer\", \"singer/musician\", \"model\",\n",
1163
+ " \"online personality\", \"public figure\", \"voice actor/ASMR\",\n",
1164
+ " \"sports professional\", \"tv personality\"\n",
1165
+ "]\n",
1166
+ "\n",
1167
+ "# === LOAD API KEY ===\n",
1168
+ "try:\n",
1169
+ " with open(api_key_file) as f:\n",
1170
+ " api_key = f.read().strip()\n",
1171
+ " print(\"✅ Llama API key loaded\")\n",
1172
+ "except FileNotFoundError:\n",
1173
+ " print(\"❌ Error: together_api_key.txt not found\")\n",
1174
+ " raise\n",
1175
+ "\n",
1176
+ "# === LOAD DATA ===\n",
1177
+ "df = pd.read_csv(input_file)\n",
1178
+ "print(f\"Loaded {len(df)} rows\")\n",
1179
+ "\n",
1180
+ "if TEST_MODE:\n",
1181
+ " print(f\"Running in TEST MODE with {TEST_SIZE} samples\")\n",
1182
+ " df = df.head(TEST_SIZE).copy()\n",
1183
+ "elif MAX_ROWS:\n",
1184
+ " df = df.head(MAX_ROWS).copy()\n",
1185
+ "\n",
1186
+ "# === CREATE PROMPT ===\n",
1187
+ "def create_prompt(name: str, tags: list, country: str) -> str:\n",
1188
+ " tags_str = \", \".join(tags[:5]) if tags else \"none\"\n",
1189
+ " country_str = country if country else \"unknown\"\n",
1190
+ " \n",
1191
+ " return f\"\"\"You are an expert at identifying public figures.\n",
1192
+ "\n",
1193
+ "Given: Name: {name}, Tags: {tags_str}, Country: {country_str}\n",
1194
+ "\n",
1195
+ "Provide in JSON format:\n",
1196
+ "{{\n",
1197
+ " \"full_name\": \"<full name>\",\n",
1198
+ " \"gender\": \"<Male/Female/Other/Unknown>\",\n",
1199
+ " \"professions\": [\"<profession 1>\", \"<profession 2>\", \"<profession 3>\"],\n",
1200
+ " \"country\": \"<country>\"\n",
1201
+ "}}\n",
1202
+ "\n",
1203
+ "Choose professions ONLY from: {\", \".join(PROFESSION_CATEGORIES)}\n",
1204
+ "- Max 3 professions, ordered by relevance\n",
1205
+ "- \"online personality\" includes streamers, cosplayers, influencers\n",
1206
+ "- Return \"Unknown\" if uncertain\n",
1207
+ "\n",
1208
+ "Return ONLY JSON, no extra text.\"\"\"\n",
1209
+ "\n",
1210
+ "# === QUERY LLAMA ===\n",
1211
+ "def query_llama(prompt: str) -> dict:\n",
1212
+ " url = \"https://api.together.xyz/v1/chat/completions\"\n",
1213
+ " \n",
1214
+ " headers = {\n",
1215
+ " \"Authorization\": f\"Bearer {api_key}\",\n",
1216
+ " \"Content-Type\": \"application/json\"\n",
1217
+ " }\n",
1218
+ " \n",
1219
+ " data = {\n",
1220
+ " \"model\": \"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo\",\n",
1221
+ " \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n",
1222
+ " \"temperature\": 0.1,\n",
1223
+ " \"max_tokens\": 500\n",
1224
+ " }\n",
1225
+ " \n",
1226
+ " for attempt in range(3):\n",
1227
+ " try:\n",
1228
+ " response = requests.post(url, headers=headers, json=data, timeout=30)\n",
1229
+ " response.raise_for_status()\n",
1230
+ " result = response.json()\n",
1231
+ " \n",
1232
+ " if 'choices' in result:\n",
1233
+ " content = result['choices'][0]['message']['content']\n",
1234
+ " json_match = re.search(r'\\{[^}]+\\}', content, re.DOTALL)\n",
1235
+ " if json_match:\n",
1236
+ " return json.loads(json_match.group())\n",
1237
+ " return None\n",
1238
+ " except Exception as e:\n",
1239
+ " print(f\"Llama error (attempt {attempt+1}): {e}\")\n",
1240
+ " if attempt < 2:\n",
1241
+ " time.sleep(2 ** attempt)\n",
1242
+ " return None\n",
1243
+ "\n",
1244
+ "# === PROCESS ===\n",
1245
+ "output_file = current_dir.parent / f\"data/CSV/llama_annotated_POI{'_test' if TEST_MODE else ''}.csv\"\n",
1246
+ "index_file = current_dir.parent / \"misc/llama_query_index.txt\"\n",
1247
+ "\n",
1248
+ "current_index = 0\n",
1249
+ "if index_file.exists():\n",
1250
+ " with open(index_file) as f:\n",
1251
+ " current_index = int(f.read().strip())\n",
1252
+ "\n",
1253
+ "for col in ['full_name', 'gender', 'profession_llm', 'country']:\n",
1254
+ " if col not in df.columns:\n",
1255
+ " df[col] = 'Unknown'\n",
1256
+ "\n",
1257
+ "print(f\"\\nAnnotating with LLAMA 3.1 70B - rows {current_index} to {len(df)}...\")\n",
1258
+ "\n",
1259
+ "try:\n",
1260
+ " for i in tqdm(range(current_index, len(df)), desc=\"Llama\"):\n",
1261
+ " row = df.iloc[i]\n",
1262
+ " \n",
1263
+ " tags = [str(row[f'tag_{j}']).lower() for j in range(1, 8)\n",
1264
+ " if f'tag_{j}' in row and pd.notna(row[f'tag_{j}'])]\n",
1265
+ " \n",
1266
+ " prompt = create_prompt(\n",
1267
+ " row.get('real_name', row.get('name', '')),\n",
1268
+ " tags,\n",
1269
+ " row.get('likely_country', '')\n",
1270
+ " )\n",
1271
+ " \n",
1272
+ " result = query_llama(prompt)\n",
1273
+ " \n",
1274
+ " if result:\n",
1275
+ " df.at[i, 'full_name'] = result.get('full_name', 'Unknown')\n",
1276
+ " df.at[i, 'gender'] = result.get('gender', 'Unknown')\n",
1277
+ " profs = result.get('professions', [])\n",
1278
+ " df.at[i, 'profession_llm'] = \", \".join(profs[:3]) if isinstance(profs, list) else str(profs)\n",
1279
+ " df.at[i, 'country'] = result.get('country', 'Unknown')\n",
1280
+ " \n",
1281
+ " current_index = i + 1\n",
1282
+ " \n",
1283
+ " if (i + 1) % SAVE_INTERVAL == 0 or (i + 1) == len(df):\n",
1284
+ " df.to_csv(output_file, index=False)\n",
1285
+ " with open(index_file, 'w') as f:\n",
1286
+ " f.write(str(current_index))\n",
1287
+ " print(f\"✅ Saved after {i+1} rows\")\n",
1288
+ " \n",
1289
+ " time.sleep(0.5)\n",
1290
+ " \n",
1291
+ " print(f\"\\n✅ Done! Results: {output_file}\")\n",
1292
+ " print(f\"\\nTop professions:\\n{df['profession_llm'].value_counts().head(10)}\")\n",
1293
+ " \n",
1294
+ "except Exception as e:\n",
1295
+ " print(f\"⚠️ Error: {e}\")\n",
1296
+ " df.to_csv(output_file, index=False)\n",
1297
+ " with open(index_file, 'w') as f:\n",
1298
+ " f.write(str(current_index))"
1299
+ ]
1300
+ },
1301
+ {
1302
+ "cell_type": "markdown",
1303
+ "id": "mistral_header",
1304
+ "metadata": {},
1305
+ "source": [
1306
+ "## Mixtral Annotation (Mistral AI)\n",
1307
+ "\n",
1308
+ "Uses **Open-Mixtral-8x22B** via Mistral AI API.\n",
1309
+ "\n",
1310
+ "**Requirements:**\n",
1311
+ "- API Key: `misc/credentials/mistral_api_key.txt`\n",
1312
+ "- Sign up: https://mistral.ai/\n",
1313
+ "- Model: open-mixtral-8x22b\n",
1314
+ "- Cost: ~$2 per 1M tokens (~$10-20 for 10k entries)"
1315
+ ]
1316
+ },
1317
+ {
1318
+ "cell_type": "code",
1319
+ "execution_count": null,
1320
+ "id": "mixtral_code",
1321
+ "metadata": {},
1322
+ "outputs": [],
1323
+ "source": [
1324
+ "import pandas as pd\n",
1325
+ "import json\n",
1326
+ "import time\n",
1327
+ "import requests\n",
1328
+ "import re\n",
1329
+ "from pathlib import Path\n",
1330
+ "from tqdm import tqdm\n",
1331
+ "\n",
1332
+ "current_dir = Path.cwd()\n",
1333
+ "input_file = current_dir.parent / \"data/CSV/NER_POI_step02_annotated.csv\"\n",
1334
+ "api_key_file = current_dir.parent / \"misc/credentials/mistral_api_key.txt\"\n",
1335
+ "\n",
1336
+ "# === CONFIGURATION ===\n",
1337
+ "TEST_MODE = True\n",
1338
+ "TEST_SIZE = 10\n",
1339
+ "MAX_ROWS = 20000\n",
1340
+ "SAVE_INTERVAL = 10\n",
1341
+ "\n",
1342
+ "PROFESSION_CATEGORIES = [\n",
1343
+ " \"actor\", \"adult performer\", \"singer/musician\", \"model\",\n",
1344
+ " \"online personality\", \"public figure\", \"voice actor/ASMR\",\n",
1345
+ " \"sports professional\", \"tv personality\"\n",
1346
+ "]\n",
1347
+ "\n",
1348
+ "# === LOAD API KEY ===\n",
1349
+ "try:\n",
1350
+ " with open(api_key_file) as f:\n",
1351
+ " api_key = f.read().strip()\n",
1352
+ " print(\"✅ Mixtral API key loaded\")\n",
1353
+ "except FileNotFoundError:\n",
1354
+ " print(\"❌ Error: mistral_api_key.txt not found\")\n",
1355
+ " raise\n",
1356
+ "\n",
1357
+ "# === LOAD DATA ===\n",
1358
+ "df = pd.read_csv(input_file)\n",
1359
+ "print(f\"Loaded {len(df)} rows\")\n",
1360
+ "\n",
1361
+ "if TEST_MODE:\n",
1362
+ " print(f\"Running in TEST MODE with {TEST_SIZE} samples\")\n",
1363
+ " df = df.head(TEST_SIZE).copy()\n",
1364
+ "elif MAX_ROWS:\n",
1365
+ " df = df.head(MAX_ROWS).copy()\n",
1366
  "\n",
1367
+ "# === CREATE PROMPT ===\n",
1368
+ "def create_prompt(name: str, tags: list, country: str) -> str:\n",
1369
+ " tags_str = \", \".join(tags[:5]) if tags else \"none\"\n",
1370
+ " country_str = country if country else \"unknown\"\n",
1371
+ " \n",
1372
+ " return f\"\"\"You are an expert at identifying public figures.\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1373
  "\n",
1374
+ "Given: Name: {name}, Tags: {tags_str}, Country: {country_str}\n",
1375
  "\n",
1376
+ "Provide in JSON format:\n",
1377
+ "{{\n",
1378
+ " \"full_name\": \"<full name>\",\n",
1379
+ " \"gender\": \"<Male/Female/Other/Unknown>\",\n",
1380
+ " \"professions\": [\"<profession 1>\", \"<profession 2>\", \"<profession 3>\"],\n",
1381
+ " \"country\": \"<country>\"\n",
1382
+ "}}\n",
1383
+ "\n",
1384
+ "Choose professions ONLY from: {\", \".join(PROFESSION_CATEGORIES)}\n",
1385
+ "- Max 3 professions, ordered by relevance\n",
1386
+ "- \"online personality\" includes streamers, cosplayers, influencers\n",
1387
+ "- Return \"Unknown\" if uncertain\n",
1388
+ "\n",
1389
+ "Return ONLY JSON, no extra text.\"\"\"\n",
1390
+ "\n",
1391
+ "# === QUERY MIXTRAL ===\n",
1392
+ "def query_mixtral(prompt: str) -> dict:\n",
1393
+ " url = \"https://api.mistral.ai/v1/chat/completions\"\n",
1394
+ " \n",
1395
+ " headers = {\n",
1396
+ " \"Authorization\": f\"Bearer {api_key}\",\n",
1397
+ " \"Content-Type\": \"application/json\"\n",
1398
+ " }\n",
1399
+ " \n",
1400
+ " data = {\n",
1401
+ " \"model\": \"open-mixtral-8x22b\",\n",
1402
+ " \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n",
1403
+ " \"temperature\": 0.1,\n",
1404
+ " \"max_tokens\": 500\n",
1405
+ " }\n",
1406
+ " \n",
1407
+ " for attempt in range(3):\n",
1408
+ " try:\n",
1409
+ " response = requests.post(url, headers=headers, json=data, timeout=30)\n",
1410
+ " response.raise_for_status()\n",
1411
+ " result = response.json()\n",
1412
+ " \n",
1413
+ " if 'choices' in result:\n",
1414
+ " content = result['choices'][0]['message']['content']\n",
1415
+ " json_match = re.search(r'\\{[^}]+\\}', content, re.DOTALL)\n",
1416
+ " if json_match:\n",
1417
+ " return json.loads(json_match.group())\n",
1418
+ " return None\n",
1419
+ " except Exception as e:\n",
1420
+ " print(f\"Mixtral error (attempt {attempt+1}): {e}\")\n",
1421
+ " if attempt < 2:\n",
1422
+ " time.sleep(2 ** attempt)\n",
1423
+ " return None\n",
1424
+ "\n",
1425
+ "# === PROCESS ===\n",
1426
+ "output_file = current_dir.parent / f\"data/CSV/mixtral_annotated_POI{'_test' if TEST_MODE else ''}.csv\"\n",
1427
+ "index_file = current_dir.parent / \"misc/mixtral_query_index.txt\"\n",
1428
+ "\n",
1429
+ "current_index = 0\n",
1430
+ "if index_file.exists():\n",
1431
+ " with open(index_file) as f:\n",
1432
+ " current_index = int(f.read().strip())\n",
1433
+ "\n",
1434
+ "for col in ['full_name', 'gender', 'profession_llm', 'country']:\n",
1435
+ " if col not in df.columns:\n",
1436
+ " df[col] = 'Unknown'\n",
1437
+ "\n",
1438
+ "print(f\"\\nAnnotating with MIXTRAL 8x22B - rows {current_index} to {len(df)}...\")\n",
1439
+ "\n",
1440
+ "try:\n",
1441
+ " for i in tqdm(range(current_index, len(df)), desc=\"Mixtral\"):\n",
1442
+ " row = df.iloc[i]\n",
1443
+ " \n",
1444
+ " tags = [str(row[f'tag_{j}']).lower() for j in range(1, 8)\n",
1445
+ " if f'tag_{j}' in row and pd.notna(row[f'tag_{j}'])]\n",
1446
+ " \n",
1447
+ " prompt = create_prompt(\n",
1448
+ " row.get('real_name', row.get('name', '')),\n",
1449
+ " tags,\n",
1450
+ " row.get('likely_country', '')\n",
1451
+ " )\n",
1452
+ " \n",
1453
+ " result = query_mixtral(prompt)\n",
1454
+ " \n",
1455
+ " if result:\n",
1456
+ " df.at[i, 'full_name'] = result.get('full_name', 'Unknown')\n",
1457
+ " df.at[i, 'gender'] = result.get('gender', 'Unknown')\n",
1458
+ " profs = result.get('professions', [])\n",
1459
+ " df.at[i, 'profession_llm'] = \", \".join(profs[:3]) if isinstance(profs, list) else str(profs)\n",
1460
+ " df.at[i, 'country'] = result.get('country', 'Unknown')\n",
1461
+ " \n",
1462
+ " current_index = i + 1\n",
1463
+ " \n",
1464
+ " if (i + 1) % SAVE_INTERVAL == 0 or (i + 1) == len(df):\n",
1465
+ " df.to_csv(output_file, index=False)\n",
1466
+ " with open(index_file, 'w') as f:\n",
1467
+ " f.write(str(current_index))\n",
1468
+ " print(f\"✅ Saved after {i+1} rows\")\n",
1469
+ " \n",
1470
+ " time.sleep(0.5)\n",
1471
+ " \n",
1472
+ " print(f\"\\n✅ Done! Results: {output_file}\")\n",
1473
+ " print(f\"\\nTop professions:\\n{df['profession_llm'].value_counts().head(10)}\")\n",
1474
+ " \n",
1475
+ "except Exception as e:\n",
1476
+ " print(f\"⚠️ Error: {e}\")\n",
1477
+ " df.to_csv(output_file, index=False)\n",
1478
+ " with open(index_file, 'w') as f:\n",
1479
+ " f.write(str(current_index))"
1480
  ]
1481
  },
 
 
 
 
 
 
 
 
1482
  {
1483
  "cell_type": "markdown",
1484
  "id": "gemma_header",
 
1502
  "id": "gemma_code",
1503
  "metadata": {},
1504
  "outputs": [],
1505
+ "source": [
1506
+ "import pandas as pd\n",
1507
+ "import json\n",
1508
+ "import time\n",
1509
+ "import requests\n",
1510
+ "import re\n",
1511
+ "from pathlib import Path\n",
1512
+ "from tqdm import tqdm\n",
1513
+ "\n",
1514
+ "current_dir = Path.cwd()\n",
1515
+ "input_file = current_dir.parent / \"data/CSV/NER_POI_step02_annotated.csv\"\n",
1516
+ "api_key_file = current_dir.parent / \"misc/credentials/together_api_key.txt\"\n",
1517
+ "\n",
1518
+ "# === CONFIGURATION ===\n",
1519
+ "TEST_MODE = True\n",
1520
+ "TEST_SIZE = 10\n",
1521
+ "MAX_ROWS = 20000\n",
1522
+ "SAVE_INTERVAL = 10\n",
1523
+ "\n",
1524
+ "PROFESSION_CATEGORIES = [\n",
1525
+ " \"actor\", \"adult performer\", \"singer/musician\", \"model\",\n",
1526
+ " \"online personality\", \"public figure\", \"voice actor/ASMR\",\n",
1527
+ " \"sports professional\", \"tv personality\"\n",
1528
+ "]\n",
1529
+ "\n",
1530
+ "# === LOAD API KEY ===\n",
1531
+ "try:\n",
1532
+ " with open(api_key_file) as f:\n",
1533
+ " api_key = f.read().strip()\n",
1534
+ " print(\"✅ Gemma API key loaded\")\n",
1535
+ "except FileNotFoundError:\n",
1536
+ " print(\"❌ Error: together_api_key.txt not found\")\n",
1537
+ " raise\n",
1538
+ "\n",
1539
+ "# === LOAD DATA ===\n",
1540
+ "df = pd.read_csv(input_file)\n",
1541
+ "print(f\"Loaded {len(df)} rows\")\n",
1542
+ "\n",
1543
+ "if TEST_MODE:\n",
1544
+ " print(f\"Running in TEST MODE with {TEST_SIZE} samples\")\n",
1545
+ " df = df.head(TEST_SIZE).copy()\n",
1546
+ "elif MAX_ROWS:\n",
1547
+ " df = df.head(MAX_ROWS).copy()\n",
1548
+ "\n",
1549
+ "# === CREATE PROMPT ===\n",
1550
+ "def create_prompt(name: str, tags: list, country: str) -> str:\n",
1551
+ " tags_str = \", \".join(tags[:5]) if tags else \"none\"\n",
1552
+ " country_str = country if country else \"unknown\"\n",
1553
+ " \n",
1554
+ " return f\"\"\"You are an expert at identifying public figures.\n",
1555
+ "\n",
1556
+ "Given: Name: {name}, Tags: {tags_str}, Country: {country_str}\n",
1557
+ "\n",
1558
+ "Provide in JSON format:\n",
1559
+ "{{\n",
1560
+ " \"full_name\": \"<full name>\",\n",
1561
+ " \"gender\": \"<Male/Female/Other/Unknown>\",\n",
1562
+ " \"professions\": [\"<profession 1>\", \"<profession 2>\", \"<profession 3>\"],\n",
1563
+ " \"country\": \"<country>\"\n",
1564
+ "}}\n",
1565
+ "\n",
1566
+ "Choose professions ONLY from: {\", \".join(PROFESSION_CATEGORIES)}\n",
1567
+ "- Max 3 professions, ordered by relevance\n",
1568
+ "- \"online personality\" includes streamers, cosplayers, influencers\n",
1569
+ "- Return \"Unknown\" if uncertain\n",
1570
+ "\n",
1571
+ "Return ONLY JSON, no extra text.\"\"\"\n",
1572
+ "\n",
1573
+ "# === QUERY GEMMA ===\n",
1574
+ "def query_gemma(prompt: str) -> dict:\n",
1575
+ " url = \"https://api.together.xyz/v1/chat/completions\"\n",
1576
+ " \n",
1577
+ " headers = {\n",
1578
+ " \"Authorization\": f\"Bearer {api_key}\",\n",
1579
+ " \"Content-Type\": \"application/json\"\n",
1580
+ " }\n",
1581
+ " \n",
1582
+ " data = {\n",
1583
+ " \"model\": \"google/gemma-2-27b-it\",\n",
1584
+ " \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n",
1585
+ " \"temperature\": 0.1,\n",
1586
+ " \"max_tokens\": 500\n",
1587
+ " }\n",
1588
+ " \n",
1589
+ " for attempt in range(3):\n",
1590
+ " try:\n",
1591
+ " response = requests.post(url, headers=headers, json=data, timeout=30)\n",
1592
+ " response.raise_for_status()\n",
1593
+ " result = response.json()\n",
1594
+ " \n",
1595
+ " if 'choices' in result:\n",
1596
+ " content = result['choices'][0]['message']['content']\n",
1597
+ " json_match = re.search(r'\\{[^}]+\\}', content, re.DOTALL)\n",
1598
+ " if json_match:\n",
1599
+ " return json.loads(json_match.group())\n",
1600
+ " return None\n",
1601
+ " except Exception as e:\n",
1602
+ " print(f\"Gemma error (attempt {attempt+1}): {e}\")\n",
1603
+ " if attempt < 2:\n",
1604
+ " time.sleep(2 ** attempt)\n",
1605
+ " return None\n",
1606
+ "\n",
1607
+ "# === PROCESS ===\n",
1608
+ "output_file = current_dir.parent / f\"data/CSV/gemma_annotated_POI{'_test' if TEST_MODE else ''}.csv\"\n",
1609
+ "index_file = current_dir.parent / \"misc/gemma_query_index.txt\"\n",
1610
+ "\n",
1611
+ "current_index = 0\n",
1612
+ "if index_file.exists():\n",
1613
+ " with open(index_file) as f:\n",
1614
+ " current_index = int(f.read().strip())\n",
1615
+ "\n",
1616
+ "for col in ['full_name', 'gender', 'profession_llm', 'country']:\n",
1617
+ " if col not in df.columns:\n",
1618
+ " df[col] = 'Unknown'\n",
1619
+ "\n",
1620
+ "print(f\"\\nAnnotating with GEMMA 2 27B (Google) - rows {current_index} to {len(df)}...\")\n",
1621
+ "\n",
1622
+ "try:\n",
1623
+ " for i in tqdm(range(current_index, len(df)), desc=\"Gemma\"):\n",
1624
+ " row = df.iloc[i]\n",
1625
+ " \n",
1626
+ " tags = [str(row[f'tag_{j}']).lower() for j in range(1, 8)\n",
1627
+ " if f'tag_{j}' in row and pd.notna(row[f'tag_{j}'])]\n",
1628
+ " \n",
1629
+ " prompt = create_prompt(\n",
1630
+ " row.get('real_name', row.get('name', '')),\n",
1631
+ " tags,\n",
1632
+ " row.get('likely_country', '')\n",
1633
+ " )\n",
1634
+ " \n",
1635
+ " result = query_gemma(prompt)\n",
1636
+ " \n",
1637
+ " if result:\n",
1638
+ " df.at[i, 'full_name'] = result.get('full_name', 'Unknown')\n",
1639
+ " df.at[i, 'gender'] = result.get('gender', 'Unknown')\n",
1640
+ " profs = result.get('professions', [])\n",
1641
+ " df.at[i, 'profession_llm'] = \", \".join(profs[:3]) if isinstance(profs, list) else str(profs)\n",
1642
+ " df.at[i, 'country'] = result.get('country', 'Unknown')\n",
1643
+ " \n",
1644
+ " current_index = i + 1\n",
1645
+ " \n",
1646
+ " if (i + 1) % SAVE_INTERVAL == 0 or (i + 1) == len(df):\n",
1647
+ " df.to_csv(output_file, index=False)\n",
1648
+ " with open(index_file, 'w') as f:\n",
1649
+ " f.write(str(current_index))\n",
1650
+ " print(f\"✅ Saved after {i+1} rows\")\n",
1651
+ " \n",
1652
+ " time.sleep(0.5)\n",
1653
+ " \n",
1654
+ " print(f\"\\n✅ Done! Results: {output_file}\")\n",
1655
+ " print(f\"\\nTop professions:\\n{df['profession_llm'].value_counts().head(10)}\")\n",
1656
+ " \n",
1657
+ "except Exception as e:\n",
1658
+ " print(f\"⚠️ Error: {e}\")\n",
1659
+ " df.to_csv(output_file, index=False)\n",
1660
+ " with open(index_file, 'w') as f:\n",
1661
+ " f.write(str(current_index))"
1662
+ ]
1663
  },
1664
  {
1665
  "cell_type": "markdown",
1666
+ "id": "01086b35",
1667
  "metadata": {},
1668
  "source": [
1669
  "## Qwen-2.5-32B Local Annotation (Ollama)\n",
 
1706
  {
1707
  "cell_type": "code",
1708
  "execution_count": null,
1709
+ "id": "5dd7a803",
1710
  "metadata": {},
1711
  "outputs": [],
1712
  "source": [
 
1938
  },
1939
  "nbformat": 4,
1940
  "nbformat_minor": 5
1941
+ }