camel-ai 0.2.67__py3-none-any.whl → 0.2.80a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- camel/__init__.py +1 -1
- camel/agents/_types.py +6 -2
- camel/agents/_utils.py +38 -0
- camel/agents/chat_agent.py +4014 -410
- camel/agents/mcp_agent.py +30 -27
- camel/agents/repo_agent.py +2 -1
- camel/benchmarks/browsecomp.py +6 -6
- camel/configs/__init__.py +15 -0
- camel/configs/aihubmix_config.py +88 -0
- camel/configs/amd_config.py +70 -0
- camel/configs/cometapi_config.py +104 -0
- camel/configs/minimax_config.py +93 -0
- camel/configs/nebius_config.py +103 -0
- camel/configs/vllm_config.py +2 -0
- camel/data_collectors/alpaca_collector.py +15 -6
- camel/datagen/self_improving_cot.py +1 -1
- camel/datasets/base_generator.py +39 -10
- camel/environments/__init__.py +12 -0
- camel/environments/rlcards_env.py +860 -0
- camel/environments/single_step.py +28 -3
- camel/environments/tic_tac_toe.py +1 -1
- camel/interpreters/__init__.py +2 -0
- camel/interpreters/docker/Dockerfile +4 -16
- camel/interpreters/docker_interpreter.py +3 -2
- camel/interpreters/e2b_interpreter.py +34 -1
- camel/interpreters/internal_python_interpreter.py +51 -2
- camel/interpreters/microsandbox_interpreter.py +395 -0
- camel/loaders/__init__.py +11 -2
- camel/loaders/base_loader.py +85 -0
- camel/loaders/chunkr_reader.py +9 -0
- camel/loaders/firecrawl_reader.py +4 -4
- camel/logger.py +1 -1
- camel/memories/agent_memories.py +84 -1
- camel/memories/base.py +34 -0
- camel/memories/blocks/chat_history_block.py +122 -4
- camel/memories/blocks/vectordb_block.py +8 -1
- camel/memories/context_creators/score_based.py +29 -237
- camel/memories/records.py +88 -8
- camel/messages/base.py +166 -40
- camel/messages/func_message.py +32 -5
- camel/models/__init__.py +10 -0
- camel/models/aihubmix_model.py +83 -0
- camel/models/aiml_model.py +1 -16
- camel/models/amd_model.py +101 -0
- camel/models/anthropic_model.py +117 -18
- camel/models/aws_bedrock_model.py +2 -33
- camel/models/azure_openai_model.py +205 -91
- camel/models/base_audio_model.py +3 -1
- camel/models/base_model.py +189 -24
- camel/models/cohere_model.py +5 -17
- camel/models/cometapi_model.py +83 -0
- camel/models/crynux_model.py +1 -16
- camel/models/deepseek_model.py +6 -16
- camel/models/fish_audio_model.py +6 -0
- camel/models/gemini_model.py +71 -20
- camel/models/groq_model.py +1 -17
- camel/models/internlm_model.py +1 -16
- camel/models/litellm_model.py +49 -32
- camel/models/lmstudio_model.py +1 -17
- camel/models/minimax_model.py +83 -0
- camel/models/mistral_model.py +1 -16
- camel/models/model_factory.py +27 -1
- camel/models/model_manager.py +24 -6
- camel/models/modelscope_model.py +1 -16
- camel/models/moonshot_model.py +185 -19
- camel/models/nebius_model.py +83 -0
- camel/models/nemotron_model.py +0 -5
- camel/models/netmind_model.py +1 -16
- camel/models/novita_model.py +1 -16
- camel/models/nvidia_model.py +1 -16
- camel/models/ollama_model.py +4 -19
- camel/models/openai_compatible_model.py +171 -46
- camel/models/openai_model.py +205 -77
- camel/models/openrouter_model.py +1 -17
- camel/models/ppio_model.py +1 -16
- camel/models/qianfan_model.py +1 -16
- camel/models/qwen_model.py +1 -16
- camel/models/reka_model.py +1 -16
- camel/models/samba_model.py +34 -47
- camel/models/sglang_model.py +64 -31
- camel/models/siliconflow_model.py +1 -16
- camel/models/stub_model.py +0 -4
- camel/models/togetherai_model.py +1 -16
- camel/models/vllm_model.py +1 -16
- camel/models/volcano_model.py +0 -17
- camel/models/watsonx_model.py +1 -16
- camel/models/yi_model.py +1 -16
- camel/models/zhipuai_model.py +60 -16
- camel/parsers/__init__.py +18 -0
- camel/parsers/mcp_tool_call_parser.py +176 -0
- camel/retrievers/auto_retriever.py +1 -0
- camel/runtimes/configs.py +11 -11
- camel/runtimes/daytona_runtime.py +15 -16
- camel/runtimes/docker_runtime.py +6 -6
- camel/runtimes/remote_http_runtime.py +5 -5
- camel/services/agent_openapi_server.py +380 -0
- camel/societies/__init__.py +2 -0
- camel/societies/role_playing.py +26 -28
- camel/societies/workforce/__init__.py +2 -0
- camel/societies/workforce/events.py +122 -0
- camel/societies/workforce/prompts.py +249 -38
- camel/societies/workforce/role_playing_worker.py +82 -20
- camel/societies/workforce/single_agent_worker.py +634 -34
- camel/societies/workforce/structured_output_handler.py +512 -0
- camel/societies/workforce/task_channel.py +169 -23
- camel/societies/workforce/utils.py +176 -9
- camel/societies/workforce/worker.py +77 -23
- camel/societies/workforce/workflow_memory_manager.py +772 -0
- camel/societies/workforce/workforce.py +3168 -478
- camel/societies/workforce/workforce_callback.py +74 -0
- camel/societies/workforce/workforce_logger.py +203 -175
- camel/societies/workforce/workforce_metrics.py +33 -0
- camel/storages/__init__.py +4 -0
- camel/storages/key_value_storages/json.py +15 -2
- camel/storages/key_value_storages/mem0_cloud.py +48 -47
- camel/storages/object_storages/google_cloud.py +1 -1
- camel/storages/vectordb_storages/__init__.py +6 -0
- camel/storages/vectordb_storages/chroma.py +731 -0
- camel/storages/vectordb_storages/oceanbase.py +13 -13
- camel/storages/vectordb_storages/pgvector.py +349 -0
- camel/storages/vectordb_storages/qdrant.py +3 -3
- camel/storages/vectordb_storages/surreal.py +365 -0
- camel/storages/vectordb_storages/tidb.py +8 -6
- camel/tasks/task.py +244 -27
- camel/toolkits/__init__.py +46 -8
- camel/toolkits/aci_toolkit.py +64 -19
- camel/toolkits/arxiv_toolkit.py +6 -6
- camel/toolkits/base.py +63 -5
- camel/toolkits/code_execution.py +28 -1
- camel/toolkits/context_summarizer_toolkit.py +684 -0
- camel/toolkits/craw4ai_toolkit.py +93 -0
- camel/toolkits/dappier_toolkit.py +10 -6
- camel/toolkits/dingtalk.py +1135 -0
- camel/toolkits/edgeone_pages_mcp_toolkit.py +49 -0
- camel/toolkits/excel_toolkit.py +901 -67
- camel/toolkits/file_toolkit.py +1402 -0
- camel/toolkits/function_tool.py +30 -6
- camel/toolkits/github_toolkit.py +107 -20
- camel/toolkits/gmail_toolkit.py +1839 -0
- camel/toolkits/google_calendar_toolkit.py +38 -4
- camel/toolkits/google_drive_mcp_toolkit.py +54 -0
- camel/toolkits/human_toolkit.py +34 -10
- camel/toolkits/hybrid_browser_toolkit/__init__.py +18 -0
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +185 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +246 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +1973 -0
- camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
- camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +3749 -0
- camel/toolkits/hybrid_browser_toolkit/ts/package.json +32 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-scripts.js +125 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +1815 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +233 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +590 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/index.ts +7 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +130 -0
- camel/toolkits/hybrid_browser_toolkit/ts/tsconfig.json +26 -0
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +319 -0
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +1032 -0
- camel/toolkits/hybrid_browser_toolkit_py/__init__.py +17 -0
- camel/toolkits/hybrid_browser_toolkit_py/actions.py +575 -0
- camel/toolkits/hybrid_browser_toolkit_py/agent.py +311 -0
- camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +787 -0
- camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +490 -0
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2390 -0
- camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +233 -0
- camel/toolkits/hybrid_browser_toolkit_py/stealth_script.js +0 -0
- camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +1043 -0
- camel/toolkits/image_generation_toolkit.py +390 -0
- camel/toolkits/jina_reranker_toolkit.py +3 -4
- camel/toolkits/klavis_toolkit.py +5 -1
- camel/toolkits/markitdown_toolkit.py +104 -0
- camel/toolkits/math_toolkit.py +64 -10
- camel/toolkits/mcp_toolkit.py +370 -45
- camel/toolkits/memory_toolkit.py +5 -1
- camel/toolkits/message_agent_toolkit.py +608 -0
- camel/toolkits/message_integration.py +724 -0
- camel/toolkits/minimax_mcp_toolkit.py +195 -0
- camel/toolkits/note_taking_toolkit.py +277 -0
- camel/toolkits/notion_mcp_toolkit.py +224 -0
- camel/toolkits/openbb_toolkit.py +5 -1
- camel/toolkits/origene_mcp_toolkit.py +56 -0
- camel/toolkits/playwright_mcp_toolkit.py +12 -31
- camel/toolkits/pptx_toolkit.py +25 -12
- camel/toolkits/resend_toolkit.py +168 -0
- camel/toolkits/screenshot_toolkit.py +213 -0
- camel/toolkits/search_toolkit.py +437 -142
- camel/toolkits/slack_toolkit.py +104 -50
- camel/toolkits/sympy_toolkit.py +1 -1
- camel/toolkits/task_planning_toolkit.py +3 -3
- camel/toolkits/terminal_toolkit/__init__.py +18 -0
- camel/toolkits/terminal_toolkit/terminal_toolkit.py +957 -0
- camel/toolkits/terminal_toolkit/utils.py +532 -0
- camel/toolkits/thinking_toolkit.py +1 -1
- camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
- camel/toolkits/video_analysis_toolkit.py +106 -26
- camel/toolkits/video_download_toolkit.py +17 -14
- camel/toolkits/web_deploy_toolkit.py +1219 -0
- camel/toolkits/wechat_official_toolkit.py +483 -0
- camel/toolkits/zapier_toolkit.py +5 -1
- camel/types/__init__.py +2 -2
- camel/types/agents/tool_calling_record.py +4 -1
- camel/types/enums.py +316 -40
- camel/types/openai_types.py +2 -2
- camel/types/unified_model_type.py +31 -4
- camel/utils/commons.py +36 -5
- camel/utils/constants.py +3 -0
- camel/utils/context_utils.py +1003 -0
- camel/utils/mcp.py +138 -4
- camel/utils/mcp_client.py +45 -1
- camel/utils/message_summarizer.py +148 -0
- camel/utils/token_counting.py +43 -20
- camel/utils/tool_result.py +44 -0
- {camel_ai-0.2.67.dist-info → camel_ai-0.2.80a2.dist-info}/METADATA +296 -85
- {camel_ai-0.2.67.dist-info → camel_ai-0.2.80a2.dist-info}/RECORD +219 -146
- camel/loaders/pandas_reader.py +0 -368
- camel/toolkits/dalle_toolkit.py +0 -175
- camel/toolkits/file_write_toolkit.py +0 -444
- camel/toolkits/openai_agent_toolkit.py +0 -135
- camel/toolkits/terminal_toolkit.py +0 -1037
- {camel_ai-0.2.67.dist-info → camel_ai-0.2.80a2.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.67.dist-info → camel_ai-0.2.80a2.dist-info}/licenses/LICENSE +0 -0
camel/toolkits/excel_toolkit.py
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
|
|
15
15
|
# Enables postponed evaluation of annotations (for string-based type hints)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from typing import TYPE_CHECKING, List, Optional
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import TYPE_CHECKING, List, Optional, Union
|
|
19
19
|
|
|
20
20
|
from camel.logger import get_logger
|
|
21
21
|
from camel.toolkits.base import BaseToolkit
|
|
@@ -24,7 +24,7 @@ from camel.utils import MCPServer
|
|
|
24
24
|
|
|
25
25
|
# Import only for type hints (not executed at runtime)
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
|
-
|
|
27
|
+
from pandas import DataFrame
|
|
28
28
|
|
|
29
29
|
logger = get_logger(__name__)
|
|
30
30
|
|
|
@@ -42,6 +42,7 @@ class ExcelToolkit(BaseToolkit):
|
|
|
42
42
|
def __init__(
|
|
43
43
|
self,
|
|
44
44
|
timeout: Optional[float] = None,
|
|
45
|
+
working_directory: Optional[str] = None,
|
|
45
46
|
):
|
|
46
47
|
r"""Initializes a new instance of the ExcelToolkit class.
|
|
47
48
|
|
|
@@ -49,14 +50,50 @@ class ExcelToolkit(BaseToolkit):
|
|
|
49
50
|
timeout (Optional[float]): The timeout value for API requests
|
|
50
51
|
in seconds. If None, no timeout is applied.
|
|
51
52
|
(default: :obj:`None`)
|
|
53
|
+
working_directory (str, optional): The default directory for
|
|
54
|
+
output files. If not provided, it will be determined by the
|
|
55
|
+
`CAMEL_WORKDIR` environment variable (if set). If the
|
|
56
|
+
environment variable is not set, it defaults to
|
|
57
|
+
`camel_working_dir`.
|
|
52
58
|
"""
|
|
53
59
|
super().__init__(timeout=timeout)
|
|
60
|
+
self.wb = None
|
|
61
|
+
if working_directory:
|
|
62
|
+
self.working_directory = Path(working_directory).resolve()
|
|
63
|
+
else:
|
|
64
|
+
camel_workdir = os.environ.get("CAMEL_WORKDIR")
|
|
65
|
+
if camel_workdir:
|
|
66
|
+
self.working_directory = Path(camel_workdir).resolve()
|
|
67
|
+
else:
|
|
68
|
+
self.working_directory = Path("./camel_working_dir").resolve()
|
|
69
|
+
|
|
70
|
+
self.working_directory.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
logger.info(
|
|
72
|
+
f"ExcelToolkit initialized with output directory: "
|
|
73
|
+
f"{self.working_directory}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def _validate_file_path(self, file_path: str) -> bool:
|
|
77
|
+
r"""Validate file path for security.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
file_path (str): The file path to validate.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
bool: True if path is safe, False otherwise.
|
|
84
|
+
"""
|
|
85
|
+
normalized_path = os.path.normpath(file_path)
|
|
54
86
|
|
|
55
|
-
|
|
87
|
+
if '..' in normalized_path.split(os.path.sep):
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def _convert_to_markdown(self, df: "DataFrame") -> str:
|
|
56
93
|
r"""Convert DataFrame to Markdown format table.
|
|
57
94
|
|
|
58
95
|
Args:
|
|
59
|
-
df (
|
|
96
|
+
df (DataFrame): DataFrame containing the Excel data.
|
|
60
97
|
|
|
61
98
|
Returns:
|
|
62
99
|
str: Markdown formatted table.
|
|
@@ -67,14 +104,22 @@ class ExcelToolkit(BaseToolkit):
|
|
|
67
104
|
return str(md_table)
|
|
68
105
|
|
|
69
106
|
def extract_excel_content(self, document_path: str) -> str:
|
|
70
|
-
r"""Extract
|
|
71
|
-
|
|
107
|
+
r"""Extract and analyze the full content of an Excel file (.xlsx/.xls/.
|
|
108
|
+
csv).
|
|
109
|
+
|
|
110
|
+
Use this tool to read and understand the structure and content of
|
|
111
|
+
Excel files. This is typically the first step when working with
|
|
112
|
+
existing Excel files.
|
|
72
113
|
|
|
73
114
|
Args:
|
|
74
|
-
document_path (str): The path
|
|
115
|
+
document_path (str): The file path to the Excel file.
|
|
75
116
|
|
|
76
117
|
Returns:
|
|
77
|
-
str:
|
|
118
|
+
str: A comprehensive report containing:
|
|
119
|
+
- Sheet names and their content in markdown table format
|
|
120
|
+
- Detailed cell information including values, colors, and
|
|
121
|
+
positions
|
|
122
|
+
- Formatted data that's easy to understand and analyze
|
|
78
123
|
"""
|
|
79
124
|
import pandas as pd
|
|
80
125
|
from openpyxl import load_workbook
|
|
@@ -85,6 +130,9 @@ class ExcelToolkit(BaseToolkit):
|
|
|
85
130
|
f": {document_path}"
|
|
86
131
|
)
|
|
87
132
|
|
|
133
|
+
if not self._validate_file_path(document_path):
|
|
134
|
+
return "Error: Invalid file path."
|
|
135
|
+
|
|
88
136
|
if not (
|
|
89
137
|
document_path.endswith("xls")
|
|
90
138
|
or document_path.endswith("xlsx")
|
|
@@ -96,6 +144,9 @@ class ExcelToolkit(BaseToolkit):
|
|
|
96
144
|
f"It is not excel format. Please try other ways."
|
|
97
145
|
)
|
|
98
146
|
|
|
147
|
+
if not os.path.exists(document_path):
|
|
148
|
+
return f"Error: File {document_path} does not exist."
|
|
149
|
+
|
|
99
150
|
if document_path.endswith("csv"):
|
|
100
151
|
try:
|
|
101
152
|
df = pd.read_csv(document_path)
|
|
@@ -111,64 +162,74 @@ class ExcelToolkit(BaseToolkit):
|
|
|
111
162
|
x2x.to_xlsx(output_path)
|
|
112
163
|
document_path = output_path
|
|
113
164
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
165
|
+
try:
|
|
166
|
+
# Load the Excel workbook
|
|
167
|
+
wb = load_workbook(document_path, data_only=True)
|
|
168
|
+
sheet_info_list = []
|
|
117
169
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
170
|
+
# Iterate through all sheets
|
|
171
|
+
for sheet in wb.sheetnames:
|
|
172
|
+
ws = wb[sheet]
|
|
173
|
+
cell_info_list = []
|
|
122
174
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
"
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
for row in ws.iter_rows():
|
|
176
|
+
for cell in row:
|
|
177
|
+
# Skip cells that don't have proper coordinates (like
|
|
178
|
+
# merged cells)
|
|
179
|
+
if (
|
|
180
|
+
not hasattr(cell, 'column_letter')
|
|
181
|
+
or cell.value is None
|
|
182
|
+
):
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
row_num = cell.row
|
|
186
|
+
# Use getattr with fallback for column_letter
|
|
187
|
+
col_letter = getattr(cell, 'column_letter', 'A')
|
|
188
|
+
|
|
189
|
+
cell_value = cell.value
|
|
190
|
+
|
|
191
|
+
font_color = None
|
|
192
|
+
if (
|
|
193
|
+
cell.font
|
|
194
|
+
and cell.font.color
|
|
195
|
+
and "rgb=None" not in str(cell.font.color)
|
|
196
|
+
): # Handle font color
|
|
197
|
+
font_color = cell.font.color.rgb
|
|
198
|
+
|
|
199
|
+
fill_color = None
|
|
200
|
+
if (
|
|
201
|
+
cell.fill
|
|
202
|
+
and cell.fill.fgColor
|
|
203
|
+
and "rgb=None" not in str(cell.fill.fgColor)
|
|
204
|
+
): # Handle fill color
|
|
205
|
+
fill_color = cell.fill.fgColor.rgb
|
|
206
|
+
|
|
207
|
+
cell_info_list.append(
|
|
208
|
+
{
|
|
209
|
+
"index": f"{row_num}{col_letter}",
|
|
210
|
+
"value": cell_value,
|
|
211
|
+
"font_color": font_color,
|
|
212
|
+
"fill_color": fill_color,
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Convert the sheet to a DataFrame and then to markdown
|
|
217
|
+
sheet_df = pd.read_excel(
|
|
218
|
+
document_path, sheet_name=sheet, engine='openpyxl'
|
|
219
|
+
)
|
|
220
|
+
markdown_content = self._convert_to_markdown(sheet_df)
|
|
221
|
+
|
|
222
|
+
# Collect all information for the sheet
|
|
223
|
+
sheet_info = {
|
|
224
|
+
"sheet_name": sheet,
|
|
225
|
+
"cell_info_list": cell_info_list,
|
|
226
|
+
"markdown_content": markdown_content,
|
|
227
|
+
}
|
|
228
|
+
sheet_info_list.append(sheet_info)
|
|
229
|
+
|
|
230
|
+
result_str = ""
|
|
231
|
+
for sheet_info in sheet_info_list:
|
|
232
|
+
result_str += f"""
|
|
172
233
|
Sheet Name: {sheet_info['sheet_name']}
|
|
173
234
|
Cell information list:
|
|
174
235
|
{sheet_info['cell_info_list']}
|
|
@@ -179,7 +240,757 @@ class ExcelToolkit(BaseToolkit):
|
|
|
179
240
|
{'-'*40}
|
|
180
241
|
"""
|
|
181
242
|
|
|
182
|
-
|
|
243
|
+
return result_str
|
|
244
|
+
except Exception as e:
|
|
245
|
+
logger.error(f"Failed to process Excel file {document_path}: {e}")
|
|
246
|
+
return f"Failed to process Excel file {document_path}: {e}"
|
|
247
|
+
|
|
248
|
+
def _save_workbook(self, file_path: str) -> str:
|
|
249
|
+
r"""Save the current workbook to file.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
file_path (str): The path to save the workbook.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
str: Success or error message.
|
|
256
|
+
"""
|
|
257
|
+
if not self.wb:
|
|
258
|
+
return "Error: No workbook loaded to save."
|
|
259
|
+
|
|
260
|
+
if not self._validate_file_path(file_path):
|
|
261
|
+
return "Error: Invalid file path for saving."
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
self.wb.save(file_path)
|
|
265
|
+
return f"Workbook saved successfully to {file_path}"
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.error(f"Failed to save workbook: {e}")
|
|
268
|
+
return f"Error: Failed to save workbook: {e}"
|
|
269
|
+
|
|
270
|
+
def save_workbook(self, filename: str) -> str:
|
|
271
|
+
r"""Save the current in-memory workbook to a file.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
filename (str): The filename to save the workbook. Must end with
|
|
275
|
+
.xlsx extension. The file will be saved in self.
|
|
276
|
+
working_directory.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
str: Success message or error details.
|
|
280
|
+
"""
|
|
281
|
+
if not self.wb:
|
|
282
|
+
return "Error: No workbook is currently loaded in memory."
|
|
283
|
+
|
|
284
|
+
# Validate filename
|
|
285
|
+
if not filename:
|
|
286
|
+
return "Error: Filename is required."
|
|
287
|
+
|
|
288
|
+
if not filename.endswith('.xlsx'):
|
|
289
|
+
return "Error: Filename must end with .xlsx extension."
|
|
290
|
+
|
|
291
|
+
# Create full path in working directory
|
|
292
|
+
file_path = self.working_directory / filename
|
|
293
|
+
resolved_file_path = str(file_path.resolve())
|
|
294
|
+
|
|
295
|
+
return self._save_workbook(resolved_file_path)
|
|
296
|
+
|
|
297
|
+
def create_workbook(
|
|
298
|
+
self,
|
|
299
|
+
filename: Optional[str] = None,
|
|
300
|
+
sheet_name: Optional[str] = None,
|
|
301
|
+
data: Optional[List[List[Union[str, int, float, None]]]] = None,
|
|
302
|
+
) -> str:
|
|
303
|
+
r"""Create a new Excel workbook from scratch.
|
|
304
|
+
|
|
305
|
+
Use this when you need to create a new Excel file. This sets up the
|
|
306
|
+
toolkit to work with the new file and optionally adds initial data.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
filename (Optional[str]): The filename for the workbook. Must end
|
|
310
|
+
with .xlsx extension. The file will be saved in
|
|
311
|
+
self.working_directory. (default: :obj:`None`)
|
|
312
|
+
sheet_name (Optional[str]): Name for the first sheet. If None,
|
|
313
|
+
creates "Sheet1". (default: :obj:`None`)
|
|
314
|
+
data (Optional[List[List[Union[str, int, float, None]]]]): Initial
|
|
315
|
+
data as rows. Each inner list is one row. (default:
|
|
316
|
+
:obj:`None`)
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
str: Success confirmation message or error details
|
|
320
|
+
"""
|
|
321
|
+
from openpyxl import Workbook
|
|
322
|
+
|
|
323
|
+
# Validate filename
|
|
324
|
+
if filename is None:
|
|
325
|
+
return "Error: Filename is required."
|
|
326
|
+
|
|
327
|
+
if not filename.endswith('.xlsx'):
|
|
328
|
+
return "Error: Filename must end with .xlsx extension."
|
|
329
|
+
|
|
330
|
+
# Create full path in working directory
|
|
331
|
+
file_path = self.working_directory / filename
|
|
332
|
+
resolved_file_path = str(file_path.resolve())
|
|
333
|
+
|
|
334
|
+
if not self._validate_file_path(resolved_file_path):
|
|
335
|
+
return "Error: Invalid file path."
|
|
336
|
+
|
|
337
|
+
# Check if file already exists
|
|
338
|
+
if os.path.exists(resolved_file_path):
|
|
339
|
+
return (
|
|
340
|
+
f"Error: File {filename} already exists in "
|
|
341
|
+
f"{self.working_directory}."
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
# Create a new workbook
|
|
346
|
+
wb = Workbook()
|
|
347
|
+
self.wb = wb
|
|
348
|
+
|
|
349
|
+
# Handle sheet creation safely
|
|
350
|
+
if sheet_name:
|
|
351
|
+
# Remove the default sheet safely
|
|
352
|
+
default_sheet = wb.active
|
|
353
|
+
if default_sheet is not None:
|
|
354
|
+
wb.remove(default_sheet)
|
|
355
|
+
ws = wb.create_sheet(sheet_name)
|
|
356
|
+
else:
|
|
357
|
+
ws = wb.active
|
|
358
|
+
if ws is not None and sheet_name is None:
|
|
359
|
+
sheet_name = "Sheet1"
|
|
360
|
+
ws.title = sheet_name
|
|
361
|
+
|
|
362
|
+
# Add data if provided
|
|
363
|
+
if data and ws is not None:
|
|
364
|
+
for row in data:
|
|
365
|
+
ws.append(row)
|
|
366
|
+
|
|
367
|
+
# Save the workbook to the specified file path
|
|
368
|
+
wb.save(resolved_file_path)
|
|
369
|
+
|
|
370
|
+
return f"Workbook created successfully at {resolved_file_path}"
|
|
371
|
+
except Exception as e:
|
|
372
|
+
logger.error(f"Failed to create workbook: {e}")
|
|
373
|
+
return f"Error: Failed to create workbook: {e}"
|
|
374
|
+
|
|
375
|
+
def delete_workbook(self, filename: str) -> str:
|
|
376
|
+
r"""Delete a spreadsheet file from the working directory.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
filename (str): The filename to delete. Must end with .xlsx
|
|
380
|
+
extension. The file will be deleted from self.
|
|
381
|
+
working_directory.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
str: Success message or error details.
|
|
385
|
+
"""
|
|
386
|
+
# Validate filename
|
|
387
|
+
if not filename:
|
|
388
|
+
return "Error: Filename is required."
|
|
389
|
+
|
|
390
|
+
if not filename.endswith('.xlsx'):
|
|
391
|
+
return "Error: Filename must end with .xlsx extension."
|
|
392
|
+
|
|
393
|
+
# Create full path in working directory
|
|
394
|
+
file_path = self.working_directory / filename
|
|
395
|
+
target_path = str(file_path.resolve())
|
|
396
|
+
|
|
397
|
+
if not self._validate_file_path(target_path):
|
|
398
|
+
return "Error: Invalid file path."
|
|
399
|
+
|
|
400
|
+
if not os.path.exists(target_path):
|
|
401
|
+
return (
|
|
402
|
+
f"Error: File {filename} does not exist in "
|
|
403
|
+
f"{self.working_directory}."
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
try:
|
|
407
|
+
os.remove(target_path)
|
|
408
|
+
# Clean up workbook if one is loaded
|
|
409
|
+
self.wb = None
|
|
410
|
+
return (
|
|
411
|
+
f"Workbook {filename} deleted successfully from "
|
|
412
|
+
f"{self.working_directory}."
|
|
413
|
+
)
|
|
414
|
+
except Exception as e:
|
|
415
|
+
logger.error(f"Failed to delete workbook: {e}")
|
|
416
|
+
return f"Error: Failed to delete workbook {filename}: {e}"
|
|
417
|
+
|
|
418
|
+
def create_sheet(
|
|
419
|
+
self,
|
|
420
|
+
sheet_name: str,
|
|
421
|
+
data: Optional[List[List[Union[str, int, float, None]]]] = None,
|
|
422
|
+
) -> str:
|
|
423
|
+
r"""Create a new sheet with the given sheet name and data.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
sheet_name (str): The name of the sheet to create.
|
|
427
|
+
data (Optional[List[List[Union[str, int, float, None]]]]):
|
|
428
|
+
The data to write to the sheet.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
str: Success message.
|
|
432
|
+
"""
|
|
433
|
+
if not self.wb:
|
|
434
|
+
return (
|
|
435
|
+
"Error: Workbook not initialized. "
|
|
436
|
+
"Please create a workbook first."
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
if sheet_name in self.wb.sheetnames:
|
|
440
|
+
return f"Error: Sheet {sheet_name} already exists."
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
ws = self.wb.create_sheet(sheet_name)
|
|
444
|
+
if data:
|
|
445
|
+
for row in data:
|
|
446
|
+
ws.append(row)
|
|
447
|
+
|
|
448
|
+
return f"Sheet {sheet_name} created successfully."
|
|
449
|
+
except Exception as e:
|
|
450
|
+
logger.error(f"Failed to create sheet: {e}")
|
|
451
|
+
return f"Error: Failed to create sheet {sheet_name}: {e}"
|
|
452
|
+
|
|
453
|
+
def delete_sheet(self, sheet_name: str) -> str:
|
|
454
|
+
r"""Delete a sheet from the workbook.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
sheet_name (str): The name of the sheet to delete.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
str: Success message.
|
|
461
|
+
"""
|
|
462
|
+
if not self.wb:
|
|
463
|
+
return "Error: Workbook not initialized."
|
|
464
|
+
|
|
465
|
+
if sheet_name not in self.wb.sheetnames:
|
|
466
|
+
return f"Sheet {sheet_name} does not exist."
|
|
467
|
+
|
|
468
|
+
if len(self.wb.sheetnames) == 1:
|
|
469
|
+
return "Cannot delete the last remaining sheet in the workbook."
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
ws = self.wb[sheet_name]
|
|
473
|
+
self.wb.remove(ws)
|
|
474
|
+
return f"Sheet {sheet_name} deleted successfully."
|
|
475
|
+
except Exception as e:
|
|
476
|
+
logger.error(f"Failed to delete sheet: {e}")
|
|
477
|
+
return f"Error: Failed to delete sheet {sheet_name}: {e}"
|
|
478
|
+
|
|
479
|
+
def clear_sheet(self, sheet_name: str) -> str:
|
|
480
|
+
r"""Clear all data from a sheet.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
sheet_name (str): The name of the sheet to clear.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
str: Success message.
|
|
487
|
+
"""
|
|
488
|
+
if not self.wb:
|
|
489
|
+
return "Error: Workbook not initialized."
|
|
490
|
+
|
|
491
|
+
if sheet_name not in self.wb.sheetnames:
|
|
492
|
+
return f"Sheet {sheet_name} does not exist."
|
|
493
|
+
|
|
494
|
+
try:
|
|
495
|
+
ws = self.wb[sheet_name]
|
|
496
|
+
|
|
497
|
+
# Clear all cells
|
|
498
|
+
for row in ws.iter_rows():
|
|
499
|
+
for cell in row:
|
|
500
|
+
cell.value = None
|
|
501
|
+
|
|
502
|
+
return f"Sheet {sheet_name} cleared successfully."
|
|
503
|
+
except Exception as e:
|
|
504
|
+
logger.error(f"Failed to clear sheet: {e}")
|
|
505
|
+
return f"Error: Failed to clear sheet {sheet_name}: {e}"
|
|
506
|
+
|
|
507
|
+
def delete_rows(
|
|
508
|
+
self, sheet_name: str, start_row: int, end_row: Optional[int] = None
|
|
509
|
+
) -> str:
|
|
510
|
+
r"""Delete rows from a sheet.
|
|
511
|
+
|
|
512
|
+
Use this to remove unwanted rows. You can delete single rows or ranges.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
sheet_name (str): Name of the sheet to modify.
|
|
516
|
+
start_row (int): Starting row number to delete (1-based, where 1
|
|
517
|
+
is first row).
|
|
518
|
+
end_row (Optional[int]): Ending row number to delete (1-based).
|
|
519
|
+
If None, deletes only start_row. (default: :obj:`None`)
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
str: Success confirmation message or error details
|
|
523
|
+
"""
|
|
524
|
+
if not self.wb:
|
|
525
|
+
return "Error: Workbook not initialized."
|
|
526
|
+
|
|
527
|
+
if sheet_name not in self.wb.sheetnames:
|
|
528
|
+
return f"Sheet {sheet_name} does not exist."
|
|
529
|
+
|
|
530
|
+
ws = self.wb[sheet_name]
|
|
531
|
+
|
|
532
|
+
if end_row is None:
|
|
533
|
+
end_row = start_row
|
|
534
|
+
|
|
535
|
+
# Delete rows (openpyxl uses 1-based indexing)
|
|
536
|
+
num_rows = end_row - start_row + 1
|
|
537
|
+
ws.delete_rows(start_row, num_rows)
|
|
538
|
+
|
|
539
|
+
return (
|
|
540
|
+
f"Deleted rows {start_row} to {end_row} from sheet "
|
|
541
|
+
f"{sheet_name} successfully."
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
def delete_columns(
|
|
545
|
+
self, sheet_name: str, start_col: int, end_col: Optional[int] = None
|
|
546
|
+
) -> str:
|
|
547
|
+
r"""Delete columns from a sheet.
|
|
548
|
+
|
|
549
|
+
Use this to remove unwanted columns. You can delete single columns or
|
|
550
|
+
ranges.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
sheet_name (str): Name of the sheet to modify.
|
|
554
|
+
start_col (int): Starting column number to delete (1-based, where
|
|
555
|
+
1 is column A).
|
|
556
|
+
end_col (Optional[int]): Ending column number to delete (1-based).
|
|
557
|
+
If None, deletes only start_col. (default: :obj:`None`)
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
str: Success confirmation message or error details
|
|
561
|
+
"""
|
|
562
|
+
if not self.wb:
|
|
563
|
+
return "Error: Workbook not initialized."
|
|
564
|
+
|
|
565
|
+
if sheet_name not in self.wb.sheetnames:
|
|
566
|
+
return f"Sheet {sheet_name} does not exist."
|
|
567
|
+
|
|
568
|
+
ws = self.wb[sheet_name]
|
|
569
|
+
|
|
570
|
+
if end_col is None:
|
|
571
|
+
end_col = start_col
|
|
572
|
+
|
|
573
|
+
# Delete columns (openpyxl uses 1-based indexing)
|
|
574
|
+
num_cols = end_col - start_col + 1
|
|
575
|
+
ws.delete_cols(start_col, num_cols)
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
f"Deleted columns {start_col} to {end_col} from sheet "
|
|
579
|
+
f"{sheet_name} successfully."
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
def get_cell_value(
|
|
583
|
+
self, sheet_name: str, cell_reference: str
|
|
584
|
+
) -> Union[str, int, float, None]:
|
|
585
|
+
r"""Get the value from a specific cell.
|
|
586
|
+
|
|
587
|
+
Use this to read a single cell's value. Useful for checking specific
|
|
588
|
+
data points or getting values for calculations.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
sheet_name (str): Name of the sheet containing the cell.
|
|
592
|
+
cell_reference (str): Excel-style cell reference (column letter +
|
|
593
|
+
row number).
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
Union[str, int, float, None]: The cell's value or error message
|
|
597
|
+
Returns None for empty cells.
|
|
598
|
+
"""
|
|
599
|
+
if not self.wb:
|
|
600
|
+
return "Error: Workbook not initialized."
|
|
601
|
+
|
|
602
|
+
if sheet_name not in self.wb.sheetnames:
|
|
603
|
+
return f"Error: Sheet {sheet_name} does not exist."
|
|
604
|
+
|
|
605
|
+
ws = self.wb[sheet_name]
|
|
606
|
+
return ws[cell_reference].value
|
|
607
|
+
|
|
608
|
+
def set_cell_value(
|
|
609
|
+
self,
|
|
610
|
+
sheet_name: str,
|
|
611
|
+
cell_reference: str,
|
|
612
|
+
value: Union[str, int, float, None],
|
|
613
|
+
) -> str:
|
|
614
|
+
r"""Set the value of a specific cell.
|
|
615
|
+
|
|
616
|
+
Use this to update individual cells with new values. Useful for
|
|
617
|
+
corrections, calculations, or updating specific data points.
|
|
618
|
+
|
|
619
|
+
Args:
|
|
620
|
+
sheet_name (str): Name of the sheet containing the cell.
|
|
621
|
+
cell_reference (str): Excel-style cell reference (column letter +
|
|
622
|
+
row number).
|
|
623
|
+
value (Union[str, int, float, None]): New value for the cell.
|
|
624
|
+
(default: :obj:`None`)
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
str: Success confirmation message or error details.
|
|
628
|
+
"""
|
|
629
|
+
if not self.wb:
|
|
630
|
+
return "Error: Workbook not initialized."
|
|
631
|
+
|
|
632
|
+
if sheet_name not in self.wb.sheetnames:
|
|
633
|
+
return f"Sheet {sheet_name} does not exist."
|
|
634
|
+
|
|
635
|
+
try:
|
|
636
|
+
ws = self.wb[sheet_name]
|
|
637
|
+
# Handle None values properly - openpyxl doesn't accept None
|
|
638
|
+
# directly
|
|
639
|
+
if value is None:
|
|
640
|
+
ws[cell_reference].value = None
|
|
641
|
+
else:
|
|
642
|
+
ws[cell_reference] = value
|
|
643
|
+
return (
|
|
644
|
+
f"Cell {cell_reference} updated successfully in sheet "
|
|
645
|
+
f"{sheet_name}."
|
|
646
|
+
)
|
|
647
|
+
except Exception as e:
|
|
648
|
+
logger.error(f"Failed to set cell value: {e}")
|
|
649
|
+
return f"Error: Failed to set cell value: {e}"
|
|
650
|
+
|
|
651
|
+
def get_column_data(
|
|
652
|
+
self, sheet_name: str, column: Union[int, str]
|
|
653
|
+
) -> Union[List[Union[str, int, float, None]], str]:
|
|
654
|
+
r"""Get all data from a specific column.
|
|
655
|
+
|
|
656
|
+
Use this to extract all values from a column for analysis or
|
|
657
|
+
processing.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
sheet_name (str): Name of the sheet to read from.
|
|
661
|
+
column (Union[int, str]): Column identifier - either number
|
|
662
|
+
(1-based) or letter.
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
Union[List[Union[str, int, float, None]], str]:
|
|
666
|
+
List of all non-empty values in the column or error message
|
|
667
|
+
"""
|
|
668
|
+
if not self.wb:
|
|
669
|
+
return "Error: Workbook not initialized."
|
|
670
|
+
|
|
671
|
+
if sheet_name not in self.wb.sheetnames:
|
|
672
|
+
return f"Error: Sheet {sheet_name} does not exist."
|
|
673
|
+
|
|
674
|
+
ws = self.wb[sheet_name]
|
|
675
|
+
|
|
676
|
+
if isinstance(column, str):
|
|
677
|
+
col_letter = column.upper()
|
|
678
|
+
else:
|
|
679
|
+
from openpyxl.utils import ( # type: ignore[import]
|
|
680
|
+
get_column_letter,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
col_letter = get_column_letter(column)
|
|
684
|
+
|
|
685
|
+
column_data = []
|
|
686
|
+
for cell in ws[col_letter]:
|
|
687
|
+
if cell.value is not None:
|
|
688
|
+
column_data.append(cell.value)
|
|
689
|
+
|
|
690
|
+
return column_data
|
|
691
|
+
|
|
692
|
+
def find_cells(
|
|
693
|
+
self,
|
|
694
|
+
sheet_name: str,
|
|
695
|
+
search_value: Union[str, int, float],
|
|
696
|
+
search_column: Optional[Union[int, str]] = None,
|
|
697
|
+
) -> Union[List[str], str]:
|
|
698
|
+
r"""Find cells containing a specific value.
|
|
699
|
+
|
|
700
|
+
Use this to locate where specific data appears in the sheet.
|
|
701
|
+
|
|
702
|
+
Args:
|
|
703
|
+
sheet_name (str): Name of the sheet to search in.
|
|
704
|
+
search_value (Union[str, int, float]): Value to search for.
|
|
705
|
+
search_column (Optional[Union[int, str]]): Limit search to
|
|
706
|
+
specific column. If None, searches entire sheet. (default:
|
|
707
|
+
:obj:`None`)
|
|
708
|
+
|
|
709
|
+
Returns:
|
|
710
|
+
Union[List[str], str]: List of cell references (like "A5", "B12")
|
|
711
|
+
where the value was found, or error message.
|
|
712
|
+
"""
|
|
713
|
+
if not self.wb:
|
|
714
|
+
return "Error: Workbook not initialized."
|
|
715
|
+
|
|
716
|
+
if sheet_name not in self.wb.sheetnames:
|
|
717
|
+
return f"Error: Sheet {sheet_name} does not exist."
|
|
718
|
+
|
|
719
|
+
ws = self.wb[sheet_name]
|
|
720
|
+
found_cells = []
|
|
721
|
+
|
|
722
|
+
if search_column:
|
|
723
|
+
# Search in specific column
|
|
724
|
+
if isinstance(search_column, str):
|
|
725
|
+
col_letter = search_column.upper()
|
|
726
|
+
for cell in ws[col_letter]:
|
|
727
|
+
if cell.value == search_value:
|
|
728
|
+
found_cells.append(cell.coordinate)
|
|
729
|
+
else:
|
|
730
|
+
from openpyxl.utils import ( # type: ignore[import]
|
|
731
|
+
get_column_letter,
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
col_letter = get_column_letter(search_column)
|
|
735
|
+
for cell in ws[col_letter]:
|
|
736
|
+
if cell.value == search_value:
|
|
737
|
+
found_cells.append(cell.coordinate)
|
|
738
|
+
else:
|
|
739
|
+
# Search entire sheet
|
|
740
|
+
for row in ws.iter_rows():
|
|
741
|
+
for cell in row:
|
|
742
|
+
if cell.value == search_value:
|
|
743
|
+
found_cells.append(cell.coordinate)
|
|
744
|
+
|
|
745
|
+
return found_cells
|
|
746
|
+
|
|
747
|
+
def get_range_values(
|
|
748
|
+
self, sheet_name: str, cell_range: str
|
|
749
|
+
) -> Union[List[List[Union[str, int, float, None]]], str]:
|
|
750
|
+
r"""Get values from a specific range of cells.
|
|
751
|
+
|
|
752
|
+
Use this to read a rectangular block of cells at once.
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
sheet_name (str): Name of the sheet to read from.
|
|
756
|
+
cell_range (str): Range in Excel format (start:end).
|
|
757
|
+
|
|
758
|
+
Returns:
|
|
759
|
+
Union[List[List[Union[str, int, float, None]]], str]:
|
|
760
|
+
2D list where each inner list is a row of cell values, or
|
|
761
|
+
error message.
|
|
762
|
+
"""
|
|
763
|
+
if not self.wb:
|
|
764
|
+
return "Error: Workbook not initialized."
|
|
765
|
+
|
|
766
|
+
if sheet_name not in self.wb.sheetnames:
|
|
767
|
+
return f"Error: Sheet {sheet_name} does not exist."
|
|
768
|
+
|
|
769
|
+
ws = self.wb[sheet_name]
|
|
770
|
+
range_values = []
|
|
771
|
+
|
|
772
|
+
for row in ws[cell_range]:
|
|
773
|
+
row_values = []
|
|
774
|
+
for cell in row:
|
|
775
|
+
row_values.append(cell.value)
|
|
776
|
+
range_values.append(row_values)
|
|
777
|
+
|
|
778
|
+
return range_values
|
|
779
|
+
|
|
780
|
+
def set_range_values(
|
|
781
|
+
self,
|
|
782
|
+
sheet_name: str,
|
|
783
|
+
cell_range: str,
|
|
784
|
+
values: List[List[Union[str, int, float, None]]],
|
|
785
|
+
) -> str:
|
|
786
|
+
r"""Set values for a specific range of cells.
|
|
787
|
+
|
|
788
|
+
Use this to update multiple cells at once with a 2D array of data.
|
|
789
|
+
|
|
790
|
+
Args:
|
|
791
|
+
sheet_name (str): Name of the sheet to modify.
|
|
792
|
+
cell_range (str): Range in Excel format to update.
|
|
793
|
+
values (List[List[Union[str, int, float, None]]]): 2D array of
|
|
794
|
+
values. Each inner list represents a row.
|
|
795
|
+
|
|
796
|
+
Returns:
|
|
797
|
+
str: Success confirmation message or error details.
|
|
798
|
+
"""
|
|
799
|
+
if not self.wb:
|
|
800
|
+
return "Error: Workbook not initialized."
|
|
801
|
+
|
|
802
|
+
if sheet_name not in self.wb.sheetnames:
|
|
803
|
+
return f"Error: Sheet {sheet_name} does not exist."
|
|
804
|
+
|
|
805
|
+
ws = self.wb[sheet_name]
|
|
806
|
+
|
|
807
|
+
# Get the range
|
|
808
|
+
cell_range_obj = ws[cell_range]
|
|
809
|
+
|
|
810
|
+
# If it's a single row or column, convert to 2D format
|
|
811
|
+
if not isinstance(cell_range_obj[0], tuple):
|
|
812
|
+
cell_range_obj = [cell_range_obj]
|
|
813
|
+
|
|
814
|
+
for row_idx, row in enumerate(cell_range_obj):
|
|
815
|
+
if row_idx < len(values):
|
|
816
|
+
for col_idx, cell in enumerate(row):
|
|
817
|
+
if col_idx < len(values[row_idx]):
|
|
818
|
+
cell.value = values[row_idx][col_idx]
|
|
819
|
+
|
|
820
|
+
return f"Values set for range {cell_range} in sheet {sheet_name}."
|
|
821
|
+
|
|
822
|
+
def export_sheet_to_csv(self, sheet_name: str, csv_filename: str) -> str:
|
|
823
|
+
r"""Export a specific sheet to CSV format.
|
|
824
|
+
|
|
825
|
+
Use this to convert Excel sheets to CSV files for compatibility or
|
|
826
|
+
data exchange.
|
|
827
|
+
|
|
828
|
+
Args:
|
|
829
|
+
sheet_name (str): Name of the sheet to export.
|
|
830
|
+
csv_filename (str): Filename for the CSV file. Must end with .csv
|
|
831
|
+
extension. The file will be saved in self.working_directory.
|
|
832
|
+
|
|
833
|
+
Returns:
|
|
834
|
+
str: Success confirmation message or error details.
|
|
835
|
+
"""
|
|
836
|
+
if not self.wb:
|
|
837
|
+
return (
|
|
838
|
+
"Error: No workbook is currently loaded. Use "
|
|
839
|
+
"extract_excel_content to load a workbook first."
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
if sheet_name not in self.wb.sheetnames:
|
|
843
|
+
return (
|
|
844
|
+
f"Error: Sheet {sheet_name} does not exist in the current "
|
|
845
|
+
"workbook."
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
# Validate filename
|
|
849
|
+
if not csv_filename:
|
|
850
|
+
return "Error: CSV filename is required."
|
|
851
|
+
|
|
852
|
+
if not csv_filename.endswith('.csv'):
|
|
853
|
+
return "Error: CSV filename must end with .csv extension."
|
|
854
|
+
|
|
855
|
+
# Create full path in working directory
|
|
856
|
+
csv_path = self.working_directory / csv_filename
|
|
857
|
+
resolved_csv_path = str(csv_path.resolve())
|
|
858
|
+
|
|
859
|
+
if not self._validate_file_path(resolved_csv_path):
|
|
860
|
+
return "Error: Invalid file path."
|
|
861
|
+
|
|
862
|
+
try:
|
|
863
|
+
# Get the worksheet
|
|
864
|
+
ws = self.wb[sheet_name]
|
|
865
|
+
|
|
866
|
+
# Convert worksheet to list of lists
|
|
867
|
+
data = []
|
|
868
|
+
for row in ws.iter_rows(values_only=True):
|
|
869
|
+
data.append(list(row))
|
|
870
|
+
|
|
871
|
+
# Write to CSV
|
|
872
|
+
import csv
|
|
873
|
+
|
|
874
|
+
with open(
|
|
875
|
+
resolved_csv_path, 'w', newline='', encoding='utf-8-sig'
|
|
876
|
+
) as csvfile:
|
|
877
|
+
writer = csv.writer(csvfile)
|
|
878
|
+
writer.writerows(data)
|
|
879
|
+
|
|
880
|
+
return (
|
|
881
|
+
f"Sheet {sheet_name} exported to {csv_filename} "
|
|
882
|
+
f"in {self.working_directory}."
|
|
883
|
+
)
|
|
884
|
+
except Exception as e:
|
|
885
|
+
logger.error(f"Failed to export sheet to CSV: {e}")
|
|
886
|
+
return f"Error: Failed to export sheet {sheet_name} to CSV: {e}"
|
|
887
|
+
|
|
888
|
+
def get_rows(
|
|
889
|
+
self,
|
|
890
|
+
sheet_name: str,
|
|
891
|
+
start_row: Optional[int] = None,
|
|
892
|
+
end_row: Optional[int] = None,
|
|
893
|
+
) -> Union[List[List[Union[str, int, float, None]]], str]:
|
|
894
|
+
r"""Retrieve rows of data from a sheet.
|
|
895
|
+
|
|
896
|
+
Use this to read data from a sheet. You can get all rows or specify a
|
|
897
|
+
range. Returns actual data as lists, making it easy to process
|
|
898
|
+
programmatically.
|
|
899
|
+
|
|
900
|
+
Args:
|
|
901
|
+
sheet_name (str): Name of the sheet to read from.
|
|
902
|
+
start_row (Optional[int]): First row to read (1-based). If None,
|
|
903
|
+
starts from row 1. (default: :obj:`None`)
|
|
904
|
+
end_row (Optional[int]): Last row to read (1-based). If None,
|
|
905
|
+
reads to the end. (default: :obj:`None`)
|
|
906
|
+
|
|
907
|
+
Returns:
|
|
908
|
+
Union[List[List[Union[str, int, float, None]]], str]:
|
|
909
|
+
List of rows (each row is a list of cell values) or error
|
|
910
|
+
message.
|
|
911
|
+
"""
|
|
912
|
+
if not self.wb:
|
|
913
|
+
return "Error: Workbook not initialized."
|
|
914
|
+
|
|
915
|
+
if sheet_name not in self.wb.sheetnames:
|
|
916
|
+
return f"Error: Sheet {sheet_name} does not exist."
|
|
917
|
+
|
|
918
|
+
ws = self.wb[sheet_name]
|
|
919
|
+
rows = []
|
|
920
|
+
|
|
921
|
+
# Get all rows with data
|
|
922
|
+
for row in ws.iter_rows(
|
|
923
|
+
min_row=start_row, max_row=end_row, values_only=True
|
|
924
|
+
):
|
|
925
|
+
# Skip completely empty rows
|
|
926
|
+
if any(cell is not None for cell in row):
|
|
927
|
+
rows.append(list(row))
|
|
928
|
+
|
|
929
|
+
return rows
|
|
930
|
+
|
|
931
|
+
def append_row(
|
|
932
|
+
self,
|
|
933
|
+
sheet_name: str,
|
|
934
|
+
row_data: List[Union[str, int, float, None]],
|
|
935
|
+
) -> str:
|
|
936
|
+
r"""Add a single row to the end of a sheet.
|
|
937
|
+
|
|
938
|
+
Use this to add one row of data to the end of existing content.
|
|
939
|
+
For multiple rows, use multiple calls to this function.
|
|
940
|
+
|
|
941
|
+
Args:
|
|
942
|
+
sheet_name (str): Name of the target sheet.
|
|
943
|
+
row_data (List[Union[str, int, float, None]]): Single row of data
|
|
944
|
+
to add.
|
|
945
|
+
|
|
946
|
+
Returns:
|
|
947
|
+
str: Success confirmation message or error details.
|
|
948
|
+
"""
|
|
949
|
+
if not self.wb:
|
|
950
|
+
return "Error: Workbook not initialized."
|
|
951
|
+
if sheet_name not in self.wb.sheetnames:
|
|
952
|
+
return f"Error: Sheet {sheet_name} does not exist."
|
|
953
|
+
ws = self.wb[sheet_name]
|
|
954
|
+
ws.append(row_data)
|
|
955
|
+
return f"Row appended to sheet {sheet_name} successfully."
|
|
956
|
+
|
|
957
|
+
def update_row(
|
|
958
|
+
self,
|
|
959
|
+
sheet_name: str,
|
|
960
|
+
row_number: int,
|
|
961
|
+
row_data: List[Union[str, int, float, None]],
|
|
962
|
+
) -> str:
|
|
963
|
+
r"""Update a specific row in the sheet.
|
|
964
|
+
|
|
965
|
+
Use this to replace all data in a specific row with new values.
|
|
966
|
+
|
|
967
|
+
Args:
|
|
968
|
+
sheet_name (str): Name of the sheet to modify.
|
|
969
|
+
row_number (int): The row number to update (1-based, where 1 is
|
|
970
|
+
first row).
|
|
971
|
+
row_data (List[Union[str, int, float, None]]): New data for the
|
|
972
|
+
entire row.
|
|
973
|
+
|
|
974
|
+
Returns:
|
|
975
|
+
str: Success confirmation message or error details.
|
|
976
|
+
"""
|
|
977
|
+
if not self.wb:
|
|
978
|
+
return "Error: Workbook not initialized."
|
|
979
|
+
|
|
980
|
+
if sheet_name not in self.wb.sheetnames:
|
|
981
|
+
return f"Sheet {sheet_name} does not exist."
|
|
982
|
+
|
|
983
|
+
ws = self.wb[sheet_name]
|
|
984
|
+
|
|
985
|
+
# Clear the existing row first
|
|
986
|
+
for col_idx in range(1, ws.max_column + 1):
|
|
987
|
+
ws.cell(row=row_number, column=col_idx).value = None
|
|
988
|
+
|
|
989
|
+
# Set new values
|
|
990
|
+
for col_idx, value in enumerate(row_data, 1):
|
|
991
|
+
ws.cell(row=row_number, column=col_idx).value = value
|
|
992
|
+
|
|
993
|
+
return f"Row {row_number} updated in sheet {sheet_name} successfully."
|
|
183
994
|
|
|
184
995
|
def get_tools(self) -> List[FunctionTool]:
|
|
185
996
|
r"""Returns a list of FunctionTool objects representing the functions
|
|
@@ -190,5 +1001,28 @@ class ExcelToolkit(BaseToolkit):
|
|
|
190
1001
|
the functions in the toolkit.
|
|
191
1002
|
"""
|
|
192
1003
|
return [
|
|
1004
|
+
# File operations
|
|
193
1005
|
FunctionTool(self.extract_excel_content),
|
|
1006
|
+
FunctionTool(self.create_workbook),
|
|
1007
|
+
FunctionTool(self.save_workbook),
|
|
1008
|
+
FunctionTool(self.delete_workbook),
|
|
1009
|
+
FunctionTool(self.export_sheet_to_csv),
|
|
1010
|
+
# Sheet operations
|
|
1011
|
+
FunctionTool(self.create_sheet),
|
|
1012
|
+
FunctionTool(self.delete_sheet),
|
|
1013
|
+
FunctionTool(self.clear_sheet),
|
|
1014
|
+
# Data reading
|
|
1015
|
+
FunctionTool(self.get_rows),
|
|
1016
|
+
FunctionTool(self.get_cell_value),
|
|
1017
|
+
FunctionTool(self.get_column_data),
|
|
1018
|
+
FunctionTool(self.get_range_values),
|
|
1019
|
+
FunctionTool(self.find_cells),
|
|
1020
|
+
# Data writing
|
|
1021
|
+
FunctionTool(self.append_row),
|
|
1022
|
+
FunctionTool(self.update_row),
|
|
1023
|
+
FunctionTool(self.set_cell_value),
|
|
1024
|
+
FunctionTool(self.set_range_values),
|
|
1025
|
+
# Structure modification
|
|
1026
|
+
FunctionTool(self.delete_rows),
|
|
1027
|
+
FunctionTool(self.delete_columns),
|
|
194
1028
|
]
|