feat: production-grade upgrade — streaming, structured JSON, metrics, safe prompts#8
Conversation
availableTextBudget returns tokens in token mode, but SentenceAwareChunker expects words. Add fallbackWordTarget helper that divides by a conservative tokens-per-word ratio before re-splitting, preventing oversized fallback chunks. Made-with: Cursor
… prompt builder Add token-accurate MLX budgeting (MLXTokenCounter, MLXTokenAwareBackend), streaming support (StreamingLLMBackend, ChainEvent), structured JSON extraction (runJSON, StructuredOutputError), rich results (ChainResult, ChainMetrics), safe prompt rendering (ChainPromptBuilder, PromptStyle), CodeBlockAwareChunker, and 87 new tests (194 total). All changes are additive — existing public APIs preserved. Remove stale process docs (pr-body.md, pr-summary.md, repo-adoption-metadata.md, repo-improvements.md). Made-with: Cursor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 45dc7e1. Configure here.
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
MapReduceChain stream ignores caller's progress parameter
Medium Severity
MapReduceChain.stream() accepts a progress parameter but never uses it. Instead, it creates a local progressRelay and passes that to runWithMetadata. The caller's ChainProgress object never receives any updates. This is inconsistent with StuffChain.stream(), which correctly reports to the caller's progress. When AdaptiveChain routes to MapReduceChain.stream, the caller's progress observer is silently dropped.
Reviewed by Cursor Bugbot for commit 45dc7e1. Configure here.
| } | ||
| } | ||
| acc.mapCallCount += results.count | ||
| return results |
There was a problem hiding this comment.
Concurrent map omits token count tracking in metrics
Medium Severity
concurrentMap only updates acc.mapCallCount after collecting results but never accumulates acc.inputTokens or acc.outputTokens. In contrast, sequentialMap tracks both input and output token counts per call when using a TokenAwareBackend. This means ChainMetrics.estimatedInputTokens and estimatedOutputTokens will be missing map-phase tokens whenever maxConcurrentMapTasks > 1.
Reviewed by Cursor Bugbot for commit 45dc7e1. Configure here.
…racks token counts stream() now forwards progress updates to the caller's ChainProgress in addition to yielding them as ChainEvent values. concurrentMap now accumulates estimatedInputTokens and estimatedOutputTokens, matching sequentialMap behavior. Fixes both issues flagged by Cursor Bugbot on PR offlyn-ai#8. Made-with: Cursor


Summary
MLXTokenAwareBackendwrapping a realTokenizerStreamingLLMBackendprotocol withChainEventasync streamrunJSON<T: Decodable>(...)with retry and code-fence strippingChainResultwith source chunks andChainMetrics(timing, call counts, token estimates)ChainPromptBuilderwith opt-in<source>tag wrappingCodeBlockAwareChunkerfor Markdown/code-heavy notespr-body.md,pr-summary.md,repo-adoption-metadata.md,repo-improvements.md)Why this matters
Turns mlx-swift-chain from a chain orchestration layer into a production-grade document processing package. Every new feature is opt-in with backward-compatible defaults (
.rawprompt style,run()still returnsString). Designed for real app workflows: meeting transcript → action items, crash report triage, log analysis, Markdown summarization.New public types (11)
ChainResult.text,.sourceChunks,.metricsChainMetricsChainEvent.chunk(String),.progress(Update),.result(ChainResult)MLXTokenCounterTokenCounterbacked by a real MLXLMCommonTokenizerMLXTokenAwareBackendTokenAwareBackend+StreamingLLMBackendconformanceStreamingLLMBackendLLMBackendwith token-by-token streamingChainPromptBuilder<source>tagsPromptStyle.raw(legacy) or.delimited(<source>tag wrapping)CodeBlockAwareChunkerStructuredOutputError.invalidJSON,.schemaMismatch,.retriesExhaustedJSONRepairHelper(internal)Modified public types
DocumentChainrunWithMetadata()andstream()protocol requirements with default implementationsChainExecutionOptionspromptStylefield (default.raw)ChainProgress.UpdatepartialMetricsfieldChainRunnerchainResult,partialText,runStreaming()MLXBackendStreamingLLMBackendconformanceBackward compatibility
All existing
run()signatures, protocols, and types are unchanged. New protocol requirements onDocumentChainhave default implementations, so existing conformances compile without changes. The existing 107 tests continue to pass alongside the 87 new tests.Test plan
swift build— 0 errors, 0 warningsswift test— 194 tests, 0 failuresMade with Cursor
Note
Medium Risk
Adds new execution paths (streaming, prompt rendering, structured JSON retries) and expands the
DocumentChainsurface area; behavior remains backward-compatible by default but changes touch core chain execution and prompt construction.Overview
Introduces streaming chain execution via
StreamingLLMBackendandChainEvent, withMLXBackend/MLXTokenAwareBackendstreaming support and SwiftUIChainRunner.runStreaming(...)+partialText.Adds rich results + metrics:
runWithMetadata(...)returnsChainResult(text + source chunks + optionalChainMetrics), chains now accumulate call counts/timing and (whenTokenAwareBackendis available) token estimates.Centralizes prompt construction in
ChainPromptBuilderwith a newPromptStyle(.rawdefault,.delimited<source>wrapping) wired throughChainExecutionOptions, and adds structured JSON extraction viaDocumentChain.runJSON(...)with deterministic JSON repair + retry-on-decode-failure.Improves long-document handling with token-accurate budgeting (
MLXTokenCounter,MLXTokenAwareBackend) and more conservative token-to-word fallback rechunking, addsCodeBlockAwareChunkerfor fenced-code-safe Markdown splitting, expands tests significantly, and removes stale process docs underdocs/.Reviewed by Cursor Bugbot for commit 45dc7e1. Bugbot is set up for automated code reviews on this repo. Configure here.