In der Finanzanalyse begegnen uns täglich große Mengen an Textdaten - von Geschäftsberichten über Analystenbewertungen bis hin zu Pressemitteilungen. Die traditionelle Textanalyse war bisher entweder sehr zeitaufwendig (manuelles Lesen) oder ungenau (einfache Wörterbuch-basierte Methoden).

Large Language Models (LLMs) wie Google Gemini bieten eine neue Möglichkeit: Sie können Texte kontextuell verstehen und komplexe Bewertungen vornehmen, ohne dass wir sie vorher auf unsere spezifische Aufgabe trainieren müssen. Diese Fähigkeit nennt sich Zero-Shot Learning.

In diesem Tutorial lernen Sie, wie Sie mit dem ellmer Paket und Google Gemini systematische Sentiment-Analysen von SEC-Filings durchführen. Als Beispiel analysieren wir Apple’s Management Discussion & Analysis (Item 7) aus dem 10-K Filing.

Was sind Large Language Models und Zero-Shot Prompting?

Large Language Models verstehen

LLMs sind Modelle, die auf riesigen Textmengen trainiert wurden und dadurch ein tiefes Verständnis von Sprache entwickelt haben. Sie funktionieren grundsätzlich wie eine sehr intelligente Textvervollständigung - sie versuchen, den wahrscheinlichsten nächsten Text zu generieren.

Der entscheidende Unterschied zu früheren NLP-Methoden:

  • Traditionelle Methoden: Benötigen Training auf spezifische Aufgaben
  • Generative LLMs: Können neue Aufgaben durch natürliche Sprache verstehen

Zero-Shot Prompting

Zero-Shot bedeutet, dass das Modell eine Aufgabe löst, ohne vorher Beispiele für diese Aufgabe gesehen zu haben. Stattdessen geben wir ihm klare Anweisungen in natürlicher Sprache.

Beispiel:

Prompt: "Bewerte die Stimmung des folgenden Texts auf einer Skala von -2 bis +2: 
'Unsere Quartalsergebnisse haben die Erwartungen deutlich übertroffen.'"

Antwort: "+2 - Der Text drückt klaren Erfolg aus ('übertroffen', 'deutlich')"

Das Modell “weiß” bereits, was Sentiment-Analyse ist, ohne dass wir es extra trainieren müssen.

Installation und Setup

Benötigte Pakete

# Pakete laden
library(ellmer)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(rvest)
## 
## Attache Paket: 'rvest'
## 
## Das folgende Objekt ist maskiert 'package:readr':
## 
##     guess_encoding
library(stringr)
library(readr)
library(httr)
library(glue)
library(jsonlite)
## 
## Attache Paket: 'jsonlite'
## 
## Das folgende Objekt ist maskiert 'package:purrr':
## 
##     flatten

Google Gemini API einrichten

Um Google Gemini zu nutzen, benötigen Sie einen API-Schlüssel:

  1. Besuchen Sie Google AI Studio
  2. Erstellen Sie einen kostenlosen API-Schlüssel
  3. Speichern Sie ihn sicher in Ihrer .Renviron Datei:
# .Renviron bearbeiten
usethis::edit_r_environ()

# Diese Zeile hinzufügen:
GEMINI_API_KEY="Ihr_API_Schlüssel_hier"

# R neustarten
.rs.restartR()

Wichtig: Speichern Sie API-Schlüssel niemals direkt im Code! Die .Renviron Datei ist der sichere Weg.

Grundlagen der Textverarbeitung

Apple 10-K Filing herunterladen und vorbereiten

Die 10K Filing sind die Jahresberichte der amerikanischen Unternehmen. Diese Unternehmen müssen ab einer bestimmten Größe alle solche Berichte einreichen. Wir können diesen 10K herunterladen und als Beispiel analysieren, möchten der SEC aber im ersten Schritt deutlich machen, wofür wir diese Datei nutzen, daher die Definition des User-Agent.

Sie können grundsätzlich auf alle diese Berichte kostenlos zugreifen, was eine sehr umfangreiche Datenbank darstellt. Für einen Zugriff auf mehrere Berichte empfiehlt es sich allerdings spezielle Pakete zu nutzen die eine direkte Schnittstelle zur EDGAR Datenbank bieten (die Datenbank auf der diese 10Ks zur Verfügung gestellt werden).

# Erweiterte Download-Funktion für moderne und ältere SEC-Filings (wir haben hier ein SEC von Apple von 2013, die moderneren Filings sind etwas komplexer)
safe_download_sec <- function(url, max_retries = 3) {
  for(i in 1:max_retries) {
    tryCatch({
      # User-Agent Header ist wichtig für SEC-Akzeptanz
      response <- GET(url, 
                     add_headers(`User-Agent` = "R/Educational-Research Mozilla/5.0"),
                     timeout(30))
      
      if(status_code(response) == 200) {
        content_text <- content(response, as = "text", encoding = "UTF-8")
        
        # Prüfen ob es sich um einen iXBRL Viewer handelt
        if(str_detect(content_text, "XBRL Viewer|ixviewer")) {
          message("Moderne iXBRL-Datei erkannt - versuche direkte HTML-URL...")
          
          # Direkte HTML-URL aus iXBRL-URL konstruieren
          direct_url <- convert_ixbrl_to_direct(url)
          if(!is.null(direct_url)) {
            response <- GET(direct_url, 
                           add_headers(`User-Agent` = "R/Educational-Research Mozilla/5.0"),
                           timeout(30))
            if(status_code(response) == 200) {
              content_text <- content(response, as = "text", encoding = "UTF-8")
              return(content_text)
            }
          }
        } else {
          return(content_text)
        }
      }
    }, error = function(e) {
      message("Versuch ", i, " fehlgeschlagen: ", e$message)
      if(i < max_retries) Sys.sleep(2)
    })
  }
  return(NULL)
}

# Apple 10K von 2013:
apple_url_new <- "https://www.sec.gov/Archives/edgar/data/320193/000119312513416534/d590790d10k.htm"

# Versuche zuerst die moderne URL
apple_html_content <- safe_download_sec(apple_url_new)

if(!is.null(apple_html_content)) {
  # HTML zu Text konvertieren
  apple_html <- read_html(apple_html_content)
  apple_2013_text <- apple_html %>%
    html_text() %>%
    str_squish()
  
  message("SEC Filing erfolgreich geladen: ", nchar(apple_2013_text), " Zeichen")
} else {
  cat("Download fehlgeschlagen. Verwende Fallback-Option...\n")
  apple_text <- NULL
}
## SEC Filing erfolgreich geladen: 292407 Zeichen

Item 7 identifizieren und extrahieren

Da es Zeit- und Kostenintensiv ist große Datenmengen mit LLMs zu analysieren sollten Sie ihre Analyse nur auf einen kleinen (aber potentiell wichtigen) Teil der 10Ks beschränken - Item 7. In Item 7 ist die “Management’s Discussion and Analysis of Financial Condition and Results of Operations (MD&A)” und enthält die Einschätzung der Unternehmensleitung zur finanziellen Lage, Leistung und Zukunftsaussichten des Unternehmens.

Mit dem Paket stringr und einigen sogenannten regulären Ausdrücken können Sie diese die Informationen aus dem Gesamtbericht, welche zu Item 7 gehören extrahieren. Das stringr-Cheatsheet gibt einen sehr guten Einblick in das Paket und was reguläre Ausrücke sind und wie Sie diese nutzen können:

# Text zum Item 7 aus 10-K extrahieren
extract_item7 <- function(text_content) {
  
  # Find Item 7 start
  start_patterns <- c(
    "This Item 7.*Management.*Discussion.*Analysis",
    "Item 7.*Management.*Discussion.*Analysis.*Financial.*Condition",
    "Overview and Highlights.*Company designs.*manufactures"
  )
  
  item7_start <- NULL
  for (pattern in start_patterns) {
    match_pos <- str_locate(text_content, regex(pattern, ignore_case = TRUE))
    if (!is.na(match_pos[1])) {
      item7_start <- match_pos[1]
      break
    }
  }
  
  if (is.null(item7_start)) {
    stop("Item 7 start not found")
  }
  
  # Find Item 7 end
  end_patterns <- c(
    "Item 7A.*Quantitative.*Qualitative.*Disclosures",
    "Item 8.*Financial Statements"
  )
  
  item7_end <- nchar(text_content)
  for (pattern in end_patterns) {
    match_pos <- str_locate(text_content, regex(pattern, ignore_case = TRUE))
    if (!is.na(match_pos[1]) && match_pos[1] > item7_start) {
      item7_end <- match_pos[1] - 1
      break
    }
  }
  
  # Extract and return Item 7 text
  str_sub(text_content, item7_start, item7_end)
}

item7_content <- extract_item7(apple_2013_text)

Analysen mit ellmer und Google Gemini

System-Prompt definieren

Der System-Prompt definiert die “Persönlichkeit” und das Verhalten unseres LLM-Assistenten. Für Sentiment-Analyse von Finanzberichten sollte dieser spezifisch und präzise sein. Dieser System-Prompt bildet die Basis für die späteren Analysen und wird dann über das “chat” Objekt an die späteren Chats übergeben.

# System-Prompt definieren (nach wissenschaftlichen Standards)
system_prompt <- "Du bist ein erfahrener Finanzanalyst, der auf die Bewertung von Unternehmenskommunikation spezialisiert ist.

Deine Aufgaben:
- Objektive Sentiment-Analyse von Finanzberichten und SEC-Filings
- Erkennung von Management-Optimismus oder -Vorsicht
- Identifikation von Risikosignalen und Wachstumsindikatoren

Deine Arbeitsweise:
- Verwende eine Skala von -2 (sehr negativ) bis +2 (sehr positiv)
- Begründe alle Bewertungen mit konkreten Textpassagen
- Sei präzise und strukturiert in deinen Antworten
- Wenn du unsicher bist, sage das explizit"

# Chat-Objekt erstellen
chat <- chat_google_gemini(
  model = "gemini-2.5-flash",  # Neuestes Modell
  system_prompt = system_prompt,
  params = params(temperature = 0)
)

# Verbindung testen
test_response <- chat$chat("Bist du bereit für die Analyse von Apple's Geschäftsbericht?")
## Absolut! Ich bin bereit.
## 
## Als erfahrener Finanzanalyst, spezialisiert auf die Bewertung von 
## Unternehmenskommunikation, freue ich mich auf diese Aufgabe.
## 
## Bitte stellen Sie mir den relevanten Textabschnitt oder die spezifischen Fragen
## zur Verfügung. Ich werde meine Analyse präzise, objektiv und strukturiert 
## durchführen, stets unter Verwendung der Skala von -2 bis +2 und mit konkreten 
## Begründungen aus dem Text.
## 
## Lassen Sie uns beginnen!

Da Sie nun ihrem LLM gesagt haben auf was es sich grundsätzlich einstellen muss (mit dem System-Prompt) können Sie im nächsten Abschnitt damit fortfahren die Seniment Analyse durchzuführen und dafür einen entsprechenden Prompt schreiben.

Da Sie in diesem Tutorial Textoutput mit numerischem Output vermischen sollen ist es vorteilhaft das JSON Format als Outputformat zu nutzen. In R kann dieses Format beispielsweise mit dem jsonlite Paket verarbeitet werden.

Prompt-Engineering

Basierend auf de Kok (2025) sollten Sie folgende Prinzipien bei ihrem Prompt beachten:

Der Prompt sollte:

  1. Spezifisch und eindeutig sein
  2. Kontextualisierung bereitstellen
  3. Ausgabeformat definieren
  4. Temperatur bestimmen (auf 0 setzen, wenn ihre Ergebnisse reproduzierbar sein sollen, dann sind diese allerdings weniger kreativ)
  5. Chain-of-Thought verwenden

Beispiel: Reproduzierbarer Prompt für Sentiment-Analyse

sentiment_prompt <- function(text_to_analyze) {
  prompt <- glue::glue("
**Aufgabe:** Analysiere die Stimmung des folgenden Finanztext-Abschnitts.

**Kontext:** Dies ist ein Auszug aus Apple's Management Discussion & Analysis (Item 7) eines 10-K Filings.

**Text zu analysieren:**
'{text_to_analyze}'  

**Instruktionen:**
1. Bewerte die Stimmung auf einer Skala von -2 (sehr negativ) bis +2 (sehr positiv)
2. Erkläre deine Bewertung mit spezifischen Textpassagen
3. Identifiziere Schlüsselwörter, die deine Bewertung stützen

**Ausgabeformat (JSON):**
{{
  'sentiment_score': <Zahl zwischen -2 und 2>,
  'explanation': '<Begründung>',
  'key_phrases': ['phrase1', 'phrase2', 'phrase3'],
  'confidence': '<hoch/mittel/niedrig>'
}}

**Antwort:**")
  
  return(prompt)
}

# Prompt anwenden
if(!is.null(item7_content)) {
  sentiment_prompt <- sentiment_prompt(item7_content)
  sentiment_result <- chat$chat(sentiment_prompt)
  cat("Sentiment-Analyse:\n", sentiment_result)
}
## ```json
## {
##   "sentiment_score": 1,
##   "explanation": "Der Textabschnitt vermittelt eine überwiegend positive 
## Stimmung, die jedoch durch deutliche und detaillierte Warnungen bezüglich der 
## Profitabilität und externer Risiken gemildert wird. Das Management hebt 
## signifikantes Umsatzwachstum, starke Produktnachfrage (insbesondere iPhone und 
## iPad), eine exzellente Liquiditätsposition und ein massiv erhöhtes 
## Kapitalrückführungsprogramm hervor. Dies deutet auf eine robuste 
## Geschäftsentwicklung und ein starkes Vertrauen in die finanzielle Stärke des 
## Unternehmens hin. Gleichzeitig werden jedoch wiederholt und explizit negative 
## Trends wie sinkende Durchschnittsverkaufspreise (ASPs), ein erheblicher 
## Rückgang der Bruttomarge und die Erwartung anhaltenden Margendrucks aufgrund 
## von Wettbewerb und Kostensteigerungen genannt. Auch die rückläufigen 
## Nettoerlöse und das Betriebsergebnis in einigen Segmenten (Mac, iPod, Europa, 
## H2 Greater China, Retail) sowie die potenziellen Auswirkungen von 
## Lieferkettenrisiken und Rechtsstreitigkeiten werden transparent kommuniziert. 
## Die Stimmung ist daher positiv, aber mit klaren und gewichtigen Einschränkungen
## bezüglich der Profitabilität und zukünftiger Herausforderungen.",
##   "key_phrases": [
##     "Net sales rose 9% or $14.4 billion",
##     "growth in net sales of iPhone; iTunes, software, and services; and iPad",
##     "strong sales of iPhone 5",
##     "All of the Company’s operating segments experienced increased net sales",
##     "particularly strong in the Americas, Greater China and Japan operating 
## segments",
##     "Overall net sales during 2012 increased $48.3 billion or 45%",
##     "significant increase to its program to return capital to shareholders by 
## raising the total amount it expected to utilize for the program through 
## December 2015 to $100 billion",
##     "increasing its share repurchase authorization to $60 billion and raising 
## its quarterly dividend to $3.05 per common share",
##     "existing balances of cash, cash equivalents and marketable securities will
## be sufficient",
##     "Cash generated by operating activities of $53.7 billion",
##     "declines in net sales of Mac and iPod",
##     "3% decline in iPhone average selling prices (“ASPs”)",
##     "reduction in iPad ASPs of 15%",
##     "Mac net sales and unit sales for 2013 were down or relatively flat",
##     "overall weakness in the market for personal computers",
##     "negatively impacted by unfavorable economic conditions in parts of the 
## region",
##     "second half 2013 net sales falling 4% compared to the second half of 
## 2012",
##     "average revenue per store decreased",
##     "year-over-year decrease in Retail operating income in 2013 is primarily 
## attributable to lower gross margin",
##     "The gross margin percentage in 2013 was 37.6% compared to 43.9% in 2012.",
##     "gross margins and margins on individual products will remain under 
## downward pressure",
##     "Company expects it will continue to take product pricing actions, which 
## would adversely affect gross margins.",
##     "Net income $ 37,037"
##   ],
##   "confidence": "hoch"
## }
## ```
## Sentiment-Analyse:
##  ```json
## {
##   "sentiment_score": 1,
##   "explanation": "Der Textabschnitt vermittelt eine überwiegend positive Stimmung, die jedoch durch deutliche und detaillierte Warnungen bezüglich der Profitabilität und externer Risiken gemildert wird. Das Management hebt signifikantes Umsatzwachstum, starke Produktnachfrage (insbesondere iPhone und iPad), eine exzellente Liquiditätsposition und ein massiv erhöhtes Kapitalrückführungsprogramm hervor. Dies deutet auf eine robuste Geschäftsentwicklung und ein starkes Vertrauen in die finanzielle Stärke des Unternehmens hin. Gleichzeitig werden jedoch wiederholt und explizit negative Trends wie sinkende Durchschnittsverkaufspreise (ASPs), ein erheblicher Rückgang der Bruttomarge und die Erwartung anhaltenden Margendrucks aufgrund von Wettbewerb und Kostensteigerungen genannt. Auch die rückläufigen Nettoerlöse und das Betriebsergebnis in einigen Segmenten (Mac, iPod, Europa, H2 Greater China, Retail) sowie die potenziellen Auswirkungen von Lieferkettenrisiken und Rechtsstreitigkeiten werden transparent kommuniziert. Die Stimmung ist daher positiv, aber mit klaren und gewichtigen Einschränkungen bezüglich der Profitabilität und zukünftiger Herausforderungen.",
##   "key_phrases": [
##     "Net sales rose 9% or $14.4 billion",
##     "growth in net sales of iPhone; iTunes, software, and services; and iPad",
##     "strong sales of iPhone 5",
##     "All of the Company’s operating segments experienced increased net sales",
##     "particularly strong in the Americas, Greater China and Japan operating segments",
##     "Overall net sales during 2012 increased $48.3 billion or 45%",
##     "significant increase to its program to return capital to shareholders by raising the total amount it expected to utilize for the program through December 2015 to $100 billion",
##     "increasing its share repurchase authorization to $60 billion and raising its quarterly dividend to $3.05 per common share",
##     "existing balances of cash, cash equivalents and marketable securities will be sufficient",
##     "Cash generated by operating activities of $53.7 billion",
##     "declines in net sales of Mac and iPod",
##     "3% decline in iPhone average selling prices (“ASPs”)",
##     "reduction in iPad ASPs of 15%",
##     "Mac net sales and unit sales for 2013 were down or relatively flat",
##     "overall weakness in the market for personal computers",
##     "negatively impacted by unfavorable economic conditions in parts of the region",
##     "second half 2013 net sales falling 4% compared to the second half of 2012",
##     "average revenue per store decreased",
##     "year-over-year decrease in Retail operating income in 2013 is primarily attributable to lower gross margin",
##     "The gross margin percentage in 2013 was 37.6% compared to 43.9% in 2012.",
##     "gross margins and margins on individual products will remain under downward pressure",
##     "Company expects it will continue to take product pricing actions, which would adversely affect gross margins.",
##     "Net income $ 37,037"
##   ],
##   "confidence": "hoch"
## }
## ```

Dieser Prompt fordert das LLM dazu auf sehr verbose zu sein, d.h. sich sehr viel zu erklären. Wenn Sie viele Texte zu analysieren haben würden Sie die Output Tokens minimieren wollen. In diesem Fall würden Sie potentiell die Erklärung, warum sich das LLM für seine Einschätzung entschieden hat nicht verlangen.

Nun sollten Sie das ausgegebene JSON noch in R verarbeiten und als Tibble abspeichern:

parse_sentiment_json <- function(json_string) {
  
  # Entferne Markdown-Code-Block-Wrapper falls vorhanden
  clean_json <- json_string %>%
    str_remove("^```json\\s*") %>%    # Entferne ```json am Anfang
    str_remove("```\\s*$") %>%        # Entferne ``` am Ende
    str_remove("^```\\s*") %>%        # Entferne ``` am Anfang (falls kein json)
    str_trim()                        # Entferne Leerzeichen
  
  # JSON parsen
  result <- fromJSON(clean_json)
  
  # Sentiment Score interpretieren
  sentiment_label <- case_when(
    result$sentiment_score > 0 ~ "Positiv",
    result$sentiment_score == 0 ~ "Neutral", 
    result$sentiment_score < 0 ~ "Negativ",
    TRUE ~ "Unbekannt"
  )
  
  # Key Phrases als Tibble strukturieren
  key_phrases_df <- tibble(
    phrase_id = seq_along(result$key_phrases),
    phrase = result$key_phrases,
    # Kategorisiere Phrases
    category = case_when(
      str_detect(phrase, regex("sales|revenue|growth|increase", ignore_case = TRUE)) ~ "Positive Performance",
      str_detect(phrase, regex("margin|decline|pressure|unfavorable", ignore_case = TRUE)) ~ "Challenges",
      str_detect(phrase, regex("cash|dividend|repurchase|capital", ignore_case = TRUE)) ~ "Capital Management", 
      str_detect(phrase, regex("iPhone|iPad|Mac|iPod", ignore_case = TRUE)) ~ "Products",
      str_detect(phrase, regex("store|retail|expansion", ignore_case = TRUE)) ~ "Retail",
      TRUE ~ "Other"
    ),
    # Extrahiere numerische Werte
    contains_number = str_detect(phrase, "\\d"),
    contains_percentage = str_detect(phrase, "%"),
    contains_dollar = str_detect(phrase, "\\$")
  )
  
  list(
    summary = tibble(
      sentiment_score = result$sentiment_score,
      sentiment_label = sentiment_label,
      confidence = result$confidence,
      explanation_length = nchar(result$explanation),
      key_phrases_count = length(result$key_phrases)
    ),
    explanation = result$explanation,
    key_phrases = key_phrases_df
  )
}

# Was wurde hauptsächlich gesagt pro Kategorie
analyze_key_phrases <- function(parsed_result) {
  
  parsed_result$key_phrases %>%
    count(category, sort = TRUE) %>%
    mutate(
      percentage = round(n / sum(n) * 100, 1)
    )
}

# Numerische Werte extrahieren
extract_numerical_insights <- function(parsed_result) {
  
  phrases_with_numbers <- parsed_result$key_phrases %>%
    filter(contains_number) %>%
    mutate(
      # Extrahiere Prozentsätze
      percentage = str_extract(phrase, "\\d+(?:\\.\\d+)?%"),
      # Extrahiere Dollarbeträge 
      dollar_amount = str_extract(phrase, "\\$\\d+(?:\\.\\d+)?\\s*(?:billion|million)?"),
      # Extrahiere andere Zahlen
      other_numbers = str_extract(phrase, "\\d+(?:\\.\\d+)?")
    ) %>%
    select(phrase_id, phrase, category, percentage, dollar_amount, other_numbers)
  
  return(phrases_with_numbers)
}

parsed_result <- parse_sentiment_json(sentiment_result)

# Zusammenfassung anzeigen
print(parsed_result$summary)
## # A tibble: 1 × 5
##   sentiment_score sentiment_label confidence explanation_length
##             <int> <chr>           <chr>                   <int>
## 1               1 Positiv         hoch                     1154
## # ℹ 1 more variable: key_phrases_count <int>
# Für weitere Analysen:
# sentiment_score <- parsed_result$summary$sentiment_score
# explanation <- parsed_result$explanation  
# phrases_df <- parsed_result$key_phrases

# Falls Sie mehrere JSON Ergebnisse haben:
process_multiple_results <- function(json_list) {
  
  map_dfr(seq_along(json_list), ~ {
    result <- parse_sentiment_json(json_list[[.x]])
    result$summary %>%
      mutate(analysis_id = .x)
  })
}

Super. Damit wissen Sie nun, wie Sie Textdateien mittels ellmer und Google Gemini auf deren Sentiment hin analysieren können und wie Sie den Output danach in R in einem Tibble sichern.

Zum Abschluss noch einige Best Practices zur Analyse mit LLMs:

  1. Evaluierung: Immer manuell ein Sample prüfen
  2. Konstruktvalidität: Ergebnisse mit anderen Methoden vergleichen
  3. Transparenz: Prompts und Modell-Parameter dokumentieren
  4. Kosten: Token-Verwendung überwachen
# Token- und Kosten-Tracking
track_usage <- function(chat_object, text_input) {
  # Geschätzte Token (grob: ~4 Zeichen pro Token)
  estimated_tokens <- nchar(text_input) / 4

  cat("Geschätzte Token:", round(estimated_tokens), "\n")
}

# Beispiel
if(!is.null(item7_content)) {
  track_usage(chat, item7_content)
}
## Geschätzte Token: 47425

Limitationen beachten

Leider neigen LLMs dazu zu halluzinieren (zumindest stand Juni 2025). Sie können am Ende noch prüfen, ob die Einschätzung der LLM mit hoher oder niedriger Sicherheit getätigt wurde, aber auch hier ist vorsicht angesagt, da die LLMs sehr selbstsicher Dinge erfinden.

# Qualitäts-Check Funktion
check_llm_response_quality <- function(response) {
  quality_indicators <- list(
    length_reasonable = nchar(response) > 10 && nchar(response) < 10000,
    no_hallucination_markers = !str_detect(response, "(?i)(ich weiß nicht|unsicher|kann nicht bestätigen)"),
    structured_format = str_detect(response, "\\{|\\[|\\:|\\-"),
    no_obvious_errors = !str_detect(response, "(?i)(error|fehler|exception)")
  )
  
  overall_quality <- mean(unlist(quality_indicators))
  
  return(list(
    quality_score = overall_quality,
    indicators = quality_indicators,
    recommendation = ifelse(overall_quality > 0.7, "Akzeptabel", "Überprüfung erforderlich")
  ))
}

# Beispiel für Qualitätsprüfung
if(exists("sentiment_result")) {
  quality_check <- check_llm_response_quality(sentiment_result)
  cat("Qualitäts-Score:", quality_check$quality_score, "\n")
  cat("Empfehlung:", quality_check$recommendation, "\n")
}
## Qualitäts-Score: 1 
## Empfehlung: Akzeptabel

In diesem Sinne frohes Arbeiten mit ellmer!

Weiterführende Ressourcen

LS0tCnRpdGxlOiAiTExNLWJhc2llcnRlIFRleHRhbmFseXNlIG1pdCBlbGxtZXIgdW5kIEdvb2dsZSBHZW1pbmkiCnN1YnRpdGxlOiAiRWluZSBFaW5mw7xocnVuZyBpbiBMTE0tYmFzaWVydGUgVGV4dGFuYWx5c2UgZsO8ciBGaW5hbnpiZXJpY2h0ZSIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IGNvc21vCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCkluIGRlciBGaW5hbnphbmFseXNlIGJlZ2VnbmVuIHVucyB0w6RnbGljaCBncm/Dn2UgTWVuZ2VuIGFuIFRleHRkYXRlbiAtIHZvbiBHZXNjaMOkZnRzYmVyaWNodGVuIMO8YmVyIEFuYWx5c3RlbmJld2VydHVuZ2VuIGJpcyBoaW4genUgUHJlc3NlbWl0dGVpbHVuZ2VuLiBEaWUgdHJhZGl0aW9uZWxsZSBUZXh0YW5hbHlzZSB3YXIgYmlzaGVyIGVudHdlZGVyIHNlaHIgemVpdGF1ZndlbmRpZyAobWFudWVsbGVzIExlc2VuKSBvZGVyIHVuZ2VuYXUgKGVpbmZhY2hlIFfDtnJ0ZXJidWNoLWJhc2llcnRlIE1ldGhvZGVuKS4gCgpMYXJnZSBMYW5ndWFnZSBNb2RlbHMgKExMTXMpIHdpZSBHb29nbGUgR2VtaW5pIGJpZXRlbiBlaW5lIG5ldWUgTcO2Z2xpY2hrZWl0OiBTaWUga8O2bm5lbiBUZXh0ZSBrb250ZXh0dWVsbCB2ZXJzdGVoZW4gdW5kIGtvbXBsZXhlIEJld2VydHVuZ2VuIHZvcm5laG1lbiwgb2huZSBkYXNzIHdpciBzaWUgdm9yaGVyIGF1ZiB1bnNlcmUgc3BlemlmaXNjaGUgQXVmZ2FiZSB0cmFpbmllcmVuIG3DvHNzZW4uIERpZXNlIEbDpGhpZ2tlaXQgbmVubnQgc2ljaCAqKlplcm8tU2hvdCBMZWFybmluZyoqLgoKSW4gZGllc2VtIFR1dG9yaWFsIGxlcm5lbiBTaWUsIHdpZSBTaWUgbWl0IGRlbSBgZWxsbWVyYCBQYWtldCB1bmQgR29vZ2xlIEdlbWluaSBzeXN0ZW1hdGlzY2hlIFNlbnRpbWVudC1BbmFseXNlbiB2b24gU0VDLUZpbGluZ3MgZHVyY2hmw7xocmVuLiBBbHMgQmVpc3BpZWwgYW5hbHlzaWVyZW4gd2lyIEFwcGxlJ3MgTWFuYWdlbWVudCBEaXNjdXNzaW9uICYgQW5hbHlzaXMgKEl0ZW0gNykgYXVzIGRlbSAxMC1LIEZpbGluZy4KCiMjIFdhcyBzaW5kIExhcmdlIExhbmd1YWdlIE1vZGVscyB1bmQgWmVyby1TaG90IFByb21wdGluZz8KCiMjIyBMYXJnZSBMYW5ndWFnZSBNb2RlbHMgdmVyc3RlaGVuCgpMTE1zIHNpbmQgTW9kZWxsZSwgZGllIGF1ZiByaWVzaWdlbiBUZXh0bWVuZ2VuIHRyYWluaWVydCB3dXJkZW4gdW5kIGRhZHVyY2ggZWluIHRpZWZlcyBWZXJzdMOkbmRuaXMgdm9uIFNwcmFjaGUgZW50d2lja2VsdCBoYWJlbi4gU2llIGZ1bmt0aW9uaWVyZW4gZ3J1bmRzw6R0emxpY2ggd2llIGVpbmUgc2VociBpbnRlbGxpZ2VudGUgVGV4dHZlcnZvbGxzdMOkbmRpZ3VuZyAtIHNpZSB2ZXJzdWNoZW4sIGRlbiB3YWhyc2NoZWlubGljaHN0ZW4gbsOkY2hzdGVuIFRleHQgenUgZ2VuZXJpZXJlbi4KCkRlciBlbnRzY2hlaWRlbmRlIFVudGVyc2NoaWVkIHp1IGZyw7xoZXJlbiBOTFAtTWV0aG9kZW46CgotICoqVHJhZGl0aW9uZWxsZSBNZXRob2RlbioqOiBCZW7DtnRpZ2VuIFRyYWluaW5nIGF1ZiBzcGV6aWZpc2NoZSBBdWZnYWJlbgotICoqR2VuZXJhdGl2ZSBMTE1zKio6IEvDtm5uZW4gbmV1ZSBBdWZnYWJlbiBkdXJjaCBuYXTDvHJsaWNoZSBTcHJhY2hlIHZlcnN0ZWhlbgoKIyMjIFplcm8tU2hvdCBQcm9tcHRpbmcKCioqWmVyby1TaG90KiogYmVkZXV0ZXQsIGRhc3MgZGFzIE1vZGVsbCBlaW5lIEF1ZmdhYmUgbMO2c3QsIG9obmUgdm9yaGVyIEJlaXNwaWVsZSBmw7xyIGRpZXNlIEF1ZmdhYmUgZ2VzZWhlbiB6dSBoYWJlbi4gU3RhdHRkZXNzZW4gZ2ViZW4gd2lyIGlobSBrbGFyZSBBbndlaXN1bmdlbiBpbiBuYXTDvHJsaWNoZXIgU3ByYWNoZS4KCkJlaXNwaWVsOgoKYGBgClByb21wdDogIkJld2VydGUgZGllIFN0aW1tdW5nIGRlcyBmb2xnZW5kZW4gVGV4dHMgYXVmIGVpbmVyIFNrYWxhIHZvbiAtMiBiaXMgKzI6IAonVW5zZXJlIFF1YXJ0YWxzZXJnZWJuaXNzZSBoYWJlbiBkaWUgRXJ3YXJ0dW5nZW4gZGV1dGxpY2ggw7xiZXJ0cm9mZmVuLiciCgpBbnR3b3J0OiAiKzIgLSBEZXIgVGV4dCBkcsO8Y2t0IGtsYXJlbiBFcmZvbGcgYXVzICgnw7xiZXJ0cm9mZmVuJywgJ2RldXRsaWNoJykiCmBgYAoKRGFzIE1vZGVsbCAid2Vpw58iIGJlcmVpdHMsIHdhcyBTZW50aW1lbnQtQW5hbHlzZSBpc3QsIG9obmUgZGFzcyB3aXIgZXMgZXh0cmEgdHJhaW5pZXJlbiBtw7xzc2VuLgoKIyMgSW5zdGFsbGF0aW9uIHVuZCBTZXR1cAoKIyMjIEJlbsO2dGlndGUgUGFrZXRlCgpgYGB7cn0KIyBQYWtldGUgbGFkZW4KbGlicmFyeShlbGxtZXIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHJ2ZXN0KQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoaHR0cikKbGlicmFyeShnbHVlKQpsaWJyYXJ5KGpzb25saXRlKQpgYGAKCiMjIyBHb29nbGUgR2VtaW5pIEFQSSBlaW5yaWNodGVuCgpVbSBHb29nbGUgR2VtaW5pIHp1IG51dHplbiwgYmVuw7Z0aWdlbiBTaWUgZWluZW4gQVBJLVNjaGzDvHNzZWw6CgoxLiBCZXN1Y2hlbiBTaWUgW0dvb2dsZSBBSSBTdHVkaW9dKGh0dHBzOi8vYWlzdHVkaW8uZ29vZ2xlLmNvbS9hcHAvYXBpa2V5KQoyLiBFcnN0ZWxsZW4gU2llIGVpbmVuIGtvc3Rlbmxvc2VuIEFQSS1TY2hsw7xzc2VsCjMuIFNwZWljaGVybiBTaWUgaWhuIHNpY2hlciBpbiBJaHJlciBgLlJlbnZpcm9uYCBEYXRlaToKCmBgYHtyLCBldmFsID0gRn0KIyAuUmVudmlyb24gYmVhcmJlaXRlbgp1c2V0aGlzOjplZGl0X3JfZW52aXJvbigpCgojIERpZXNlIFplaWxlIGhpbnp1ZsO8Z2VuOgpHRU1JTklfQVBJX0tFWT0iSWhyX0FQSV9TY2hsw7xzc2VsX2hpZXIiCgojIFIgbmV1c3RhcnRlbgoucnMucmVzdGFydFIoKQpgYGAKCioqV2ljaHRpZyoqOiBTcGVpY2hlcm4gU2llIEFQSS1TY2hsw7xzc2VsIG5pZW1hbHMgZGlyZWt0IGltIENvZGUhIERpZSBgLlJlbnZpcm9uYCBEYXRlaSBpc3QgZGVyIHNpY2hlcmUgV2VnLgoKIyMgR3J1bmRsYWdlbiBkZXIgVGV4dHZlcmFyYmVpdHVuZwoKIyMjIEFwcGxlIDEwLUsgRmlsaW5nIGhlcnVudGVybGFkZW4gdW5kIHZvcmJlcmVpdGVuCgpEaWUgMTBLIEZpbGluZyBzaW5kIGRpZSBKYWhyZXNiZXJpY2h0ZSBkZXIgYW1lcmlrYW5pc2NoZW4gVW50ZXJuZWhtZW4uIERpZXNlIFVudGVybmVobWVuIG3DvHNzZW4gYWIgZWluZXIgYmVzdGltbXRlbiBHcsO2w59lIGFsbGUgc29sY2hlIEJlcmljaHRlIGVpbnJlaWNoZW4uIFdpciBrw7ZubmVuIGRpZXNlbiAxMEsgaGVydW50ZXJsYWRlbiB1bmQgYWxzIEJlaXNwaWVsIGFuYWx5c2llcmVuLCBtw7ZjaHRlbiBkZXIgU0VDIGFiZXIgaW0gZXJzdGVuIFNjaHJpdHQgZGV1dGxpY2ggbWFjaGVuLCB3b2bDvHIgd2lyIGRpZXNlIERhdGVpIG51dHplbiwgZGFoZXIgZGllIERlZmluaXRpb24gZGVzIGBVc2VyLUFnZW50YC4KClNpZSBrw7ZubmVuIGdydW5kc8OkdHpsaWNoIGF1ZiBhbGxlIGRpZXNlIEJlcmljaHRlIGtvc3RlbmxvcyB6dWdyZWlmZW4sIHdhcyBlaW5lIHNlaHIgdW1mYW5ncmVpY2hlIERhdGVuYmFuayBkYXJzdGVsbHQuIEbDvHIgZWluZW4gWnVncmlmZiBhdWYgbWVocmVyZSBCZXJpY2h0ZSBlbXBmaWVobHQgZXMgc2ljaCBhbGxlcmRpbmdzIHNwZXppZWxsZSBQYWtldGUgenUgbnV0emVuIGRpZSBlaW5lIGRpcmVrdGUgU2Nobml0dHN0ZWxsZSB6dXIgRURHQVIgRGF0ZW5iYW5rIGJpZXRlbiAoZGllIERhdGVuYmFuayBhdWYgZGVyIGRpZXNlIDEwS3MgenVyIFZlcmbDvGd1bmcgZ2VzdGVsbHQgd2VyZGVuKS4KCmBgYHtyfQojIEVyd2VpdGVydGUgRG93bmxvYWQtRnVua3Rpb24gZsO8ciBtb2Rlcm5lIHVuZCDDpGx0ZXJlIFNFQy1GaWxpbmdzICh3aXIgaGFiZW4gaGllciBlaW4gU0VDIHZvbiBBcHBsZSB2b24gMjAxMywgZGllIG1vZGVybmVyZW4gRmlsaW5ncyBzaW5kIGV0d2FzIGtvbXBsZXhlcikKc2FmZV9kb3dubG9hZF9zZWMgPC0gZnVuY3Rpb24odXJsLCBtYXhfcmV0cmllcyA9IDMpIHsKICBmb3IoaSBpbiAxOm1heF9yZXRyaWVzKSB7CiAgICB0cnlDYXRjaCh7CiAgICAgICMgVXNlci1BZ2VudCBIZWFkZXIgaXN0IHdpY2h0aWcgZsO8ciBTRUMtQWt6ZXB0YW56CiAgICAgIHJlc3BvbnNlIDwtIEdFVCh1cmwsIAogICAgICAgICAgICAgICAgICAgICBhZGRfaGVhZGVycyhgVXNlci1BZ2VudGAgPSAiUi9FZHVjYXRpb25hbC1SZXNlYXJjaCBNb3ppbGxhLzUuMCIpLAogICAgICAgICAgICAgICAgICAgICB0aW1lb3V0KDMwKSkKICAgICAgCiAgICAgIGlmKHN0YXR1c19jb2RlKHJlc3BvbnNlKSA9PSAyMDApIHsKICAgICAgICBjb250ZW50X3RleHQgPC0gY29udGVudChyZXNwb25zZSwgYXMgPSAidGV4dCIsIGVuY29kaW5nID0gIlVURi04IikKICAgICAgICAKICAgICAgICAjIFByw7xmZW4gb2IgZXMgc2ljaCB1bSBlaW5lbiBpWEJSTCBWaWV3ZXIgaGFuZGVsdAogICAgICAgIGlmKHN0cl9kZXRlY3QoY29udGVudF90ZXh0LCAiWEJSTCBWaWV3ZXJ8aXh2aWV3ZXIiKSkgewogICAgICAgICAgbWVzc2FnZSgiTW9kZXJuZSBpWEJSTC1EYXRlaSBlcmthbm50IC0gdmVyc3VjaGUgZGlyZWt0ZSBIVE1MLVVSTC4uLiIpCiAgICAgICAgICAKICAgICAgICAgICMgRGlyZWt0ZSBIVE1MLVVSTCBhdXMgaVhCUkwtVVJMIGtvbnN0cnVpZXJlbgogICAgICAgICAgZGlyZWN0X3VybCA8LSBjb252ZXJ0X2l4YnJsX3RvX2RpcmVjdCh1cmwpCiAgICAgICAgICBpZighaXMubnVsbChkaXJlY3RfdXJsKSkgewogICAgICAgICAgICByZXNwb25zZSA8LSBHRVQoZGlyZWN0X3VybCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZF9oZWFkZXJzKGBVc2VyLUFnZW50YCA9ICJSL0VkdWNhdGlvbmFsLVJlc2VhcmNoIE1vemlsbGEvNS4wIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVvdXQoMzApKQogICAgICAgICAgICBpZihzdGF0dXNfY29kZShyZXNwb25zZSkgPT0gMjAwKSB7CiAgICAgICAgICAgICAgY29udGVudF90ZXh0IDwtIGNvbnRlbnQocmVzcG9uc2UsIGFzID0gInRleHQiLCBlbmNvZGluZyA9ICJVVEYtOCIpCiAgICAgICAgICAgICAgcmV0dXJuKGNvbnRlbnRfdGV4dCkKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICByZXR1cm4oY29udGVudF90ZXh0KQogICAgICAgIH0KICAgICAgfQogICAgfSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7CiAgICAgIG1lc3NhZ2UoIlZlcnN1Y2ggIiwgaSwgIiBmZWhsZ2VzY2hsYWdlbjogIiwgZSRtZXNzYWdlKQogICAgICBpZihpIDwgbWF4X3JldHJpZXMpIFN5cy5zbGVlcCgyKQogICAgfSkKICB9CiAgcmV0dXJuKE5VTEwpCn0KCiMgQXBwbGUgMTBLIHZvbiAyMDEzOgphcHBsZV91cmxfbmV3IDwtICJodHRwczovL3d3dy5zZWMuZ292L0FyY2hpdmVzL2VkZ2FyL2RhdGEvMzIwMTkzLzAwMDExOTMxMjUxMzQxNjUzNC9kNTkwNzkwZDEway5odG0iCgojIFZlcnN1Y2hlIHp1ZXJzdCBkaWUgbW9kZXJuZSBVUkwKYXBwbGVfaHRtbF9jb250ZW50IDwtIHNhZmVfZG93bmxvYWRfc2VjKGFwcGxlX3VybF9uZXcpCgppZighaXMubnVsbChhcHBsZV9odG1sX2NvbnRlbnQpKSB7CiAgIyBIVE1MIHp1IFRleHQga29udmVydGllcmVuCiAgYXBwbGVfaHRtbCA8LSByZWFkX2h0bWwoYXBwbGVfaHRtbF9jb250ZW50KQogIGFwcGxlXzIwMTNfdGV4dCA8LSBhcHBsZV9odG1sICU+JQogICAgaHRtbF90ZXh0KCkgJT4lCiAgICBzdHJfc3F1aXNoKCkKICAKICBtZXNzYWdlKCJTRUMgRmlsaW5nIGVyZm9sZ3JlaWNoIGdlbGFkZW46ICIsIG5jaGFyKGFwcGxlXzIwMTNfdGV4dCksICIgWmVpY2hlbiIpCn0gZWxzZSB7CiAgY2F0KCJEb3dubG9hZCBmZWhsZ2VzY2hsYWdlbi4gVmVyd2VuZGUgRmFsbGJhY2stT3B0aW9uLi4uXG4iKQogIGFwcGxlX3RleHQgPC0gTlVMTAp9CgpgYGAKCiMjIyBJdGVtIDcgaWRlbnRpZml6aWVyZW4gdW5kIGV4dHJhaGllcmVuCgpEYSBlcyBaZWl0LSB1bmQgS29zdGVuaW50ZW5zaXYgaXN0IGdyb8OfZSBEYXRlbm1lbmdlbiBtaXQgTExNcyB6dSBhbmFseXNpZXJlbiBzb2xsdGVuIFNpZSBpaHJlIEFuYWx5c2UgbnVyIGF1ZiBlaW5lbiBrbGVpbmVuIChhYmVyIHBvdGVudGllbGwgd2ljaHRpZ2VuKSBUZWlsIGRlciAxMEtzIGJlc2NocsOkbmtlbiAtIEl0ZW0gNy4gSW4gSXRlbSA3IGlzdCBkaWUgIk1hbmFnZW1lbnTigJlzIERpc2N1c3Npb24gYW5kIEFuYWx5c2lzIG9mIEZpbmFuY2lhbCBDb25kaXRpb24gYW5kIFJlc3VsdHMgb2YgT3BlcmF0aW9ucyAoTUQmQSkiIHVuZCBlbnRow6RsdCBkaWUgRWluc2Now6R0enVuZyBkZXIgVW50ZXJuZWhtZW5zbGVpdHVuZyB6dXIgZmluYW56aWVsbGVuIExhZ2UsIExlaXN0dW5nIHVuZCBadWt1bmZ0c2F1c3NpY2h0ZW4gZGVzIFVudGVybmVobWVucy4gCgpNaXQgZGVtIFBha2V0IGBzdHJpbmdyYCB1bmQgZWluaWdlbiBzb2dlbmFubnRlbiByZWd1bMOkcmVuIEF1c2Ryw7xja2VuIGvDtm5uZW4gU2llIGRpZXNlIGRpZSBJbmZvcm1hdGlvbmVuIGF1cyBkZW0gR2VzYW10YmVyaWNodCwgd2VsY2hlIHp1IEl0ZW0gNyBnZWjDtnJlbiBleHRyYWhpZXJlbi4gRGFzIFtgc3RyaW5ncmAtQ2hlYXRzaGVldF0oaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9jaGVhdHNoZWV0cy9zdHJpbmdzLnBkZikgZ2lidCBlaW5lbiBzZWhyIGd1dGVuIEVpbmJsaWNrIGluIGRhcyBQYWtldCB1bmQgd2FzIHJlZ3Vsw6RyZSBBdXNyw7xja2Ugc2luZCB1bmQgd2llIFNpZSBkaWVzZSBudXR6ZW4ga8O2bm5lbjoKCmBgYHtyfQojIFRleHQgenVtIEl0ZW0gNyBhdXMgMTAtSyBleHRyYWhpZXJlbgpleHRyYWN0X2l0ZW03IDwtIGZ1bmN0aW9uKHRleHRfY29udGVudCkgewogIAogICMgRmluZCBJdGVtIDcgc3RhcnQKICBzdGFydF9wYXR0ZXJucyA8LSBjKAogICAgIlRoaXMgSXRlbSA3LipNYW5hZ2VtZW50LipEaXNjdXNzaW9uLipBbmFseXNpcyIsCiAgICAiSXRlbSA3LipNYW5hZ2VtZW50LipEaXNjdXNzaW9uLipBbmFseXNpcy4qRmluYW5jaWFsLipDb25kaXRpb24iLAogICAgIk92ZXJ2aWV3IGFuZCBIaWdobGlnaHRzLipDb21wYW55IGRlc2lnbnMuKm1hbnVmYWN0dXJlcyIKICApCiAgCiAgaXRlbTdfc3RhcnQgPC0gTlVMTAogIGZvciAocGF0dGVybiBpbiBzdGFydF9wYXR0ZXJucykgewogICAgbWF0Y2hfcG9zIDwtIHN0cl9sb2NhdGUodGV4dF9jb250ZW50LCByZWdleChwYXR0ZXJuLCBpZ25vcmVfY2FzZSA9IFRSVUUpKQogICAgaWYgKCFpcy5uYShtYXRjaF9wb3NbMV0pKSB7CiAgICAgIGl0ZW03X3N0YXJ0IDwtIG1hdGNoX3Bvc1sxXQogICAgICBicmVhawogICAgfQogIH0KICAKICBpZiAoaXMubnVsbChpdGVtN19zdGFydCkpIHsKICAgIHN0b3AoIkl0ZW0gNyBzdGFydCBub3QgZm91bmQiKQogIH0KICAKICAjIEZpbmQgSXRlbSA3IGVuZAogIGVuZF9wYXR0ZXJucyA8LSBjKAogICAgIkl0ZW0gN0EuKlF1YW50aXRhdGl2ZS4qUXVhbGl0YXRpdmUuKkRpc2Nsb3N1cmVzIiwKICAgICJJdGVtIDguKkZpbmFuY2lhbCBTdGF0ZW1lbnRzIgogICkKICAKICBpdGVtN19lbmQgPC0gbmNoYXIodGV4dF9jb250ZW50KQogIGZvciAocGF0dGVybiBpbiBlbmRfcGF0dGVybnMpIHsKICAgIG1hdGNoX3BvcyA8LSBzdHJfbG9jYXRlKHRleHRfY29udGVudCwgcmVnZXgocGF0dGVybiwgaWdub3JlX2Nhc2UgPSBUUlVFKSkKICAgIGlmICghaXMubmEobWF0Y2hfcG9zWzFdKSAmJiBtYXRjaF9wb3NbMV0gPiBpdGVtN19zdGFydCkgewogICAgICBpdGVtN19lbmQgPC0gbWF0Y2hfcG9zWzFdIC0gMQogICAgICBicmVhawogICAgfQogIH0KICAKICAjIEV4dHJhY3QgYW5kIHJldHVybiBJdGVtIDcgdGV4dAogIHN0cl9zdWIodGV4dF9jb250ZW50LCBpdGVtN19zdGFydCwgaXRlbTdfZW5kKQp9CgppdGVtN19jb250ZW50IDwtIGV4dHJhY3RfaXRlbTcoYXBwbGVfMjAxM190ZXh0KQpgYGAKCiMjIEFuYWx5c2VuIG1pdCBgZWxsbWVyYCB1bmQgR29vZ2xlIEdlbWluaQoKIyMjIFN5c3RlbS1Qcm9tcHQgZGVmaW5pZXJlbgoKRGVyIFN5c3RlbS1Qcm9tcHQgZGVmaW5pZXJ0IGRpZSAiUGVyc8O2bmxpY2hrZWl0IiB1bmQgZGFzIFZlcmhhbHRlbiB1bnNlcmVzIExMTS1Bc3Npc3RlbnRlbi4gRsO8ciBTZW50aW1lbnQtQW5hbHlzZSB2b24gRmluYW56YmVyaWNodGVuIHNvbGx0ZSBkaWVzZXIgc3BlemlmaXNjaCB1bmQgcHLDpHppc2Ugc2Vpbi4gRGllc2VyIFN5c3RlbS1Qcm9tcHQgYmlsZGV0IGRpZSBCYXNpcyBmw7xyIGRpZSBzcMOkdGVyZW4gQW5hbHlzZW4gdW5kIHdpcmQgZGFubiDDvGJlciBkYXMgImNoYXQiIE9iamVrdCBhbiBkaWUgc3DDpHRlcmVuIENoYXRzIMO8YmVyZ2ViZW4uCgpgYGB7cn0KIyBTeXN0ZW0tUHJvbXB0IGRlZmluaWVyZW4gKG5hY2ggd2lzc2Vuc2NoYWZ0bGljaGVuIFN0YW5kYXJkcykKc3lzdGVtX3Byb21wdCA8LSAiRHUgYmlzdCBlaW4gZXJmYWhyZW5lciBGaW5hbnphbmFseXN0LCBkZXIgYXVmIGRpZSBCZXdlcnR1bmcgdm9uIFVudGVybmVobWVuc2tvbW11bmlrYXRpb24gc3BlemlhbGlzaWVydCBpc3QuCgpEZWluZSBBdWZnYWJlbjoKLSBPYmpla3RpdmUgU2VudGltZW50LUFuYWx5c2Ugdm9uIEZpbmFuemJlcmljaHRlbiB1bmQgU0VDLUZpbGluZ3MKLSBFcmtlbm51bmcgdm9uIE1hbmFnZW1lbnQtT3B0aW1pc211cyBvZGVyIC1Wb3JzaWNodAotIElkZW50aWZpa2F0aW9uIHZvbiBSaXNpa29zaWduYWxlbiB1bmQgV2FjaHN0dW1zaW5kaWthdG9yZW4KCkRlaW5lIEFyYmVpdHN3ZWlzZToKLSBWZXJ3ZW5kZSBlaW5lIFNrYWxhIHZvbiAtMiAoc2VociBuZWdhdGl2KSBiaXMgKzIgKHNlaHIgcG9zaXRpdikKLSBCZWdyw7xuZGUgYWxsZSBCZXdlcnR1bmdlbiBtaXQga29ua3JldGVuIFRleHRwYXNzYWdlbgotIFNlaSBwcsOkemlzZSB1bmQgc3RydWt0dXJpZXJ0IGluIGRlaW5lbiBBbnR3b3J0ZW4KLSBXZW5uIGR1IHVuc2ljaGVyIGJpc3QsIHNhZ2UgZGFzIGV4cGxpeml0IgoKIyBDaGF0LU9iamVrdCBlcnN0ZWxsZW4KY2hhdCA8LSBjaGF0X2dvb2dsZV9nZW1pbmkoCiAgbW9kZWwgPSAiZ2VtaW5pLTIuNS1mbGFzaCIsICAjIE5ldWVzdGVzIE1vZGVsbAogIHN5c3RlbV9wcm9tcHQgPSBzeXN0ZW1fcHJvbXB0LAogIHBhcmFtcyA9IHBhcmFtcyh0ZW1wZXJhdHVyZSA9IDApCikKCiMgVmVyYmluZHVuZyB0ZXN0ZW4KdGVzdF9yZXNwb25zZSA8LSBjaGF0JGNoYXQoIkJpc3QgZHUgYmVyZWl0IGbDvHIgZGllIEFuYWx5c2Ugdm9uIEFwcGxlJ3MgR2VzY2jDpGZ0c2JlcmljaHQ/IikKYGBgCgpEYSBTaWUgbnVuIGlocmVtIExMTSBnZXNhZ3QgaGFiZW4gYXVmIHdhcyBlcyBzaWNoIGdydW5kc8OkdHpsaWNoIGVpbnN0ZWxsZW4gbXVzcyAobWl0IGRlbSBTeXN0ZW0tUHJvbXB0KSBrw7ZubmVuIFNpZSBpbSBuw6RjaHN0ZW4gQWJzY2huaXR0IGRhbWl0IGZvcnRmYWhyZW4gZGllIFNlbmltZW50IEFuYWx5c2UgZHVyY2h6dWbDvGhyZW4gdW5kIGRhZsO8ciBlaW5lbiBlbnRzcHJlY2hlbmRlbiBQcm9tcHQgc2NocmVpYmVuLgoKRGEgU2llIGluIGRpZXNlbSBUdXRvcmlhbCBUZXh0b3V0cHV0IG1pdCBudW1lcmlzY2hlbSBPdXRwdXQgdmVybWlzY2hlbiBzb2xsZW4gaXN0IGVzIHZvcnRlaWxoYWZ0IGRhcyBKU09OIEZvcm1hdCBhbHMgT3V0cHV0Zm9ybWF0IHp1IG51dHplbi4gSW4gUiBrYW5uIGRpZXNlcyBGb3JtYXQgYmVpc3BpZWxzd2Vpc2UgbWl0IGRlbSBganNvbmxpdGVgIFBha2V0IHZlcmFyYmVpdGV0IHdlcmRlbi4KCiMjIFByb21wdC1FbmdpbmVlcmluZwoKQmFzaWVyZW5kIGF1ZiBkZSBLb2sgKDIwMjUpIHNvbGx0ZW4gU2llIGZvbGdlbmRlIFByaW56aXBpZW4gYmVpIGlocmVtIFByb21wdCBiZWFjaHRlbjoKCkRlciBQcm9tcHQgc29sbHRlOgoKMS4gKipTcGV6aWZpc2NoIHVuZCBlaW5kZXV0aWcqKiBzZWluCjIuICoqS29udGV4dHVhbGlzaWVydW5nKiogYmVyZWl0c3RlbGxlbgozLiAqKkF1c2dhYmVmb3JtYXQqKiBkZWZpbmllcmVuCjQuICoqVGVtcGVyYXR1cioqIGJlc3RpbW1lbiAoYXVmIDAgc2V0emVuLCB3ZW5uIGlocmUgRXJnZWJuaXNzZSByZXByb2R1emllcmJhciBzZWluIHNvbGxlbiwgZGFubiBzaW5kIGRpZXNlIGFsbGVyZGluZ3Mgd2VuaWdlciBrcmVhdGl2KQo1LiAqKkNoYWluLW9mLVRob3VnaHQqKiB2ZXJ3ZW5kZW4KCiMjIyBCZWlzcGllbDogUmVwcm9kdXppZXJiYXJlciBQcm9tcHQgZsO8ciBTZW50aW1lbnQtQW5hbHlzZQoKYGBge3J9CnNlbnRpbWVudF9wcm9tcHQgPC0gZnVuY3Rpb24odGV4dF90b19hbmFseXplKSB7CiAgcHJvbXB0IDwtIGdsdWU6OmdsdWUoIgoqKkF1ZmdhYmU6KiogQW5hbHlzaWVyZSBkaWUgU3RpbW11bmcgZGVzIGZvbGdlbmRlbiBGaW5hbnp0ZXh0LUFic2Nobml0dHMuCgoqKktvbnRleHQ6KiogRGllcyBpc3QgZWluIEF1c3p1ZyBhdXMgQXBwbGUncyBNYW5hZ2VtZW50IERpc2N1c3Npb24gJiBBbmFseXNpcyAoSXRlbSA3KSBlaW5lcyAxMC1LIEZpbGluZ3MuCgoqKlRleHQgenUgYW5hbHlzaWVyZW46KioKJ3t0ZXh0X3RvX2FuYWx5emV9JyAgCgoqKkluc3RydWt0aW9uZW46KioKMS4gQmV3ZXJ0ZSBkaWUgU3RpbW11bmcgYXVmIGVpbmVyIFNrYWxhIHZvbiAtMiAoc2VociBuZWdhdGl2KSBiaXMgKzIgKHNlaHIgcG9zaXRpdikKMi4gRXJrbMOkcmUgZGVpbmUgQmV3ZXJ0dW5nIG1pdCBzcGV6aWZpc2NoZW4gVGV4dHBhc3NhZ2VuCjMuIElkZW50aWZpemllcmUgU2NobMO8c3NlbHfDtnJ0ZXIsIGRpZSBkZWluZSBCZXdlcnR1bmcgc3TDvHR6ZW4KCioqQXVzZ2FiZWZvcm1hdCAoSlNPTik6KioKe3sKICAnc2VudGltZW50X3Njb3JlJzogPFphaGwgendpc2NoZW4gLTIgdW5kIDI+LAogICdleHBsYW5hdGlvbic6ICc8QmVncsO8bmR1bmc+JywKICAna2V5X3BocmFzZXMnOiBbJ3BocmFzZTEnLCAncGhyYXNlMicsICdwaHJhc2UzJ10sCiAgJ2NvbmZpZGVuY2UnOiAnPGhvY2gvbWl0dGVsL25pZWRyaWc+Jwp9fQoKKipBbnR3b3J0OioqIikKICAKICByZXR1cm4ocHJvbXB0KQp9CgojIFByb21wdCBhbndlbmRlbgppZighaXMubnVsbChpdGVtN19jb250ZW50KSkgewogIHNlbnRpbWVudF9wcm9tcHQgPC0gc2VudGltZW50X3Byb21wdChpdGVtN19jb250ZW50KQogIHNlbnRpbWVudF9yZXN1bHQgPC0gY2hhdCRjaGF0KHNlbnRpbWVudF9wcm9tcHQpCiAgY2F0KCJTZW50aW1lbnQtQW5hbHlzZTpcbiIsIHNlbnRpbWVudF9yZXN1bHQpCn0KYGBgCgpEaWVzZXIgUHJvbXB0IGZvcmRlcnQgZGFzIExMTSBkYXp1IGF1ZiBzZWhyIHZlcmJvc2UgenUgc2VpbiwgZC5oLiBzaWNoIHNlaHIgdmllbCB6dSBlcmtsw6RyZW4uIFdlbm4gU2llIHZpZWxlIFRleHRlIHp1IGFuYWx5c2llcmVuIGhhYmVuIHfDvHJkZW4gU2llIGRpZSBPdXRwdXQgVG9rZW5zIG1pbmltaWVyZW4gd29sbGVuLiBJbiBkaWVzZW0gRmFsbCB3w7xyZGVuIFNpZSBwb3RlbnRpZWxsIGRpZSBFcmtsw6RydW5nLCB3YXJ1bSBzaWNoIGRhcyBMTE0gZsO8ciBzZWluZSBFaW5zY2jDpHR6dW5nIGVudHNjaGllZGVuIGhhdCBuaWNodCB2ZXJsYW5nZW4uCgpOdW4gc29sbHRlbiBTaWUgZGFzIGF1c2dlZ2ViZW5lIEpTT04gbm9jaCBpbiBSIHZlcmFyYmVpdGVuIHVuZCBhbHMgVGliYmxlIGFic3BlaWNoZXJuOgoKYGBge3J9CnBhcnNlX3NlbnRpbWVudF9qc29uIDwtIGZ1bmN0aW9uKGpzb25fc3RyaW5nKSB7CiAgCiAgIyBFbnRmZXJuZSBNYXJrZG93bi1Db2RlLUJsb2NrLVdyYXBwZXIgZmFsbHMgdm9yaGFuZGVuCiAgY2xlYW5fanNvbiA8LSBqc29uX3N0cmluZyAlPiUKICAgIHN0cl9yZW1vdmUoIl5gYGBqc29uXFxzKiIpICU+JSAgICAjIEVudGZlcm5lIGBgYGpzb24gYW0gQW5mYW5nCiAgICBzdHJfcmVtb3ZlKCJgYGBcXHMqJCIpICU+JSAgICAgICAgIyBFbnRmZXJuZSBgYGAgYW0gRW5kZQogICAgc3RyX3JlbW92ZSgiXmBgYFxccyoiKSAlPiUgICAgICAgICMgRW50ZmVybmUgYGBgIGFtIEFuZmFuZyAoZmFsbHMga2VpbiBqc29uKQogICAgc3RyX3RyaW0oKSAgICAgICAgICAgICAgICAgICAgICAgICMgRW50ZmVybmUgTGVlcnplaWNoZW4KICAKICAjIEpTT04gcGFyc2VuCiAgcmVzdWx0IDwtIGZyb21KU09OKGNsZWFuX2pzb24pCiAgCiAgIyBTZW50aW1lbnQgU2NvcmUgaW50ZXJwcmV0aWVyZW4KICBzZW50aW1lbnRfbGFiZWwgPC0gY2FzZV93aGVuKAogICAgcmVzdWx0JHNlbnRpbWVudF9zY29yZSA+IDAgfiAiUG9zaXRpdiIsCiAgICByZXN1bHQkc2VudGltZW50X3Njb3JlID09IDAgfiAiTmV1dHJhbCIsIAogICAgcmVzdWx0JHNlbnRpbWVudF9zY29yZSA8IDAgfiAiTmVnYXRpdiIsCiAgICBUUlVFIH4gIlVuYmVrYW5udCIKICApCiAgCiAgIyBLZXkgUGhyYXNlcyBhbHMgVGliYmxlIHN0cnVrdHVyaWVyZW4KICBrZXlfcGhyYXNlc19kZiA8LSB0aWJibGUoCiAgICBwaHJhc2VfaWQgPSBzZXFfYWxvbmcocmVzdWx0JGtleV9waHJhc2VzKSwKICAgIHBocmFzZSA9IHJlc3VsdCRrZXlfcGhyYXNlcywKICAgICMgS2F0ZWdvcmlzaWVyZSBQaHJhc2VzCiAgICBjYXRlZ29yeSA9IGNhc2Vfd2hlbigKICAgICAgc3RyX2RldGVjdChwaHJhc2UsIHJlZ2V4KCJzYWxlc3xyZXZlbnVlfGdyb3d0aHxpbmNyZWFzZSIsIGlnbm9yZV9jYXNlID0gVFJVRSkpIH4gIlBvc2l0aXZlIFBlcmZvcm1hbmNlIiwKICAgICAgc3RyX2RldGVjdChwaHJhc2UsIHJlZ2V4KCJtYXJnaW58ZGVjbGluZXxwcmVzc3VyZXx1bmZhdm9yYWJsZSIsIGlnbm9yZV9jYXNlID0gVFJVRSkpIH4gIkNoYWxsZW5nZXMiLAogICAgICBzdHJfZGV0ZWN0KHBocmFzZSwgcmVnZXgoImNhc2h8ZGl2aWRlbmR8cmVwdXJjaGFzZXxjYXBpdGFsIiwgaWdub3JlX2Nhc2UgPSBUUlVFKSkgfiAiQ2FwaXRhbCBNYW5hZ2VtZW50IiwgCiAgICAgIHN0cl9kZXRlY3QocGhyYXNlLCByZWdleCgiaVBob25lfGlQYWR8TWFjfGlQb2QiLCBpZ25vcmVfY2FzZSA9IFRSVUUpKSB+ICJQcm9kdWN0cyIsCiAgICAgIHN0cl9kZXRlY3QocGhyYXNlLCByZWdleCgic3RvcmV8cmV0YWlsfGV4cGFuc2lvbiIsIGlnbm9yZV9jYXNlID0gVFJVRSkpIH4gIlJldGFpbCIsCiAgICAgIFRSVUUgfiAiT3RoZXIiCiAgICApLAogICAgIyBFeHRyYWhpZXJlIG51bWVyaXNjaGUgV2VydGUKICAgIGNvbnRhaW5zX251bWJlciA9IHN0cl9kZXRlY3QocGhyYXNlLCAiXFxkIiksCiAgICBjb250YWluc19wZXJjZW50YWdlID0gc3RyX2RldGVjdChwaHJhc2UsICIlIiksCiAgICBjb250YWluc19kb2xsYXIgPSBzdHJfZGV0ZWN0KHBocmFzZSwgIlxcJCIpCiAgKQogIAogIGxpc3QoCiAgICBzdW1tYXJ5ID0gdGliYmxlKAogICAgICBzZW50aW1lbnRfc2NvcmUgPSByZXN1bHQkc2VudGltZW50X3Njb3JlLAogICAgICBzZW50aW1lbnRfbGFiZWwgPSBzZW50aW1lbnRfbGFiZWwsCiAgICAgIGNvbmZpZGVuY2UgPSByZXN1bHQkY29uZmlkZW5jZSwKICAgICAgZXhwbGFuYXRpb25fbGVuZ3RoID0gbmNoYXIocmVzdWx0JGV4cGxhbmF0aW9uKSwKICAgICAga2V5X3BocmFzZXNfY291bnQgPSBsZW5ndGgocmVzdWx0JGtleV9waHJhc2VzKQogICAgKSwKICAgIGV4cGxhbmF0aW9uID0gcmVzdWx0JGV4cGxhbmF0aW9uLAogICAga2V5X3BocmFzZXMgPSBrZXlfcGhyYXNlc19kZgogICkKfQoKIyBXYXMgd3VyZGUgaGF1cHRzw6RjaGxpY2ggZ2VzYWd0IHBybyBLYXRlZ29yaWUKYW5hbHl6ZV9rZXlfcGhyYXNlcyA8LSBmdW5jdGlvbihwYXJzZWRfcmVzdWx0KSB7CiAgCiAgcGFyc2VkX3Jlc3VsdCRrZXlfcGhyYXNlcyAlPiUKICAgIGNvdW50KGNhdGVnb3J5LCBzb3J0ID0gVFJVRSkgJT4lCiAgICBtdXRhdGUoCiAgICAgIHBlcmNlbnRhZ2UgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAxKQogICAgKQp9CgojIE51bWVyaXNjaGUgV2VydGUgZXh0cmFoaWVyZW4KZXh0cmFjdF9udW1lcmljYWxfaW5zaWdodHMgPC0gZnVuY3Rpb24ocGFyc2VkX3Jlc3VsdCkgewogIAogIHBocmFzZXNfd2l0aF9udW1iZXJzIDwtIHBhcnNlZF9yZXN1bHQka2V5X3BocmFzZXMgJT4lCiAgICBmaWx0ZXIoY29udGFpbnNfbnVtYmVyKSAlPiUKICAgIG11dGF0ZSgKICAgICAgIyBFeHRyYWhpZXJlIFByb3plbnRzw6R0emUKICAgICAgcGVyY2VudGFnZSA9IHN0cl9leHRyYWN0KHBocmFzZSwgIlxcZCsoPzpcXC5cXGQrKT8lIiksCiAgICAgICMgRXh0cmFoaWVyZSBEb2xsYXJiZXRyw6RnZSAKICAgICAgZG9sbGFyX2Ftb3VudCA9IHN0cl9leHRyYWN0KHBocmFzZSwgIlxcJFxcZCsoPzpcXC5cXGQrKT9cXHMqKD86YmlsbGlvbnxtaWxsaW9uKT8iKSwKICAgICAgIyBFeHRyYWhpZXJlIGFuZGVyZSBaYWhsZW4KICAgICAgb3RoZXJfbnVtYmVycyA9IHN0cl9leHRyYWN0KHBocmFzZSwgIlxcZCsoPzpcXC5cXGQrKT8iKQogICAgKSAlPiUKICAgIHNlbGVjdChwaHJhc2VfaWQsIHBocmFzZSwgY2F0ZWdvcnksIHBlcmNlbnRhZ2UsIGRvbGxhcl9hbW91bnQsIG90aGVyX251bWJlcnMpCiAgCiAgcmV0dXJuKHBocmFzZXNfd2l0aF9udW1iZXJzKQp9CgpwYXJzZWRfcmVzdWx0IDwtIHBhcnNlX3NlbnRpbWVudF9qc29uKHNlbnRpbWVudF9yZXN1bHQpCgojIFp1c2FtbWVuZmFzc3VuZyBhbnplaWdlbgpwcmludChwYXJzZWRfcmVzdWx0JHN1bW1hcnkpCgojIEbDvHIgd2VpdGVyZSBBbmFseXNlbjoKIyBzZW50aW1lbnRfc2NvcmUgPC0gcGFyc2VkX3Jlc3VsdCRzdW1tYXJ5JHNlbnRpbWVudF9zY29yZQojIGV4cGxhbmF0aW9uIDwtIHBhcnNlZF9yZXN1bHQkZXhwbGFuYXRpb24gIAojIHBocmFzZXNfZGYgPC0gcGFyc2VkX3Jlc3VsdCRrZXlfcGhyYXNlcwoKIyBGYWxscyBTaWUgbWVocmVyZSBKU09OIEVyZ2Vibmlzc2UgaGFiZW46CnByb2Nlc3NfbXVsdGlwbGVfcmVzdWx0cyA8LSBmdW5jdGlvbihqc29uX2xpc3QpIHsKICAKICBtYXBfZGZyKHNlcV9hbG9uZyhqc29uX2xpc3QpLCB+IHsKICAgIHJlc3VsdCA8LSBwYXJzZV9zZW50aW1lbnRfanNvbihqc29uX2xpc3RbWy54XV0pCiAgICByZXN1bHQkc3VtbWFyeSAlPiUKICAgICAgbXV0YXRlKGFuYWx5c2lzX2lkID0gLngpCiAgfSkKfQpgYGAKClN1cGVyLiBEYW1pdCB3aXNzZW4gU2llIG51biwgd2llIFNpZSBUZXh0ZGF0ZWllbiBtaXR0ZWxzIGVsbG1lciB1bmQgR29vZ2xlIEdlbWluaSBhdWYgZGVyZW4gU2VudGltZW50IGhpbiBhbmFseXNpZXJlbiBrw7ZubmVuIHVuZCB3aWUgU2llIGRlbiBPdXRwdXQgZGFuYWNoIGluIFIgaW4gZWluZW0gVGliYmxlIHNpY2hlcm4uCgpadW0gQWJzY2hsdXNzIG5vY2ggZWluaWdlIEJlc3QgUHJhY3RpY2VzIHp1ciBBbmFseXNlIG1pdCBMTE1zOgoKMS4gKipFdmFsdWllcnVuZzoqKiBJbW1lciBtYW51ZWxsIGVpbiBTYW1wbGUgcHLDvGZlbgoyLiAqKktvbnN0cnVrdHZhbGlkaXTDpHQ6KiogRXJnZWJuaXNzZSBtaXQgYW5kZXJlbiBNZXRob2RlbiB2ZXJnbGVpY2hlbgozLiAqKlRyYW5zcGFyZW56OioqIFByb21wdHMgdW5kIE1vZGVsbC1QYXJhbWV0ZXIgZG9rdW1lbnRpZXJlbgo0LiAqKktvc3RlbjoqKiBUb2tlbi1WZXJ3ZW5kdW5nIMO8YmVyd2FjaGVuCgpgYGB7cn0KIyBUb2tlbi0gdW5kIEtvc3Rlbi1UcmFja2luZwp0cmFja191c2FnZSA8LSBmdW5jdGlvbihjaGF0X29iamVjdCwgdGV4dF9pbnB1dCkgewogICMgR2VzY2jDpHR6dGUgVG9rZW4gKGdyb2I6IH40IFplaWNoZW4gcHJvIFRva2VuKQogIGVzdGltYXRlZF90b2tlbnMgPC0gbmNoYXIodGV4dF9pbnB1dCkgLyA0CgogIGNhdCgiR2VzY2jDpHR6dGUgVG9rZW46Iiwgcm91bmQoZXN0aW1hdGVkX3Rva2VucyksICJcbiIpCn0KCiMgQmVpc3BpZWwKaWYoIWlzLm51bGwoaXRlbTdfY29udGVudCkpIHsKICB0cmFja191c2FnZShjaGF0LCBpdGVtN19jb250ZW50KQp9CmBgYAoKIyMjIExpbWl0YXRpb25lbiBiZWFjaHRlbgoKTGVpZGVyIG5laWdlbiBMTE1zIGRhenUgenUgaGFsbHV6aW5pZXJlbiAoenVtaW5kZXN0IHN0YW5kIEp1bmkgMjAyNSkuIFNpZSBrw7ZubmVuIGFtIEVuZGUgbm9jaCBwcsO8ZmVuLCBvYiBkaWUgRWluc2Now6R0enVuZyBkZXIgTExNIG1pdCBob2hlciBvZGVyIG5pZWRyaWdlciBTaWNoZXJoZWl0IGdldMOkdGlndCB3dXJkZSwgYWJlciBhdWNoIGhpZXIgaXN0IHZvcnNpY2h0IGFuZ2VzYWd0LCBkYSBkaWUgTExNcyBzZWhyIHNlbGJzdHNpY2hlciBEaW5nZSBlcmZpbmRlbi4KCgpgYGB7cn0KIyBRdWFsaXTDpHRzLUNoZWNrIEZ1bmt0aW9uCmNoZWNrX2xsbV9yZXNwb25zZV9xdWFsaXR5IDwtIGZ1bmN0aW9uKHJlc3BvbnNlKSB7CiAgcXVhbGl0eV9pbmRpY2F0b3JzIDwtIGxpc3QoCiAgICBsZW5ndGhfcmVhc29uYWJsZSA9IG5jaGFyKHJlc3BvbnNlKSA+IDEwICYmIG5jaGFyKHJlc3BvbnNlKSA8IDEwMDAwLAogICAgbm9faGFsbHVjaW5hdGlvbl9tYXJrZXJzID0gIXN0cl9kZXRlY3QocmVzcG9uc2UsICIoP2kpKGljaCB3ZWnDnyBuaWNodHx1bnNpY2hlcnxrYW5uIG5pY2h0IGJlc3TDpHRpZ2VuKSIpLAogICAgc3RydWN0dXJlZF9mb3JtYXQgPSBzdHJfZGV0ZWN0KHJlc3BvbnNlLCAiXFx7fFxcW3xcXDp8XFwtIiksCiAgICBub19vYnZpb3VzX2Vycm9ycyA9ICFzdHJfZGV0ZWN0KHJlc3BvbnNlLCAiKD9pKShlcnJvcnxmZWhsZXJ8ZXhjZXB0aW9uKSIpCiAgKQogIAogIG92ZXJhbGxfcXVhbGl0eSA8LSBtZWFuKHVubGlzdChxdWFsaXR5X2luZGljYXRvcnMpKQogIAogIHJldHVybihsaXN0KAogICAgcXVhbGl0eV9zY29yZSA9IG92ZXJhbGxfcXVhbGl0eSwKICAgIGluZGljYXRvcnMgPSBxdWFsaXR5X2luZGljYXRvcnMsCiAgICByZWNvbW1lbmRhdGlvbiA9IGlmZWxzZShvdmVyYWxsX3F1YWxpdHkgPiAwLjcsICJBa3plcHRhYmVsIiwgIsOcYmVycHLDvGZ1bmcgZXJmb3JkZXJsaWNoIikKICApKQp9CgojIEJlaXNwaWVsIGbDvHIgUXVhbGl0w6R0c3Byw7xmdW5nCmlmKGV4aXN0cygic2VudGltZW50X3Jlc3VsdCIpKSB7CiAgcXVhbGl0eV9jaGVjayA8LSBjaGVja19sbG1fcmVzcG9uc2VfcXVhbGl0eShzZW50aW1lbnRfcmVzdWx0KQogIGNhdCgiUXVhbGl0w6R0cy1TY29yZToiLCBxdWFsaXR5X2NoZWNrJHF1YWxpdHlfc2NvcmUsICJcbiIpCiAgY2F0KCJFbXBmZWhsdW5nOiIsIHF1YWxpdHlfY2hlY2skcmVjb21tZW5kYXRpb24sICJcbiIpCn0KYGBgCgpJbiBkaWVzZW0gU2lubmUgZnJvaGVzIEFyYmVpdGVuIG1pdCBgZWxsbWVyYCEKCiMjIFdlaXRlcmbDvGhyZW5kZSBSZXNzb3VyY2VuCgotICoqZWxsbWVyIERva3VtZW50YXRpb246KiogaHR0cHM6Ly9lbGxtZXIudGlkeXZlcnNlLm9yZy8KLSAqKkdvb2dsZSBHZW1pbmkgQVBJOioqIGh0dHBzOi8vYWkuZ29vZ2xlLmRldi8KLSAqKlRpZXMgZGUgS29rICgyMDI1KSAiQ2hhdEdQVCBmb3IgVGV4dHVhbCBBbmFseXNpcz8gSG93IHRvIFVzZSBHZW5lcmF0aXZlIExMTXMgaW4gQWNjb3VudGluZyBSZXNlYXJjaCIuIE1hbmFnZW1lbnQgU2NpZW5jZS4gaHR0cHM6Ly9kb2kub3JnLzEwLjEyODcvbW5zYy4yMDIzLjAzMjUzOioqIEbDvHIgd2VpdGVyZSBQcm9tcHQtRW5naW5lZXJpbmcgVGVjaG5pa2VuIAotICoqU0VDIEVER0FSIERhdGFiYXNlOioqIGh0dHBzOi8vd3d3LnNlYy5nb3YvZWRnYXIvc2VhcmNoZWRnYXIvY29tcGFueXNlYXJjaC5odG1sCg==