agent_context/message.rs
1use serde::Serialize;
2use serde::de::DeserializeOwned;
3
4use crate::Role;
5
6/// 后端消息类型必须实现此 trait。
7///
8/// `AgentContext` 不感知消息的具体内容结构,仅通过此 trait 获取两个关键信息:
9///
10/// - **角色**:用于按角色筛选和保留
11/// - **推理保留策略**:用于格式转换时决定是否剥离 `reasoning_content`
12///
13/// ## 实现要求
14///
15/// 由于孤儿规则,你无法为外部类型直接实现此 trait。需要在你的 crate 中
16/// 创建 newtype 包装器:
17///
18/// ```ignore
19/// #[derive(Debug, Clone, Serialize, Deserialize)]
20/// struct MyMessage(deepseek_sdk::Message);
21///
22/// impl ContextMessage for MyMessage {
23/// fn role(&self) -> Role { /* 映射 */ }
24/// fn preserve_reasoning(&self) -> bool { /* 按 provider 规则 */ }
25/// }
26/// ```
27pub trait ContextMessage: Send + Sync + Clone + Serialize + DeserializeOwned {
28 /// 返回此消息的角色。
29 fn role(&self) -> Role;
30
31 /// 此消息的 `reasoning_content` 是否需要在后续请求中保留。
32 ///
33 /// 各 LLM provider 有不同规则。例如 DeepSeek 要求:
34 /// - 工具调用场景中 assistant 的 `reasoning_content` 必须完整回传
35 /// - 非工具调用场景中可省略
36 fn preserve_reasoning(&self) -> bool;
37
38 /// 返回一个剥离 `reasoning_content` 的副本。
39 ///
40 /// 用于 [`ContextBackend::to_request_messages`](crate::ContextBackend::to_request_messages) 的默认实现:
41 /// 对 `!preserve_reasoning()` 的消息剥离思维链,减少 token 消耗。
42 fn without_reasoning(self) -> Self;
43
44 /// 返回一个角色被替换为新角色的副本。
45 ///
46 /// 用于 [`ContextBackend::to_system_message`](crate::ContextBackend::to_system_message) 的默认实现:
47 /// 将消息转换为 System 角色(压缩摘要等场景)。
48 fn with_role(self, role: Role) -> Self;
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
56 struct MockMsg {
57 role: Role,
58 content: String,
59 }
60
61 impl ContextMessage for MockMsg {
62 fn role(&self) -> Role {
63 self.role
64 }
65 fn preserve_reasoning(&self) -> bool {
66 false
67 }
68 fn without_reasoning(self) -> Self {
69 self
70 }
71 fn with_role(mut self, role: Role) -> Self {
72 self.role = role;
73 self
74 }
75 }
76
77 #[test]
78 fn mock_msg_role() {
79 let msg = MockMsg {
80 role: Role::User,
81 content: "hello".into(),
82 };
83 assert_eq!(msg.role(), Role::User);
84 }
85
86 #[test]
87 fn mock_msg_preserve_reasoning() {
88 let msg = MockMsg {
89 role: Role::Assistant,
90 content: "world".into(),
91 };
92 assert!(!msg.preserve_reasoning());
93 }
94
95 #[test]
96 fn mock_msg_serde() {
97 let msg = MockMsg {
98 role: Role::Assistant,
99 content: "world".into(),
100 };
101 let json = serde_json::to_string(&msg).unwrap();
102 let back: MockMsg = serde_json::from_str(&json).unwrap();
103 assert_eq!(back.content, "world");
104 }
105}