mirror of
https://github.com/discourse/discourse.git
synced 2026-08-04 10:39:43 +08:00
Reported at https://meta.discourse.org/t/translation-silently-truncated-when-json-stream-parsing-breaks-no-error-raised/407251 ## The bug Some providers stream structured output whose string values were unescaped by an outer JSON parse, so real newlines appear inside string values. When that happened, `JsonStreamingTracker` had two failure modes: - It marked the stream broken and `StructuredOutput` fell back to `BestEffortJsonParser`, whose extraction regex (`[^"]+`) cut the value at the first escaped quote and left `\n` sequences as literal text. A 2,000-char translation could come back as ~50 chars, cut right before the first quoted word — exactly what the report shows. - Its escape-and-resume hack (`String#dump` + buffer-growth offset) miscomputed the resume index whenever the chunk contained non-ASCII, quotes, or backslashes, silently duplicating or corrupting content **without ever marking the stream broken**. Testing a realistic corrupted payload across chunk sizes 1–60: 31 produced the truncated fallback, 27 produced silent corruption, 1 raised, and only 1 came out correct. Either way the result was persisted as a successful translation with nothing in the logs. ## The fix Replace the hand-rolled parsing with two gems and keep only glue: - **json_completer** (pure Ruby): `JsonStreamingTracker` now feeds the cumulative buffer — with control characters re-escaped — to an incremental, truncation-tolerant parser and notifies consumers of changed keys. The corrupted payloads above stream correctly at every chunk size, so the broken-stream path is only reached for responses that aren't JSON at all. - **smarter_json**: `BestEffortJsonParser` becomes a three-attempt chain (strict-with-completion → control-chars re-escaped → lenient) covering the quirk shapes the old regexes handled: single quotes, unquoted keys, markdown fences, prose-wrapped JSON. This deletes the vendored 668-line SAX parser, the resume hack, and all manual regex extraction (net −694 lines), and adds a log warning whenever a response falls back to best-effort parsing. ## Behavior changes - Scalars now stream progressively: mid-stream `read_buffered_property(:number)` returns the digits buffered so far instead of `nil`. Consumers act on final values, so this only affects mid-stream reads. - Arrays of objects stream partial objects mid-stream instead of returning `nil` until finish. - A trailing comma in an array reads as a `nil` placeholder slot until the next element arrives. - Partial tool calls surface a few more progressive updates (the openai endpoint spec count moved 128 → 134); values still only ever grow. ## Tests - Regression specs for the report: unescaped control characters with escaped quotes/emoji streamed across chunk boundaries, fenced + unescaped responses, truncated JSON, numeric casting. - 916 examples green across `completions/`, `translation/`, `modules/ai_helper/`, and `utils/`.
317 lines
7.9 KiB
Ruby
Vendored
317 lines
7.9 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
ruby "~> 3.4"
|
|
|
|
source "https://rubygems.org"
|
|
# if there is a super emergency and rubygems is playing up, try
|
|
#source 'http://production.cf.rubygems.org'
|
|
|
|
gem "bootsnap", require: false, platform: :mri
|
|
|
|
gem "actionmailer", "~> 8.0.0"
|
|
gem "actionpack", "~> 8.0.0"
|
|
gem "actionview", "~> 8.0.0"
|
|
gem "activemodel", "~> 8.0.0"
|
|
gem "activerecord", "~> 8.0.0"
|
|
gem "activesupport", "~> 8.0.0"
|
|
gem "railties", "~> 8.0.0"
|
|
|
|
gem "propshaft"
|
|
gem "json"
|
|
|
|
# this will eventually be added to rails,
|
|
# allows us to precompile all our templates in the app server master
|
|
gem "actionview_precompiler", require: false
|
|
|
|
gem "discourse-seed-fu"
|
|
|
|
gem "mail"
|
|
gem "mini_mime"
|
|
gem "mini_suffix"
|
|
|
|
# NOTE: hiredis-client is recommended for high performance use of Redis
|
|
# however a recent attempt at an upgrade lead to https://meta.discourse.org/t/rebuild-error/375387
|
|
# for now we are sticking with the socked based implementation that is not sensitive to this issue
|
|
# gem "hiredis-client"
|
|
gem "redis"
|
|
|
|
# This is explicitly used by Sidekiq and is an optional dependency.
|
|
# We tell Sidekiq to use the namespace "sidekiq" which triggers this
|
|
# gem to be used. There is no explicit dependency in sidekiq cause
|
|
# redis namespace support is optional
|
|
# We already namespace stuff in DiscourseRedis, so we should consider
|
|
# just using a single implementation in core vs having 2 namespace implementations
|
|
gem "redis-namespace"
|
|
|
|
# NOTE: AM serializer gets a lot slower with recent updates
|
|
# we used an old branch which is the fastest one out there
|
|
# are long term goal here is to fork this gem so we have a
|
|
# better maintained living fork
|
|
gem "active_model_serializers", "~> 0.8.3"
|
|
|
|
gem "http_accept_language", require: false
|
|
|
|
gem "discourse-fonts", require: "discourse_fonts"
|
|
gem "discourse-emojis", require: "discourse_emojis"
|
|
gem "discourse_math_bundle"
|
|
|
|
gem "message_bus"
|
|
|
|
gem "rails_multisite"
|
|
|
|
gem "fastimage"
|
|
|
|
gem "aws-sdk-s3", require: false
|
|
gem "aws-sdk-sns", require: false
|
|
gem "aws-sdk-sts", require: false
|
|
gem "aws-sdk-mediaconvert", require: false
|
|
gem "aws-sdk-bedrockruntime", require: false
|
|
gem "excon"
|
|
gem "unf", require: false
|
|
|
|
gem "email_reply_trimmer"
|
|
|
|
gem "image_optim"
|
|
gem "multi_json"
|
|
gem "mustache"
|
|
gem "liquid", "5.12.0"
|
|
gem "nokogiri"
|
|
gem "loofah"
|
|
gem "css_parser", require: false
|
|
|
|
gem "omniauth"
|
|
gem "omniauth-facebook"
|
|
gem "omniauth-twitter"
|
|
gem "omniauth-github"
|
|
|
|
gem "omniauth-oauth2", require: false
|
|
|
|
gem "omniauth-google-oauth2"
|
|
|
|
gem "oj"
|
|
|
|
gem "pg"
|
|
gem "mini_sql"
|
|
gem "pry-rails", require: false
|
|
gem "rtlcss", require: false
|
|
gem "messageformat-wrapper", require: false
|
|
gem "rake"
|
|
|
|
gem "thor", require: false
|
|
gem "diffy", require: false
|
|
gem "rinku"
|
|
gem "sidekiq", ">= 7.3.10" # ensuring it won't get downgraded to accomodate a connection_pool upgrade
|
|
gem "mini_scheduler"
|
|
|
|
gem "mini_racer"
|
|
|
|
gem "highline", require: false
|
|
|
|
# TODO: upgrade to Rack 3 now that Unicorn has been removed
|
|
gem "rack", "< 3"
|
|
|
|
gem "rack-protection" # security
|
|
gem "cbor", require: false
|
|
gem "cose", require: false
|
|
gem "addressable"
|
|
gem "json_schemer"
|
|
|
|
gem "net-smtp", require: false
|
|
gem "net-pop", require: false
|
|
gem "digest", require: false
|
|
|
|
gem "goldiloader"
|
|
|
|
group :test do
|
|
gem "capybara", require: false
|
|
gem "webmock", require: false
|
|
gem "simplecov", require: false
|
|
gem "test-prof"
|
|
gem "rails-dom-testing", require: false
|
|
gem "minio_runner", require: false
|
|
gem "capybara-playwright-driver"
|
|
gem "puma", require: false
|
|
end
|
|
|
|
group :test, :development do
|
|
gem "rspec"
|
|
gem "listen", require: false
|
|
gem "certified", require: false
|
|
gem "fabrication", require: false
|
|
gem "mocha", require: false
|
|
|
|
gem "rb-fsevent", require: RUBY_PLATFORM =~ /darwin/i ? "rb-fsevent" : false
|
|
|
|
gem "rspec-rails"
|
|
|
|
gem "shoulda-matchers", require: false
|
|
gem "rspec-html-matchers"
|
|
gem "debug", ">= 1.0.0", require: "debug/prelude"
|
|
gem "rubocop-discourse", require: false
|
|
gem "parallel_tests"
|
|
|
|
gem "rswag-specs"
|
|
|
|
gem "annotaterb"
|
|
|
|
gem "syntax_tree"
|
|
|
|
gem "rspec-multi-mock"
|
|
end
|
|
|
|
group :development do
|
|
gem "ruby-prof", require: false, platform: :mri
|
|
gem "bullet", require: !!ENV["BULLET"]
|
|
gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"]
|
|
gem "yaml-lint"
|
|
gem "yard"
|
|
gem "ruby-lsp", require: false
|
|
gem "ruby-lsp-rails", require: false
|
|
gem "ruby-lsp-rspec", require: false
|
|
end
|
|
|
|
if ENV["ALLOW_DEV_POPULATE"] == "1"
|
|
gem "discourse_dev_assets"
|
|
gem "faker"
|
|
else
|
|
group :development, :test do
|
|
gem "discourse_dev_assets"
|
|
gem "faker"
|
|
end
|
|
end
|
|
|
|
# this is an optional gem, it provides a high performance replacement
|
|
# to String#blank? a method that is called quite frequently in current
|
|
# ActiveRecord, this may change in the future
|
|
gem "fast_blank", platform: :ruby
|
|
|
|
# this provides a very efficient lru cache
|
|
gem "lru_redux"
|
|
|
|
gem "htmlentities", require: false
|
|
|
|
# IMPORTANT: mini profiler monkey patches, so it better be required last
|
|
# If you want to amend mini profiler to do the monkey patches in the railties
|
|
# we are open to it. by deferring require to the initializer we can configure discourse installs without it
|
|
|
|
gem "rack-mini-profiler", require: ["enable_rails_patches"]
|
|
|
|
gem "pitchfork", require: false
|
|
|
|
# Used by discourse-prometheus to collect socket queue stats.
|
|
# Was previously a transitive dependency of the unicorn gem.
|
|
gem "raindrops", require: false, platform: :ruby
|
|
|
|
gem "rbtrace", require: false, platform: :mri
|
|
|
|
# required for feed importing and embedding
|
|
gem "ruby-readability", require: false
|
|
|
|
# rss gem is a bundled gem from Ruby 3 onwards
|
|
gem "rss", require: false
|
|
|
|
gem "stackprof", require: false, platform: :mri
|
|
gem "memory_profiler", require: false, platform: :mri
|
|
|
|
gem "cppjieba_rb", require: false
|
|
|
|
gem "lograge", require: false
|
|
gem "logstash-event", require: false
|
|
gem "logster"
|
|
|
|
# A fork of sassc with dart-sass support
|
|
gem "sassc-embedded"
|
|
|
|
gem "rotp", require: false
|
|
|
|
gem "rqrcode"
|
|
|
|
gem "rubyzip", require: false
|
|
|
|
gem "landlock", require: false
|
|
|
|
gem "sshkey", require: false
|
|
|
|
gem "rchardet", require: false
|
|
gem "lz4-ruby", require: false, platform: :ruby
|
|
|
|
gem "sanitize"
|
|
|
|
if ENV["IMPORT"] == "1"
|
|
gem "mysql2"
|
|
gem "redcarpet"
|
|
|
|
# NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one
|
|
gem "sqlite3", "~> 1.3", ">= 1.3.13"
|
|
gem "ruby-bbcode-to-md", git: "https://github.com/nlalonde/ruby-bbcode-to-md"
|
|
gem "reverse_markdown"
|
|
gem "tiny_tds"
|
|
gem "csv"
|
|
end
|
|
|
|
group :generic_import, optional: true do
|
|
gem "sqlite3"
|
|
gem "redcarpet"
|
|
end
|
|
|
|
gem "web-push"
|
|
gem "colored2", require: false
|
|
gem "maxminddb"
|
|
|
|
gem "rails_failover", require: false
|
|
|
|
gem "faraday"
|
|
gem "faraday-retry"
|
|
|
|
# workaround for faraday-net_http, see
|
|
# https://github.com/ruby/net-imap/issues/16#issuecomment-803086765
|
|
gem "net-http"
|
|
|
|
# Workaround until Ruby ships with cgi version 0.3.6 or higher.
|
|
gem "cgi", ">= 0.3.6", require: false
|
|
|
|
gem "tzinfo-data"
|
|
gem "csv", require: false
|
|
|
|
# dependencies for the automation plugin
|
|
gem "iso8601"
|
|
gem "rrule"
|
|
|
|
group :migrations, optional: true do
|
|
gem "migrations-core", path: "migrations/core"
|
|
gem "migrations-tooling", path: "migrations/tooling"
|
|
gem "migrations-converters", path: "migrations/converters"
|
|
gem "migrations-importer", path: "migrations/importer"
|
|
end
|
|
|
|
gem "dry-initializer", "~> 3.1"
|
|
|
|
gem "parallel"
|
|
gem "tty-prompt", require: false
|
|
|
|
# for discourse-zendesk-plugin
|
|
gem "inflection", require: false
|
|
gem "multipart-post", require: false
|
|
gem "faraday-multipart", require: false
|
|
gem "zendesk_api", require: false
|
|
|
|
# for discourse-subscriptions
|
|
gem "stripe", require: false
|
|
|
|
# for discourse-code-review
|
|
gem "sawyer", require: false
|
|
gem "octokit", require: false
|
|
|
|
# for discourse-ai
|
|
gem "tokenizers", require: false
|
|
gem "tiktoken_ruby", require: false
|
|
gem "smarter_json", require: false
|
|
gem "json_completer", require: false
|
|
gem "discourse_ai-tokenizers", require: false
|
|
gem "ed25519" # TODO: remove this as existing ssl gem should handle this
|
|
gem "Ascii85", require: false
|
|
gem "ruby-rc4", require: false
|
|
gem "hashery", require: false
|
|
gem "ttfunk", require: false
|
|
gem "afm", require: false
|
|
gem "pdf-reader", require: false
|