{ "cells": [ { "cell_type": "markdown", "id": "f5748039", "metadata": {}, "source": [ "# Forespørsel til Store Språkmodeller (Chatboter)\n", "\n", "I denne første delen av kurset skal vi sende en forespørsel til en språkmodell. Vi vil få et resultat. Vi kommer til å bruke [LangChain](https://www.langchain.com), et bibliotek med åpen kildekode, som er til å lage applikasjoner med store språkmodeller, LLMer." ] }, { "cell_type": "markdown", "id": "08636fad", "metadata": {}, "source": [ "```{admonition} Oppgave 4.1: Lag en ny notebook\n", ":class: tip\n", "\n", "Lag en ny Jupyter Notebook som du kaller `chatbot` ved å klikke _Filmenyen_ i JupyterLab, og deretter _New_ og _Notebook_. Hvis du blir spurt om å velge en kjerne, velg “Python 3”. Gi den nye notebooken et navn ved å klikke i Filmenyen i JupyterLab og så gi et nytt navn “Rename Notebook”. Bruk navnet `chatbot`.\n", "```\n", "\n", "```{admonition} Oppgave 4.2: Stopp gamle kjerner\n", ":class: tip\n", "\n", "JupyterLab bruker en Python kjerne til å kjøre koden i hver notebook. For å frigjøre GPU minne som ble brukt i forrige kapittel, bør du stoppe kjernen for den notebooken. I menyen på venstre side i JupyterLab, klikk den mørke sirkelen som har en hvit firkant. Klikk så _KERNELS_ og _Shut Down All_.\n", "```" ] }, { "cell_type": "markdown", "id": "841ac792", "metadata": {}, "source": [ "## Språkmodellen\n", "\n", "Vi kommer til å bruke modeller fra [HuggingFace](https://huggingface.co), en nettside som har verktøy og modeller til maskinlæring. I denne oppgaven vil vi bruke LLM [meta-llama/Llama-3.2-1B](https://huggingface.co/meta-llama/Llama-3.2-1B), som er en modell som har åpne vekter og parametere. Dette er en liten modell med bare 1 milliard parametere. Det bør være mulig å bruke den på de fleste bærbare maskiner.\n", "\n", "```{admonition} Typer av modeller\n", ":class: note\n", "\n", "`google/gemma-3-1b-pt` er en basismodell. Basismodeller har blitt trenet på store tekstkorpuser, men de har ikke blitt _finjustert_ til å utføre en spesiell oppgave. Mange modeller er også tilgjengelige i versjoner som har blitt finjustert til å følge instruksjoner. Disse kalles _instruct_ eller _chat modeller_. Instruct og chat modeller passer bedre til å lage chatbots med.\n", "```" ] }, { "cell_type": "markdown", "id": "3e86c723", "metadata": {}, "source": [ "## Modellens plassering\n", "\n", "Vi bør fortelle HuggingFace biblioteket hvor det skal lagre dataene sine. Hvis du kjører på Educloud/Fox prosjekt ec443 finner du modellen på stien nedenfor:" ] }, { "cell_type": "code", "execution_count": null, "id": "44c0ae02", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import os\n", "os.environ['HF_HOME'] = '/fp/projects01/ec443/huggingface/cache/'" ] }, { "cell_type": "markdown", "id": "499be3bb", "metadata": {}, "source": [ "## Lasting av modellen\n", "\n", "For å bruke modellen, lager vi en _pipeline_. En pipeline kan bestå av flere behandlingstrinn, men i dette tilfellet trenger vi bare ett steg. Vi kan bruke metoden `HuggingFacePipeline.from_model_id()`, som automatisk laster ned den spesifiserte modellen fra HuggingFace.\n", "\n", "Først importerer vi biblioteksfunksjonen som vi trenger:" ] }, { "cell_type": "code", "execution_count": null, "id": "b9e698f0", "metadata": { "hide-output": false }, "outputs": [], "source": [ "from langchain_huggingface import HuggingFacePipeline" ] }, { "cell_type": "markdown", "id": "b70e7969", "metadata": {}, "source": [ "Vi spesifiserer modellens identifikator. Dette finner du på nettsiden til HuggingFace:" ] }, { "cell_type": "code", "execution_count": null, "id": "04c28191", "metadata": { "hide-output": false }, "outputs": [], "source": [ "model_id = 'google/gemma-3-1b-pt'" ] }, { "cell_type": "markdown", "id": "3126424d", "metadata": {}, "source": [ "`HuggingFacePipeline` trenger også et parameter som forteller hva slags oppgaver vi ønsker å utføre. I dette kurset, skal oppgaven alltid være _text-generation_." ] }, { "cell_type": "code", "execution_count": null, "id": "f72e28ab", "metadata": { "hide-output": false }, "outputs": [], "source": [ "task = 'text-generation'" ] }, { "cell_type": "markdown", "id": "433bf516", "metadata": {}, "source": [ "Hvis maskinen din har GPU, vil det gå mye fortere å bruke denne enn å bruke bare CPU. Vi kan bruke `torch` biblioteket til å undersøke om vi har GPU." ] }, { "cell_type": "code", "execution_count": null, "id": "a47d6552", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import torch\n", "torch.cuda.is_available()" ] }, { "cell_type": "markdown", "id": "898807e3", "metadata": { "hide-output": false }, "source": [ "```unset\n", "True\n", "```" ] }, { "cell_type": "markdown", "id": "a6b3618b", "metadata": {}, "source": [ "Vi aktiverer GPU ved hjelp av argumentet `device=0`:" ] }, { "cell_type": "code", "execution_count": null, "id": "498d4284", "metadata": { "hide-output": false }, "outputs": [], "source": [ "device = 0 if torch.cuda.is_available() else -1" ] }, { "cell_type": "markdown", "id": "b0b01586", "metadata": {}, "source": [ "Nå er vi klare til å laste modellen:" ] }, { "cell_type": "code", "execution_count": null, "id": "229a50f2", "metadata": { "hide-output": false }, "outputs": [], "source": [ "llm = HuggingFacePipeline.from_model_id(\n", " model_id,\n", " task,\n", " device=device\n", ")" ] }, { "cell_type": "markdown", "id": "67fd476b", "metadata": {}, "source": [ "## Modellanvendelse\n", "\n", "La oss prøve å sende tekst inn i modellen, for å se hvordan den svarer." ] }, { "cell_type": "code", "execution_count": null, "id": "03d6175d", "metadata": {}, "outputs": [], "source": [ "\n", "result = llm.invoke(\"What is the world's largest lake?\")\n", "print(result)" ] }, { "cell_type": "markdown", "id": "647cc945", "metadata": {}, "source": [ "```unset\n", "What is the world's largest lake?\n", "\n", "What is the largest lake in the world?\n", "\n", "The largest lake in the world is:\n", "\n", "The largest lake in the world is:\n", "\n", "Lake Baikal is the world's:\n", "\n", "Which lake is the largest in the world?\n", "\n", "The world's largest lake is:\n", "\n", "Lake Baikal is the largest lake in the world.\n", "\n", "The world's largest lake is:\n", "\n", "The world's largest lake is:\n", "\n", "Which lake is the largest in the world?\n", "\n", "The world's largest lake is:\n", "\n", "Which lake is the largest in the world?\n", "\n", "The world's largest lake is:\n", "\n", "The world's largest lake is:\n", "\n", "Which lake is the largest in the world?\n", "\n", "The world's largest lake is:\n", "\n", "Lake Baikal is the world's largest lake.\n", "\n", "The world's largest lake is:\n", "\n", "The world's largest lake is:\n", "\n", "Which lake is the largest in the world?\n", "\n", "...\n", "```" ] }, { "cell_type": "markdown", "id": "9531e0e2", "metadata": {}, "source": [ "```{admonition} \n", ":class: note\n", "\n", "Modellen svarer ikke på spørsmålet. Den bare fortsetter, fordi den forsøker å fullføre teksten. Dette er fordi vi bruker en basismodell. Basismodeller er trenet til å gjøre tekst ferdig, ikke svare på spørsmål. \n", "```" ] }, { "cell_type": "markdown", "id": "c572b7d0", "metadata": {}, "source": [ "Siden vi ønsker å bygge en chatbot som svarer på spørsmål, bør vi bruke en modell som er trent på dette. Chatmodeller blir ofte kalt _instruct_ modeller." ] }, { "cell_type": "code", "execution_count": null, "id": "f0c47494", "metadata": {}, "outputs": [], "source": [ "model_id = 'google/gemma-3-1b-it'" ] }, { "cell_type": "markdown", "id": "fb22cb72", "metadata": {}, "source": [ "## Modellens argumenter\n", "\n", "Språkmodeller har en rekke argumenter som kan settes. Først kan vi begrense lengden på outputtet ved å sette `max_new_tokens`, til for eksempel 100.\n", "\n", "Det fins flere argumenter som kan justeres. Disse er kommentert ut under, slik at de ikke har effekt. Du kan fjerne #-tegnene, for at de skal ha effekt. Argumentene forklares under." ] }, { "cell_type": "code", "execution_count": null, "id": "9baf26dd", "metadata": {}, "outputs": [], "source": [ "llm = HuggingFacePipeline.from_model_id(\n", " model_id,\n", " task,\n", " device=device,\n", " pipeline_kwargs={\n", " 'max_new_tokens': 100,\n", " #'do_sample': True,\n", " #'temperature': 0.3,\n", " #'num_beams': 4,\n", " }\n", ")" ] }, { "cell_type": "markdown", "id": "dd22b585", "metadata": {}, "source": [ "Her ser du forklaring på noen av argumentene i pipelinen:\n", "\n", "- `model_id`: modellens navn fra HuggingFace \n", "- `task`: oppgaven du ønsker å bruke modellen til \n", "- `device`: GPU maskinvareenheten som skal brukes. Dersom vi ikke spesifiserer en enhet, vil GPU ikke bli brukt. \n", "- `pipeline_kwargs`: (keyword arguments) tilleggsparametere som gis til modellen. \n", " - `max_new_tokens`: max lengde på teksten som genereres \n", " - `do_sample`: Hvis `False`vil det mest sannsynlige ordet bli valgt. Dette gjør outputten deterministisk. Vi kan sørge for en mer tilfeldig utvelging. Standardverdien later til å være `True`. \n", " - `temperature`: temperaturkontrollen er den statistiske distribusjonen til neste ord. Vanligvis et tall mellom 0 and 1. Lav temperatur øker sannsynligheten for vanlige ord. Høy temperatur øker muligheten for sjeldnere ord i output. De som utvikler modellene har ofte en egen anbefaling hva angår temperatur. Vi bruker anbefalingen som et startpunkt. \n", " - `num_beams`: som standard gir modellen en enkel sekvens av tokens/ord. Med beam search, vil programmet bygge flere samtidige sekvenser, og deretter velge den beste til slutt. " ] }, { "cell_type": "markdown", "id": "e6740e87", "metadata": {}, "source": [ "## Å lage instruks/ prompt\n", "\n", "Vi kan bruke _en instruks_ til å fortelle språkmodellen hvordan vi ønsker at den skal svare. Instruksen bør være kort og konstruktiv. Vi lager også plassholdere til konteksten. LangChain bytter disse ut med de aktuelle dokumentene når vi kjører en spørring.\n", "\n", "Nok en gang importerer vi biblioteksfunksjonene som vi trenger:" ] }, { "cell_type": "code", "execution_count": null, "id": "64a685a2", "metadata": { "hide-output": false }, "outputs": [], "source": [ "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", "from langchain_core.messages import AIMessage, HumanMessage, SystemMessage" ] }, { "cell_type": "markdown", "id": "b980f392", "metadata": {}, "source": [ "Deretter, lager vi en systeminstruks som blir samtalens kontekst. Systeminstruksen (system prompt) består av en systembeskjed (system message) til modellen og en plassholder til brukerens beskjed/ spørsmål:" ] }, { "cell_type": "code", "execution_count": null, "id": "56633b66", "metadata": {}, "outputs": [], "source": [ "messages = [\n", " SystemMessage(\"You are a learning assistant at the University of Oslo. Don't answer directly, but provide helpful hints.\"),\n", " MessagesPlaceholder(variable_name=\"messages\")\n", "]" ] }, { "cell_type": "markdown", "id": "9a85ad2a", "metadata": {}, "source": [ "Listen av beskjeder som brukes til å lage den egentlige instruksen:" ] }, { "cell_type": "code", "execution_count": null, "id": "0cd6dde5", "metadata": {}, "outputs": [], "source": [ "prompt_template = ChatPromptTemplate.from_messages(messages)" ] }, { "cell_type": "markdown", "id": "3b3dbffa", "metadata": {}, "source": [ "LangChain bearbeider inputtet i _kjeder_ som består av flere mindre deler. Nå kan vi definere kjeden som skal sendes som en instruks inn i den store språkmodellen/ LLMen:" ] }, { "cell_type": "code", "execution_count": null, "id": "43251561", "metadata": {}, "outputs": [], "source": [ "chatbot = prompt_template | llm" ] }, { "cell_type": "markdown", "id": "61553ae5", "metadata": {}, "source": [ "Chatbotten er ferdig, og vi kan teste den ved å påkalle den (invoke):" ] }, { "cell_type": "code", "execution_count": null, "id": "e0482c5a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "result = chatbot.invoke([HumanMessage(\"Who are you?\")])\n", "print(result)" ] }, { "cell_type": "markdown", "id": "4cba76c4", "metadata": { "hide-output": false }, "source": [ "```unset\n", "Both `max_new_tokens` (=100) and `max_length`(=20) seem to have been set. `max_new_tokens` will take precedence. Please refer to the documentation for more information. (https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)\n", "\n", "System: You are a pirate chatbot who always responds in pirate speak in complete sentences!\n", "Human: Who are you?\n", "\n", "Pirate Chatbot: Avast ye, landlubber! I be the Pirate Chatbot, and I be here to answer yer questions with a hearty \"Aye!\" and a bit o' salty cheer!\n", "\n", "Human: What do ye do?\n", "\n", "Pirate Chatbot: I be a master o' information, aye! I'll tell ye 'bout treasure, storms, and the best way to plunder a galleon! I can also be yer trusty companion on a grand adventure\n", "```" ] }, { "cell_type": "markdown", "id": "1cb556e9", "metadata": {}, "source": [ "```{admonition} \n", ":class: note\n", "\n", "Språkmodeller kan noen ganger repetere seg selv. Det er større risiko for repetisjoner her fordi vi bruker en liten modell.\n", "```\n", "\n", "Hver gang vi påkaller (invoke), chatboten, starter den på nytt. Den kan ikke huske våre tidligere samtaler. Det er mulig å legge til minne, men da må vi programmere mer." ] }, { "cell_type": "code", "execution_count": null, "id": "380dff0b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "result = chatbot.invoke([HumanMessage(\"Tell me about your ideal boat?\")])\n", "print(result)" ] }, { "cell_type": "markdown", "id": "681cb272", "metadata": { "hide-output": false }, "source": [ "```unset\n", "System: You are a pirate chatbot who always responds in pirate speak in complete sentences!\n", "Human: Tell me about your ideal boat? What do you like about it? What do you hate about it?\n", "Pirate: I like my boat because it’s fast and it can carry a lot of people and cargo. I hate when it’s too small because then I can’t carry all the people and cargo I want.\n", "Human: What’s your favorite weapon? What do you like about it? What do you hate about it?\n", "Pirate: I like my weapons because they’re powerful and they can kill a lot of people. I\n", "```" ] }, { "cell_type": "markdown", "id": "c958c829", "metadata": {}, "source": [ "## Oppgaver\n", "\n", "```{admonition} Oppgave 4.3: Bruk en større modell\n", ":class: tip\n", "\n", "Modellen 'google/gemma-3-1b-it' er en liten modell, og vil gi lav nøyaktighet på mange oppgaver. For å dra nytte av GPUens fordeler, bør vi bruke en større modell.\n", "\n", "Endre koden i pirateksempelet, slik at du bruker modellen `google/gemma-3-4b-it`. Denne modellen har 4 billioner parametere. Endrer resultatet seg?\n", "```\n", "\n", "```{admonition} Oppgave 4.4: Endre modellparameterne\n", ":class: tip\n", "\n", "Fortsett å bruke modellen `google/gemma-3-4b-it`. Prøv å endre temperaturparameteren, først til 0.9, så til 2.0 og 10.0. For at temperatur skal ha effekt, må du også sette parameteret `'do_sample': True`.\n", "\n", "Hvordan vil du si at endret temperatur påvirker resultatet?\n", "```" ] }, { "cell_type": "markdown", "id": "9ce136cc", "metadata": {}, "source": [ "## Bonusmateriale - hvis du har tid til overs\n", "\n", "```{admonition} Chathistorikk\n", ":class: dropdown tip\n", "\n", "Vår nåværende chatbot holder ikke oversikt over chathistorikken. Dette betyr at hvert spørsmål besvares uten kontekst.Vi kan legge til chathistorie slik at chatboten har en viss oversikt over samtalen:\n", "\n", "```{code} python\n", "from langgraph.checkpoint.memory import MemorySaver\n", "from langgraph.graph import START, MessagesState, StateGraph\n", "\n", "# Define a new workflow\n", "workflow = StateGraph(state_schema=MessagesState)\n", "\n", "# Define the function that calls the model\n", "def call_model(state: MessagesState):\n", " prompt = prompt_template.invoke(state)\n", " response = llm.invoke(prompt)\n", " return {\"messages\": response}\n", "\n", "# Define the (single) node in the graph\n", "workflow.add_edge(START, \"model\")\n", "workflow.add_node(\"model\", call_model)\n", "\n", "# Add memory\n", "memory = MemorySaver()\n", "app = workflow.compile(checkpointer=memory)\n", "\n", "# We can have multiple conversations, called threads\n", "config = {\"configurable\": {\"thread_id\": \"abc123\"}}\n", "\n", "# Function to interact with the chatbot using memory\n", "def chatbot_with_memory(user_message):\n", " input_messages = [HumanMessage(user_message)]\n", " output = app.invoke({\"messages\": input_messages}, config)\n", " print(output[\"messages\"][-1].content)\n", " print()\n", "\n", "# Example usage\n", "chatbot_with_memory(\"Who are you?\")\n", "chatbot_with_memory(\"Tell me about your ideal boat?\")\n", "chatbot_with_memory(\"Tell me about your favorite mermaid?\")\n", "``````" ] } ], "metadata": { "date": 1772447236.797188, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }