Skip to content

Commit 2ba0460

Browse files
authored
feat: introduce file extract capability (#3870)
* feat: introduce file extract capability powered by MoonshotAI * fix: correct indentation in default configuration file * fix: add error handling for file extract application in InternalAgentSubStage * fix: update file name handling in InternalAgentSubStage to correctly associate file names with extracted content * feat: add condition settings for local agent runner in default configuration * fix: enhance file naming logic in File component and update prompt handling in InternalAgentSubStage
1 parent 0e034f0 commit 2ba0460

7 files changed

Lines changed: 188 additions & 10 deletions

File tree

astrbot/core/config/default.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@
7676
"reachability_check": False,
7777
"max_agent_step": 30,
7878
"tool_call_timeout": 60,
79+
"file_extract": {
80+
"enable": False,
81+
"provider": "moonshotai",
82+
"moonshotai_api_key": "",
83+
},
7984
},
8085
"provider_stt_settings": {
8186
"enable": False,
@@ -2069,6 +2074,20 @@
20692074
"tool_call_timeout": {
20702075
"type": "int",
20712076
},
2077+
"file_extract": {
2078+
"type": "object",
2079+
"items": {
2080+
"enable": {
2081+
"type": "bool",
2082+
},
2083+
"provider": {
2084+
"type": "string",
2085+
},
2086+
"moonshotai_api_key": {
2087+
"type": "string",
2088+
},
2089+
},
2090+
},
20722091
},
20732092
},
20742093
"provider_stt_settings": {
@@ -2403,6 +2422,36 @@
24032422
"provider_settings.enable": True,
24042423
},
24052424
},
2425+
"file_extract": {
2426+
"description": "文档解析能力",
2427+
"type": "object",
2428+
"items": {
2429+
"provider_settings.file_extract.enable": {
2430+
"description": "启用文档解析能力",
2431+
"type": "bool",
2432+
},
2433+
"provider_settings.file_extract.provider": {
2434+
"description": "文档解析提供商",
2435+
"type": "string",
2436+
"options": ["moonshotai"],
2437+
"condition": {
2438+
"provider_settings.file_extract.enable": True,
2439+
},
2440+
},
2441+
"provider_settings.file_extract.moonshotai_api_key": {
2442+
"description": "Moonshot AI API Key",
2443+
"type": "string",
2444+
"condition": {
2445+
"provider_settings.file_extract.provider": "moonshotai",
2446+
"provider_settings.file_extract.enable": True,
2447+
},
2448+
},
2449+
},
2450+
"condition": {
2451+
"provider_settings.agent_runner_type": "local",
2452+
"provider_settings.enable": True,
2453+
},
2454+
},
24062455
"others": {
24072456
"description": "其他配置",
24082457
"type": "object",

astrbot/core/message/components.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,12 @@ async def _download_file(self):
722722
"""下载文件"""
723723
download_dir = os.path.join(get_astrbot_data_path(), "temp")
724724
os.makedirs(download_dir, exist_ok=True)
725-
file_path = os.path.join(download_dir, f"{uuid.uuid4().hex}")
725+
if self.name:
726+
name, ext = os.path.splitext(self.name)
727+
filename = f"{name}_{uuid.uuid4().hex[:8]}{ext}"
728+
else:
729+
filename = f"{uuid.uuid4().hex}"
730+
file_path = os.path.join(download_dir, filename)
726731
await download_file(self.url, file_path)
727732
self.file_ = os.path.abspath(file_path)
728733

astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from astrbot.core.agent.tool import ToolSet
1010
from astrbot.core.astr_agent_context import AstrAgentContext
1111
from astrbot.core.conversation_mgr import Conversation
12-
from astrbot.core.message.components import Image
12+
from astrbot.core.message.components import File, Image, Reply
1313
from astrbot.core.message.message_event_result import (
1414
MessageChain,
1515
MessageEventResult,
@@ -22,6 +22,7 @@
2222
ProviderRequest,
2323
)
2424
from astrbot.core.star.star_handler import EventType, star_map
25+
from astrbot.core.utils.file_extract import extract_file_moonshotai
2526
from astrbot.core.utils.metrics import Metric
2627
from astrbot.core.utils.session_lock import session_lock_manager
2728

@@ -56,6 +57,13 @@ async def initialize(self, ctx: PipelineContext) -> None:
5657
self.show_reasoning = settings.get("display_reasoning_text", False)
5758
self.kb_agentic_mode: bool = conf.get("kb_agentic_mode", False)
5859

60+
file_extract_conf: dict = settings.get("file_extract", {})
61+
self.file_extract_enabled: bool = file_extract_conf.get("enable", False)
62+
self.file_extract_prov: str = file_extract_conf.get("provider", "moonshotai")
63+
self.file_extract_msh_api_key: str = file_extract_conf.get(
64+
"moonshotai_api_key", ""
65+
)
66+
5967
self.conv_manager = ctx.plugin_manager.context.conversation_manager
6068

6169
def _select_provider(self, event: AstrMessageEvent):
@@ -114,6 +122,50 @@ async def _apply_kb(
114122
req.func_tool = ToolSet()
115123
req.func_tool.add_tool(KNOWLEDGE_BASE_QUERY_TOOL)
116124

125+
async def _apply_file_extract(
126+
self,
127+
event: AstrMessageEvent,
128+
req: ProviderRequest,
129+
):
130+
"""Apply file extract to the provider request"""
131+
file_paths = []
132+
file_names = []
133+
for comp in event.message_obj.message:
134+
if isinstance(comp, File):
135+
file_paths.append(await comp.get_file())
136+
file_names.append(comp.name)
137+
elif isinstance(comp, Reply) and comp.chain:
138+
for reply_comp in comp.chain:
139+
if isinstance(reply_comp, File):
140+
file_paths.append(await reply_comp.get_file())
141+
file_names.append(reply_comp.name)
142+
if not file_paths:
143+
return
144+
if not req.prompt:
145+
req.prompt = "总结一下文件里面讲了什么?"
146+
if self.file_extract_prov == "moonshotai":
147+
if not self.file_extract_msh_api_key:
148+
logger.error("Moonshot AI API key for file extract is not set")
149+
return
150+
file_contents = await asyncio.gather(
151+
*[
152+
extract_file_moonshotai(file_path, self.file_extract_msh_api_key)
153+
for file_path in file_paths
154+
]
155+
)
156+
else:
157+
logger.error(f"Unsupported file extract provider: {self.file_extract_prov}")
158+
return
159+
160+
# add file extract results to contexts
161+
for file_content, file_name in zip(file_contents, file_names):
162+
req.contexts.append(
163+
{
164+
"role": "system",
165+
"content": f"File Extract Results of user uploaded files:\n{file_content}\nFile Name: {file_name or 'Unknown'}",
166+
},
167+
)
168+
117169
def _truncate_contexts(
118170
self,
119171
contexts: list[dict],
@@ -346,6 +398,17 @@ async def process(
346398

347399
event.set_extra("provider_request", req)
348400

401+
# fix contexts json str
402+
if isinstance(req.contexts, str):
403+
req.contexts = json.loads(req.contexts)
404+
405+
# apply file extract
406+
if self.file_extract_enabled:
407+
try:
408+
await self._apply_file_extract(event, req)
409+
except Exception as e:
410+
logger.error(f"Error occurred while applying file extract: {e}")
411+
349412
if not req.prompt and not req.image_urls:
350413
return
351414

@@ -356,10 +419,6 @@ async def process(
356419
# apply knowledge base feature
357420
await self._apply_kb(event, req)
358421

359-
# fix contexts json str
360-
if isinstance(req.contexts, str):
361-
req.contexts = json.loads(req.contexts)
362-
363422
# truncate contexts to fit max length
364423
if req.contexts:
365424
req.contexts = self._truncate_contexts(req.contexts)

astrbot/core/platform/sources/telegram/tg_adapter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,9 @@ async def convert_message(
381381
f"Telegram document file_path is None, cannot save the file {file_name}.",
382382
)
383383
else:
384-
message.message.append(Comp.File(file=file_path, name=file_name))
384+
message.message.append(
385+
Comp.File(file=file_path, name=file_name, url=file_path)
386+
)
385387

386388
elif update.message.video:
387389
file = await update.message.video.get_file()

astrbot/core/utils/file_extract.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from pathlib import Path
2+
3+
from openai import AsyncOpenAI
4+
5+
6+
async def extract_file_moonshotai(file_path: str, api_key: str) -> str:
7+
"""Extract text from a file using Moonshot AI API"""
8+
"""
9+
Args:
10+
file_path: The path to the file to extract text from
11+
api_key: The API key to use to extract text from the file
12+
Returns:
13+
The text extracted from the file
14+
"""
15+
client = AsyncOpenAI(
16+
api_key=api_key,
17+
base_url="https://api.moonshot.cn/v1",
18+
)
19+
file_object = await client.files.create(
20+
file=Path(file_path),
21+
purpose="file-extract", # type: ignore
22+
)
23+
return (await client.files.content(file_id=file_object.id)).text

dashboard/src/i18n/locales/en-US/features/config-metadata.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@
109109
}
110110
}
111111
},
112+
"file_extract": {
113+
"description": "File Extract",
114+
"provider_settings": {
115+
"file_extract": {
116+
"enable": {
117+
"description": "Enable File Extract"
118+
},
119+
"provider": {
120+
"description": "File Extract Provider"
121+
},
122+
"moonshotai_api_key": {
123+
"description": "Moonshot AI API Key"
124+
}
125+
}
126+
}
127+
},
112128
"others": {
113129
"description": "Other Settings",
114130
"provider_settings": {

dashboard/src/i18n/locales/zh-CN/features/config-metadata.json

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
},
1212
"agent_runner_type": {
1313
"description": "执行器",
14-
"labels": ["内置 Agent", "Dify", "Coze", "阿里云百炼应用"]
14+
"labels": [
15+
"内置 Agent",
16+
"Dify",
17+
"Coze",
18+
"阿里云百炼应用"
19+
]
1520
},
1621
"coze_agent_runner_provider_id": {
1722
"description": "Coze Agent 执行器提供商 ID"
@@ -109,6 +114,22 @@
109114
}
110115
}
111116
},
117+
"file_extract": {
118+
"description": "文档解析能力",
119+
"provider_settings": {
120+
"file_extract": {
121+
"enable": {
122+
"description": "启用文档解析能力"
123+
},
124+
"provider": {
125+
"description": "文档解析提供商"
126+
},
127+
"moonshotai_api_key": {
128+
"description": "Moonshot AI API Key"
129+
}
130+
}
131+
}
132+
},
112133
"others": {
113134
"description": "其他配置",
114135
"provider_settings": {
@@ -142,7 +163,10 @@
142163
"unsupported_streaming_strategy": {
143164
"description": "不支持流式回复的平台",
144165
"hint": "选择在不支持流式回复的平台上的处理方式。实时分段回复会在系统接收流式响应检测到诸如标点符号等分段点时,立即发送当前已接收的内容",
145-
"labels": ["实时分段回复", "关闭流式回复"]
166+
"labels": [
167+
"实时分段回复",
168+
"关闭流式回复"
169+
]
146170
},
147171
"max_context_length": {
148172
"description": "最多携带对话轮数",
@@ -457,4 +481,4 @@
457481
}
458482
}
459483
}
460-
}
484+
}

0 commit comments

Comments
 (0)