Skip to content

Commit 6eae94e

Browse files
committed
Release v0.1.11: Add support for stripping LLM trailing dot truncation pattern
Introduces new content cleaning logic in Layer 1 to handle JSON responses truncated when an LLM hits `max_output_tokens` and pads the remainder with dots (e.g., Gemini API). **Key Features:** - **Trailing Dots Stripping**: New public function `ContentCleaning.strip_trailing_dots/1` detects 10+ consecutive trailing dots, ignoring whitespace interspersed between them. - **Pipeline Integration**: Applied in Layer 1 pipeline before encoding normalization. - **Real-World Fix**: Addresses output from Gemini API when structured JSON output is abruptly terminated by token limits. **Details:** - New module functions implemented for robust Grapheme-based detection and counting. - Preserves legitimate ellipsis (`...`) within string values. - Updated CHANGELOG, README, and related documentation files. - Test coverage increased with dedicated unit and integration tests for this pattern.
1 parent 28c3979 commit 6eae94e

9 files changed

Lines changed: 534 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.1.11] - 2025-12-03
11+
12+
### Added
13+
- **Trailing dots truncation handling**: Layer 1 now detects and strips trailing dots from truncated JSON content
14+
- Pattern: `{"key": "value.............``{"key": "value`
15+
- Handles Gemini API `max_output_tokens` truncation pattern where remaining tokens are filled with dots
16+
- Threshold: 10+ consecutive trailing dots trigger cleanup (preserves legitimate ellipsis)
17+
- Works with mixed whitespace/newlines between dots
18+
- **Test status**: ✅ 14/14 new tests passing
19+
- **New public API function**: `ContentCleaning.strip_trailing_dots/1` for direct access to trailing dots removal
20+
21+
### Technical Details
22+
- **Module**: Enhanced `JsonRemedy.Layer1.ContentCleaning`
23+
- **New functions**:
24+
- `strip_trailing_dots/1` - Public API for trailing dots removal
25+
- `strip_trailing_dots_internal/1` - Pipeline integration function
26+
- `find_trailing_dots_start/1` - Position detection
27+
- `find_dots_at_end/1` - End-of-string dot detection
28+
- `count_trailing_dots/4` - Dot counting with whitespace tolerance
29+
- **Integration**: Runs in Layer 1 pipeline after content extraction, before encoding normalization
30+
- **Performance**: Grapheme-based processing for proper UTF-8 handling
31+
32+
### Documentation
33+
- **Test case reference**: Based on real Gemini API response in `docs/20251203/test_case/gemini_max_tokens_trailing_dots.md`
34+
- **Layer 1 enhancements**: Added trailing dots stripping to content cleaning pipeline
35+
1036
## [0.1.10] - 2025-10-28
1137

1238
### Fixed
@@ -353,7 +379,8 @@ This is a **100% rewrite** - all previous code has been replaced with the new la
353379
- Minimal memory overhead (< 8KB for repairs)
354380
- All operations pass performance thresholds
355381

356-
[Unreleased]: https://github.com/nshkrdotcom/json_remedy/compare/v0.1.10...HEAD
382+
[Unreleased]: https://github.com/nshkrdotcom/json_remedy/compare/v0.1.11...HEAD
383+
[0.1.11]: https://github.com/nshkrdotcom/json_remedy/compare/v0.1.10...v0.1.11
357384
[0.1.10]: https://github.com/nshkrdotcom/json_remedy/compare/v0.1.9...v0.1.10
358385
[0.1.9]: https://github.com/nshkrdotcom/json_remedy/compare/v0.1.8...v0.1.9
359386
[0.1.8]: https://github.com/nshkrdotcom/json_remedy/compare/v0.1.7...v0.1.8

CLAUDE.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,18 @@ JsonRemedy - A practical, multi-layered JSON repair library for Elixir that inte
2222
- ✅ Comment stripping (// and /* */)
2323
- ✅ Wrapper text extraction (HTML, prose)
2424
- ✅ Encoding normalization
25+
- ✅ Trailing dots truncation (`test/unit/layer1_trailing_dots_test.exs`) *(v0.1.11+)*
2526

26-
**Implementation Status**: **TDD COMPLETE**
27-
- ✅ Core functionality implemented (21/21 unit tests passing)
27+
**Implementation Status**: **TDD COMPLETE**
28+
- ✅ Core functionality implemented (35/35 unit tests passing)
2829
- ✅ LayerBehaviour contract fully implemented
2930
- ✅ All required callbacks: `process/2`, `supports?/1`, `priority/0`, `name/0`, `validate_options/1`
30-
- ✅ Public API functions: `strip_comments/1`, `extract_json_content/1`, `normalize_encoding/1`
31+
- ✅ Public API functions: `strip_comments/1`, `extract_json_content/1`, `normalize_encoding/1`, `strip_trailing_dots/1`
3132
- ✅ Context-aware processing that preserves string content
3233
- ✅ Performance tests passing (4/4 tests, all functions under performance thresholds)
3334
- ✅ Code quality checks passing (Credo, mix format)
3435
- ✅ Type specifications and documentation complete
36+
-**Trailing dots handling** (v0.1.11): Detects and strips Gemini max_output_tokens truncation pattern
3537

3638
### Phase 3: Layer 2 - Structural Repair ✅ COMPLETED
3739
**Goal**: Fix missing/extra delimiters using state machine for context tracking
@@ -242,7 +244,7 @@ State machine approach preserves JSON semantics:
242244
- **✅ Production Ready**: Core 3-layer pipeline battle-tested and robust
243245

244246
### 📊 **Current Statistics**
245-
- **Total Test Suite**: 449 tests passing, 0 failures (36 excluded)
247+
- **Total Test Suite**: 660 tests passing, 0 failures (63 excluded)
246248
- **Critical Tests**: 82 tests passing, 0 failures (19 excluded)
247249
- **Layer 4 Tests**: 201 tests passing, 0 failures
248250
- **Unit Tests**: 100+ tests across all layers
@@ -266,10 +268,10 @@ The core 4-layer repair pipeline is **production-ready** and **battle-tested**:
266268
- **Type Safety Excellence**: Zero Dialyzer warnings with comprehensive type checking
267269
- **Defensive Programming**: Robust nil input handling throughout the codebase
268270
- **Code Quality Standards**: Zero warnings, comprehensive documentation, production-ready code
269-
- **Test Coverage Excellence**: 449 total tests with 100% success rate
271+
- **Test Coverage Excellence**: 660 total tests with 100% success rate
270272

271273
## Key Commands
272-
- `mix test` - Run all tests (449 tests, 0 failures)
274+
- `mix test` - Run all tests (660 tests, 0 failures)
273275
- `mix test test/critical` - Run critical test suite (82 tests)
274276
- `mix test test/layer4` - Run Layer 4 validation tests (201 tests)
275277
- `mix test test/unit/layer1_content_cleaning_test.exs` - Run Layer 1 tests

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ Malformed JSON is everywhere in real-world systems:
4545
{"status": "processing", "results": [{"id": 1, "name": "Alice"
4646
```
4747

48+
```text
49+
// Gemini API max_output_tokens truncation (fills rest with dots)
50+
{"title": "Report", "content": "Analysis of.............................
51+
```
52+
4853
```text
4954
// Human input with common mistakes
5055
{name: Alice, "age": 30, "scores": [95 87 92], active: true,}
@@ -67,9 +72,12 @@ Runs **before** the main layer pipeline to handle complex patterns that would ot
6772
### 🧹 **Content Cleaning (Layer 1)**
6873
- **Code fences**: ````json ... ```` → clean JSON
6974
- **Comments**: `// line comments` and `/* block comments */` → removed
70-
- **Hash comments**: `# python-style comments` → removed
75+
- **Hash comments**: `# python-style comments` → removed
7176
- **Wrapper text**: Extracts JSON from prose, HTML tags, API responses
7277
- **Trailing text removal**: `[{"id": 1}]\n1 Volume(s) created``[{"id": 1}]` *(v0.1.3+)*
78+
- **Trailing dots truncation**: `{"key": "val............``{"key": "val` *(v0.1.11+)*
79+
- Handles Gemini API `max_output_tokens` truncation pattern
80+
- Strips 10+ consecutive dots while preserving legitimate ellipsis
7381
- **Encoding normalization**: UTF-8 handling and cleanup
7482

7583
### 🏗️ **Structural Repairs (Layer 2)**
@@ -160,7 +168,7 @@ Add JsonRemedy to your `mix.exs`:
160168
```elixir
161169
def deps do
162170
[
163-
{:json_remedy, "~> 0.1.10"}
171+
{:json_remedy, "~> 0.1.11"}
164172
]
165173
end
166174
```
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Read the test case at docs/20251203/test_case/gemini_max_tokens_trailing_dots.md - this is real broken JSON from Gemini API when it hits max_output_tokens and fills the rest with dots.
2+
3+
Tasks:
4+
1. Create a test case in the test suite for this trailing dots truncation pattern
5+
2. Devise a plan to enhance JsonRemedy to handle this (strip trailing dots, repair truncated JSON)
6+
3. Implement the fix with a working example
7+
4. Bump mix.exs version x.y.z -> x.y.(z+1)
8+
5. Add CHANGELOG.md entry for the new version dated 2025-12-03
9+
6. Update README.md to document this capability
10+
7. Update other docs as needed
11+
12+
Success criteria - ALL must pass:
13+
- mix compile --warnings-as-errors
14+
- mix test
15+
- mix dialyzer
16+
- mix credo --strict
17+
18+
Do not stop until all criteria pass.

docs/20251203/test_case/gemini_max_tokens_trailing_dots.md

Lines changed: 71 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Task: Handle Gemini Max Tokens Trailing Dots Truncation
2+
3+
## Context
4+
5+
See `gemini_max_tokens_trailing_dots.md` in this directory for a real-world test case where Gemini API hits `max_output_tokens` and fills the remaining output with dots (`...`) instead of completing the JSON.
6+
7+
The JSON is cut off mid-object like:
8+
```json
9+
{
10+
"excerpt": "- Weight loss.......................................................................................
11+
```
12+
13+
And continues with 14,192 dots until the response ends.
14+
15+
## Requirements
16+
17+
1. **Create a test case** in the test suite based on the truncated JSON in `gemini_max_tokens_trailing_dots.md`
18+
19+
2. **Enhance JsonRemedy** to detect and handle this pattern:
20+
- Detect trailing dots (`.{3,}` at end of string)
21+
- Strip trailing dots
22+
- Attempt to repair the truncated JSON (close open strings, arrays, objects)
23+
- Return the repaired JSON with as much valid data as possible
24+
25+
3. **Create a working example** demonstrating the fix
26+
27+
4. **Bump version** in `mix.exs` (increment patch version x.y.z -> x.y.(z+1))
28+
29+
5. **Update CHANGELOG.md** with entry for the new version dated 2025-12-03:
30+
- Describe the new trailing dots truncation handling feature
31+
32+
6. **Update README.md** if needed to document this capability
33+
34+
7. **Update any other docs** as needed
35+
36+
## Success Criteria
37+
38+
- [ ] No compilation warnings
39+
- [ ] All tests pass (`mix test`)
40+
- [ ] Dialyzer passes (`mix dialyzer`)
41+
- [ ] Credo clean (`mix credo --strict`)
42+
43+
## Implementation Notes
44+
45+
- The dots appear when Gemini hits token limits during structured output
46+
- The truncation can happen mid-string, mid-array, or mid-object
47+
- Priority is recovering as much valid data as possible
48+
- The `citations` array in the test case is incomplete - we want to preserve the citations that ARE complete

0 commit comments

Comments
 (0)