[
  {
    "id": "74eb249bbf",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "I just discovered the course. Can I still join?",
    "answer": "Yes, but if you want to receive a certificate, you need to submit your project while we’re still accepting submissions."
  },
  {
    "id": "977bf7786c",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Course: I have registered for the LLM Zoomcamp. When can I expect to receive the confirmation email?",
    "answer": "You don't need it. You're accepted. You can also just start learning and submitting homework (while the form is open) without registering. It is not checked against any registered list. Registration is just to gauge interest before the start date."
  },
  {
    "id": "489dd1c9d9",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "What is the video/zoom link to the stream for the “Office Hours” or live/workshop sessions?",
    "answer": "The zoom link is only published to instructors/presenters/TAs.\n\nStudents participate via YouTube Live and submit questions to Slido (link is pinned in the chat when live). The video URL should be posted in the [announcements channel on Telegram and Slack](https://t.me/dezoomcamp) before it begins. You can also watch live on the DataTalksClub [YouTube Channel](https://www.youtube.com/c/DataTalksClub).\n\nDon’t post questions in chat as they may be missed if the room is very active."
  },
  {
    "id": "04919992b3",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "How should I start the course and follow the weekly workflow?",
    "answer": "Start with the [LLM Zoomcamp docs](https://datatalks.club/docs/courses/llm-zoomcamp/), the [general Zoomcamp logistics docs](https://datatalks.club/docs/courses/zoomcamp-logistics/), and the [LLM Zoomcamp GitHub repository](https://github.com/DataTalksClub/llm-zoomcamp).\n\nYou can start whenever you want. The videos and GitHub materials are available, and the deadlines are listed in the [course management platform](https://courses.datatalks.club/llm-zoomcamp-2026/).\n\nA typical workflow is:\n\n1. Watch the lesson videos.\n2. Work through the lesson notebooks/code.\n3. Read the homework instructions on GitHub.\n4. Submit answers through the course platform before the deadline.\n\nHomework is similar to the lesson flow, but uses a different dataset or slightly different task."
  },
  {
    "id": "c2903069a0",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Leaderboard: I am not on the leaderboard / how do I know which one I am on the leaderboard?",
    "answer": "When you set up your account, you are automatically assigned a random name, such as “Lucid Elbakyan.” Click on the \"Jump to your record on the leaderboard\" link to find your entry.\n\nIf you want to see what your Display name is, click on the \"Edit Course Profile\" button.\n\n<{IMAGE:image_1}>\n\n- **First field:** This is your nickname/displayed name. You can change it if you want to be known by your Slack username, GitHub username, or any other nickname of your choice. This is useful if you want to remain anonymous.\n- **Second field:** Change this to your official name as in your identification documents—passport, national ID card, driver's license, etc. This is mandatory if you do not want \"Lucid Elbakyan\" on your certificate. This name will appear on your Certificate!"
  },
  {
    "id": "69d122f12e",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Certificate: Can I follow the course in a self-paced mode and get a certificate?",
    "answer": "No, you can only get a certificate if you finish the course with a \"live\" cohort.\n\nTo get the certificate, you need to finish a capstone project and complete the\nrequired peer reviews. Homework is not required. You can work through the\nmaterial and prepare your project in self-paced mode, but project submission and\npeer review must happen while a live cohort is accepting them."
  },
  {
    "id": "9f689c185f",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "I missed the first homework - can I still get a certificate?",
    "answer": "Yes, you need to pass the Capstone project to get the certificate. Homework is not mandatory, though it is recommended for reinforcing concepts, and the points awarded count towards your rank on the leaderboard."
  },
  {
    "id": "96286b4be4",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Homework: Why does the content keep changing?",
    "answer": "If the homework title contains `[DRAFT]`, it means the homework is not ready yet.\n\nThe homework is ready only when both are true:\n\n- The homework form is open on the course management platform.\n- The homework title does not contain `[DRAFT]`.\n\nUntil then, the content can still change. Working on the material or homework in advance is at your own risk, because the final version can be different."
  },
  {
    "id": "bd31146b0e",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "When will the course be offered next?",
    "answer": "Summer 2027."
  },
  {
    "id": "31456f4b5f",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Are there any lectures/videos? Where are they?",
    "answer": "Use the [LLM Zoomcamp GitHub repository](https://github.com/DataTalksClub/llm-zoomcamp) as the main entry point.\n\nOpen the lesson folders in the repo. Each lesson page has the relevant videos linked at the top.\n\n<{IMAGE:lessons}>\n\nWhen in doubt, follow the GitHub repo first, because it is easier to keep updated than the YouTube playlist."
  },
  {
    "id": "20c5a1347e",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Where can I track the LLM Zoomcamp syllabus, deadlines, homework, and progress?",
    "answer": "Use the [LLM Zoomcamp course management platform](https://courses.datatalks.club/llm-zoomcamp-2026/).\n\nIt contains the current cohort structure, homework, deadlines, and progress tracking. The process is the same as in other DataTalks.Club Zoomcamps."
  },
  {
    "id": "d65e05bd7a",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Are there live sessions or office hours for each module?",
    "answer": "There are no separate live sessions for every module by default. Module materials are pre-recorded and available in the course repo.\n\nLive sessions are announced separately when they happen. If you are stuck, ask your question in Slack and follow the [asking questions guidelines](https://datatalks.club/docs/courses/zoomcamp-logistics/asking-questions/).\n\nOptional extra support is available through [AI Shipping Labs](https://datatalks.club/docs/courses/llm-zoomcamp/ai-shipping-labs/), a paid community that includes regular Zoom office hours and additional structure. This is optional; the DataTalks.Club course content remains free."
  },
  {
    "id": "dbf5369006",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Can I use Bluesky for learning in public credits?",
    "answer": "Yes. Bluesky posts can be used for learning in public credits."
  },
  {
    "id": "054f3fd58f",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Where is the LLM Zoomcamp Telegram channel?",
    "answer": "The Telegram channel is [https://t.me/llm_zoomcamp](https://t.me/llm_zoomcamp).\n\nUse it for announcements. For technical discussion and questions, use the course Slack channel."
  },
  {
    "id": "e2d595f23c",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Why is the number of documents in the FAQ dataset different from the video, and why do my RAG results differ?",
    "answer": "The course loads documents from the live FAQ dataset, which changes over time as\nquestions are added, updated, or deleted. If your notebook downloads the latest\ndata, its document count and RAG index can differ from the snapshot used when the\nvideos were recorded. Different retrieved context can then produce a different\nfinal answer.\n\nThis does not necessarily mean your implementation is wrong. To reproduce a\nvideo exactly, use the same dataset snapshot or Git commit; otherwise, expect\nresults from the current dataset to differ."
  },
  {
    "id": "a9353fadfe",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "The homework submission form is still open even though the deadline has passed — can I still submit?",
    "answer": "Yes. As long as the submission form is still open, you can submit your answers, even if the listed deadline has already passed. You can no longer submit only after the form has been closed — so while it's still open, go ahead and submit."
  },
  {
    "id": "cdc3b285e5",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Can I submit homework after the deadline, or get a deadline extension?",
    "answer": "No. We don't give individual deadline extensions, and once the homework submission form is closed you can no longer submit it — there are no late submissions. While the form is still open you can submit, even if the listed deadline has already passed.\n\nMissing a homework won't affect your certificate: homework isn't mandatory, only passing the Capstone project is. Homework points only count toward your leaderboard rank, so you'll still appear on the leaderboard with your other submissions."
  },
  {
    "id": "651ba06b34",
    "course": "llm-zoomcamp",
    "section": "General Course-Related Questions",
    "question": "Will the name I put in the certificate field be shown publicly online or shared with third parties?",
    "answer": "No. The certificate name only appears on your certificate — it isn't published online or shared with third parties. The public leaderboard uses your separate display name instead, which is a random nickname (like “Lucid Elbakyan”) and anonymous by default.\n\nYou can set both in “Edit Course Profile”: the first field is your public nickname for the leaderboard, and the second is the official name printed on your certificate. This lets you keep your real name off the public leaderboard while still having it on the certificate."
  },
  {
    "id": "b7cdde6b25",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Why are we not using Langchain in the course?",
    "answer": "LangChain is a framework for building LLM-powered apps. In this course, we first build the core pieces ourselves: prompting, retrieval, indexing, and evaluation.\n\nThink of it like learning HTML, CSS, and JavaScript before using React or Angular. Frameworks are easier to use well once you understand what they automate."
  },
  {
    "id": "f5df151c59",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "OpenAI: Error when running OpenAI responses.create command",
    "answer": "You may receive the following error when running the OpenAI `responses.create` command due to insufficient credits in your OpenAI account:\n\n```\nOpenAI API Error: Insufficient credits\n```"
  },
  {
    "id": "152af39a53",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "OpenAI: Error: RateLimitError: Error code: 429 -",
    "answer": "```json\nRateLimitError: Error code: 429 - {'error': {'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: [https://platform.openai.com/docs/guides/error-codes/api-errors.](https://platform.openai.com/docs/guides/error-codes/api-errors.)', 'type': 'insufficient_quota', 'param': None, 'code': 'insufficient_quota'}\n```\n\nThe above errors are related to your OpenAI API account’s quota. There is no free usage of OpenAI’s API, so you will need to add funds using a credit card (see pay-as-you-go in the OpenAI settings at [platform.openai.com](http://platform.openai.com)). Once added, re-run your Python command and you should receive a successful return code.\n\nSteps to resolve:\n\n1. Add credits to your account [here](https://platform.openai.com/settings/organization/billing/overview) (min $5).\n2. In `responses.create(model='gpt-4o', …)` specify one of the models available to you:\n\n   <{IMAGE:image_1}>\n\n3. You might need to recreate an API key after adding credits to your account and update it locally."
  },
  {
    "id": "554d0eb78b",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "OpenAI: How much will I have to spend to use the Open AI API?",
    "answer": "Using the OpenAI API for the course should cost very little. You can recharge starting from $5, but initial usage is usually fractions of one cent."
  },
  {
    "id": "85384a18e5",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "OpenAI: Do I have to subscribe and pay for Open AI API for this course?",
    "answer": "No, you don't have to pay for this service in order to complete the course homeworks. You can use free or low-cost alternatives listed in the course GitHub repo.\n\nSee the course list of [OpenAI API alternatives](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/awesome-llms.md#openai-api-alternatives)."
  },
  {
    "id": "f81dea8f7e",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Why use uv package/project manager instead of the more traditional Python tools like pip and virtualenv?",
    "answer": "The course uses uv because it's fast and convenient. It's an all-in-one tool that replaces pip, virtualenv, pip-tools, pipx, poetry, pyenv, and twine — instead of juggling several tools, you manage Python versions, virtual environments, and dependencies through one fast command with reproducible lockfiles.\n\nYou're not required to use it — pip and virtualenv still work fine — but uv makes the setup simpler and faster, which is why the lessons use it.\n\nOfficial docs: https://docs.astral.sh/uv/"
  },
  {
    "id": "86d99bbf21",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Authentication: Why is my OPENAI_API_KEY not found in the Jupyter notebook?",
    "answer": "Make sure you installed and used `python-dotenv`.\n\n```bash\npip install python-dotenv\n```\n\nThen load the `.env` file in the notebook before creating the OpenAI client:\n\n```python\nfrom dotenv import load_dotenv\n\nload_dotenv()\n```\n\nAlso check that the variable name in `.env` is exactly `OPENAI_API_KEY`."
  },
  {
    "id": "233dabe430",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "How to store and load API keys using .env file",
    "answer": "Store API keys in a `.env` file and load them with `python-dotenv`, as recommended in the course.\n\nAdd `.env` to `.gitignore` so keys are never committed:\n\n```gitignore\n.env\n```\n\nCreate a `.env` file:\n\n```bash\nOPENAI_API_KEY=sk-...\nGROQ_API_KEY=gsk_...\nGEMINI_API_KEY=...\n```\n\nInstall `python-dotenv` if needed:\n\n```bash\npip install python-dotenv\n```\n\nLoad the keys in Python:\n\n```python\nimport os\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nopenai_api_key = os.getenv(\"OPENAI_API_KEY\")\ngroq_api_key = os.getenv(\"GROQ_API_KEY\")\n```"
  },
  {
    "id": "ee43413718",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Can I use a model or provider different from the one recommended in homework?",
    "answer": "Yes. The recommended model is not mandatory. You can use OpenAI, Gemini, Groq, OpenRouter, Azure OpenAI, local models, or another provider.\n\nThe homework is designed so you do not need a paid service. You may need to adapt the code for your provider, because response formats, tool schemas, and tokenizers differ.\n\nFor provider ideas, see the course list of [OpenAI API alternatives](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/awesome-llms.md)."
  },
  {
    "id": "0ae5c221b9",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "How do I start using Google Gemini models in the Module 1 notebook through the OpenAI-compatible endpoint?",
    "answer": "To get started you need three things:\n\n1. A Gemini API key saved in your `.env` file, for example as `GEMINI_API_KEY`.\n2. An OpenAI client pointed at Google’s OpenAI-compatible base URL.\n3. Your selected Google Gemini model name in your request.\n\nExample code (loads the API key from `.env`, creates the Gemini client, and defines the `llm` helper):\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom openai import OpenAI\n\nload_dotenv()\n\nclient = OpenAI(\n    api_key=os.getenv(\"GEMINI_API_KEY\"),\n    base_url=\"https://generativelanguage.googleapis.com/v1beta/openai/\"\n)\n\ndef llm(instructions, user_prompt, model=\"gemini-3.1-flash-lite\"):\n    message_history = [\n        {\"role\": \"developer\", \"content\": instructions},\n        {\"role\": \"user\", \"content\": user_prompt}\n    ]\n\n    response = client.chat.completions.create(\n        model=model,\n        messages=message_history\n    )\n\n    return response.choices[0].message.content\n```\n\nThis uses the older chat completions style via the OpenAI-compatible endpoint, whereas many course examples use the newer Responses format. That means you will need to change the notebook code in a few places, especially where it reads the model response and where it handles tools or function calls."
  },
  {
    "id": "341f71f28c",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "How can I get structured output (Pydantic objects) from Gemini via the OpenAI-compatible endpoint when responses.parse isn't available?",
    "answer": "To get parsed structured output, use the OpenAI SDK's chat-completions parsing flow instead of the newer Responses API. This is the right choice when you want to stay on the OpenAI SDK but call a chat-completions-compatible model like Gemini through the OpenAI-compatible endpoint.\n\nFirst, define the structure you want Gemini to return as a Pydantic model:\n\n```python\nfrom pydantic import BaseModel\n\nclass Question(BaseModel):\n    question: str\n    answer: str\n\nclass Questions(BaseModel):\n    questions: list[Question]\n```\n\nThen call `chat.completions.parse` with `response_format=Questions`. Pass the same kind of `messages` you would to any chat completion:\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom openai import OpenAI\n\nload_dotenv()\n\nclient = OpenAI(\n    api_key=os.getenv(\"GEMINI_API_KEY\"),\n    base_url=\"https://generativelanguage.googleapis.com/v1beta/openai/\"\n)\n\nmessages = [\n    {\"role\": \"developer\", \"content\": \"Generate three FAQ questions about RAG.\"},\n    {\"role\": \"user\", \"content\": \"Topic: structured output with Gemini\"}\n]\n\nresponse = client.chat.completions.parse(\n    model=\"gemini-3.1-flash-lite\",\n    messages=messages,\n    response_format=Questions\n)\n\nresult = response.choices[0].message.parsed\nprint(result.questions)\n```\n\nNotes:\n- This approach keeps using the OpenAI SDK while leveraging Gemini through Google's endpoint.\n- The parsed output (e.g., `result.questions`) is a Pydantic object available directly, with no manual JSON parsing.\n- `chat.completions.parse` requires `pydantic` (`uv add pydantic`)."
  },
  {
    "id": "1d0b969028",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Ollama: How to install Ollama?",
    "answer": "First, install Ollama by visiting [https://ollama.com/download](https://ollama.com/download) and choosing your operating system:\n\n- **macOS**: Download the `.pkg` and install it.\n- **Windows**: Download the `.msi` and install it.\n- **Linux**: Run the following command in the terminal:\n\n  ```bash\n  curl -fsSL https://ollama.com/install.sh | sh\n  ```\n\nOnce installed, open a terminal and type:\n\n```bash\nollama run llama3\n```\n\nThis command will:\n\n- Download the LLaMA 3 model (~4GB).\n- Start the model locally.\n- Open a chat-like interface where you can type questions.\n\nTo test the Ollama local server, run the following command:\n\n```bash\ncurl http://localhost:11434\n```\n\nYou should receive a response similar to:\n\n```json\n{\"models\": [...]}  \n```\n\nThen, install the Python client with:\n\n```bash\npip install ollama\n```\n\nHere is a minimal Python example:\n\n```python\nimport ollama\n\nresponse = ollama.chat(\n    model='llama3',\n    messages=[{\"role\": \"user\", \"content\": your_prompt}]\n)\n\nprint(response['message']['content'])\n```"
  },
  {
    "id": "c6fc2d4d11",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Connection refused error when prompting Ollama RAG",
    "answer": "If you encounter this error while doing the homework, you can resolve it by restarting the Ollama server using the following command:\n\n```bash\n!nohup ollama serve > nohup.out 2>&1 &\n```\n\nMake sure to rerun the cell containing `ollama serve` if you stop and restart the notebook cell."
  },
  {
    "id": "bf0403d21b",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "OpenAI: Why does my token count differ from what OpenAI reports?",
    "answer": "When using `tiktoken.encode()` to count tokens in your prompt, you might see a difference compared to OpenAI’s API response. For instance, you might get 320 tokens, while OpenAI reports 327. This is due to internal tokens added by OpenAI’s chat formatting.\n\nHere’s what happens:\n\n- Each message in a `chat.completions.create()` call (e.g., `{role: \"user\", content: \"...\"}`) adds 4 structural tokens (role, content, separators).\n- The API adds 2 tokens globally to mark the start of assistant response generation.\n- Extra newlines, whitespace, or uncommon Unicode characters in your content may slightly increase the token count.\n\nThus, even if your visible text is 320 tokens, OpenAI may count 327 due to these internal additions."
  },
  {
    "id": "8b2f5e9d04",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "API keys: how do I set them once and not re-export every terminal?",
    "answer": "Use [`dirdotenv`](https://github.com/alexeygrigorev/dirdotenv). It is like `direnv`, but works with both `.env` and `.envrc`, and is more portable across shells and operating systems.\n\n```bash\nuv tool install dirdotenv\n\n# add this line to your ~/.bashrc or ~/.zshrc:\neval \"$(dirdotenv hook bash)\"   # or zsh\n\n# inside your project:\necho 'OPENAI_API_KEY=sk-...' > .env\necho '.env' >> .gitignore\n```\n\nAfter that, the key is loaded automatically when you `cd` into the project directory.\n\nImportant: always add `.env` and `.envrc` to `.gitignore` so keys never land on GitHub.\n\n`direnv` is also fine if you already use it. In that case, create `.envrc`, add your exports there, and run `direnv allow`.\n\nFor GitHub Codespaces, use the built-in [Codespaces secrets](https://docs.github.com/en/codespaces/managing-your-codespaces/managing-your-account-specific-secrets-for-github-codespaces) instead of files in the repo.\n\nFor Python scripts, the equivalent is `python-dotenv`:\n\n```python\nfrom dotenv import load_dotenv\nload_dotenv()  # loads .env from project root\n```"
  },
  {
    "id": "87d4ea4e08",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "How should I choose field weights for minsearch or another search engine?",
    "answer": "The systematic approach is to evaluate different weight settings against a ground-truth dataset.\n\nFor example:\n\n1. Create a small set of representative questions.\n2. Mark which documents should be retrieved for each question.\n3. Try different field weights.\n4. Compare retrieval metrics such as hit rate, precision@k, recall@k, or MRR.\n\nYou can tune weights by trial and error for small projects, but evaluation is the more reliable approach. The course covers this topic more directly in the evaluation module."
  },
  {
    "id": "649c280e6d",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "How should I prepare documents for RAG?",
    "answer": "Prepare the data so it is clean, structured, and easy to chunk and retrieve.\n\nCommon steps:\n\n- Remove obvious noise such as broken HTML, duplicate text, boilerplate, OCR errors, repeated headers, and repeated footers.\n- Preserve useful context such as titles, section names, dates, page numbers, speaker names, and Q&A structure.\n- Store the result in a structured format that is easy to process. JSON is often convenient, but it is not mandatory.\n- Chunk the documents in a way that keeps related context together.\n- Keep metadata that may help filtering or ranking later.\n\nThe exact format depends on the source data. The goal is not just to make the text shorter, but to make retrieval more accurate."
  },
  {
    "id": "acf8fa5356",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "uv says Failed to hardlink files in Codespaces. Is it an error?",
    "answer": "No. This warning can happen in GitHub Codespaces when `uv` cannot hardlink files between the cache and the target environment.\n\nThe package still installs. `uv` falls back to copying files.\n\nTo suppress the warning for the current shell:\n\n```bash\nexport UV_LINK_MODE=copy\n```\n\nTo make it persistent:\n\n```bash\necho 'export UV_LINK_MODE=copy' >> ~/.bashrc\nsource ~/.bashrc\n```\n\nSee the `uv` documentation for more details: [https://docs.astral.sh/uv/](https://docs.astral.sh/uv/)."
  },
  {
    "id": "0bed1f48da",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "dotenv is not recognized. What should I install?",
    "answer": "Install `python-dotenv`:\n\n```bash\nuv add python-dotenv\n```\n\nThen import and use it in Python:\n\n```python\nfrom dotenv import load_dotenv\n\nload_dotenv()\n```\n\nThe package is documented here: [python-dotenv](https://pypi.org/project/python-dotenv/)."
  },
  {
    "id": "aa310de435",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Can I run the course locally instead of Codespaces?",
    "answer": "Yes. Codespaces is just the easiest way for everyone to start with the same environment.\n\nYou can run the course locally if you are comfortable setting up Python, `uv`, Jupyter, Docker, and any other tools needed for the module.\n\nIf you run locally, make sure you document your setup and keep your environment reproducible."
  },
  {
    "id": "610ccb23c0",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "What happens to code saved in Codespaces if I do not commit it?",
    "answer": "The code is saved inside the Codespaces Linux VM.\n\nHowever, you should still commit your work regularly. Codespaces can stop, disconnect, or be deleted later, and committing makes sure your work is stored in your GitHub repository."
  },
  {
    "id": "f3dd94f323",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "WSL2: ResponseError: model requires more system memory (X.X GiB) than is available (Y.Y GiB). My system has more than X.X GiB.",
    "answer": "Your WSL2 is set to use Y.Y GiB, not all your computer memory. To allocate more RAM, follow these steps:\n\n1. Create a `.wslconfig` file under your Windows user profile directory: `C:\\Users\\YourUsername\\.wslconfig`.\n\n2. Include the desired RAM allocation in the file:\n\n   ```ini\n   [wsl2]\n   memory=8GB\n   ```\n\n3. Restart WSL using the command:\n\n   ```bash\n   wsl --shutdown\n   ```\n\n4. Run the `free` command in WSL to verify the changes.\n\nFor more details, read [this article](https://www.aleksandrhovhannisyan.com/blog/limiting-memory-usage-in-wsl-2/)."
  },
  {
    "id": "830f3d2018",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "My Codespace won't reconnect (stuck on \"Finishing up\") or my setup has disappeared — what should I do?",
    "answer": "These are usually GitHub Codespaces reliability issues rather than a problem with the course, so there's no single guaranteed fix — but the following workarounds resolve most cases.\n\n**Codespace won't connect / stuck on \"Finishing up\":**\n\n- Go to [github.com/codespaces](https://github.com/codespaces), stop the codespace, and start it again.\n- If it still won't connect, open it in the **browser** instead of desktop VS Code, or try a different browser (Edge/Chrome/Brave).\n- As a last resort, delete the codespace and create a new one.\n\n**\"My setup is all gone\":**\n\n- The repo in `/workspaces` persists across stop/start, but a **rebuild or a brand-new codespace** starts from a clean image, and system/global installs outside your project don't always survive. Reinstalling is quick with `uv` (`uv sync` / `uv add ...`).\n- **Commit and push your work often** — uncommitted changes survive a stop/start but are lost if you delete or recreate the codespace.\n\nIf Codespaces keeps being flaky for you, consider running the course locally instead — see \"[Can I run the course locally instead of Codespaces?](026_aa310de435_can-i-run-the-course-locally-instead-of-codespaces.md)\"."
  },
  {
    "id": "c6ff9083c0",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Do I have to use OpenAI, or can I use a different provider?",
    "answer": "If the provider used in the course isn't available or is blocked in your region (or you simply prefer another one), you can use any other LLM provider — the course isn't tied to OpenAI. Just switch to something else:\n\n- Hosted, OpenAI-compatible providers — e.g. Groq, OpenRouter, DeepSeek, Gemini, Z.ai, Mistral. The course code uses the OpenAI client, so you usually only need to change the `base_url`, the API key, and the model name.\n- Open models via Hugging Face (e.g. Qwen, Llama) if you prefer hosted open-source models.\n- Serve a model locally with [Ollama](https://ollama.com/), [vLLM](https://github.com/vllm-project/vllm), LM Studio, or anything else — no external API call at all, so regional blocks don't apply and you don't need a paid key. Most of these also expose an OpenAI-compatible endpoint, so the course code works with only a `base_url` change.\n- Rent a GPU machine and serve the model there (e.g. with vLLM) if your own machine can't run the model you want. This gives you a private OpenAI-compatible endpoint to point the course code at — just remember to stop/delete the instance when you're done so you don't keep paying for it.\n- A VPN also works if you just need to reach a provider that's blocked at the network level.\n\nAnything with an OpenAI-compatible endpoint (or a locally served model) will work — pick whatever is available and convenient for you.\n\nFor a curated list of options, see [Awesome LLMs](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/awesome-llms.md) in the course repo, which collects OpenAI API alternatives and tools for running models locally."
  },
  {
    "id": "8e21752415",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "How do I avoid hitting OpenAI rate limits (429 errors) during the course?",
    "answer": "Free-tier and low-spend OpenAI accounts have per-minute and per-day request limits that are easy to blow through when a notebook loops over many documents. Reduce the number of API calls before you worry about handling the error after the fact.\n\n- **Retry with backoff.** The `openai` Python client retries automatically with exponential backoff; raise the limit so transient 429s resolve themselves instead of crashing the run:\n\n  ```python\n  from openai import OpenAI\n\n  client = OpenAI(max_retries=5)\n  ```\n\n- **Lower concurrency.** In a thread/process pool, keep the pool small (2-3 workers) so you stay under the per-minute cap. A larger pool finishes one batch fast and then fails on the next.\n- **Cache results.** Write embeddings and LLM responses to disk (JSONL, pickle, or a vector DB) and reload on re-run. Re-executing a notebook should not re-call the API for inputs you already processed.\n- **Batch where possible.** Group independent inputs into a single request (e.g. embed a list of texts in one call) instead of looping one-by-one.\n- **Use a cheaper/free provider.** `gpt-4o-mini` is cheap enough for the whole course; Groq's free tier (`llama-3.3-70b-versatile`) has generous per-minute limits via the OpenAI-compatible endpoint.\n\nIf you already see `insufficient_quota`, that is a billing issue, not a rate limit — see [OpenAI: Error: RateLimitError: Error code: 429](openai-error-ratelimiterror-error-code-429)."
  },
  {
    "id": "4a2c64c6e7",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "How can I inspect an unfamiliar LLM API response to find tool calls, token usage, or other fields?",
    "answer": "If you’re adapting the course code to another provider or API style, first inspect the complete response object—different APIs (e.g., Responses API vs Chat Completions vs provider SDKs) expose different field names, so the fastest path is to print the whole structure.\n\nIf `response` is a Pydantic model, convert it to a dictionary with `response.model_dump()`. In a notebook, you can display it as an expandable JSON tree:\n\n```python\nfrom IPython.display import JSON\n\n# Assuming `response` is the full object returned by the API\nJSON(response.model_dump())\n```\n\nIn a script/terminal, print formatted JSON:\n\n```python\nimport json\n\nprint(json.dumps(response.model_dump(), indent=2))\n```\n\nThen search the printed output for the fields you care about, such as:\n\n- tool/function calling results (often under names like `tool_calls`, `tools`, or provider-specific structures)\n- token usage (often under `usage`, with subfields like `prompt_tokens`, `completion_tokens`, `total_tokens`)\n- the actual generated text (often under `choices` / `message` / `content` or provider-specific equivalents)\n\nOnce you identify the corresponding keys for your provider, update the course’s parsing code to read those fields instead of the OpenAI-specific ones (and avoid assuming tokenization fields are consistent across providers)."
  },
  {
    "id": "91675fa206",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "uv keeps using the wrong virtual environment—how do I fix it?",
    "answer": "This usually happens when you have multiple projects in different folders, each with its own `.venv`, and `uv` (or your shell) is still pointing to the previous environment.\n\nTry this:\n\n- Run `deactivate` to exit the currently active virtual environment (if one is active).\n- `cd` into the folder for the project you want.\n- Create/use the environment in that folder: `uv venv` (this creates a `.venv` inside the current project directory).\n- Activate it: `source .venv/bin/activate`.\n- Run `which python` (or `where python` on Windows) and confirm it points into that project's `.venv`."
  },
  {
    "id": "74486f4c87",
    "course": "llm-zoomcamp",
    "section": "Module 1: RAG",
    "question": "Using GitHub Codespaces and added a Python package, but imports fail in my Jupyter notebook—what should I do?",
    "answer": "The notebook is probably using a different Python environment from the one\nwhere you installed the package.\n\nRun this in a notebook cell to see the kernel's interpreter:\n\n```python\nimport sys\n\nprint(sys.executable)\n```\n\nThen run `uv pip list` in the Codespaces terminal. If the package is listed\nthere but `sys.executable` does not point into the same project environment,\nuse the kernel picker near the top of the notebook to select the repository's\n`.venv`.\n\nRestart the kernel after switching, then try the import again."
  },
  {
    "id": "c296ea5a21",
    "course": "llm-zoomcamp",
    "section": "Module 1: Agentic RAG",
    "question": "What are tools and functions in agentic RAG?",
    "answer": "In the context of this course, tools and functions are closely related terms. Do not worry too much about the naming difference.\n\nA tool/function is something the model can call when it needs external help, such as searching documents, querying a database, calling an API, or running a calculation.\n\nThe important idea is that in agentic RAG, the model can decide when to call a tool/function. In basic RAG, retrieval is usually a fixed step that happens before the model answers."
  },
  {
    "id": "0d74a3616f",
    "course": "llm-zoomcamp",
    "section": "Module 1: Agentic RAG",
    "question": "Any free models with tool use support?",
    "answer": "Several Groq models offer tool use, such as Deepseek R1 or Llama 4, all of which can be used for free for development.\n\nOther providers also support tool or function calling, including Mistral, Gemini, and some local Ollama models.\n\nYou'll typically need to adapt the code when not using OpenAI, because tool schemas and response shapes differ between providers.\n\nFor more details, see the [Groq Tool Use Documentation](https://console.groq.com/docs/tool-use)."
  },
  {
    "id": "9e7b3f0c25",
    "course": "llm-zoomcamp",
    "section": "Module 1: Agentic RAG",
    "question": "Agents: \"AttributeError: 'str' object has no attribute 'output'\" when using OpenAI's Responses API on a non-OpenAI model",
    "answer": "The new OpenAI Responses API (`client.responses.create(...)`, accessed via `response.output`) is OpenAI-specific. Other providers (Mistral, Groq, Gemini, etc.) don't implement it.\n\nFor non-OpenAI providers, use the chat-completions API and read `response.choices[0].message.content`:\n\n```python\nresponse = client.chat.completions.create(\n    model=\"<provider-model>\",\n    messages=[{\"role\": \"user\", \"content\": prompt}],\n    tools=tools_schema,  # may need adapting per provider\n)\nreturn response.choices[0].message.content\n```\n\nYou'll also have to adapt the tools schema to whatever shape your provider expects.\n\nIt is okay to use the older `chat.completions` API for homework or projects if your provider supports that interface better than the Responses API."
  },
  {
    "id": "b9c7c6bb34",
    "course": "llm-zoomcamp",
    "section": "Module 1: Agentic RAG",
    "question": "I am using Azure OpenAI and I am still getting an error of Error code: 400 - {'error': {'message': \"Missing required parameter: 'tools[0].function'.\", 'type': 'invalid_request_error', 'param': 'tools[0].function', 'code': 'missing_required_parameter'}}?",
    "answer": "Modify the `get_weather_tool` JSON to be the following:\n\n```json\nget_weather_tool = {\n    \"type\": \"function\",\n    \"function\": {\n        \"name\": \"get_weather\",\n        \"description\": \"Get the current weather for a specific city or generate fake data\",\n        \"parameters\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"city\": {\n                    \"type\": \"string\",\n                    \"description\": \"The name of the city to get the weather for.\"\n                }\n            },\n            \"required\": [\"city\"],\n            \"additionalProperties\": false\n        }\n    }\n}\n```"
  },
  {
    "id": "0224d980b3",
    "course": "llm-zoomcamp",
    "section": "Module 1: Agentic RAG",
    "question": "toyaikit: how do I use it with a non-OpenAI provider (e.g. Groq 404 error) or with Anthropic instead of OpenAI?",
    "answer": "If you get a `404` (or similar) when using toyaikit with Groq or another non-OpenAI provider, it's because `OpenAIResponsesRunner` / `OpenAIClient` call OpenAI's Responses endpoint (`responses.create`), which only OpenAI implements. Switch to the **chat completions** classes, which use the standard `chat.completions.create` endpoint that Groq and other OpenAI-compatible providers support.\n\n### OpenAI or Groq (chat completions)\n\n```python\nimport os\nfrom openai import OpenAI\nfrom toyaikit.tools import Tools\nfrom toyaikit.llm import OpenAIChatCompletionsClient\nfrom toyaikit.chat.runners import OpenAIChatCompletionsRunner\n\ntools = Tools()\ntools.add_tools(my_tools_object)   # functions need type hints + an Args: docstring\n\n# OpenAI:\nllm_client = OpenAIChatCompletionsClient(model=\"gpt-4o-mini\", client=OpenAI())\n\n# Groq (same runner, just point the OpenAI client at Groq's base URL):\ngroq_client = OpenAI(\n    api_key=os.getenv(\"GROQ_API_KEY\"),\n    base_url=\"https://api.groq.com/openai/v1\",\n)\nllm_client = OpenAIChatCompletionsClient(model=\"llama-3.3-70b-versatile\", client=groq_client)\n\nrunner = OpenAIChatCompletionsRunner(\n    tools=tools,\n    developer_prompt=\"You are a helpful assistant.\",\n    llm_client=llm_client,\n)\nresult = runner.loop(prompt=\"What's the weather in Berlin?\")\nprint(result.last_message)\n```\n\n### Anthropic\n\n```python\nfrom toyaikit.tools import Tools\nfrom toyaikit.llm import AnthropicClient\nfrom toyaikit.chat.runners import AnthropicMessagesRunner\n\ntools = Tools()\ntools.add_tools(my_tools_object)\n\n# Reads ANTHROPIC_API_KEY from the environment automatically:\nllm_client = AnthropicClient(model=\"claude-haiku-4-5\")\n\nrunner = AnthropicMessagesRunner(\n    tools=tools,\n    developer_prompt=\"You are a helpful assistant.\",\n    llm_client=llm_client,\n)\nresult = runner.loop(prompt=\"What's the weather in Berlin?\")\nprint(result.last_message)\n```\n\nNotes:\n\n- `runner.loop(prompt=...)` runs one turn programmatically and returns a result with `.last_message`, `.all_messages`, and `.tokens` — no chat interface needed. Use `runner.run()` only for the interactive Jupyter loop.\n- Pass an explicit, current model id. For Anthropic, `claude-haiku-4-5` / `claude-sonnet-4-5` work; an outdated id can 404.\n- For Groq you'll see a harmless `UnknownModelWarning: No pricing data...` — the call still succeeds, only cost calculation is skipped."
  },
  {
    "id": "53f15299b6",
    "course": "llm-zoomcamp",
    "section": "Module 1 Homework",
    "question": "Where can I find the homework questions?",
    "answer": "Homework links are available in the course GitHub repo and in the course management platform.\n\nFor the 2026 Module 1 homework, use:\n\n- [Module 1 cohort materials](https://github.com/DataTalksClub/llm-zoomcamp/tree/main/cohorts/2026/01-agentic-rag)\n- [Module 1 homework instructions](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/cohorts/2026/01-agentic-rag/homework.md)\n\nThe course platform is useful for submission and deadlines, but the GitHub homework instructions often contain important extra context."
  },
  {
    "id": "83da208a64",
    "course": "llm-zoomcamp",
    "section": "Module 1 Homework",
    "question": "Homework: Returning Empty list after filtering my query (HW Q3)",
    "answer": "This is likely to be an error when indexing the data. First, you need to add the index settings before adding the data to the indices, then you will be good to go applying your filters and query."
  },
  {
    "id": "cfb07a27d5",
    "course": "llm-zoomcamp",
    "section": "Module 1 Homework",
    "question": "OpenRouter: Error code 402 when calling responses.create (max_output_tokens)",
    "answer": "OpenRouter can return APIStatusError with code 402 when responses.create() is called without a reasonable max_output_tokens limit. This happens because OpenRouter bills/limits checks against the maximum possible output (which can be very large, around 65536 tokens), so a free or low-limit key can be rejected before the model runs. This is different from a direct OpenAI endpoint (which typically returns 429 for insufficient quota).\n\nFix\n\nPass a lower limit in your responses.create() call:\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom openai import OpenAI\n\nload_dotenv()\n\nclient = OpenAI()  # uses OPENAI_API_KEY and OPENAI_BASE_URL from .env\n\nresponse = client.responses.create(\n    model=os.environ[\"OPENAI_MODEL\"],\n    input=message_history,\n    max_output_tokens=1024,\n)\n```\n\nFor Module 1 homework with rag_helper.py, add the same parameter in the ``llm()`` method.\n\n```python\nresponse = client.responses.create(\n    model=os.environ[\"OPENAI_MODEL\"],\n    input=message_history,\n    max_output_tokens=1024,\n)\n```\n\nFor Module 1 homework Q6 with ToyAIKit:\n\n```python\nfrom toyaikit.llm import OpenAIClient\n\nllm_client = OpenAIClient(\n    model=os.environ[\"OPENAI_MODEL\"],\n    extra_kwargs={\"max_output_tokens\": 1024},\n)\n```\n\n1024 is enough for homework answers; you can raise it later if needed.\n\nIf it still fails\n\n1) Confirm your `.env` points at OpenRouter:\n\n```env\nOPENAI_API_KEY=sk-or-v1-...\nOPENAI_BASE_URL=https://openrouter.ai/api/v1\nOPENAI_MODEL=openai/gpt-oss-120b:free\n```\n\n2) Check your OpenRouter key limit and remaining credits at https://openrouter.ai/settings/keys\n\n3) Prefer a pinned model (for example `openai/gpt-oss-120b:free`) instead of `openrouter/free`, which can route to models with different limits.\n\nNote: This behavior is different from OpenAI’s typical 429 handling for insufficient quota. If you still encounter issues after these steps, double-check the model and endpoint configuration to ensure the key is valid and has sufficient credits."
  },
  {
    "id": "b4300adeb1",
    "course": "llm-zoomcamp",
    "section": "Module 1 Homework",
    "question": "What does it mean to point RAG at the chunk index in Module 1 homework?",
    "answer": "It means you should build and search the index using the chunked documents, not the original full documents.\n\nFor example, if your original records are in `documents` and your split records are in `chunks`, fit the search index on `chunks`:\n\n```python\nindex.fit(chunks)\n```\n\nnot:\n\n```python\nindex.fit(documents)\n```\n\nThen the RAG pipeline retrieves relevant chunks and puts only those chunks into the prompt. This is what reduces the amount of context sent to the model."
  },
  {
    "id": "5e4ca64cb5",
    "course": "llm-zoomcamp",
    "section": "Module 1 Homework",
    "question": "Do homework answers need to match the options exactly?",
    "answer": "Not always. If your numeric answer is close to one of the options, choose the closest option.\n\nSmall differences can come from:\n\n- Slightly different filtering.\n- Different dataset versions.\n- Floating-point or rounding differences.\n- Different model/provider behavior.\n\nIf your answer is far from every option, re-check the question, the dataset version, and the GitHub homework instructions.\n\nSee the general homework guidance: [homework logistics](https://datatalks.club/docs/courses/zoomcamp-logistics/homework/)."
  },
  {
    "id": "7d3a2e3505",
    "course": "llm-zoomcamp",
    "section": "Module 1 Homework",
    "question": "What should I do if homework questions feel unclear?",
    "answer": "First read the GitHub homework instructions, not only the course platform page.\n\nFor Module 1 in the 2026 cohort, start here:\n\n- [Module 1 cohort materials](https://github.com/DataTalksClub/llm-zoomcamp/tree/main/cohorts/2026/01-agentic-rag)\n- [Module 1 homework instructions](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/cohorts/2026/01-agentic-rag/homework.md)\n\nThe homework follows the lesson workflow, but usually uses a different dataset or asks you to apply the same idea in a slightly different way.\n\nIf it is still unclear, ask in Slack and include the code or command output as text, not a screenshot. Follow the [asking questions guidelines](https://datatalks.club/docs/courses/zoomcamp-logistics/asking-questions/)."
  },
  {
    "id": "fe8fed31e6",
    "course": "llm-zoomcamp",
    "section": "Module 1 Homework",
    "question": "How do I get token counts for Module 1 homework if I use a different provider?",
    "answer": "For the current Module 1 homework, get the token count from the model response object.\n\nFor example, OpenAI-compatible clients usually return usage information on the response, such as `response.usage.input_tokens` or `response.usage.prompt_tokens`, depending on the API style.\n\nIf you use a non-OpenAI provider, check the provider's response object for its usage fields and adapt the code. Do not use `tiktoken` or `cl100k_base` as a generic tokenizer for Gemini, Mistral, Hugging Face, Groq, or other providers because tokenization differs by model.\n\nIf your provider does not expose token usage, use that provider's native tokenizer as an approximation. For multiple-choice homework questions, choose the closest option."
  },
  {
    "id": "bcdb8740ce",
    "course": "llm-zoomcamp",
    "section": "Module 1 Homework",
    "question": "My homework submission is rejected because my repo URL returns a non-200 status (e.g. 500) — how do I fix it?",
    "answer": "The submission checker fetches the URL you submit with a GET request and expects an HTTP `200` response. A non-200 status (404, 500, etc.) almost always means the link isn't publicly reachable as-is. Check the usual causes:\n\n- **Private repo** — make the repository public so the checker can access it.\n- **Trailing `.git`** — submit the plain repository URL (e.g. `https://github.com/you/repo`), not `https://github.com/you/repo.git`.\n- **Typo in the URL** — paste the exact link.\n\nA quick test: open the URL in a private/incognito browser window. If it loads for you there, it'll work for the checker too."
  },
  {
    "id": "69430a79a8",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "What are embeddings?",
    "answer": "Embeddings refer to the process of converting non-numerical data into numerical data while preserving meaning and context. When similar non-numerical data is input into an embedding algorithm, it should yield similar numerical data. The proximity of these numerical values allows for the use of mathematical semantic similarity algorithms. Related concepts include the \"vector space model\" and \"dimensionality reduction.\""
  },
  {
    "id": "aa3f597424",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Warning: 'model \"multi-qa-mpnet-base-dot-v1\" was made on sentence transformers v3.0.0 bet' how to suppress?",
    "answer": "To suppress the warning, upgrade `sentence-transformers` to version 3.0.0 or higher. You can do this by running the following command:\n\n```bash\npip install sentence-transformers>=3.0.0\n```"
  },
  {
    "id": "92cd22cada",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Why was .dot(...) used directly to compute cosine similarity in the lesson, but normalization is emphasized in the homework?",
    "answer": "In the lesson, `.dot(...)` was used under the assumption that the embeddings returned by the model (e.g., `model.encode(...)` from OpenAI) are already normalized to have unit length. In that case, the dot product is mathematically equivalent to cosine similarity.\n\nIn the homework, however, we use classic embeddings like TF-IDF + SVD, which are not normalized by default. This means that the dot product does not represent cosine similarity unless we manually normalize the vectors."
  },
  {
    "id": "5c4a8d2e60",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Vector search: should I embed the question, the answer, or both?",
    "answer": "There's no single right answer — it's an experiment to run on your dataset. The course shows three options:\n\n- Embed the answer (`text`) only — works because the model captures semantic similarity between questions and their answers.\n- Embed the question only — works because user queries look like the indexed questions.\n- Embed `question + \" \" + text` — often the best, but produces longer input and slightly more cost.\n\nPick whichever gives the best hit rate / MRR on your ground-truth set. The course materials include a side-by-side comparison."
  },
  {
    "id": "db78580409",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "What is the cosine similarity?",
    "answer": "Cosine similarity is a measure used to calculate the similarity between two non-zero vectors, often used in text analysis to determine how similar two documents are based on their content. This metric computes the cosine of the angle between two vectors, which are typically word counts or TF-IDF values of the documents. The cosine similarity value ranges from -1 to 1, where 1 indicates that the vectors are identical, 0 indicates that the vectors are orthogonal (no similarity), and -1 represents completely opposite vectors."
  },
  {
    "id": "95feb4e75b",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Why does cosine similarity reduce to a matrix multiplication between the embeddings and the query vector?",
    "answer": "Cosine similarity measures how aligned two vectors are, regardless of their magnitude. When all vectors (including the query) are normalized to unit length, their magnitudes no longer matter. In this case, cosine similarity is equivalent to simply taking the dot product between the query and each document embedding. This allows us to compute similarities efficiently using matrix multiplication."
  },
  {
    "id": "1e829a8c6f",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Do I need a new GitHub repo for Module 2, or just a new codespace?",
    "answer": "Just a new codespace. A codespace is an environment (see *Can I run the course locally instead of Codespaces?*); you create it from your existing repository, so you don't need a new GitHub repo.\n\nUse a separate codespace for Module 2 because the vector-search dependencies are fairly heavy. Keeping them isolated means you can simply stop or delete that codespace when you're done, rather than leaving the extra weight in your Module 1 environment. Setup is quick.\n\nSince you may delete this codespace later, commit your work to your repository (see *What happens to code saved in Codespaces if I do not commit it?*)."
  },
  {
    "id": "29b69fbe0b",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Why does download.py hang at 0% when downloading model.onnx from HuggingFace?",
    "answer": "This can happen due to a slow or blocked connection to HuggingFace's CDN. Fix: download the file directly from your browser at https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/onnx/model.onnx and save it to models/Xenova/all-MiniLM-L6-v2/model.onnx. The script checks if the file exists locally before re-downloading, so it'll skip straight past once it's there."
  },
  {
    "id": "e889793af9",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Why do the matrix and for-loop versions of vector search give slightly different results?",
    "answer": "In [Lesson 4 (Vector Search)](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/02-vector-search/lessons/04-vector-search.md) we compute the scores two ways — `scores = X.dot(v_query)` and the equivalent for-loop — and they can come out slightly different. This is normal, it's just floating-point precision. If you compare them with `np.allclose(scores, scores_loop)` you might get `False`, even though both compute the same dot products. They just add the numbers up in a slightly different order, and floating-point addition isn't perfectly associative, so the results can differ in the last few decimal places.\n\nUnder the hood, `X.dot(v)` runs optimized BLAS code that sums in a different order than a sequential Python loop. The math is the same; only the rounding differs, so the values aren't really \"wrong.\"\n\nJust compare them with a small tolerance instead of expecting an exact match:\n\n```python\nnp.allclose(scores, scores_loop, atol=1e-5)\n```"
  },
  {
    "id": "1a7b27c4df",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Why do I get IndexError: list index out of range when accessing the best chunk?",
    "answer": "The error typically happens when the number of embeddings you generate does not match the number of document chunks. Make sure you create embeddings directly from the chunk list:\n\n```python\ncontents = [chunk[\"content\"] for chunk in chunks]\nX = embedder.encode_batch(contents)\n```\n\nThe number of rows in `X` should be equal to `len(chunks)`."
  },
  {
    "id": "6609b934b6",
    "course": "llm-zoomcamp",
    "section": "Module 2: Vector Search",
    "question": "Why does FastEmbed raise an SSL error even though the model is cached?",
    "answer": "During construction, FastEmbed normally checks the model source. A temporary\nHugging Face or network failure can therefore stop initialization even when\nyou previously downloaded the model.\n\nAfter you have populated the cache once, load the model with the same cache\ndirectory and enable local-only loading:\n\n```python\nfrom fastembed import TextEmbedding\n\nmodel = TextEmbedding(\n    model_name=\"sentence-transformers/all-MiniLM-L6-v2\",\n    cache_dir=\"/models/fastembed\",\n    local_files_only=True,\n)\n```\n\nMount `/models/fastembed` into the container and use that same `cache_dir` when\ninitially downloading and later loading the model. A model stored only in\nanother cache directory, such as an unmounted host cache, won't be available\ninside the container.\n\n`SparseTextEmbedding` and `TextCrossEncoder` also accept `cache_dir` and\n`local_files_only`.\n\nIf you downloaded and prepared the FastEmbed model directory yourself, you can\ninstead pass `specific_model_path`. Use retries only while initially\ndownloading the model. Catching every exception around normal offline\nconstruction can hide configuration and model-format errors.\n\nAn incomplete Hugging Face cache can still trigger FastEmbed's fallback network\nbehavior. Use a complete, explicitly mounted cache or `specific_model_path` for\na fully offline deployment."
  },
  {
    "id": "eaa17a9dc6",
    "course": "llm-zoomcamp",
    "section": "Module 2 Homework",
    "question": "My Module 2 homework cosine similarity (Q2) isn't any of the options — what am I doing wrong?",
    "answer": "The most common cause is using a different embedding model than the homework specifies. Homework 2 tells you **not** to use `sentence-transformers` and to use the lightweight ONNX `Embedder` from `embedder.py` instead:\n\n```python\nfrom embedder import Embedder\nmodel = Embedder()\n```\n\nBoth approaches produce the same vectors for the same model, but if you embed with a different model (for example the `multi-qa-mpnet-base-dot-v1` model from the lessons, or `all-mpnet-base-v2`) you'll get a cosine value that isn't among the options.\n\nAlso check that:\n\n- You're embedding the page's `content` field (not the filename or the whole dict).\n- You're comparing against the query vector from Q1.\n- The vectors are normalized — the `Embedder` returns normalized vectors, so the dot product is the cosine similarity directly.\n\nIf your value is still slightly off after using the right model, pick the closest option — small numerical differences are expected."
  },
  {
    "id": "3860e5fe8b",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "How do I configure the Gemini (and OpenAI/Tavily) API keys for the Kestra module?",
    "answer": "Kestra reads secrets from environment variables that are prefixed with `SECRET_` and whose value is **base64-encoded**. You export them in the terminal *before* starting Kestra. For Gemini you need two variables — the plain one (used by the AI Copilot) and the base64-encoded `SECRET_` one (used by the flows):\n\n```bash\nexport GEMINI_API_KEY=\"your-gemini-api-key-here\"                              # used by AI Copilot\nexport SECRET_GEMINI_API_KEY=$(echo -n $GEMINI_API_KEY | base64)             # used by the flows\nexport SECRET_OPENAI_API_KEY=$(echo -n \"your-openai-api-key-here\" | base64)  # required for flow 3\nexport SECRET_TAVILY_API_KEY=$(echo -n \"your-tavily-api-key-here\" | base64)  # required for web search (flows 3, 5, 6)\n```\n\nThen start (or restart) Kestra so it picks up the variables:\n\n```bash\ndocker compose up -d\n```\n\nInside a flow, reference a secret **without** the `SECRET_` prefix:\n\n```yaml\n{{ secret('GEMINI_API_KEY') }}\n```\n\nSee the [setup lesson](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/03-orchestration/lessons/03-setup.md) for the full walkthrough. Never commit your keys to Git."
  },
  {
    "id": "04440cab11",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "Kestra AI Copilot replies \"I can only assist with creating Kestra flows\" — how do I fix it?",
    "answer": "This message means the AI Copilot didn't get a valid Gemini API key, so it falls back to a canned refusal. In Kestra's Open Source edition the Copilot only supports Gemini, and it reads the **plain** `GEMINI_API_KEY` variable (not the base64-encoded `SECRET_GEMINI_API_KEY` that the flows use).\n\nMake sure you exported the plain key before starting Kestra, then restart it:\n\n```bash\nexport GEMINI_API_KEY=\"your-gemini-api-key-here\"\ndocker compose up -d\n```\n\nIf it still fails, the key is usually missing, mistyped, or rate-limited:\n\n- Confirm the variable is actually set in the shell you ran `docker compose up` from (`echo $GEMINI_API_KEY`).\n- Generate a fresh key in [Google AI Studio](https://aistudio.google.com/app/apikey).\n- If you've been running the agent/multi-agent flows a lot, you may have hit the free-tier quota (`429 Resource Exhausted`) — wait a minute and retry.\n\nNote that the Copilot needs the **plain** `GEMINI_API_KEY` while the flows need `SECRET_GEMINI_API_KEY` — export both."
  },
  {
    "id": "c8ca21af33",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "I set SECRET_GEMINI_API_KEY but my Kestra flow still fails with an invalid/missing key — what's wrong?",
    "answer": "The most common cause is a corrupted base64 value. If you encode the key with a trailing space or newline, the decoded secret is wrong and authentication fails.\n\nUse `echo -n` (which omits the trailing newline) and quote the value:\n\n```bash\nexport SECRET_GEMINI_API_KEY=$(echo -n \"$GEMINI_API_KEY\" | base64)\n```\n\nOther things to check:\n\n- You must **restart** Kestra after exporting, so the container picks up the new variables: `docker compose up -d` (or `docker compose down && docker compose up -d`).\n- Reference the secret in flows **without** the `SECRET_` prefix: `{{ secret('GEMINI_API_KEY') }}`.\n- Export the variables in the same shell session you start Kestra from.\n\nA cleaner alternative is to put the keys in a `.env` file and pass it explicitly:\n\n```bash\ndocker compose --env-file ./.env -f ./docker-compose.yml up -d\n```"
  },
  {
    "id": "193612db63",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "Why do we need orchestration / Kestra — can't I just run the code in a notebook?",
    "answer": "Notebooks are great for learning and experimenting, but real AI workflows need more than a script that runs once: scheduling, retries, monitoring, secret management, and reliably chaining tasks together. That's what an orchestrator like Kestra provides.\n\nIn this module Kestra is also the vehicle for the AI techniques the course is teaching: AI Copilot to generate flows from natural language, RAG to ground responses in real data, and AI agents that decide which tools to call. The goal is to see how AI fits into production-style workflows, not just notebook cells.\n\nKestra's AI plugins also work with any major provider (OpenAI, Gemini, Anthropic, and more), so you can swap providers in a flow without changing anything else. See the [module intro](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/03-orchestration/lessons/01-intro.md) for the full motivation."
  },
  {
    "id": "cce328db64",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "Is Kestra mandatory for the LLM Zoomcamp course, or can I use another orchestrator?",
    "answer": "No. Kestra is the orchestrator the course teaches in Module 3, but it is not a requirement beyond that module's homework.\n\nFor the **capstone project** you are not restricted in technology: you can use Airflow, Prefect, Dagster, or no orchestrator at all. A plain Python script that ingests and indexes your data is enough for full points on the ingestion-pipeline criterion (a Jupyter notebook with the same steps is worth 1 point instead of 2). See the [project guidelines](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/project.md) for details.\n\nThe course uses Kestra in Module 3 because it is the vehicle for the AI techniques being taught — AI Copilot for generating flows, RAG for grounding responses, and AI agents that call tools. Other orchestrators cover the same scheduling/retry/monitoring ground, but they won't map onto those specific lessons, so it's worth running the Kestra flows at least once. If you already know Airflow (or another tool) and want to compare, give Kestra a try for the module and then use whatever fits your project best.\n\nKestra's AI plugins also work with any major provider (OpenAI, Gemini, Anthropic, and more), so you can swap providers in a flow without changing anything else — see the [supported providers list](https://kestra.io/plugins/plugin-ai/provider)."
  },
  {
    "id": "f442efe5b4",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "Which AI providers does Kestra support besides Gemini? Can I use Groq, Ollama, or a local model?",
    "answer": "Kestra's AI plugin is provider-agnostic: it supports OpenAI, Gemini, Anthropic, xAI, Grok, and any OpenAI-compatible provider, including local models served through Ollama or LM Studio. You swap the provider block in a flow without changing anything else. See the [full list of supported providers](https://kestra.io/plugins/plugin-ai/provider).\n\nThe course uses Gemini because it has a generous free tier, but you are free to use any provider. The [awesome-llms list](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/awesome-llms.md) in the course repo tracks free and free-tier options; Groq is a popular choice because it is OpenAI-compatible and works with both the chat completions and responses APIs.\n\nThere are two ways to use AI in Kestra:\n\n- The **AI plugin** (`io.kestra.plugin.ai`) is the generic one. It is the most flexible for switching providers, though new vendor-specific API features take a bit longer to land here.\n- The **provider-specific plugins** (e.g. `plugin-gemini`, `plugin-openai`) expose features unique to that vendor, such as Gemini video generation, before they reach the generic AI plugin.\n\nFor OpenAI-compatible providers that don't have their own plugin (DeepSeek, Groq, xAI), point the OpenAI plugin at the provider's base URL instead of the default endpoint."
  },
  {
    "id": "bfe5502c30",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "Can I use Kestra without writing any code? Is there a no-code option?",
    "answer": "Yes. You have three options that require no hand-written YAML or code:\n\n- **The no-code form editor** builds a flow from a form instead of YAML. It is form-based (not drag-and-drop like n8n), so it suits people who don't want to edit YAML directly.\n- **The AI Copilot** generates a flow from a natural-language description, and you can iterate with follow-up messages to add tasks or change the order. See the [AI Copilot lesson](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/03-orchestration/lessons/04-ai-copilot.md).\n- **External agent skills** (Claude Code, Codex, Kestra CTL) let you generate flows and push them into Kestra from your editor without touching the Kestra UI.\n\nUnlike Airflow, where business logic and orchestration logic are intertwined in Python, Kestra keeps the two separate: your Python script stays a plain script, and the workflow YAML just describes how and when to run it. This is closer to a cron job than to a traditional Airflow DAG.\n\nFor simple point-to-point automations (e.g. \"post a Slack message when a YouTube video goes live\"), a tool like Zapier or n8n may be simpler and cheaper. Kestra's advantage is observability, retries, and handling more complex or technical workflows in one place."
  },
  {
    "id": "93a3e8b98c",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "Why does a generic AI assistant generate Kestra flow YAML with properties that don't exist, and how can I avoid it?",
    "answer": "A general AI (e.g., ChatGPT) isn't grounded in Kestra's plugin schemas for your running version, so it can surface plausible but invalid property names (such as `bucket`/`name`) instead of the real, version-specific property. For example:\n\n**GCS Upload** — invented `bucket`/`name` props vs. the real `to` property:\n\n```yaml\n# Incorrect (invented props)\n- id: upload_to_gcs\n  type: \"io.kestra.plugin.gcp.gcs.Upload\"\n  bucket: \"my-bucket\"\n  name: \"path/to/file\"\n\n# Correct\n- id: upload_to_gcs\n  type: \"io.kestra.plugin.gcp.gcs.Upload\"\n  to: \"gs://my-bucket/path/to/file\"\n```\n\n**BigQuery LoadFromGcs** — split `projectId`/`dataset`/`table` vs. the real `destinationTable`:\n\n```yaml\n# Incorrect (pseudo-split properties)\n- id: load_to_bq\n  type: \"io.kestra.plugin.gcp.bigquery.LoadFromGcs\"\n  projectId: \"my-project\"\n  dataset: \"my_dataset\"\n  table: \"my_table\"\n\n# Correct\n- id: load_to_bq\n  type: \"io.kestra.plugin.gcp.bigquery.LoadFromGcs\"\n  destinationTable: \"my-project.my_dataset.my_table\"\n```\n\nTo avoid this:\n\n- Cross-check generated YAML against the official plugin docs: https://kestra.io/plugins/plugin-gcp\n- Use Kestra's built-in AI Copilot, which is grounded in the current plugin schema for your running version.\n- Validate and test your YAML in your Kestra environment to ensure it parses and runs as expected.\n\nNote that plugin properties can vary by plugin version; what's correct in one release may be invalid in another. If you share a snippet, we can help verify it against the docs."
  },
  {
    "id": "a4adc70f41",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "Why is token usage monitored in Kestra workflows?",
    "answer": "Token usage is tracked because it maps directly to what LLM providers charge you for, and Kestra workflows run LLM calls on a schedule or in loops where costs add up fast. The main reasons:\n\n- **Cost control**: tokens are the unit providers bill by, so tracking usage lets you budget, forecast, and spot cost spikes before they get expensive.\n- **Prompt optimization**: seeing how many tokens each prompt and response consumes helps you tighten prompts and trim output without losing quality.\n- **Guardrails**: in long-running or looping flows, usage monitoring lets you set thresholds and alerts so a runaway task doesn't quietly rack up a large bill.\n\nIn practice, each AI task in a Kestra flow can capture usage from the provider's response (for example `usage.total_tokens`), and you can surface those numbers in logs, metrics, or dashboards to keep an eye on spend per run."
  },
  {
    "id": "dd8b4c9fda",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "How do I fix the Docker error 'mounts denied: The path /tmp/... is not shared from the host'?",
    "answer": "This error means Docker Desktop isn't allowed to access the host path the container is trying to mount (commonly `/tmp/kestra-wd` when running Kestra). Add the path to Docker Desktop's file sharing list:\n\n1. Open Docker Desktop.\n2. Click the gear icon (Settings) in the top right.\n3. Go to **Resources > File Sharing**.\n4. Click the **+** (Add) button.\n5. Enter `/tmp` (or the specific path like `/tmp/kestra-wd`) and press Enter.\n6. Click **Apply & restart**.\n\nOnce Docker restarts, rerun `docker compose up -d` to start Kestra."
  },
  {
    "id": "d51bbe2da0",
    "course": "llm-zoomcamp",
    "section": "Module 3: Orchestration",
    "question": "Kestra: Gemini model returns 404 or is unavailable. What do I do?",
    "answer": "Model availability changes over time and can vary by account. If a course workflow returns a `404 NOT_FOUND` saying that its Gemini model is unavailable, replace the model id in the affected YAML file with one currently available to you. For example, in a Kestra flow:\n\n```yaml\nprovider:\n  type: io.kestra.plugin.ai.provider.GoogleGemini\n  modelName: gemini-3.5-flash\n  apiKey: \"{{ secret('GEMINI_API_KEY') }}\"\n```\n\nIn `docker-compose.yml`, update the equivalent `model-name` setting. Then rerun the flow, or restart the Docker Compose services if you changed their configuration.\n\nCheck the [official Gemini model list](https://ai.google.dev/gemini-api/docs/models) for current model ids. Do not assume the original model was permanently shut down based on a temporary `404`; select a model that the API makes available to your account."
  },
  {
    "id": "f29f622684",
    "course": "llm-zoomcamp",
    "section": "Module 3 Homework",
    "question": "What do I submit for the Module 3 (Kestra) homework? Everything was done inside Kestra.",
    "answer": "The submission form asks for a link to a public repo (GitHub or any code-hosting site), but unlike earlier modules the work happens inside the Kestra UI rather than as code you write from scratch. That leaves people unsure what to put in the repo.\n\nDownload the Kestra flows you created or edited for the homework and commit them to the repo. The flow YAML files are the code you submit. If you also wrote or tweaked any scripts the flows call, include those too. A README summarizing your answers is welcome but not required as long as the flow files are there.\n\nPut the flows in a folder (e.g. `module-3/flows/`), make the repo public, and paste the link in the submission form. The form answers (Q1, Q2, etc.) are separate and go directly into the form fields."
  },
  {
    "id": "a3c7e1b4f9",
    "course": "llm-zoomcamp",
    "section": "Module 4: Evaluation",
    "question": "Evaluation: \"JSONDecodeError: Expecting value\" when generating ground-truth questions with the LLM",
    "answer": "The LLM sometimes wraps the JSON in a markdown code fence or adds prose around it, so `json.loads(response)` fails with:\n\n```\nJSONDecodeError: Expecting value: line 1 column 1 (char 0)\n```\n\nForce JSON output with OpenAI's `response_format`:\n\n```python\nresponse = openai_client.chat.completions.create(\n    model='gpt-4o-mini',\n    messages=[{\"role\": \"user\", \"content\": prompt}],\n    response_format={\"type\": \"json_object\"},\n)\nparsed = json.loads(response.choices[0].message.content)\n```\n\nAlso be explicit in the prompt about the expected shape:\n\n```\nOutput a JSON object with a single key \"questions\" whose value is a list of 5 strings.\nDo not include any extra text, explanation, or formatting.\n```\n\nMost providers have an equivalent (Gemini's `response_mime_type=\"application/json\"`, Groq's `response_format`, etc.)."
  },
  {
    "id": "b8f2d6a30c",
    "course": "llm-zoomcamp",
    "section": "Module 4: Evaluation",
    "question": "Evaluation: Jupyter kernel crashes when embedding the ground-truth set",
    "answer": "Small-RAM machines (Codespaces default, low-end laptops) run out of memory when an embedding model is loaded alongside the rest of the notebook state.\n\nWorkarounds:\n\n- Switch to a smaller embedder. `sentence-transformers/all-MiniLM-L6-v2` (384-dim) is a common drop-in. Note: switching models will change your hit-rate / MRR numbers, so re-run the eval after the switch.\n- Move the embedding step into a separate Python script that you run from the terminal, then load the saved vectors back into the notebook.\n- Use a Codespaces machine type with more RAM (Settings → \"Machine type\" on a Codespace), or run locally.\n- Process the ground-truth set in batches and free memory between batches (`del`, `gc.collect()`)."
  },
  {
    "id": "c1e9f4b820",
    "course": "llm-zoomcamp",
    "section": "Module 4: Evaluation",
    "question": "Evaluation: hitting rate limits while generating the ground-truth dataset",
    "answer": "Free-tier Gemini limits both per-minute and per-day requests. Adding `time.sleep(4)` only fixes the per-minute side — a long `tqdm` loop can still blow through the per-day quota in one run.\n\nOptions when this happens:\n\n- Spend ~$5 on OpenAI and use `gpt-4o-mini`. It's cheap enough to embed/generate the entire ground-truth set and has higher rate limits.\n- Use Groq's free tier (`llama-3.3-70b-versatile`) — generous request-per-minute limits.\n- Lower concurrency for thread-pool calls. Use a smaller pool size (2–3 workers) instead of pushing the API hard.\n- Resume from where you stopped. Save progress periodically (e.g. dump the partial results to a JSONL file) so a hit limit doesn't lose all work."
  },
  {
    "id": "57c8d3a1e4",
    "course": "llm-zoomcamp",
    "section": "Module 4: Evaluation",
    "question": "Why is there a gap between lessons 6 and 11 in Module 4?",
    "answer": "No lessons are missing. Module 4 is split into two subsections, and the filenames reflect that structure:\n\n- `01`–`06`: RAG evaluation\n- `11`–`15`: agent evaluation\n\nYou can read the numbering as lessons 0.1–0.6 for the first subsection and 1.1–1.5 for the second. That is why the filenames jump from `06-search-tuning.md` to `11-evaluation-intro.md`."
  },
  {
    "id": "8a5ece0688",
    "course": "llm-zoomcamp",
    "section": "Module 4: Evaluation",
    "question": "Why can MRR improve while Hit Rate stays the same?",
    "answer": "Hit Rate can stay unchanged while MRR improves when the same queries still\nretrieve a relevant document in the top‑k results, but the first relevant\ndocument moves higher. For example, moving a correct result from position 5 to\nposition 1 leaves that query's Hit Rate contribution at `1`, while its\nreciprocal-rank contribution improves from `1/5` to `1`.\n\nIf the final Hit Rate and MRR values are exactly equal, verify the evaluation\ncode before trusting the result. Equality is legitimate only when every query\nwith a hit has its first relevant result at position 1 (including the special\ncase where both metrics are zero). Check that:\n\n- Hit Rate counts a query once when any result is relevant.\n- MRR uses only the first relevant result and calculates `1 / (rank + 1)`.\n- The MRR loop stops after the first relevant result.\n- Both metrics divide by the same total number of queries.\n- The relevance labels and result ordering are correct before aggregation.\n\nReranking, chunking, or embedding changes can improve MRR without changing Hit\nRate, but inspect the per-query relevance lists to confirm that relevant results\nactually moved upward."
  },
  {
    "id": "4de1cb9a3f",
    "course": "llm-zoomcamp",
    "section": "Module 4: Evaluation",
    "question": "Where is the code or notebook for the agent-evaluation lesson?",
    "answer": "The agent-evaluation code is in the\n[`04-evaluation/code` directory](https://github.com/DataTalksClub/llm-zoomcamp/tree/main/04-evaluation/code).\nThe lesson itself is\n[`14-agent-evaluation.md`](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/04-evaluation/lessons/14-agent-evaluation.md)."
  },
  {
    "id": "15e931476c",
    "course": "llm-zoomcamp",
    "section": "Module 5: Monitoring",
    "question": "In Windows OS: OSError: [WinError 126] The specified module could not be found. Error loading \"C:\\Users\\USER\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\torch\\lib\\fbgemm.dll\" or one of its dependencies.",
    "answer": "- **Solution 1**: Install Visual C++ Redistributable.\n\n- **Solution 2**: Install Visual Studio, not Visual Studio Code. \n\n  <{IMAGE:image_1}>\n\n  For more details, please follow this link: [discuss.pytorch.org](https://discuss.pytorch.org/t/failed-to-import-pytorch-fbgemm-dll-or-one-of-its-dependencies-is-missing/201969)"
  },
  {
    "id": "4b65d5542d",
    "course": "llm-zoomcamp",
    "section": "Module 5: Monitoring",
    "question": "OperationalError when running python prep.py: psycopg2. OperationalError: could not translate host name \"postgres\" to address: No such host is known. How do I fix this issue?",
    "answer": "To resolve this error, update the `.env` file:\n\n- Change the `POSTGRES_HOST` variable to `localhost`.\n\n```ini\nPOSTGRES_HOST=localhost\n```"
  },
  {
    "id": "641aedbbf5",
    "course": "llm-zoomcamp",
    "section": "Module 5: Monitoring",
    "question": "How set Pandas to show entire text content in a column. Useful to view the entire Explanation column content in the LLM-as-judge section of the offline-rag-evaluation notebook",
    "answer": "By default, Pandas truncates text content in a column to 50 characters. To view the entire explanation provided by the judge LLM for a non-relevant answer, use the following instruction:\n\n```python\npd.set_option('display.max_colwidth', None)\n```\n\n- **Option:** `display.max_colwidth`\n- **Type:** `int` or `None`\n- **Description:** Sets the maximum width in characters of a column in the representation of a pandas data structure. When a column overflows, a \"...\" placeholder is used in the output. Setting it to 'None' allows unlimited width.\n- **Default:** 50\n\nRefer to the [official documentation](https://pandas.pydata.org/docs/user_guide/options.html) for more details.\n\n<{IMAGE:image_1}>"
  },
  {
    "id": "5039707c1a",
    "course": "llm-zoomcamp",
    "section": "Module 5: Monitoring",
    "question": "How to normalize vectors in a Pandas DataFrame column (or Pandas Series)?",
    "answer": "To normalize vectors in a Pandas DataFrame column, you can use the following approach:\n\n```python\nimport numpy as np\n\nnormalize_vec = lambda v: v / np.linalg.norm(v)\n\ndf[\"new_col\"] = df[\"org_col\"].apply(normalize_vec)\n```"
  },
  {
    "id": "b4c8ac5a0c",
    "course": "llm-zoomcamp",
    "section": "Module 5: Monitoring",
    "question": "How to compute the quantile or percentile of Pandas DataFrame column (or Pandas Series)?",
    "answer": "To compute the 75% percentile or 0.75 quantile:\n\n```python\nquantile = df[\"col\"].quantile(q=0.75)\n```"
  },
  {
    "id": "66ccbb7da0",
    "course": "llm-zoomcamp",
    "section": "Module 5: Monitoring",
    "question": "How can I remove all Docker containers, images, and volumes, and builds from the terminal?",
    "answer": "1. Delete all containers (including running ones):\n\n```bash\ndocker rm -f $(docker ps -aq)\n```\n\n2. Remove all images:\n\n```bash\ndocker rmi -f $(docker images -q)\n```\n\n3. Delete all volumes:\n\n```bash\ndocker volume rm $(docker volume ls -q)\n```"
  },
  {
    "id": "2e729f1271",
    "course": "llm-zoomcamp",
    "section": "Module 5: Monitoring",
    "question": "Session State: I want the user to only be able to give feedback once per submission (+1 or -1). When I submit text using the ask button, the buttons should be disabled if `st.session.submitted` is False. The issue is mainly with `st.session.submitted`, which gets reassigned to True again despite one feedback button being pressed.",
    "answer": "Solved:\n\n[Refer to the solution on Streamlit Discuss](https://discuss.streamlit.io/t/streamlit-session-attributes-reassigned-somewhere/76059/2?u=mohammed2)"
  },
  {
    "id": "ba1d5f13e7",
    "course": "llm-zoomcamp",
    "section": "Module 5 Homework",
    "question": "Why does my SQLite exporter receive no spans, or report \"Overriding of current TracerProvider is not allowed\"?",
    "answer": "OpenTelemetry allows the global tracer provider to be registered only once per\nPython process. In a notebook, creating another `TracerProvider` and calling\n`trace.set_tracer_provider(provider)` again doesn't replace the first provider.\nA tracer returned by `trace.get_tracer(...)` therefore remains connected to the\noriginal exporter, which can leave `traces.db` empty.\n\nRestart the kernel, replace the exporter in the original setup cell, and run the\nsetup cells once in order.\n\nIf you intentionally need an independent provider for manually created spans,\nget the tracer directly from it:\n\n```python\ntracer = provider.get_tracer(\"llm-zoomcamp\")\n```\n\nIn a script, initialize the provider and exporter once. Separate\n`python script.py` runs start fresh Python processes, so they don't share the\nprevious global provider."
  },
  {
    "id": "702399225b",
    "course": "llm-zoomcamp",
    "section": "Module 5 Homework",
    "question": "Why does my execution script freeze or crash with `RecursionError` when initializing OpenTelemetry with `ConsoleSpanExporter` in Module 5?",
    "answer": "Upgrade OpenTelemetry first. Older releases did not support Python 3.14, while\ncurrent releases do:\n\n```bash\nuv add --upgrade opentelemetry-api opentelemetry-sdk\n```\n\nRestart the Python process or notebook kernel after upgrading, then run the\n`ConsoleSpanExporter` example again. You can confirm which versions the project\nuses with:\n\n```bash\nuv run python -c \"import importlib.metadata as m; print(m.version('opentelemetry-api'), m.version('opentelemetry-sdk'))\"\n```\n\nIf upgrading is not possible, use Python 3.13 for the homework.\n\nDo not replace the console exporter with an incomplete custom exporter merely\nto hide the error. When you reach the SQLite question, use the complete\n`SQLiteSpanExporter` from the homework, including its `shutdown()` and\n`force_flush()` methods."
  },
  {
    "id": "01517c80df",
    "course": "llm-zoomcamp",
    "section": "Module 5 Homework",
    "question": "How do I adapt the monitoring homework to a provider such as Groq or Gemini that uses Chat Completions?",
    "answer": "Do not give `starter.py` a fake `OPENAI_API_KEY`. Configure its module-level\nclient for your provider instead. For Groq:\n\n```python\nimport os\nfrom openai import OpenAI\n\nclient = OpenAI(\n    api_key=os.environ[\"GROQ_API_KEY\"],\n    base_url=\"https://api.groq.com/openai/v1\",\n)\nrag = RAGBase(\n    index=index,\n    llm_client=client,\n    model=\"<current-groq-model-id>\",\n)\n```\n\nUse a model ID currently available from your provider; an OpenAI model name\nwill return a `404` on Groq.\n\nNext, keep the course's `LLMCallRecord` and `RAGBase`, but change the three\n`RAGWithMetrics` methods that depend on the OpenAI Responses API. Providers\nwith an OpenAI-compatible Chat Completions endpoint return the answer and token\ncounts under different attributes:\n\n```python\nclass RAGWithMetrics(RAGBase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.last_call: LLMCallRecord | None = None\n\n    def llm(self, prompt):\n        start_time = time.time()\n        response = self._call_llm(prompt)\n        response_time = time.time() - start_time\n        self._log_response(prompt, response, response_time)\n        return response.choices[0].message.content\n\n    def _call_llm(self, prompt):\n        return self.llm_client.chat.completions.create(\n            model=self.model,\n            messages=[\n                {\"role\": \"system\", \"content\": self.instructions},\n                {\"role\": \"user\", \"content\": prompt},\n            ],\n        )\n\n    def _log_response(self, prompt, response, response_time):\n        usage = response.usage\n        self.last_call = LLMCallRecord(\n            model=self.model,\n            prompt=prompt,\n            instructions=self.instructions,\n            answer=response.choices[0].message.content,\n            prompt_tokens=usage.prompt_tokens,\n            completion_tokens=usage.completion_tokens,\n            total_tokens=usage.total_tokens,\n            response_time=response_time,\n            cost=0.0,\n        )\n```\n\nConfigure `llm_client` with the provider's API key and OpenAI-compatible base\nURL, and pass its model name to `RAGWithMetrics`. The `0.0` cost is a deliberate\nplaceholder: the course's `calculate_cost` handles only its OpenAI model. Add\nthe selected provider's current input and output prices before using cost in a\ndashboard or database.\n\nIf the provider rejects the first request because it is already larger than\nyour token limit, retries and delays will not help. Reduce the retrieved\ncontext—for example, change the default `num_results` in `RAGBase.search()` from\n`5` to `2`—so each request fits within the limit."
  },
  {
    "id": "e8df9f0d12",
    "course": "llm-zoomcamp",
    "section": "Module 6: Best Practices",
    "question": "Docker: When trying to run a streamlit app using docker-compose, I get: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: \"streamlit\": executable file not found in $PATH: unknown. The app runs fine outside of docker-compose",
    "answer": "To resolve this issue:\n\n1. Ensure you have created a `Dockerfile`.\n2. Add `streamlit` to the `docker-compose` configuration.\n3. Run the following command to rebuild and start the application:\n\n   ```bash\n   docker-compose up --build\n   ```"
  },
  {
    "id": "0fab61eca2",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Is it a group project?",
    "answer": "No, the capstone is an individual project.\n\nYou can collaborate or discuss a larger idea with other students, but each submitted project must stand on its own. A shared system can work only if it is clearly decomposed into independent projects, where each person has a separate qualifying component and a separate repository.\n\nIf the work cannot be evaluated independently for each participant, it does not satisfy the project requirement."
  },
  {
    "id": "d5fc98925d",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Do we submit 2 projects, what does attempt 1 and 2 mean?",
    "answer": "You only need to submit one project. If the submission at the first attempt fails, you can improve it and re-submit during the attempt#2 submission window.\n\n- If you want to submit two projects for the experience and exposure, you must use different datasets and problem statements.\n- If you can’t make it to the attempt#1 submission window, you still have time to catch up to meet the attempt#2 submission window.\n\nRemember that the submission does not count towards the certification if you do not participate in the peer-review of three peers in your cohort."
  },
  {
    "id": "e892d44e42",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Does the competition count as the capstone?",
    "answer": "No, it does not. You can participate in the [math-kaggle-llm-competition](https://datatalks-club.slack.com/archives/C0791HB4A58) as a group if you want to form teams; but the capstone is an individual attempt."
  },
  {
    "id": "9a2e2d2008",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "How is my capstone project going to be evaluated?",
    "answer": "Each submitted project will be evaluated by three randomly assigned students who have also submitted the project.\n\nYou will also be responsible for grading the projects from three fellow students yourself. Please be aware that not complying with this rule implies you may fail to achieve the Certificate at the end of the course.\n\nThe final grade you receive will be the median score of the grades from the peer reviewers. The peer review criteria for evaluation must follow the guidelines defined here (TBA for link)."
  },
  {
    "id": "eae0fb50aa",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "When and how will we be assigned projects for review/grading?",
    "answer": "After project submissions close, your peer-review assignments become available\non the course-management platform. Use the platform to see the projects assigned\nto you and the separate peer-review deadline."
  },
  {
    "id": "a8a7fef016",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "I’ve already submitted my project. Why can’t I review any projects?",
    "answer": "Once the project submission deadline has passed, projects will be assigned to you for evaluation. You can't choose which projects to evaluate, and you can’t review before the list has been released."
  },
  {
    "id": "e76a70cde3",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "How can I find some good ideas or datasets for the project?",
    "answer": "Please check [this GitHub page](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/project.md) for several ideas and datasets that could be used for the project, along with tips and guidelines."
  },
  {
    "id": "d4f7c08ea1",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Project: do I need an orchestration tool (Airflow, Mage, Kestra) for the capstone?",
    "answer": "No. A plain Python script that ingests and indexes your data is enough for full points on the \"ingestion pipeline\" criterion. A Jupyter notebook with the same steps is worth 1 point instead of 2.\n\nUse an orchestrator only if it actually fits your project — for example, recurring ingestion of a feed that updates daily. Don't add one just to score the criterion."
  },
  {
    "id": "9b0e2d6a47",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Project: how do I evaluate a recommender-style RAG (no obvious Q&A ground truth)?",
    "answer": "Two complementary approaches that both score for the evaluation criterion:\n\n1. Synthetic ground truth (same idea as the course, adapted). For each item in your dataset, prompt the LLM with the item's description and ask it to generate ~5 user queries that should return that item as the top result. Then run those queries through your retrieval and measure hit rate / MRR / NDCG.\n\n2. LLM-as-a-judge for end-to-end quality. Sample queries, run the full RAG, and have an LLM rate the result for relevance/usefulness on a fixed rubric (e.g. 1–5 scale, with criteria you specify in the prompt).\n\nNDCG is often a better fit than hit-rate for ranking-style problems where multiple items are acceptable answers — it rewards getting good items high in the list, not just first."
  },
  {
    "id": "6f2a8b3d10",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Project: my corpus is large (long PDFs, many paragraphs). What's a good chunking strategy?",
    "answer": "Don't try to find the perfect chunker upfront — iterate.\n\n- Start simple: fixed-size chunking (~1000 tokens with some overlap) and run a small ground-truth eval.\n- Try smart chunking: ask an LLM to split each document into logical sections, then index each section.\n- Add a short LLM-generated summary per chunk and index it alongside, or use it to boost retrieval.\n- For long, structured documents (legal, financial), prefer hybrid search (BM25 + dense) so exact wording isn't lost during semantic matching.\n\nUseful tools for parsing PDFs to clean markdown before chunking:\n\n- [`pymupdf4llm`](https://pypi.org/project/pymupdf4llm/) — fast, decent quality.\n- [Docling](https://github.com/docling-project/docling) — slower but higher quality on tables/figures.\n- [GROBID](https://github.com/kermitt2/grobid) — for academic papers, extracts structure (sections, refs, etc.).\n\nRun the eval again after each change. The goal is measurable improvement on hit rate / MRR for your ground-truth set, not a \"perfect\" chunker in the abstract."
  },
  {
    "id": "e5d8a2c761",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Project: what does \"reproducibility\" mean — do reviewers need access to my API keys?",
    "answer": "Never share API keys or hosted-service credentials in your repo. Reproducibility means a peer reviewer can clone the repo and follow your README to recreate the system from scratch — using their own credentials.\n\nConcretely:\n\n- Provide a script (or notebook) that ingests the dataset and (re)builds the search index locally.\n- Ship a `.env.example` with the variable names but no values; have the reviewer create their own `.env` with their own keys. Keep `.env` in `.gitignore`.\n- Use a cheap model (`gpt-4o-mini`, Groq, etc.) so reviewers don't burn through credits when running your project.\n- Pin dependency versions (`requirements.txt` / `pyproject.toml` lock file) and document the Python version (and Docker version, if used)."
  },
  {
    "id": "ea815eb858",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Can I use a programming language other than Python (for example JavaScript, TypeScript, Go, Rust, Java, Scala, C#, or R) for homework or the project?",
    "answer": "In most cases, use Python. The course materials, examples, and reviewer expectations are Python-based, so Python is the easiest path. This applies to every homework and to the final/capstone project.\n\nUsing another programming language or stack - for example JavaScript, TypeScript, Go, Rust, Java, Scala, C#, or R - is technically possible, but do it only if you have a strong reason. We do not want to restrict your choice of technology, but a non-Python stack makes reproducibility and review harder.\n\nIf you use a language other than Python, your documentation must be very thorough. Assume the reviewer has no knowledge of that language or ecosystem. Your README should explain how to install dependencies and run the homework or project on Windows, macOS, and Linux.\n\nFor example, a Go project should include steps at the level of:\n\n```bash\ngo mod tidy\ngo run .\n```\n\nThe submission must still be easy to reproduce and evaluate."
  },
  {
    "id": "930286278d",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Where can I find previous LLM Zoomcamp projects?",
    "answer": "You can browse previous LLM Zoomcamp project submissions here:\n\n- [2024 projects](https://courses.datatalks.club/llm-zoomcamp-2024/projects)\n- [2025 projects](https://courses.datatalks.club/llm-zoomcamp-2025/projects)\n\nThese pages show submitted repositories and can help you understand the expected scope and quality of capstone projects."
  },
  {
    "id": "897a350476",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Do I need to announce or reserve my project idea?",
    "answer": "No. You do not need to announce or reserve your project idea before starting.\n\nIf you want feedback, you can ask in Slack. You can also look at previous projects to understand scope:\n\n- [2024 projects](https://courses.datatalks.club/llm-zoomcamp-2024/projects)\n- [2025 projects](https://courses.datatalks.club/llm-zoomcamp-2025/projects)\n\nSimilar topics are okay as long as your implementation is your own and your submission meets the project requirements."
  },
  {
    "id": "7318a90b14",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Can I use WhatsApp, Telegram, or some other interface for my project instead of a web app?",
    "answer": "Yes. You can use any interface for your project — a web app, a CLI, a Telegram or WhatsApp bot, a Slack app, a notebook, or anything else. There are no strict guidelines on how you make the project available to the end user. Pick whatever fits your idea, as long as the project meets the evaluation criteria.\n\nNote that the course doesn't teach how to build these interfaces, so you'll need to figure that part out on your own. With an AI assistant it's usually quite simple."
  },
  {
    "id": "d8b28c47a0",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "What can I use to extract text from PDFs (or scanned/OCR documents) for my RAG project?",
    "answer": "The course doesn't prescribe a specific tool — use whatever works best for your data. Some options people in the community reach for:\n\n- **[docling](https://github.com/docling-project/docling)** — converts PDFs (and other docs) to structured Markdown/JSON, good for RAG.\n- **[pymupdf4llm](https://pymupdf.readthedocs.io/en/latest/pymupdf4llm/)** / **PyMuPDF** — fast text extraction, Markdown output.\n- **[unstructured](https://github.com/Unstructured-IO/unstructured)** — handles many document types and layouts.\n- **[pytesseract](https://github.com/madmaze/pytesseract)** (Tesseract OCR) — for scanned/image-based PDFs that have no text layer.\n- **Hosted OCR** such as Mistral OCR — if you'd rather call an API than run OCR locally.\n\nTry a couple on a sample of your documents and see which gives the cleanest text. For how to chunk/prepare the extracted text afterwards, see \"[How should I prepare documents for RAG?](../module-1-rag/023_649c280e6d_how-should-i-prepare-documents-for-rag.md)\"."
  },
  {
    "id": "f0439b5faf",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Is there a minimum dataset size for the capstone project?",
    "answer": "No, you don't need a minimum dataset size for the capstone project. Choose\nenough data to demonstrate and meaningfully evaluate your retrieval or agent\nflow. Reviewers assess the project using the\n[published evaluation criteria](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/project.md#evaluation-criteria),\nnot by counting documents or rows."
  },
  {
    "id": "2cf500aae6",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Does my capstone project data need to come from a live API?",
    "answer": "No, you can use a static file, a self-created dataset, or an API-backed source.\nFor full reproducibility points, make the data accessible and provide a script\nthat lets reviewers rebuild the knowledge base. See the\n[project evaluation criteria](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/project.md#evaluation-criteria)\nfor the ingestion and reproducibility requirements."
  },
  {
    "id": "5ffe92bb02",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Can I use a non-English dataset for the capstone project?",
    "answer": "Yes, your dataset and application responses can use a language other than\nEnglish. Write the README and setup documentation in English so reviewers can\nunderstand, run, and evaluate the project."
  },
  {
    "id": "6baab508b1",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Does the capstone project need a document-based dataset?",
    "answer": "No, you can use structured data such as database schemas, table descriptions,\nor records. You still need to retrieve or query the data as part of an\nend-to-end RAG or agent flow. Evaluate that flow against the\n[project criteria](https://github.com/DataTalksClub/llm-zoomcamp/blob/main/project.md#evaluation-criteria)."
  },
  {
    "id": "ec22c5eb86",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Can I use Evidently for the capstone project?",
    "answer": "Yes, you can use Evidently for the capstone project."
  },
  {
    "id": "4179bc68b6",
    "course": "llm-zoomcamp",
    "section": "Capstone Project",
    "question": "Does the capstone project need a completely original idea?",
    "answer": "No. You may implement an existing idea yourself. Do not reuse a dataset from\nthe course lectures or homework for your project."
  },
  {
    "id": "d850a0e7ea",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "Can I use the workshop materials for my own projects or share them with others?",
    "answer": "Since dlt is open-source, you can use the content of this workshop for a capstone project. As the main goal of dlt is to load and store data easily, you can even use it for other Zoomcamps (like the MLOps Zoomcamp project). Feel free to ask questions or use it directly in your projects."
  },
  {
    "id": "6bceeebf04",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "How to set up a new dlt project when loading from cloud?",
    "answer": "Start with the following command on the command line:\n\n```bash\n dlt init filesystem duckdb\n```\n\nMore directions can be found at [dlthub.com](https://dlthub.com/docs/tutorial/filesystem)"
  },
  {
    "id": "0407213397",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "There is an error when opening the table using `dbtable = db.open_table(\"notion_pages___homework\")`: `FileNotFoundError: Table notion_pages___homework does not exist. Please first call db.create_table(notion_pages___homework, data)`",
    "answer": "The error indicates that you have not changed all instances of \"employee_handbook\" to \"homework\" in your pipeline settings."
  },
  {
    "id": "e95bfafc0e",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "There is an error when running main(): FileNotFoundError: Table notion_pages___homework does not exist. Please first call db.create_table(notion_pages___homework, data)",
    "answer": "Make sure you open the correct table in line 3:\n\n```python\ndbtable = db.open_table(\"notion_pages___homework\")\n```"
  },
  {
    "id": "e394e6f738",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "How do I know which tables are in the db?",
    "answer": "You can use the `db.table_names()` method to list all the tables in the database."
  },
  {
    "id": "cd19f122f3",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "Does DLT have connectors to ClickHouse or StarRocks?",
    "answer": "Currently, DLT does not have connectors for ClickHouse or StarRocks but is open to contributions from the community to add these connectors."
  },
  {
    "id": "0dfba07ace",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "Notebook does not have secret access or 401 Client Error: Unauthorized for url: [api.notion.com](https://api.notion.com/v1/search)",
    "answer": "If you encounter this error, it typically indicates an authorization issue with the Notion API. Here’s how you can resolve it:\n\n1. **Check API Key**: Ensure that you are using the correct API key with appropriate permissions.\n2. **Verify API Endpoint**: Confirm that you are hitting the correct Notion API endpoint.\n3. **Token Expiry**: Check if your token has expired and regenerate it if necessary.\n4. **Configurations**: Double-check all access configurations in your application.\n\nIf the error persists, review the API documentation and make sure all necessary authentication steps are correctly implemented."
  },
  {
    "id": "4b30b918bc",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "Error: How to fix requests library only installs v2.28 instead of v2.32 required for lancedb?",
    "answer": "If you encounter a 401 Client Error, it may indicate the need to grant access to the key or that the key is incorrect.\n\nTo install the correct version directly from the source, use the following command:\n\n```bash\npip install \"requests @ https://github.com/psf/requests/archive/refs/tags/v2.32.3.zip\"\n```"
  },
  {
    "id": "46efd1088d",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "Why does my EU-region Logfire token return 401 Unauthorized?",
    "answer": "Current Logfire SDK versions normally infer the region from the write token, so\nan EU token should work with `logfire.configure()` without a manually configured\nbase URL. A `401 Unauthorized` usually means the process loaded the wrong token\nor did not load the intended `.env` value.\n\nCheck the configuration in this order:\n\n1. Call `load_dotenv()` before `logfire.configure()`.\n2. Make sure `LOGFIRE_TOKEN` is a **write token** from the intended project;\n   `LOGFIRE_READ_TOKEN` is only for reading traces.\n3. Check for an older `LOGFIRE_TOKEN` already exported by the shell. By default,\n   `load_dotenv()` does not replace an existing environment variable. Use\n   `load_dotenv(override=True)` when the local `.env` should take precedence.\n4. Upgrade the SDK with `uv add --upgrade logfire` so token-based region\n   detection is current.\n\nIf an older client still sends an EU token to the wrong region, explicitly\nsetting `LOGFIRE_BASE_URL=https://logfire-eu.pydantic.dev` can be used as a\nfallback. The token and endpoint must belong to the same region, but this manual\nsetting should not be necessary with the current SDK.\n\nSee Logfire's documentation for\n[SDK configuration](https://logfire.pydantic.dev/docs/reference/configuration/)\nand [data-region URLs](https://logfire.pydantic.dev/docs/reference/data-regions/)."
  },
  {
    "id": "671f72f4c4",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "Can I load multiple JSONL log directories into the same dlt pipeline?",
    "answer": "Yes. Build one filesystem resource for each directory and pass the resources\ntogether to the same pipeline, as the workshop's reference\n`filesystem_pipeline.py` does for Claude and Codex logs.\n\nFor JSONL files with the same structure:\n\n```python\nimport dlt\nfrom dlt.sources.filesystem import filesystem, read_jsonl\n\npipeline = dlt.pipeline(\n    pipeline_name=\"agent_logs\",\n    destination=\"duckdb\",\n    dataset_name=\"agent_logs\",\n)\n\nresources = []\nfor name, directory in {\n    \"claude\": \"/home/me/.claude/projects\",\n    \"codex\": \"/home/me/.codex/sessions\",\n}.items():\n    resource = (\n        filesystem(\n            bucket_url=f\"file://{directory}\",\n            file_glob=\"**/*.jsonl\",\n        )\n        | read_jsonl()\n    ).with_name(name)\n    resources.append(resource)\n\npipeline.run(\n    resources,\n    table_name=\"log_records\",\n    write_disposition=\"append\",\n)\n```\n\nReplace the example paths with your directories. `table_name` sends the\nresources to the same table, and dlt can add columns as the schema evolves.\nFor heterogeneous agent logs, reuse the workshop's `raw_reader()` transformer\nso each line is preserved consistently before loading."
  },
  {
    "id": "c306f20bf3",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "dlt.attach() in the deployment lesson uses the wrong dataset name—how do I fix it for the REST API pipeline dashboard?",
    "answer": "The lesson 06 dashboard deploy snippet may show `dlt.attach(\"agent_traces\", destination=\"playground\", dataset_name=\"agent_logs\")`, but that `dataset_name` must match what your deployed REST API pipeline actually writes.\n\nIn the reference REST API pipeline (lesson 4, e.g. `code/rest_api_pipeline.py`), the pipeline is created with `dataset_name=\"traces\"` (not `agent_logs`). The snippet likely got copied from the lesson 2 filesystem pipeline, where `agent_logs` is the dataset name.\n\nFix:\n\n- Open your `rest_api_pipeline.py` and check the `dlt.pipeline(..., dataset_name=\"...\")` call.\n- Use that exact dataset name in your dashboard attach, e.g. `dlt.attach(\"agent_traces\", destination=\"playground\", dataset_name=\"traces\")`.\n\nThis avoids ambiguous DuckDB catalog/schema resolution and ensures the dashboard connects to the data your pipeline produced."
  },
  {
    "id": "dd53e420ef",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "What needs to change in `agent.py` to use Gemini instead of OpenAI?",
    "answer": "In the dlt workshop's `homework/agent.py`, change the PydanticAI model string\nfrom the `openai:` provider to the `google:` provider:\n\nFor example:\n\n```python\nfaq_agent = Agent(\n    \"google:gemini-3.1-flash-lite\",\n    deps_type=SearchDeps,\n    instructions=INSTRUCTIONS,\n)\n```\n\nSet the corresponding key in `.env`:\n\n```dotenv\nGOOGLE_API_KEY=your-gemini-api-key\n```\n\nThe `SearchDeps`, instructions, and `@faq_agent.tool` code do not need to\nchange."
  },
  {
    "id": "8ede00dc4c",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "Why does the dlt workshop pipeline load fewer Logfire spans or tokens than expected?",
    "answer": "Logfire may not have exported and indexed the complete trace when you run the\ndlt pipeline immediately after the agent. The Query API can then return only\npart of the trace. As a result, dlt loads fewer rows or nested tables and your\ntoken total is too low.\n\nFlush pending telemetry after the agent finishes:\n\n```python\nimport logfire\n\nif not logfire.force_flush(timeout_millis=10_000):\n    raise RuntimeError(\"Logfire did not finish exporting the trace\")\n```\n\nThen run the ingestion pipeline. If the Query API still returns only part of\nthe trace, wait a few seconds and retry the query.\n\nDon't hard-code an expected span count because the model can make a different\nnumber of search calls on each run. Instead, query all records with the same\n`trace_id`. For Question 3, compare the sum of\n`gen_ai.usage.input_tokens` on the model-call spans with\n`gen_ai.aggregated_usage.input_tokens` on the top-level agent-run span. They\nshould agree once the complete trace is available."
  },
  {
    "id": "8ca99429d0",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "How do I fix DuckDB's \"Ambiguous reference to catalog or schema\" error in a dlt pipeline?",
    "answer": "When you use `destination=\"duckdb\"`, dlt normally creates a database file named\nafter `pipeline_name` and a database schema named after `dataset_name`. If both\nnames are identical, DuckDB can't tell whether the unqualified name refers to\nthe catalog or the schema.\n\nGive the pipeline and dataset different names:\n\n```python\npipeline = dlt.pipeline(\n    pipeline_name=\"agent_traces_pipeline\",\n    destination=\"duckdb\",\n    dataset_name=\"agent_traces\",\n)\n```\n\nYou can also provide an explicit DuckDB file whose basename differs from the\ndataset:\n\n```python\npipeline = dlt.pipeline(\n    pipeline_name=\"agent_traces\",\n    destination=dlt.destinations.duckdb(\"workshop.duckdb\"),\n    dataset_name=\"agent_traces\",\n)\n```\n\nIf you already loaded data into another DuckDB file, point the destination at\nthat existing file rather than accidentally creating a new empty database."
  },
  {
    "id": "e00ceb24be",
    "course": "llm-zoomcamp",
    "section": "Workshop: Open-Source Data Ingestion (dlt)",
    "question": "Where are the current dlt workshop materials and video?",
    "answer": "The current workshop materials are in the\n[`cohorts/2026/workshops/dlt` directory](https://github.com/DataTalksClub/llm-zoomcamp/tree/main/cohorts/2026/workshops/dlt).\nOpen that directory for the workshop instructions and video link."
  }
]