Skip to main content

otherone_agent/
response_parser.rs

1// 作用:解析各 AI 提供商的响应为统一格式
2// 关联:被 invoke_agent 调用,提取 content、role、token_consumption、tools、thinking
3// 预期结果:返回 provider 无关的统一 ParsedResponse
4
5use otherone_ai::types::{ChatResponse, ParsedResponse, ToolCallsWrapper};
6
7/// 解析 AI 模型的响应
8/// 作用:根据 provider 类型解析不同格式的响应
9/// 关联:被 invoke_agent 调用
10/// 预期结果:返回统一格式的 ParsedResponse
11pub fn parse_ai_response(
12    response: &ChatResponse,
13    provider: &otherone_ai::types::ProviderType,
14) -> Result<ParsedResponse, String> {
15    match provider {
16        otherone_ai::types::ProviderType::OpenAI
17        | otherone_ai::types::ProviderType::OpenRouter
18        | otherone_ai::types::ProviderType::Local => parse_openai_response(response),
19        otherone_ai::types::ProviderType::Fetch => parse_fetch_response(response),
20        otherone_ai::types::ProviderType::Anthropic => parse_anthropic_response(response),
21    }
22}
23
24/// 解析 OpenAI 的响应格式
25fn parse_openai_response(response: &ChatResponse) -> Result<ParsedResponse, String> {
26    if response.choices.is_empty() {
27        return Err("Invalid OpenAI response format: missing choices".to_string());
28    }
29
30    let choice = &response.choices[0];
31    let message = choice.message.as_ref().unwrap();
32    let content = message.content.as_deref().unwrap_or("");
33    let role = message.role.as_deref().unwrap_or("assistant");
34
35    let token_consumption = response
36        .usage
37        .as_ref()
38        .map(|u| u.total_tokens.unwrap_or(0))
39        .unwrap_or(0);
40
41    let tools = message.tool_calls.as_ref().map(|tc| ToolCallsWrapper {
42        tool_calls: tc.clone(),
43    });
44
45    Ok(ParsedResponse {
46        content: content.to_string(),
47        role: role.to_string(),
48        token_consumption,
49        tools,
50        thinking: None,
51        raw_response: None,
52    })
53}
54
55/// 解析 Anthropic 的响应格式
56fn parse_anthropic_response(response: &ChatResponse) -> Result<ParsedResponse, String> {
57    if response.choices.is_empty() {
58        return Err("Invalid Anthropic response format: missing choices".to_string());
59    }
60
61    let choice = &response.choices[0];
62    let message = choice.message.as_ref().unwrap();
63    let content = message.content.as_deref().unwrap_or("");
64    let role = message.role.as_deref().unwrap_or("assistant");
65
66    let token_consumption = response
67        .usage
68        .as_ref()
69        .map(|u| u.total_tokens.unwrap_or(0))
70        .unwrap_or(0);
71
72    Ok(ParsedResponse {
73        content: content.to_string(),
74        role: role.to_string(),
75        token_consumption,
76        tools: None,
77        thinking: None,
78        raw_response: None,
79    })
80}
81
82/// 解析 Fetch 的响应格式
83fn parse_fetch_response(response: &ChatResponse) -> Result<ParsedResponse, String> {
84    // Fetch 响应预期与 OpenAI 格式兼容
85    parse_openai_response(response)
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use otherone_ai::types::{ChatResponse, Choice, ResponseMessage, Usage};
92
93    #[test]
94    fn test_parse_openai_response() {
95        let response = ChatResponse {
96            id: Some("chat-123".to_string()),
97            object: Some("chat.completion".to_string()),
98            created: Some(1234567890),
99            model: Some("gpt-4".to_string()),
100            choices: vec![Choice {
101                index: 0,
102                message: Some(ResponseMessage {
103                    role: Some("assistant".to_string()),
104                    content: Some("Hello!".to_string()),
105                    tool_calls: None,
106                }),
107                delta: None,
108                finish_reason: Some("stop".to_string()),
109            }],
110            usage: Some(Usage {
111                prompt_tokens: Some(10),
112                completion_tokens: Some(5),
113                total_tokens: Some(15),
114            }),
115        };
116
117        let parsed = parse_openai_response(&response).unwrap();
118        assert_eq!(parsed.content, "Hello!");
119        assert_eq!(parsed.role, "assistant");
120        assert_eq!(parsed.token_consumption, 15);
121        assert!(parsed.tools.is_none());
122    }
123}