add v2 api

This commit is contained in:
朱潮 2025-11-04 23:16:21 +08:00
parent ed609eba6c
commit 40aa71b966
5 changed files with 571 additions and 88 deletions

View File

@ -217,6 +217,83 @@ def submit_and_monitor_task():
submit_and_monitor_task() submit_and_monitor_task()
``` ```
### 4. 项目目录树接口
#### 获取完整目录树
**端点**: `GET /api/v1/projects/tree`
```bash
# 获取完整目录树
curl "http://localhost:8001/api/v1/projects/tree"
# 只显示目录结构(不包含文件)
curl "http://localhost:8001/api/v1/projects/tree?include_files=false"
# 只显示data目录
curl "http://localhost:8001/api/v1/projects/tree?filter_type=data"
```
**响应示例**:
```json
{
"success": true,
"message": "目录树获取成功",
"tree": {
"name": "projects",
"path": "",
"type": "directory",
"children": [
{
"name": "data",
"path": "data",
"type": "directory",
"children": [
{
"name": "1624be71-5432-40bf-9758-f4aecffd4e9c",
"path": "data/1624be71-5432-40bf-9758-f4aecffd4e9c",
"type": "directory",
"children": [...]
}
]
}
],
"size": 0,
"modified_time": 1234567890
},
"stats": {
"total_directories": 15,
"total_files": 32,
"total_size": 1048576
}
}
```
#### 获取子目录树结构
**端点**: `GET /api/v1/projects/subtree/{sub_path:path}`
```bash
# 获取特定项目的目录结构
curl "http://localhost:8001/api/v1/projects/subtree/data/1624be71-5432-40bf-9758-f4aecffd4e9c"
# 只显示目录层级
curl "http://localhost:8001/api/v1/projects/subtree/data/1624be71-5432-40bf-9758-f4aecffd4e9c?include_files=false"
```
**参数说明**:
- `sub_path`: 子目录路径,如 'data/1624be71-5432-40bf-9758-f4aecffd4e9c'
- `include_files`: 是否包含文件详情默认true
- `max_depth`: 最大深度限制默认10
**功能特性**:
- 递归构建完整的目录树结构
- 包含文件大小和修改时间信息
- 支持过滤文件类型和目录层级
- 提供统计信息(目录数、文件数、总大小)
- 安全的错误处理机制
```
--- ---
## 🗃️ 数据包结构 ## 🗃️ 数据包结构
@ -308,6 +385,10 @@ curl -X POST "http://localhost:8001/api/v1/tasks/cleanup?older_than_days=7"
- `DELETE /api/v1/task/{task_id}` - 删除任务记录 - `DELETE /api/v1/task/{task_id}` - 删除任务记录
- `POST /api/v1/project/cleanup` - 清理项目数据 - `POST /api/v1/project/cleanup` - 清理项目数据
### 项目目录树接口
- `GET /api/v1/projects/tree` - 获取projects文件夹完整目录树结构
- `GET /api/v1/projects/subtree/{sub_path:path}` - 获取指定子目录的树结构
### 系统管理接口 ### 系统管理接口
- `GET /api/health` - 健康检查 - `GET /api/health` - 健康检查
- `GET /system/status` - 系统状态 - `GET /system/status` - 系统状态

177
api_v2_example.md Normal file
View File

@ -0,0 +1,177 @@
# API v2 Usage Example
## Overview
API v2 提供了简化的聊天完成接口,与 v1 接口共享核心逻辑,确保功能一致性和代码维护性。
## Endpoint
`POST /api/v2/chat/completions`
## Description
This is a simplified version of the chat completions API that only requires essential parameters. All other configuration parameters are automatically fetched from the backend bot configuration API.
## Code Architecture (重构后的代码结构)
### 1. 公共函数提取
- **`process_messages()`**: 处理消息列表,包括[ANSWER]分割和语言指令添加
- **`create_agent_and_generate_response()`**: 创建agent并生成响应的公共逻辑
- **`create_project_directory()`**: 创建项目目录的公共逻辑
- **`extract_api_key_from_auth()`**: 从Authorization header中提取API key
### 2. 不同的鉴权方式
- **v1接口**: Authorization header中的API key直接用作模型API密钥
```bash
Authorization: Bearer your-model-api-key
```
- **v2接口**: 需要有效的MD5哈希令牌进行认证
```bash
# 生成鉴权token
token=$(echo -n "master:your-bot-id" | md5sum | cut -d' ' -f1)
Authorization: Bearer ${token}
```
### 3. 接口设计
- **`/api/v1/chat/completions`**: 处理 `ChatRequest`,直接使用请求中的所有参数
- **`/api/v2/chat/completions`**: 处理 `ChatRequestV2`,从后端获取配置参数
### 4. 设计优势
- ✅ 最大化代码复用,减少重复逻辑
- ✅ 保持不同的鉴权方式,满足不同需求
- ✅ 清晰的函数分离,易于维护和测试
- ✅ 统一的错误处理和响应格式
- ✅ 异步HTTP请求提高并发性能
- ✅ 使用aiohttp替代requests避免阻塞
## Request Format
### Required Parameters
- `bot_id`: string - The target robot ID
- `messages`: array of message objects - Conversation messages
### Optional Parameters
- `stream`: boolean - Whether to stream responses (default: false)
- `tool_response`: boolean - Whether to include tool responses (default: false)
- `language`: string - Response language (default: "ja")
### Message Object Format
```json
{
"role": "user" | "assistant" | "system",
"content": "string"
}
```
## Example Request
### Basic Request
```bash
curl -X POST "http://localhost:8001/api/v2/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"bot_id": "1624be71-5432-40bf-9758-f4aecffd4e9c",
"messages": [
{
"role": "user",
"content": "Hello, how are you?"
}
],
"language": "en",
"stream": false
}'
```
### Streaming Request
```bash
curl -X POST "http://localhost:8001/api/v2/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"bot_id": "1624be71-5432-40bf-9758-f4aecffd4e9c",
"messages": [
{
"role": "user",
"content": "Tell me about yourself"
}
],
"language": "ja",
"stream": true
}'
```
## Backend Configuration
The endpoint automatically fetches the following configuration from `{BACKEND_HOST}/v1/agent_bot_config/{bot_id}`:
- `model`: Model name (e.g., "qwen/qwen3-next-80b-a3b-instruct")
- `model_server`: Model server URL
- `dataset_ids`: Array of dataset IDs for knowledge base
- `system_prompt`: System prompt for the agent
- `mcp_settings`: MCP configuration settings
- `robot_type`: Type of robot (e.g., "catalog_agent")
- `api_key`: API key for model server access
## Authentication
### v2 API Authentication (Required)
The v2 endpoint requires a specific authentication token format:
**Token Generation:**
```bash
# Method 1: Using environment variables (recommended)
export MASTERKEY="your-master-key"
export BOT_ID="1624be71-5432-40bf-9758-f4aecffd4e9c"
token=$(echo -n "${MASTERKEY}:${BOT_ID}" | md5sum | cut -d' ' -f1)
# Method 2: Direct calculation
token=$(echo -n "master:1624be71-5432-40bf-9758-f4aecffd4e9c" | md5sum | cut -d' ' -f1)
```
**Usage:**
```bash
curl -X POST "http://localhost:8001/api/v2/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-d '{
"bot_id": "1624be71-5432-40bf-9758-f4aecffd4e9c",
"messages": [
{
"role": "user",
"content": "Hello"
}
]
}'
```
**Authentication Errors:**
- `401 Unauthorized`: Missing Authorization header
- `403 Forbidden`: Invalid authentication token
## Response Format
Returns the same response format as `/api/v1/chat/completions`:
### Non-Streaming Response
```json
{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Response content here"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30
}
}
```
### Streaming Response
Returns Server-Sent Events (SSE) format compatible with OpenAI's streaming API.

View File

@ -3,6 +3,9 @@ import os
import tempfile import tempfile
import shutil import shutil
import uuid import uuid
import hashlib
import requests
import aiohttp
from typing import AsyncGenerator, Dict, List, Optional, Union, Any from typing import AsyncGenerator, Dict, List, Optional, Union, Any
from datetime import datetime from datetime import datetime
@ -36,6 +39,9 @@ from utils import (
get_global_agent_manager, init_global_agent_manager get_global_agent_manager, init_global_agent_manager
) )
# Import ChatRequestV2 directly from api_models
from utils.api_models import ChatRequestV2
# Import modified_assistant # Import modified_assistant
from modified_assistant import update_agent_llm from modified_assistant import update_agent_llm
@ -122,14 +128,14 @@ app.add_middleware(
# Models are now imported from utils module # Models are now imported from utils module
async def generate_stream_response(agent, messages, request) -> AsyncGenerator[str, None]: async def generate_stream_response(agent, messages, tool_response: bool, model: str) -> AsyncGenerator[str, None]:
"""生成流式响应""" """生成流式响应"""
accumulated_content = "" accumulated_content = ""
chunk_id = 0 chunk_id = 0
try: try:
for response in agent.run(messages=messages): for response in agent.run(messages=messages):
previous_content = accumulated_content previous_content = accumulated_content
accumulated_content = get_content_from_messages(response, tool_response=request.tool_response) accumulated_content = get_content_from_messages(response, tool_response=tool_response)
# 计算新增的内容 # 计算新增的内容
if accumulated_content.startswith(previous_content): if accumulated_content.startswith(previous_content):
@ -146,7 +152,7 @@ async def generate_stream_response(agent, messages, request) -> AsyncGenerator[s
"id": f"chatcmpl-{chunk_id}", "id": f"chatcmpl-{chunk_id}",
"object": "chat.completion.chunk", "object": "chat.completion.chunk",
"created": int(__import__('time').time()), "created": int(__import__('time').time()),
"model": request.model, "model": model,
"choices": [{ "choices": [{
"index": 0, "index": 0,
"delta": { "delta": {
@ -163,7 +169,7 @@ async def generate_stream_response(agent, messages, request) -> AsyncGenerator[s
"id": f"chatcmpl-{chunk_id + 1}", "id": f"chatcmpl-{chunk_id + 1}",
"object": "chat.completion.chunk", "object": "chat.completion.chunk",
"created": int(__import__('time').time()), "created": int(__import__('time').time()),
"model": request.model, "model": model,
"choices": [{ "choices": [{
"index": 0, "index": 0,
"delta": {}, "delta": {},
@ -441,78 +447,166 @@ async def chat_completions(request: ChatRequest, authorization: Optional[str] =
{"dataset_ids": ["project-123", "project-456"], "bot_id": "my-bot-002"} {"dataset_ids": ["project-123", "project-456"], "bot_id": "my-bot-002"}
""" """
try: try:
# 从Authorization header中提取API key # v1接口从Authorization header中提取API key作为模型API密钥
api_key = None api_key = extract_api_key_from_auth(authorization)
if authorization:
# 移除 "Bearer " 前缀
if authorization.startswith("Bearer "):
api_key = authorization[7:]
else:
api_key = authorization
# 获取bot_id必需参数 # 获取bot_id必需参数
bot_id = request.bot_id bot_id = request.bot_id
if not bot_id: if not bot_id:
raise HTTPException(status_code=400, detail="bot_id is required") raise HTTPException(status_code=400, detail="bot_id is required")
# 获取dataset_ids可选参数当提供时必须是数组 # 创建项目目录如果有dataset_ids
dataset_ids_list = request.dataset_ids project_dir = create_project_directory(request.dataset_ids, bot_id)
project_dir = None
# 只有当提供了 dataset_ids 时才创建机器人目录并合并数据
if dataset_ids_list and len(dataset_ids_list) > 0:
from utils.multi_project_manager import create_robot_project
project_dir = create_robot_project(dataset_ids_list, bot_id)
# 收集额外参数作为 generate_cfg # 收集额外参数作为 generate_cfg
exclude_fields = {'messages', 'model', 'model_server', 'dataset_ids', 'language', 'tool_response', 'system_prompt', 'mcp_settings' ,'stream', 'robot_type', 'bot_id'} exclude_fields = {'messages', 'model', 'model_server', 'dataset_ids', 'language', 'tool_response', 'system_prompt', 'mcp_settings' ,'stream', 'robot_type', 'bot_id'}
generate_cfg = {k: v for k, v in request.model_dump().items() if k not in exclude_fields} generate_cfg = {k: v for k, v in request.model_dump().items() if k not in exclude_fields}
# 从全局管理器获取或创建助手实例配置读取逻辑已在agent_manager内部处理 # 处理消息
agent = await agent_manager.get_or_create_agent( messages = process_messages(request.messages, request.language)
# 调用公共的agent创建和响应生成逻辑
return await create_agent_and_generate_response(
bot_id=bot_id, bot_id=bot_id,
project_dir=project_dir,
model_name=request.model,
api_key=api_key, api_key=api_key,
messages=messages,
stream=request.stream,
tool_response=request.tool_response,
model_name=request.model,
model_server=request.model_server, model_server=request.model_server,
generate_cfg=generate_cfg,
language=request.language, language=request.language,
system_prompt=request.system_prompt, system_prompt=request.system_prompt,
mcp_settings=request.mcp_settings, mcp_settings=request.mcp_settings,
robot_type=request.robot_type robot_type=request.robot_type,
project_dir=project_dir,
generate_cfg=generate_cfg
) )
# 构建包含项目信息的消息上下文
messages = [] except Exception as e:
for msg in request.messages: import traceback
error_details = traceback.format_exc()
print(f"Error in chat_completions: {str(e)}")
print(f"Full traceback: {error_details}")
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
async def fetch_bot_config(bot_id: str) -> Dict[str, Any]:
"""获取机器人配置从后端API"""
try:
backend_host = os.getenv("BACKEND_HOST", "https://api-dev.gptbase.ai")
url = f"{backend_host}/v1/agent_bot_config/{bot_id}"
auth_token = generate_v2_auth_token(bot_id)
headers = {
"content-type": "application/json",
"authorization": f"Bearer {auth_token}"
}
print(url,headers)
# 使用异步HTTP请求
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, timeout=30) as response:
if response.status != 200:
raise HTTPException(
status_code=400,
detail=f"Failed to fetch bot config: API returned status code {response.status}"
)
# 解析响应
response_data = await response.json()
if not response_data.get("success"):
raise HTTPException(
status_code=400,
detail=f"Failed to fetch bot config: {response_data.get('message', 'Unknown error')}"
)
return response_data.get("data", {})
except aiohttp.ClientError as e:
raise HTTPException(
status_code=500,
detail=f"Failed to connect to backend API: {str(e)}"
)
except Exception as e:
if isinstance(e, HTTPException):
raise
raise HTTPException(
status_code=500,
detail=f"Failed to fetch bot config: {str(e)}"
)
def process_messages(messages: List[Message], language: Optional[str] = None) -> List[Dict[str, str]]:
"""处理消息列表,包括[ANSWER]分割和语言指令添加"""
processed_messages = []
# 处理每条消息
for msg in messages:
if msg.role == "assistant": if msg.role == "assistant":
# 对assistant消息进行[ANSWER]分割处理,只保留最后一段 # 对assistant消息进行[ANSWER]分割处理,只保留最后一段
content_parts = msg.content.split("[ANSWER]") content_parts = msg.content.split("[ANSWER]")
if content_parts: if content_parts:
# 取最后一段非空文本 # 取最后一段非空文本
last_part = content_parts[-1].strip() last_part = content_parts[-1].strip()
messages.append({"role": msg.role, "content": last_part}) processed_messages.append({"role": msg.role, "content": last_part})
else: else:
messages.append({"role": msg.role, "content": msg.content}) processed_messages.append({"role": msg.role, "content": msg.content})
else: else:
messages.append({"role": msg.role, "content": msg.content}) processed_messages.append({"role": msg.role, "content": msg.content})
# 在最后一条消息的末尾追加回复语言 # 在最后一条消息的末尾追加回复语言
if messages and request.language: if processed_messages and language:
language_map = { language_map = {
'zh': '请用中文回复', 'zh': '请用中文回复',
'en': 'Please reply in English', 'en': 'Please reply in English',
'ja': '日本語で回答してください', 'ja': '日本語で回答してください',
'jp': '日本語で回答してください' 'jp': '日本語で回答してください'
} }
language_instruction = language_map.get(request.language.lower(), '') language_instruction = language_map.get(language.lower(), '')
if language_instruction: if language_instruction:
# 在最后一条消息末尾追加语言指令 # 在最后一条消息末尾追加语言指令
messages[-1]['content'] = messages[-1]['content'] + f"\n\n{language_instruction}" processed_messages[-1]['content'] = processed_messages[-1]['content'] + f"\n\n{language_instruction}"
return processed_messages
async def create_agent_and_generate_response(
bot_id: str,
api_key: str,
messages: List[Dict[str, str]],
stream: bool,
tool_response: bool,
model_name: str,
model_server: str,
language: str,
system_prompt: Optional[str],
mcp_settings: Optional[List[Dict]],
robot_type: str,
project_dir: Optional[str] = None,
generate_cfg: Optional[Dict] = None
) -> Union[ChatResponse, StreamingResponse]:
"""创建agent并生成响应的公共逻辑"""
if generate_cfg is None:
generate_cfg = {}
# 从全局管理器获取或创建助手实例
agent = await agent_manager.get_or_create_agent(
bot_id=bot_id,
project_dir=project_dir,
model_name=model_name,
api_key=api_key,
model_server=model_server,
generate_cfg=generate_cfg,
language=language,
system_prompt=system_prompt,
mcp_settings=mcp_settings,
robot_type=robot_type
)
# 根据stream参数决定返回流式还是非流式响应 # 根据stream参数决定返回流式还是非流式响应
if request.stream: if stream:
return StreamingResponse( return StreamingResponse(
generate_stream_response(agent, messages, request), generate_stream_response(agent, messages, tool_response, model_name),
media_type="text/event-stream", media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"} headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}
) )
@ -522,7 +616,7 @@ async def chat_completions(request: ChatRequest, authorization: Optional[str] =
if final_responses and len(final_responses) > 0: if final_responses and len(final_responses) > 0:
# 使用 get_content_from_messages 处理响应,支持 tool_response 参数 # 使用 get_content_from_messages 处理响应,支持 tool_response 参数
content = get_content_from_messages(final_responses, tool_response=request.tool_response) content = get_content_from_messages(final_responses, tool_response=tool_response)
# 构造OpenAI格式的响应 # 构造OpenAI格式的响应
return ChatResponse( return ChatResponse(
@ -535,18 +629,134 @@ async def chat_completions(request: ChatRequest, authorization: Optional[str] =
"finish_reason": "stop" "finish_reason": "stop"
}], }],
usage={ usage={
"prompt_tokens": sum(len(msg.content) for msg in request.messages), "prompt_tokens": sum(len(msg.get("content", "")) for msg in messages),
"completion_tokens": len(content), "completion_tokens": len(content),
"total_tokens": sum(len(msg.content) for msg in request.messages) + len(content) "total_tokens": sum(len(msg.get("content", "")) for msg in messages) + len(content)
} }
) )
else: else:
raise HTTPException(status_code=500, detail="No response from agent") raise HTTPException(status_code=500, detail="No response from agent")
def create_project_directory(dataset_ids: List[str], bot_id: str) -> Optional[str]:
"""创建项目目录的公共逻辑"""
if not dataset_ids:
return None
try:
from utils.multi_project_manager import create_robot_project
return create_robot_project(dataset_ids, bot_id)
except Exception as e:
print(f"Error creating project directory: {e}")
return None
def extract_api_key_from_auth(authorization: Optional[str]) -> Optional[str]:
"""从Authorization header中提取API key"""
if not authorization:
return None
# 移除 "Bearer " 前缀
if authorization.startswith("Bearer "):
return authorization[7:]
else:
return authorization
def generate_v2_auth_token(bot_id: str) -> str:
"""生成v2接口的认证token"""
masterkey = os.getenv("MASTERKEY", "master")
token_input = f"{masterkey}:{bot_id}"
return hashlib.md5(token_input.encode()).hexdigest()
@app.post("/api/v2/chat/completions")
async def chat_completions_v2(request: ChatRequestV2, authorization: Optional[str] = Header(None)):
"""
Chat completions API v2 with simplified parameters.
Only requires messages, stream, tool_response, bot_id, and language parameters.
Other parameters are fetched from the backend bot configuration API.
Args:
request: ChatRequestV2 containing only essential parameters
authorization: Authorization header for authentication (different from v1)
Returns:
Union[ChatResponse, StreamingResponse]: Chat completion response or stream
Required Parameters:
- bot_id: str - 目标机器人ID
- messages: List[Message] - 对话消息列表
Optional Parameters:
- stream: bool - 是否流式输出默认false
- tool_response: bool - 是否包含工具响应默认false
- language: str - 回复语言默认"ja"
Authentication:
- Requires valid MD5 hash token: MD5(MASTERKEY:bot_id)
- Authorization header should contain: Bearer {token}
- Uses MD5 hash of MASTERKEY:bot_id for backend API authentication
- Optionally uses API key from bot config for model access
"""
try:
# 获取bot_id必需参数
bot_id = request.bot_id
if not bot_id:
raise HTTPException(status_code=400, detail="bot_id is required")
# v2接口鉴权验证
expected_token = generate_v2_auth_token(bot_id)
provided_token = extract_api_key_from_auth(authorization)
if not provided_token:
raise HTTPException(
status_code=401,
detail="Authorization header is required for v2 API"
)
if provided_token != expected_token:
raise HTTPException(
status_code=403,
detail=f"Invalid authentication token. Expected: {expected_token[:8]}..., Provided: {provided_token[:8]}..."
)
# 从后端API获取机器人配置使用v2的鉴权方式
bot_config = await fetch_bot_config(bot_id)
# v2接口API密钥优先从后端配置获取其次才从Authorization header获取
# 注意这里的Authorization header已经用于鉴权不再作为API key使用
api_key = bot_config.get("api_key")
# 创建项目目录从后端配置获取dataset_ids
project_dir = create_project_directory(bot_config.get("dataset_ids", []), bot_id)
# 处理消息
messages = process_messages(request.messages, request.language)
# 调用公共的agent创建和响应生成逻辑
return await create_agent_and_generate_response(
bot_id=bot_id,
api_key=api_key,
messages=messages,
stream=request.stream,
tool_response=request.tool_response,
model_name=bot_config.get("model", "qwen/qwen3-next-80b-a3b-instruct"),
model_server=bot_config.get("model_server", ""),
language=request.language or bot_config.get("language", "ja"),
system_prompt=bot_config.get("system_prompt"),
mcp_settings=bot_config.get("mcp_settings", []),
robot_type=bot_config.get("robot_type", "agent"),
project_dir=project_dir,
generate_cfg={} # v2接口不传递额外的generate_cfg
)
except HTTPException:
raise
except Exception as e: except Exception as e:
import traceback import traceback
error_details = traceback.format_exc() error_details = traceback.format_exc()
print(f"Error in chat_completions: {str(e)}") print(f"Error in chat_completions_v2: {str(e)}")
print(f"Full traceback: {error_details}") print(f"Full traceback: {error_details}")
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")

View File

@ -5,6 +5,7 @@ RAG检索MCP服务器
""" """
import asyncio import asyncio
import hashlib
import json import json
import sys import sys
import os import os
@ -46,8 +47,14 @@ def rag_retrieve(query: str, top_k: int = 50) -> Dict[str, Any]:
] ]
} }
# 获取masterkey并生成认证token
masterkey = os.getenv("MASTERKEY", "master")
token_input = f"{masterkey}:{bot_id}"
auth_token = hashlib.md5(token_input.encode()).hexdigest()
headers = { headers = {
"content-type": "application/json" "content-type": "application/json",
"authorization": f"Bearer {auth_token}"
} }
data = { data = {
"query": query, "query": query,

View File

@ -53,6 +53,14 @@ class ChatRequest(BaseModel):
robot_type: Optional[str] = "agent" robot_type: Optional[str] = "agent"
class ChatRequestV2(BaseModel):
messages: List[Message]
stream: Optional[bool] = False
tool_response: Optional[bool] = False
bot_id: str
language: Optional[str] = "ja"
class FileProcessRequest(BaseModel): class FileProcessRequest(BaseModel):
unique_id: str unique_id: str
files: Optional[Dict[str, List[str]]] = Field(default=None, description="Files organized by key groups. Each key maps to a list of file paths (supports zip files)") files: Optional[Dict[str, List[str]]] = Field(default=None, description="Files organized by key groups. Each key maps to a list of file paths (supports zip files)")