message / plugins /json /api.py
hunian
refactor(plugins): 插件短名并统一 MCP tool 为 {plugin}-{tool}
cc826a1
Raw
History Blame Contribute Delete
9.96 kB
import json
import logging
from typing import Any, List, Optional
from fastapi import APIRouter, HTTPException, Request, UploadFile, File
from pydantic import BaseModel, Field
from .openai_parser import get_openai_parser
logger = logging.getLogger(__name__)
router = APIRouter()
plugin = None
core = None
def set_plugin_instance(plugin_instance):
"""设置插件实例"""
global plugin
plugin = plugin_instance
def set_core_instance(core_instance):
"""设置核心逻辑实例"""
global core
core = core_instance
class ExtractRequest(BaseModel):
"""提取请求模型"""
file_path: Optional[str] = Field(default=None, description="JSON文件路径")
json_data: Optional[Any] = Field(default=None, description="JSON数据(直接传入)")
output_file: Optional[str] = Field(default=None, description="输出文件路径(可选)")
class ExtractResponse(BaseModel):
"""提取响应模型"""
success: bool = Field(description="操作是否成功")
contents: List[str] = Field(default=[], description="提取的内容列表")
formatted_output: str = Field(default="", description="格式化后的输出")
error: Optional[str] = Field(default=None, description="错误信息")
class ConversationMessage(BaseModel):
"""对话消息模型"""
role: str = Field(description="消息角色(system/user/assistant)")
content: str = Field(description="消息内容")
class ConversationExtractRequest(BaseModel):
"""对话提取请求模型"""
file_path: Optional[str] = Field(default=None, description="JSON文件路径")
json_data: Optional[Any] = Field(default=None, description="JSON数据(直接传入)")
class ConversationExtractResponse(BaseModel):
"""对话提取响应模型"""
success: bool = Field(description="操作是否成功")
is_conversation: bool = Field(description="是否为对话格式")
messages: List[ConversationMessage] = Field(default=[], description="对话消息列表")
formatted_output: str = Field(default="", description="格式化后的对话记录")
error: Optional[str] = Field(default=None, description="错误信息")
@router.get("/status")
async def get_status():
"""获取插件状态"""
if plugin is None:
return {
"name": "json",
"enabled": False,
"message": "插件未加载",
}
return plugin.get_status()
@router.post("/upload")
async def upload_json(file: UploadFile = File(...)):
"""
上传 JSON/JSONL 文件并解析为对话视图模型
Returns:
对话视图模型 {
"conversation_id": str,
"messages": List[dict],
"raw_warnings": List[str],
}
"""
if plugin is None or not plugin.enabled:
raise HTTPException(status_code=400, detail="插件未启用")
try:
content = await file.read()
text = content.decode("utf-8")
# 检测文件格式
filename = file.filename or ""
is_jsonl = filename.endswith(".jsonl") or filename.endswith(".jsonl")
parser = get_openai_parser()
if is_jsonl:
# JSONL 格式
result = parser.parse_jsonl(text)
else:
# JSON 格式
try:
json_data = json.loads(text)
result = parser.parse_json(json_data)
except json.JSONDecodeError as e:
raise HTTPException(status_code=400, detail=f"JSON 解析失败: {e}")
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"上传解析失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/conversation/parse")
async def parse_conversation(request: Request):
"""
解析 JSON 数据为对话视图模型
接受 JSON body,返回对话视图模型。
"""
if plugin is None or not plugin.enabled:
raise HTTPException(status_code=400, detail="插件未启用")
try:
data = await request.json()
parser = get_openai_parser()
result = parser.parse_json(data)
return result
except Exception as e:
logger.error(f"解析对话失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/extract")
async def extract_content(request: ExtractRequest):
"""
从JSON中提取content字段
支持两种方式:
1. 传入 file_path - 从文件读取JSON
2. 传入 json_data - 直接处理JSON数据
如果是OpenAI对话格式,会自动识别并按对话格式输出
Returns:
提取结果
"""
if plugin is None or not plugin.enabled:
raise HTTPException(status_code=400, detail="插件未启用")
if core is None:
raise HTTPException(status_code=500, detail="核心逻辑未初始化")
try:
# 从文件提取
if request.file_path:
result = core.process_single_file(
request.file_path,
output_file=request.output_file
)
contents = core.extract_content_from_file(request.file_path)
return ExtractResponse(
success=True,
contents=contents,
formatted_output=result
)
# 直接处理JSON数据
if request.json_data is not None:
# 检测是否为对话格式
if core._is_conversation_format(request.json_data):
formatted_output = core._format_conversation(request.json_data)
messages = core._extract_conversation(request.json_data)
contents = [msg["content"] for msg in messages]
else:
contents = core.extract_content_from_json(request.json_data)
formatted_contents = []
for i, content in enumerate(contents, 1):
formatted = core.format_content(content)
formatted_contents.append(f"=== Content {i} ===\n{formatted}")
formatted_output = "\n\n".join(formatted_contents)
return ExtractResponse(
success=True,
contents=contents,
formatted_output=formatted_output
)
raise HTTPException(
status_code=400,
detail="请提供 file_path 或 json_data"
)
except Exception as e:
logger.error(f"提取内容时出错: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/extract-conversation")
async def extract_conversation(request: ConversationExtractRequest):
"""
从JSON中提取对话消息(结构化)
专门用于处理OpenAI格式的对话JSON,返回结构化的消息列表
Returns:
结构化的对话消息
"""
if plugin is None or not plugin.enabled:
raise HTTPException(status_code=400, detail="插件未启用")
if core is None:
raise HTTPException(status_code=500, detail="核心逻辑未初始化")
try:
json_data = None
# 从文件读取
if request.file_path:
import os
if not os.path.exists(request.file_path):
raise HTTPException(status_code=400, detail=f"文件不存在: {request.file_path}")
messages = core.extract_conversation_from_file(request.file_path)
is_conversation = len(messages) > 0
if is_conversation:
formatted_output = core.process_json_file(request.file_path)
else:
formatted_output = "该文件不是对话格式"
return ConversationExtractResponse(
success=True,
is_conversation=is_conversation,
messages=[ConversationMessage(**msg) for msg in messages],
formatted_output=formatted_output
)
# 直接处理JSON数据
if request.json_data is not None:
messages = core.extract_conversation_from_json(request.json_data)
is_conversation = len(messages) > 0
if is_conversation:
formatted_output = core._format_conversation(request.json_data)
else:
formatted_output = "该数据不是对话格式"
return ConversationExtractResponse(
success=True,
is_conversation=is_conversation,
messages=[ConversationMessage(**msg) for msg in messages],
formatted_output=formatted_output
)
raise HTTPException(
status_code=400,
detail="请提供 file_path 或 json_data"
)
except HTTPException:
raise
except Exception as e:
logger.error(f"提取对话时出错: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/extract-batch")
async def extract_batch(request: Request):
"""
批量提取目录中所有JSON文件的content字段
Returns:
批量提取结果
"""
if plugin is None or not plugin.enabled:
raise HTTPException(status_code=400, detail="插件未启用")
if core is None:
raise HTTPException(status_code=500, detail="核心逻辑未初始化")
try:
data = await request.json()
dir_path = data.get("dir_path")
output_file = data.get("output_file")
if not dir_path:
raise HTTPException(status_code=400, detail="请提供 dir_path")
result = core.process_directory(dir_path, output_file=output_file)
return {
"success": True,
"dir_path": dir_path,
"result": result
}
except HTTPException:
raise
except Exception as e:
logger.error(f"批量提取时出错: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))