From d0c8eea5cd38302e32c0bca4efa1136bf154ddb3 Mon Sep 17 00:00:00 2001 From: zws <2985693012@qq.com> Date: Wed, 29 Oct 2025 14:28:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=20=E5=86=99=E5=85=A5=20Word=20=E6=96=87=E6=A1=A3=EF=BC=88.docx?= =?UTF-8?q?=EF=BC=89=20=E7=9A=84=E5=B7=A5=E5=85=B7=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E3=80=82=20=E8=AF=A5=E5=B7=A5=E5=85=B7=E8=83=BD=E5=A4=9F?= =?UTF-8?q?=E5=B0=86=E7=94=9F=E6=88=90=E7=9A=84=E6=8A=A5=E5=91=8A=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E4=BB=A5=20.docx=20=E6=A0=BC=E5=BC=8F=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=86=99=E5=87=BA=E5=88=B0=E6=9C=AC=E5=9C=B0=E6=88=96?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E8=B7=AF=E5=BE=84=EF=BC=8C=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9B=B4=E6=8E=A5=E8=8E=B7=E5=8F=96=E5=8F=AF?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E7=9A=84=E6=8A=A5=E5=91=8A=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E3=80=82=20=E6=96=B0=E5=A2=9E=E4=BA=86=20word=5Fwriter=20?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E6=96=87=E4=BB=B6=EF=BC=8C=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E5=B0=86=E6=96=87=E6=9C=AC=E5=86=85=E5=AE=B9=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E4=B8=BA=20Word=20=E6=96=87=E6=A1=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PS: 我是第一次提交 PR,如果有任何不合适或不规范的地方,请多多指正! 我会积极修改并学习改进,非常感谢你的时间与帮助! --- .../tool/common_tool/write_word_tool.py | 62 +++++++++++++ .../agent/action/tool/test_write_word_tool.py | 89 +++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 agentuniverse/agent/action/tool/common_tool/write_word_tool.py create mode 100644 tests/test_agentuniverse/unit/agent/action/tool/test_write_word_tool.py diff --git a/agentuniverse/agent/action/tool/common_tool/write_word_tool.py b/agentuniverse/agent/action/tool/common_tool/write_word_tool.py new file mode 100644 index 00000000..4b7cade2 --- /dev/null +++ b/agentuniverse/agent/action/tool/common_tool/write_word_tool.py @@ -0,0 +1,62 @@ +import os +import json +from typing import Any, Dict + +from agentuniverse.agent.action.tool.tool import Tool + + +class WriteWordDocumentTool(Tool): + def execute(self, file_path: str, content: str = "", append: bool = False) -> str: + directory = os.path.dirname(file_path) + if directory and not os.path.exists(directory): + try: + os.makedirs(directory, exist_ok=True) + except Exception as e: + return json.dumps( + {"error": f"Failed to create directory: {str(e)}", "file_path": file_path, "status": "error"} + ) + + try: + from docx import Document # type: ignore + except ImportError as e: + return json.dumps( + { + "error": f"python-docx is required to write Word documents: {str(e)}", + "file_path": file_path, + "status": "error", + } + ) + + if not file_path.lower().endswith(".docx"): + return json.dumps( + {"error": "The target file must have a .docx extension.", "file_path": file_path, "status": "error"} + ) + + document = None + if append and os.path.exists(file_path): + try: + document = Document(file_path) + except Exception as e: + return json.dumps( + {"error": f"Failed to load existing document: {str(e)}", "file_path": file_path, "status": "error"} + ) + else: + document = Document() + + try: + document.add_paragraph(content) + document.save(file_path) + file_size = os.path.getsize(file_path) + return json.dumps( + { + "file_path": file_path, + "bytes_written": len(content.encode("utf-8")), + "file_size": file_size, + "append_mode": append, + "status": "success", + } + ) + except Exception as e: + return json.dumps( + {"error": f"Failed to write document: {str(e)}", "file_path": file_path, "status": "error"} + ) diff --git a/tests/test_agentuniverse/unit/agent/action/tool/test_write_word_tool.py b/tests/test_agentuniverse/unit/agent/action/tool/test_write_word_tool.py new file mode 100644 index 00000000..c987aadd --- /dev/null +++ b/tests/test_agentuniverse/unit/agent/action/tool/test_write_word_tool.py @@ -0,0 +1,89 @@ +import os +import json +import tempfile +import unittest +from agentuniverse.agent.action.tool.common_tool.write_word_tool import WriteWordDocumentTool + + +class WriteWordDocumentToolTest(unittest.TestCase): + def setUp(self): + self.tool = WriteWordDocumentTool() + self.temp_dir = tempfile.mkdtemp() + + def tearDown(self): + for root, dirs, files in os.walk(self.temp_dir, topdown=False): + for name in files: + os.unlink(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + os.rmdir(self.temp_dir) + + def test_write_new_word_file(self): + file_path = os.path.join(self.temp_dir, "test_new.docx") + content = "***This is a test paragraph.***" + + result_json = self.tool.execute(file_path=file_path, content=content, append=False) + result = json.loads(result_json) + + self.assertEqual(result["status"], "success") + self.assertEqual(result["file_path"], file_path) + self.assertTrue(os.path.exists(file_path)) + + def test_append_to_word_file(self): + file_path = os.path.join(self.temp_dir, "test_append.docx") + + initial_content = "Initial paragraph." + self.tool.execute(file_path=file_path, content=initial_content, append=False) + + append_content = "Appended paragraph." + result_json = self.tool.execute(file_path=file_path, content=append_content, append=True) + result = json.loads(result_json) + + self.assertEqual(result["status"], "success") + self.assertEqual(result["append_mode"], True) + + def test_invalid_file_extension(self): + file_path = os.path.join(self.temp_dir, "invalid_file.txt") + content = "This should fail." + + result_json = self.tool.execute(file_path=file_path, content=content, append=False) + result = json.loads(result_json) + + self.assertEqual(result["status"], "error") + self.assertIn("The target file must have a .docx extension.", result["error"]) + + def test_create_directory_structure(self): + file_path = os.path.join(self.temp_dir, "nested/dir/structure/test.docx") + content = "Test content in nested directory." + + result_json = self.tool.execute(file_path=file_path, content=content, append=False) + result = json.loads(result_json) + + self.assertEqual(result["status"], "success") + self.assertTrue(os.path.exists(file_path)) + self.assertTrue(os.path.isdir(os.path.join(self.temp_dir, "nested/dir/structure"))) + + def test_missing_dependency(self): + original_import = __import__ + + def mock_import(name, *args): + if name == "docx": + raise ImportError("No module named 'docx'") + return original_import(name, *args) + + try: + __builtins__["__import__"] = mock_import + file_path = os.path.join(self.temp_dir, "test_missing_dependency.docx") + content = "This should fail due to missing dependency." + + result_json = self.tool.execute(file_path=file_path, content=content, append=False) + result = json.loads(result_json) + + self.assertEqual(result["status"], "error") + self.assertIn("python-docx is required to write Word documents", result["error"]) + finally: + __builtins__["__import__"] = original_import + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file