Skip to main content

lc_core/output_parsers/
structured_parser.rs

1use async_trait::async_trait;
2use futures_util::Stream;
3use serde::de::DeserializeOwned;
4use std::collections::HashMap;
5use std::marker::PhantomData;
6use std::pin::Pin;
7
8use super::base::{BaseOutputParser, OutputParserError, OutputParserResult};
9use crate::runnables::{Runnable, RunnableConfig};
10
11/// 结构化输出解析器
12///
13/// 将 LLM 输出的键值对格式(每行一个 `key: value`)解析为 HashMap。
14/// 适用于 LLM 以非 JSON 格式输出结构化信息的场景。
15///
16/// # 格式
17/// 输入格式应为每行一个 `key: value`,例如:
18/// ```text
19/// 姓名: 张三
20/// 年龄: 28
21/// 城市: 北京
22/// ```
23///
24/// # 示例
25/// ```ignore
26/// use langchainrust::output_parsers::StructuredOutputParser;
27///
28/// let parser = StructuredOutputParser::new();
29/// let result = parser.parse("姓名: 张三\n年龄: 28").await?;
30/// assert_eq!(result.get("姓名").unwrap(), "张三");
31/// ```
32pub struct StructuredOutputParser {
33    /// 键值对之间的分隔符
34    separator: char,
35}
36
37impl StructuredOutputParser {
38    pub fn new() -> Self {
39        Self { separator: ':' }
40    }
41
42    /// 使用自定义分隔符创建解析器
43    pub fn with_separator(separator: char) -> Self {
44        Self { separator }
45    }
46}
47
48impl Default for StructuredOutputParser {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54#[async_trait]
55impl BaseOutputParser<HashMap<String, String>> for StructuredOutputParser {
56    async fn parse(&self, text: &str) -> OutputParserResult<HashMap<String, String>> {
57        let mut map = HashMap::new();
58
59        for line in text.lines() {
60            let line = line.trim();
61            if line.is_empty() {
62                continue;
63            }
64
65            if let Some(pos) = line.find(self.separator) {
66                let key = line[..pos].trim().to_string();
67                let value = line[pos + 1..].trim().to_string();
68
69                if !key.is_empty() {
70                    map.insert(key, value);
71                }
72            }
73        }
74
75        Ok(map)
76    }
77
78    fn get_format_instructions(&self) -> String {
79        format!(
80            "请按以下格式输出(每行一个键值对,使用 '{}' 分隔):\n键{}值",
81            self.separator, self.separator
82        )
83    }
84}
85
86#[async_trait]
87impl Runnable<String, HashMap<String, String>> for StructuredOutputParser {
88    type Error = OutputParserError;
89
90    async fn invoke(
91        &self,
92        input: String,
93        _config: Option<RunnableConfig>,
94    ) -> Result<HashMap<String, String>, Self::Error> {
95        self.parse(&input).await
96    }
97
98    async fn stream(
99        &self,
100        input: String,
101        _config: Option<RunnableConfig>,
102    ) -> Result<
103        Pin<Box<dyn Stream<Item = Result<HashMap<String, String>, Self::Error>> + Send>>,
104        Self::Error,
105    > {
106        let result = self.parse(&input).await?;
107        let stream = futures_util::stream::once(async move { Ok(result) });
108        Ok(Box::pin(stream))
109    }
110}
111
112/// 类型化输出解析器
113///
114/// 将 LLM 输出的 JSON 字符串解析为指定的 Rust 结构体。
115/// 相当于 Python LangChain 的 `PydanticOutputParser`(使用 serde 替代 pydantic)。
116///
117/// 需要目标类型实现 `serde::Deserialize`。
118///
119/// # 示例
120/// ```ignore
121/// use serde::Deserialize;
122/// use langchainrust::output_parsers::TypedOutputParser;
123///
124/// #[derive(Deserialize, Debug, PartialEq)]
125/// struct Person {
126///     name: String,
127///     age: u32,
128/// }
129///
130/// let parser = TypedOutputParser::<Person>::new();
131/// let person = parser.parse(r#"{"name": "Alice", "age": 30}"#).await?;
132/// assert_eq!(person.name, "Alice");
133/// ```
134pub struct TypedOutputParser<T> {
135    _phantom: PhantomData<T>,
136}
137
138impl<T> TypedOutputParser<T> {
139    pub fn new() -> Self {
140        Self {
141            _phantom: PhantomData,
142        }
143    }
144}
145
146impl<T: DeserializeOwned> Default for TypedOutputParser<T> {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152#[async_trait]
153impl<T: DeserializeOwned + Send + Sync + 'static> BaseOutputParser<T> for TypedOutputParser<T> {
154    async fn parse(&self, text: &str) -> OutputParserResult<T> {
155        let text = text.trim();
156
157        // 尝试从 Markdown 代码块中提取 JSON
158        let json_str = Self::extract_from_markdown(text).unwrap_or(text);
159
160        // 先尝试解析为 Value 验证合法性
161        serde_json::from_str::<serde_json::Value>(json_str)
162            .map_err(|e| OutputParserError::JsonError(format!("输入不是合法 JSON:{}", e)))?;
163
164        // 反序列化为目标类型
165        serde_json::from_str::<T>(json_str).map_err(|e| {
166            OutputParserError::TypeError(format!(
167                "类型反序列化失败(请检查 JSON 字段是否匹配):{}",
168                e
169            ))
170        })
171    }
172}
173
174impl<T: DeserializeOwned> TypedOutputParser<T> {
175    /// 从 Markdown 代码块中提取 JSON 字符串
176    fn extract_from_markdown(text: &str) -> Option<&str> {
177        // 尝试 ```json ... ```
178        if let Some(start) = text.find("```json") {
179            let after = &text[start + 7..];
180            if let Some(end) = after.find("```") {
181                return Some(after[..end].trim());
182            }
183        }
184        // 尝试 ``` ... ```
185        if let Some(start) = text.find("```") {
186            let after = &text[start + 3..];
187            let after = after.trim();
188            let skip = after.find('\n').unwrap_or(0);
189            let after = &after[skip..].trim();
190            if let Some(end) = after.find("```") {
191                return Some(after[..end].trim());
192            }
193        }
194        None
195    }
196
197    #[allow(dead_code)]
198    fn get_format_instructions(&self) -> String {
199        "请输出符合以下 JSON Schema 的合法 JSON:\n```json\n{\n  // 目标类型的字段定义\n}\n```"
200            .to_string()
201    }
202}
203
204#[async_trait]
205impl<T: DeserializeOwned + Send + Sync + 'static> Runnable<String, T> for TypedOutputParser<T> {
206    type Error = OutputParserError;
207
208    async fn invoke(
209        &self,
210        input: String,
211        _config: Option<RunnableConfig>,
212    ) -> Result<T, Self::Error> {
213        self.parse(&input).await
214    }
215
216    async fn stream(
217        &self,
218        input: String,
219        _config: Option<RunnableConfig>,
220    ) -> Result<Pin<Box<dyn Stream<Item = Result<T, Self::Error>> + Send>>, Self::Error> {
221        let result = self.parse(&input).await?;
222        let stream = futures_util::stream::once(async move { Ok(result) });
223        Ok(Box::pin(stream))
224    }
225}