Compare commits

37 Commits

Author SHA1 Message Date
carry
541d37c674 feat(schema): 新增数据集相关模型并添加文档扫描功能
- 新增 dataset.py 文件,定义数据集相关模型
- 新增 tools 目录,包含解析 Markdown 和扫描文档的功能
- 修改 parse_markdown.py,增加处理 Markdown 文件的函数
- 新增 scan_doc_dir.py,实现文档目录扫描功能
2025-04-09 13:02:18 +08:00
carry
6a00699472 feat(frontend): 实现提示词模板管理页面
- 添加获取、添加、编辑和删除提示词功能
- 实现数据表格展示和操作
2025-04-09 11:08:18 +08:00
carry
ff8162890d refactor(db): 移除了提示词模板中冗余的 JSON 格式说明 2025-04-09 10:35:11 +08:00
carry
daddcd34da fix(db): 为 promptStore 添加空数据库初始化逻辑
- 在 initialize_prompt_store 函数中增加空数据库检查和初始化逻辑
- 为默认模板添加 id 字段,设置为 0
2025-04-09 10:28:31 +08:00
carry
5c7ced30df fix(db): 修复 prompt_store 初始化逻辑
- 在插入默认模板之前检查数据库是否为空,如果数据库已有数据,则跳过插入默认模板
2025-04-09 10:26:14 +08:00
carry
9741ce6b92 refactor(db): 优化了代码,调整了import顺序,删除了无用变量 2025-04-09 10:19:57 +08:00
carry
67281fe06a feat(db): 添加 prompt 存储功能
- 新增 prompt_store 模块,使用 TinyDB 存储 prompt 模板
- 在全局变量中添加 prompt_store 实例
- 更新 main.py,初始化 prompt 存储
- 新增 prompt 模板的 Pydantic 模型
- 更新 requirements.txt,添加 tinydb 依赖
2025-04-09 09:58:42 +08:00
carry
2d905a0270 refactor(db): 调整导入模块顺序
- 将 os 和 sys 模块导入提前到文件顶部
- 优化代码结构,遵循常见的 Python 导入模块顺序
2025-04-09 09:57:20 +08:00
carry
374b124cf8 feat(setting_page): 添加供应商后清空输入框
- 修改 add_provider 函数,返回清空后的输入框值
- 更新 add_button.click 事件处理,添加清空输入框的输出
2025-04-09 08:17:43 +08:00
carry
74ae5e1426 refactor(db): 重命名数据库引擎获取函数
将 get_engine 函数重命名为 get_sqlite_engine,以更清晰地表示其功能和用途。
- 更新了 db/__init__.py 中的导入和 __all__ 列表
- 修改了 db/init_db.py 中的函数定义
- 更新了前端设置页面和全局变量中的导入和函数调用

此更改提高了代码的可读性和维护性,特别是在将来可能添加其他类型数据库引擎的情况下。
2025-04-09 08:12:59 +08:00
carry
0a6ae7a4ee feat(frontend): 重构前端页面并添加新功能
- 重命名 dataset_page 为 prompt_manage_page,支持提示词模板管理
- 新增 dataset_generate_page 和 dataset_manage_page 页面
- 更新 main.py 中的页面引用和标签名称
- 修改前端初始化文件,使用 * 导入所有页面模块
2025-04-09 08:11:40 +08:00
carry
faf72d1e99 feat(frontend): 完成了编辑 API Provider 功能 2025-04-09 08:04:40 +08:00
carry
cce5e4e114 feat(frontend): 完成了 API Provider 删除和添加了编辑功能的函数 2025-04-09 00:48:22 +08:00
carry
293f63017f feat(frontend): 添加 API Provider 表格选中行状态监听
- 新增选中行的全局变量 selected_row
- 实现 select_record 函数来保存选中行数据
- 在表格中添加选中行事件监听
- 优化代码结构,提高可读性和可维护性
2025-04-09 00:37:15 +08:00
carry
2e31f4f57c build: 升级 gradio 至 5.0.0 版本
- 将 requirements.txt 中 gradio 版本要求从 >=3.0.0 修改为 >=5.0.0
- 此次升级可能会影响项目的用户界面或功能,需要进行测试以确保兼容性
2025-04-08 16:17:21 +08:00
carry
967133162e refactor(schema): 在 APIProvider 模型中设置 id 字段为不可变
- 在 APIProvider 类中,将 id 字段的定义更新,添加 allow_mutation=False 参数
- 这个改动确保了主键字段在创建后不可更改,提高了数据的一致性和安全性
2025-04-08 16:02:46 +08:00
carry
dc28c25c65 feat(frontend): 更新设置页面按钮样式
- 为"添加新API"按钮添加 primary 样式
- 为"编辑选中行"按钮添加 primary 样式
- 为"删除选中行"按钮添加 stop 样式
- 保持"刷新数据"按钮的 secondary 样式
2025-04-08 14:23:31 +08:00
carry
70b64dc3d3 refactor(db): 重命名数据库初始化函数以明确其适用范围
- 将 initialize_db 函数重命名为 initialize_sqlite_db,以明确该函数专用于 SQLite 数据库
- 更新相关模块和文件中的引用,以确保代码一致性
- 此修改旨在提高代码的可读性和维护性,特别是未来可能接入多种数据库时
2025-04-08 14:16:12 +08:00
carry
b52ca9b1af docs: 添加项目基础文档
- 新增 LICENSE 文件,定义项目使用的 MIT 开源许可证
- 新增 README.md 文件,简要介绍项目内容和技术栈
2025-04-08 13:35:30 +08:00
carry
46b4453ccd refactor(frontend): 重构数据库连接方式
- 移除各前端页面中重复的数据库引擎初始化代码
- 在 global_var.py 中统一初始化和存储数据库引擎
- 更新 setting_page.py 和 main.py 中的数据库连接逻辑
- 优化代码结构,提高可维护性和可扩展性
2025-04-08 13:19:58 +08:00
carry
d5b528d375 chore: 更新 .gitignore 文件
- 保留 unsloth_compiled_cache 目录
- 添加 test.ipynb 到忽略列表,避免测试代码影响版本控制
2025-04-08 12:28:42 +08:00
carry
475cd033d9 build: 添加 langchain 依赖
- 在 requirements.txt 中添加 langchain>=0.3 版本的依赖
- 保持其他依赖版本不变
2025-04-08 11:53:58 +08:00
carry
3970a67df3 refactor(dataset_generation): 增加 APIProvider 模型字段的最小长度验证
- 为 base_url 和 model_id 字段添加 min_length=1 的验证
- 更新字段描述,明确这些字段不能为空
2025-04-07 23:37:14 +08:00
carry
286db405ca feat(frontend): 优化设置页面并添加数据刷新功能
- 为 get_providers 函数添加异常处理,提高数据获取的稳定性
- 在设置页面添加刷新按钮,用户可手动触发数据刷新
- 优化页面布局,调整组件间距和对齐方式
2025-04-07 23:17:43 +08:00
carry
d40f5b1f24 fix(frontend): 优化 API Provider 添加功能并处理异常
- 为 model_id、base_url 和 api_key 添加空值检查,避免无效输入
- 添加异常处理,确保在出现错误时能够及时响应并提示用户
- 优化 add_provider 函数,提高代码可读性和健壮性
2025-04-07 13:02:45 +08:00
carry
7a77f61ee6 feat(frontend): 添加 API Provider 的增加功能 2025-04-07 00:28:52 +08:00
carry
841e14a093 feat(frontend): 添加数据集页面并重构主页面布局
- 新增 dataset_page 模块,实现数据集页面的基本布局
- 重构 main.py 中的页面加载方式,使用列表收集所有页面
- 更新主页面布局,将聊天页面作为第一个选项卡
- 调整设置页面的加载方式,直接使用函数调用
2025-04-06 22:49:37 +08:00
carry
2ff077bb1c refactor(frontend): 重构前端页面导入方式
- 在 main.py 中使用更简洁的导入方式
- 新增 __init__.py 文件以简化前端页面的导入
2025-04-06 22:46:31 +08:00
carry
513b639bce feat(frontend): 添加了设置页面的api provider展示 2025-04-06 22:05:56 +08:00
carry
f93f213a31 feat(db): 添加数据库连接和初始化功能
- 新增 db/__init__.py 文件,提供数据库连接和初始化的接口
- 导入 get_engine 和 initialize_db 函数,方便外部使用
2025-04-06 21:27:25 +08:00
carry
10b4c29bda docs(db): 修改了代码注释 2025-04-06 21:26:53 +08:00
carry
b1e98ca913 feat(db): 初始化数据库并创建 APIProvider 表
- 新增 init_db.py 文件,实现数据库初始化和 APIProvider 表的创建
- 新增 dataset_generation.py 文件,定义 LLMRequest、LLMResponse 和 APIProvider 模型
- 在初始化数据库时,如果环境变量中存在 API_KEY、BASE_URL 和 MODEL_ID,会自动添加一条 APIProvider 记录
2025-04-06 19:59:23 +08:00
carry
2d5a5277ae refactor(schema): 更新 prompt 导入
- 将 prompt_templeta 重命名为 promptTempleta,以符合驼峰命名规范
- 优化导入语句格式
2025-04-06 19:39:43 +08:00
carry
519a5f3773 feat(frontend): 添加前端页面模块并实现基本布局
- 新增 chat_page.py、setting_page.py 和 train_page.py 文件,分别实现聊天、设置和微调页面的基本布局
- 添加 main.py 文件,集成所有页面并创建主应用
- 在 requirements.txt 中添加 gradio 依赖
2025-04-06 14:49:01 +08:00
carry
1f4d491694 build: 添加 pydantic 依赖 2025-04-05 01:00:33 +08:00
carry
8ce4f1e373 chore: 添加 .roo 到 .gitignore 文件
- 在 .gitignore 文件中添加 .roo 目录,以忽略相关文件
2025-04-05 00:59:42 +08:00
carry
3395b860e4 refactor(parse_markdown): 重构 Markdown 解析逻辑并使用 Pydantic 模型
将 MarkdownNode 类重构为使用 Pydantic 模型,提高代码的可维护性和类型安全性。同时,将解析逻辑与节点操作分离,简化代码结构。
2025-04-04 20:50:39 +08:00
24 changed files with 702 additions and 31 deletions

4
.gitignore vendored
View File

@@ -11,6 +11,7 @@ env/
# IDE # IDE
.vscode/ .vscode/
.idea/ .idea/
.roo
# Environment files # Environment files
.env .env
@@ -28,3 +29,6 @@ workdir/
# cache # cache
unsloth_compiled_cache unsloth_compiled_cache
# 测试代码
test.ipynb

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 C-a-r-r-y
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
# 基于文档驱动的自适应编码大模型微调框架
## 简介
本人的毕业设计
### 项目概述
* 通过深度解析私有库的文档以及其他资源,生成指令型语料,据此对大语言模型进行针对私有库的微调。
### 项目技术
* 使用unsloth框架在GPU上实现大语言模型的qlora微调
* 使用langchain框架编写工作流实现批量生成微调语料
* 使用tinydb和sqlite实现数据的持久化
* 使用gradio框架实现前端展示
**施工中......**

9
db/__init__.py Normal file
View File

@@ -0,0 +1,9 @@
from .init_db import get_sqlite_engine, initialize_sqlite_db
from .prompt_store import get_prompt_tinydb, initialize_prompt_store
__all__ = [
"get_sqlite_engine",
"initialize_sqlite_db",
"get_prompt_tinydb",
"initialize_prompt_store"
]

79
db/init_db.py Normal file
View File

@@ -0,0 +1,79 @@
import os
import sys
from sqlmodel import SQLModel, create_engine, Session
from sqlmodel import select
from typing import Optional
from pathlib import Path
from dotenv import load_dotenv
from sqlalchemy.engine import Engine
# 将项目根目录添加到系统路径中,以便能够导入项目中的其他模块
sys.path.append(str(Path(__file__).resolve().parent.parent))
from schema.dataset_generation import APIProvider
# 全局变量,用于存储数据库引擎实例
_engine: Optional[Engine] = None
def get_sqlite_engine(workdir: str) -> Engine:
"""
获取数据库引擎实例。如果引擎尚未创建,则创建一个新的引擎并返回。
Args:
workdir (str): 工作目录路径,用于确定数据库文件的存储位置。
Returns:
Engine: SQLAlchemy 数据库引擎实例。
"""
global _engine
if not _engine:
# 创建数据库目录(如果不存在)
db_dir = os.path.join(workdir, "db")
os.makedirs(db_dir, exist_ok=True)
# 定义数据库文件路径
db_path = os.path.join(db_dir, "db.sqlite")
# 创建数据库URL
db_url = f"sqlite:///{db_path}"
# 创建数据库引擎
_engine = create_engine(db_url)
return _engine
def initialize_sqlite_db(engine: Engine) -> None:
"""
初始化数据库,创建所有表结构,并插入初始数据(如果不存在)。
Args:
engine (Engine): SQLAlchemy 数据库引擎实例。
"""
# 创建所有表结构
SQLModel.metadata.create_all(engine)
# 加载环境变量
load_dotenv()
# 从环境变量中获取API相关配置
api_key = os.getenv("API_KEY")
base_url = os.getenv("BASE_URL")
model_id = os.getenv("MODEL_ID")
# 如果所有必要的环境变量都存在,则插入初始数据
if api_key and base_url and model_id:
with Session(engine) as session:
# 查询是否已存在APIProvider记录
statement = select(APIProvider).limit(1)
existing_provider = session.exec(statement).first()
# 如果不存在则插入新的APIProvider记录
if not existing_provider:
provider = APIProvider(
base_url=base_url,
model_id=model_id,
api_key=api_key
)
session.add(provider)
session.commit()
if __name__ == "__main__":
# 定义工作目录路径
workdir = os.path.join(os.path.dirname(__file__), "..", "workdir")
# 获取数据库引擎
engine = get_sqlite_engine(workdir)
# 初始化数据库
initialize_sqlite_db(engine)

62
db/prompt_store.py Normal file
View File

@@ -0,0 +1,62 @@
import os
import sys
from typing import Optional
from pathlib import Path
from datetime import datetime, timezone
from tinydb import TinyDB, Query
from tinydb.storages import JSONStorage
# 将项目根目录添加到系统路径中,以便能够导入项目中的其他模块
sys.path.append(str(Path(__file__).resolve().parent.parent))
from schema.prompt import promptTempleta
# 全局变量用于存储TinyDB实例
_db_instance: Optional[TinyDB] = None
# 自定义存储类用于格式化JSON数据
def get_prompt_tinydb(workdir: str) -> TinyDB:
"""
获取TinyDB实例。如果实例尚未创建则创建一个新的并返回。
Args:
workdir (str): 工作目录路径,用于确定数据库文件的存储位置。
Returns:
TinyDB: TinyDB数据库实例
"""
global _db_instance
if not _db_instance:
# 创建数据库目录(如果不存在)
db_dir = os.path.join(workdir, "db")
os.makedirs(db_dir, exist_ok=True)
# 定义数据库文件路径
db_path = os.path.join(db_dir, "prompts.json")
# 创建TinyDB实例
_db_instance = TinyDB(db_path)
return _db_instance
def initialize_prompt_store(db: TinyDB) -> None:
"""
初始化prompt模板存储
Args:
db (TinyDB): TinyDB数据库实例
"""
# 检查数据库是否为空
if not db.all(): # 如果数据库中没有数据
db.insert(promptTempleta(
id=0,
name="default",
description="默认提示词模板",
content="""项目名为:{ project_name }
请依据以下该项目官方文档的部分内容,创造合适的对话数据集用于微调一个了解该项目的小模型的语料,要求兼顾文档中间尽可能多的信息点,使用中文
文档节选:{ content }""").model_dump())
# 如果数据库中已有数据,则跳过插入
if __name__ == "__main__":
# 定义工作目录路径
workdir = os.path.join(os.path.dirname(__file__), "..", "workdir")
# 获取数据库实例
db = get_prompt_tinydb(workdir)
# 初始化prompt存储
initialize_prompt_store(db)

6
frontend/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
from .chat_page import *
from .setting_page import *
from .train_page import *
from .dataset_manage_page import *
from .dataset_generate_page import *
from .prompt_manage_page import *

9
frontend/chat_page.py Normal file
View File

@@ -0,0 +1,9 @@
import gradio as gr
def chat_page():
with gr.Blocks() as demo:
gr.Markdown("## 聊天")
with gr.Row():
with gr.Column():
pass
return demo

View File

@@ -0,0 +1,9 @@
import gradio as gr
def dataset_generate_page():
with gr.Blocks() as demo:
gr.Markdown("## 数据集生成")
with gr.Row():
with gr.Column():
pass
return demo

View File

@@ -0,0 +1,9 @@
import gradio as gr
def dataset_manage_page():
with gr.Blocks() as demo:
gr.Markdown("## 数据集管理")
with gr.Row():
with gr.Column():
pass
return demo

View File

@@ -0,0 +1,116 @@
import gradio as gr
from typing import List
from global_var import prompt_store
from schema.prompt import promptTempleta
def prompt_manage_page():
def get_prompts() -> List[List[str]]:
selected_row = None
try:
db = prompt_store
prompts = db.all()
return [
[p["id"], p["name"], p["description"], p["content"]]
for p in prompts
]
except Exception as e:
raise gr.Error(f"获取提示词失败: {str(e)}")
def add_prompt(name, description, content):
try:
db = prompt_store
new_prompt = promptTempleta(
name=name if name else "",
description=description if description else "",
content=content if content else ""
)
prompt_id = db.insert(new_prompt.model_dump())
# 更新ID
db.update({"id": prompt_id}, doc_ids=[prompt_id])
return get_prompts(), "", "", "" # 返回清空后的输入框值
except Exception as e:
raise gr.Error(f"添加失败: {str(e)}")
def edit_prompt():
global selected_row
if not selected_row:
raise gr.Error("请先选择要编辑的行")
try:
db = prompt_store
db.update({
"name": selected_row[1] if selected_row[1] else "",
"description": selected_row[2] if selected_row[2] else "",
"content": selected_row[3] if selected_row[3] else ""
}, doc_ids=[selected_row[0]])
return get_prompts()
except Exception as e:
raise gr.Error(f"编辑失败: {str(e)}")
def delete_prompt():
global selected_row
if not selected_row:
raise gr.Error("请先选择要删除的行")
try:
db = prompt_store
db.remove(doc_ids=[selected_row[0]])
return get_prompts()
except Exception as e:
raise gr.Error(f"删除失败: {str(e)}")
selected_row = None # 保存当前选中行的全局变量
def select_record(evt: gr.SelectData):
global selected_row
selected_row = evt.row_value
with gr.Blocks() as demo:
gr.Markdown("## 提示词模板管理")
with gr.Row():
with gr.Column(scale=1):
name_input = gr.Textbox(label="模板名称")
description_input = gr.Textbox(label="模板描述")
content_input = gr.Textbox(label="模板内容", lines=10)
add_button = gr.Button("添加新模板", variant="primary")
with gr.Column(scale=3):
prompt_table = gr.DataFrame(
headers=["id", "名称", "描述", "内容"],
datatype=["number", "str", "str", "str"],
interactive=True,
value=get_prompts(),
wrap=True,
col_count=(4, "auto")
)
with gr.Row():
refresh_button = gr.Button("刷新数据", variant="secondary")
edit_button = gr.Button("编辑选中行", variant="primary")
delete_button = gr.Button("删除选中行", variant="stop")
refresh_button.click(
fn=get_prompts,
outputs=[prompt_table],
queue=False
)
add_button.click(
fn=add_prompt,
inputs=[name_input, description_input, content_input],
outputs=[prompt_table, name_input, description_input, content_input]
)
prompt_table.select(select_record, [], [], show_progress="hidden")
edit_button.click(
fn=edit_prompt,
inputs=[],
outputs=[prompt_table]
)
delete_button.click(
fn=delete_prompt,
inputs=[],
outputs=[prompt_table]
)
return demo

126
frontend/setting_page.py Normal file
View File

@@ -0,0 +1,126 @@
import gradio as gr
from typing import List
from sqlmodel import Session, select
from schema import APIProvider
from global_var import sql_engine
def setting_page():
def get_providers() -> List[List[str]]:
selected_row = None
try: # 添加异常处理
with Session(sql_engine) as session:
providers = session.exec(select(APIProvider)).all()
return [
[p.id, p.model_id, p.base_url, p.api_key or ""]
for p in providers
]
except Exception as e:
raise gr.Error(f"获取数据失败: {str(e)}")
def add_provider(model_id, base_url, api_key):
try:
with Session(sql_engine) as session:
new_provider = APIProvider(
model_id=model_id if model_id else None,
base_url=base_url if base_url else None,
api_key=api_key if api_key else None
)
session.add(new_provider)
session.commit()
session.refresh(new_provider)
return get_providers(), "", "", "" # 返回清空后的输入框值
except Exception as e:
raise gr.Error(f"添加失败: {str(e)}")
def edit_provider():
global selected_row
if not selected_row:
raise gr.Error("请先选择要编辑的行")
try:
with Session(sql_engine) as session:
provider = session.get(APIProvider, selected_row[0])
if not provider:
raise gr.Error("找不到选中的记录")
provider.model_id = selected_row[1] if selected_row[1] else None
provider.base_url = selected_row[2] if selected_row[2] else None
provider.api_key = selected_row[3] if selected_row[3] else None
session.add(provider)
session.commit()
session.refresh(provider)
return get_providers()
except Exception as e:
raise gr.Error(f"编辑失败: {str(e)}")
def delete_provider():
global selected_row
if not selected_row:
raise gr.Error("请先选择要删除的行")
try:
with Session(sql_engine) as session:
provider = session.get(APIProvider, selected_row[0])
if not provider:
raise gr.Error("找不到选中的记录")
session.delete(provider)
session.commit()
return get_providers()
except Exception as e:
raise gr.Error(f"删除失败: {str(e)}")
selected_row = None # 保存当前选中行的全局变量
def select_record(evt: gr.SelectData):
global selected_row
selected_row = evt.row_value
with gr.Blocks() as demo:
gr.Markdown("## API Provider 管理")
with gr.Row():
with gr.Column(scale=1):
model_id_input = gr.Textbox(label="Model ID")
base_url_input = gr.Textbox(label="Base URL")
api_key_input = gr.Textbox(label="API Key")
add_button = gr.Button("添加新API", variant="primary")
with gr.Column(scale=3):
provider_table = gr.DataFrame(
headers=["id", "model id", "base URL", "API Key"],
datatype=["number", "str", "str", "str"],
interactive=True,
value=get_providers(),
wrap=True,
col_count=(4, "auto")
)
with gr.Row():
refresh_button = gr.Button("刷新数据", variant="secondary")
edit_button = gr.Button("编辑选中行", variant="primary")
delete_button = gr.Button("删除选中行", variant="stop")
refresh_button.click(
fn=get_providers,
outputs=[provider_table],
queue=False # 立即刷新不需要排队
)
add_button.click(
fn=add_provider,
inputs=[model_id_input, base_url_input, api_key_input],
outputs=[provider_table, model_id_input, base_url_input, api_key_input] # 添加清空输入框的输出
)
provider_table.select(select_record, [], [], show_progress="hidden")
edit_button.click(
fn=edit_provider,
inputs=[],
outputs=[provider_table]
)
delete_button.click(
fn=delete_provider,
inputs=[],
outputs=[provider_table]
)
return demo

9
frontend/train_page.py Normal file
View File

@@ -0,0 +1,9 @@
import gradio as gr
def train_page():
with gr.Blocks() as demo:
gr.Markdown("## 微调")
with gr.Row():
with gr.Column():
pass
return demo

4
global_var.py Normal file
View File

@@ -0,0 +1,4 @@
from db import get_sqlite_engine,get_prompt_tinydb
prompt_store = get_prompt_tinydb("workdir")
sql_engine = get_sqlite_engine("workdir")

26
main.py Normal file
View File

@@ -0,0 +1,26 @@
import gradio as gr
from frontend.setting_page import setting_page
from frontend import *
from db import initialize_sqlite_db,initialize_prompt_store
from global_var import sql_engine,prompt_store
if __name__ == "__main__":
initialize_sqlite_db(sql_engine)
initialize_prompt_store(prompt_store)
with gr.Blocks() as app:
gr.Markdown("# 基于文档驱动的自适应编码大模型微调框架")
with gr.Tabs():
with gr.TabItem("模型推理"):
chat_page()
with gr.TabItem("模型微调"):
train_page()
with gr.TabItem("数据集生成"):
dataset_generate_page()
with gr.TabItem("数据集管理"):
dataset_manage_page()
with gr.TabItem("提示词模板管理"):
prompt_manage_page()
with gr.TabItem("设置"):
setting_page()
app.launch()

View File

@@ -1,2 +1,6 @@
openai>=1.0.0 openai>=1.0.0
python-dotenv>=1.0.0 python-dotenv>=1.0.0
pydantic>=2.0.0
gradio>=5.0.0
langchain>=0.3
tinydb>=4.0.0

4
schema/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
from .dataset import *
from .dataset_generation import APIProvider, LLMResponse, LLMRequest
from .md_doc import MarkdownNode
from .prompt import promptTempleta

28
schema/dataset.py Normal file
View File

@@ -0,0 +1,28 @@
from typing import Optional
from pydantic import BaseModel, Field
from datetime import datetime, timezone
class doc(BaseModel):
id: Optional[int] = Field(default=None, description="文档ID")
name: str = Field(default="", description="文档名称")
path: str = Field(default="", description="文档路径")
markdown_files: list[str] = Field(default_factory=list, description="文档路径列表")
class Q_A(BaseModel):
question: str = Field(default="", min_length=1,description="问题")
answer: str = Field(default="", min_length=1, description="答案")
class dataset_item(BaseModel):
id: Optional[int] = Field(default=None, description="数据集项ID")
message: list[Q_A] = Field(description="数据集项内容")
class dataset(BaseModel):
id: Optional[int] = Field(default=None, description="数据集ID")
name: Optional[str] = Field(default=None, description="数据集名称")
model_id: Optional[list[str]] = Field(default=None, description="数据集使用的模型ID")
description: Optional[str] = Field(default="", description="数据集描述")
created_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
description="记录创建时间"
)
dataset_items: list[dataset_item] = Field(default_factory=list, description="数据集项列表")

View File

@@ -0,0 +1,51 @@
from datetime import datetime, timezone
from typing import Optional
from sqlmodel import SQLModel, Relationship, Field
class APIProvider(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True,allow_mutation=False)
base_url: str = Field(...,min_length=1,description="API的基础URL不能为空")
model_id: str = Field(...,min_length=1,description="API使用的模型ID不能为空")
api_key: Optional[str] = Field(default=None, description="用于身份验证的API密钥")
created_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
description="记录创建时间"
)
class LLMResponse(SQLModel):
timestamp: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
description="响应的时间戳"
)
response_id: str = Field(..., description="响应的唯一ID")
tokens_usage: dict = Field(default_factory=lambda: {
"prompt_tokens": 0,
"completion_tokens": 0,
"prompt_cache_hit_tokens": None,
"prompt_cache_miss_tokens": None
}, description="token使用信息")
response_content: dict = Field(default_factory=dict, description="API响应的内容")
total_duration: float = Field(default=0.0, description="请求的总时长,单位为秒")
llm_parameters: dict = Field(default_factory=lambda: {
"temperature": None,
"max_tokens": None,
"top_p": None,
"frequency_penalty": None,
"presence_penalty": None,
"seed": None
}, description="API的生成参数")
class LLMRequest(SQLModel):
prompt: str = Field(..., description="发送给API的提示词")
provider_id: int = Field(foreign_key="apiprovider.id")
provider: APIProvider = Relationship()
format: Optional[str] = Field(default=None, description="API响应的格式")
response: list[LLMResponse] = Field(default_factory=list, description="API响应列表")
error: Optional[list[str]] = Field(default=None, description="API请求过程中发生的错误信息")
total_duration: float = Field(default=0.0, description="请求的总时长,单位为秒")
total_tokens_usage: dict = Field(default_factory=lambda: {
"prompt_tokens": 0,
"completion_tokens": 0,
"prompt_cache_hit_tokens": None,
"prompt_cache_miss_tokens": None
}, description="token使用信息")

13
schema/md_doc.py Normal file
View File

@@ -0,0 +1,13 @@
from pydantic import BaseModel, Field
from typing import List, Optional
class MarkdownNode(BaseModel):
level: int = Field(default=0, description="节点层级")
title: str = Field(default="Root", description="节点标题")
content: Optional[str] = Field(default=None, description="节点内容")
children: List['MarkdownNode'] = Field(default_factory=list, description="子节点列表")
class Config:
arbitrary_types_allowed = True
MarkdownNode.model_rebuild()

13
schema/prompt.py Normal file
View File

@@ -0,0 +1,13 @@
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime, timezone
class promptTempleta(BaseModel):
id: Optional[int] = Field(default=None, description="模板ID")
name: Optional[str] = Field(default="", description="模板名称")
description: Optional[str] = Field(default="", description="模板描述")
content: str = Field(default="", min_length=1, description="模板内容")
created_at: str = Field(
default_factory=lambda: datetime.now(timezone.utc).isoformat(),
description="记录创建时间"
)

2
tools/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from .parse_markdown import parse_markdown
from .scan_doc_dir import *

View File

@@ -1,28 +1,45 @@
import re import re
import sys
from pathlib import Path
class MarkdownNode: # 添加项目根目录到sys.path
def __init__(self, level=0, title="Root"): sys.path.append(str(Path(__file__).resolve().parent.parent))
self.level = level from schema import MarkdownNode
self.title = title
self.content = "" # 使用字符串存储合并后的内容
self.children = []
def __repr__(self): def process_markdown_file(file_path):
return f"({self.level}) {self.title}" with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
def add_child(self, child): root = parse_markdown(content)
self.children.append(child) results = []
def print_tree(self, indent=0): def traverse(node, parent_titles):
prefix = "" * (indent - 1) + "└─ " if indent > 0 else "" current_titles = parent_titles.copy()
print(f"{prefix}{self.title}") current_titles.append(node.title)
if self.content:
content_prefix = "" * indent + "├─ [内容]" if not node.children: # 叶子节点
print(content_prefix) if node.content:
for line in self.content.split('\n'): full_text = ' -> '.join(current_titles) + '\n' + node.content
print("" * indent + "" + line) results.append(full_text)
for child in self.children: else:
child.print_tree(indent + 1) for child in node.children:
traverse(child, current_titles)
traverse(root, [])
return results
def add_child(parent, child):
parent.children.append(child)
def print_tree(node, indent=0):
prefix = "" * (indent - 1) + "└─ " if indent > 0 else ""
print(f"{prefix}{node.title}")
if node.content:
content_prefix = "" * indent + "├─ [内容]"
print(content_prefix)
for line in node.content.split('\n'):
print("" * indent + "" + line)
for child in node.children:
print_tree(child, indent + 1)
def parse_markdown(markdown): def parse_markdown(markdown):
lines = markdown.split('\n') lines = markdown.split('\n')
@@ -51,10 +68,10 @@ def parse_markdown(markdown):
if match: if match:
level = len(match.group(1)) level = len(match.group(1))
title = match.group(2) title = match.group(2)
node = MarkdownNode(level, title) node = MarkdownNode(level=level, title=title, content="", children=[])
while stack[-1].level >= level: while stack[-1].level >= level:
stack.pop() stack.pop()
stack[-1].add_child(node) add_child(stack[-1], node)
stack.append(node) stack.append(node)
else: else:
if stack[-1].content: if stack[-1].content:
@@ -64,10 +81,13 @@ def parse_markdown(markdown):
return root return root
if __name__=="__main__": if __name__=="__main__":
# 从文件读取 Markdown 内容 # # 从文件读取 Markdown 内容
with open("example.md", "r", encoding="utf-8") as f: # with open("workdir/example.md", "r", encoding="utf-8") as f:
markdown = f.read() # markdown = f.read()
# 解析 Markdown 并打印树结构 # # 解析 Markdown 并打印树结构
root = parse_markdown(markdown) # root = parse_markdown(markdown)
root.print_tree() # print_tree(root)
for i in process_markdown_file("workdir/example.md"):
print("~"*20)
print(i)

32
tools/scan_doc_dir.py Normal file
View File

@@ -0,0 +1,32 @@
import sys
import os
from pathlib import Path
# 添加项目根目录到sys.path
sys.path.append(str(Path(__file__).resolve().parent.parent))
from schema import doc
def scan_docs_directory(workdir: str):
docs_dir = os.path.join(workdir, "docs")
doc_list = os.listdir(docs_dir)
to_return = []
for doc_name in doc_list:
doc_path = os.path.join(docs_dir, doc_name)
if os.path.isdir(doc_path):
markdown_files = []
for root, dirs, files in os.walk(doc_path):
for file in files:
if file.endswith(".md"):
markdown_files.append(os.path.join(root, file))
to_return.append(doc(name=doc_name, path=doc_path, markdown_files=markdown_files))
return to_return
# 添加测试代码
if __name__ == "__main__":
workdir = os.path.join(os.path.dirname(__file__), "..", "workdir")
docs = scan_docs_directory(workdir)
print(docs)