lc_core/output_parsers/
structured_parser.rs1use 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
11pub struct StructuredOutputParser {
33 separator: char,
35}
36
37impl StructuredOutputParser {
38 pub fn new() -> Self {
39 Self { separator: ':' }
40 }
41
42 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
112pub 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 let json_str = Self::extract_from_markdown(text).unwrap_or(text);
159
160 serde_json::from_str::<serde_json::Value>(json_str)
162 .map_err(|e| OutputParserError::JsonError(format!("输入不是合法 JSON:{}", e)))?;
163
164 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 fn extract_from_markdown(text: &str) -> Option<&str> {
177 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 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}