{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Multi-model Evaluation LinkedIn Summary and FAQ" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import os\n", "import gradio as gr\n", "from dotenv import load_dotenv\n", "from pypdf import PdfReader\n", "from pathlib import Path\n", "from IPython.display import Markdown, display\n", "from anthropic import Anthropic\n", "from openai import OpenAI # Used here to call Ollama-compatible API and Google Gemini\n", "\n", "load_dotenv(override=True)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OpenAI API Key not set\n", "Anthropic API Key exists and begins sk-ant-\n", "Google API Key exists and begins AI\n", "DeepSeek API Key not set (and this is optional)\n", "Groq API Key exists and begins gsk_\n" ] } ], "source": [ "# Print the key prefixes to help with any debugging\n", "\n", "openai_api_key = os.getenv('OPENAI_API_KEY')\n", "anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')\n", "google_api_key = os.getenv('GOOGLE_API_KEY')\n", "deepseek_api_key = os.getenv('DEEPSEEK_API_KEY')\n", "groq_api_key = os.getenv('GROQ_API_KEY')\n", "\n", "if openai_api_key:\n", " print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n", "else:\n", " print(\"OpenAI API Key not set\")\n", " \n", "if anthropic_api_key:\n", " print(f\"Anthropic API Key exists and begins {anthropic_api_key[:7]}\")\n", "else:\n", " print(\"Anthropic API Key not set (and this is optional)\")\n", "\n", "if google_api_key:\n", " print(f\"Google API Key exists and begins {google_api_key[:2]}\")\n", "else:\n", " print(\"Google API Key not set (and this is optional)\")\n", "\n", "if deepseek_api_key:\n", " print(f\"DeepSeek API Key exists and begins {deepseek_api_key[:3]}\")\n", "else:\n", " print(\"DeepSeek API Key not set (and this is optional)\")\n", "\n", "if groq_api_key:\n", " print(f\"Groq API Key exists and begins {groq_api_key[:4]}\")\n", "else:\n", " print(\"Groq API Key not set (and this is optional)\")" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "anthropic = Anthropic()\n", "\n", "# === Load PDF and extract resume text ===\n", "\n", "reader = PdfReader(\"../claude_based_chatbot_tc/me/linkedin.pdf\")\n", "linkedin = \"\"\n", "for page in reader.pages:\n", " text = page.extract_text()\n", " if text:\n", " linkedin += text\n", "\n", "# === Create the shared FAQ generation prompt ===\n", "faq_prompt = (\n", " \"Please read the following professional background and resume content carefully. \"\n", " \"Based on this information, generate a well-structured FAQ (Frequently Asked Questions) document that reflects the subject’s professional background.\\n\\n\"\n", " \"== RESUME TEXT START ==\\n\"\n", " f\"{linkedin}\\n\"\n", " \"== RESUME TEXT END ==\\n\\n\"\n", "\n", " \"**Instructions:**\\n\"\n", " \"- Write at least 15 FAQs.\\n\"\n", " \"- Each entry should be in the format:\\n\"\n", " \" - Q: [Question here]\\n\"\n", " \" - A: [Answer here]\\n\"\n", " \"- Focus on real-world questions that recruiters, collaborators, or website visitors would ask.\\n\"\n", " \"- Be concise, accurate, and use only the information in the resume. Do not speculate or invent details.\\n\"\n", " \"- Use a professional tone suitable for publishing on a personal website.\\n\\n\"\n", "\n", " \"Output only the FAQ content. Do not include commentary, headers, or formatting outside of the Q/A list.\"\n", ")\n", "\n", "messages = [{\"role\": \"user\", \"content\": faq_prompt}]\n", "evaluators = []\n", "answers = []\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Anthropic API Call\n", "\n", "model_name = \"claude-3-7-sonnet-latest\"\n", "\n", "claude = Anthropic()\n", "faq_prompt = claude.messages.create(\n", " model=model_name, \n", " messages=messages, \n", " max_tokens=1000\n", ")\n", "\n", "faq_answer = faq_prompt.content[0].text\n", "\n", "display(Markdown(faq_answer))\n", "evaluators.append(model_name)\n", "answers.append(faq_answer)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# === 2. Google Gemini Call ===\n", "\n", "gemini = OpenAI(api_key=google_api_key, base_url=\"https://generativelanguage.googleapis.com/v1beta/openai/\")\n", "model_name = \"gemini-2.5-flash\"\n", "\n", "faq_prompt = gemini.chat.completions.create(model=model_name, messages=messages)\n", "faq_answer = faq_prompt.choices[0].message.content\n", "\n", "display(Markdown(faq_answer))\n", "evaluators.append(model_name)\n", "answers.append(faq_answer)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# === 2. Ollama Groq Call ===\n", "\n", "groq = OpenAI(api_key=groq_api_key, base_url=\"https://api.groq.com/openai/v1\")\n", "model_name = \"llama-3.3-70b-versatile\"\n", "\n", "faq_prompt = groq.chat.completions.create(model=model_name, messages=messages)\n", "faq_answer = faq_prompt.choices[0].message.content\n", "\n", "display(Markdown(faq_answer))\n", "evaluators.append(model_name)\n", "answers.append(faq_answer)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# It's nice to know how to use \"zip\"\n", "\n", "for evaluator, answer in zip(evaluators, answers):\n", " print(f\"Evaluator: {evaluator}\\n\\n{answer}\")\n", "\n", "\n", "# Let's bring this together - note the use of \"enumerate\"\n", "\n", "together = \"\"\n", "for index, answer in enumerate(answers):\n", " together += f\"# Response from evaluator {index+1}\\n\\n\"\n", " together += answer + \"\\n\\n\"" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "formatter = f\"\"\"You are a meticulous AI evaluator tasked with synthesizing multiple assistant-generated career FAQs and summaries into one high-quality file. You have received {len(evaluators)} drafts based on the same resume, each containing a 2-line summary and a set of FAQ questions with answers.\n", "\n", "---\n", "**Original Request:**\n", "\"{faq_prompt}\"\n", "---\n", "\n", "Your goal is to combine the strongest parts of each submission into a single, polished output. This will be the final `faq.txt` that lives in a public-facing portfolio folder.\n", "\n", "**Evaluation & Synthesis Instructions:**\n", "\n", "1. **Prioritize Accuracy:** Only include information clearly supported by the resume. Do not invent or speculate.\n", "2. **Best Questions Only:** Select the most relevant and insightful FAQ questions. Discard weak, redundant, or generic ones.\n", "3. **Edit for Quality:** Improve the clarity and fluency of answers. Fix grammar, wording, or formatting inconsistencies.\n", "4. **Merge Strengths:** If two assistants answer the same question differently, combine the best phrasing and facts from each.\n", "5. **Consistency in Voice:** Ensure a single professional tone throughout the summary and FAQ.\n", "\n", "**Required Output Structure:**\n", "\n", "1. **2-Line Summary:** Start with the best or synthesized version of the summary, capturing key career strengths.\n", "2. **FAQ Entries:** Follow with at least 8–12 strong FAQ entries in this format:\n", "\n", "Q: [Question] \n", "A: [Answer]\n", "\n", "---\n", "**Examples of Strong FAQ Topics:**\n", "- Key technical skills or languages\n", "- Past projects or employers\n", "- Teamwork or communication style\n", "- Remote work or leadership experience\n", "- Career goals or current availability\n", "\n", "This will be saved as a plain text file (`faq.txt`). Ensure the tone is accurate, clean, and helpful. Do not add unnecessary commentary or meta-analysis. The final version should look like it was written by a professional assistant who knows the subject well.\n", "\"\"\"\n", "\n", "formatter_messages = [{\"role\": \"user\", \"content\": formatter}]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# === 1. Final (Claude) API Call ===\n", "anthropic = Anthropic(api_key=anthropic_api_key)\n", "faq_prompt = anthropic.messages.create(\n", " model=\"claude-3-7-sonnet-latest\",\n", " messages=formatter_messages,\n", " max_tokens=1000,\n", ")\n", "results = faq_prompt.content[0].text\n", "display(Markdown(results))\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gr.ChatInterface(results, type=\"messages\").launch()" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.10" } }, "nbformat": 4, "nbformat_minor": 2 }