performance tests with json repair and cut handling

This commit is contained in:
ValueOn AG 2026-01-06 17:18:51 +01:00
parent c40d5e5133
commit e331c7fbce
11 changed files with 1129 additions and 7294 deletions

View file

@ -278,11 +278,13 @@ class JsonContinuationContexts(BaseModel):
- hierarchyContext: Full structure from root to cut WITHOUT budget limitations (for internal use)
- hierarchyContextForPrompt: Full structure from root to cut WITH budget limitations (for prompts)
- completePart: Valid JSON with all structures properly closed
- jsonParsingSuccess: True if completePart is valid parseable JSON
"""
overlapContext: str = Field(description="The innermost object/array element containing the cut point (for merging)")
hierarchyContext: str = Field(description="Full structure from root to cut WITHOUT budget limitations (for internal use)")
hierarchyContextForPrompt: str = Field(description="Full structure from root to cut WITH budget limitations (for prompts)")
completePart: str = Field(description="Valid JSON with all structures properly closed")
jsonParsingSuccess: bool = Field(default=False, description="True if completePart is valid parseable JSON")
class SectionPromptArgs(BaseModel):

View file

@ -1,901 +0,0 @@
================================================================================
JSON MERGE OPERATION #1
================================================================================
Timestamp: 2026-01-05T22:51:14.407688
INPUT:
Accumulated length: 31737 chars
New Fragment length: 32830 chars
Accumulated: 409 lines (showing first 5 and last 5)
{
"elements": [
{
"type": "table",
"content": {
... (399 lines omitted) ...
[37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573, 37579],
[37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, 37691],
[37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831],
[37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, 37957],
[37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039
New Fragment: 406 lines (showing first 5 and last 5)
```json
{
"elements": [
{
"type": "table",
... (396 lines omitted) ...
[79873, 79889, 79901, 79903, 79907, 79939, 79943, 79967, 79973, 79979],
[79987, 79997, 79999, 80021, 80039, 80051, 80071, 80077, 80107, 80111],
[80141, 80147, 80149, 80153, 80167, 80173, 80177, 80191, 80207, 80209],
[80221, 80231, 80233, 80239, 80251, 80263, 80273, 80279, 80287, 80309],
[80317, 80329, 80341, 80347, 80363, 80369,
Normalized Accumulated (31737 chars)
(showing first 5 and last 5 of 409 lines)
{
"elements": [
{
"type": "table",
"content": {
... (399 lines omitted) ...
[37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573, 37579],
[37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, 37691],
[37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831],
[37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, 37957],
[37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039
Normalized New Fragment (32822 chars)
(showing first 5 and last 5 of 405 lines)
{
"elements": [
{
"type": "table",
"content": {
... (395 lines omitted) ...
[79873, 79889, 79901, 79903, 79907, 79939, 79943, 79967, 79973, 79979],
[79987, 79997, 79999, 80021, 80039, 80051, 80071, 80077, 80107, 80111],
[80141, 80147, 80149, 80153, 80167, 80173, 80177, 80191, 80207, 80209],
[80221, 80231, 80233, 80239, 80251, 80263, 80273, 80279, 80287, 80309],
[80317, 80329, 80341, 80347, 80363, 80369,
STEP: PHASE 1
Description: Finding overlap between JSON strings
⏳ In progress...
Overlap Detection (string):
Overlap length: 0
⚠️ No overlap detected - appending all
⚠️ NO OVERLAP FOUND - This indicates iterations should stop
Closing JSON and returning final result
Closed JSON (31743 chars):
==============================================================================
{
"elements": [
{
"type": "table",
"content": {
"headers": ["Spalte1", "Spalte2", "Spalte3", "Spalte4", "Spalte5", "Spalte6", "Spalte7", "Spalte8", "Spalte9", "Spalte10"],
"rows": [
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29],
[31, 37, 41, 43, 47, 53, 59, 61, 67, 71],
[73, 79, 83, 89, 97, 101, 103, 107, 109, 113],
[127, 131, 137, 139, 149, 151, 157, 163, 167, 173],
[179, 181, 191, 193, 197, 199, 211, 223, 227, 229],
[233, 239, 241, 251, 257, 263, 269, 271, 277, 281],
[283, 293, 307, 311, 313, 317, 331, 337, 347, 349],
[353, 359, 367, 373, 379, 383, 389, 397, 401, 409],
[419, 421, 431, 433, 439, 443, 449, 457, 461, 463],
[467, 479, 487, 491, 499, 503, 509, 521, 523, 541],
[547, 557, 563, 569, 571, 577, 587, 593, 599, 601],
[607, 613, 617, 619, 631, 641, 643, 647, 653, 659],
[661, 673, 677, 683, 691, 701, 709, 719, 727, 733],
[739, 743, 751, 757, 761, 769, 773, 787, 797, 809],
[811, 821, 823, 827, 829, 839, 853, 857, 859, 863],
[877, 881, 883, 887, 907, 911, 919, 929, 937, 941],
[947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013],
[1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069],
[1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151],
[1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223],
[1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291],
[1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373],
[1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451],
[1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511],
[1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583],
[1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657],
[1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733],
[1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811],
[1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889],
[1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987],
[1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053],
[2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129],
[2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213],
[2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287],
[2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357],
[2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423],
[2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531],
[2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617],
[2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687],
[2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741],
[2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819],
[2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903],
[2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999],
[3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079],
[3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181],
[3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257],
[3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331],
[3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413],
[3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511],
[3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571],
[3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643],
[3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727],
[3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821],
[3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907],
[3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989],
[4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057],
[4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139],
[4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231],
[4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297],
[4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409],
[4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493],
[4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583],
[4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657],
[4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751],
[4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831],
[4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937],
[4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003],
[5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087],
[5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179],
[5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279],
[5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387],
[5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443],
[5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521],
[5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639],
[5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693],
[5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791],
[5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857],
[5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939],
[5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053],
[6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133],
[6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221],
[6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301],
[6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367],
[6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473],
[6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571],
[6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673],
[6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761],
[6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833],
[6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917],
[6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997],
[7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103],
[7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207],
[7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297],
[7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411],
[7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499],
[7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561],
[7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643],
[7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723],
[7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829],
[7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919],
[7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017],
[8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111],
[8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219],
[8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291],
[8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387],
[8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501],
[8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597],
[8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677],
[8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741],
[8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831],
[8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929],
[8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011],
[9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109],
[9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199],
[9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283],
[9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377],
[9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439],
[9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533],
[9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631],
[9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733],
[9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811],
[9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887],
[9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007],
[10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099],
[10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177],
[10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271],
[10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343],
[10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459],
[10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567],
[10589, 10597, 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657],
[10663, 10667, 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739],
[10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859],
[10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, 10949],
[10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059],
[11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149],
[11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251],
[11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329],
[11351, 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443],
[11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527],
[11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657],
[11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777],
[11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833],
[11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, 11927, 11933],
[11939, 11941, 11953, 11959, 11969, 11971, 11981, 11987, 12007, 12011],
[12037, 12041, 12043, 12049, 12071, 12073, 12097, 12101, 12107, 12109],
[12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, 12203, 12211],
[12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289],
[12301, 12323, 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401],
[12409, 12413, 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487],
[12491, 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553],
[12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, 12641],
[12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, 12721, 12739],
[12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829],
[12841, 12853, 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923],
[12941, 12953, 12959, 12967, 12973, 12979, 12983, 13001, 13003, 13007],
[13009, 13033, 13037, 13043, 13049, 13063, 13093, 13099, 13103, 13109],
[13121, 13127, 13147, 13151, 13159, 13163, 13171, 13177, 13183, 13187],
[13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309],
[13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411],
[13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499],
[13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, 13613, 13619],
[13627, 13633, 13649, 13669, 13679, 13681, 13687, 13691, 13693, 13697],
[13709, 13711, 13721, 13723, 13729, 13751, 13757, 13759, 13763, 13781],
[13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, 13877, 13879],
[13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967],
[13997, 13999, 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081],
[14083, 14087, 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197],
[14207, 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323],
[14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, 14419],
[14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, 14503, 14519],
[14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593],
[14621, 14627, 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699],
[14713, 14717, 14723, 14731, 14737, 14741, 14747, 14753, 14759, 14767],
[14771, 14779, 14783, 14797, 14813, 14821, 14827, 14831, 14843, 14851],
[14867, 14869, 14879, 14887, 14891, 14897, 14923, 14929, 14939, 14947],
[14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073],
[15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149],
[15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259],
[15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, 15313, 15319],
[15329, 15331, 15349, 15359, 15361, 15373, 15377, 15383, 15391, 15401],
[15413, 15427, 15439, 15443, 15451, 15461, 15467, 15473, 15493, 15497],
[15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, 15601, 15607],
[15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679],
[15683, 15727, 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773],
[15787, 15791, 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881],
[15887, 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971],
[15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, 16069],
[16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, 16141, 16183],
[16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267],
[16273, 16301, 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381],
[16411, 16417, 16421, 16427, 16433, 16447, 16451, 16453, 16477, 16481],
[16487, 16493, 16519, 16529, 16547, 16553, 16561, 16567, 16573, 16603],
[16607, 16619, 16631, 16633, 16649, 16651, 16657, 16661, 16673, 16691],
[16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811],
[16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903],
[16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993],
[17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, 17077, 17093],
[17099, 17107, 17117, 17123, 17137, 17159, 17167, 17183, 17189, 17191],
[17203, 17207, 17209, 17231, 17239, 17257, 17291, 17293, 17299, 17317],
[17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, 17387, 17389],
[17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477],
[17483, 17489, 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573],
[17579, 17581, 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669],
[17681, 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783],
[17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, 17891],
[17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, 17959, 17971],
[17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059],
[18061, 18077, 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143],
[18149, 18169, 18181, 18191, 18199, 18211, 18217, 18223, 18229, 18233],
[18251, 18253, 18257, 18269, 18287, 18289, 18301, 18307, 18311, 18313],
[18329, 18341, 18353, 18367, 18371, 18379, 18397, 18401, 18413, 18427],
[18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517],
[18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637],
[18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749],
[18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, 18869, 18899],
[18911, 18913, 18917, 18919, 18947, 18959, 18973, 18979, 19001, 19009],
[19013, 19031, 19037, 19051, 19069, 19073, 19079, 19081, 19087, 19121],
[19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, 19213, 19219],
[19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319],
[19333, 19373, 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423],
[19427, 19429, 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477],
[19483, 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571],
[19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, 19699],
[19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, 19777, 19793],
[19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891],
[19913, 19919, 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991],
[19993, 19997, 20011, 20021, 20023, 20029, 20047, 20051, 20063, 20071],
[20089, 20101, 20107, 20113, 20117, 20123, 20129, 20143, 20147, 20149],
[20161, 20173, 20177, 20183, 20201, 20219, 20231, 20233, 20249, 20261],
[20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357],
[20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443],
[20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551],
[20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, 20681, 20693],
[20707, 20717, 20719, 20731, 20743, 20747, 20749, 20753, 20759, 20771],
[20773, 20789, 20807, 20809, 20849, 20857, 20873, 20879, 20887, 20897],
[20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, 20981, 20983],
[21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067],
[21089, 21101, 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169],
[21179, 21187, 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277],
[21283, 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383],
[21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, 21491],
[21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, 21559, 21563],
[21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647],
[21649, 21661, 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751],
[21757, 21767, 21773, 21787, 21799, 21803, 21817, 21821, 21839, 21841],
[21851, 21859, 21863, 21871, 21881, 21893, 21911, 21929, 21937, 21943],
[21961, 21977, 21991, 21997, 22003, 22013, 22027, 22031, 22037, 22039],
[22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123],
[22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229],
[22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307],
[22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, 22433, 22441],
[22447, 22453, 22469, 22481, 22483, 22501, 22511, 22531, 22541, 22543],
[22549, 22567, 22571, 22573, 22613, 22619, 22621, 22637, 22639, 22643],
[22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, 22721, 22727],
[22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817],
[22853, 22859, 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943],
[22961, 22963, 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029],
[23039, 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099],
[23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, 23203],
[23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, 23311, 23321],
[23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447],
[23459, 23473, 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561],
[23563, 23567, 23581, 23593, 23599, 23603, 23609, 23623, 23627, 23629],
[23633, 23663, 23669, 23671, 23677, 23687, 23689, 23719, 23741, 23743],
[23747, 23753, 23761, 23767, 23773, 23789, 23801, 23813, 23819, 23827],
[23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909],
[23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007],
[24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091],
[24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, 24151, 24169],
[24179, 24181, 24197, 24203, 24223, 24229, 24239, 24247, 24251, 24281],
[24317, 24329, 24337, 24359, 24371, 24373, 24379, 24391, 24407, 24413],
[24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, 24509, 24517],
[24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659],
[24671, 24677, 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767],
[24781, 24793, 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877],
[24889, 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977],
[24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, 25097],
[25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, 25171, 25183],
[25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303],
[25307, 25309, 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391],
[25409, 25411, 25423, 25439, 25447, 25453, 25457, 25463, 25469, 25471],
[25523, 25537, 25541, 25561, 25577, 25579, 25583, 25589, 25601, 25603],
[25609, 25621, 25633, 25639, 25643, 25657, 25667, 25673, 25679, 25693],
[25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799],
[25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913],
[25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999],
[26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, 26107, 26111],
[26113, 26119, 26141, 26153, 26161, 26171, 26177, 26183, 26189, 26203],
[26209, 26227, 26237, 26249, 26251, 26261, 26263, 26267, 26293, 26297],
[26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, 26393, 26399],
[26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497],
[26501, 26513, 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633],
[26641, 26647, 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711],
[26713, 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801],
[26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, 26891],
[26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, 26981, 26987],
[26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077],
[27091, 27103, 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211],
[27239, 27241, 27253, 27259, 27271, 27277, 27281, 27283, 27299, 27329],
[27337, 27361, 27367, 27397, 27407, 27409, 27427, 27431, 27437, 27449],
[27457, 27479, 27481, 27487, 27509, 27527, 27529, 27539, 27541, 27551],
[27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691],
[27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767],
[27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827],
[27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, 27943, 27947],
[27953, 27961, 27967, 27983, 27997, 28001, 28019, 28027, 28031, 28051],
[28057, 28069, 28081, 28087, 28097, 28099, 28109, 28111, 28123, 28151],
[28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, 28279, 28283],
[28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403],
[28409, 28411, 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499],
[28513, 28517, 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579],
[28591, 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649],
[28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, 28729],
[28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, 28817, 28837],
[28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933],
[28949, 28961, 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059],
[29063, 29077, 29101, 29123, 29129, 29131, 29137, 29147, 29153, 29167],
[29173, 29179, 29191, 29201, 29207, 29209, 29221, 29231, 29243, 29251],
[29269, 29287, 29297, 29303, 29311, 29327, 29333, 29339, 29347, 29363],
[29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443],
[29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573],
[29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671],
[29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, 29803, 29819],
[29833, 29837, 29851, 29863, 29867, 29873, 29879, 29881, 29917, 29921],
[29927, 29947, 29959, 29983, 29989, 30011, 30013, 30029, 30047, 30059],
[30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, 30133, 30137],
[30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241],
[30253, 30259, 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341],
[30347, 30367, 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469],
[30491, 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559],
[30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, 30689],
[30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, 30781, 30803],
[30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871],
[30881, 30893, 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983],
[31013, 31019, 31033, 31039, 31051, 31063, 31069, 31079, 31081, 31091],
[31121, 31123, 31139, 31147, 31151, 31153, 31159, 31177, 31181, 31189],
[31193, 31219, 31223, 31231, 31237, 31247, 31249, 31253, 31259, 31267],
[31271, 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357, 31379],
[31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, 31513],
[31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601, 31607],
[31627, 31643, 31649, 31657, 31663, 31667, 31687, 31699, 31721, 31723],
[31727, 31729, 31741, 31751, 31769, 31771, 31793, 31799, 31817, 31847],
[31849, 31859, 31873, 31883, 31891, 31907, 31957, 31963, 31973, 31981],
[31991, 32003, 32009, 32027, 32029, 32051, 32057, 32059, 32063, 32069],
[32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159, 32173],
[32183, 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257, 32261],
[32297, 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353, 32359],
[32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423, 32429],
[32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, 32531, 32533],
[32537, 32561, 32563, 32569, 32573, 32579, 32587, 32603, 32609, 32611],
[32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717, 32719],
[32749, 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831, 32833],
[32839, 32843, 32869, 32887, 32909, 32911, 32917, 32933, 32939, 32941],
[32957, 32969, 32971, 32983, 32987, 32993, 32999, 33013, 33023, 33029],
[33037, 33049, 33053, 33071, 33073, 33083, 33091, 33107, 33113, 33119],
[33149, 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211, 33223],
[33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, 33347],
[33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427, 33457],
[33461, 33469, 33479, 33487, 33493, 33503, 33521, 33529, 33533, 33547],
[33563, 33569, 33577, 33581, 33587, 33589, 33599, 33601, 33613, 33617],
[33619, 33623, 33629, 33637, 33641, 33647, 33679, 33703, 33713, 33721],
[33739, 33749, 33751, 33757, 33767, 33769, 33773, 33791, 33797, 33809],
[33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893, 33911],
[33923, 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031, 34033],
[34039, 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157, 34159],
[34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261, 34267],
[34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, 34337, 34351],
[34361, 34367, 34369, 34381, 34403, 34421, 34429, 34439, 34457, 34469],
[34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537, 34543],
[34549, 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649, 34651],
[34667, 34673, 34679, 34687, 34693, 34703, 34721, 34729, 34739, 34747],
[34757, 34759, 34763, 34781, 34807, 34819, 34841, 34843, 34847, 34849],
[34871, 34877, 34883, 34897, 34913, 34919, 34939, 34949, 34961, 34963],
[34981, 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083, 35089],
[35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, 35171],
[35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291, 35311],
[35317, 35323, 35327, 35339, 35353, 35363, 35381, 35393, 35401, 35407],
[35419, 35423, 35437, 35447, 35449, 35461, 35491, 35507, 35509, 35521],
[35527, 35531, 35533, 35537, 35543, 35569, 35573, 35591, 35593, 35597],
[35603, 35617, 35671, 35677, 35729, 35731, 35747, 35753, 35759, 35771],
[35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863, 35869],
[35879, 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969, 35977],
[35983, 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061, 36067],
[36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161, 36187],
[36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, 36277, 36293],
[36299, 36307, 36313, 36319, 36341, 36343, 36353, 36373, 36383, 36389],
[36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497, 36523],
[36527, 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587, 36599],
[36607, 36629, 36637, 36643, 36653, 36671, 36677, 36683, 36691, 36697],
[36709, 36713, 36721, 36739, 36749, 36761, 36767, 36779, 36781, 36787],
[36791, 36793, 36809, 36821, 36833, 36847, 36857, 36871, 36877, 36887],
[36899, 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947, 36973],
[36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, 37061],
[37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189, 37199],
[37201, 37217, 37223, 37243, 37253, 37273, 37277, 37307, 37309, 37313],
[37321, 37337, 37339, 37357, 37361, 37363, 37369, 37379, 37397, 37409],
[37423, 37441, 37447, 37463, 37483, 37489, 37493, 37501, 37507, 37511],
[37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573, 37579],
[37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, 37691],
[37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831],
[37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, 37957],
[37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039]]}}]}
==============================================================================
================================================================================
MERGE RESULT: ✅ SUCCESS
================================================================================
Final result length: 31743 chars
Final result (COMPLETE):
================================================================================
{
"elements": [
{
"type": "table",
"content": {
"headers": ["Spalte1", "Spalte2", "Spalte3", "Spalte4", "Spalte5", "Spalte6", "Spalte7", "Spalte8", "Spalte9", "Spalte10"],
"rows": [
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29],
[31, 37, 41, 43, 47, 53, 59, 61, 67, 71],
[73, 79, 83, 89, 97, 101, 103, 107, 109, 113],
[127, 131, 137, 139, 149, 151, 157, 163, 167, 173],
[179, 181, 191, 193, 197, 199, 211, 223, 227, 229],
[233, 239, 241, 251, 257, 263, 269, 271, 277, 281],
[283, 293, 307, 311, 313, 317, 331, 337, 347, 349],
[353, 359, 367, 373, 379, 383, 389, 397, 401, 409],
[419, 421, 431, 433, 439, 443, 449, 457, 461, 463],
[467, 479, 487, 491, 499, 503, 509, 521, 523, 541],
[547, 557, 563, 569, 571, 577, 587, 593, 599, 601],
[607, 613, 617, 619, 631, 641, 643, 647, 653, 659],
[661, 673, 677, 683, 691, 701, 709, 719, 727, 733],
[739, 743, 751, 757, 761, 769, 773, 787, 797, 809],
[811, 821, 823, 827, 829, 839, 853, 857, 859, 863],
[877, 881, 883, 887, 907, 911, 919, 929, 937, 941],
[947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013],
[1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069],
[1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151],
[1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223],
[1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291],
[1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373],
[1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451],
[1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511],
[1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583],
[1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657],
[1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733],
[1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811],
[1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889],
[1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987],
[1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053],
[2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129],
[2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213],
[2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287],
[2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357],
[2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423],
[2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531],
[2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617],
[2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687],
[2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741],
[2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819],
[2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903],
[2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999],
[3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079],
[3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181],
[3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257],
[3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331],
[3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413],
[3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511],
[3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571],
[3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643],
[3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727],
[3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821],
[3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907],
[3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989],
[4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057],
[4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139],
[4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231],
[4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297],
[4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409],
[4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493],
[4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583],
[4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657],
[4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751],
[4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831],
[4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937],
[4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003],
[5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087],
[5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179],
[5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279],
[5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387],
[5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443],
[5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521],
[5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639],
[5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693],
[5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791],
[5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857],
[5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939],
[5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053],
[6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133],
[6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221],
[6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301],
[6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367],
[6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473],
[6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571],
[6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673],
[6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761],
[6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833],
[6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917],
[6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997],
[7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103],
[7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207],
[7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297],
[7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411],
[7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499],
[7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561],
[7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643],
[7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723],
[7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829],
[7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919],
[7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017],
[8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111],
[8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219],
[8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291],
[8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387],
[8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501],
[8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597],
[8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677],
[8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741],
[8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831],
[8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929],
[8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011],
[9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109],
[9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199],
[9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283],
[9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377],
[9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439],
[9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533],
[9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631],
[9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733],
[9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811],
[9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887],
[9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007],
[10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099],
[10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177],
[10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271],
[10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343],
[10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459],
[10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567],
[10589, 10597, 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657],
[10663, 10667, 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739],
[10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859],
[10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, 10949],
[10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059],
[11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149],
[11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251],
[11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329],
[11351, 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443],
[11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527],
[11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657],
[11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777],
[11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833],
[11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, 11927, 11933],
[11939, 11941, 11953, 11959, 11969, 11971, 11981, 11987, 12007, 12011],
[12037, 12041, 12043, 12049, 12071, 12073, 12097, 12101, 12107, 12109],
[12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, 12203, 12211],
[12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289],
[12301, 12323, 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401],
[12409, 12413, 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487],
[12491, 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553],
[12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, 12641],
[12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, 12721, 12739],
[12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829],
[12841, 12853, 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923],
[12941, 12953, 12959, 12967, 12973, 12979, 12983, 13001, 13003, 13007],
[13009, 13033, 13037, 13043, 13049, 13063, 13093, 13099, 13103, 13109],
[13121, 13127, 13147, 13151, 13159, 13163, 13171, 13177, 13183, 13187],
[13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309],
[13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411],
[13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499],
[13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, 13613, 13619],
[13627, 13633, 13649, 13669, 13679, 13681, 13687, 13691, 13693, 13697],
[13709, 13711, 13721, 13723, 13729, 13751, 13757, 13759, 13763, 13781],
[13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, 13877, 13879],
[13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967],
[13997, 13999, 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081],
[14083, 14087, 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197],
[14207, 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323],
[14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, 14419],
[14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, 14503, 14519],
[14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593],
[14621, 14627, 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699],
[14713, 14717, 14723, 14731, 14737, 14741, 14747, 14753, 14759, 14767],
[14771, 14779, 14783, 14797, 14813, 14821, 14827, 14831, 14843, 14851],
[14867, 14869, 14879, 14887, 14891, 14897, 14923, 14929, 14939, 14947],
[14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073],
[15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149],
[15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259],
[15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, 15313, 15319],
[15329, 15331, 15349, 15359, 15361, 15373, 15377, 15383, 15391, 15401],
[15413, 15427, 15439, 15443, 15451, 15461, 15467, 15473, 15493, 15497],
[15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, 15601, 15607],
[15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679],
[15683, 15727, 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773],
[15787, 15791, 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881],
[15887, 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971],
[15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, 16069],
[16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, 16141, 16183],
[16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267],
[16273, 16301, 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381],
[16411, 16417, 16421, 16427, 16433, 16447, 16451, 16453, 16477, 16481],
[16487, 16493, 16519, 16529, 16547, 16553, 16561, 16567, 16573, 16603],
[16607, 16619, 16631, 16633, 16649, 16651, 16657, 16661, 16673, 16691],
[16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811],
[16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903],
[16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993],
[17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, 17077, 17093],
[17099, 17107, 17117, 17123, 17137, 17159, 17167, 17183, 17189, 17191],
[17203, 17207, 17209, 17231, 17239, 17257, 17291, 17293, 17299, 17317],
[17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, 17387, 17389],
[17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477],
[17483, 17489, 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573],
[17579, 17581, 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669],
[17681, 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783],
[17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, 17891],
[17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, 17959, 17971],
[17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059],
[18061, 18077, 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143],
[18149, 18169, 18181, 18191, 18199, 18211, 18217, 18223, 18229, 18233],
[18251, 18253, 18257, 18269, 18287, 18289, 18301, 18307, 18311, 18313],
[18329, 18341, 18353, 18367, 18371, 18379, 18397, 18401, 18413, 18427],
[18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517],
[18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637],
[18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749],
[18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, 18869, 18899],
[18911, 18913, 18917, 18919, 18947, 18959, 18973, 18979, 19001, 19009],
[19013, 19031, 19037, 19051, 19069, 19073, 19079, 19081, 19087, 19121],
[19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, 19213, 19219],
[19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319],
[19333, 19373, 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423],
[19427, 19429, 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477],
[19483, 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571],
[19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, 19699],
[19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, 19777, 19793],
[19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891],
[19913, 19919, 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991],
[19993, 19997, 20011, 20021, 20023, 20029, 20047, 20051, 20063, 20071],
[20089, 20101, 20107, 20113, 20117, 20123, 20129, 20143, 20147, 20149],
[20161, 20173, 20177, 20183, 20201, 20219, 20231, 20233, 20249, 20261],
[20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357],
[20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443],
[20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551],
[20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, 20681, 20693],
[20707, 20717, 20719, 20731, 20743, 20747, 20749, 20753, 20759, 20771],
[20773, 20789, 20807, 20809, 20849, 20857, 20873, 20879, 20887, 20897],
[20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, 20981, 20983],
[21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067],
[21089, 21101, 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169],
[21179, 21187, 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277],
[21283, 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383],
[21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, 21491],
[21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, 21559, 21563],
[21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647],
[21649, 21661, 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751],
[21757, 21767, 21773, 21787, 21799, 21803, 21817, 21821, 21839, 21841],
[21851, 21859, 21863, 21871, 21881, 21893, 21911, 21929, 21937, 21943],
[21961, 21977, 21991, 21997, 22003, 22013, 22027, 22031, 22037, 22039],
[22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123],
[22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229],
[22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307],
[22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, 22433, 22441],
[22447, 22453, 22469, 22481, 22483, 22501, 22511, 22531, 22541, 22543],
[22549, 22567, 22571, 22573, 22613, 22619, 22621, 22637, 22639, 22643],
[22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, 22721, 22727],
[22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817],
[22853, 22859, 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943],
[22961, 22963, 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029],
[23039, 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099],
[23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, 23203],
[23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, 23311, 23321],
[23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447],
[23459, 23473, 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561],
[23563, 23567, 23581, 23593, 23599, 23603, 23609, 23623, 23627, 23629],
[23633, 23663, 23669, 23671, 23677, 23687, 23689, 23719, 23741, 23743],
[23747, 23753, 23761, 23767, 23773, 23789, 23801, 23813, 23819, 23827],
[23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909],
[23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007],
[24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091],
[24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, 24151, 24169],
[24179, 24181, 24197, 24203, 24223, 24229, 24239, 24247, 24251, 24281],
[24317, 24329, 24337, 24359, 24371, 24373, 24379, 24391, 24407, 24413],
[24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, 24509, 24517],
[24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659],
[24671, 24677, 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767],
[24781, 24793, 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877],
[24889, 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977],
[24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, 25097],
[25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, 25171, 25183],
[25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303],
[25307, 25309, 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391],
[25409, 25411, 25423, 25439, 25447, 25453, 25457, 25463, 25469, 25471],
[25523, 25537, 25541, 25561, 25577, 25579, 25583, 25589, 25601, 25603],
[25609, 25621, 25633, 25639, 25643, 25657, 25667, 25673, 25679, 25693],
[25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799],
[25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913],
[25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999],
[26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, 26107, 26111],
[26113, 26119, 26141, 26153, 26161, 26171, 26177, 26183, 26189, 26203],
[26209, 26227, 26237, 26249, 26251, 26261, 26263, 26267, 26293, 26297],
[26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, 26393, 26399],
[26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497],
[26501, 26513, 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633],
[26641, 26647, 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711],
[26713, 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801],
[26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, 26891],
[26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, 26981, 26987],
[26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077],
[27091, 27103, 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211],
[27239, 27241, 27253, 27259, 27271, 27277, 27281, 27283, 27299, 27329],
[27337, 27361, 27367, 27397, 27407, 27409, 27427, 27431, 27437, 27449],
[27457, 27479, 27481, 27487, 27509, 27527, 27529, 27539, 27541, 27551],
[27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691],
[27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767],
[27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827],
[27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, 27943, 27947],
[27953, 27961, 27967, 27983, 27997, 28001, 28019, 28027, 28031, 28051],
[28057, 28069, 28081, 28087, 28097, 28099, 28109, 28111, 28123, 28151],
[28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, 28279, 28283],
[28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403],
[28409, 28411, 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499],
[28513, 28517, 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579],
[28591, 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649],
[28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, 28729],
[28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, 28817, 28837],
[28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933],
[28949, 28961, 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059],
[29063, 29077, 29101, 29123, 29129, 29131, 29137, 29147, 29153, 29167],
[29173, 29179, 29191, 29201, 29207, 29209, 29221, 29231, 29243, 29251],
[29269, 29287, 29297, 29303, 29311, 29327, 29333, 29339, 29347, 29363],
[29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443],
[29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573],
[29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671],
[29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, 29803, 29819],
[29833, 29837, 29851, 29863, 29867, 29873, 29879, 29881, 29917, 29921],
[29927, 29947, 29959, 29983, 29989, 30011, 30013, 30029, 30047, 30059],
[30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, 30133, 30137],
[30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241],
[30253, 30259, 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341],
[30347, 30367, 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469],
[30491, 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559],
[30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, 30689],
[30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, 30781, 30803],
[30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871],
[30881, 30893, 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983],
[31013, 31019, 31033, 31039, 31051, 31063, 31069, 31079, 31081, 31091],
[31121, 31123, 31139, 31147, 31151, 31153, 31159, 31177, 31181, 31189],
[31193, 31219, 31223, 31231, 31237, 31247, 31249, 31253, 31259, 31267],
[31271, 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357, 31379],
[31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, 31513],
[31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601, 31607],
[31627, 31643, 31649, 31657, 31663, 31667, 31687, 31699, 31721, 31723],
[31727, 31729, 31741, 31751, 31769, 31771, 31793, 31799, 31817, 31847],
[31849, 31859, 31873, 31883, 31891, 31907, 31957, 31963, 31973, 31981],
[31991, 32003, 32009, 32027, 32029, 32051, 32057, 32059, 32063, 32069],
[32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159, 32173],
[32183, 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257, 32261],
[32297, 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353, 32359],
[32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423, 32429],
[32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, 32531, 32533],
[32537, 32561, 32563, 32569, 32573, 32579, 32587, 32603, 32609, 32611],
[32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717, 32719],
[32749, 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831, 32833],
[32839, 32843, 32869, 32887, 32909, 32911, 32917, 32933, 32939, 32941],
[32957, 32969, 32971, 32983, 32987, 32993, 32999, 33013, 33023, 33029],
[33037, 33049, 33053, 33071, 33073, 33083, 33091, 33107, 33113, 33119],
[33149, 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211, 33223],
[33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, 33347],
[33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427, 33457],
[33461, 33469, 33479, 33487, 33493, 33503, 33521, 33529, 33533, 33547],
[33563, 33569, 33577, 33581, 33587, 33589, 33599, 33601, 33613, 33617],
[33619, 33623, 33629, 33637, 33641, 33647, 33679, 33703, 33713, 33721],
[33739, 33749, 33751, 33757, 33767, 33769, 33773, 33791, 33797, 33809],
[33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893, 33911],
[33923, 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031, 34033],
[34039, 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157, 34159],
[34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261, 34267],
[34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, 34337, 34351],
[34361, 34367, 34369, 34381, 34403, 34421, 34429, 34439, 34457, 34469],
[34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537, 34543],
[34549, 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649, 34651],
[34667, 34673, 34679, 34687, 34693, 34703, 34721, 34729, 34739, 34747],
[34757, 34759, 34763, 34781, 34807, 34819, 34841, 34843, 34847, 34849],
[34871, 34877, 34883, 34897, 34913, 34919, 34939, 34949, 34961, 34963],
[34981, 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083, 35089],
[35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, 35171],
[35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291, 35311],
[35317, 35323, 35327, 35339, 35353, 35363, 35381, 35393, 35401, 35407],
[35419, 35423, 35437, 35447, 35449, 35461, 35491, 35507, 35509, 35521],
[35527, 35531, 35533, 35537, 35543, 35569, 35573, 35591, 35593, 35597],
[35603, 35617, 35671, 35677, 35729, 35731, 35747, 35753, 35759, 35771],
[35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863, 35869],
[35879, 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969, 35977],
[35983, 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061, 36067],
[36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161, 36187],
[36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, 36277, 36293],
[36299, 36307, 36313, 36319, 36341, 36343, 36353, 36373, 36383, 36389],
[36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497, 36523],
[36527, 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587, 36599],
[36607, 36629, 36637, 36643, 36653, 36671, 36677, 36683, 36691, 36697],
[36709, 36713, 36721, 36739, 36749, 36761, 36767, 36779, 36781, 36787],
[36791, 36793, 36809, 36821, 36833, 36847, 36857, 36871, 36877, 36887],
[36899, 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947, 36973],
[36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, 37061],
[37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189, 37199],
[37201, 37217, 37223, 37243, 37253, 37273, 37277, 37307, 37309, 37313],
[37321, 37337, 37339, 37357, 37361, 37363, 37369, 37379, 37397, 37409],
[37423, 37441, 37447, 37463, 37483, 37489, 37493, 37501, 37507, 37511],
[37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573, 37579],
[37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, 37691],
[37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831],
[37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, 37957],
[37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039]]}}]}
================================================================================

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,237 @@
# AI Call Iteration Flow - JSON Merging System
This document describes the iteration flow for handling large JSON responses from AI that may be truncated and need to be merged across multiple iterations.
## Overview
When an AI response is too large, it may be truncated (cut) at an arbitrary point. The iteration system:
1. Detects incomplete JSON
2. Requests continuation from the AI
3. Merges the continuation with the existing JSON
4. Repeats until complete or max failures reached
---
## Key Variables
| Variable | Type | Purpose |
|----------|------|---------|
| `jsonBase` | `str \| None` | The merged JSON string (CUT version for overlap matching) |
| `candidateJson` | `str` | Temporary holder for merged result until validated |
| `lastValidCompletePart` | `str \| None` | Fallback - last successfully parsed CLOSED JSON |
| `lastOverlapContext` | `str` | Context for retry/continuation prompts |
| `lastHierarchyContextForPrompt` | `str` | Context for retry/continuation prompts |
| `mergeFailCount` | `int` | Global counter (max 3 failures) |
---
## Key Distinction: hierarchyContext vs completePart
| Field | Description | Use Case |
|-------|-------------|----------|
| `hierarchyContext` | **CUT JSON** - truncated at cut point | Used as `jsonBase` for merging with next AI fragment |
| `completePart` | **CLOSED JSON** - all structures properly closed | Used for validation, parsing, and fallback |
**Why this matters:**
- The next AI fragment starts with an **overlap** that matches the CUT point
- If we used `completePart` (closed), the overlap detection would FAIL
- We must use `hierarchyContext` (cut) so overlap matching works correctly
---
## Flow Steps
### Step 1: BUILD PROMPT
**Location:** `subAiCallLooping.py` lines 163-212
**Function:** `buildContinuationContext()` from `modules/shared/jsonUtils.py`
- **First iteration:** Use original prompt
- **Continuation:** `buildContinuationContext(allSections, lastRawResponse, ...)`
- Internally calls `getContexts(lastRawResponse)` to get overlap/hierarchy
- Builds continuation prompt with `overlapContext` + `hierarchyContextForPrompt`
### Step 2: CALL AI
**Location:** `subAiCallLooping.py` lines 214-299
**Function:** `self.aiService.callAi(request)`
- Returns `response.content` as `result`
- Store raw response: `lastRawResponse = result`
### Step 4: MERGE
**Location:** `subAiCallLooping.py` lines 338-396
**Function:** `JsonResponseHandler.mergeJsonStringsWithOverlap()` from `modules/services/serviceAi/subJsonResponseHandling.py`
```
IF first iteration (jsonBase is None):
→ candidateJson = result
ELSE:
→ mergedJsonString, hasOverlap = mergeJsonStringsWithOverlap(jsonBase, result)
IF hasOverlap = False (MERGE FAILED):
→ mergeFailCount++
→ If mergeFailCount >= 3: return lastValidCompletePart (fallback)
→ Else: continue (retry with unchanged jsonBase)
ELSE:
→ candidateJson = mergedJsonString (don't update jsonBase yet!)
TRY DIRECT PARSE of candidateJson:
IF parse succeeds:
→ jsonBase = candidateJson (commit)
→ FINISHED! Return normalized result
ELSE:
→ Proceed to Step 5
```
### Step 5: GET CONTEXTS
**Location:** `subAiCallLooping.py` lines 420-427
**Function:** `getContexts()` from `modules/shared/jsonContinuation.py`
```python
contexts = getContexts(candidateJson)
```
Returns `JsonContinuationContexts`:
- `overlapContext`: `""` if JSON is complete (no cut point)
- `hierarchyContext`: CUT JSON (for merging with next fragment)
- `hierarchyContextForPrompt`: CUT JSON with budget limits (for prompts)
- `completePart`: CLOSED JSON (repaired if needed)
- `jsonParsingSuccess`: `True` if completePart is valid JSON
**Enhancement:** If original JSON is already complete → `overlapContext = ""`
This signals "JSON is complete, no more continuation needed"
### Step 6: DECIDE
**Location:** `subAiCallLooping.py` lines 429-528
#### Case A: `jsonParsingSuccess=true` AND `overlapContext=""`
**→ FINISHED**
- JSON is complete (no cut point)
- `jsonBase = contexts.completePart` (use CLOSED version for final result)
- Return `completePart` as result
#### Case B: `jsonParsingSuccess=true` AND `overlapContext!=""`
**→ CONTINUE to next iteration**
- JSON parseable but has cut point
- `jsonBase = contexts.hierarchyContext` ← **CUT version for next merge!**
- `lastValidCompletePart = contexts.completePart` ← **CLOSED version for fallback**
- Store contexts for next prompt
- `mergeFailCount = 0` (reset on success)
- `lastRawResponse = jsonBase`
- Continue to next iteration
#### Case C: `jsonParsingSuccess=false`
**→ RETRY with same prompt**
- Do NOT update `jsonBase` (keep previous valid state)
- `mergeFailCount++`
- If `mergeFailCount >= 3`: return `lastValidCompletePart` (fallback)
- Else: continue (retry with unchanged jsonBase/lastRawResponse)
---
## Flow Diagram
```
┌───────────────────────────────────────────────────────────────┐
│ ITERATION START │
└───────────────────────────┬───────────────────────────────────┘
┌───────────────────────────▼───────────────────────────────────┐
│ STEP 1: BUILD PROMPT │
│ - First: original prompt │
│ - Next: buildContinuationContext(lastRawResponse) │
└───────────────────────────┬───────────────────────────────────┘
┌───────────────────────────▼───────────────────────────────────┐
│ STEP 2: CALL AI → result │
└───────────────────────────┬───────────────────────────────────┘
┌───────────────────────────▼───────────────────────────────────┐
│ STEP 4: MERGE jsonBase + result → candidateJson │
└───────────────────────────┬───────────────────────────────────┘
┌────────────▼────────────┐
│ Merge OK? │
└────────────┬────────────┘
┌─────────────────────┼─────────────────────┐
│ NO │ YES │
▼ ▼ │
┌──────────────┐ ┌──────────────────┐ │
│ fails++ │ │ TRY DIRECT PARSE │ │
│ if >=3: │ │ of candidateJson │ │
│ RETURN │ └────────┬─────────┘ │
│ fallback │ │ │
│ else: RETRY │ ┌────────▼─────────┐ │
│ (continue) │ │ Parse OK? │ │
└──────────────┘ └────────┬─────────┘ │
│ │
┌─────────────────────┼─────────────────────┐
│ YES │ NO │
▼ ▼ │
┌──────────────┐ ┌──────────────────────────────┐
│ FINISHED ✓ │ │ STEP 5: getContexts() │
│ Return │ │ → jsonParsingSuccess │
│ normalized │ │ → overlapContext │
│ result │ └────────────┬─────────────────┘
└──────────────┘ │
┌────────────▼────────────────────┐
│ STEP 6: DECIDE │
└────────────┬────────────────────┘
┌────────────────────────────┼────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────────┐ ┌───────────────────┐
│ success=true │ │ success=true │ │ success=false │
│ overlap="" │ │ overlap!="" │ │ │
│ ───────────── │ │ ───────────────── │ │ ───────────── │
│ FINISHED ✓ │ │ CONTINUE │ │ RETRY │
│ │ │ │ │ │
│ jsonBase = │ │ jsonBase = │ │ jsonBase unchanged│
│ completePart │ │ hierarchyContext │ │ fails++ │
│ (CLOSED) │ │ (CUT for merge!) │ │ │
│ │ │ │ │ if >=3: fallback │
│ Return result │ │ fallback = │ │ else: retry │
│ │ │ completePart │ │ │
│ │ │ (CLOSED) │ │ │
│ │ │ │ │ │
│ │ │ Next iteration → │ │ │
└───────────────────┘ └───────────────────────┘ └───────────────────┘
```
---
## Files Involved
| File | Purpose |
|------|---------|
| `modules/services/serviceAi/subAiCallLooping.py` | Main iteration loop |
| `modules/shared/jsonContinuation.py` | `getContexts()` - context extraction & repair |
| `modules/shared/jsonUtils.py` | `buildContinuationContext()` - prompt building |
| `modules/services/serviceAi/subJsonResponseHandling.py` | `mergeJsonStringsWithOverlap()` |
| `modules/services/serviceAi/subJsonMerger.py` | `ModularJsonMerger` - actual merge logic |
| `modules/datamodels/datamodelAi.py` | `JsonContinuationContexts` model |
---
## Error Handling
### Merge Failures
- Max 3 consecutive failures allowed
- On failure: retry with unchanged `jsonBase` (previous valid state)
- After 3 failures: return `lastValidCompletePart` as fallback
### Parse Failures
- If `getContexts()` cannot produce valid JSON: increment fail counter
- Retry with same prompt (don't update jsonBase)
- After 3 failures: return `lastValidCompletePart` as fallback
### Fallback Strategy
- `lastValidCompletePart` stores the last successfully parsed CLOSED JSON
- Always available as fallback when things go wrong
- Ensures we return valid JSON even after multiple failures

View file

@ -7,7 +7,44 @@ Handles AI calls with looping and repair logic, including:
- Looping with JSON repair and continuation
- KPI definition and tracking
- Progress tracking and iteration management
FLOW LOGIC
VARIABLES:
- jsonBase: str (merged JSON so far, starts empty)
- lastValidCompletePart: str (fallback for failures)
- mergeFailCount: int = 0 (max 3)
FLOW:
1. BUILD PROMPT
- First: original prompt
- Next: buildContinuationContext(lastRawResponse)
2. CALL AI response fragment
4. MERGE jsonBase + response
FAILS: repeat prompt, fails++ (if >=3 return fallback)
SUCCEEDS: try parse
SUCCEEDS: FINISHED
FAILS: step 5
5. GET CONTEXTS (merge OK, parse failed)
getContexts(mergedJson)
- If no cut point: overlapContext = ""
- Store contexts for next iteration
6. DECIDE
jsonParsingSuccess=true AND overlapContext="":
FINISHED. return completePart
jsonParsingSuccess=true AND overlapContext!="":
CONTINUE, fails=0
ELSE: repeat prompt, fails++
"""
import json
import logging
from typing import Dict, Any, List, Optional, Callable
@ -16,10 +53,14 @@ from modules.datamodels.datamodelAi import (
AiCallRequest, AiCallOptions
)
from modules.datamodels.datamodelExtraction import ContentPart
from modules.shared.jsonUtils import buildContinuationContext, extractJsonString, tryParseJson
from modules.services.serviceAi.subJsonResponseHandling import JsonResponseHandler
from modules.services.serviceAi.subLoopingUseCases import LoopingUseCaseRegistry
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
from modules.shared.jsonContinuation import getContexts
from modules.shared.jsonUtils import buildContinuationContext, extractJsonString, tryParseJson
from modules.shared.jsonUtils import tryParseJson
from modules.shared.jsonUtils import closeJsonStructures
from modules.shared.jsonUtils import stripCodeFences, normalizeJsonText
logger = logging.getLogger(__name__)
@ -88,7 +129,18 @@ class AiCallLooper:
iteration = 0
allSections = [] # Accumulate all sections across iterations
lastRawResponse = None # Store last raw JSON response for continuation
accumulatedDirectJson = [] # Accumulate JSON strings for direct return use cases (chapter_structure, code_structure)
# JSON Base Iteration System:
# - jsonBase: the merged JSON string (replaces accumulatedDirectJson array)
# - After each iteration, new response is merged with jsonBase
# - On merge success: check if complete, store contexts for next iteration
# - On merge fail: retry with same prompt, increment fails
jsonBase = None # Merged JSON string (starts None, set on first response)
# Merge fail tracking - stop after 3 consecutive merge failures
MAX_MERGE_FAILS = 3
mergeFailCount = 0 # Global counter for merge failures across entire loop
lastValidCompletePart = None # Store last successfully parsed completePart for fallback
# Get parent operation ID for iteration operations (parentId should be operationId, not log entry ID)
parentOperationId = operationId # Use the parent's operationId directly
@ -136,6 +188,17 @@ class AiCallLooper:
if not lastRawResponse:
logger.warning(f"Iteration {iteration}: No previous response available for continuation!")
# Store valid completePart from continuation context for fallback on merge failures
# Use getContexts to check if completePart is parseable and store it
if lastRawResponse and not lastValidCompletePart:
try:
contexts = getContexts(lastRawResponse)
if contexts.jsonParsingSuccess and contexts.completePart:
lastValidCompletePart = contexts.completePart
logger.debug(f"Iteration {iteration}: Stored initial valid completePart ({len(lastValidCompletePart)} chars)")
except Exception as e:
logger.debug(f"Iteration {iteration}: Failed to extract completePart: {e}")
# Unified prompt builder call: Continuation builders only need continuationContext, templateStructure, and basePrompt
# All initial context (section, userPrompt, etc.) is already in basePrompt, so promptArgs is not needed
# Extract templateStructure and basePrompt from promptArgs (they're explicit parameters)
@ -247,238 +310,220 @@ class AiCallLooper:
# Store raw response for continuation (even if broken)
lastRawResponse = result
# Parse JSON for use case handling
parsedJsonForUseCase = None
extractedJsonForUseCase = None
try:
extractedJsonForUseCase = extractJsonString(result)
parsedJson, parseError, _ = tryParseJson(extractedJsonForUseCase)
if parseError is None and parsedJson:
parsedJsonForUseCase = parsedJson
except Exception:
pass
# Handle use cases that return JSON directly (no section extraction needed)
# Check if use case supports direct return (all registered use cases do)
if useCase and not useCase.requiresExtraction:
# For all direct return use cases, check completeness and support looping
if True: # All registered use cases support looping
# If parsing failed (e.g., invalid JSON with comments or truncated JSON), continue looping to get valid JSON
if not parsedJsonForUseCase:
logger.info(f"Iteration {iteration}: Use case '{useCaseId}' - JSON parsing failed (likely incomplete/truncated), continuing iteration to complete")
# Accumulate response for merging in next iteration
accumulatedDirectJson.append(result)
# =====================================================================
# ITERATION FLOW (Simplified)
# =====================================================================
# Step 4: MERGE jsonBase + new response
# - FAILS: repeat prompt, increment fails cont (if >=3 return fallback)
# - SUCCEEDS: try parse
# - SUCCEEDS: FINISHED
# - FAILS: proceed to Step 5
# Step 5: GET CONTEXTS (merge OK, parse failed)
# - getContexts() with repair
# - If no cut point: overlapContext = ""
# Step 6: DECIDE
# - jsonParsingSuccess=true AND overlapContext="": FINISHED
# - jsonParsingSuccess=true AND overlapContext!="": continue, fails=0
# - ELSE: repeat prompt, increment fails count
# =====================================================================
# STEP 4: MERGE jsonBase + new response
# Use candidateJson to hold merged result until we confirm it's valid
candidateJson = None
if jsonBase is None:
# First iteration - candidate is the current result
candidateJson = result
logger.debug(f"Iteration {iteration}: First response, candidateJson ({len(candidateJson)} chars)")
else:
# Merge jsonBase with new response
logger.info(f"Iteration {iteration}: Merging jsonBase ({len(jsonBase)} chars) with new response ({len(result)} chars)")
mergedJsonString, hasOverlap = JsonResponseHandler.mergeJsonStringsWithOverlap(jsonBase, result)
if not hasOverlap:
# MERGE FAILED - repeat prompt with unchanged jsonBase
mergeFailCount += 1
logger.warning(
f"Iteration {iteration}: Merge failed, no overlap found "
f"(fail {mergeFailCount}/{MAX_MERGE_FAILS})"
)
# Continue to next iteration - continuation prompt builder will handle the rest
if iterationOperationId:
self.services.chat.progressLogUpdate(iterationOperationId, 0.7, "JSON incomplete, requesting continuation")
self.services.chat.progressLogFinish(iterationOperationId, True)
continue
# If we successfully parsed JSON, check completeness using parsed structure
# CRITICAL: If parsing succeeded, trust the parsed JSON structure check
# The string-based check can have false positives on valid JSON (e.g., due to normalization issues)
# Only use string-based check when parsing fails (already handled above)
isComplete = JsonResponseHandler.isJsonComplete(parsedJsonForUseCase)
# If parsed check says complete, trust it - don't override with string check
# String check is only reliable when parsing fails (truncated JSON that gets closed for parsing)
# For successfully parsed JSON, the structure check is definitive
if not isComplete:
logger.warning(f"Iteration {iteration}: Use case '{useCaseId}' - JSON is incomplete, continuing for continuation")
# Accumulate response for merging in next iteration
accumulatedDirectJson.append(result)
# Continue to next iteration - continuation prompt builder will handle the rest
if iterationOperationId:
self.services.chat.progressLogUpdate(iterationOperationId, 0.7, "JSON incomplete, requesting continuation")
self.services.chat.progressLogFinish(iterationOperationId, True)
continue
else:
# JSON is complete - merge accumulated responses if any
if accumulatedDirectJson:
logger.info(f"Iteration {iteration}: Merging {len(accumulatedDirectJson) + 1} accumulated responses")
if mergeFailCount >= MAX_MERGE_FAILS:
# Max failures reached - return last valid completePart
logger.error(
f"Iteration {iteration}: Max merge failures ({MAX_MERGE_FAILS}) reached, "
"returning last valid completePart"
)
if iterationOperationId:
self.services.chat.progressLogFinish(iterationOperationId, False)
# Use generic data-based merging for all use cases
try:
# Strategy: Merge strings first for incomplete JSON, then parse and merge parsed objects
# This ensures incomplete JSON from part 1 is preserved
allJsonStrings = accumulatedDirectJson + [result]
# Step 1: Merge all JSON strings using existing overlap detection
mergedJsonString = allJsonStrings[0] if allJsonStrings else ""
hasOverlap = True # Track if any overlap was found
for jsonStr in allJsonStrings[1:]:
mergedJsonString, hasOverlapInMerge = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, jsonStr)
# If no overlap found in any merge, stop iterations
if not hasOverlapInMerge:
hasOverlap = False
logger.info(f"Iteration {iteration}: No overlap found during merge - stopping iterations and closing JSON")
break
# If no overlap was found, mark as complete and use closed JSON
if not hasOverlap:
isComplete = True
# JSON is already closed by mergeJsonStringsWithOverlap when no overlap
# Use the merged (closed) JSON string directly
result = mergedJsonString
# CRITICAL: Update lastRawResponse with merged result for next iteration
lastRawResponse = mergedJsonString
# Try to parse it to get parsedJsonForUseCase
try:
extracted = extractJsonString(mergedJsonString)
parsed, parseErr, _ = tryParseJson(extracted)
if parseErr is None and parsed:
# Use callback to normalize JSON structure
normalized = self._normalizeJsonStructure(parsed, useCase)
parsedJsonForUseCase = normalized
result = json.dumps(normalized, indent=2, ensure_ascii=False)
# CRITICAL: Update lastRawResponse with final result
lastRawResponse = result
else:
# Parsing failed - try to repair JSON
from modules.shared.jsonUtils import repairBrokenJson
logger.warning(
f"Iteration {iteration}: JSON parse failed after no-overlap merge, "
f"attempting repair: {str(parseErr) if parseErr else 'Unknown error'}"
)
repairedJson = repairBrokenJson(extracted)
if repairedJson and isinstance(repairedJson, dict):
# repairBrokenJson returns a dict directly - use it
normalized = self._normalizeJsonStructure(repairedJson, useCase)
parsedJsonForUseCase = normalized
result = json.dumps(normalized, indent=2, ensure_ascii=False)
# CRITICAL: Update lastRawResponse with final result
lastRawResponse = result
logger.info(f"Iteration {iteration}: Successfully repaired JSON after no-overlap merge")
except Exception as e:
# Last resort: try repair on the original merged string
logger.warning(
f"Iteration {iteration}: Exception during no-overlap JSON processing, "
f"attempting repair: {str(e)}"
)
try:
from modules.shared.jsonUtils import repairBrokenJson
repairedJson = repairBrokenJson(mergedJsonString)
if repairedJson and isinstance(repairedJson, dict):
normalized = self._normalizeJsonStructure(repairedJson, useCase)
parsedJsonForUseCase = normalized
result = json.dumps(normalized, indent=2, ensure_ascii=False)
logger.info(f"Iteration {iteration}: Successfully repaired JSON after exception")
else:
logger.error(f"Iteration {iteration}: JSON repair failed, using string result as-is")
except Exception as repairError:
logger.error(
f"Iteration {iteration}: JSON repair also failed: {str(repairError)}, "
"using string result as-is"
)
else:
# Overlap found - continue with normal processing
# Step 2: Try to parse the merged string
extracted = extractJsonString(mergedJsonString)
parsed, parseErr, _ = tryParseJson(extracted)
if parseErr is None and parsed:
# Parsing succeeded - normalize and use (via callback)
normalized = self._normalizeJsonStructure(parsed, useCase)
parsedJsonForUseCase = normalized
result = json.dumps(normalized, indent=2, ensure_ascii=False)
# CRITICAL: Update lastRawResponse with merged result
lastRawResponse = result
else:
# Parsing failed - try to extract partial data using Deep-Structure-Merging
# This fallback works for all use cases: parse what we can from each part
allParsed = []
for jsonStr in allJsonStrings:
extracted = extractJsonString(jsonStr)
parsed, parseErr, _ = tryParseJson(extracted)
if parseErr is None and parsed:
# Use callback to normalize JSON structure
normalized = self._normalizeJsonStructure(parsed, useCase)
allParsed.append(normalized)
if allParsed:
# Use mergeDeepStructures for intelligent merging across all use cases
if len(allParsed) > 1:
mergedJsonObj = allParsed[0]
for nextObj in allParsed[1:]:
mergedJsonObj = JsonResponseHandler.mergeDeepStructures(
mergedJsonObj, nextObj, iteration, f"{useCaseId}.merge"
)
else:
mergedJsonObj = allParsed[0]
parsedJsonForUseCase = mergedJsonObj
result = json.dumps(mergedJsonObj, indent=2, ensure_ascii=False)
# CRITICAL: Update lastRawResponse with merged result
lastRawResponse = result
else:
# All parsing failed - use string merge result
result = mergedJsonString
# CRITICAL: Update lastRawResponse with merged result
lastRawResponse = mergedJsonString
except Exception as e:
logger.warning(f"Failed data-based merge, falling back to string merging: {e}")
# Fallback to string merging
mergedJsonString = accumulatedDirectJson[0] if accumulatedDirectJson else result
hasOverlap = True # Track if any overlap was found
for prevJson in accumulatedDirectJson[1:]:
mergedJsonString, hasOverlapInMerge = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, prevJson)
if not hasOverlapInMerge:
hasOverlap = False
logger.info(f"Iteration {iteration}: No overlap found during fallback merge - stopping iterations")
break
if hasOverlap:
mergedJsonString, hasOverlapInMerge = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, result)
if not hasOverlapInMerge:
hasOverlap = False
logger.info(f"Iteration {iteration}: No overlap found in final fallback merge - stopping iterations")
result = mergedJsonString
# CRITICAL: Update lastRawResponse with merged result
lastRawResponse = mergedJsonString
# If no overlap was found, mark as complete and use closed JSON
if not hasOverlap:
isComplete = True
# JSON is already closed by mergeJsonStringsWithOverlap when no overlap
# Try to parse it to get parsedJsonForUseCase
try:
extractedMerged = extractJsonString(result)
parsedMerged, parseError, _ = tryParseJson(extractedMerged)
if parseError is None and parsedMerged:
parsedJsonForUseCase = parsedMerged
except Exception:
pass # Use string result if parsing fails
# Try to parse the string-merged result
if lastValidCompletePart:
try:
extractedMerged = extractJsonString(result)
parsedMerged, parseError, _ = tryParseJson(extractedMerged)
if parseError is None and parsedMerged:
parsedJsonForUseCase = parsedMerged
extracted = extractJsonString(lastValidCompletePart)
parsed, parseErr, _ = tryParseJson(extracted)
if parseErr is None and parsed:
normalized = self._normalizeJsonStructure(parsed, useCase)
return json.dumps(normalized, indent=2, ensure_ascii=False)
except Exception:
pass
return lastValidCompletePart
else:
# No valid fallback - return whatever we have
return jsonBase if jsonBase else ""
logger.info(f"Iteration {iteration}: Use case '{useCaseId}' - JSON is complete")
# Not at max failures - retry with same prompt (jsonBase unchanged)
if iterationOperationId:
self.services.chat.progressLogUpdate(
iterationOperationId, 0.7,
f"Merge failed ({mergeFailCount}/{MAX_MERGE_FAILS}), retrying"
)
self.services.chat.progressLogFinish(iterationOperationId, True)
continue
# MERGE SUCCEEDED - set candidate (don't update jsonBase yet!)
candidateJson = mergedJsonString
logger.debug(f"Iteration {iteration}: Merge succeeded, candidateJson ({len(candidateJson)} chars)")
logger.info(f"Iteration {iteration}: Use case '{useCaseId}' - returning JSON directly")
if iterationOperationId:
self.services.chat.progressLogFinish(iterationOperationId, True)
# Try direct parse of candidate
try:
extracted = extractJsonString(candidateJson)
parsed, parseErr, _ = tryParseJson(extracted)
if parseErr is None and parsed:
# Direct parse succeeded - FINISHED
# Commit candidate to jsonBase
jsonBase = candidateJson
logger.info(f"Iteration {iteration}: Direct parse succeeded, JSON is complete")
normalized = self._normalizeJsonStructure(parsed, useCase)
result = json.dumps(normalized, indent=2, ensure_ascii=False)
if iterationOperationId:
self.services.chat.progressLogFinish(iterationOperationId, True)
if not useCase.finalResultHandler:
raise ValueError(
f"Use case '{useCaseId}' is missing required 'finalResultHandler' callback."
)
return useCase.finalResultHandler(
result, normalized, extracted, debugPrefix, self.services
)
except Exception as e:
logger.debug(f"Iteration {iteration}: Direct parse failed: {e}")
# Use callback to handle final result formatting and debug file writing (REQUIRED - no fallback)
if not useCase.finalResultHandler:
raise ValueError(
f"Use case '{useCaseId}' is missing required 'finalResultHandler' callback. "
"All use cases must provide a finalResultHandler function."
)
final_json = useCase.finalResultHandler(
result, parsedJsonForUseCase, extractedJsonForUseCase,
debugPrefix, self.services
# STEP 5: GET CONTEXTS (merge OK, parse failed = cut JSON)
# Use candidateJson for context extraction
contexts = getContexts(candidateJson)
logger.debug(
f"Iteration {iteration}: getContexts() -> "
f"jsonParsingSuccess={contexts.jsonParsingSuccess}, "
f"overlapContext={'\"\"' if not contexts.overlapContext else f'({len(contexts.overlapContext)} chars)'}"
)
return final_json
# STEP 6: DECIDE based on jsonParsingSuccess and overlapContext
if contexts.jsonParsingSuccess and contexts.overlapContext == "":
# JSON is complete (no cut point) - FINISHED
# Use completePart for final result (closed, repaired JSON)
# No more merging needed, so we don't need the cut version
jsonBase = contexts.completePart
logger.info(f"Iteration {iteration}: jsonParsingSuccess=true, overlapContext='', JSON complete")
# Store and parse completePart
lastValidCompletePart = contexts.completePart
try:
extracted = extractJsonString(contexts.completePart)
parsed, parseErr, _ = tryParseJson(extracted)
if parseErr is None and parsed:
normalized = self._normalizeJsonStructure(parsed, useCase)
result = json.dumps(normalized, indent=2, ensure_ascii=False)
if iterationOperationId:
self.services.chat.progressLogFinish(iterationOperationId, True)
if not useCase.finalResultHandler:
raise ValueError(
f"Use case '{useCaseId}' is missing required 'finalResultHandler' callback."
)
return useCase.finalResultHandler(
result, normalized, extracted, debugPrefix, self.services
)
except Exception as e:
logger.warning(f"Iteration {iteration}: Failed to parse completePart: {e}")
# Fallback: return completePart as-is
if iterationOperationId:
self.services.chat.progressLogFinish(iterationOperationId, True)
return contexts.completePart
elif contexts.jsonParsingSuccess and contexts.overlapContext != "":
# JSON parseable but has cut point - CONTINUE to next iteration
# CRITICAL: Use hierarchyContext (CUT json) as jsonBase for next merge!
# - hierarchyContext = the truncated JSON at cut point (needed for overlap matching)
# - completePart = closed JSON (for validation/fallback only)
# The next AI fragment's overlap must match the CUT point, not closed structures
jsonBase = contexts.hierarchyContext
logger.info(
f"Iteration {iteration}: jsonParsingSuccess=true, overlapContext not empty, "
f"continuing iteration (jsonBase updated to hierarchyContext: {len(jsonBase)} chars)"
)
# Store valid completePart as fallback (different from jsonBase!)
lastValidCompletePart = contexts.completePart
# Reset fail counter on successful progress
mergeFailCount = 0
# Update lastRawResponse for continuation prompt building
# Use the CUT version for prompt context as well
lastRawResponse = jsonBase
if iterationOperationId:
self.services.chat.progressLogUpdate(iterationOperationId, 0.7, "JSON incomplete, requesting continuation")
self.services.chat.progressLogFinish(iterationOperationId, True)
continue
else:
# JSON not parseable after repair - repeat prompt, increment fails
# Do NOT update jsonBase - keep previous valid state
mergeFailCount += 1
logger.warning(
f"Iteration {iteration}: jsonParsingSuccess=false, "
f"repeat prompt (fail {mergeFailCount}/{MAX_MERGE_FAILS})"
)
if mergeFailCount >= MAX_MERGE_FAILS:
# Max failures reached - return last valid completePart
logger.error(
f"Iteration {iteration}: Max failures ({MAX_MERGE_FAILS}) reached, "
"returning last valid completePart"
)
if iterationOperationId:
self.services.chat.progressLogFinish(iterationOperationId, False)
if lastValidCompletePart:
try:
extracted = extractJsonString(lastValidCompletePart)
parsed, parseErr, _ = tryParseJson(extracted)
if parseErr is None and parsed:
normalized = self._normalizeJsonStructure(parsed, useCase)
return json.dumps(normalized, indent=2, ensure_ascii=False)
except Exception:
pass
return lastValidCompletePart
else:
return jsonBase if jsonBase else ""
# Not at max - retry with same prompt
# Do NOT update jsonBase or lastRawResponse - keep previous for retry
if iterationOperationId:
self.services.chat.progressLogUpdate(
iterationOperationId, 0.7,
f"Parse failed ({mergeFailCount}/{MAX_MERGE_FAILS}), retrying"
)
self.services.chat.progressLogFinish(iterationOperationId, True)
continue
except Exception as e:
logger.error(f"Error in AI call iteration {iteration}: {str(e)}")
@ -510,9 +555,7 @@ class AiCallLooper:
"""
if not jsonString or not jsonString.strip():
return False
from modules.shared.jsonUtils import stripCodeFences, normalizeJsonText
# Normalize JSON string
normalized = stripCodeFences(normalizeJsonText(jsonString)).strip()
if not normalized:
@ -587,14 +630,12 @@ class AiCallLooper:
# - Ends with incomplete object property (e.g., {"key": "val)
# If JSON parses successfully without closing, it's complete
from modules.shared.jsonUtils import tryParseJson
parsed, parseErr, _ = tryParseJson(jsonContent)
if parseErr is None:
# Parses successfully - it's complete
return False
# If it doesn't parse, try closing it and see if that helps
from modules.shared.jsonUtils import closeJsonStructures
closed = closeJsonStructures(jsonContent)
parsedClosed, parseErrClosed, _ = tryParseJson(closed)

View file

@ -1,169 +0,0 @@
# Budget Rendering Requirement for JSON Continuation Context
## Problem Statement
When rendering the `hierarchyContextForPrompt` with budget constraints, we need to prioritize data near the cut point (where JSON was truncated) over data near the root. Currently, the implementation renders top-down (root → children), causing root-level data to consume budget before cut-level data gets rendered.
## Requirement
### Core Logic
1. **Find the cut element**: Identify the element in the JSON structure where truncation occurred (the cut point).
2. **Build path from cut to root**: Create an ordered list of nodes from the cut element back to the root element. The path should be: `[cut_element, parent_of_cut, grandparent_of_cut, ..., root]`.
3. **Walk backwards from cut to root, consuming budget**:
- Start at the cut element
- Walk backwards along the path to root
- For each value node encountered on the path:
- If value size ≤ remaining budget: render full value and deduct budget
- If value size > remaining budget: render type hint (`<str>`, `<number>`, etc.) and **DO NOT** deduct budget (never let budget go below 0)
- If remaining budget < 50: set budget to 0 and enable "summary mode"
4. **Summary mode (budget = 0)**:
- Continue walking towards root
- For elements on the **same level** as current position: render with type hints
- For elements on **higher levels** (closer to root): render only structure (keys/attributes) without values - just show the level hierarchy
### Critical Constraints
- **Never let budget go below 0**: If a value is bigger than remaining budget, use type hint instead of rendering data
- **Budget allocation order**: Cut element → parent → grandparent → ... → root (bottom-up)
- **Rendering order**: Can be top-down (root → children) for structure, but budget must be allocated bottom-up
- **When budget < 50**: Set to 0 and enable summary mode immediately
### Example Flow
Given JSON structure (truncated at cut point):
```json
{
"document": {
"metadata": {
"title": "My Document",
"author": "John Doe",
"version": 1
},
"sections": [
{
"id": "section1",
"title": "Introduction",
"content": "This is the introduction content..."
},
{
"id": "section2",
"title": "Main Content",
"content": "This is a very long content that gets cut right here in the middle of this sentence and the JSON is truncated..."
```
**Cut point**: The JSON is truncated in the middle of `sections[1].content` value.
**Path from cut to root**:
```
[
sections[1].content (value - CUT ELEMENT),
sections[1].content (key-value pair),
sections[1] (object),
sections (array),
document (object),
root (object)
]
```
**Budget allocation order (walking backwards from cut → root)**:
1. **`sections[1].content` (value)** - CUT ELEMENT
- Check: value size = 120 chars, budget = 500
- Action: Render full value "This is a very long content that gets cut right here in the middle of this sentence and the JSON is truncated..."
- Deduct: 120 chars from budget → remaining = 380
2. **`sections[1]` (object)** - Parent of cut
- Action: Render structure only (no budget needed for `{`, `}`, keys)
- Render: `"id": "section2", "title": "Main Content", "content": <already rendered>`
3. **`sections` (array)** - Grandparent
- Action: Render structure only
- Render: `[<section1>, <section2>]` where section2 already has content rendered
4. **`document` (object)** - Great-grandparent
- Action: Render structure only
- Render: `"metadata": {...}, "sections": <already rendered>`
5. **`root` (object)** - Root
- Action: Render structure only
- Render: `{"document": <already rendered>}`
**If budget becomes 0 during step 1** (e.g., value size = 600, budget = 500):
- `sections[1].content` gets type hint `<str>` (value too big, don't deduct budget)
- Budget remains 500, but if < 50, set to 0 and enable summary mode
- Continue to root with summary mode:
- **Same level elements** (e.g., `sections[1].id`, `sections[1].title`): type hints (`<str>`)
- **Higher level elements** (e.g., `sections[0]`, `metadata`): structure only (keys, braces, no values)
**Expected output with budget = 500** (sufficient budget):
```json
{
"document": {
"metadata": {
"title": "My Document",
"author": "John Doe",
"version": 1
},
"sections": [
{
"id": "section1",
"title": "Introduction",
"content": "This is the introduction content..."
},
{
"id": "section2",
"title": "Main Content",
"content": "This is a very long content that gets cut right here in the middle of this sentence and the JSON is truncated..."
```
*All values rendered because budget is sufficient.*
**Expected output if ONE value is too big and budget running out** (budget = 100, cut value = 120):
```json
{
"document": {
"metadata": <object>>,
"sections": [
{
"id": "section1",
"title": "Introduction",
"content": "This is the introduction content..."
},
{
"id": "section2",
"title": <str>,
"content": "This is a very long content that gets cut right here in the middle of this sentence and the JSON is truncated..."
```
**Key Points:**
- **Single value too big**: Only that value gets type hint, continue rendering other data
- **Budget > 0**: Render side paths (siblings, other branches) as long as budget allows
- **Budget = 0**: Stop rendering side paths, only render path from cut element to root (structure only for higher levels, type hints for same level)
## Current Implementation Issues
The current implementation in `jsonContinuation.py`:
- Pre-allocates budget to path elements before rendering
- But rendering still happens top-down, so root elements consume budget first
- Path elements check for pre-allocated budget, but non-path elements also consume budget during top-down rendering
## Expected Behavior
**Before budget runs out:**
- Cut element and path to root: full values rendered
- Other elements: full values if budget allows
**After budget < 50 (summary mode):**
- Cut element and path to root: full values (if budget was allocated)
- Same level elements: type hints (`<str>`, `<object>`, etc.)
- Higher level elements: structure only (keys, braces, brackets - no values)
## Implementation Notes
- The path should be built using `_findPathToRoot()` which walks from root to find cut element
- Budget should be consumed during a separate pass that walks the path (cut → root)
- During rendering, path elements should check if they have pre-allocated budget
- Non-path elements should only consume leftover budget after path elements are processed
- Structure elements (objects, arrays) don't consume budget - only values do

View file

@ -1,305 +0,0 @@
"""
FINAL CORRECTED _renderWithBudgetFromStructure - Version 3
This file contains the CORRECT implementation that should REPLACE
the existing methods in jsonContinuation.py
KEY BEHAVIOR:
1. Budget is allocated from CUT ROOT (not top-down)
2. Cut-near values get priority
3. When budget < 50: summary_mode enabled, non-path containers <object>/<array>
4. Path containers always render their structure
COPY THESE METHODS INTO YOUR JsonAnalyzer CLASS:
- _renderWithBudgetFromStructure (REPLACE existing)
- _buildPathFromCutToRootV3 (ADD)
- _collectAllValuesWithDistance (ADD)
- _renderNodeV3 (ADD)
- _renderObjectV3 (ADD)
- _renderArrayV3 (ADD)
- _renderValueV3 (ADD)
"""
from typing import List, Set
from dataclasses import dataclass, field
@dataclass
class BudgetAllocation:
"""Tracks which nodes have been allocated budget"""
allocated_node_ids: Set[int] = field(default_factory=set)
path_node_ids: Set[int] = field(default_factory=set)
summary_mode: bool = False
# =============================================================================
# METHODS TO COPY INTO JsonAnalyzer CLASS
# =============================================================================
def _renderWithBudgetFromStructure(self, structure: dict, cutPos: int) -> str:
"""
Render structure with budget logic - allocate from CUT to ROOT.
ALGORITHM:
Phase 1: Build path from cut to root
- Find the cut element (truncated value or deepest incomplete node)
- Build ordered path: [cut_element, parent, grandparent, ..., root]
Phase 2: Allocate budget
- Collect ALL value nodes with their distance to cut
- Sort by distance (smaller = closer to cut = higher priority)
- Allocate budget to values in this order
- When budget < 50: enable summary_mode (affects containers only)
Phase 3: Render
- PATH containers: always render structure
- NON-PATH containers in summary_mode: render as <object>/<array>
- Values: render if allocated, else type hint
Returns:
Rendered JSON string with budget constraints applied
"""
# Phase 1: Build path from cut to root
pathFromCutToRoot = []
self._buildPathFromCutToRootV3(structure, cutPos, [], pathFromCutToRoot)
pathNodeIds = set(id(node) for node in pathFromCutToRoot)
# Phase 2: Collect ALL values and allocate budget
allValues = []
self._collectAllValuesWithDistance(structure, cutPos, allValues)
# Sort by distance (smaller = closer to cut = higher priority)
allValues.sort(key=lambda x: x['distance'])
# Initialize allocation tracker
allocation = BudgetAllocation(
path_node_ids=pathNodeIds,
allocated_node_ids=set(),
summary_mode=False
)
remainingBudget = self.budgetLimit
# Phase 2a: Allocate PATH values first (truncated values are always rendered)
pathValues = [item for item in allValues if id(item['node']) in pathNodeIds]
for item in pathValues:
node = item['node']
nodeType = node.get('type')
if nodeType == 'truncated_value':
allocation.allocated_node_ids.add(id(node))
continue
if nodeType != 'value':
continue
rawValue = node.get('raw', '')
valueSize = len(rawValue)
if valueSize <= remainingBudget:
allocation.allocated_node_ids.add(id(node))
remainingBudget -= valueSize
if remainingBudget < 50:
allocation.summary_mode = True
# Phase 2b: Allocate NON-PATH values (skip if path already triggered summary mode)
if not allocation.summary_mode:
nonPathValues = [item for item in allValues if id(item['node']) not in pathNodeIds]
for item in nonPathValues:
node = item['node']
nodeType = node.get('type')
if nodeType != 'value':
continue
rawValue = node.get('raw', '')
valueSize = len(rawValue)
if valueSize <= remainingBudget:
allocation.allocated_node_ids.add(id(node))
remainingBudget -= valueSize
if remainingBudget < 50 and not allocation.summary_mode:
allocation.summary_mode = True
# Phase 3: Render with allocation info
return self._renderNodeV3(structure, 0, allocation)
def _buildPathFromCutToRootV3(self, node: dict, cutPos: int, currentPath: list, resultPath: list) -> bool:
"""
Recursively find the path from root to cut element, then reverse it.
Result path is ordered: [cut_element, parent, ..., root]
"""
nodeType = node.get('type')
startPos = node.get('start_pos', 0)
endPos = node.get('end_pos', cutPos + 1)
pathWithCurrent = currentPath + [node]
for child in node.get('children', []):
if self._buildPathFromCutToRootV3(child, cutPos, pathWithCurrent, resultPath):
return True
if nodeType == 'truncated_value':
resultPath.clear()
resultPath.extend(reversed(pathWithCurrent))
return True
if nodeType == 'value' and startPos <= cutPos <= endPos:
resultPath.clear()
resultPath.extend(reversed(pathWithCurrent))
return True
if nodeType in ('object', 'array') and not node.get('complete') and startPos <= cutPos:
resultPath.clear()
resultPath.extend(reversed(pathWithCurrent))
return True
if nodeType == 'root' and not resultPath:
resultPath.clear()
resultPath.extend(reversed(pathWithCurrent))
return True
return False
def _collectAllValuesWithDistance(self, node: dict, cutPos: int, result: list, depth: int = 0):
"""Collect ALL value nodes with their distance to cut point."""
nodeType = node.get('type')
if nodeType in ('value', 'truncated_value'):
endPos = node.get('end_pos', cutPos)
distance = cutPos - endPos
result.append({
'node': node,
'distance': distance,
'depth': depth
})
for child in node.get('children', []):
self._collectAllValuesWithDistance(child, cutPos, result, depth + 1)
def _renderNodeV3(self, node: dict, depth: int, allocation) -> str:
"""Render a node with budget allocation info."""
nodeType = node.get('type')
if nodeType == 'root':
parts = []
for child in node.get('children', []):
parts.append(self._renderNodeV3(child, depth, allocation))
return '\n'.join(parts)
elif nodeType == 'object':
return self._renderObjectV3(node, depth, allocation)
elif nodeType == 'array':
return self._renderArrayV3(node, depth, allocation)
elif nodeType == 'value':
return self._renderValueV3(node, depth, allocation)
elif nodeType == 'truncated_value':
keyPrefix = f'"{node.get("key")}": ' if node.get('key') else ''
return f"{keyPrefix}{node.get('raw', '')}"
return ''
def _renderObjectV3(self, node: dict, depth: int, allocation) -> str:
"""Render object - summary mode non-path objects become <object>."""
indentStr = " " * depth
innerIndent = " " * (depth + 1)
keyPrefix = f'"{node.get("key")}": ' if node.get('key') else ''
children = node.get('children', [])
isOnPath = id(node) in allocation.path_node_ids
if allocation.summary_mode and not isOnPath:
return f"{keyPrefix}<object>"
if not children:
return f"{keyPrefix}{{}}" if node.get('complete') else f"{keyPrefix}{{"
parts = [f"{keyPrefix}{{"]
for i, child in enumerate(children):
childRendered = self._renderNodeV3(child, depth + 1, allocation)
isLast = (i == len(children) - 1)
isTruncated = child.get('type') == 'truncated_value'
if isLast or isTruncated:
parts.append(f"{innerIndent}{childRendered}")
else:
parts.append(f"{innerIndent}{childRendered},")
if node.get('complete'):
parts.append(f"{indentStr}}}")
return '\n'.join(parts)
def _renderArrayV3(self, node: dict, depth: int, allocation) -> str:
"""Render array - summary mode non-path arrays become <array>."""
indentStr = " " * depth
innerIndent = " " * (depth + 1)
keyPrefix = f'"{node.get("key")}": ' if node.get('key') else ''
children = node.get('children', [])
isOnPath = id(node) in allocation.path_node_ids
if allocation.summary_mode and not isOnPath:
return f"{keyPrefix}<array>"
if not children:
return f"{keyPrefix}[]" if node.get('complete') else f"{keyPrefix}["
parts = [f"{keyPrefix}["]
for i, child in enumerate(children):
childRendered = self._renderNodeV3(child, depth + 1, allocation)
isLast = (i == len(children) - 1)
isTruncated = child.get('type') == 'truncated_value'
if isLast or isTruncated:
parts.append(f"{innerIndent}{childRendered}")
else:
parts.append(f"{innerIndent}{childRendered},")
if node.get('complete'):
parts.append(f"{indentStr}]")
return '\n'.join(parts)
def _renderValueV3(self, node: dict, depth: int, allocation) -> str:
"""Render value - if allocated render full, else type hint."""
keyPrefix = f'"{node.get("key")}": ' if node.get('key') else ''
rawValue = node.get('raw', '""')
valueType = node.get('value_type', 'string')
typeHints = {
'string': '<str>',
'number': '<number>',
'boolean': '<boolean>',
'null': '<null>'
}
typeHint = typeHints.get(valueType, '<value>')
if id(node) in allocation.allocated_node_ids:
return f"{keyPrefix}{rawValue}"
else:
return f"{keyPrefix}{typeHint}"
# =============================================================================
# ALSO ADD THIS IMPORT AT THE TOP OF YOUR FILE
# =============================================================================
# from dataclasses import dataclass, field
# from typing import Set
# And add the BudgetAllocation class inside your file or as a nested class

File diff suppressed because it is too large Load diff

View file

@ -32,11 +32,16 @@ Autor: Claude
Version: 2.0
"""
import json
import logging
import re
from typing import Tuple, List, Optional, Any, Set
from dataclasses import dataclass, field
from enum import Enum
from modules.datamodels.datamodelAi import JsonContinuationContexts
logger = logging.getLogger(__name__)
# =============================================================================
# MODULE CONSTANTS
@ -1442,12 +1447,7 @@ class JsonAnalyzer:
return ''
def _renderObjectV3(self, node: dict, depth: int, allocation: BudgetAllocation) -> str:
"""
Render object.
- PATH containers: always render with structure
- NON-PATH containers: only render as <object> if summary_mode AND no allocated children
- Otherwise: render with full details (children may have allocated values)
"""
"""Render object - summary mode non-path objects become <object>."""
indentStr = " " * depth
innerIndent = " " * (depth + 1)
@ -1455,26 +1455,19 @@ class JsonAnalyzer:
children = node.get('children', [])
isOnPath = id(node) in allocation.path_node_ids
# Check if any child has allocated values
hasAllocatedChildren = False
if children:
for child in children:
if child.get('type') in ('value', 'truncated_value'):
if id(child) in allocation.allocated_node_ids:
hasAllocatedChildren = True
break
else:
# For containers, check recursively if they have allocated descendants
if self._hasAllocatedDescendants(child, allocation):
hasAllocatedChildren = True
break
# NON-PATH containers: only render as <object> if summary_mode AND no allocated children
# PATH containers always render with structure
if not isOnPath and allocation.summary_mode and not hasAllocatedChildren:
if allocation.summary_mode and not isOnPath:
return f"{keyPrefix}<object>"
# Render with details (either PATH container or NON-PATH with allocated children)
# If object is incomplete and cut is directly here (no incomplete child),
# extract exact string from original JSON to preserve formatting
if not node.get('complete') and node.get('start_pos') is not None:
hasIncompleteChild = any(
child.get('type') in ('object', 'array') and not child.get('complete')
for child in children
)
if not hasIncompleteChild:
return self.jsonStr[node.get('start_pos'):]
if not children:
return f"{keyPrefix}{{}}" if node.get('complete') else f"{keyPrefix}{{"
@ -1495,26 +1488,8 @@ class JsonAnalyzer:
return '\n'.join(parts)
def _hasAllocatedDescendants(self, node: dict, allocation: BudgetAllocation) -> bool:
"""Check if node or any of its descendants have allocated values."""
nodeType = node.get('type')
if nodeType in ('value', 'truncated_value'):
return id(node) in allocation.allocated_node_ids
for child in node.get('children', []):
if self._hasAllocatedDescendants(child, allocation):
return True
return False
def _renderArrayV3(self, node: dict, depth: int, allocation: BudgetAllocation) -> str:
"""
Render array.
- PATH containers: always render with structure
- NON-PATH containers: only render as <array> if summary_mode AND no allocated children
- Otherwise: render with full details (children may have allocated values)
"""
"""Render array - summary mode non-path arrays become <array>."""
indentStr = " " * depth
innerIndent = " " * (depth + 1)
@ -1522,26 +1497,19 @@ class JsonAnalyzer:
children = node.get('children', [])
isOnPath = id(node) in allocation.path_node_ids
# Check if any child has allocated values
hasAllocatedChildren = False
if children:
for child in children:
if child.get('type') in ('value', 'truncated_value'):
if id(child) in allocation.allocated_node_ids:
hasAllocatedChildren = True
break
else:
# For containers, check recursively if they have allocated descendants
if self._hasAllocatedDescendants(child, allocation):
hasAllocatedChildren = True
break
# NON-PATH containers: only render as <array> if summary_mode AND no allocated children
# PATH containers always render with structure
if not isOnPath and allocation.summary_mode and not hasAllocatedChildren:
if allocation.summary_mode and not isOnPath:
return f"{keyPrefix}<array>"
# Render with details (either PATH container or NON-PATH with allocated children)
# If array is incomplete and cut is directly here (no incomplete child),
# extract exact string from original JSON to preserve formatting
if not node.get('complete') and node.get('start_pos') is not None:
hasIncompleteChild = any(
child.get('type') in ('object', 'array') and not child.get('complete')
for child in children
)
if not hasIncompleteChild:
return self.jsonStr[node.get('start_pos'):]
if not children:
return f"{keyPrefix}[]" if node.get('complete') else f"{keyPrefix}["
@ -1669,6 +1637,276 @@ def extractContinuationContexts(
return getJsonContinuationContext(truncatedJson)
# =============================================================================
# JSON REPAIR FUNCTIONS
# =============================================================================
def _repairInternalJsonErrors(jsonStr: str) -> str:
"""
Repair internal JSON errors WITHOUT touching incomplete structures at cut point.
This function fixes common internal JSON issues:
- Invalid escape sequences (e.g., \\x, \\u without proper hex)
- Unescaped control characters
- Invalid Unicode characters
- Trailing commas before closing brackets/braces
- Comments (// and /* */)
- Single quotes instead of double quotes (outside of string values)
- Unquoted keys
IMPORTANT: Does NOT modify incomplete structures at the end of the JSON.
Those are handled separately by structure closing logic.
Args:
jsonStr: JSON string that may have internal errors
Returns:
Repaired JSON string with internal errors fixed
"""
if not jsonStr or not jsonStr.strip():
return jsonStr
result = jsonStr
# Fix 1: Remove BOM and normalize whitespace at start
if result.startswith('\ufeff'):
result = result[1:]
# Fix 2: Normalize smart quotes to straight quotes
result = result.replace('"', '"').replace('"', '"')
result = result.replace(''', "'").replace(''', "'")
# Fix 3: Remove JavaScript-style comments (but be careful not to break strings)
result = _removeJsonComments(result)
# Fix 4: Fix invalid escape sequences
result = _fixInvalidEscapeSequences(result)
# Fix 5: Remove trailing commas before ] or }
result = _removeTrailingCommas(result)
# Fix 6: Fix unquoted keys (simple cases only)
result = _fixUnquotedKeys(result)
return result
def _removeJsonComments(jsonStr: str) -> str:
"""Remove JavaScript-style comments from JSON, preserving strings."""
result = []
i = 0
inString = False
escaped = False
while i < len(jsonStr):
char = jsonStr[i]
if escaped:
result.append(char)
escaped = False
i += 1
continue
if char == '\\' and inString:
result.append(char)
escaped = True
i += 1
continue
if char == '"':
inString = not inString
result.append(char)
i += 1
continue
if inString:
result.append(char)
i += 1
continue
# Check for // comment
if char == '/' and i + 1 < len(jsonStr) and jsonStr[i + 1] == '/':
# Skip until end of line
while i < len(jsonStr) and jsonStr[i] != '\n':
i += 1
continue
# Check for /* */ comment
if char == '/' and i + 1 < len(jsonStr) and jsonStr[i + 1] == '*':
i += 2
while i + 1 < len(jsonStr):
if jsonStr[i] == '*' and jsonStr[i + 1] == '/':
i += 2
break
i += 1
continue
result.append(char)
i += 1
return ''.join(result)
def _fixInvalidEscapeSequences(jsonStr: str) -> str:
"""Fix invalid escape sequences in JSON strings."""
result = []
i = 0
inString = False
while i < len(jsonStr):
char = jsonStr[i]
if char == '"' and (i == 0 or jsonStr[i - 1] != '\\'):
inString = not inString
result.append(char)
i += 1
continue
if inString and char == '\\' and i + 1 < len(jsonStr):
nextChar = jsonStr[i + 1]
# Valid JSON escape sequences: \", \\, \/, \b, \f, \n, \r, \t, \uXXXX
validEscapes = ['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u']
if nextChar in validEscapes:
if nextChar == 'u':
# Check if followed by 4 hex digits
if i + 5 < len(jsonStr) and all(c in '0123456789abcdefABCDEF' for c in jsonStr[i + 2:i + 6]):
result.append(char)
i += 1
continue
else:
# Invalid \u sequence - escape the backslash
result.append('\\')
result.append('\\')
i += 1
continue
else:
result.append(char)
i += 1
continue
else:
# Invalid escape - escape the backslash
result.append('\\')
result.append('\\')
i += 1
continue
result.append(char)
i += 1
return ''.join(result)
def _removeTrailingCommas(jsonStr: str) -> str:
"""Remove trailing commas before ] or } (not valid in JSON)."""
# Pattern: comma followed by whitespace and ] or }
result = re.sub(r',(\s*[}\]])', r'\1', jsonStr)
return result
def _fixUnquotedKeys(jsonStr: str) -> str:
"""
Fix simple unquoted keys in JSON objects.
Only handles simple cases to avoid breaking valid JSON.
"""
# Pattern: { or , followed by whitespace and an unquoted identifier and :
# Be conservative - only fix clear cases
result = []
i = 0
inString = False
escaped = False
while i < len(jsonStr):
char = jsonStr[i]
if escaped:
result.append(char)
escaped = False
i += 1
continue
if char == '\\' and inString:
result.append(char)
escaped = True
i += 1
continue
if char == '"':
inString = not inString
result.append(char)
i += 1
continue
if inString:
result.append(char)
i += 1
continue
# Check for unquoted key after { or ,
if char in '{,' and i + 1 < len(jsonStr):
result.append(char)
i += 1
# Skip whitespace
while i < len(jsonStr) and jsonStr[i] in ' \t\n\r':
result.append(jsonStr[i])
i += 1
if i >= len(jsonStr):
continue
# Check if next is an unquoted identifier (starts with letter or _)
if jsonStr[i] not in '"{[' and (jsonStr[i].isalpha() or jsonStr[i] == '_'):
# Collect the identifier
keyStart = i
while i < len(jsonStr) and (jsonStr[i].isalnum() or jsonStr[i] == '_'):
i += 1
key = jsonStr[keyStart:i]
# Skip whitespace
while i < len(jsonStr) and jsonStr[i] in ' \t\n\r':
i += 1
# Check if followed by :
if i < len(jsonStr) and jsonStr[i] == ':':
# This was an unquoted key - quote it
result.append('"')
result.append(key)
result.append('"')
else:
# Not a key, put back as-is
result.append(key)
continue
result.append(char)
i += 1
return ''.join(result)
def _tryParseJson(jsonStr: str) -> tuple:
"""
Try to parse JSON string and return (parsed, error).
Returns:
Tuple of (parsed_object, error_string)
- If successful: (parsed_object, None)
- If failed: (None, error_message)
"""
if not jsonStr or not jsonStr.strip():
return None, "Empty JSON string"
try:
parsed = json.loads(jsonStr)
return parsed, None
except json.JSONDecodeError as e:
return None, str(e)
except Exception as e:
return None, str(e)
# Convenience function with named results
def getContexts(
truncatedJson: str
@ -1678,28 +1916,86 @@ def getContexts(
Uses module constants BUDGET_LIMIT and OVERLAP_MAX_CHARS.
This function:
1. Extracts continuation contexts (overlap, hierarchy, completePart)
2. Tries to parse completePart as JSON
3. If parsing fails, repairs internal errors and retries
4. Sets jsonParsingSuccess to indicate if completePart is valid JSON
5. Sets overlapContext="" if JSON is complete (no cut point)
IMPORTANT: overlapContext="" signals that JSON is complete (no more data expected).
This happens when the original JSON is already valid (no structures needed closing).
Args:
truncatedJson: The truncated JSON string
Returns:
JsonContinuationContexts Pydantic model with:
- overlapContext: The innermost object/element containing the cut
Empty string "" if JSON is complete (no cut point)
- hierarchyContext: Full structure WITHOUT budget limitations (for internal use)
- hierarchyContextForPrompt: Full structure WITH budget limitations (for prompts)
- completePart: Valid JSON with all structures properly closed
- jsonParsingSuccess: True if completePart is valid parseable JSON
Example:
>>> json_str = '{"users": [{"name": "John", "bio": "Hello Wor'
>>> contexts = getContexts(json_str)
>>> print(contexts.overlapContext)
>>> print(contexts.hierarchyContext)
>>> print(contexts.hierarchyContextForPrompt)
>>> print(contexts.completePart)
>>> print(contexts.overlapContext) # Contains cut point context
>>> print(contexts.jsonParsingSuccess)
>>> complete_json = '{"users": [{"name": "John"}]}'
>>> contexts = getContexts(complete_json)
>>> print(contexts.overlapContext) # "" (empty - JSON is complete)
>>> print(contexts.jsonParsingSuccess) # True
"""
# First, check if original JSON is already complete (parseable without modification)
jsonIsComplete = False
if truncatedJson and truncatedJson.strip():
parsed, error = _tryParseJson(truncatedJson.strip())
if error is None:
jsonIsComplete = True
logger.debug("Original JSON is already complete (no cut point)")
# Extract contexts
overlap, hierarchy, hierarchyForPrompt, completePart = extractContinuationContexts(truncatedJson)
# If JSON is complete (no cut point), set overlapContext to empty string
# This signals that no more continuation is needed
if jsonIsComplete:
overlap = ""
logger.debug("Setting overlapContext='' (JSON is complete)")
# Try to parse completePart as JSON
jsonParsingSuccess = False
if completePart and completePart.strip():
# First attempt: parse as-is
parsed, error = _tryParseJson(completePart)
if error is None:
jsonParsingSuccess = True
else:
# Second attempt: repair internal errors and retry
logger.debug(f"Initial parse failed: {error}, attempting repair")
repairedCompletePart = _repairInternalJsonErrors(completePart)
parsed, error = _tryParseJson(repairedCompletePart)
if error is None:
# Repair succeeded - use repaired version
completePart = repairedCompletePart
jsonParsingSuccess = True
logger.debug("JSON repair successful")
else:
# Repair also failed - keep original completePart, mark as failed
logger.debug(f"JSON repair also failed: {error}")
jsonParsingSuccess = False
return JsonContinuationContexts(
overlapContext=overlap,
hierarchyContext=hierarchy,
hierarchyContextForPrompt=hierarchyForPrompt,
completePart=completePart
completePart=completePart,
jsonParsingSuccess=jsonParsingSuccess
)

View file

@ -126,170 +126,97 @@ class JsonSplitMergeTester12:
]
}
},
{
"name": "large_customers.json",
"data": self._createLargeCustomersData()
},
{
"name": "large_products.json",
"data": self._createLargeProductsData()
},
{
"name": "large_documents.json",
"data": self._createLargeDocumentsData()
},
{
"name": "table_example.json",
"data": self._loadTableJsonExample()
},
{
"name": "complete_json.json",
"data": {
"status": "complete",
"message": "This is a complete, valid JSON object",
"data": {
"items": [1, 2, 3, 4, 5],
"metadata": {
"version": "1.0",
"timestamp": "2025-01-05T12:00:00Z"
}
}
},
"isComplete": True # Flag to indicate this is complete JSON (not cut)
},
{
"name": "json_with_comments.json",
"data": None, # Will be set as string with comments
"jsonString": '''{
// This is a single-line comment
"name": "Test",
"value": 42,
/* This is a multi-line comment
spanning multiple lines */
"items": [1, 2, 3],
"nested": {
// Another comment
"key": "value"
}
}''',
"hasComments": True
},
{
"name": "json_with_trailing_comma.json",
"data": None, # Will be set as string with trailing comma
"jsonString": '''{
"name": "Test",
"value": 42,
"items": [1, 2, 3,],
"nested": {
"key": "value",
}
}''',
"hasTrailingComma": True
},
{
"name": "json_with_unquoted_keys.json",
"data": None, # Will be set as string with unquoted keys
"jsonString": '''{
name: "Test",
value: 42,
items: [1, 2, 3],
nested: {
key: "value"
}
}''',
"hasUnquotedKeys": True
},
{
"name": "json_with_invalid_escape.json",
"data": None, # Will be set as string with invalid escape
"jsonString": '''{
"name": "Test\\xInvalid",
"value": 42,
"description": "This has \\u invalid escape"
}''',
"hasInvalidEscape": True
},
{
"name": "json_mixed_errors.json",
"data": None, # Will be set as string with multiple errors
"jsonString": '''{
// Comment here
name: "Test", // Unquoted key
"value": 42,
"items": [1, 2, 3,], // Trailing comma
"description": "Has \\x invalid escape",
"nested": {
key: "value", // Unquoted key and trailing comma
}
}''',
"hasMixedErrors": True
}
]
return testFiles
def _createLargeCustomersData(self) -> Dict[str, Any]:
"""Create a large customers dataset for budget testing."""
customers = []
# Create 100 customers with long descriptions
for i in range(100):
customers.append({
"id": i + 1,
"name": f"Customer {i + 1}",
"email": f"customer{i+1}@example.com",
"phone": f"+1{5550000000 + i}",
"address": f"{100 + i} Main Street, City {i % 10}, State {i % 5}, ZIP {10000 + i}",
"description": f"This is a detailed description for customer {i + 1}. " * 10 +
f"They have been a loyal customer since {2000 + (i % 25)}. " +
f"Their preferences include various products and services. " * 5,
"orders": [
{
"orderId": f"ORD-{i+1}-{j+1}",
"date": f"2024-{(j % 12) + 1:02d}-{(j % 28) + 1:02d}",
"total": round(100.0 + (i * 10) + (j * 5), 2),
"items": [
{
"productId": f"PROD-{k+1}",
"quantity": (k % 5) + 1,
"price": round(10.0 + k * 2, 2)
}
for k in range(3)
]
}
for j in range(5)
],
"metadata": {
"created": f"2020-{(i % 12) + 1:02d}-{(i % 28) + 1:02d}",
"lastLogin": f"2024-{(i % 12) + 1:02d}-{(i % 28) + 1:02d}",
"tags": [f"tag-{i % 10}", f"category-{i % 5}", f"segment-{i % 3}"]
}
})
return {"customers": customers}
def _createLargeProductsData(self) -> Dict[str, Any]:
"""Create a large products dataset for budget testing."""
products = []
# Create 200 products with detailed information
categories = ["Electronics", "Clothing", "Books", "Home & Garden", "Sports", "Toys", "Automotive", "Health"]
for i in range(200):
category = categories[i % len(categories)]
products.append({
"id": f"PROD-{i+1:04d}",
"name": f"Product {i+1} - {category}",
"category": category,
"price": round(10.0 + (i * 2.5), 2),
"cost": round(5.0 + (i * 1.5), 2),
"inStock": i % 3 != 0,
"stockQuantity": (i % 100) * 10,
"description": f"This is a comprehensive product description for Product {i+1}. " * 15 +
f"It belongs to the {category} category and offers excellent value. " * 10 +
f"Features include: feature-1, feature-2, feature-3, and many more. " * 5,
"specifications": {
"weight": f"{1.0 + (i % 10)} kg",
"dimensions": f"{10 + (i % 20)}x{5 + (i % 15)}x{3 + (i % 10)} cm",
"color": ["red", "blue", "green", "black", "white"][i % 5],
"material": ["plastic", "metal", "wood", "fabric"][i % 4],
"warranty": f"{1 + (i % 5)} years"
},
"reviews": [
{
"userId": f"USER-{j+1}",
"rating": (j % 5) + 1,
"comment": f"Review {j+1} for product {i+1}: " + "This is a detailed review comment. " * 10,
"date": f"2024-{(j % 12) + 1:02d}-{(j % 28) + 1:02d}"
}
for j in range(3)
],
"relatedProducts": [f"PROD-{k+1:04d}" for k in range(max(0, i-2), min(200, i+3)) if k != i]
})
return {"products": products}
def _createLargeDocumentsData(self) -> Dict[str, Any]:
"""Create a large documents dataset for budget testing."""
documents = []
# Create 50 documents with nested structures
for i in range(50):
sections = []
for j in range(10):
elements = []
for k in range(5):
if k % 2 == 0:
elements.append({
"type": "heading",
"level": (k % 3) + 1,
"content": {
"text": f"Section {j+1} Heading {k+1} for Document {i+1}",
"style": "bold"
}
})
else:
elements.append({
"type": "paragraph",
"content": {
"text": f"This is paragraph {k+1} in section {j+1} of document {i+1}. " * 20 +
f"It contains detailed information about various topics. " * 15 +
f"The content is structured and well-organized. " * 10
}
})
sections.append({
"id": f"sec-{i+1}-{j+1}",
"title": f"Section {j+1}",
"content_type": "mixed",
"elements": elements,
"metadata": {
"created": f"2024-{(j % 12) + 1:02d}-{(j % 28) + 1:02d}",
"modified": f"2024-{(j % 12) + 1:02d}-{(j % 28) + 1:02d}",
"author": f"Author-{(i % 10) + 1}",
"tags": [f"tag-{j % 10}", f"category-{i % 5}"]
}
})
documents.append({
"id": f"doc-{i+1:03d}",
"title": f"Document {i+1} - Comprehensive Report",
"description": f"This is a comprehensive document with detailed information. " * 30 +
f"It covers multiple topics and sections. " * 20 +
f"The content is extensive and well-structured. " * 15,
"sections": sections,
"metadata": {
"created": f"2024-{(i % 12) + 1:02d}-{(i % 28) + 1:02d}",
"modified": f"2024-{(i % 12) + 1:02d}-{(i % 28) + 1:02d}",
"author": f"Author-{(i % 10) + 1}",
"version": f"1.{(i % 10)}",
"status": ["draft", "review", "published"][i % 3],
"tags": [f"tag-{i % 20}" for _ in range(5)]
}
})
return {
"metadata": {
"title": "Large Document Collection",
"description": "A collection of 50 comprehensive documents for testing budget limits",
"totalDocuments": 50,
"created": "2024-01-01"
},
"documents": documents
}
def _loadTableJsonExample(self) -> Dict[str, Any]:
"""Load the table JSON example from the debug prompts file."""
try:
@ -460,23 +387,51 @@ class JsonSplitMergeTester12:
async def testJsonSplitMerge(self, jsonFile: Dict[str, Any]) -> Dict[str, Any]:
"""Test splitting and merging a single JSON file."""
fileName = jsonFile["name"]
originalData = jsonFile["data"]
# Check if this is a complete JSON test (no cut)
isComplete = jsonFile.get("isComplete", False)
# Check if this is a JSON string with errors (not from data dict)
jsonString = jsonFile.get("jsonString")
if jsonString:
# Use the provided JSON string directly (may have errors)
originalJsonString = jsonString
originalData = None # No original data for error tests
else:
# Convert data dict to JSON string
originalData = jsonFile["data"]
originalJsonString = json.dumps(originalData, indent=2, ensure_ascii=False)
originalSize = len(originalJsonString)
self._log("")
self._log("="*80)
self._log(f"TESTING JSON SPLIT AND MERGE: {fileName}")
testType = "COMPLETE JSON" if isComplete else ("JSON WITH ERRORS" if jsonString else "SPLIT JSON")
self._log(f"TESTING {testType}: {fileName}")
self._log("="*80)
# Convert to JSON string
originalJsonString = json.dumps(originalData, indent=2, ensure_ascii=False)
originalSize = len(originalJsonString)
# Log original JSON
self._log("")
self._log("="*80)
self._log("ORIGINAL JSON")
self._log("="*80)
self._log(f"JSON length: {originalSize} characters")
if isComplete:
self._log(" ⚠️ This is COMPLETE JSON (not cut) - testing overlapContext='' detection")
if jsonString:
errorType = []
if jsonFile.get("hasComments"):
errorType.append("comments")
if jsonFile.get("hasTrailingComma"):
errorType.append("trailing commas")
if jsonFile.get("hasUnquotedKeys"):
errorType.append("unquoted keys")
if jsonFile.get("hasInvalidEscape"):
errorType.append("invalid escapes")
if jsonFile.get("hasMixedErrors"):
errorType.append("mixed errors")
if errorType:
self._log(f" ⚠️ This JSON has errors: {', '.join(errorType)} - testing repair function")
self._log("")
self._log("Full JSON content:")
self._log("-"*80)
@ -491,39 +446,63 @@ class JsonSplitMergeTester12:
for line in jsonLines:
self._log(line)
# Split JSON at random position (simulating AI response cut)
self._log("")
self._log("="*80)
self._log("SPLITTING JSON AT RANDOM POSITION (SIMULATING AI RESPONSE CUT)")
self._log("="*80)
# Find random cut position (not at start or end)
import random
minCutPos = max(100, originalSize // 10) # At least 10% from start
maxCutPos = min(originalSize - 100, originalSize * 9 // 10) # At least 10% from end
cutPosition = random.randint(minCutPos, maxCutPos)
# Get part from start to cut
partContent = originalJsonString[:cutPosition]
self._log("")
self._log("="*80)
self._log("PART (from start to cut):")
self._log("="*80)
self._log(f"Cut position: {cutPosition} characters")
self._log(f"Part length: {len(partContent)} characters")
self._log("")
self._log("Part content:")
partLines = partContent.split('\n')
if len(partLines) > 30:
for line in partLines[:15]:
self._log(f" {line}")
self._log(f" ... ({len(partLines) - 30} lines omitted) ...")
for line in partLines[-15:]:
self._log(f" {line}")
# Handle complete JSON, JSON with errors, vs split JSON
if isComplete or jsonString:
# For complete JSON or JSON with errors, use the full string (no cut)
# We want to test repair on the full error-containing JSON
partContent = originalJsonString
cutPosition = None # No cut
self._log("")
self._log("="*80)
if isComplete:
self._log("COMPLETE JSON TEST (NO CUT)")
self._log("="*80)
self._log(" Testing that getContexts() detects complete JSON and sets overlapContext=''")
else:
self._log("JSON WITH ERRORS TEST (NO CUT)")
self._log("="*80)
self._log(" Testing that getContexts() repairs the errors and produces valid JSON")
else:
for line in partLines:
self._log(f" {line}")
# Split JSON at random position (simulating AI response cut)
self._log("")
self._log("="*80)
self._log("SPLITTING JSON AT RANDOM POSITION (SIMULATING AI RESPONSE CUT)")
self._log("="*80)
# Find random cut position (not at start or end)
import random
minCutPos = max(100, originalSize // 10) # At least 10% from start
maxCutPos = min(originalSize - 100, originalSize * 9 // 10) # At least 10% from end
# Ensure valid range
if maxCutPos <= minCutPos:
# For small JSON, just cut in the middle
cutPosition = originalSize // 2
else:
cutPosition = random.randint(minCutPos, maxCutPos)
# Get part from start to cut
partContent = originalJsonString[:cutPosition]
if not isComplete:
self._log("")
self._log("="*80)
self._log("PART (from start to cut):")
self._log("="*80)
self._log(f"Cut position: {cutPosition} characters")
self._log(f"Part length: {len(partContent)} characters")
self._log("")
self._log("Part content:")
partLines = partContent.split('\n')
if len(partLines) > 30:
for line in partLines[:15]:
self._log(f" {line}")
self._log(f" ... ({len(partLines) - 30} lines omitted) ...")
for line in partLines[-15:]:
self._log(f" {line}")
else:
for line in partLines:
self._log(f" {line}")
# Generate contexts using getContexts()
self._log("")
@ -590,12 +569,23 @@ class JsonSplitMergeTester12:
for line in completeLines:
self._log(f" {line}")
# Validate completePart as JSON
# Validate completePart as JSON and check overlapContext
self._log("")
self._log("="*80)
self._log("VALIDATING COMPLETE PART AS JSON:")
self._log("VALIDATION RESULTS:")
self._log("="*80)
# Check overlapContext for complete JSON
if isComplete:
if contexts.overlapContext == "":
self._log(" ✅ overlapContext is empty (correct for complete JSON)")
else:
self._log(f" ❌ overlapContext is NOT empty: '{contexts.overlapContext[:50]}...'")
self._log(" Expected empty string for complete JSON")
# Validate completePart as JSON
self._log("")
self._log("VALIDATING COMPLETE PART AS JSON:")
isValidJson = False
parsedCompletePart = None
jsonError = None
@ -606,34 +596,40 @@ class JsonSplitMergeTester12:
self._log(" ✅ completePart is valid JSON")
self._log(f" Parsed type: {type(parsedCompletePart).__name__}")
# Compare with truncated JSON (not original) - parse the truncated part to compare
from modules.shared.jsonUtils import closeJsonStructures, tryParseJson
# For error tests, verify repair worked
if jsonString:
self._log(" ✅ JSON repair successful - errors were fixed")
# Try to parse the truncated JSON part (with structures closed)
truncatedClosed = closeJsonStructures(partContent)
truncatedParsed, truncatedError, _ = tryParseJson(truncatedClosed)
if truncatedParsed is not None:
# Compare completePart with the parsed truncated JSON
if isinstance(parsedCompletePart, dict) and isinstance(truncatedParsed, dict):
comparison = self.compareJson(truncatedParsed, parsedCompletePart)
self._log(f" Comparison with truncated JSON (at cut position {cutPosition}):")
self._log(f" Exact match: {comparison['exactMatch']}")
self._log(f" Size match: {comparison['sizeMatch']}")
if comparison['differences']:
self._log(f" Differences found: {len(comparison['differences'])}")
for diff in comparison['differences'][:10]: # Show first 10 differences
self._log(f" - {diff}")
if len(comparison['differences']) > 10:
self._log(f" ... ({len(comparison['differences']) - 10} more differences)")
# For split JSON, compare with truncated JSON
if not isComplete and not jsonString:
# Compare with truncated JSON (not original) - parse the truncated part to compare
from modules.shared.jsonUtils import closeJsonStructures, tryParseJson
# Try to parse the truncated JSON part (with structures closed)
truncatedClosed = closeJsonStructures(partContent)
truncatedParsed, truncatedError, _ = tryParseJson(truncatedClosed)
if truncatedParsed is not None:
# Compare completePart with the parsed truncated JSON
if isinstance(parsedCompletePart, dict) and isinstance(truncatedParsed, dict):
comparison = self.compareJson(truncatedParsed, parsedCompletePart)
self._log(f" Comparison with truncated JSON (at cut position {cutPosition}):")
self._log(f" Exact match: {comparison['exactMatch']}")
self._log(f" Size match: {comparison['sizeMatch']}")
if comparison['differences']:
self._log(f" Differences found: {len(comparison['differences'])}")
for diff in comparison['differences'][:10]: # Show first 10 differences
self._log(f" - {diff}")
if len(comparison['differences']) > 10:
self._log(f" ... ({len(comparison['differences']) - 10} more differences)")
else:
self._log(" No differences found - completePart matches truncated JSON structure")
elif isinstance(parsedCompletePart, list) and isinstance(truncatedParsed, list):
self._log(f" Both are lists: truncated={len(truncatedParsed)} items, completePart={len(parsedCompletePart)} items")
else:
self._log(" No differences found - completePart matches truncated JSON structure")
elif isinstance(parsedCompletePart, list) and isinstance(truncatedParsed, list):
self._log(f" Both are lists: truncated={len(truncatedParsed)} items, completePart={len(parsedCompletePart)} items")
self._log(f" Different types: truncated={type(truncatedParsed).__name__}, completePart={type(parsedCompletePart).__name__}")
else:
self._log(f" Different types: truncated={type(truncatedParsed).__name__}, completePart={type(parsedCompletePart).__name__}")
else:
self._log(f" Could not parse truncated JSON for comparison (error: {truncatedError})")
self._log(f" Could not parse truncated JSON for comparison (error: {truncatedError})")
except json.JSONDecodeError as e:
isValidJson = False
@ -641,13 +637,15 @@ class JsonSplitMergeTester12:
self._log(f" ❌ completePart is NOT valid JSON")
self._log(f" Error: {jsonError}")
self._log(f" Error position: line {e.lineno}, column {e.colno}")
if jsonString:
self._log(" ❌ JSON repair FAILED - errors were not fixed")
# Return test results
return {
result = {
"success": isValidJson,
"fileName": fileName,
"originalSize": originalSize,
"cutPosition": cutPosition,
"cutPosition": cutPosition if not isComplete else None,
"partSize": len(partContent),
"overlapContextSize": len(contexts.overlapContext),
"hierarchyContextSize": len(contexts.hierarchyContext),
@ -655,8 +653,23 @@ class JsonSplitMergeTester12:
"completePartSize": len(contexts.completePart),
"isValidJson": isValidJson,
"jsonError": jsonError,
"parsedCompletePart": parsedCompletePart is not None
"parsedCompletePart": parsedCompletePart is not None,
"jsonParsingSuccess": contexts.jsonParsingSuccess
}
# Add complete JSON specific checks
if isComplete:
result["overlapContextIsEmpty"] = contexts.overlapContext == ""
result["isComplete"] = True
# For complete JSON, success means overlapContext is empty AND valid JSON
result["success"] = isValidJson and (contexts.overlapContext == "")
# Add error test specific checks
if jsonString:
result["hasErrors"] = True
result["repairSuccess"] = isValidJson
return result
async def testAllJsonFiles(self) -> Dict[str, Any]:
"""Test splitting and merging all test JSON files."""
@ -723,11 +736,31 @@ class JsonSplitMergeTester12:
if result.get("success"):
successCount += 1
isValidJson = result.get("isValidJson", False)
if isValidJson:
print(f"{fileName:30s}: Valid JSON - completePart parsed successfully")
isComplete = result.get("isComplete", False)
hasErrors = result.get("hasErrors", False)
if isComplete:
overlapEmpty = result.get("overlapContextIsEmpty", False)
if isValidJson and overlapEmpty:
print(f"{fileName:30s}: Complete JSON - overlapContext='' and valid JSON")
elif not overlapEmpty:
print(f"⚠️ {fileName:30s}: Complete JSON but overlapContext not empty")
else:
jsonError = result.get("jsonError", "Unknown error")
print(f"⚠️ {fileName:30s}: Complete JSON but not valid - {jsonError}")
elif hasErrors:
repairSuccess = result.get("repairSuccess", False)
if repairSuccess:
print(f"{fileName:30s}: JSON with errors - repair successful")
else:
jsonError = result.get("jsonError", "Unknown error")
print(f"{fileName:30s}: JSON with errors - repair failed - {jsonError}")
else:
jsonError = result.get("jsonError", "Unknown error")
print(f"⚠️ {fileName:30s}: Contexts generated but completePart is not valid JSON - {jsonError}")
if isValidJson:
print(f"{fileName:30s}: Valid JSON - completePart parsed successfully")
else:
jsonError = result.get("jsonError", "Unknown error")
print(f"⚠️ {fileName:30s}: Contexts generated but completePart is not valid JSON - {jsonError}")
else:
error = result.get("error", "Unknown error")
print(f"{fileName:30s}: FAILED - {error}")