matrixcode_core/tools/workflow/
content.rs1use anyhow::Result;
6use async_trait::async_trait;
7use serde_json::{Value, json};
8
9use crate::providers::Provider;
10use crate::tools::{Tool, ToolDefinition};
11use std::sync::Arc;
12
13pub struct ContentGenerationTool {
15 provider: Arc<dyn Provider>,
16}
17
18impl ContentGenerationTool {
19 pub fn new(provider: Arc<dyn Provider>) -> Self {
20 Self { provider }
21 }
22
23 fn extract_topic<'a>(&self, params: &'a Value) -> Result<&'a str> {
25 params
26 .get("topic")
27 .and_then(|v| v.as_str())
28 .ok_or_else(|| anyhow::anyhow!("缺少 topic 参数"))
29 }
30
31 fn build_prompt(&self, params: &Value) -> String {
33 let topic = params.get("topic").and_then(|v| v.as_str()).unwrap_or("");
34 let style = params
35 .get("style")
36 .and_then(|v| v.as_str())
37 .unwrap_or("informative");
38
39 let mut prompt = format!("主题: {}\n\n风格: {}\n\n", topic, style);
40
41 if let Some(research) = params.get("research_data").and_then(|v| v.as_str()) {
42 prompt.push_str(&format!("参考资料:\n{}\n\n", research));
43 }
44
45 self.append_image_instructions(&mut prompt, params);
46 prompt.push_str("\n请生成一篇完整的图文文章,**必须包含图片**。");
47
48 prompt
49 }
50
51 fn append_image_instructions(&self, prompt: &mut String, params: &Value) {
53 if let Some(images) = params.get("image_urls")
54 && let Some(arr) = images.as_array()
55 && !arr.is_empty()
56 {
57 prompt.push_str(
58 "\n**重要:请在文章中插入以下图片**(使用 Markdown 图片格式 ``):\n",
59 );
60 for (idx, img) in arr.iter().enumerate() {
61 let (url, desc) = self.extract_image_info(img, idx);
62 prompt.push_str(&format!("{}. \n", idx + 1, desc, url));
63 }
64 prompt.push_str("\n请将图片插入到文章的合适位置,使文章更加生动。\n");
65 }
66 }
67
68 fn extract_image_info(&self, img: &Value, idx: usize) -> (String, String) {
70 if let Some(url_str) = img.as_str() {
71 (url_str.to_string(), format!("图片{}", idx + 1))
72 } else if let Some(obj) = img.as_object() {
73 let url = obj
74 .get("url")
75 .and_then(|u| u.as_str())
76 .unwrap_or("")
77 .to_string();
78 let desc = obj
79 .get("description")
80 .and_then(|d| d.as_str())
81 .unwrap_or("配图")
82 .to_string();
83 (url, desc)
84 } else {
85 ("".to_string(), format!("图片{}", idx + 1))
86 }
87 }
88
89 fn build_image_gallery(&self, params: &Value) -> String {
91 if let Some(images) = params.get("image_urls")
92 && let Some(arr) = images.as_array()
93 && !arr.is_empty()
94 {
95 let mut gallery = String::from("\n## 📷 配图\n\n");
96 for (idx, img) in arr.iter().enumerate().take(5) {
97 let (url, desc) = self.extract_image_info(img, idx);
98 if !url.is_empty() {
99 gallery.push_str(&format!("\n\n", desc, url));
100 }
101 }
102 return gallery;
103 }
104 String::new()
105 }
106
107 async fn call_ai(&self, prompt: String) -> Result<String> {
109 log::info!(
110 "ContentGenerationTool: starting for model {}",
111 self.provider.model_name()
112 );
113
114 let request = crate::providers::ChatRequest {
115 messages: vec![crate::providers::Message {
116 role: crate::providers::Role::User,
117 content: crate::providers::MessageContent::Text(prompt),
118 }],
119 system: Some("你是一个专业的内容创作者。".to_string()),
120 tools: vec![],
121 think: false,
122 max_tokens: 4096,
123 server_tools: vec![],
124 enable_caching: false,
125 };
126
127 match self.provider.chat(request).await {
128 Ok(response) => {
129 log::info!("ContentGenerationTool: received successful response");
130 Ok(self.extract_text_content(response))
131 }
132 Err(e) => {
133 log::error!("ContentGenerationTool: AI call failed: {:?}", e);
134 Err(e)
135 }
136 }
137 }
138
139 fn extract_text_content(&self, response: crate::providers::ChatResponse) -> String {
141 response
142 .content
143 .iter()
144 .filter_map(|block| match block {
145 crate::providers::ContentBlock::Text { text } => Some(text.clone()),
146 _ => None,
147 })
148 .collect::<Vec<_>>()
149 .join("\n")
150 }
151
152 fn format_response(&self, content: String, topic: &str, style: &str, params: &Value) -> String {
154 let gallery = self.build_image_gallery(params);
155 let final_content = if gallery.is_empty() {
156 content
157 } else {
158 format!("{}\n\n{}", gallery, content)
159 };
160
161 json!({
162 "content": final_content,
163 "topic": topic,
164 "style": style,
165 "word_count": final_content.chars().count()
166 })
167 .to_string()
168 }
169
170 fn format_error_response(&self, topic: &str, style: &str, error: anyhow::Error) -> String {
172 json!({
173 "content": format!("主题《{}》的内容生成失败: {}", topic, error),
174 "topic": topic,
175 "style": style,
176 "error": true
177 })
178 .to_string()
179 }
180}
181
182#[async_trait]
183impl Tool for ContentGenerationTool {
184 fn definition(&self) -> ToolDefinition {
185 ToolDefinition {
186 name: "content_generation".to_string(),
187 description: "使用 AI 生成内容(文章等)。需要 Provider 支持。".to_string(),
188 parameters: json!({
189 "type": "object",
190 "properties": {
191 "topic": {
192 "type": "string",
193 "description": "主题"
194 },
195 "research_data": {
196 "type": "string",
197 "description": "研究资料(可选)"
198 },
199 "image_urls": {
200 "type": "array",
201 "description": "图片 URL 列表(可选)",
202 "items": {"type": "string"}
203 },
204 "style": {
205 "type": "string",
206 "description": "写作风格(informative/casual/professional)",
207 "default": "informative"
208 }
209 },
210 "required": ["topic"]
211 }),
212 ..Default::default()
213 }
214 }
215
216 async fn execute(&self, params: Value) -> Result<String> {
217 let topic = self.extract_topic(¶ms)?;
218 let style = params
219 .get("style")
220 .and_then(|v| v.as_str())
221 .unwrap_or("informative");
222
223 let prompt = self.build_prompt(¶ms);
224
225 match self.call_ai(prompt).await {
226 Ok(content) => Ok(self.format_response(content, topic, style, ¶ms)),
227 Err(e) => Ok(self.format_error_response(topic, style, e)),
228 }
229 }
230}