{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "94551532",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from dotenv import load_dotenv\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2fba5943",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ XAI_API_KEY found!\n"
]
}
],
"source": [
"from dotenv import load_dotenv\n",
"import os\n",
"\n",
"# This forces the notebook to pull keys from your .env file\n",
"load_dotenv(override=True)\n",
"\n",
"# Test if it's working (it should print the masked key or a success message)\n",
"if os.getenv(\"XAI_API_KEY\"):\n",
" print(\"✅ XAI_API_KEY found!\")\n",
"else:\n",
" print(\"❌ XAI_API_KEY NOT found. Check your .env file path.\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "476ded0b",
"metadata": {},
"outputs": [],
"source": [
"from __future__ import annotations\n",
"\n",
"import operator\n",
"import os\n",
"import re\n",
"from datetime import date, timedelta\n",
"from pathlib import Path\n",
"from typing import TypedDict, List, Optional, Literal, Annotated\n",
"\n",
"from pydantic import BaseModel, Field\n",
"\n",
"from langgraph.graph import StateGraph, START, END\n",
"from langgraph.types import Send\n",
"\n",
"# from langchain_openai import ChatOpenAI\n",
"from langchain_core.messages import SystemMessage, HumanMessage\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "ecb4e276",
"metadata": {},
"outputs": [],
"source": [
"# -----------------------------\n",
"# 1) Schemas\n",
"# -----------------------------\n",
"class Task(BaseModel):\n",
" id: int\n",
" title: str\n",
"\n",
" goal: str = Field(\n",
" ...,\n",
" description=\"One sentence describing what the reader should be able to do/understand after this section.\",\n",
" )\n",
" bullets: List[str] = Field(\n",
" ...,\n",
" min_length=3,\n",
" max_length=6,\n",
" description=\"3–6 concrete, non-overlapping subpoints to cover in this section.\",\n",
" )\n",
" target_words: int = Field(..., description=\"Target word count for this section (120–550).\")\n",
"\n",
" tags: List[str] = Field(default_factory=list)\n",
" requires_research: bool = False\n",
" requires_citations: bool = False\n",
" requires_code: bool = False\n",
"\n",
"\n",
"class Plan(BaseModel):\n",
" blog_title: str\n",
" audience: str\n",
" tone: str\n",
" blog_kind: Literal[\"explainer\", \"tutorial\", \"news_roundup\", \"comparison\", \"system_design\"] = \"explainer\"\n",
" constraints: List[str] = Field(default_factory=list)\n",
" tasks: List[Task]\n",
"\n",
"\n",
"class EvidenceItem(BaseModel):\n",
" title: str\n",
" url: str\n",
" published_at: Optional[str] = None # keep if Tavily provides; DO NOT rely on it\n",
" snippet: Optional[str] = None\n",
" source: Optional[str] = None\n",
"\n",
"\n",
"class RouterDecision(BaseModel):\n",
" needs_research: bool\n",
" mode: Literal[\"closed_book\", \"hybrid\", \"open_book\"]\n",
" queries: List[str] = Field(default_factory=list)\n",
"\n",
"\n",
"class EvidencePack(BaseModel):\n",
" evidence: List[EvidenceItem] = Field(default_factory=list)\n",
"\n",
"\n",
"class ImageSpec(BaseModel):\n",
" placeholder: str = Field(..., description=\"e.g. [[IMAGE_1]]\")\n",
" filename: str = Field(..., description=\"Save under images/, e.g. qkv_flow.png\")\n",
" alt: str\n",
" caption: str\n",
" prompt: str = Field(..., description=\"Prompt to send to the image model.\")\n",
" size: Literal[\"1024x1024\", \"1024x1536\", \"1536x1024\"] = \"1024x1024\"\n",
" quality: Literal[\"low\", \"medium\", \"high\"] = \"medium\"\n",
"\n",
"\n",
"class GlobalImagePlan(BaseModel):\n",
" md_with_placeholders: str\n",
" images: List[ImageSpec] = Field(default_factory=list)\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "6d9f8957",
"metadata": {},
"outputs": [],
"source": [
"class State(TypedDict):\n",
" topic: str\n",
"\n",
" # routing / research\n",
" mode: str\n",
" needs_research: bool\n",
" queries: List[str]\n",
" evidence: List[EvidenceItem]\n",
" plan: Optional[Plan]\n",
"\n",
" # workers\n",
" sections: Annotated[List[tuple[int, str]], operator.add] # (task_id, section_md)\n",
"\n",
" # reducer/image\n",
" merged_md: str\n",
" md_with_placeholders: str\n",
" image_specs: List[dict]\n",
"\n",
" final: str\n",
"\n",
"\n",
"# -----------------------------\n",
"# 2) LLM\n",
"# -----------------------------\n",
"from langchain_groq import ChatGroq\n",
"\n",
"llm = ChatGroq(\n",
" model=\"llama-3.3-70b-versatile\", # Or another Groq-supported model\n",
" api_key=os.getenv(\"XAI_API_KEY\") # This is your gsk_ key\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "da1c0162",
"metadata": {},
"outputs": [],
"source": [
"# -----------------------------\n",
"# 3) Router (decide upfront)\n",
"# -----------------------------\n",
"\n",
"load_dotenv()\n",
"# Use the correct 2026 model ID\n",
"# llm = ChatXAI(model=\"grok-4.20-non-reasoning\")\n",
"\n",
"\n",
"ROUTER_SYSTEM = \"\"\"You are a routing module for a technical blog planner.\n",
"\n",
"Decide whether web research is needed BEFORE planning.\n",
"\n",
"Modes:\n",
"- closed_book (needs_research=false):\n",
" Evergreen topics where correctness does not depend on recent facts (concepts, fundamentals).\n",
"- hybrid (needs_research=true):\n",
" Mostly evergreen but needs up-to-date examples/tools/models to be useful.\n",
"- open_book (needs_research=true):\n",
" Mostly volatile: weekly roundups, \"this week\", \"latest\", rankings, pricing, policy/regulation.\n",
"\n",
"If needs_research=true:\n",
"- Output 3–10 high-signal queries.\n",
"- Queries should be scoped and specific (avoid generic queries like just \"AI\" or \"LLM\").\n",
"- If user asked for \"last week/this week/latest\", reflect that constraint IN THE QUERIES.\n",
"\"\"\"\n",
"\n",
"def router_node(state: State) -> dict:\n",
" \n",
" topic = state[\"topic\"]\n",
" decider = llm.with_structured_output(RouterDecision)\n",
" decision = decider.invoke(\n",
" [\n",
" SystemMessage(content=ROUTER_SYSTEM),\n",
" HumanMessage(content=f\"Topic: {topic}\"),\n",
" ]\n",
" )\n",
"\n",
" return {\n",
" \"needs_research\": decision.needs_research,\n",
" \"mode\": decision.mode,\n",
" \"queries\": decision.queries,\n",
" }\n",
"\n",
"def route_next(state: State) -> str:\n",
" return \"research\" if state[\"needs_research\"] else \"orchestrator\"\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e09e9229",
"metadata": {},
"outputs": [],
"source": [
"# -----------------------------\n",
"# 4) Research (Tavily) \n",
"# -----------------------------\n",
"def _tavily_search(query: str, max_results: int = 5) -> List[dict]:\n",
" \n",
" tool = TavilySearchResults(max_results=max_results)\n",
" results = tool.invoke({\"query\": query})\n",
"\n",
" normalized: List[dict] = []\n",
" for r in results or []:\n",
" normalized.append(\n",
" {\n",
" \"title\": r.get(\"title\") or \"\",\n",
" \"url\": r.get(\"url\") or \"\",\n",
" \"snippet\": r.get(\"content\") or r.get(\"snippet\") or \"\",\n",
" \"published_at\": r.get(\"published_date\") or r.get(\"published_at\"),\n",
" \"source\": r.get(\"source\"),\n",
" }\n",
" )\n",
" return normalized\n",
"\n",
"\n",
"RESEARCH_SYSTEM = \"\"\"You are a research synthesizer for technical writing.\n",
"\n",
"Given raw web search results, produce a deduplicated list of EvidenceItem objects.\n",
"\n",
"Rules:\n",
"- Only include items with a non-empty url.\n",
"- Prefer relevant + authoritative sources (company blogs, docs, reputable outlets).\n",
"- If a published date is explicitly present in the result payload, keep it as YYYY-MM-DD.\n",
" If missing or unclear, set published_at=null. Do NOT guess.\n",
"- Keep snippets short.\n",
"- Deduplicate by URL.\n",
"\"\"\"\n",
"\n",
"def research_node(state: State) -> dict:\n",
"\n",
" # take the first 10 queries from state\n",
" queries = (state.get(\"queries\", []) or [])\n",
" max_results = 6\n",
"\n",
" raw_results: List[dict] = []\n",
"\n",
" for q in queries:\n",
" raw_results.extend(_tavily_search(q, max_results=max_results))\n",
"\n",
" if not raw_results:\n",
" return {\"evidence\": []}\n",
"\n",
" extractor = llm.with_structured_output(EvidencePack)\n",
" pack = extractor.invoke(\n",
" [\n",
" SystemMessage(content=RESEARCH_SYSTEM),\n",
" HumanMessage(content=f\"Raw results:\\n{raw_results}\"),\n",
" ]\n",
" )\n",
"\n",
" # Deduplicate by URL\n",
" dedup = {}\n",
" for e in pack.evidence:\n",
" if e.url:\n",
" dedup[e.url] = e\n",
"\n",
" return {\"evidence\": list(dedup.values())}\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "1e6c91ba",
"metadata": {},
"outputs": [],
"source": [
"# -----------------------------\n",
"# 5) Orchestrator (Plan)\n",
"# -----------------------------\n",
"ORCH_SYSTEM = \"\"\"You are a senior technical writer and developer advocate.\n",
"Your job is to produce a highly actionable outline for a technical blog post.\n",
"\n",
"Hard requirements:\n",
"- Create 5–9 sections (tasks) suitable for the topic and audience.\n",
"- Each task must include:\n",
" 1) goal (1 sentence)\n",
" 2) 3–6 bullets that are concrete, specific, and non-overlapping\n",
" 3) target word count (120–550)\n",
"\n",
"Quality bar:\n",
"- Assume the reader is a developer; use correct terminology.\n",
"- Bullets must be actionable: build/compare/measure/verify/debug.\n",
"- Ensure the overall plan includes at least 2 of these somewhere:\n",
" * minimal code sketch / MWE (set requires_code=True for that section)\n",
" * edge cases / failure modes\n",
" * performance/cost considerations\n",
" * security/privacy considerations (if relevant)\n",
" * debugging/observability tips\n",
"\n",
"Grounding rules:\n",
"- Mode closed_book: keep it evergreen; do not depend on evidence.\n",
"- Mode hybrid:\n",
" - Use evidence for up-to-date examples (models/tools/releases) in bullets.\n",
" - Mark sections using fresh info as requires_research=True and requires_citations=True.\n",
"- Mode open_book:\n",
" - Set blog_kind = \"news_roundup\".\n",
" - Every section is about summarizing events + implications.\n",
" - DO NOT include tutorial/how-to sections unless user explicitly asked for that.\n",
" - If evidence is empty or insufficient, create a plan that transparently says \"insufficient sources\"\n",
" and includes only what can be supported.\n",
"\n",
"Output must strictly match the Plan schema.\n",
"\"\"\"\n",
"\n",
"def orchestrator_node(state: State) -> dict:\n",
" planner = llm.with_structured_output(Plan)\n",
"\n",
" evidence = state.get(\"evidence\", [])\n",
" mode = state.get(\"mode\", \"closed_book\")\n",
"\n",
" plan = planner.invoke(\n",
" [\n",
" SystemMessage(content=ORCH_SYSTEM),\n",
" HumanMessage(\n",
" content=(\n",
" f\"Topic: {state['topic']}\\n\"\n",
" f\"Mode: {mode}\\n\\n\"\n",
" f\"Evidence (ONLY use for fresh claims; may be empty):\\n\"\n",
" f\"{[e.model_dump() for e in evidence][:16]}\"\n",
" )\n",
" ),\n",
" ]\n",
" )\n",
"\n",
" return {\"plan\": plan}\n",
"\n",
"# -----------------------------\n",
"# 6) Fanout\n",
"# -----------------------------\n",
"def fanout(state: State):\n",
" return [\n",
" Send(\n",
" \"worker\",\n",
" {\n",
" \"task\": task.model_dump(),\n",
" \"topic\": state[\"topic\"],\n",
" \"mode\": state[\"mode\"],\n",
" \"plan\": state[\"plan\"].model_dump(),\n",
" \"evidence\": [e.model_dump() for e in state.get(\"evidence\", [])],\n",
" },\n",
" )\n",
" for task in state[\"plan\"].tasks\n",
" ]\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "f634ce01",
"metadata": {},
"outputs": [],
"source": [
"# -----------------------------\n",
"# 7) Worker (write one section)\n",
"# -----------------------------\n",
"WORKER_SYSTEM = \"\"\"You are a senior technical writer and developer advocate.\n",
"Write ONE section of a technical blog post in Markdown.\n",
"\n",
"Hard constraints:\n",
"- Follow the provided Goal and cover ALL Bullets in order (do not skip or merge bullets).\n",
"- Stay close to Target words (±15%).\n",
"- Output ONLY the section content in Markdown (no blog title H1, no extra commentary).\n",
"- Start with a '## <Section Title>' heading.\n",
"\n",
"Scope guard:\n",
"- If blog_kind == \"news_roundup\": do NOT turn this into a tutorial/how-to guide.\n",
" Do NOT teach web scraping, RSS, automation, or \"how to fetch news\" unless bullets explicitly ask for it.\n",
" Focus on summarizing events and implications.\n",
"\n",
"Grounding policy:\n",
"- If mode == open_book:\n",
" - Do NOT introduce any specific event/company/model/funding/policy claim unless it is supported by provided Evidence URLs.\n",
" - For each event claim, attach a source as a Markdown link: ([Source](URL)).\n",
" - Only use URLs provided in Evidence. If not supported, write: \"Not found in provided sources.\"\n",
"- If requires_citations == true:\n",
" - For outside-world claims, cite Evidence URLs the same way.\n",
"- Evergreen reasoning is OK without citations unless requires_citations is true.\n",
"\n",
"Code:\n",
"- If requires_code == true, include at least one minimal, correct code snippet relevant to the bullets.\n",
"\n",
"Style:\n",
"- Short paragraphs, bullets where helpful, code fences for code.\n",
"- Avoid fluff/marketing. Be precise and implementation-oriented.\n",
"\"\"\"\n",
"\n",
"def worker_node(payload: dict) -> dict:\n",
" \n",
" task = Task(**payload[\"task\"])\n",
" plan = Plan(**payload[\"plan\"])\n",
" evidence = [EvidenceItem(**e) for e in payload.get(\"evidence\", [])]\n",
" topic = payload[\"topic\"]\n",
" mode = payload.get(\"mode\", \"closed_book\")\n",
"\n",
" bullets_text = \"\\n- \" + \"\\n- \".join(task.bullets)\n",
"\n",
" evidence_text = \"\"\n",
" if evidence:\n",
" evidence_text = \"\\n\".join(\n",
" f\"- {e.title} | {e.url} | {e.published_at or 'date:unknown'}\".strip()\n",
" for e in evidence[:20]\n",
" )\n",
"\n",
" section_md = llm.invoke(\n",
" [\n",
" SystemMessage(content=WORKER_SYSTEM),\n",
" HumanMessage(\n",
" content=(\n",
" f\"Blog title: {plan.blog_title}\\n\"\n",
" f\"Audience: {plan.audience}\\n\"\n",
" f\"Tone: {plan.tone}\\n\"\n",
" f\"Blog kind: {plan.blog_kind}\\n\"\n",
" f\"Constraints: {plan.constraints}\\n\"\n",
" f\"Topic: {topic}\\n\"\n",
" f\"Mode: {mode}\\n\\n\"\n",
" f\"Section title: {task.title}\\n\"\n",
" f\"Goal: {task.goal}\\n\"\n",
" f\"Target words: {task.target_words}\\n\"\n",
" f\"Tags: {task.tags}\\n\"\n",
" f\"requires_research: {task.requires_research}\\n\"\n",
" f\"requires_citations: {task.requires_citations}\\n\"\n",
" f\"requires_code: {task.requires_code}\\n\"\n",
" f\"Bullets:{bullets_text}\\n\\n\"\n",
" f\"Evidence (ONLY use these URLs when citing):\\n{evidence_text}\\n\"\n",
" )\n",
" ),\n",
" ]\n",
" ).content.strip()\n",
"\n",
" return {\"sections\": [(task.id, section_md)]}\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "ea4856b2",
"metadata": {},
"outputs": [],
"source": [
"# ============================================================\n",
"# 8) ReducerWithImages (subgraph)\n",
"# merge_content -> decide_images -> generate_and_place_images\n",
"# ============================================================\n",
"def merge_content(state: State) -> dict:\n",
"\n",
" plan = state[\"plan\"]\n",
"\n",
" ordered_sections = [md for _, md in sorted(state[\"sections\"], key=lambda x: x[0])]\n",
" body = \"\\n\\n\".join(ordered_sections).strip()\n",
" merged_md = f\"# {plan.blog_title}\\n\\n{body}\\n\"\n",
" return {\"merged_md\": merged_md}\n",
"\n",
"\n",
"DECIDE_IMAGES_SYSTEM = \"\"\"You are an expert technical editor.\n",
"Decide if images/diagrams are needed for THIS blog.\n",
"\n",
"Rules:\n",
"- Max 3 images total.\n",
"- Each image must materially improve understanding (diagram/flow/table-like visual).\n",
"- Insert placeholders exactly: [[IMAGE_1]], [[IMAGE_2]], [[IMAGE_3]].\n",
"- If no images needed: md_with_placeholders must equal input and images=[].\n",
"- Avoid decorative images; prefer technical diagrams with short labels.\n",
"Return strictly GlobalImagePlan.\n",
"\"\"\"\n",
"\n",
"def decide_images(state: State) -> dict:\n",
" \n",
" planner = llm.with_structured_output(GlobalImagePlan)\n",
" merged_md = state[\"merged_md\"]\n",
" plan = state[\"plan\"]\n",
" assert plan is not None\n",
"\n",
" image_plan = planner.invoke(\n",
" [\n",
" SystemMessage(content=DECIDE_IMAGES_SYSTEM),\n",
" HumanMessage(\n",
" content=(\n",
" f\"Blog kind: {plan.blog_kind}\\n\"\n",
" f\"Topic: {state['topic']}\\n\\n\"\n",
" \"Insert placeholders + propose image prompts.\\n\\n\"\n",
" f\"{merged_md}\"\n",
" )\n",
" ),\n",
" ]\n",
" )\n",
"\n",
" return {\n",
" \"md_with_placeholders\": image_plan.md_with_placeholders,\n",
" \"image_specs\": [img.model_dump() for img in image_plan.images],\n",
" }\n",
"\n",
"\n",
"def _gemini_generate_image_bytes(prompt: str) -> bytes:\n",
" \"\"\"\n",
" Returns raw image bytes generated by Gemini.\n",
" Requires: pip install google-genai\n",
" Env var: GOOGLE_API_KEY\n",
" \"\"\"\n",
" from google import genai\n",
" from google.genai import types\n",
"\n",
" api_key = os.environ.get(\"GOOGLE_API_KEY\")\n",
" if not api_key:\n",
" raise RuntimeError(\"GOOGLE_API_KEY is not set.\")\n",
"\n",
" client = genai.Client(api_key=api_key)\n",
"\n",
" resp = client.models.generate_content(\n",
" model=\"gemini-3.1-flash-image-preview\",\n",
" contents=prompt,\n",
" config=types.GenerateContentConfig(\n",
" response_modalities=[\"IMAGE\"],\n",
" safety_settings=[\n",
" types.SafetySetting(\n",
" category=\"HARM_CATEGORY_DANGEROUS_CONTENT\",\n",
" threshold=\"BLOCK_ONLY_HIGH\",\n",
" )\n",
" ],\n",
" ),\n",
" )\n",
"\n",
" # Depending on SDK version, parts may hang off resp.candidates[0].content.parts\n",
" parts = getattr(resp, \"parts\", None)\n",
" if not parts and getattr(resp, \"candidates\", None):\n",
" try:\n",
" parts = resp.candidates[0].content.parts\n",
" except Exception:\n",
" parts = None\n",
"\n",
" if not parts:\n",
" raise RuntimeError(\"No image content returned (safety/quota/SDK change).\")\n",
"\n",
" for part in parts:\n",
" inline = getattr(part, \"inline_data\", None)\n",
" if inline and getattr(inline, \"data\", None):\n",
" return inline.data\n",
"\n",
" raise RuntimeError(\"No inline image bytes found in response.\")\n",
"\n",
"\n",
"def generate_and_place_images(state: State) -> dict:\n",
"\n",
" plan = state[\"plan\"]\n",
" assert plan is not None\n",
"\n",
" md = state.get(\"md_with_placeholders\") or state[\"merged_md\"]\n",
" image_specs = state.get(\"image_specs\", []) or []\n",
"\n",
" # If no images requested, just write merged markdown\n",
" if not image_specs:\n",
" filename = f\"{plan.blog_title}.md\"\n",
" Path(filename).write_text(md, encoding=\"utf-8\")\n",
" return {\"final\": md}\n",
"\n",
" images_dir = Path(\"images\")\n",
" images_dir.mkdir(exist_ok=True)\n",
"\n",
" for spec in image_specs:\n",
" placeholder = spec[\"placeholder\"]\n",
" filename = spec[\"filename\"]\n",
" out_path = images_dir / filename\n",
"\n",
" # generate only if needed\n",
" if not out_path.exists():\n",
" try:\n",
" img_bytes = _gemini_generate_image_bytes(spec[\"prompt\"])\n",
" out_path.write_bytes(img_bytes)\n",
" except Exception as e:\n",
" # graceful fallback: keep doc usable\n",
" prompt_block = (\n",
" f\"> **[IMAGE GENERATION FAILED]** {spec.get('caption','')}\\n>\\n\"\n",
" f\"> **Alt:** {spec.get('alt','')}\\n>\\n\"\n",
" f\"> **Prompt:** {spec.get('prompt','')}\\n>\\n\"\n",
" f\"> **Error:** {e}\\n\"\n",
" )\n",
" md = md.replace(placeholder, prompt_block)\n",
" continue\n",
"\n",
" img_md = f\"![{spec['alt']}](images/{filename})\\n*{spec['caption']}*\"\n",
" md = md.replace(placeholder, img_md)\n",
"\n",
" filename = f\"{plan.blog_title}.md\"\n",
" Path(filename).write_text(md, encoding=\"utf-8\")\n",
" return {\"final\": md}\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "cebc44ef",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<langgraph.graph.state.CompiledStateGraph object at 0x11bd178c0>"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# build reducer subgraph\n",
"reducer_graph = StateGraph(State)\n",
"reducer_graph.add_node(\"merge_content\", merge_content)\n",
"reducer_graph.add_node(\"decide_images\", decide_images)\n",
"reducer_graph.add_node(\"generate_and_place_images\", generate_and_place_images)\n",
"reducer_graph.add_edge(START, \"merge_content\")\n",
"reducer_graph.add_edge(\"merge_content\", \"decide_images\")\n",
"reducer_graph.add_edge(\"decide_images\", \"generate_and_place_images\")\n",
"reducer_graph.add_edge(\"generate_and_place_images\", END)\n",
"reducer_subgraph = reducer_graph.compile()\n",
"\n",
"reducer_subgraph\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "45b41ece",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<langgraph.graph.state.CompiledStateGraph object at 0x11c19c550>"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# -----------------------------\n",
"# 9) Build main graph\n",
"# -----------------------------\n",
"g = StateGraph(State)\n",
"g.add_node(\"router\", router_node)\n",
"g.add_node(\"research\", research_node)\n",
"g.add_node(\"orchestrator\", orchestrator_node)\n",
"g.add_node(\"worker\", worker_node)\n",
"g.add_node(\"reducer\", reducer_subgraph)\n",
"\n",
"g.add_edge(START, \"router\")\n",
"g.add_conditional_edges(\"router\", route_next, {\"research\": \"research\", \"orchestrator\": \"orchestrator\"})\n",
"g.add_edge(\"research\", \"orchestrator\")\n",
"\n",
"g.add_conditional_edges(\"orchestrator\", fanout, [\"worker\"])\n",
"g.add_edge(\"worker\", \"reducer\")\n",
"g.add_edge(\"reducer\", END)\n",
"\n",
"app = g.compile()\n",
"app\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "f2f5a07e",
"metadata": {},
"outputs": [],
"source": [
"# -----------------------------\n",
"# 10) Runner\n",
"# -----------------------------\n",
"def run(topic: str, as_of: Optional[str] = None):\n",
" if as_of is None:\n",
" as_of = date.today().isoformat()\n",
"\n",
" out = app.invoke(\n",
" {\n",
" \"topic\": topic,\n",
" \"mode\": \"\",\n",
" \"needs_research\": False,\n",
" \"queries\": [],\n",
" \"evidence\": [],\n",
" \"plan\": None,\n",
" \"as_of\": as_of,\n",
" \"recency_days\": 7,\n",
" \"sections\": [],\n",
" \"merged_md\": \"\",\n",
" \"md_with_placeholders\": \"\",\n",
" \"image_specs\": [],\n",
" \"final\": \"\",\n",
" }\n",
" )\n",
"\n",
" return out\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "5c066987",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'topic': 'Self Attention in Transformer Architecture',\n",
" 'mode': 'closed_book',\n",
" 'needs_research': False,\n",
" 'queries': [],\n",
" 'evidence': [],\n",
" 'plan': Plan(blog_title='Self Attention in Transformer Architecture', audience='developers', tone='technical', blog_kind='explainer', constraints=[], tasks=[Task(id=1, title='Introduction to Self Attention', goal='Understand the concept of self attention in transformer architecture', bullets=['Define self attention and its role in transformer models', 'Explain the difference between self attention and traditional attention mechanisms', 'Discuss the benefits of self attention in natural language processing tasks'], target_words=200, tags=['transformer', 'self attention'], requires_research=False, requires_citations=False, requires_code=False), Task(id=2, title='Mathematical Formulation of Self Attention', goal='Learn the mathematical formulation of self attention', bullets=['Derive the self attention equation step by step', 'Explain the role of query, key, and value matrices in self attention', 'Discuss the importance of scaling in self attention calculations'], target_words=300, tags=['math', 'transformer'], requires_research=False, requires_citations=False, requires_code=False), Task(id=3, title='Implementing Self Attention in Code', goal='Implement self attention in a simple transformer model', bullets=['Write a minimal code sketch for self attention using a popular deep learning library', 'Explain the key components of the self attention implementation', 'Discuss potential optimization techniques for self attention calculations'], target_words=250, tags=['code', 'transformer'], requires_research=False, requires_citations=False, requires_code=True), Task(id=4, title='Edge Cases and Failure Modes', goal='Understand potential edge cases and failure modes of self attention', bullets=['Discuss the impact of input size on self attention calculations', 'Explain the effects of sparse input data on self attention performance', 'Analyze the robustness of self attention to adversarial attacks'], target_words=220, tags=['edge cases', 'failure modes'], requires_research=False, requires_citations=False, requires_code=False), Task(id=5, title='Performance and Cost Considerations', goal='Learn about performance and cost considerations for self attention', bullets=['Discuss the computational complexity of self attention calculations', 'Explain the memory requirements for self attention implementations', 'Analyze the trade-offs between self attention and other attention mechanisms'], target_words=280, tags=['performance', 'cost'], requires_research=False, requires_citations=False, requires_code=False), Task(id=6, title='Debugging and Observability Tips', goal='Learn debugging and observability tips for self attention implementations', bullets=['Explain how to visualize self attention weights and activations', 'Discuss techniques for identifying and fixing common self attention bugs', 'Analyze the importance of monitoring self attention performance metrics'], target_words=240, tags=['debugging', 'observability'], requires_research=False, requires_citations=False, requires_code=False), Task(id=7, title='Security and Privacy Considerations', goal='Understand security and privacy considerations for self attention', bullets=['Discuss the potential risks of self attention in sensitive applications', 'Explain techniques for securing self attention implementations', 'Analyze the importance of data privacy in self attention models'], target_words=260, tags=['security', 'privacy'], requires_research=False, requires_citations=False, requires_code=False)]),\n",
" 'sections': [(1,\n",
" '## Introduction to Self Attention\\nSelf attention is a key component in transformer architecture, enabling the model to attend to different parts of the input sequence simultaneously. \\n* Define self attention and its role in transformer models: Self attention is a mechanism that allows the model to weigh the importance of different input elements relative to each other.\\n* Explain the difference between self attention and traditional attention mechanisms: Unlike traditional attention, self attention does not rely on recurrent neural networks (RNNs) or convolutional neural networks (CNNs), allowing for more parallelization.\\n* Discuss the benefits of self attention in natural language processing tasks: Self attention has been shown to be highly effective in various NLP tasks, such as machine translation and text classification, due to its ability to capture long-range dependencies and contextual relationships.'),\n",
" (2,\n",
" '## Mathematical Formulation of Self Attention\\nThe self attention mechanism is a core component of the Transformer architecture, allowing the model to attend to different parts of the input sequence simultaneously. To understand how self attention works, we need to derive the self attention equation step by step. The equation is based on the concept of attention, which is calculated as the weighted sum of the value matrix, where the weights are determined by the query and key matrices.\\n\\n* The self attention equation is derived as follows:\\n * First, we calculate the query (Q), key (K), and value (V) matrices from the input sequence.\\n * Then, we compute the attention scores by taking the dot product of Q and K and applying a scaling factor.\\n * The role of query, key, and value matrices in self attention is crucial: the query matrix represents the context in which the attention is being applied, the key matrix represents the information being attended to, and the value matrix represents the information being retrieved.\\n* The query, key, and value matrices are used to calculate the attention weights, which are then used to compute the final output of the self attention mechanism.\\n* The importance of scaling in self attention calculations cannot be overstated. The scaling factor helps to prevent the attention scores from becoming too large, which can lead to extremely small gradients during backpropagation. This, in turn, can make it difficult to train the model effectively. By scaling the attention scores, we can ensure that the gradients remain manageable and the model can learn effectively.'),\n",
" (3,\n",
" '## Implementing Self Attention in Code\\nTo implement self attention in a simple transformer model, we can utilize a popular deep learning library like PyTorch. \\n### Code Implementation\\n```python\\nimport torch\\nimport torch.nn as nn\\nimport torch.nn.functional as F\\n\\nclass SelfAttention(nn.Module):\\n def __init__(self, embed_dim, num_heads):\\n super(SelfAttention, self).__init__()\\n self.embed_dim = embed_dim\\n self.num_heads = num_heads\\n self.query_linear = nn.Linear(embed_dim, embed_dim)\\n self.key_linear = nn.Linear(embed_dim, embed_dim)\\n self.value_linear = nn.Linear(embed_dim, embed_dim)\\n\\n def forward(self, x):\\n # Get query, key, and value matrices\\n Q = self.query_linear(x)\\n K = self.key_linear(x)\\n V = self.value_linear(x)\\n\\n # Calculate self attention scores\\n attention_scores = torch.matmul(Q, K.T) / math.sqrt(self.embed_dim)\\n\\n # Calculate weighted sum of value matrix\\n attention_weights = F.softmax(attention_scores, dim=-1)\\n output = torch.matmul(attention_weights, V)\\n\\n return output\\n```\\n### Key Components and Optimization\\n* The key components of the self attention implementation include the query, key, and value matrices, which are calculated using linear layers.\\n* Potential optimization techniques for self attention calculations include using sparse attention patterns or knowledge distillation to reduce computational complexity.'),\n",
" (4,\n",
" '## Edge Cases and Failure Modes\\nSelf attention in Transformer architecture can be affected by several edge cases and failure modes. \\nThe input size has a significant impact on self attention calculations, as larger input sizes increase computational complexity and memory requirements. \\nThis can lead to slower processing times and increased risk of overflow errors.\\n\\n* Sparse input data can also affect self attention performance, as the attention mechanism may struggle to capture relevant information from sparse inputs.\\n* The robustness of self attention to adversarial attacks is a concern, as self attention can be vulnerable to attacks that manipulate the input data to mislead the attention mechanism. \\nIn such cases, the model may produce suboptimal results or fail to generalize well to new data. \\nUnderstanding these edge cases and failure modes is crucial to designing and training robust self attention models.'),\n",
" (5,\n",
" '## Performance and Cost Considerations\\nThe self attention mechanism in Transformer architecture has significant performance and cost implications. \\n* The computational complexity of self attention calculations is a major consideration, as it involves computing attention weights for all pairs of input elements, resulting in a time complexity of O(n^2), where n is the sequence length.\\n* The memory requirements for self attention implementations are also substantial, as they require storing attention weights and intermediate results, which can lead to high memory usage for long sequences.\\n* When evaluating the trade-offs between self attention and other attention mechanisms, such as local attention or hierarchical attention, developers must balance the benefits of self attention, including its ability to capture long-range dependencies, against its computational and memory costs. \\nOverall, understanding these performance and cost considerations is crucial for effective implementation and optimization of self attention in Transformer-based models.'),\n",
" (6,\n",
" \"## Debugging and Observability Tips\\nTo effectively debug and monitor self attention implementations, several techniques can be employed. \\n* Visualizing self attention weights and activations can provide valuable insights into the model's behavior, allowing developers to understand which parts of the input are being focused on.\\n* Identifying and fixing common self attention bugs, such as incorrect weight initialization or faulty attention masking, can be achieved through careful code review and testing.\\n* Monitoring self attention performance metrics, including attention distribution and loss curves, is crucial for optimizing model performance and detecting potential issues. \\nBy applying these techniques, developers can improve the reliability and efficiency of their self attention implementations.\"),\n",
" (7,\n",
" '## Security and Privacy Considerations\\nSelf attention in transformer architecture can pose significant security and privacy risks, particularly in sensitive applications. \\n* The use of self attention can lead to information leakage, where sensitive data is inadvertently exposed through the attention mechanism.\\n* Additionally, self attention can be vulnerable to adversarial attacks, which can compromise the integrity of the model.\\n\\nTechniques for securing self attention implementations include:\\n* Implementing robust regularization techniques to prevent overfitting and reduce the risk of information leakage.\\n* Using secure multi-party computation protocols to protect sensitive data during the attention computation process.\\n\\nThe importance of data privacy in self attention models cannot be overstated. \\n* Self attention models often require access to large amounts of sensitive data, which can be a significant privacy concern if not handled properly.\\n* As such, it is crucial to implement robust data protection measures, such as data anonymization and encryption, to ensure the confidentiality and integrity of sensitive data. \\n* By prioritizing data privacy and security, developers can ensure that self attention models are used responsibly and with minimal risk.'),\n",
" (1,\n",
" '## Introduction to Self Attention\\nSelf attention is a key component in transformer architecture, enabling the model to attend to different parts of the input sequence simultaneously. \\n* Define self attention and its role in transformer models: Self attention is a mechanism that allows the model to weigh the importance of different input elements relative to each other.\\n* Explain the difference between self attention and traditional attention mechanisms: Unlike traditional attention, self attention does not rely on recurrent neural networks (RNNs) or convolutional neural networks (CNNs), allowing for more parallelization.\\n* Discuss the benefits of self attention in natural language processing tasks: Self attention has been shown to be highly effective in various NLP tasks, such as machine translation and text classification, due to its ability to capture long-range dependencies and contextual relationships.'),\n",
" (2,\n",
" '## Mathematical Formulation of Self Attention\\nThe self attention mechanism is a core component of the Transformer architecture, allowing the model to attend to different parts of the input sequence simultaneously. To understand how self attention works, we need to derive the self attention equation step by step. The equation is based on the concept of attention, which is calculated as the weighted sum of the value matrix, where the weights are determined by the query and key matrices.\\n\\n* The self attention equation is derived as follows:\\n * First, we calculate the query (Q), key (K), and value (V) matrices from the input sequence.\\n * Then, we compute the attention scores by taking the dot product of Q and K and applying a scaling factor.\\n * The role of query, key, and value matrices in self attention is crucial: the query matrix represents the context in which the attention is being applied, the key matrix represents the information being attended to, and the value matrix represents the information being retrieved.\\n* The query, key, and value matrices are used to calculate the attention weights, which are then used to compute the final output of the self attention mechanism.\\n* The importance of scaling in self attention calculations cannot be overstated. The scaling factor helps to prevent the attention scores from becoming too large, which can lead to extremely small gradients during backpropagation. This, in turn, can make it difficult to train the model effectively. By scaling the attention scores, we can ensure that the gradients remain manageable and the model can learn effectively.'),\n",
" (3,\n",
" '## Implementing Self Attention in Code\\nTo implement self attention in a simple transformer model, we can utilize a popular deep learning library like PyTorch. \\n### Code Implementation\\n```python\\nimport torch\\nimport torch.nn as nn\\nimport torch.nn.functional as F\\n\\nclass SelfAttention(nn.Module):\\n def __init__(self, embed_dim, num_heads):\\n super(SelfAttention, self).__init__()\\n self.embed_dim = embed_dim\\n self.num_heads = num_heads\\n self.query_linear = nn.Linear(embed_dim, embed_dim)\\n self.key_linear = nn.Linear(embed_dim, embed_dim)\\n self.value_linear = nn.Linear(embed_dim, embed_dim)\\n\\n def forward(self, x):\\n # Get query, key, and value matrices\\n Q = self.query_linear(x)\\n K = self.key_linear(x)\\n V = self.value_linear(x)\\n\\n # Calculate self attention scores\\n attention_scores = torch.matmul(Q, K.T) / math.sqrt(self.embed_dim)\\n\\n # Calculate weighted sum of value matrix\\n attention_weights = F.softmax(attention_scores, dim=-1)\\n output = torch.matmul(attention_weights, V)\\n\\n return output\\n```\\n### Key Components and Optimization\\n* The key components of the self attention implementation include the query, key, and value matrices, which are calculated using linear layers.\\n* Potential optimization techniques for self attention calculations include using sparse attention patterns or knowledge distillation to reduce computational complexity.'),\n",
" (4,\n",
" '## Edge Cases and Failure Modes\\nSelf attention in Transformer architecture can be affected by several edge cases and failure modes. \\nThe input size has a significant impact on self attention calculations, as larger input sizes increase computational complexity and memory requirements. \\nThis can lead to slower processing times and increased risk of overflow errors.\\n\\n* Sparse input data can also affect self attention performance, as the attention mechanism may struggle to capture relevant information from sparse inputs.\\n* The robustness of self attention to adversarial attacks is a concern, as self attention can be vulnerable to attacks that manipulate the input data to mislead the attention mechanism. \\nIn such cases, the model may produce suboptimal results or fail to generalize well to new data. \\nUnderstanding these edge cases and failure modes is crucial to designing and training robust self attention models.'),\n",
" (5,\n",
" '## Performance and Cost Considerations\\nThe self attention mechanism in Transformer architecture has significant performance and cost implications. \\n* The computational complexity of self attention calculations is a major consideration, as it involves computing attention weights for all pairs of input elements, resulting in a time complexity of O(n^2), where n is the sequence length.\\n* The memory requirements for self attention implementations are also substantial, as they require storing attention weights and intermediate results, which can lead to high memory usage for long sequences.\\n* When evaluating the trade-offs between self attention and other attention mechanisms, such as local attention or hierarchical attention, developers must balance the benefits of self attention, including its ability to capture long-range dependencies, against its computational and memory costs. \\nOverall, understanding these performance and cost considerations is crucial for effective implementation and optimization of self attention in Transformer-based models.'),\n",
" (6,\n",
" \"## Debugging and Observability Tips\\nTo effectively debug and monitor self attention implementations, several techniques can be employed. \\n* Visualizing self attention weights and activations can provide valuable insights into the model's behavior, allowing developers to understand which parts of the input are being focused on.\\n* Identifying and fixing common self attention bugs, such as incorrect weight initialization or faulty attention masking, can be achieved through careful code review and testing.\\n* Monitoring self attention performance metrics, including attention distribution and loss curves, is crucial for optimizing model performance and detecting potential issues. \\nBy applying these techniques, developers can improve the reliability and efficiency of their self attention implementations.\"),\n",
" (7,\n",
" '## Security and Privacy Considerations\\nSelf attention in transformer architecture can pose significant security and privacy risks, particularly in sensitive applications. \\n* The use of self attention can lead to information leakage, where sensitive data is inadvertently exposed through the attention mechanism.\\n* Additionally, self attention can be vulnerable to adversarial attacks, which can compromise the integrity of the model.\\n\\nTechniques for securing self attention implementations include:\\n* Implementing robust regularization techniques to prevent overfitting and reduce the risk of information leakage.\\n* Using secure multi-party computation protocols to protect sensitive data during the attention computation process.\\n\\nThe importance of data privacy in self attention models cannot be overstated. \\n* Self attention models often require access to large amounts of sensitive data, which can be a significant privacy concern if not handled properly.\\n* As such, it is crucial to implement robust data protection measures, such as data anonymization and encryption, to ensure the confidentiality and integrity of sensitive data. \\n* By prioritizing data privacy and security, developers can ensure that self attention models are used responsibly and with minimal risk.')],\n",
" 'merged_md': \"# Self Attention in Transformer Architecture\\n\\n## Introduction to Self Attention\\nSelf attention is a key component in transformer architecture, enabling the model to attend to different parts of the input sequence simultaneously. \\n* Define self attention and its role in transformer models: Self attention is a mechanism that allows the model to weigh the importance of different input elements relative to each other.\\n* Explain the difference between self attention and traditional attention mechanisms: Unlike traditional attention, self attention does not rely on recurrent neural networks (RNNs) or convolutional neural networks (CNNs), allowing for more parallelization.\\n* Discuss the benefits of self attention in natural language processing tasks: Self attention has been shown to be highly effective in various NLP tasks, such as machine translation and text classification, due to its ability to capture long-range dependencies and contextual relationships.\\n\\n## Mathematical Formulation of Self Attention\\nThe self attention mechanism is a core component of the Transformer architecture, allowing the model to attend to different parts of the input sequence simultaneously. To understand how self attention works, we need to derive the self attention equation step by step. The equation is based on the concept of attention, which is calculated as the weighted sum of the value matrix, where the weights are determined by the query and key matrices.\\n\\n* The self attention equation is derived as follows:\\n * First, we calculate the query (Q), key (K), and value (V) matrices from the input sequence.\\n * Then, we compute the attention scores by taking the dot product of Q and K and applying a scaling factor.\\n * The role of query, key, and value matrices in self attention is crucial: the query matrix represents the context in which the attention is being applied, the key matrix represents the information being attended to, and the value matrix represents the information being retrieved.\\n* The query, key, and value matrices are used to calculate the attention weights, which are then used to compute the final output of the self attention mechanism.\\n* The importance of scaling in self attention calculations cannot be overstated. The scaling factor helps to prevent the attention scores from becoming too large, which can lead to extremely small gradients during backpropagation. This, in turn, can make it difficult to train the model effectively. By scaling the attention scores, we can ensure that the gradients remain manageable and the model can learn effectively.\\n\\n## Implementing Self Attention in Code\\nTo implement self attention in a simple transformer model, we can utilize a popular deep learning library like PyTorch. \\n### Code Implementation\\n```python\\nimport torch\\nimport torch.nn as nn\\nimport torch.nn.functional as F\\n\\nclass SelfAttention(nn.Module):\\n def __init__(self, embed_dim, num_heads):\\n super(SelfAttention, self).__init__()\\n self.embed_dim = embed_dim\\n self.num_heads = num_heads\\n self.query_linear = nn.Linear(embed_dim, embed_dim)\\n self.key_linear = nn.Linear(embed_dim, embed_dim)\\n self.value_linear = nn.Linear(embed_dim, embed_dim)\\n\\n def forward(self, x):\\n # Get query, key, and value matrices\\n Q = self.query_linear(x)\\n K = self.key_linear(x)\\n V = self.value_linear(x)\\n\\n # Calculate self attention scores\\n attention_scores = torch.matmul(Q, K.T) / math.sqrt(self.embed_dim)\\n\\n # Calculate weighted sum of value matrix\\n attention_weights = F.softmax(attention_scores, dim=-1)\\n output = torch.matmul(attention_weights, V)\\n\\n return output\\n```\\n### Key Components and Optimization\\n* The key components of the self attention implementation include the query, key, and value matrices, which are calculated using linear layers.\\n* Potential optimization techniques for self attention calculations include using sparse attention patterns or knowledge distillation to reduce computational complexity.\\n\\n## Edge Cases and Failure Modes\\nSelf attention in Transformer architecture can be affected by several edge cases and failure modes. \\nThe input size has a significant impact on self attention calculations, as larger input sizes increase computational complexity and memory requirements. \\nThis can lead to slower processing times and increased risk of overflow errors.\\n\\n* Sparse input data can also affect self attention performance, as the attention mechanism may struggle to capture relevant information from sparse inputs.\\n* The robustness of self attention to adversarial attacks is a concern, as self attention can be vulnerable to attacks that manipulate the input data to mislead the attention mechanism. \\nIn such cases, the model may produce suboptimal results or fail to generalize well to new data. \\nUnderstanding these edge cases and failure modes is crucial to designing and training robust self attention models.\\n\\n## Performance and Cost Considerations\\nThe self attention mechanism in Transformer architecture has significant performance and cost implications. \\n* The computational complexity of self attention calculations is a major consideration, as it involves computing attention weights for all pairs of input elements, resulting in a time complexity of O(n^2), where n is the sequence length.\\n* The memory requirements for self attention implementations are also substantial, as they require storing attention weights and intermediate results, which can lead to high memory usage for long sequences.\\n* When evaluating the trade-offs between self attention and other attention mechanisms, such as local attention or hierarchical attention, developers must balance the benefits of self attention, including its ability to capture long-range dependencies, against its computational and memory costs. \\nOverall, understanding these performance and cost considerations is crucial for effective implementation and optimization of self attention in Transformer-based models.\\n\\n## Debugging and Observability Tips\\nTo effectively debug and monitor self attention implementations, several techniques can be employed. \\n* Visualizing self attention weights and activations can provide valuable insights into the model's behavior, allowing developers to understand which parts of the input are being focused on.\\n* Identifying and fixing common self attention bugs, such as incorrect weight initialization or faulty attention masking, can be achieved through careful code review and testing.\\n* Monitoring self attention performance metrics, including attention distribution and loss curves, is crucial for optimizing model performance and detecting potential issues. \\nBy applying these techniques, developers can improve the reliability and efficiency of their self attention implementations.\\n\\n## Security and Privacy Considerations\\nSelf attention in transformer architecture can pose significant security and privacy risks, particularly in sensitive applications. \\n* The use of self attention can lead to information leakage, where sensitive data is inadvertently exposed through the attention mechanism.\\n* Additionally, self attention can be vulnerable to adversarial attacks, which can compromise the integrity of the model.\\n\\nTechniques for securing self attention implementations include:\\n* Implementing robust regularization techniques to prevent overfitting and reduce the risk of information leakage.\\n* Using secure multi-party computation protocols to protect sensitive data during the attention computation process.\\n\\nThe importance of data privacy in self attention models cannot be overstated. \\n* Self attention models often require access to large amounts of sensitive data, which can be a significant privacy concern if not handled properly.\\n* As such, it is crucial to implement robust data protection measures, such as data anonymization and encryption, to ensure the confidentiality and integrity of sensitive data. \\n* By prioritizing data privacy and security, developers can ensure that self attention models are used responsibly and with minimal risk.\\n\",\n",
" 'md_with_placeholders': \"# Self Attention in Transformer Architecture\\n\\n## Introduction to Self Attention\\nSelf attention is a key component in transformer architecture, enabling the model to attend to different parts of the input sequence simultaneously. \\n* Define self attention and its role in transformer models: Self attention is a mechanism that allows the model to weigh the importance of different input elements relative to each other.\\n* Explain the difference between self attention and traditional attention mechanisms: Unlike traditional attention, self attention does not rely on recurrent neural networks (RNNs) or convolutional neural networks (CNNs), allowing for more parallelization.\\n* Discuss the benefits of self attention in natural language processing tasks: Self attention has been shown to be highly effective in various NLP tasks, such as machine translation and text classification, due to its ability to capture long-range dependencies and contextual relationships.\\n\\n## Mathematical Formulation of Self Attention\\nThe self attention mechanism is a core component of the Transformer architecture, allowing the model to attend to different parts of the input sequence simultaneously. To understand how self attention works, we need to derive the self attention equation step by step. The equation is based on the concept of attention, which is calculated as the weighted sum of the value matrix, where the weights are determined by the query and key matrices.\\n\\n* The self attention equation is derived as follows:\\n * First, we calculate the query (Q), key (K), and value (V) matrices from the input sequence.\\n * Then, we compute the attention scores by taking the dot product of Q and K and applying a scaling factor.\\n * The role of query, key, and value matrices in self attention is crucial: the query matrix represents the context in which the attention is being applied, the key matrix represents the information being attended to, and the value matrix represents the information being retrieved.\\n* The query, key, and value matrices are used to calculate the attention weights, which are then used to compute the final output of the self attention mechanism.\\n* The importance of scaling in self attention calculations cannot be overstated. The scaling factor helps to prevent the attention scores from becoming too large, which can lead to extremely small gradients during backpropagation. This, in turn, can make it difficult to train the model effectively. By scaling the attention scores, we can ensure that the gradients remain manageable and the model can learn effectively.\\n\\n[[IMAGE_1]]\\n## Implementing Self Attention in Code\\nTo implement self attention in a simple transformer model, we can utilize a popular deep learning library like PyTorch. \\n### Code Implementation\\n```python\\nimport torch\\nimport torch.nn as nn\\nimport torch.nn.functional as F\\n\\nclass SelfAttention(nn.Module):\\n def __init__(self, embed_dim, num_heads):\\n super(SelfAttention, self).__init__()\\n self.embed_dim = embed_dim\\n self.num_heads = num_heads\\n self.query_linear = nn.Linear(embed_dim, embed_dim)\\n self.key_linear = nn.Linear(embed_dim, embed_dim)\\n self.value_linear = nn.Linear(embed_dim, embed_dim)\\n\\n def forward(self, x):\\n # Get query, key, and value matrices\\n Q = self.query_linear(x)\\n K = self.key_linear(x)\\n V = self.value_linear(x)\\n\\n # Calculate self attention scores\\n attention_scores = torch.matmul(Q, K.T) / math.sqrt(self.embed_dim)\\n\\n # Calculate weighted sum of value matrix\\n attention_weights = F.softmax(attention_scores, dim=-1)\\n output = torch.matmul(attention_weights, V)\\n\\n return output\\n```\\n### Key Components and Optimization\\n* The key components of the self attention implementation include the query, key, and value matrices, which are calculated using linear layers.\\n* Potential optimization techniques for self attention calculations include using sparse attention patterns or knowledge distillation to reduce computational complexity.\\n\\n[[IMAGE_2]]\\n## Edge Cases and Failure Modes\\nSelf attention in Transformer architecture can be affected by several edge cases and failure modes. \\nThe input size has a significant impact on self attention calculations, as larger input sizes increase computational complexity and memory requirements. \\nThis can lead to slower processing times and increased risk of overflow errors.\\n\\n* Sparse input data can also affect self attention performance, as the attention mechanism may struggle to capture relevant information from sparse inputs.\\n* The robustness of self attention to adversarial attacks is a concern, as self attention can be vulnerable to attacks that manipulate the input data to mislead the attention mechanism. \\nIn such cases, the model may produce suboptimal results or fail to generalize well to new data. \\nUnderstanding these edge cases and failure modes is crucial to designing and training robust self attention models.\\n\\n## Performance and Cost Considerations\\nThe self attention mechanism in Transformer architecture has significant performance and cost implications. \\n* The computational complexity of self attention calculations is a major consideration, as it involves computing attention weights for all pairs of input elements, resulting in a time complexity of O(n^2), where n is the sequence length.\\n* The memory requirements for self attention implementations are also substantial, as they require storing attention weights and intermediate results, which can lead to high memory usage for long sequences.\\n* When evaluating the trade-offs between self attention and other attention mechanisms, such as local attention or hierarchical attention, developers must balance the benefits of self attention, including its ability to capture long-range dependencies, against its computational and memory costs. \\nOverall, understanding these performance and cost considerations is crucial for effective implementation and optimization of self attention in Transformer-based models.\\n\\n[[IMAGE_3]]\\n## Debugging and Observability Tips\\nTo effectively debug and monitor self attention implementations, several techniques can be employed. \\n* Visualizing self attention weights and activations can provide valuable insights into the model's behavior, allowing developers to understand which parts of the input are being focused on.\\n* Identifying and fixing common self attention bugs, such as incorrect weight initialization or faulty attention masking, can be achieved through careful code review and testing.\\n* Monitoring self attention performance metrics, including attention distribution and loss curves, is crucial for optimizing model performance and detecting potential issues. \\nBy applying these techniques, developers can improve the reliability and efficiency of their self attention implementations.\\n\\n## Security and Privacy Considerations\\nSelf attention in transformer architecture can pose significant security and privacy risks, particularly in sensitive applications. \\n* The use of self attention can lead to information leakage, where sensitive data is inadvertently exposed through the attention mechanism.\\n* Additionally, self attention can be vulnerable to adversarial attacks, which can compromise the integrity of the model.\\n\\nTechniques for securing self attention implementations include:\\n* Implementing robust regularization techniques to prevent overfitting and reduce the risk of information leakage.\\n* Using secure multi-party computation protocols to protect sensitive data during the attention computation process.\\n\\nThe importance of data privacy in self attention models cannot be overstated. \\n* Self attention models often require access to large amounts of sensitive data, which can be a significant privacy concern if not handled properly.\\n* As such, it is crucial to implement robust data protection measures, such as data anonymization and encryption, to ensure the confidentiality and integrity of sensitive data. \\n* By prioritizing data privacy and security, developers can ensure that self attention models are used responsibly and with minimal risk.\\n\",\n",
" 'image_specs': [{'placeholder': '[[IMAGE_1]]',\n",
" 'filename': 'self_attention_equation.png',\n",
" 'alt': 'Self attention equation',\n",
" 'caption': 'Self attention equation',\n",
" 'prompt': 'A diagram showing the self attention equation, including query, key, and value matrices.',\n",
" 'size': '1024x1024',\n",
" 'quality': 'high'},\n",
" {'placeholder': '[[IMAGE_2]]',\n",
" 'filename': 'self_attention_code.png',\n",
" 'alt': 'Self attention code',\n",
" 'caption': 'Self attention code',\n",
" 'prompt': 'A code snippet showing the implementation of self attention in PyTorch, including the calculation of query, key, and value matrices.',\n",
" 'size': '1024x1024',\n",
" 'quality': 'high'},\n",
" {'placeholder': '[[IMAGE_3]]',\n",
" 'filename': 'self_attention_performance.png',\n",
" 'alt': 'Self attention performance',\n",
" 'caption': 'Self attention performance',\n",
" 'prompt': 'A graph showing the performance of self attention in terms of computational complexity and memory usage, including the impact of input size and sequence length.',\n",
" 'size': '1024x1024',\n",
" 'quality': 'high'}],\n",
" 'final': \"# Self Attention in Transformer Architecture\\n\\n## Introduction to Self Attention\\nSelf attention is a key component in transformer architecture, enabling the model to attend to different parts of the input sequence simultaneously. \\n* Define self attention and its role in transformer models: Self attention is a mechanism that allows the model to weigh the importance of different input elements relative to each other.\\n* Explain the difference between self attention and traditional attention mechanisms: Unlike traditional attention, self attention does not rely on recurrent neural networks (RNNs) or convolutional neural networks (CNNs), allowing for more parallelization.\\n* Discuss the benefits of self attention in natural language processing tasks: Self attention has been shown to be highly effective in various NLP tasks, such as machine translation and text classification, due to its ability to capture long-range dependencies and contextual relationships.\\n\\n## Mathematical Formulation of Self Attention\\nThe self attention mechanism is a core component of the Transformer architecture, allowing the model to attend to different parts of the input sequence simultaneously. To understand how self attention works, we need to derive the self attention equation step by step. The equation is based on the concept of attention, which is calculated as the weighted sum of the value matrix, where the weights are determined by the query and key matrices.\\n\\n* The self attention equation is derived as follows:\\n * First, we calculate the query (Q), key (K), and value (V) matrices from the input sequence.\\n * Then, we compute the attention scores by taking the dot product of Q and K and applying a scaling factor.\\n * The role of query, key, and value matrices in self attention is crucial: the query matrix represents the context in which the attention is being applied, the key matrix represents the information being attended to, and the value matrix represents the information being retrieved.\\n* The query, key, and value matrices are used to calculate the attention weights, which are then used to compute the final output of the self attention mechanism.\\n* The importance of scaling in self attention calculations cannot be overstated. The scaling factor helps to prevent the attention scores from becoming too large, which can lead to extremely small gradients during backpropagation. This, in turn, can make it difficult to train the model effectively. By scaling the attention scores, we can ensure that the gradients remain manageable and the model can learn effectively.\\n\\n> **[IMAGE GENERATION FAILED]** Self attention equation\\n>\\n> **Alt:** Self attention equation\\n>\\n> **Prompt:** A diagram showing the self attention equation, including query, key, and value matrices.\\n>\\n> **Error:** 429 RESOURCE_EXHAUSTED. {'error': {'code': 429, 'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-3.1-flash-image\\\\nPlease retry in 37.367828011s.', 'status': 'RESOURCE_EXHAUSTED', 'details': [{'@type': 'type.googleapis.com/google.rpc.Help', 'links': [{'description': 'Learn more about Gemini API quotas', 'url': 'https://ai.google.dev/gemini-api/docs/rate-limits'}]}, {'@type': 'type.googleapis.com/google.rpc.QuotaFailure', 'violations': [{'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_requests', 'quotaId': 'GenerateRequestsPerDayPerProjectPerModel-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-3.1-flash-image'}}, {'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_requests', 'quotaId': 'GenerateRequestsPerMinutePerProjectPerModel-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-3.1-flash-image'}}, {'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_input_token_count', 'quotaId': 'GenerateContentInputTokensPerModelPerMinute-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-3.1-flash-image'}}]}, {'@type': 'type.googleapis.com/google.rpc.RetryInfo', 'retryDelay': '37s'}]}}\\n\\n## Implementing Self Attention in Code\\nTo implement self attention in a simple transformer model, we can utilize a popular deep learning library like PyTorch. \\n### Code Implementation\\n```python\\nimport torch\\nimport torch.nn as nn\\nimport torch.nn.functional as F\\n\\nclass SelfAttention(nn.Module):\\n def __init__(self, embed_dim, num_heads):\\n super(SelfAttention, self).__init__()\\n self.embed_dim = embed_dim\\n self.num_heads = num_heads\\n self.query_linear = nn.Linear(embed_dim, embed_dim)\\n self.key_linear = nn.Linear(embed_dim, embed_dim)\\n self.value_linear = nn.Linear(embed_dim, embed_dim)\\n\\n def forward(self, x):\\n # Get query, key, and value matrices\\n Q = self.query_linear(x)\\n K = self.key_linear(x)\\n V = self.value_linear(x)\\n\\n # Calculate self attention scores\\n attention_scores = torch.matmul(Q, K.T) / math.sqrt(self.embed_dim)\\n\\n # Calculate weighted sum of value matrix\\n attention_weights = F.softmax(attention_scores, dim=-1)\\n output = torch.matmul(attention_weights, V)\\n\\n return output\\n```\\n### Key Components and Optimization\\n* The key components of the self attention implementation include the query, key, and value matrices, which are calculated using linear layers.\\n* Potential optimization techniques for self attention calculations include using sparse attention patterns or knowledge distillation to reduce computational complexity.\\n\\n> **[IMAGE GENERATION FAILED]** Self attention code\\n>\\n> **Alt:** Self attention code\\n>\\n> **Prompt:** A code snippet showing the implementation of self attention in PyTorch, including the calculation of query, key, and value matrices.\\n>\\n> **Error:** 429 RESOURCE_EXHAUSTED. {'error': {'code': 429, 'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-3.1-flash-image\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image\\\\nPlease retry in 37.112481234s.', 'status': 'RESOURCE_EXHAUSTED', 'details': [{'@type': 'type.googleapis.com/google.rpc.Help', 'links': [{'description': 'Learn more about Gemini API quotas', 'url': 'https://ai.google.dev/gemini-api/docs/rate-limits'}]}, {'@type': 'type.googleapis.com/google.rpc.QuotaFailure', 'violations': [{'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_input_token_count', 'quotaId': 'GenerateContentInputTokensPerModelPerMinute-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-3.1-flash-image'}}, {'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_requests', 'quotaId': 'GenerateRequestsPerMinutePerProjectPerModel-FreeTier', 'quotaDimensions': {'model': 'gemini-3.1-flash-image', 'location': 'global'}}, {'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_requests', 'quotaId': 'GenerateRequestsPerDayPerProjectPerModel-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-3.1-flash-image'}}]}, {'@type': 'type.googleapis.com/google.rpc.RetryInfo', 'retryDelay': '37s'}]}}\\n\\n## Edge Cases and Failure Modes\\nSelf attention in Transformer architecture can be affected by several edge cases and failure modes. \\nThe input size has a significant impact on self attention calculations, as larger input sizes increase computational complexity and memory requirements. \\nThis can lead to slower processing times and increased risk of overflow errors.\\n\\n* Sparse input data can also affect self attention performance, as the attention mechanism may struggle to capture relevant information from sparse inputs.\\n* The robustness of self attention to adversarial attacks is a concern, as self attention can be vulnerable to attacks that manipulate the input data to mislead the attention mechanism. \\nIn such cases, the model may produce suboptimal results or fail to generalize well to new data. \\nUnderstanding these edge cases and failure modes is crucial to designing and training robust self attention models.\\n\\n## Performance and Cost Considerations\\nThe self attention mechanism in Transformer architecture has significant performance and cost implications. \\n* The computational complexity of self attention calculations is a major consideration, as it involves computing attention weights for all pairs of input elements, resulting in a time complexity of O(n^2), where n is the sequence length.\\n* The memory requirements for self attention implementations are also substantial, as they require storing attention weights and intermediate results, which can lead to high memory usage for long sequences.\\n* When evaluating the trade-offs between self attention and other attention mechanisms, such as local attention or hierarchical attention, developers must balance the benefits of self attention, including its ability to capture long-range dependencies, against its computational and memory costs. \\nOverall, understanding these performance and cost considerations is crucial for effective implementation and optimization of self attention in Transformer-based models.\\n\\n> **[IMAGE GENERATION FAILED]** Self attention performance\\n>\\n> **Alt:** Self attention performance\\n>\\n> **Prompt:** A graph showing the performance of self attention in terms of computational complexity and memory usage, including the impact of input size and sequence length.\\n>\\n> **Error:** 429 RESOURCE_EXHAUSTED. {'error': {'code': 429, 'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-3.1-flash-image\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image\\\\nPlease retry in 36.925917158s.', 'status': 'RESOURCE_EXHAUSTED', 'details': [{'@type': 'type.googleapis.com/google.rpc.Help', 'links': [{'description': 'Learn more about Gemini API quotas', 'url': 'https://ai.google.dev/gemini-api/docs/rate-limits'}]}, {'@type': 'type.googleapis.com/google.rpc.QuotaFailure', 'violations': [{'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_input_token_count', 'quotaId': 'GenerateContentInputTokensPerModelPerMinute-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-3.1-flash-image'}}, {'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_requests', 'quotaId': 'GenerateRequestsPerMinutePerProjectPerModel-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-3.1-flash-image'}}, {'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_requests', 'quotaId': 'GenerateRequestsPerDayPerProjectPerModel-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-3.1-flash-image'}}]}, {'@type': 'type.googleapis.com/google.rpc.RetryInfo', 'retryDelay': '36s'}]}}\\n\\n## Debugging and Observability Tips\\nTo effectively debug and monitor self attention implementations, several techniques can be employed. \\n* Visualizing self attention weights and activations can provide valuable insights into the model's behavior, allowing developers to understand which parts of the input are being focused on.\\n* Identifying and fixing common self attention bugs, such as incorrect weight initialization or faulty attention masking, can be achieved through careful code review and testing.\\n* Monitoring self attention performance metrics, including attention distribution and loss curves, is crucial for optimizing model performance and detecting potential issues. \\nBy applying these techniques, developers can improve the reliability and efficiency of their self attention implementations.\\n\\n## Security and Privacy Considerations\\nSelf attention in transformer architecture can pose significant security and privacy risks, particularly in sensitive applications. \\n* The use of self attention can lead to information leakage, where sensitive data is inadvertently exposed through the attention mechanism.\\n* Additionally, self attention can be vulnerable to adversarial attacks, which can compromise the integrity of the model.\\n\\nTechniques for securing self attention implementations include:\\n* Implementing robust regularization techniques to prevent overfitting and reduce the risk of information leakage.\\n* Using secure multi-party computation protocols to protect sensitive data during the attention computation process.\\n\\nThe importance of data privacy in self attention models cannot be overstated. \\n* Self attention models often require access to large amounts of sensitive data, which can be a significant privacy concern if not handled properly.\\n* As such, it is crucial to implement robust data protection measures, such as data anonymization and encryption, to ensure the confidentiality and integrity of sensitive data. \\n* By prioritizing data privacy and security, developers can ensure that self attention models are used responsibly and with minimal risk.\\n\"}"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"run(\"Self Attention in Transformer Architecture\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c9022798",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv (3.14.3)",
"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.14.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Workflows from the Neura Market marketplace related to this Grok resource