初始化仅前端版本
This commit is contained in:
@@ -4,15 +4,21 @@ FROM python:3.11-slim
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制依赖文件并安装
|
||||
# 复制依赖文件
|
||||
# 注意:这里的 requirements.txt 在 backend/ 目录下
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
|
||||
|
||||
# 复制所有代码
|
||||
COPY app/ ./app/
|
||||
# 关键修改:把 backend/ 目录下的内容复制到 /app/backend/ 下
|
||||
# 这样镜像内的结构就是 /app/backend/app/...
|
||||
COPY . ./backend/
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8000
|
||||
|
||||
# 启动命令
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
# 关键修改:路径改为 backend.app.api.route
|
||||
CMD ["uvicorn", "backend.app.api.route:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
18
backend/api/route.py
Normal file
18
backend/api/route.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from fastapi import FastAPI
|
||||
# 假设这些函数已经在其他地方定义
|
||||
from backend.core.items import ChatRequest
|
||||
from backend.tools.get_all_role_and_chat import get_all_role_and_chat as get_chat_file
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# 1. 将输入内容持久化存储到本地jsonl方便前端读
|
||||
@app.post("/generate_reply")
|
||||
async def save_input_to_json(chat_request: ChatRequest):
|
||||
return 0
|
||||
|
||||
# 2. 从本地jsonl中读取历史对话
|
||||
@app.get("/get_all_role_and_chat")
|
||||
def get_all_role_and_chat():
|
||||
# 直接调用导入的函数
|
||||
result = get_chat_file()
|
||||
return result
|
||||
@@ -1,46 +0,0 @@
|
||||
import base64
|
||||
from typing import Any, Dict
|
||||
from IPython.core.magic_arguments import defaults
|
||||
from .. import nodes
|
||||
|
||||
class StartNode():
|
||||
name = "开始节点"
|
||||
inputs = {
|
||||
"user_input": "string", # 用户输入文本
|
||||
"stream": "boolean", # 是否流式输出
|
||||
"img_switch": "boolean", # 是否处理图片
|
||||
"table_switch": "boolean", # 是否处理表格
|
||||
"role_name": "string", # 角色名称
|
||||
"chat_name": "string" # 会话名称
|
||||
}
|
||||
|
||||
async def run(self, text: str = None, image: bytes = None, **kwargs) -> Dict[str, Any]:
|
||||
# 查空:文本不能为空字符串
|
||||
if not text or text.strip() == "":
|
||||
raise ValueError("文本输入不能为空")
|
||||
|
||||
# 查空:图片数据不能为空
|
||||
if image is None or len(image) == 0:
|
||||
raise ValueError("图片输入不能为空")
|
||||
|
||||
# 将图片字节转换为 Base64 字符串,便于在节点间传递
|
||||
image_base64 = base64.b64encode(image).decode('utf-8')
|
||||
|
||||
return {
|
||||
"text": text,
|
||||
"image": image_base64
|
||||
}
|
||||
|
||||
async def run(is_user,floor_number,mes: str = None, stream: bool = False, img_switch: bool = False,name = "default",
|
||||
table_switch: bool = False, role_name: str = None, chat_name: str = None,preset: str = None):
|
||||
# 将输入内容持久化存储到本地json方便前端读
|
||||
nodes.save_input_to_json(mes=mes, role_name=role_name, chat_name=chat_name, name=name, is_user=is_user, floor_number=floor_number)
|
||||
# 对上一条输入内容(已确定不变的内容)调用向量化,根据role和chat嵌入到对应本地数据库
|
||||
embed_input(user_input, role_name, chat_name)
|
||||
# 根据role和chat去读取绑定的世界书
|
||||
# 读取预设,进行拼接
|
||||
# 调用模型,返回结果
|
||||
# 将结果持久化存储到本地json方便前端读(用JSONL)
|
||||
# 如果img_switch是开的,那么异步调用生图,并存储到目标文件夹里
|
||||
# 如果table_switch是开的,那么异步调用表格生成,并存储到目标文件夹里
|
||||
# 将结果返回给前端
|
||||
0
backend/core/__init__.py
Normal file
0
backend/core/__init__.py
Normal file
47
backend/core/config.py
Normal file
47
backend/core/config.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 1. 动态计算项目根目录
|
||||
# 假设 config.py 位于 backend/ 目录下
|
||||
# __file__ 指向本文件的绝对路径
|
||||
# .parent 指向 backend/ 目录
|
||||
# .parent.parent 指向项目根目录 (即包含 backend/ 和 frontend/ 的目录)
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent # 修改这里,添加一个 .parent
|
||||
|
||||
# 2. 加载 .env 文件
|
||||
# 假设 .env 文件位于项目根目录下
|
||||
load_dotenv(PROJECT_ROOT / ".env")
|
||||
|
||||
|
||||
class Settings:
|
||||
# --- 主模型配置 ---
|
||||
MAIN_LLM_API_KEY = os.getenv("MAIN_LLM_API_KEY")
|
||||
MAIN_LLM_MODEL = os.getenv("MAIN_LLM_MODEL", "gpt-3.5-turbo")
|
||||
MAIN_LLM_BASE_URL = os.getenv("MAIN_LLM_BASE_URL", "https://api.openai.com/v1")
|
||||
MAIN_LLM_MAX_TOKENS = int(os.getenv("MAIN_LLM_MAX_TOKENS", "4096"))
|
||||
MAIN_LLM_STREAM = os.getenv("MAIN_LLM_STREAM", "true").lower() == "true"
|
||||
|
||||
# --- 路径配置 (核心修改) ---
|
||||
|
||||
# 强制使用计算出的项目根目录,不再依赖 .env 中的 BASE_PATH
|
||||
BASE_PATH = PROJECT_ROOT
|
||||
|
||||
# 数据目录:固定为根目录下的 data 文件夹
|
||||
# 即使 .env 里写了 DATA_PATH=/data,这里也会强制指向项目根目录下的 data
|
||||
DATA_PATH = BASE_PATH / "data"
|
||||
|
||||
# 其他文件路径:基于 DATA_PATH 拼接
|
||||
STATE_FILE = DATA_PATH / "state.json"
|
||||
SCHEMA_FILE = DATA_PATH / "schema.json"
|
||||
PRESETS_FILE = DATA_PATH / "presets.json"
|
||||
REGEX_FILE = DATA_PATH / "regex_rules.json"
|
||||
VECTORSTORE_PATH = DATA_PATH / "vectorstore"
|
||||
|
||||
settings = Settings()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 实例化配置对象
|
||||
settings = Settings()
|
||||
print(settings.BASE_PATH)
|
||||
|
||||
24
backend/core/items.py
Normal file
24
backend/core/items.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
# 1. 定义请求体模型
|
||||
class ChatRequest(BaseModel):
|
||||
# --- 基础信息 ---
|
||||
mes: str = Field(..., description="用户输入的消息内容")
|
||||
is_user: bool = Field(..., description="标识发送者是否为用户(True为用户,False为AI)")
|
||||
floor_number: int = Field(..., description="当前对话的楼层号,用于判断是否为重试(Regenerate)请求")
|
||||
|
||||
# --- 身份与会话 ---
|
||||
name: str = Field("default", description="发送者的显示名称,默认为'default'")
|
||||
role_name: Optional[str] = Field(None, description="当前绑定的角色名称")
|
||||
chat_name: Optional[str] = Field(None, description="当前会话的标识名称")
|
||||
preset: Optional[str] = Field(None, description="预设的提示词或系统指令")
|
||||
|
||||
# --- 功能开关 ---
|
||||
stream: bool = Field(False, description="是否开启流式输出")
|
||||
img_switch: bool = Field(False, description="是否开启图片生成功能")
|
||||
table_switch: bool = Field(False, description="是否开启表格生成功能")
|
||||
|
||||
# 其他可能需要的参数,比如历史记录,可以在这里加
|
||||
# history: Optional[List[Dict]] = None
|
||||
12
backend/main.py
Normal file
12
backend/main.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# backend/app/main.py
|
||||
from fastapi import FastAPI
|
||||
from .api.routes import router
|
||||
|
||||
app = FastAPI(title="LLM Workflow Engine")
|
||||
|
||||
# 注册路由
|
||||
app.include_router(router, prefix="/api")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
0
backend/nodes/__init__.py
Normal file
0
backend/nodes/__init__.py
Normal file
0
backend/tools/__init__.py
Normal file
0
backend/tools/__init__.py
Normal file
46
backend/tools/get_all_role_and_chat.py
Normal file
46
backend/tools/get_all_role_and_chat.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from ..core import config
|
||||
from typing import Dict, List
|
||||
|
||||
# 使用配置中的 DATA_PATH 并添加 "chat" 子目录
|
||||
ROOT_DIR = config.settings.DATA_PATH / "chat"
|
||||
|
||||
def get_all_role_and_chat() -> Dict[str, List[str]]:
|
||||
"""
|
||||
读取配置目录下的所有子文件夹,并收集每个子文件夹中的 JSONL 文件
|
||||
|
||||
返回:
|
||||
dict: 字典结构,键是文件夹名称,值是该文件夹中的 JSONL 文件列表
|
||||
"""
|
||||
result = {}
|
||||
|
||||
# 确保目标目录存在
|
||||
if not ROOT_DIR.exists():
|
||||
print(f"警告: 目录 {ROOT_DIR} 不存在")
|
||||
return result
|
||||
|
||||
# 打印根目录路径和内容(调试用)
|
||||
print(f"正在扫描目录: {ROOT_DIR}")
|
||||
print(f"根目录内容: {list(ROOT_DIR.iterdir())}")
|
||||
|
||||
# 遍历根目录下的所有条目
|
||||
for entry in ROOT_DIR.iterdir():
|
||||
try:
|
||||
# 只处理文件夹
|
||||
if entry.is_dir():
|
||||
print(f"处理文件夹: {entry.name}") # 调试信息
|
||||
jsonl_files = []
|
||||
|
||||
# 遍历子文件夹中的所有文件
|
||||
for file in entry.iterdir():
|
||||
if file.is_file() and file.suffix == '.jsonl':
|
||||
jsonl_files.append(str(file))
|
||||
print(f" 找到文件: {file.name}") # 调试信息
|
||||
|
||||
# 如果该文件夹中有 JSONL 文件,则添加到结果中
|
||||
if jsonl_files:
|
||||
result[entry.name] = jsonl_files
|
||||
except Exception as e:
|
||||
print(f"处理文件夹 {entry.name} 时出错: {str(e)}")
|
||||
continue
|
||||
|
||||
return result
|
||||
@@ -1,33 +1,37 @@
|
||||
import json
|
||||
from typing import Dict, Any
|
||||
from datetime import datetime
|
||||
import config as cfg
|
||||
from backend.core import config as cfg
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def save_input_to_json(
|
||||
mes: str,
|
||||
role_name: str,
|
||||
chat_name: str,
|
||||
name: str,
|
||||
is_user: bool,
|
||||
floor_number: int = 0
|
||||
) -> Dict[str, Any]:
|
||||
# 假设 ChatRequest 定义在这里或者从其他地方导入
|
||||
# from backend.app.core.items import ChatRequest
|
||||
|
||||
async def save_input_to_json(chat_request: ChatRequest):
|
||||
"""
|
||||
保存消息到JSONL文件或处理重roll请求
|
||||
|
||||
参数:
|
||||
mes: 消息内容
|
||||
role_name: 角色名称
|
||||
chat_name: 对话名称
|
||||
name: 发送者名称
|
||||
is_user: 是否为用户消息
|
||||
floor_number: 楼层号(对话中的第几次回复),用于判断是否为重roll请求
|
||||
|
||||
返回:
|
||||
更新后的消息对象
|
||||
chat_request: 包含消息详情的请求对象
|
||||
"""
|
||||
# 1. 从对象中提取属性
|
||||
mes = chat_request.mes
|
||||
role_name = chat_request.role_name
|
||||
chat_name = chat_request.chat_name
|
||||
name = chat_request.name
|
||||
is_user = chat_request.is_user
|
||||
floor_number = chat_request.floor_number
|
||||
# stream, img_switch, table_switch 等虽然在这个函数逻辑中没用到,
|
||||
# 但如果 ChatRequest 中有,也可以提取出来备用
|
||||
# stream = chat_request.stream
|
||||
# ...
|
||||
|
||||
config = cfg.settings
|
||||
# 注意:这里要确保 role_name 和 chat_name 不为 None,否则路径拼接会报错
|
||||
# 建议在函数入口处增加校验,或者在 Pydantic 模型中设置为必填项
|
||||
if not role_name or not chat_name:
|
||||
raise ValueError("role_name and chat_name cannot be empty")
|
||||
|
||||
file_path = config.BASE_PATH / "data" / "chat" / role_name / f"{chat_name}.jsonl"
|
||||
|
||||
# 确保目录存在
|
||||
@@ -115,31 +119,34 @@ def save_input_to_json(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试普通消息保存
|
||||
# save_input_to_json(
|
||||
# mes="你好",
|
||||
# role_name="test",
|
||||
# chat_name="111",
|
||||
# name="用户",
|
||||
# is_user=True,
|
||||
# floor_number=0
|
||||
# )
|
||||
#
|
||||
# save_input_to_json(
|
||||
# mes="你好,我是AI助手",
|
||||
# role_name="test",
|
||||
# chat_name="111",
|
||||
# name="AI",
|
||||
# is_user=False,
|
||||
# floor_number=1
|
||||
# )
|
||||
# 注意:为了在本地运行测试,你需要手动构造一个 ChatRequest 对象
|
||||
# 或者临时修改函数签名以便直接传参测试
|
||||
|
||||
# 示例:假设 ChatRequest 是一个简单的类或 Pydantic 模型
|
||||
class MockChatRequest:
|
||||
def __init__(self, **kwargs):
|
||||
self.mes = kwargs.get('mes')
|
||||
self.role_name = kwargs.get('role_name')
|
||||
self.chat_name = kwargs.get('chat_name')
|
||||
self.name = kwargs.get('name')
|
||||
self.is_user = kwargs.get('is_user')
|
||||
self.floor_number = kwargs.get('floor_number')
|
||||
|
||||
|
||||
# 测试重roll最后一条AI消息
|
||||
save_input_to_json(
|
||||
mes="这是重roll后的新回复2",
|
||||
role_name="test",
|
||||
chat_name="111",
|
||||
name="AI",
|
||||
is_user=False,
|
||||
floor_number=2 # 与当前楼层号相同,表示重roll
|
||||
)
|
||||
import asyncio
|
||||
|
||||
|
||||
async def test():
|
||||
req = MockChatRequest(
|
||||
mes="这是重roll后的新回复2",
|
||||
role_name="test",
|
||||
chat_name="111",
|
||||
name="AI",
|
||||
is_user=False,
|
||||
floor_number=2
|
||||
)
|
||||
await save_input_to_json(req)
|
||||
|
||||
|
||||
asyncio.run(test())
|
||||
0
backend/workflows/__init__.py
Normal file
0
backend/workflows/__init__.py
Normal file
201
backend/workflows/main_llm_workflow.py
Normal file
201
backend/workflows/main_llm_workflow.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# backend/app/workflows/llm_workflow.py
|
||||
from typing import Dict, Any, List, Callable
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class WorkflowStatus(Enum):
|
||||
"""工作流状态枚举"""
|
||||
INITIALIZED = "initialized"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
PAUSED = "paused"
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowContext:
|
||||
"""工作流上下文"""
|
||||
data: Dict[str, Any]
|
||||
status: WorkflowStatus = WorkflowStatus.INITIALIZED
|
||||
metadata: Dict[str, Any] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.metadata is None:
|
||||
self.metadata = {}
|
||||
|
||||
|
||||
class WorkflowNode:
|
||||
"""工作流节点声明"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
handler: Callable,
|
||||
enabled: bool = True,
|
||||
config: Dict[str, Any] = None
|
||||
):
|
||||
self.name = name # 节点的唯一标识符,用于区分不同的节点。
|
||||
self.handler = handler # 一个可调用对象(函数或方法),这是节点实际执行的处理逻辑。
|
||||
self.enabled = enabled # 布尔值,控制节点是否启用。默认为 True,如果设置为 False,节点将被跳过。
|
||||
self.config = config or {} # 一个字典,用于存储节点的配置信息。默认为空字典。
|
||||
self.next_nodes: List['WorkflowNode'] = [] # 一个节点列表,用于指定当前节点执行完成后应跳转到的下一个节点。默认为空列表,可能指向多分支。
|
||||
|
||||
def execute(self, context: WorkflowContext) -> WorkflowContext:
|
||||
"""执行节点处理"""
|
||||
if not self.enabled:
|
||||
return context
|
||||
|
||||
try:
|
||||
context = self.handler(context, self.config)
|
||||
return context
|
||||
except Exception as e:
|
||||
context.status = WorkflowStatus.FAILED
|
||||
context.metadata["error"] = str(e)
|
||||
raise
|
||||
|
||||
|
||||
class LLMWorkflow:
|
||||
"""LLM工作流声明"""
|
||||
|
||||
def __init__(self):
|
||||
self.nodes: List[WorkflowNode] = []
|
||||
self._initialize_workflow()
|
||||
|
||||
def _initialize_workflow(self):
|
||||
"""初始化工作流节点(仅声明,不实现)"""
|
||||
# 输入节点
|
||||
input_node = WorkflowNode(
|
||||
name="input",
|
||||
handler=self._input_handler
|
||||
)
|
||||
|
||||
# 输入预处理节点(可开关)
|
||||
preprocessing_node = WorkflowNode(
|
||||
name="preprocessing",
|
||||
handler=self._preprocessing_handler,
|
||||
enabled=False
|
||||
)
|
||||
|
||||
# RAG处理节点
|
||||
rag_node = WorkflowNode(
|
||||
name="rag",
|
||||
handler=self._rag_handler
|
||||
)
|
||||
|
||||
# 提示词组装节点
|
||||
prompt_assembly_node = WorkflowNode(
|
||||
name="prompt_assembly",
|
||||
handler=self._prompt_assembly_handler
|
||||
)
|
||||
|
||||
# LLM请求节点
|
||||
llm_request_node = WorkflowNode(
|
||||
name="llm_request",
|
||||
handler=self._llm_request_handler
|
||||
)
|
||||
|
||||
# 图像生成节点(可开关)
|
||||
image_generation_node = WorkflowNode(
|
||||
name="image_generation",
|
||||
handler=self._image_generation_handler,
|
||||
enabled=False
|
||||
)
|
||||
|
||||
# 动态表格更新节点(可开关)
|
||||
dynamic_table_node = WorkflowNode(
|
||||
name="dynamic_table",
|
||||
handler=self._dynamic_table_handler,
|
||||
enabled=False
|
||||
)
|
||||
|
||||
# 输出过滤节点
|
||||
output_filter_node = WorkflowNode(
|
||||
name="output_filter",
|
||||
handler=self._output_filter_handler
|
||||
)
|
||||
|
||||
# 输出节点
|
||||
output_node = WorkflowNode(
|
||||
name="output",
|
||||
handler=self._output_handler
|
||||
)
|
||||
|
||||
# 设置节点顺序(构建工作流)
|
||||
self.nodes = [
|
||||
input_node,
|
||||
preprocessing_node,
|
||||
rag_node,
|
||||
prompt_assembly_node,
|
||||
llm_request_node,
|
||||
image_generation_node,
|
||||
dynamic_table_node,
|
||||
output_filter_node,
|
||||
output_node
|
||||
]
|
||||
|
||||
def execute(self, context: WorkflowContext) -> WorkflowContext:
|
||||
"""执行工作流"""
|
||||
context.status = WorkflowStatus.RUNNING
|
||||
|
||||
for node in self.nodes:
|
||||
try:
|
||||
context = node.execute(context)
|
||||
|
||||
# 如果工作流失败,停止执行
|
||||
if context.status == WorkflowStatus.FAILED:
|
||||
break
|
||||
except Exception as e:
|
||||
context.status = WorkflowStatus.FAILED
|
||||
context.metadata["error"] = str(e)
|
||||
break
|
||||
|
||||
if context.status != WorkflowStatus.FAILED:
|
||||
context.status = WorkflowStatus.COMPLETED
|
||||
|
||||
return context
|
||||
|
||||
def enable_node(self, node_name: str, enabled: bool = True):
|
||||
"""启用或禁用特定节点"""
|
||||
for node in self.nodes:
|
||||
if node.name == node_name:
|
||||
node.enabled = enabled
|
||||
return True
|
||||
return False
|
||||
|
||||
# 以下是节点处理函数声明(仅声明,不实现)
|
||||
def _input_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""输入节点处理函数"""
|
||||
pass
|
||||
|
||||
def _preprocessing_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""输入预处理节点处理函数"""
|
||||
pass
|
||||
|
||||
def _rag_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""RAG处理节点处理函数"""
|
||||
pass
|
||||
|
||||
def _prompt_assembly_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""提示词组装节点处理函数"""
|
||||
pass
|
||||
|
||||
def _llm_request_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""LLM请求节点处理函数"""
|
||||
pass
|
||||
|
||||
def _image_generation_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""图像生成节点处理函数"""
|
||||
pass
|
||||
|
||||
def _dynamic_table_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""动态表格更新节点处理函数"""
|
||||
pass
|
||||
|
||||
def _output_filter_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""输出过滤节点处理函数"""
|
||||
pass
|
||||
|
||||
def _output_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext:
|
||||
"""输出节点处理函数"""
|
||||
pass
|
||||
Reference in New Issue
Block a user