最新哦,分词策略放到cli了
This commit is contained in:
1 parent
fcc94ffd73
commit
f9f9645fa1
9 files changed
+270
-305
No files matched your search
+26
-4
@@ -10,6 +10,7 @@ from pydantic import Field
|
||||
import re, json
|
||||
from datetime import datetime as dt
|
||||
import requests
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
from langchain_core.messages import HumanMessage, AIMessage, BaseMessage, SystemMessage
|
||||
from langchain_core.outputs import ChatResult, ChatGeneration
|
||||
@@ -17,7 +18,7 @@ from langchain_openai import ChatOpenAI
|
||||
|
||||
from agent.utils.image_utils import image_bytes_to_openai_image_url_part
|
||||
from agent.utils.env_utils import LLM_API_KEY, LLM_BASE_URL, LLM_MODEL, LLM_TIMEOUT, TTS_BASE_URL, ASR_BASE_URL
|
||||
from agent.utils.agent_utils import assistant_text, Documents_process
|
||||
from agent.utils.agent_utils import assistant_text, Documents_process, split_text, merge_wavs
|
||||
from agent.utils.image_utils import image_path_to_openai_image_url_part, image_bytes_to_openai_image_url_part, IMAGE_FILE_EXTENSIONS
|
||||
from agent.memory.system_prompt import VISION_SYSTEM_PROMPT
|
||||
|
||||
@@ -218,9 +219,30 @@ class ChatQwen(ChatOpenAI):
|
||||
return new_messages
|
||||
|
||||
def _text_to_audio(self, text: str) -> bytes:
|
||||
resp = requests.get(f"{TTS_BASE_URL}/tts", params={"text": text})
|
||||
resp.raise_for_status()
|
||||
return resp.content
|
||||
chunks = split_text(text)
|
||||
if len(chunks) == 1:
|
||||
resp = requests.get(f"{TTS_BASE_URL}/tts", params={"text": chunks[0]})
|
||||
resp.raise_for_status()
|
||||
return resp.content
|
||||
|
||||
# 多块并行请求
|
||||
audio_parts = [None] * len(chunks)
|
||||
with ThreadPoolExecutor(max_workers=min(len(chunks), 4)) as pool:
|
||||
future_to_idx = {
|
||||
pool.submit(
|
||||
lambda t: requests.get(
|
||||
f"{TTS_BASE_URL}/tts", params={"text": t}
|
||||
).content,
|
||||
chunk
|
||||
): idx for idx, chunk in enumerate(chunks)
|
||||
}
|
||||
for future in as_completed(future_to_idx):
|
||||
idx = future_to_idx[future]
|
||||
try:
|
||||
audio_parts[idx] = future.result()
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"TTS request failed for chunk {idx}: {e}")
|
||||
return merge_wavs(audio_parts)
|
||||
|
||||
@staticmethod
|
||||
def _get_wav_duration_seconds(audio_bytes: bytes) -> float | None:
|
||||
|
||||
@@ -5,7 +5,7 @@ import shutil
|
||||
import sqlite3
|
||||
from typing import List, Tuple, Any
|
||||
import pathlib
|
||||
import mimetypes
|
||||
import mimetypes, re
|
||||
|
||||
from langchain_community.chat_message_histories import SQLChatMessageHistory
|
||||
from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage
|
||||
@@ -527,32 +527,50 @@ class Documents_process:
|
||||
return Documents_process.process_tabular(file_path)
|
||||
else:
|
||||
raise ValueError(f"未处理的文件类型: {file_type}")
|
||||
# 文本分块工具
|
||||
SENT_SPLIT_PATTERN = re.compile(r'(?<=[。!?;,、])')
|
||||
REMOVE_PUNCT_PATTERN = re.compile(r'[。!?;,、:;“”()《》【】\-\_\—\~\.\,\!\?\s·]')
|
||||
def effective_length(text: str) -> int:
|
||||
return len(REMOVE_PUNCT_PATTERN.sub('', text))
|
||||
|
||||
if __name__ == "__main__":
|
||||
docx_path = r"C:\Users\86166\Desktop\Agent_Learning_minor\信息科学与工程学院-赵永顺-《兼爱》——墨翟一生的“白月光”.docx"
|
||||
pdf_path = r"C:\Users\86166\Desktop\Agent_Learning_minor\信息科学与工程学院-赵永顺-《兼爱》——墨翟一生的“白月光”.pdf"
|
||||
xlsx_path = r"C:\Users\86166\Desktop\Agent_Learning_minor\电科二班2025德育评价.xlsx"
|
||||
py_path = r"C:\Users\86166\Desktop\Agent_Learning_minor\speech_server.py"
|
||||
def split_text(text: str, min_effective_chars: int = 20) -> list[str]:
|
||||
# 先按标点粗分
|
||||
raw_chunks = [c.strip() for c in SENT_SPLIT_PATTERN.split(text) if c.strip()]
|
||||
if not raw_chunks:
|
||||
return [text]
|
||||
|
||||
merged = []
|
||||
buffer = ""
|
||||
for chunk in raw_chunks:
|
||||
buffer += chunk
|
||||
# 当累积的有效字符数达到阈值,或已是最后一个块时,切出一块
|
||||
if effective_length(buffer) >= min_effective_chars:
|
||||
merged.append(buffer)
|
||||
buffer = ""
|
||||
if buffer: # 剩余部分(必然小于阈值)
|
||||
if merged:
|
||||
merged[-1] += buffer # 合并到最后一块
|
||||
else:
|
||||
merged.append(buffer) # 整段文本都太短,保留原样
|
||||
return merged
|
||||
|
||||
output_path = "test_results.txt"
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
test_files = [
|
||||
("py", py_path),
|
||||
("docx", docx_path),
|
||||
("xlsx", xlsx_path),
|
||||
("pdf", pdf_path),
|
||||
]
|
||||
for label, path in test_files:
|
||||
f.write(f"\n===== 测试 {label} 文件:{path} =====\n")
|
||||
try:
|
||||
text, images = Documents_process.process(path)
|
||||
f.write(f"提取文本长度:{len(text)}\n")
|
||||
f.write(f"提取图片数量:{len(images)}\n")
|
||||
f.write("文本预览:\n")
|
||||
f.write(text)
|
||||
except Exception as e:
|
||||
f.write(f"❌ 处理失败: {e}\n")
|
||||
print(f"测试结果已写入 {output_path}")
|
||||
|
||||
def merge_wavs(audio_parts: list[bytes]) -> bytes:
|
||||
"""将多个 WAV 字节串按顺序拼接成一个 WAV"""
|
||||
if len(audio_parts) == 1:
|
||||
return audio_parts[0]
|
||||
import io, wave
|
||||
data_frames = []
|
||||
params = None
|
||||
for part in audio_parts:
|
||||
with wave.open(io.BytesIO(part), 'rb') as wf:
|
||||
if params is None:
|
||||
params = wf.getparams()
|
||||
data_frames.append(wf.readframes(wf.getnframes()))
|
||||
buf = io.BytesIO()
|
||||
with wave.open(buf, 'wb') as wf_out:
|
||||
wf_out.setparams(params)
|
||||
for frames in data_frames:
|
||||
wf_out.writeframes(frames)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
+6
-19
@@ -27,21 +27,13 @@ if str(_SRC) not in sys.path:
|
||||
from agent import chatbot_agent as ca # noqa: E402
|
||||
from web import ui_session as ui # noqa: E402
|
||||
from agent.utils.image_utils import IMAGE_FILE_EXTENSIONS # noqa: E402
|
||||
from agent.utils.agent_utils import DOC_EXT_MAP # noqa: E402
|
||||
|
||||
_WEB_DIR = Path(__file__).resolve().parent
|
||||
_IMAGE_DIR = _WEB_DIR / "image"
|
||||
|
||||
_AUDIO_EXT = frozenset({".wav", ".mp3", ".m4a", ".flac", ".ogg", ".webm"})
|
||||
_IMAGE_EXT = frozenset(IMAGE_FILE_EXTENSIONS)
|
||||
|
||||
|
||||
def _infer_upload_kind(fp: str) -> str | None:
|
||||
ext = os.path.splitext(fp)[1].lower()
|
||||
if ext in _IMAGE_EXT:
|
||||
return "image"
|
||||
if ext in _AUDIO_EXT:
|
||||
return "audio"
|
||||
return "file"
|
||||
_FILE_EXT = frozenset(DOC_EXT_MAP.keys())
|
||||
|
||||
|
||||
def _data_url_for_file(fp: str) -> str | None:
|
||||
@@ -92,7 +84,6 @@ def _serialize_content(content: Any, *, meta: dict[str, Any] | None = None, role
|
||||
parts.append({"type": "text", "text": piece.get("text") or ""})
|
||||
continue
|
||||
fp = piece.get("path") or (piece.get("file") or {}).get("path")
|
||||
display_name = piece.get("name")
|
||||
if piece.get("type") == "image_url":
|
||||
fp = fp or (piece.get("image_url") or {}).get("url")
|
||||
if piece.get("type") == "audio":
|
||||
@@ -104,19 +95,19 @@ def _serialize_content(content: Any, *, meta: dict[str, Any] | None = None, role
|
||||
continue
|
||||
ext = os.path.splitext(fp)[1].lower()
|
||||
if ext in IMAGE_FILE_EXTENSIONS:
|
||||
parts.append({"type": "image", "url": data_url, "path": fp, "name": display_name or os.path.basename(fp)})
|
||||
parts.append({"type": "image", "url": data_url, "path": fp, "name": os.path.basename(fp)})
|
||||
elif ext in _AUDIO_EXT:
|
||||
audio_meta = meta.get("audio") if isinstance(meta.get("audio"), dict) else {}
|
||||
parts.append({
|
||||
"type": "audio",
|
||||
"url": data_url,
|
||||
"path": fp,
|
||||
"name": display_name or os.path.basename(fp),
|
||||
"name": os.path.basename(fp),
|
||||
"autoplay": bool(meta.get("autoplay") or audio_meta.get("autoplay")) if role == "assistant" else False,
|
||||
"duration_seconds": audio_meta.get("duration_seconds"),
|
||||
})
|
||||
else:
|
||||
parts.append({"type": "file", "url": data_url, "path": fp, "name": display_name or os.path.basename(fp)})
|
||||
parts.append({"type": "file", "url": data_url, "path": fp, "name": os.path.basename(fp)})
|
||||
return parts
|
||||
|
||||
|
||||
@@ -253,7 +244,6 @@ def _materialize_uploads_to_temp(
|
||||
"""将上传文件落盘到临时目录,返回 ``(query_dict, tmp_root)``;调用方须在 finally 中删除 ``tmp_root``。"""
|
||||
tmp_root = tempfile.mkdtemp(prefix="web_mm_")
|
||||
file_paths: list[str] = []
|
||||
file_names: list[str] = []
|
||||
for uf in images or []:
|
||||
if uf is None or not uf.filename:
|
||||
continue
|
||||
@@ -262,7 +252,6 @@ def _materialize_uploads_to_temp(
|
||||
with open(dest, "wb") as f:
|
||||
shutil.copyfileobj(uf.file, f)
|
||||
file_paths.append(dest)
|
||||
file_names.append(uf.filename)
|
||||
|
||||
for uf in files or []:
|
||||
if uf is None or not uf.filename:
|
||||
@@ -272,7 +261,6 @@ def _materialize_uploads_to_temp(
|
||||
with open(dest, "wb") as f:
|
||||
shutil.copyfileobj(uf.file, f)
|
||||
file_paths.append(dest)
|
||||
file_names.append(uf.filename)
|
||||
|
||||
if audio is not None and audio.filename:
|
||||
ext = Path(audio.filename).suffix or ".bin"
|
||||
@@ -280,9 +268,8 @@ def _materialize_uploads_to_temp(
|
||||
with open(dest, "wb") as f:
|
||||
shutil.copyfileobj(audio.file, f)
|
||||
file_paths.append(dest)
|
||||
file_names.append(audio.filename)
|
||||
|
||||
query = {"text": (text or "").strip(), "files": file_paths, "file_names": file_names}
|
||||
query = {"text": (text or "").strip(), "files": file_paths}
|
||||
return query, tmp_root
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user