Skip to main content

zeph_common/
llm_response.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Helpers for extracting structured content embedded in free-form LLM prose.
5//!
6//! LLMs are prompted to return JSON but often wrap it in prose or markdown fences.
7//! This module provides the bracket-matching extraction step shared by callers that
8//! parse a JSON array out of a raw LLM response; each caller keeps its own policy for
9//! what to do when extraction fails (empty-result fallback vs. hard error).
10
11/// Extract the substring spanning the first `[` and the last `]` in `raw`.
12///
13/// Returns `None` when `raw` has no `[`, no `]` after it, or when the `]` does not come
14/// after the `[` (e.g. `"] before ["`). Does not validate that the slice is valid JSON —
15/// callers must still attempt to parse the returned slice.
16///
17/// # Examples
18///
19/// ```
20/// use zeph_common::llm_response::extract_json_array_slice;
21///
22/// assert_eq!(
23///     extract_json_array_slice("Sure, here you go: [1, 2, 3] — hope that helps!"),
24///     Some("[1, 2, 3]")
25/// );
26/// assert_eq!(extract_json_array_slice("no brackets here"), None);
27/// ```
28#[must_use]
29pub fn extract_json_array_slice(raw: &str) -> Option<&str> {
30    let start = raw.find('[')?;
31    // Search only the suffix from `start` onward, so a stray `]` earlier in `raw`
32    // (before any `[`) can never produce an inverted (end < start) range.
33    let end = start + raw[start..].rfind(']')?;
34    Some(&raw[start..=end])
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn extracts_array_wrapped_in_prose() {
43        assert_eq!(
44            extract_json_array_slice("Sure! [1, 2, 3] there you go."),
45            Some("[1, 2, 3]")
46        );
47    }
48
49    #[test]
50    fn extracts_bare_array() {
51        assert_eq!(extract_json_array_slice("[]"), Some("[]"));
52    }
53
54    #[test]
55    fn none_when_no_open_bracket() {
56        assert_eq!(extract_json_array_slice("no brackets"), None);
57    }
58
59    #[test]
60    fn none_when_no_close_bracket_after_open() {
61        assert_eq!(extract_json_array_slice("] then ["), None);
62    }
63
64    #[test]
65    fn none_when_only_close_bracket() {
66        assert_eq!(extract_json_array_slice("no open bracket]"), None);
67    }
68}