RUBY ON RAILS · 25 MIN READ ·

Rails LLM Observability: Prompts, Latentie en Tokengebruik Tracen in Productie met Langfuse

Rails LLM observability-gids: trace prompts, latentie en tokenkosten in productie met Langfuse en ActiveSupport::Notifications. Geen stille AI-fouten.

Rails LLM Observability: Prompts, Latentie en Tokengebruik Tracen in Productie met Langfuse

Drie weken nadat een klant zijn AI-gestuurde documentsamenvatting lanceerde, kreeg ik op een dinsdagmiddag een Slack-bericht: “Waarom duurt de samenvattenknop zo lang?” Ik opende de Rails-requestlog. Elke LLM-aanroep duurde 4,2 seconden. Twee weken eerder lag de mediaan op 800 milliseconden. De feature degradeerde al dagen voordat iemand het merkte, omdat niemand naar de juiste getallen keek.

De requestlog gaf me HTTP-responstijden. De OpenTelemetry-opzet die ik het kwartaal daarvoor had gebouwd gaf me querytijden voor de database. Geen van beide vertelde me wat er inside de LLM-aanroep gebeurde: welk model werd gebruikt, hoeveel tokens werden verstuurd, of de prompt recent was gewijzigd, of de vertraging afkomstig was van tokengeneratie of van de netwerkrondtrip. Ik vloog blind in de AI-laag.

Rails LLM observability is de instrumentatie die dit oplost. Het is niet hetzelfde als HTTP-request tracing of databasequery-profilering — het is een laag daarboven, specifiek voor hoe LLMs zich gedragen in productie.

Waarom Generieke Observability de AI-laag Mist

OpenTelemetry ingebouwd in Rails geeft je spans voor elke HTTP-request, databasequery en achtergrondtaak. Ik behandelde de volledige opzet in de OpenTelemetry Rails-post. Het is uitstekende infrastructuur en ik draai het op elke klantapplicatie. Maar het kan je niet vertellen:

  • Welke prompttemplate de trage request genereerde
  • Of je nieuwe Claude Sonnet 5-migratie de latentie daadwerkelijk verminderde
  • Hoeveel invoertokens een gemiddeld samenvattingsverzoek verstuurt
  • Of de promptwijziging van gisteren de foutpercentages omhoog stuurde
  • Wat je dagelijkse tokenkosten zijn per operatie, niet alleen per factuurmaand

Dit zijn de vragen die ertoe doen als een AI-feature in productie misbehaves. HTTP-tracing ziet een 200-OK met een body van 4,2 seconden. Rails LLM observability ziet “de samenvatoperatie op claude-sonnet-5 met promptversie v14 verwerkte 8.200 invoertokens in 3,8 seconden, kostte $0,041 en slaagde — maar dezelfde operatie op verzoeken met documenten boven 5.000 tokens degradeerde 2x vanaf donderdagochtend.”

Dat is uitvoerbaar. De HTTP-trace is het niet.

LLM-aanroepen Instrumenteren met ActiveSupport::Notifications

De Rails-instrumentatielaag is ActiveSupport::Notifications. Die zit al in je applicatie — het is wat ActiveRecord::LogSubscriber en de request-timings die je in je ontwikkelingslogs ziet aanstuurt. Voeg een wrapper toe rondom je LLM-client die een event publiceert voor elke aanroep.

Begin met het definiëren van het eventschema op één plek:

# app/llm/instrumentation.rb
module LLM
  module Instrumentation
    EVENT_NAME = "llm.call".freeze

    def self.instrument(operation:, model:, prompt_version: nil, tenant_id: nil, &block)
      payload = {
        operation:      operation,
        model:          model,
        prompt_version: prompt_version,
        tenant_id:      tenant_id,
        started_at:     Process.clock_gettime(Process::CLOCK_MONOTONIC)
      }

      ActiveSupport::Notifications.instrument(EVENT_NAME, payload) do
        result = block.call
        payload[:input_tokens]  = result[:usage]&.dig(:input_tokens)
        payload[:output_tokens] = result[:usage]&.dig(:output_tokens)
        result
      end
    rescue => e
      payload[:error]     = e.class.name
      payload[:error_msg] = e.message.first(200)
      raise
    ensure
      elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - payload[:started_at]
      payload[:duration_ms] = (elapsed * 1000).round
    end
  end
end

Wikkel je bestaande Claude-client om dit event bij elke aanroep te publiceren:

# app/llm/claude_client.rb
require "anthropic"

class LLM::ClaudeClient
  MODEL = "claude-sonnet-5"

  def initialize
    @client = Anthropic::Client.new(api_key: ENV.fetch("ANTHROPIC_API_KEY"))
  end

  def complete(system:, user:, operation:, prompt_version: nil, tenant_id: nil, max_tokens: 1024)
    LLM::Instrumentation.instrument(
      operation:      operation,
      model:          MODEL,
      prompt_version: prompt_version,
      tenant_id:      tenant_id
    ) do
      response = @client.messages.create(
        model:      MODEL,
        max_tokens: max_tokens,
        system:     system,
        messages:   [{ role: "user", content: user }]
      )

      {
        text:  response.content.first.text,
        usage: {
          input_tokens:  response.usage.input_tokens,
          output_tokens: response.usage.output_tokens
        }
      }
    end
  end
end

De parameter prompt_version is een string als "v14" of een 8-karakter inhouds-hash. Hij is optioneel — je kunt hem incrementeel aan aanroeplocaties toevoegen. De parameter tenant_id laat je traces per klant filteren, wat cruciaal wordt zodra je meer dan één betalend account hebt en iemand een supportticket indient.

Op de aanroeplocatie:

class DocumentSummarizer
  PROMPT_VERSION = "v14"
  SYSTEM_PROMPT  = <<~PROMPT.freeze
    You are a precise document summarizer. Return a factual summary
    in 2-4 sentences covering the main points. No bullet points.
    No preamble. Just the summary.
  PROMPT

  def summarize(document, tenant_id:)
    client = LLM::ClaudeClient.new
    result = client.complete(
      system:         SYSTEM_PROMPT,
      user:           "Summarize this document:\n\n#{document.body}",
      operation:      "document.summarize",
      prompt_version: PROMPT_VERSION,
      tenant_id:      tenant_id,
      max_tokens:     256
    )
    document.update!(summary: result[:text])
  end
end

Traces Verzenden naar Langfuse

Langfuse is een open source Rails LLM observability-platform met een zelf te hosten Docker-server en een cloud-gehoste versie. Het datamodel spreekt LLM natively: traces, generaties, promptversies, scores. Je ActiveSupport::Notifications-subscriber publiceert elke aanroep naar Langfuse op de achtergrond zonder het requestpad te blokkeren.

Voeg de gem toe:

# Gemfile
gem "langfuse"

Bouw dan de subscriber:

# app/llm/langfuse_subscriber.rb
class LLM::LangfuseSubscriber
  INPUT_TOKEN_COSTS = {
    "claude-sonnet-5"   => 0.000003,
    "claude-haiku-4-5"  => 0.00000025
  }.freeze

  OUTPUT_TOKEN_COSTS = {
    "claude-sonnet-5"   => 0.000015,
    "claude-haiku-4-5"  => 0.00000125
  }.freeze

  def self.attach
    ActiveSupport::Notifications.subscribe(LLM::Instrumentation::EVENT_NAME) do |*args|
      event = ActiveSupport::Notifications::Event.new(*args)
      new(event).call
    end
  end

  def initialize(event)
    @event   = event
    @payload = event.payload
    @client  = Langfuse.new(
      public_key: ENV.fetch("LANGFUSE_PUBLIC_KEY"),
      secret_key: ENV.fetch("LANGFUSE_SECRET_KEY"),
      host:       ENV.fetch("LANGFUSE_HOST", "https://cloud.langfuse.com")
    )
  end

  def call
    trace = @client.trace(
      name:     @payload[:operation],
      metadata: {
        prompt_version: @payload[:prompt_version],
        tenant_id:      @payload[:tenant_id],
        rails_env:      Rails.env
      }
    )

    trace.generation(
      name:           @payload[:operation],
      model:          @payload[:model],
      start_time:     @event.time,
      end_time:       @event.end,
      usage:          usage_hash,
      level:          @payload[:error] ? "ERROR" : "DEFAULT",
      status_message: @payload[:error_msg]
    )

    @client.flush
  rescue => e
    Rails.logger.warn("LLM::LangfuseSubscriber mislukt: #{e.class}: #{e.message}")
  end

  private

  def usage_hash
    input_tokens  = @payload[:input_tokens].to_i
    output_tokens = @payload[:output_tokens].to_i
    model         = @payload[:model]

    {
      input:       input_tokens,
      output:      output_tokens,
      total:       input_tokens + output_tokens,
      input_cost:  (input_tokens  * INPUT_TOKEN_COSTS.fetch(model,  0)).round(6),
      output_cost: (output_tokens * OUTPUT_TOKEN_COSTS.fetch(model, 0)).round(6),
      total_cost:  ((input_tokens  * INPUT_TOKEN_COSTS.fetch(model,  0)) +
                    (output_tokens * OUTPUT_TOKEN_COSTS.fetch(model, 0))).round(6),
      unit:        "TOKENS"
    }
  end
end

Koppel de subscriber in een initializer:

# config/initializers/llm_observability.rb
Rails.application.config.after_initialize do
  LLM::LangfuseSubscriber.attach
end

De rescue binnenin call is niet optioneel. Observability-infrastructuur die productie neerhaalt wanneer Langfuse niet bereikbaar is, is erger dan helemaal geen observability.

Als Je Liever in Postgres Blijft

Langfuse is voor de meeste teams die in 2026 AI-features lanceren het juiste gereedschap. Maar als je geen nieuwe gehoste service kunt introduceren, kost een PostgreSQL-gebaseerde tracetabel dertig minuten om op te zetten en integreert hij natuurlijk met je bestaande Rails-monitoring.

# db/migrate/20260804000000_create_llm_traces.rb
class CreateLlmTraces < ActiveRecord::Migration[8.0]
  def change
    create_table :llm_traces do |t|
      t.string  :operation,      null: false
      t.string  :model,          null: false
      t.string  :prompt_version
      t.bigint  :tenant_id
      t.integer :input_tokens
      t.integer :output_tokens
      t.integer :duration_ms
      t.decimal :cost,           precision: 10, scale: 6
      t.string  :error_class
      t.text    :error_message
      t.jsonb   :metadata,       default: {}
      t.timestamps
    end

    add_index :llm_traces, :operation
    add_index :llm_traces, :tenant_id
    add_index :llm_traces, :created_at
    add_index :llm_traces, [:operation, :created_at]
    add_index :llm_traces, [:tenant_id, :created_at]
  end
end

Zodra data naar llm_traces stroomt, kan Postgres dezelfde vragen beantwoorden als Langfuse — alleen zonder de voorgebouwde UI:

# p95-latentie per operatie over de afgelopen 24 uur
LlmTrace
  .where(created_at: 24.hours.ago..)
  .group(:operation)
  .select(
    "operation",
    "percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) AS p95_ms",
    "AVG(duration_ms) AS avg_ms",
    "COUNT(*) AS total"
  )

# dagelijkse kosten per model over de afgelopen 7 dagen
LlmTrace
  .where(created_at: 7.days.ago..)
  .group(:model, "DATE(created_at)")
  .sum(:cost)

# foutpercentage per operatie
LlmTrace
  .where(created_at: 24.hours.ago..)
  .group(:operation)
  .select(
    "operation",
    "COUNT(*) AS total",
    "SUM(CASE WHEN error_class IS NOT NULL THEN 1 ELSE 0 END) AS errors",
    "ROUND(100.0 * SUM(CASE WHEN error_class IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*), 2) AS error_pct"
  )

Ik behandelde de pg_stat_statements-aanpak voor het opsporen van trage SQL in de pg_stat_statements-post. Dezelfde mindset geldt hier: percentieldistributies boven gemiddelden, altijd segmenteren op operatie en model voordat je conclusies trekt over het systeem als geheel.

Automatisch Promptversies Bijhouden

Elke keer dat je een prompt wijzigt, voer je een impliciete A/B-test uit op productieverkeer. Zonder versietags op je traces weet je niet of de wijziging heeft geholpen of gekwetst. Een handmatige stringconstante werkt maar raakt verouderd — iemand wijzigt de prompt en vergeet PROMPT_VERSION te verhogen.

Hash de promptinhoud in plaats daarvan automatisch:

# app/llm/prompt_registry.rb
module LLM
  class PromptRegistry
    def self.version_for(prompt_string)
      Digest::SHA1.hexdigest(prompt_string)[0, 8]
    end

    def self.register(name, content)
      @prompts       ||= {}
      @prompts[name]   = { content: content, version: version_for(content) }
    end

    def self.get(name)
      @prompts&.fetch(name) { raise KeyError, "Onbekende prompt: #{name}" }
    end
  end
end

Registreer prompts bij het laden:

# config/initializers/llm_prompts.rb
LLM::PromptRegistry.register(
  :document_summarizer,
  <<~PROMPT
    You are a precise document summarizer. Return a factual summary
    in 2-4 sentences covering the main points. No bullet points.
    No preamble. Just the summary.
  PROMPT
)

Gebruik ze op de aanroeplocatie:

class DocumentSummarizer
  def summarize(document, tenant_id:)
    prompt = LLM::PromptRegistry.get(:document_summarizer)
    client = LLM::ClaudeClient.new

    result = client.complete(
      system:         prompt[:content],
      user:           "Summarize:\n\n#{document.body}",
      operation:      "document.summarize",
      prompt_version: prompt[:version],
      tenant_id:      tenant_id
    )

    document.update!(summary: result[:text])
  end
end

Wanneer je de prompt in de initializer bewerkt, verandert de SHA1-hash, verschijnt de nieuwe versietag automatisch in Langfuse, en kun je p95-latentie en foutpercentages vergelijken tussen a3f2b1c0 en 7e9d4a21 zonder de aanroeplocatie aan te raken. Ik behandelde het diepere onderwerp van geautomatiseerd promptregressietesten in CI in de LLM evals-post.

Alerteren op Wat Ertoe Doet

Rails LLM observability is alleen nuttig als het actie triggert. Vier alerts dekken de meeste productie-AI-incidenten af:

Tokenkostenspiek. Vergelijk de uitgaven van het afgelopen uur met het 7-daagse voortschrijdende uurgemiddelde voor dezelfde operatie. Meer dan 2x is het waard om iemand wakker te maken, omdat het meestal betekent dat een promptbug enorme contextvensters genereert.

# app/jobs/llm_cost_alert_job.rb
class LlmCostAlertJob < ApplicationJob
  queue_as :monitoring

  def perform
    LlmTrace.distinct.pluck(:operation).each do |op|
      recent_cost = LlmTrace
        .where(operation: op, created_at: 1.hour.ago..)
        .sum(:cost).to_f

      baseline = LlmTrace
        .where(operation: op, created_at: 7.days.ago..1.day.ago)
        .sum(:cost).to_f / 168.0  # uurgemiddelde over 6 dagen

      next if baseline < 0.01

      if recent_cost > baseline * 2.0
        Sentry.capture_message(
          "LLM-kostenspiek: #{op}#{recent_cost.round(4)} vs " \
          "#{baseline.round(4)} per uur (#{(recent_cost / baseline).round(1)}x)"
        )
      end
    end
  end
end

Latentiedegradatie. Een p95 boven je SLO moet binnen minuten afgaan. Plan dit elke vijf minuten via de recurring task van Solid Queue:

# config/recurring.yml
llm_cost_alert:
  class: LlmCostAlertJob
  schedule: "0 * * * *"

llm_latency_check:
  class: LlmLatencyAlertJob
  schedule: "*/5 * * * *"
# app/jobs/llm_latency_alert_job.rb
class LlmLatencyAlertJob < ApplicationJob
  SLO_MS = { "document.summarize" => 3000, "chat.respond" => 5000 }.freeze

  def perform
    SLO_MS.each do |operation, threshold_ms|
      recent = LlmTrace.where(operation: operation, created_at: 15.minutes.ago..)
      next if recent.count < 10

      p95 = recent.pluck(:duration_ms).sort.then { |ms| ms[(ms.size * 0.95).ceil - 1] }

      if p95.to_i > threshold_ms
        Sentry.capture_message(
          "LLM-latentie SLO-schending: #{operation} p95=#{p95}ms (SLO #{threshold_ms}ms)"
        )
      end
    end
  end
end

Foutpercentage. Meer dan 5% van de traces met een ingevuld error_class is ongewoon. Alles boven 10% is een productie-incident.

Stille operaties. Een operatie die nul traces produceert in een venster waar die normaal honderden heeft, betekent dat een codepad kapot of onbereikbaar is. Dit is het moeilijkst te merken zonder expliciete monitoring, omdat elk individueel verzoek ofwel nooit de LLM-laag bereikt of stil faalt daarvoor.

Streamingresponsies

Streamingresponsies — gebruikt wanneer je LLM-uitvoer token voor token naar de browser stuurt via Turbo Streams — hebben geen volledig tokencount op het moment dat de stream opent. Je accumuleert gebruik vanuit de laatste event. Werk de clientwrapper bij:

def stream(system:, user:, operation:, prompt_version: nil, tenant_id: nil, &block)
  accumulated_output = ""
  usage              = {}

  LLM::Instrumentation.instrument(
    operation:      operation,
    model:          MODEL,
    prompt_version: prompt_version,
    tenant_id:      tenant_id
  ) do
    @client.messages.stream_message(
      model:      MODEL,
      max_tokens: 1024,
      system:     system,
      messages:   [{ role: "user", content: user }]
    ) do |event|
      case event.type
      when "content_block_delta"
        chunk = event.delta.text
        accumulated_output += chunk
        block.call(chunk) if block
      when "message_delta"
        usage = {
          input_tokens:  event.usage.input_tokens,
          output_tokens: event.usage.output_tokens
        }
      end
    end

    { text: accumulated_output, usage: usage }
  end
end

De usage-event arriveert aan het einde van elke Claude-streamresponsie. Door hem te accumuleren binnen het instrumentatieblok, verschijnen tokencounts in je Langfuse-traces zelfs bij streamingaanroepen. Ik behandelde de volledige SSE-streaming-opzet voor Rails in de streaming Claude-responsies-post.

Healthcheck-eindpunt

Stel LLM-gezondheid bloot aan je load balancer en uptimemonitor via een speciaal eindpunt:

# app/checks/llm_health_check.rb
class LlmHealthCheck
  def self.call
    recent = LlmTrace.where(created_at: 10.minutes.ago..)
    total  = recent.count
    return { status: :ok, message: "geen recent verkeer" } if total < 5

    error_count = recent.where.not(error_class: nil).count
    error_rate  = error_count.to_f / total
    p95         = recent.pluck(:duration_ms).sort
                        .then { |ms| ms[(ms.size * 0.95).ceil - 1].to_i }

    {
      status:     (error_rate > 0.1 || p95 > 5000) ? :degraded : :ok,
      error_rate: error_rate.round(3),
      p95_ms:     p95,
      total:      total
    }
  end
end

Koppel het aan je routes:

# config/routes.rb
get "/up/llm", to: lambda { |_env|
  health = LlmHealthCheck.call
  status = health[:status] == :ok ? 200 : 503
  [status, { "Content-Type" => "application/json" }, [health.to_json]]
}

Pingdom of BetterUptime op /up/llm geeft je een AI-specifieke alert zonder je primaire /up-healthcheck aan te raken.

Per-Tenant Debuggen

Op multi-tenant applicaties betekent de tenant_id op elke trace dat je kunt beantwoorden: “degradeerden de AI-features van deze specifieke klant?” zonder door globale telemetrie te moeten spitten. In Langfuse filter je op het metadata-veld tenant_id. Tegen je llm_traces-tabel:

# Gemiddelde latentie voor tenant 847 vs het globale mediaan over de afgelopen 7 dagen
tenant_p50 = LlmTrace
  .where(tenant_id: 847, created_at: 7.days.ago..)
  .pluck(:duration_ms)
  .sort
  .then { |ms| ms[ms.size / 2] }

global_p50 = LlmTrace
  .where(created_at: 7.days.ago..)
  .pluck(:duration_ms)
  .sort
  .then { |ms| ms[ms.size / 2] }

Wanneer een klant meldt dat “de AI vandaag traag is voor ons,” voer je deze query uit, bevestig je dat hun p50 3x het globale mediaan is, en heb je nu een feit in plaats van een hypothese. Daarna filter je hun traces op promptversie en model om te isoleren waar de regressie begon. Zonder Rails LLM observability begint dat onderzoek met “laten we alle traces bekijken en zien of ik een patroon kan vinden” — wat een uur muurkloktijd kost voordat je ook maar een theorie hebt.

De per-tenant kostensamenvoeging van deze traces vloeit natuurlijk in het facturerings- en quotasysteem dat ik bouwde in de LLM-kostentraceringspost.

Het Ding Dat Mensen Altijd Struikelt

De meest voorkomende fout die ik zie wanneer teams Rails LLM observability toevoegen, is het behandelen ervan als een nakomeling in plaats van een dag-één-zorg. Ze instrumenteren LLM-aanroepen pas na het eerste productie-incident, wanneer het spoor al koud is.

Bedraat de instrumentatie voordat je de eerste AI-feature lanceert. Voeg de llm_traces-tabel en de subscriber toe in dezelfde pull request die de eerste LLM-aanroep toevoegt. De kosten zijn twee bestanden en één migratie. De opbrengst is dat wanneer je CEO op een dinsdagmiddag vraagt waarom de AI-feature traag is, je zeven dagen tracedata achter je hebt in plaats van nul.

De regressie van de 4,2-seconden-samenvatter waarmee ik opende, duurde veertig minuten om te diagnosticeren met goede traces. De grondoorzaak was een derde-partij OCR-dienst die twee pagina’s metadata toevoegde aan geëxtraheerde tekst voordat die naar de samenvatter werd gestuurd — het invoertokencount was stilletjes verdrievoudigd. Ik vond het in vier queries. Zonder de traces zou ik zijn begonnen met het lezen van de Anthropic-statuspagina, dan het model hebben beschuldigd, en uiteindelijk naar de OCR-uitvoer hebben gestaard totdat ik de metadata toevallig had opgemerkt. Twee dagen debuggen versus veertig minuten. Dat is waarvoor Rails LLM observability is.

Veelgestelde Vragen

Wat is Rails LLM observability en waarom verschilt het van standaard Rails-monitoring?

Rails LLM observability is instrumentatie specifiek voor je LLM-aanroeplagen — welke operaties werden uitgevoerd, welke modellen ze verwerkten, hoeveel tokens ze verbruikten, hoe lang elke aanroep duurde en wat er misging bij een fout. Standaard Rails-monitoring (OpenTelemetry, Datadog APM) ziet LLM-aanroepen als ondoorzichtige HTTP-requests: een 200-OK in 3 seconden. LLM observability ziet “de document.summarize-operatie op claude-sonnet-5 met prompt v14 verwerkte 9.200 invoertokens in 3,2 seconden voor $0,046.” Het actiegerichte verschil is aanzienlijk bij het debuggen van een latentieregressie of een kostenspiek.

Waarom Langfuse en niet Datadog of Honeycomb voor LLM-tracing?

Langfuse spreekt LLM natively — zijn datamodel heeft native concepten voor traces, generaties, promptversies en evaluatiescores. Datadog en Honeycomb zijn uitstekende generieke observability-platforms, maar je zou LLM-specifieke dashboardprimitieven zelf moeten bouwen. Langfuse is ook open source en zelf te hosten, wat belangrijk is voor teams met gegevenslocatievereisten. De ActiveSupport::Notifications-instrumentatielaag in dit bericht is backend-agnostisch — het inwisselen van LLM::LangfuseSubscriber voor een Honeycomb- of Datadog-subscriber kost een middag.

Hoe volg ik promptversiewijzigingen automatisch bij?

Hash de promptinhoud met Digest::SHA1.hexdigest(prompt_string)[0, 8]. Wanneer de prompt verandert, verandert de hash, verschijnt de nieuwe versietag automatisch in traces en kun je metrics over versies vergelijken zonder een stringconstante te updaten. Het compromis is dat versiestrings niet leesbaar zijn voor mensen (a3f2b1c0 vs v14), maar ze kunnen niet verouderen — de hash wordt altijd afgeleid van de daadwerkelijke inhoud die in gebruik is.

Moet ik LLM-aanroepen ook in development en staging instrumenteren?

Ja, in elke omgeving. Development-traces vangen latentieregressies op voordat ze productie bereiken, en je kunt kostgegevens uit development gebruiken om productieuitgaven te schatten voordat een feature wordt gelanceerd. Wijs LANGFUSE_HOST naar een lokale Langfuse-instantie, of schrijf naar een aparte llm_traces_development-tabel. Houd development- en productiedata in aparte projecten in Langfuse om development-ruis niet te mengen in je productie-dashboards.

AI-features in productie lanceren zonder observability is hetzelfde als Rails deployen zonder logs. Na negentien jaar productie-Rails en drie jaar LLMs integreren in productieapplicaties, is Rails LLM observability het eerste wat ik aansluit voordat een AI-feature live gaat. TTB Software helpt teams observeerbare, productie-waardige AI-features te bouwen in Rails — het soort dat niet twee weken stil degradeert voordat een klant het merkt. Als je AI-laag nu een zwarte doos is, kunnen we dat verhelpen.

#rails-llm-observability #langfuse-rails-integration #rails-llm-tracing #rails-activesupport-notifications-llm #rails-ai-monitoring-production #rails-llm-production-debugging

Related Articles

Laatste sectie. Bel dan alsjeblieft.

Het is een telefoongesprek. Erger dan dat kan het niet worden.

Geen discovery-deck. Geen 45-minuten "kwalificatiegesprek." 30 minuten, jouw probleem, mijn mening. Als we een fit zijn weet je dat in minuut 12.

Directe lijn — Roger neemt zelf op
+31 6 5123 6132
Ma–vr, 09:00–18:00 CET · Nu beschikbaar

OF
info@ttb.software