Hopp til hovedinnhold

LLM-er har blitt et hverdagsverktøy for å løse et hav av små og store utfordringer for veldig mange. Vi bruker dem til å oppsummere dokumenter, skrive kode, og rett og slett bare holde styr på hverdagen. Etter hvert som vi har blitt mer komfortable med teknologien, har vi også gitt dem friheten til å handle, ta initiativ og løse oppgaver helt på egenhånd.

Har du derimot opplevd at en agent plutselig bestemmer seg for å gjøre litt mer enn du ba om? Som når du ber om en liten bugfix og den i stedet refaktorerer hele prosjektet. Slike overraskelser er ufarlige i en samtale, men langt mindre morsomt når det skjer i systemer som faktisk betyr noe.

Vi ønsker å automatisere komplekse og tidkrevende prosesser med autonome modeller, men i praksis trenger vi samtidig systemer som er pålitelige, repeterbare og kontrollerbare. Det er her LangGraph kommer inn i bildet.

Illustrasjonsbilde for LangGraph

Hva er LangGraph?

LangGraph er et open-source rammeverk som lar deg bygge AI-agenter som deterministiske workflows. Det bygger videre på LangChain, men legger oppå et lag av eksplisitt kontrollflyt og state management.

Tradisjonelt sett tenker vi på en AI-agent som en modell som kan blant annet bestemme hvilke steg den skal ta, hvilke verktøy den vil bruke, og hvordan den vil treffe beslutninger. I motsetning til mange andre agent-rammeverk lar ikke LangGraph modellen improvisere kontrollflyten. LLM-en får gjøre det den er trent til å være god på, men du bestemmer når.

Dette gjør agenten mer forutsigbar og, viktigst av alt, kontrollerbar. Vi går dermed vekk fra en tilnærming i stil med:

Dette er målet mitt, fiks det, lykke til!

...og over til noe som ligner mer på:

Gjør dette i steg A, så dette i steg B og så videre..

Slik får du AI-agenter som fortsatt er fleksible, men som ikke tar beslutninger du ikke har godkjent eller planlagt for.

Nå lurer du kanskje på hvordan LangGraph faktisk bygger AI-agenter som er kontrollerbare?

Fra magisk agent til eksplisitt graf

I LangGraph representerer du hele den tidligere “magiske” agenten som en eksplisitt graf. Her beskriver du hele prosessen i en kontrollerbar struktur. Grafen består av tre sentrale byggesteiner: state, noder og edges.

State

En state definerer nøyaktig hva agenten vet til enhver tid og den:

  • oppdateres etter hver node,
  • kan inspiseres til enhver tid,
  • kan versjoneres,
  • og gjør debugging langt enklere.

Dette betyr at du alltid kan se nøyaktig hva agenten hadde av kunnskap da en beslutning ble tatt. Det gjør det også enkelt å pause workflowen, be brukeren ta en beslutning, og fortsette uten tap av kontekst.

Noder

En node er rett og slett en funksjon som gjør noe. Det kan være:

  • et LLM-kall
  • et API-kall
  • en validering
  • en human-in-the-loop interaksjon

Edges

Edges definerer kontrollflyten mellom steg, altså hvordan agenten beveger seg gjennom grafen. Her defineres for eksempel:

  • Hvilken node følger etter hvilken?
  • Når skal vi ta en betinget vei videre?
  • Når skal vi loope tilbake og prøve igjen?
  • Når skal prosessen avsluttes?

Når du ser disse byggesteinene samlet, blir også forskjellen til mange tradisjonelle agent-rammeverk tydelig. I LangChain bygges flyten ofte som en kjede eller en agent-loop der kontrollflyten i stor grad er implisitt, for eksempel styrt av prompts og modellens egne vurderinger. Det betyr at veivalg, tilbakehopp og stoppkriterier ofte er vanskelig å lese ut av selve strukturen.

Flyt for LangChain-struktur
Eksempel på flyt i LangChain

I LangGraph er denne flyten derimot eksplisitt modellert som en graf. Alle steg, forgreininger og løkker er definert på forhånd som noder og edges, og state fungerer som en felles sannhet gjennom hele prosessen. Det gjør agentens oppførsel lettere å forstå og langt mer kontrollerbar når kompleksiteten øker.

Flyt for LangGraph-struktur
Eksempel på grafstruktur i LangGraph

Et enkelt, lite eksempel: en oppsummeringsagent 🤖

Nå som vi har fått forståelsen av disse begrepene på plass, kan vi bygge noe mer konkret: en liten oppsummeringsagent. For å kunne lage denne agenten, må vi initialisere en LLM. Her står du helt fritt til å velge selv, du kan for eksempel bruke en Anthropic Claude modell med din egen API-nøkkel. Husk å sette API-nøkkelen ANTHROPIC_API_KEY som miljøvariabel.

from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(
		model="claude-sonnet-4-5-20250929", 
		temperature=0.7
)

evaluator_llm = ChatAnthropic(
		model="claude-sonnet-4-5-20250929", 
		temperature=0.2
)

For enkelhetensskyld kan du også initialisere en LLM med Ollama - denne kjører lokalt og trenger ingen API-nøkkel! Du trenger kun å ha Ollama installert og modellen du velger lastet ned.

from langchain_ollama import ChatOllama

llm = ChatOllama(
    model="llama3.2",
    temperature=0.7
)

evaluator_llm = ChatOllama(
    model="llama3.2",
    temperature=0.2
)

Merk at evaluator-modellen bruker lavere temperatur for å gi mer konsistente vurderinger. Her ønsker vi stabilitet, ikke kreativitet.

Vi starter med å definere en tilstand som skal brukes gjennom hele prosessen. State-objektet vårt kommer etter hvert til å inneholde informasjonen agenten trenger.

from typing import TypedDict, Optional

class State(TypedDict):
    input_text: str                # Original tekst
    summary: str                   # Generert sammendrag
    score: float                   # Kvalitetsscore (0.0-1.0)
    approved: Optional[bool]       # Beslutning
    attempt: int                   # Antall forsøk

Videre definerer vi en node som lar en LLM generere et sammendrag. Denne funksjonen vil også oppdatere staten med et nytt felt: summary.

def generate_summary(state: State) -> State:
    """Genererer sammendrag med LLM."""
    text = state["input_text"]
    
    prompt = f"""
    Du er en ekspert på å lage konsise og informative møtesammendrag.
    Oppsummer følgende møtereferat. Sammendraget skal:
		- Inkludere de viktigste diskusjonspunktene
		- Trekke frem konkrete beslutninger og aksjonspunkter
		- Være profesjonelt og klart formulert
		
		Møtereferat:
		{text}
    """
    
    summary = llm.invoke(prompt).content
    
    return {
        **state,
        "summary": summary,
        "attempt": state.get("attempt", 0) + 1
    }

Neste steg er å sjekke om sammendraget faktisk er godt nok. Vi implementerer en evaluering som returnerer en score mellom 0 og 1:

def evaluate_quality(state: State) -> State:
    """Evaluerer kvalitet på sammendraget."""
    summary = state["summary"]
    
    prompt = f"""
    Du er en ekspert på å evaluere kvaliteten på møtesammendrag.
    Vurder kvaliteten på dette sammendraget på en skala fra 0.0 til 1.0. 
		Svar BARE med et desimaltall mellom 0.0 og 1.0, ingenting annet.
		
		Sammendrag:
		{summary}
    """
    
    score = evaluator_llm.invoke(prompt).content
    
    return {**state, "score": float(score)}

Dette er et bra sted å sette inn human-in-the-loop. Brukeren bestemmer om sammendraget kan sendes videre. Eksplisitt state gjør dette mulig uten ekstra kontekst-håndtering. I dette eksempelet pauser grafen før human_review, og selve godkjenningen håndteres med en input() utenfor grafen for å holde koden enkel.

def human_review(state: State) -> State:
    """Placeholder for human review."""
    return state

Når vi først har et review-steg, må vi også beskrive hvordan agenten beveger seg videre. Skal den prøve å generere et nytt sammendrag, eller er resultatet godt nok til å gå videre? Vi implementerer funksjoner som bestemmer neste steg i grafen basert på innholdet i state.

QUALITY_THRESHOLD = 0.8
MAX_ATTEMPTS = 3

def quality_branch(state: State) -> str:
    """Beslutter neste steg basert på kvalitet."""
    score = state["score"]
    attempt = state.get("attempt", 0)

    if attempt >= MAX_ATTEMPTS:
        return "human_review"

    if score < QUALITY_THRESHOLD:
        return "generate_summary"
    
    return "human_review"

def approval_branch(state: State) -> str:
    """Sjekker om sammendraget er godkjent."""
    if state.get("approved") is True:
        return "end"
    return "generate_summary"

Nå som vi har definert noder og en state, må vi koble alt sammen med edges - altså definere flyten. Vi begynner med å legge til de første obligatoriske stegene:

from langgraph.graph import StateGraph, START

# Opprett graf
graph = StateGraph(State)

# Legg til noder (stegene)
graph.add_node("generate_summary", generate_summary)
graph.add_node("evaluate_quality", evaluate_quality)
graph.add_node("human_review", human_review)

# Definer flyt
graph.add_edge(START, "generate_summary")
graph.add_edge("generate_summary", "evaluate_quality")

Deretter legger vi inn betinget edges som bestemmer om vi skal gå tilbake og generere på nytt basert på kvalitetsscore eller gå videre til vurdering fra brukeren.

from langgraph.graph import END

# Betinget edge: fortsett eller loop?
graph.add_conditional_edges(
    "evaluate_quality",
    quality_branch,
    {
        "generate_summary": "generate_summary",  # Loop
        "human_review": "human_review"           # Fortsett
    }
)

# Human review kan også loope eller avslutte
graph.add_conditional_edges(
    "human_review",
    approval_branch,
    {
        "generate_summary": "generate_summary",  # Loop
        "end": END                               # Ferdig
    }
)

Til slutt kompilerer vi grafen til en kjørbar agent. Ved å bruke interrupt_before=["human_review"] pauser vi kjøringen rett før review-steget, slik at brukeren kan inspisere state og eksplisitt godkjenne (eller be om ny generering) før agenten fortsetter.

app = graph.compile(interrupt_before=["human_review"])

For å faktisk ta agenten i bruk, definerer vi en enkel kjørbar logikk rundt grafen.

def run_agent(meeting_text: str) -> dict:
		"""Kjører agenten"""
    
    state = {
        "input_text": meeting_text,
        "summary": "",
        "score": 0.0,
        "approved": None,
        "attempt": 0
    }

    config = {"configurable": {"thread_id": "demo"}}

    while True:
        result = app.invoke(state, config)

        print(f"Forslag til sammendrag: {result['summary']}")
        print(f"Kvalitet: {result['score']}")

        approved = input("Godkjenn? (ja/nei): ").strip().lower() == "ja"

        if approved:
            result["approved"] = True
            return result

        state = {**result, "approved": None}

Dette gir oss en enkel, men komplett agentflyt som kombinerer automatisk evaluering med eksplisitt godkjenning fra bruker. Den kan illustreres som i figuren under. I praksis kan flyten videreutvikles med mer avansert review-logikk, flere kvalitetskriterier eller integrasjon mot UI og eksterne systemer.

Grafstruktur for oppsummeringsagenten
Grafstruktur for oppsummeringsagenten

Punkter som er verdt å notere seg

Når du bygger AI-agenter med eksplisitt kontrollflyt, følger det både klare fordeler og noen praktiske begrensninger. Her er de viktigste å ha i bakhodet:

Fordeler

  • Du får mer kontroll, fordi LLM-en alltid jobber innenfor rammer du selv definerer.
  • Du får forutsigbarhet: samme input gir samme flyt gjennom grafen.
  • Debugging blir enklere, siden du kan se nøyaktig hvilket steg som feilet.
  • Løsningen blir modulær, og du kan bytte ut én node uten å påvirke resten av agenten.
  • Det gir en mer profesjonell tilnærming som passer godt i systemer med krav til logging og sporbarhet.

Begrensninger

  • En eksplisitt graf krever mer eksplisitt tenking; du må investere mer tid i arkitektur og kontrollflyt før du bygger.
  • Store grafer kan bli komplekse å vedlikeholde dersom de ikke struktureres godt.
  • LLM-kall er fortsatt probabilistiske. LangGraph gir struktur, men løser ikke all “magien” i hvordan modeller resonnerer.

Relevante lenker anbefalt av forfatteren

Liker du innlegget?

Del gjerne med kollegaer og venner