first commit

This commit is contained in:
2026-04-07 16:05:05 +08:00
commit 9d9bdbb1ce
136 changed files with 5103 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
from flask import Flask
from flask_cors import CORS
from flask_migrate import Migrate
from flask_jwt_extended import JWTManager
import logging
import os
from .config import config_by_name
from .models import db
def create_app(config_name=None):
app = Flask(__name__)
if config_name is None:
config_name = os.environ.get("FLASK_ENV", "development")
app.config.from_object(config_by_name[config_name])
CORS(app, origins=app.config["CORS_ORIGINS"])
db.init_app(app)
migrate = Migrate(app, db)
jwt = JWTManager(app)
setup_logging(app)
register_blueprints(app)
setup_error_handlers(app)
return app
def setup_logging(app):
log_dir = os.path.dirname(app.config["LOG_FILE"])
if not os.path.exists(log_dir):
os.makedirs(log_dir)
logging.basicConfig(
level=getattr(logging, app.config["LOG_LEVEL"]),
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler(app.config["LOG_FILE"]), logging.StreamHandler()],
)
def register_blueprints(app):
from .api import api_bp
app.register_blueprint(api_bp, url_prefix="/api")
def setup_error_handlers(app):
@app.errorhandler(404)
def not_found(error):
return {"error": "Not found"}, 404
@app.errorhandler(500)
def internal_error(error):
app.logger.error(f"Internal error: {error}")
return {"error": "Internal server error"}, 500
+6
View File
@@ -0,0 +1,6 @@
from .engine import AgentEngine
from .react import ReActAgent
from .plan_execute import PlanAndExecuteAgent
__all__ = ["AgentEngine", "ReActAgent", "PlanAndExecuteAgent"]
+6
View File
@@ -0,0 +1,6 @@
class AgentEngine:
def __init__(self, config):
self.config = config
def run(self, prompt, tools=None):
raise NotImplementedError
+6
View File
@@ -0,0 +1,6 @@
from .engine import AgentEngine
class PlanAndExecuteAgent(AgentEngine):
def run(self, prompt, tools=None):
pass
+6
View File
@@ -0,0 +1,6 @@
from .engine import AgentEngine
class ReActAgent(AgentEngine):
def run(self, prompt, tools=None):
pass
+18
View File
@@ -0,0 +1,18 @@
from flask import Blueprint
api_bp = Blueprint("api", __name__)
from . import (
auth,
users,
workspaces,
agents,
conversations,
messages,
tools,
skills,
memories,
models,
cron_jobs,
channels,
)
+107
View File
@@ -0,0 +1,107 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from . import api_bp
from ..models import Agent, Workspace
from ..schemas import AgentSchema, AgentCreateSchema, AgentUpdateSchema
from ..services import AgentService
agent_schema = AgentSchema()
agent_create_schema = AgentCreateSchema()
agent_update_schema = AgentUpdateSchema()
@api_bp.route("/workspaces/<int:workspace_id>/agents", methods=["GET"])
@jwt_required()
def get_agents(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
agents = Agent.query.filter_by(workspace_id=workspace_id).all()
return jsonify(agent_schema.dump(agents, many=True))
@api_bp.route("/workspaces/<int:workspace_id>/agents", methods=["POST"])
@jwt_required()
def create_agent(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
data = request.get_json()
errors = agent_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
agent = AgentService.create_agent(workspace_id, data)
return jsonify(agent_schema.dump(agent)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/agents/<int:agent_id>", methods=["GET"])
@jwt_required()
def get_agent(agent_id):
user_id = get_jwt_identity()
agent = Agent.query.filter_by(id=agent_id).first()
if not agent:
return jsonify({"error": "Agent 不存在"}), 404
workspace = Workspace.query.filter_by(
id=agent.workspace_id, user_id=user_id
).first()
if not workspace:
return jsonify({"error": "无权访问此 Agent"}), 403
return jsonify(agent_schema.dump(agent))
@api_bp.route("/agents/<int:agent_id>", methods=["PUT"])
@jwt_required()
def update_agent(agent_id):
user_id = get_jwt_identity()
agent = Agent.query.filter_by(id=agent_id).first()
if not agent:
return jsonify({"error": "Agent 不存在"}), 404
workspace = Workspace.query.filter_by(
id=agent.workspace_id, user_id=user_id
).first()
if not workspace:
return jsonify({"error": "无权访问此 Agent"}), 403
data = request.get_json()
errors = agent_update_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
updated_agent = AgentService.update_agent(agent, data)
return jsonify(agent_schema.dump(updated_agent))
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/agents/<int:agent_id>", methods=["DELETE"])
@jwt_required()
def delete_agent(agent_id):
user_id = get_jwt_identity()
agent = Agent.query.filter_by(id=agent_id).first()
if not agent:
return jsonify({"error": "Agent 不存在"}), 404
workspace = Workspace.query.filter_by(
id=agent.workspace_id, user_id=user_id
).first()
if not workspace:
return jsonify({"error": "无权访问此 Agent"}), 403
try:
AgentService.delete_agent(agent)
return jsonify({"message": "Agent 已删除"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
+55
View File
@@ -0,0 +1,55 @@
from flask import request, jsonify
from flask_jwt_extended import create_access_token
from . import api_bp
from ..models import User
from ..schemas import UserCreateSchema
from ..services import AuthService
user_create_schema = UserCreateSchema()
@api_bp.route("/auth/register", methods=["POST"])
def register():
data = request.get_json()
errors = user_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
user = AuthService.register(
data["username"], data["password"], data.get("email")
)
access_token = create_access_token(identity=user.id)
return jsonify(
{
"message": "注册成功",
"user": user.to_dict(),
"access_token": access_token,
}
), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/auth/login", methods=["POST"])
def login():
data = request.get_json()
username = data.get("username")
password = data.get("password")
if not username or not password:
return jsonify({"error": "用户名和密码不能为空"}), 400
try:
user = AuthService.login(username, password)
access_token = create_access_token(identity=user.id)
return jsonify(
{
"message": "登录成功",
"user": user.to_dict(),
"access_token": access_token,
}
), 200
except ValueError as e:
return jsonify({"error": str(e)}), 401
+71
View File
@@ -0,0 +1,71 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required
from . import api_bp
from ..models import Channel
from ..schemas import ChannelSchema, ChannelCreateSchema, ChannelUpdateSchema
from ..services import ChannelService
channel_schema = ChannelSchema()
channel_create_schema = ChannelCreateSchema()
channel_update_schema = ChannelUpdateSchema()
@api_bp.route("/channels", methods=["GET"])
@jwt_required()
def get_channels():
channels = Channel.query.all()
return jsonify(channel_schema.dump(channels, many=True))
@api_bp.route("/channels", methods=["POST"])
@jwt_required()
def create_channel():
data = request.get_json()
errors = channel_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
channel = ChannelService.create_channel(data)
return jsonify(channel_schema.dump(channel)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/channels/<int:channel_id>", methods=["GET"])
@jwt_required()
def get_channel(channel_id):
channel = Channel.query.get(channel_id)
if not channel:
return jsonify({"error": "渠道不存在"}), 404
return jsonify(channel_schema.dump(channel))
@api_bp.route("/channels/<int:channel_id>", methods=["PUT"])
@jwt_required()
def update_channel(channel_id):
channel = Channel.query.get(channel_id)
if not channel:
return jsonify({"error": "渠道不存在"}), 404
data = request.get_json()
errors = channel_update_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
updated_channel = ChannelService.update_channel(channel, data)
return jsonify(channel_schema.dump(updated_channel))
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/channels/<int:channel_id>", methods=["DELETE"])
@jwt_required()
def delete_channel(channel_id):
try:
ChannelService.delete_channel(channel_id)
return jsonify({"message": "渠道已删除"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
+92
View File
@@ -0,0 +1,92 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from . import api_bp
from ..models import Conversation, Workspace
from ..schemas import (
ConversationSchema,
ConversationCreateSchema,
ConversationUpdateSchema,
)
from ..services import ConversationService
conversation_schema = ConversationSchema()
conversation_create_schema = ConversationCreateSchema()
conversation_update_schema = ConversationUpdateSchema()
@api_bp.route("/workspaces/<int:workspace_id>/conversations", methods=["GET"])
@jwt_required()
def get_conversations(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
conversations = Conversation.query.filter_by(workspace_id=workspace_id).all()
return jsonify(conversation_schema.dump(conversations, many=True))
@api_bp.route("/workspaces/<int:workspace_id>/conversations", methods=["POST"])
@jwt_required()
def create_conversation(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
data = request.get_json()
errors = conversation_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
conversation = ConversationService.create_conversation(workspace_id, data)
return jsonify(conversation_schema.dump(conversation)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/conversations/<int:conversation_id>", methods=["GET"])
@jwt_required()
def get_conversation(conversation_id):
user_id = get_jwt_identity()
conversation = Conversation.query.filter_by(id=conversation_id).first()
if not conversation:
return jsonify({"error": "会话不存在"}), 404
workspace = Workspace.query.filter_by(
id=conversation.workspace_id, user_id=user_id
).first()
if not workspace:
return jsonify({"error": "无权访问此会话"}), 403
return jsonify(conversation_schema.dump(conversation))
@api_bp.route("/conversations/<int:conversation_id>", methods=["PUT"])
@jwt_required()
def update_conversation(conversation_id):
user_id = get_jwt_identity()
conversation = Conversation.query.filter_by(id=conversation_id).first()
if not conversation:
return jsonify({"error": "会话不存在"}), 404
workspace = Workspace.query.filter_by(
id=conversation.workspace_id, user_id=user_id
).first()
if not workspace:
return jsonify({"error": "无权访问此会话"}), 403
data = request.get_json()
errors = conversation_update_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
updated_conversation = ConversationService.update_conversation(
conversation, data
)
return jsonify(conversation_schema.dump(updated_conversation))
except ValueError as e:
return jsonify({"error": str(e)}), 400
+71
View File
@@ -0,0 +1,71 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required
from . import api_bp
from ..models import CronJob
from ..schemas import CronJobSchema, CronJobCreateSchema, CronJobUpdateSchema
from ..services import CronJobService
cron_job_schema = CronJobSchema()
cron_job_create_schema = CronJobCreateSchema()
cron_job_update_schema = CronJobUpdateSchema()
@api_bp.route("/agents/<int:agent_id>/cron-jobs", methods=["GET"])
@jwt_required()
def get_cron_jobs(agent_id):
cron_jobs = CronJob.query.filter_by(agent_id=agent_id).all()
return jsonify(cron_job_schema.dump(cron_jobs, many=True))
@api_bp.route("/agents/<int:agent_id>/cron-jobs", methods=["POST"])
@jwt_required()
def create_cron_job(agent_id):
data = request.get_json()
errors = cron_job_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
cron_job = CronJobService.create_cron_job(agent_id, data)
return jsonify(cron_job_schema.dump(cron_job)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/cron-jobs/<int:cron_job_id>", methods=["GET"])
@jwt_required()
def get_cron_job(cron_job_id):
cron_job = CronJob.query.get(cron_job_id)
if not cron_job:
return jsonify({"error": "定时任务不存在"}), 404
return jsonify(cron_job_schema.dump(cron_job))
@api_bp.route("/cron-jobs/<int:cron_job_id>", methods=["PUT"])
@jwt_required()
def update_cron_job(cron_job_id):
cron_job = CronJob.query.get(cron_job_id)
if not cron_job:
return jsonify({"error": "定时任务不存在"}), 404
data = request.get_json()
errors = cron_job_update_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
updated_cron_job = CronJobService.update_cron_job(cron_job, data)
return jsonify(cron_job_schema.dump(updated_cron_job))
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/cron-jobs/<int:cron_job_id>", methods=["DELETE"])
@jwt_required()
def delete_cron_job(cron_job_id):
try:
CronJobService.delete_cron_job(cron_job_id)
return jsonify({"message": "定时任务已删除"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
+81
View File
@@ -0,0 +1,81 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from . import api_bp
from ..models import Memory, Workspace
from ..schemas import MemorySchema, MemoryCreateSchema, MemoryUpdateSchema
from ..services import MemoryService
memory_schema = MemorySchema()
memory_create_schema = MemoryCreateSchema()
memory_update_schema = MemoryUpdateSchema()
@api_bp.route("/workspaces/<int:workspace_id>/memories", methods=["GET"])
@jwt_required()
def get_memories(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
memories = Memory.query.filter_by(workspace_id=workspace_id).all()
return jsonify(memory_schema.dump(memories, many=True))
@api_bp.route("/workspaces/<int:workspace_id>/memories", methods=["POST"])
@jwt_required()
def create_memory(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
data = request.get_json()
errors = memory_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
memory = MemoryService.create_memory(workspace_id, data)
return jsonify(memory_schema.dump(memory)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/memories/<int:memory_id>", methods=["GET"])
@jwt_required()
def get_memory(memory_id):
memory = Memory.query.get(memory_id)
if not memory:
return jsonify({"error": "记忆不存在"}), 404
return jsonify(memory_schema.dump(memory))
@api_bp.route("/memories/<int:memory_id>", methods=["PUT"])
@jwt_required()
def update_memory(memory_id):
memory = Memory.query.get(memory_id)
if not memory:
return jsonify({"error": "记忆不存在"}), 404
data = request.get_json()
errors = memory_update_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
updated_memory = MemoryService.update_memory(memory, data)
return jsonify(memory_schema.dump(updated_memory))
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/memories/<int:memory_id>", methods=["DELETE"])
@jwt_required()
def delete_memory(memory_id):
try:
MemoryService.delete_memory(memory_id)
return jsonify({"message": "记忆已删除"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
+58
View File
@@ -0,0 +1,58 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from . import api_bp
from ..models import Message, Conversation, Workspace
from ..schemas import MessageSchema, MessageCreateSchema
from ..services import MessageService
message_schema = MessageSchema()
message_create_schema = MessageCreateSchema()
@api_bp.route("/conversations/<int:conversation_id>/messages", methods=["GET"])
@jwt_required()
def get_messages(conversation_id):
user_id = get_jwt_identity()
conversation = Conversation.query.filter_by(id=conversation_id).first()
if not conversation:
return jsonify({"error": "会话不存在"}), 404
workspace = Workspace.query.filter_by(
id=conversation.workspace_id, user_id=user_id
).first()
if not workspace:
return jsonify({"error": "无权访问此会话"}), 403
messages = (
Message.query.filter_by(conversation_id=conversation_id)
.order_by(Message.created_at)
.all()
)
return jsonify(message_schema.dump(messages, many=True))
@api_bp.route("/conversations/<int:conversation_id>/messages", methods=["POST"])
@jwt_required()
def create_message(conversation_id):
user_id = get_jwt_identity()
conversation = Conversation.query.filter_by(id=conversation_id).first()
if not conversation:
return jsonify({"error": "会话不存在"}), 404
workspace = Workspace.query.filter_by(
id=conversation.workspace_id, user_id=user_id
).first()
if not workspace:
return jsonify({"error": "无权访问此会话"}), 403
data = request.get_json()
errors = message_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
message = MessageService.create_message(conversation_id, data)
return jsonify(message_schema.dump(message)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
+75
View File
@@ -0,0 +1,75 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required
from . import api_bp
from ..models import Model
from ..schemas import ModelSchema, ModelCreateSchema
from ..services import ModelService
model_schema = ModelSchema()
model_create_schema = ModelCreateSchema()
@api_bp.route("/models", methods=["GET"])
@jwt_required()
def get_models():
models = Model.query.all()
return jsonify(model_schema.dump(models, many=True))
@api_bp.route("/models", methods=["POST"])
@jwt_required()
def create_model():
data = request.get_json()
errors = model_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
model = ModelService.create_model(data)
return jsonify(model_schema.dump(model)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/models/<int:model_id>", methods=["GET"])
@jwt_required()
def get_model(model_id):
model = Model.query.get(model_id)
if not model:
return jsonify({"error": "模型不存在"}), 404
return jsonify(model_schema.dump(model))
@api_bp.route("/models/<int:model_id>", methods=["PUT"])
@jwt_required()
def update_model(model_id):
model = Model.query.get(model_id)
if not model:
return jsonify({"error": "模型不存在"}), 404
data = request.get_json()
try:
updated_model = ModelService.update_model(model, data)
return jsonify(model_schema.dump(updated_model))
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/models/<int:model_id>", methods=["DELETE"])
@jwt_required()
def delete_model(model_id):
try:
ModelService.delete_model(model_id)
return jsonify({"message": "模型已删除"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/models/default", methods=["GET"])
@jwt_required()
def get_default_model():
model = Model.query.filter_by(is_default=True, is_active=True).first()
if not model:
return jsonify({"error": "默认模型不存在"}), 404
return jsonify(model_schema.dump(model))
+45
View File
@@ -0,0 +1,45 @@
from flask import jsonify
from flask_jwt_extended import jwt_required
from . import api_bp
from ..models import Skill
from ..schemas import SkillSchema
from ..services import SkillService
skill_schema = SkillSchema()
@api_bp.route("/skills", methods=["GET"])
@jwt_required()
def get_skills():
skills = Skill.query.all()
return jsonify(skill_schema.dump(skills, many=True))
@api_bp.route("/skills/<int:skill_id>", methods=["GET"])
@jwt_required()
def get_skill(skill_id):
skill = Skill.query.get(skill_id)
if not skill:
return jsonify({"error": "技能不存在"}), 404
return jsonify(skill_schema.dump(skill))
@api_bp.route("/skills/<int:skill_id>/install", methods=["POST"])
@jwt_required()
def install_skill(skill_id):
try:
SkillService.install_skill(skill_id)
return jsonify({"message": "技能已安装"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/skills/<int:skill_id>/uninstall", methods=["POST"])
@jwt_required()
def uninstall_skill(skill_id):
try:
SkillService.uninstall_skill(skill_id)
return jsonify({"message": "技能已卸载"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
+80
View File
@@ -0,0 +1,80 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required
from . import api_bp
from ..models import Tool, AgentTool
from ..schemas import (
ToolSchema,
ToolCreateSchema,
AgentToolSchema,
AgentToolCreateSchema,
)
from ..services import ToolService
tool_schema = ToolSchema()
tool_create_schema = ToolCreateSchema()
agent_tool_schema = AgentToolSchema()
agent_tool_create_schema = AgentToolCreateSchema()
@api_bp.route("/tools", methods=["GET"])
@jwt_required()
def get_tools():
tools = Tool.query.all()
return jsonify(tool_schema.dump(tools, many=True))
@api_bp.route("/tools", methods=["POST"])
@jwt_required()
def create_tool():
data = request.get_json()
errors = tool_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
tool = ToolService.create_tool(data)
return jsonify(tool_schema.dump(tool)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/tools/<int:tool_id>", methods=["GET"])
@jwt_required()
def get_tool(tool_id):
tool = Tool.query.get(tool_id)
if not tool:
return jsonify({"error": "工具不存在"}), 404
return jsonify(tool_schema.dump(tool))
@api_bp.route("/agents/<int:agent_id>/tools", methods=["GET"])
@jwt_required()
def get_agent_tools(agent_id):
agent_tools = AgentTool.query.filter_by(agent_id=agent_id).all()
return jsonify(agent_tool_schema.dump(agent_tools, many=True))
@api_bp.route("/agents/<int:agent_id>/tools", methods=["POST"])
@jwt_required()
def add_agent_tool(agent_id):
data = request.get_json()
errors = agent_tool_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
agent_tool = ToolService.add_tool_to_agent(agent_id, data["tool_id"])
return jsonify(agent_tool_schema.dump(agent_tool)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/agents/<int:agent_id>/tools/<int:tool_id>", methods=["DELETE"])
@jwt_required()
def remove_agent_tool(agent_id, tool_id):
try:
ToolService.remove_tool_from_agent(agent_id, tool_id)
return jsonify({"message": "工具已移除"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
+40
View File
@@ -0,0 +1,40 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from . import api_bp
from ..models import User
from ..schemas import UserSchema, UserUpdateSchema
from ..services import UserService
user_schema = UserSchema()
user_update_schema = UserUpdateSchema()
@api_bp.route("/users/me", methods=["GET"])
@jwt_required()
def get_current_user():
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({"error": "用户不存在"}), 404
return jsonify(user_schema.dump(user))
@api_bp.route("/users/me", methods=["PUT"])
@jwt_required()
def update_current_user():
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({"error": "用户不存在"}), 404
data = request.get_json()
errors = user_update_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
updated_user = UserService.update_user(user, data)
return jsonify(user_schema.dump(updated_user))
except ValueError as e:
return jsonify({"error": str(e)}), 400
+82
View File
@@ -0,0 +1,82 @@
from flask import jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from . import api_bp
from ..models import Workspace
from ..schemas import WorkspaceSchema, WorkspaceCreateSchema, WorkspaceUpdateSchema
from ..services import WorkspaceService
workspace_schema = WorkspaceSchema()
workspace_create_schema = WorkspaceCreateSchema()
workspace_update_schema = WorkspaceUpdateSchema()
@api_bp.route("/workspaces", methods=["GET"])
@jwt_required()
def get_workspaces():
user_id = get_jwt_identity()
workspaces = Workspace.query.filter_by(user_id=user_id).all()
return jsonify(workspace_schema.dump(workspaces, many=True))
@api_bp.route("/workspaces", methods=["POST"])
@jwt_required()
def create_workspace():
user_id = get_jwt_identity()
data = request.get_json()
errors = workspace_create_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
workspace = WorkspaceService.create_workspace(
user_id, data["name"], data.get("description")
)
return jsonify(workspace_schema.dump(workspace)), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/workspaces/<int:workspace_id>", methods=["GET"])
@jwt_required()
def get_workspace(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
return jsonify(workspace_schema.dump(workspace))
@api_bp.route("/workspaces/<int:workspace_id>", methods=["PUT"])
@jwt_required()
def update_workspace(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
data = request.get_json()
errors = workspace_update_schema.validate(data)
if errors:
return jsonify({"error": errors}), 400
try:
updated_workspace = WorkspaceService.update_workspace(workspace, data)
return jsonify(workspace_schema.dump(updated_workspace))
except ValueError as e:
return jsonify({"error": str(e)}), 400
@api_bp.route("/workspaces/<int:workspace_id>", methods=["DELETE"])
@jwt_required()
def delete_workspace(workspace_id):
user_id = get_jwt_identity()
workspace = Workspace.query.filter_by(id=workspace_id, user_id=user_id).first()
if not workspace:
return jsonify({"error": "工作空间不存在"}), 404
try:
WorkspaceService.delete_workspace(workspace)
return jsonify({"message": "工作空间已删除"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
+14
View File
@@ -0,0 +1,14 @@
from .web import WebChannel
from .dingtalk import DingTalkChannel
from .feishu import FeishuChannel
from .telegram import TelegramChannel
from .discord import DiscordChannel
__all__ = [
"WebChannel",
"DingTalkChannel",
"FeishuChannel",
"TelegramChannel",
"DiscordChannel",
]
+9
View File
@@ -0,0 +1,9 @@
class DingTalkChannel:
def __init__(self, config):
self.config = config
def send_message(self, user_id, message):
pass
def receive_message(self, data):
pass
+9
View File
@@ -0,0 +1,9 @@
class DiscordChannel:
def __init__(self, config):
self.config = config
def send_message(self, user_id, message):
pass
def receive_message(self, data):
pass
+9
View File
@@ -0,0 +1,9 @@
class FeishuChannel:
def __init__(self, config):
self.config = config
def send_message(self, user_id, message):
pass
def receive_message(self, data):
pass
+9
View File
@@ -0,0 +1,9 @@
class TelegramChannel:
def __init__(self, config):
self.config = config
def send_message(self, user_id, message):
pass
def receive_message(self, data):
pass
+9
View File
@@ -0,0 +1,9 @@
class WebChannel:
def __init__(self, config):
self.config = config
def send_message(self, user_id, message):
pass
def receive_message(self, data):
pass
+69
View File
@@ -0,0 +1,69 @@
import os
from datetime import timedelta
from dotenv import load_dotenv
load_dotenv()
class Config:
SECRET_KEY = os.environ.get("SECRET_KEY") or "dev-secret-key-change-in-production"
Flask_ENV = os.environ.get("FLASK_ENV") or "development"
Flask_DEBUG = os.environ.get("FLASK_DEBUG", "True").lower() == "true"
Flask_HOST = os.environ.get("FLASK_HOST", "0.0.0.0")
Flask_PORT = int(os.environ.get("FLASK_PORT", 5000))
DATABASE_URL = (
os.environ.get("DATABASE_URL")
or "mysql+pymysql://sentclaw:sentclaw123@localhost:3306/sentclaw"
)
REDIS_URL = os.environ.get("REDIS_URL") or "redis://:redis123@localhost:6379/0"
JWT_SECRET_KEY = (
os.environ.get("JWT_SECRET_KEY") or "jwt-secret-key-change-in-production"
)
JWT_ACCESS_TOKEN_EXPIRES = timedelta(
seconds=int(os.environ.get("JWT_ACCESS_TOKEN_EXPIRES", 3600))
)
CORS_ORIGINS = os.environ.get(
"CORS_ORIGINS", "http://localhost:5173,http://localhost:3000"
).split(",")
UPLOAD_FOLDER = os.environ.get("UPLOAD_FOLDER", "uploads")
MAX_CONTENT_LENGTH = int(os.environ.get("MAX_CONTENT_LENGTH", 16777216))
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
LOG_FILE = os.environ.get("LOG_FILE", "logs/app.log")
LLM_TIMEOUT = 30
LLM_MAX_RETRIES = 3
MCP_TIMEOUT = 10
AGENT_MAX_ITERATIONS = 10
AGENT_THINKING_TIMEOUT = 60
MEMORY_WINDOW_SIZE = 10
MEMORY_COMPRESSION_THRESHOLD = 0.8
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
class TestingConfig(Config):
TESTING = True
DATABASE_URL = "sqlite:///:memory:"
config_by_name = {
"development": DevelopmentConfig,
"production": ProductionConfig,
"testing": TestingConfig,
}
+14
View File
@@ -0,0 +1,14 @@
from .factory import LLMFactory
from .base import BaseLLMClient
from .dashscope import DashScopeClient
from .openai import OpenAIClient
from .anthropic import AnthropicClient
__all__ = [
"LLMFactory",
"BaseLLMClient",
"DashScopeClient",
"OpenAIClient",
"AnthropicClient",
]
+9
View File
@@ -0,0 +1,9 @@
from .base import BaseLLMClient
class AnthropicClient(BaseLLMClient):
def chat(self, messages, **kwargs):
pass
def stream_chat(self, messages, **kwargs):
pass
+14
View File
@@ -0,0 +1,14 @@
from abc import ABC, abstractmethod
class BaseLLMClient(ABC):
def __init__(self, config):
self.config = config
@abstractmethod
def chat(self, messages, **kwargs):
pass
@abstractmethod
def stream_chat(self, messages, **kwargs):
pass
+9
View File
@@ -0,0 +1,9 @@
from .base import BaseLLMClient
class DashScopeClient(BaseLLMClient):
def chat(self, messages, **kwargs):
pass
def stream_chat(self, messages, **kwargs):
pass
+22
View File
@@ -0,0 +1,22 @@
from .dashscope import DashScopeClient
from .openai import OpenAIClient
from .anthropic import AnthropicClient
class LLMFactory:
_clients = {
"dashscope": DashScopeClient,
"openai": OpenAIClient,
"anthropic": AnthropicClient,
}
@classmethod
def create_client(cls, provider, config):
client_class = cls._clients.get(provider)
if not client_class:
raise ValueError(f"不支持的 LLM 提供商: {provider}")
return client_class(config)
@classmethod
def register_client(cls, provider, client_class):
cls._clients[provider] = client_class
+9
View File
@@ -0,0 +1,9 @@
from .base import BaseLLMClient
class OpenAIClient(BaseLLMClient):
def chat(self, messages, **kwargs):
pass
def stream_chat(self, messages, **kwargs):
pass
+6
View File
@@ -0,0 +1,6 @@
from .manager import MemoryManager
from .extractor import MemoryExtractor
from .integrator import MemoryIntegrator
__all__ = ["MemoryManager", "MemoryExtractor", "MemoryIntegrator"]
+6
View File
@@ -0,0 +1,6 @@
class MemoryExtractor:
def __init__(self, llm_client):
self.llm_client = llm_client
def extract_from_conversation(self, conversation):
pass
+3
View File
@@ -0,0 +1,3 @@
class MemoryIntegrator:
def integrate_memories(self, memories):
pass
+13
View File
@@ -0,0 +1,13 @@
class MemoryManager:
def __init__(self, workspace_id):
self.workspace_id = workspace_id
self.memories = []
def add_memory(self, content, memory_type="short", tags=None):
pass
def retrieve_memories(self, query=None, memory_type=None):
pass
def compress_memories(self):
pass
+15
View File
@@ -0,0 +1,15 @@
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
from .user import User
from .workspace import Workspace
from .agent import Agent
from .conversation import Conversation
from .message import Message
from .tool import Tool, AgentTool
from .skill import Skill
from .memory import Memory
from .model import Model
from .cron_job import CronJob
from .channel import Channel
+37
View File
@@ -0,0 +1,37 @@
from .base import BaseModel
from . import db
class Agent(BaseModel):
__tablename__ = "agents"
workspace_id = db.Column(
db.BigInteger,
db.ForeignKey("workspaces.id"),
nullable=False,
comment="工作空间ID",
)
name = db.Column(db.String(100), nullable=False, comment="Agent 名称")
description = db.Column(db.Text, nullable=True, comment="描述")
system_prompt = db.Column(db.Text, nullable=True, comment="系统提示词")
model_id = db.Column(db.String(50), nullable=True, comment="模型ID")
temperature = db.Column(
db.Numeric(3, 2), default=0.70, nullable=False, comment="温度参数"
)
max_tokens = db.Column(
db.Integer, default=2000, nullable=False, comment="最大Token数"
)
is_active = db.Column(db.Boolean, default=True, nullable=False, comment="是否激活")
conversations = db.relationship(
"Conversation", backref="agent", lazy=True, cascade="all, delete-orphan"
)
tools = db.relationship(
"AgentTool", backref="agent", lazy=True, cascade="all, delete-orphan"
)
cron_jobs = db.relationship(
"CronJob", backref="agent", lazy=True, cascade="all, delete-orphan"
)
memories = db.relationship(
"Memory", backref="agent", lazy=True, cascade="all, delete-orphan"
)
+30
View File
@@ -0,0 +1,30 @@
from datetime import datetime
from . import db
class BaseModel(db.Model):
__abstract__ = True
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True, comment="ID")
created_at = db.Column(
db.DateTime, default=datetime.utcnow, nullable=False, comment="创建时间"
)
updated_at = db.Column(
db.DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow,
nullable=False,
comment="更新时间",
)
def to_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
def save(self):
db.session.add(self)
db.session.commit()
return self
def delete(self):
db.session.delete(self)
db.session.commit()
+15
View File
@@ -0,0 +1,15 @@
from .base import BaseModel
from . import db
class Channel(BaseModel):
__tablename__ = "channels"
type = db.Column(
db.String(50),
nullable=False,
comment="类型(web, dingtalk, feishu, telegram, discord, qq",
)
name = db.Column(db.String(100), nullable=False, comment="渠道名称")
config = db.Column(db.JSON, nullable=False, comment="配置(JSON")
is_active = db.Column(db.Boolean, default=True, nullable=False, comment="是否激活")
+34
View File
@@ -0,0 +1,34 @@
from .base import BaseModel
from . import db
class Conversation(BaseModel):
__tablename__ = "conversations"
workspace_id = db.Column(
db.BigInteger,
db.ForeignKey("workspaces.id"),
nullable=False,
comment="工作空间ID",
)
agent_id = db.Column(
db.BigInteger, db.ForeignKey("agents.id"), nullable=False, comment="Agent ID"
)
title = db.Column(db.String(200), nullable=True, comment="会话标题")
channel = db.Column(
db.String(50),
default="web",
nullable=False,
comment="渠道(web, dingtalk, feishu, telegram, discord",
)
channel_user_id = db.Column(db.String(100), nullable=True, comment="渠道用户ID")
status = db.Column(
db.String(20),
default="active",
nullable=False,
comment="状态(active, archived, deleted",
)
messages = db.relationship(
"Message", backref="conversation", lazy=True, cascade="all, delete-orphan"
)
+16
View File
@@ -0,0 +1,16 @@
from .base import BaseModel
from . import db
class CronJob(BaseModel):
__tablename__ = "cron_jobs"
agent_id = db.Column(
db.BigInteger, db.ForeignKey("agents.id"), nullable=False, comment="Agent ID"
)
name = db.Column(db.String(100), nullable=False, comment="任务名称")
cron_expression = db.Column(db.String(50), nullable=False, comment="Cron 表达式")
prompt = db.Column(db.Text, nullable=False, comment="执行提示词")
is_active = db.Column(db.Boolean, default=True, nullable=False, comment="是否激活")
last_run_at = db.Column(db.DateTime, nullable=True, comment="上次运行时间")
next_run_at = db.Column(db.DateTime, nullable=True, comment="下次运行时间")
+24
View File
@@ -0,0 +1,24 @@
from .base import BaseModel
from . import db
class Memory(BaseModel):
__tablename__ = "memories"
workspace_id = db.Column(
db.BigInteger,
db.ForeignKey("workspaces.id"),
nullable=False,
comment="工作空间ID",
)
agent_id = db.Column(
db.BigInteger, db.ForeignKey("agents.id"), nullable=True, comment="Agent ID"
)
type = db.Column(
db.String(50), nullable=False, comment="类型(profile, memory, note"
)
content = db.Column(db.Text, nullable=False, comment="内容")
tags = db.Column(db.JSON, nullable=True, comment="标签")
importance = db.Column(
db.Integer, default=5, nullable=False, comment="重要性(1-10"
)
+38
View File
@@ -0,0 +1,38 @@
import json
from .base import BaseModel
from . import db
class Message(BaseModel):
__tablename__ = "messages"
conversation_id = db.Column(
db.BigInteger,
db.ForeignKey("conversations.id"),
nullable=False,
comment="会话ID",
)
role = db.Column(
db.String(20), nullable=False, comment="角色(user, assistant, system"
)
content = db.Column(db.Text, nullable=False, comment="消息内容")
tokens = db.Column(db.Integer, nullable=True, comment="Token数")
model = db.Column(db.String(50), nullable=True, comment="使用的模型")
tool_calls = db.Column(db.JSON, nullable=True, comment="工具调用记录")
metadata = db.Column(db.JSON, nullable=True, comment="元数据")
def to_dict(self):
result = super().to_dict()
if result.get("tool_calls"):
result["tool_calls"] = (
json.loads(result["tool_calls"])
if isinstance(result["tool_calls"], str)
else result["tool_calls"]
)
if result.get("metadata"):
result["metadata"] = (
json.loads(result["metadata"])
if isinstance(result["metadata"], str)
else result["metadata"]
)
return result
+14
View File
@@ -0,0 +1,14 @@
from .base import BaseModel
from . import db
class Model(BaseModel):
__tablename__ = 'models'
provider = db.Column(db.String(50), nullable=False, comment='提供商(dashscope, openai, anthropic, etc')
name = db.Column(db.String(100), nullable=False, comment='模型名称')
model_id = db.Column(db.String(100), nullable=False, comment='模型ID(如 qwen-plus')
api_key = db.Column(db.String(255), nullable=True, comment='API Key')
base_url = db.Column(db.String(255), nullable=True, comment='API Base URL')
is_default = db.Column(db.Boolean, default=False, nullable=False, comment='是否默认')
is_active = db.Column(db.Boolean, default=True, nullable=False, comment='是否激活')
+17
View File
@@ -0,0 +1,17 @@
from .base import BaseModel
from . import db
class Skill(BaseModel):
__tablename__ = "skills"
name = db.Column(db.String(100), nullable=False, comment="技能名称")
version = db.Column(db.String(20), default="1.0.0", nullable=False, comment="版本")
description = db.Column(db.Text, nullable=True, comment="描述")
author = db.Column(db.String(100), nullable=True, comment="作者")
repository = db.Column(db.String(255), nullable=True, comment="仓库地址")
config = db.Column(db.JSON, nullable=True, comment="配置(JSON")
is_installed = db.Column(
db.Boolean, default=False, nullable=False, comment="是否已安装"
)
is_active = db.Column(db.Boolean, default=True, nullable=False, comment="是否激活")
+29
View File
@@ -0,0 +1,29 @@
from .base import BaseModel
from . import db
class Tool(BaseModel):
__tablename__ = "tools"
name = db.Column(db.String(100), unique=True, nullable=False, comment="工具名称")
type = db.Column(
db.String(50), nullable=False, comment="类型(builtin, mcp, custom"
)
description = db.Column(db.Text, nullable=True, comment="描述")
config = db.Column(db.JSON, nullable=True, comment="配置(JSON")
is_active = db.Column(db.Boolean, default=True, nullable=False, comment="是否激活")
agent_tools = db.relationship(
"AgentTool", backref="tool", lazy=True, cascade="all, delete-orphan"
)
class AgentTool(BaseModel):
__tablename__ = "agent_tools"
agent_id = db.Column(
db.BigInteger, db.ForeignKey("agents.id"), nullable=False, comment="Agent ID"
)
tool_id = db.Column(
db.BigInteger, db.ForeignKey("tools.id"), nullable=False, comment="工具ID"
)
+19
View File
@@ -0,0 +1,19 @@
from .base import BaseModel
from . import db
class User(BaseModel):
__tablename__ = "users"
username = db.Column(db.String(50), unique=True, nullable=False, comment="用户名")
password = db.Column(db.String(255), nullable=False, comment="密码(加密)")
email = db.Column(db.String(100), unique=True, nullable=True, comment="邮箱")
avatar = db.Column(db.String(255), nullable=True, comment="头像URL")
is_active = db.Column(db.Boolean, default=True, nullable=False, comment="是否激活")
is_admin = db.Column(
db.Boolean, default=False, nullable=False, comment="是否管理员"
)
workspaces = db.relationship(
"Workspace", backref="user", lazy=True, cascade="all, delete-orphan"
)
+25
View File
@@ -0,0 +1,25 @@
from .base import BaseModel
from . import db
class Workspace(BaseModel):
__tablename__ = "workspaces"
user_id = db.Column(
db.BigInteger, db.ForeignKey("users.id"), nullable=False, comment="用户ID"
)
name = db.Column(db.String(100), nullable=False, comment="工作空间名称")
description = db.Column(db.Text, nullable=True, comment="描述")
is_default = db.Column(
db.Boolean, default=False, nullable=False, comment="是否默认"
)
agents = db.relationship(
"Agent", backref="workspace", lazy=True, cascade="all, delete-orphan"
)
conversations = db.relationship(
"Conversation", backref="workspace", lazy=True, cascade="all, delete-orphan"
)
memories = db.relationship(
"Memory", backref="workspace", lazy=True, cascade="all, delete-orphan"
)
+11
View File
@@ -0,0 +1,11 @@
from .user import UserSchema
from .workspace import WorkspaceSchema
from .agent import AgentSchema
from .conversation import ConversationSchema
from .message import MessageSchema
from .tool import ToolSchema, AgentToolSchema
from .skill import SkillSchema
from .memory import MemorySchema
from .model import ModelSchema
from .cron_job import CronJobSchema
from .channel import ChannelSchema
+34
View File
@@ -0,0 +1,34 @@
from marshmallow import Schema, fields
class AgentSchema(Schema):
id = fields.Integer(dump_only=True)
workspace_id = fields.Integer()
name = fields.String(required=True)
description = fields.String(allow_none=True)
system_prompt = fields.String(allow_none=True)
model_id = fields.String(allow_none=True)
temperature = fields.Float()
max_tokens = fields.Integer()
is_active = fields.Boolean()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class AgentCreateSchema(Schema):
name = fields.String(required=True)
description = fields.String(allow_none=True)
system_prompt = fields.String(allow_none=True)
model_id = fields.String(allow_none=True)
temperature = fields.Float()
max_tokens = fields.Integer()
class AgentUpdateSchema(Schema):
name = fields.String()
description = fields.String()
system_prompt = fields.String()
model_id = fields.String()
temperature = fields.Float()
max_tokens = fields.Integer()
is_active = fields.Boolean()
+23
View File
@@ -0,0 +1,23 @@
from marshmallow import Schema, fields
class ChannelSchema(Schema):
id = fields.Integer(dump_only=True)
type = fields.String(required=True)
name = fields.String(required=True)
config = fields.Dict(required=True, load_only=True)
is_active = fields.Boolean()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class ChannelCreateSchema(Schema):
type = fields.String(required=True)
name = fields.String(required=True)
config = fields.Dict(required=True, load_only=True)
class ChannelUpdateSchema(Schema):
name = fields.String()
config = fields.Dict(load_only=True)
is_active = fields.Boolean()
+23
View File
@@ -0,0 +1,23 @@
from marshmallow import Schema, fields
class ConversationSchema(Schema):
id = fields.Integer(dump_only=True)
workspace_id = fields.Integer()
agent_id = fields.Integer()
title = fields.String(allow_none=True)
channel = fields.String()
channel_user_id = fields.String(allow_none=True)
status = fields.String()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class ConversationCreateSchema(Schema):
agent_id = fields.Integer(required=True)
title = fields.String(allow_none=True)
class ConversationUpdateSchema(Schema):
title = fields.String()
status = fields.String()
+27
View File
@@ -0,0 +1,27 @@
from marshmallow import Schema, fields
class CronJobSchema(Schema):
id = fields.Integer(dump_only=True)
agent_id = fields.Integer()
name = fields.String(required=True)
cron_expression = fields.String(required=True)
prompt = fields.String(required=True)
is_active = fields.Boolean()
last_run_at = fields.DateTime(allow_none=True)
next_run_at = fields.DateTime(allow_none=True)
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class CronJobCreateSchema(Schema):
name = fields.String(required=True)
cron_expression = fields.String(required=True)
prompt = fields.String(required=True)
class CronJobUpdateSchema(Schema):
name = fields.String()
cron_expression = fields.String()
prompt = fields.String()
is_active = fields.Boolean()
+26
View File
@@ -0,0 +1,26 @@
from marshmallow import Schema, fields
class MemorySchema(Schema):
id = fields.Integer(dump_only=True)
workspace_id = fields.Integer()
agent_id = fields.Integer(allow_none=True)
type = fields.String(required=True)
content = fields.String(required=True)
tags = fields.List(fields.String(), allow_none=True)
importance = fields.Integer()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class MemoryCreateSchema(Schema):
type = fields.String(required=True)
content = fields.String(required=True)
tags = fields.List(fields.String(), allow_none=True)
importance = fields.Integer()
class MemoryUpdateSchema(Schema):
content = fields.String()
tags = fields.List(fields.String())
importance = fields.Integer()
+20
View File
@@ -0,0 +1,20 @@
from marshmallow import Schema, fields
class MessageSchema(Schema):
id = fields.Integer(dump_only=True)
conversation_id = fields.Integer()
role = fields.String(required=True)
content = fields.String(required=True)
tokens = fields.Integer(allow_none=True)
model = fields.String(allow_none=True)
tool_calls = fields.List(fields.Dict(), allow_none=True)
metadata = fields.Dict(allow_none=True)
created_at = fields.DateTime(dump_only=True)
class MessageCreateSchema(Schema):
role = fields.String(required=True)
content = fields.String(required=True)
tool_calls = fields.List(fields.Dict(), allow_none=True)
metadata = fields.Dict(allow_none=True)
+24
View File
@@ -0,0 +1,24 @@
from marshmallow import Schema, fields
class ModelSchema(Schema):
id = fields.Integer(dump_only=True)
provider = fields.String(required=True)
name = fields.String(required=True)
model_id = fields.String(required=True)
api_key = fields.String(allow_none=True, load_only=True)
base_url = fields.String(allow_none=True)
is_default = fields.Boolean()
is_active = fields.Boolean()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class ModelCreateSchema(Schema):
provider = fields.String(required=True)
name = fields.String(required=True)
model_id = fields.String(required=True)
api_key = fields.String(allow_none=True, load_only=True)
base_url = fields.String(allow_none=True)
is_default = fields.Boolean()
is_active = fields.Boolean()
+15
View File
@@ -0,0 +1,15 @@
from marshmallow import Schema, fields
class SkillSchema(Schema):
id = fields.Integer(dump_only=True)
name = fields.String(required=True)
version = fields.String()
description = fields.String(allow_none=True)
author = fields.String(allow_none=True)
repository = fields.String(allow_none=True)
config = fields.Dict(allow_none=True)
is_installed = fields.Boolean()
is_active = fields.Boolean()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
+30
View File
@@ -0,0 +1,30 @@
from marshmallow import Schema, fields
class ToolSchema(Schema):
id = fields.Integer(dump_only=True)
name = fields.String(required=True)
type = fields.String(required=True)
description = fields.String(allow_none=True)
config = fields.Dict(allow_none=True)
is_active = fields.Boolean()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class ToolCreateSchema(Schema):
name = fields.String(required=True)
type = fields.String(required=True)
description = fields.String(allow_none=True)
config = fields.Dict(allow_none=True)
class AgentToolSchema(Schema):
id = fields.Integer(dump_only=True)
agent_id = fields.Integer()
tool_id = fields.Integer()
created_at = fields.DateTime(dump_only=True)
class AgentToolCreateSchema(Schema):
tool_id = fields.Integer(required=True)
+23
View File
@@ -0,0 +1,23 @@
from marshmallow import Schema, fields
class UserSchema(Schema):
id = fields.Integer(dump_only=True)
username = fields.String(required=True)
email = fields.String(allow_none=True)
avatar = fields.String(allow_none=True)
is_active = fields.Boolean()
is_admin = fields.Boolean()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class UserCreateSchema(Schema):
username = fields.String(required=True)
password = fields.String(required=True, load_only=True)
email = fields.String(allow_none=True)
class UserUpdateSchema(Schema):
email = fields.String(allow_none=True)
avatar = fields.String(allow_none=True)
+21
View File
@@ -0,0 +1,21 @@
from marshmallow import Schema, fields
class WorkspaceSchema(Schema):
id = fields.Integer(dump_only=True)
user_id = fields.Integer()
name = fields.String(required=True)
description = fields.String(allow_none=True)
is_default = fields.Boolean()
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class WorkspaceCreateSchema(Schema):
name = fields.String(required=True)
description = fields.String(allow_none=True)
class WorkspaceUpdateSchema(Schema):
name = fields.String()
description = fields.String()
+12
View File
@@ -0,0 +1,12 @@
from .auth import AuthService
from .user import UserService
from .workspace import WorkspaceService
from .agent import AgentService
from .conversation import ConversationService
from .message import MessageService
from .tool import ToolService
from .skill import SkillService
from .memory import MemoryService
from .model import ModelService
from .cron_job import CronJobService
from .channel import ChannelService
+47
View File
@@ -0,0 +1,47 @@
from ..models import Agent
class AgentService:
@staticmethod
def create_agent(workspace_id, data):
agent = Agent(
workspace_id=workspace_id,
name=data["name"],
description=data.get("description"),
system_prompt=data.get("system_prompt"),
model_id=data.get("model_id"),
temperature=data.get("temperature", 0.70),
max_tokens=data.get("max_tokens", 2000),
)
agent.save()
return agent
@staticmethod
def update_agent(agent, data):
if "name" in data:
agent.name = data["name"]
if "description" in data:
agent.description = data["description"]
if "system_prompt" in data:
agent.system_prompt = data["system_prompt"]
if "model_id" in data:
agent.model_id = data["model_id"]
if "temperature" in data:
agent.temperature = data["temperature"]
if "max_tokens" in data:
agent.max_tokens = data["max_tokens"]
if "is_active" in data:
agent.is_active = data["is_active"]
agent.save()
return agent
@staticmethod
def delete_agent(agent):
agent.delete()
+34
View File
@@ -0,0 +1,34 @@
import bcrypt
from ..models import User
class AuthService:
@staticmethod
def register(username, password, email=None):
if User.query.filter_by(username=username).first():
raise ValueError("用户名已存在")
if email and User.query.filter_by(email=email).first():
raise ValueError("邮箱已被使用")
hashed_password = bcrypt.hashpw(
password.encode("utf-8"), bcrypt.gensalt()
).decode("utf-8")
user = User(username=username, password=hashed_password, email=email)
user.save()
return user
@staticmethod
def login(username, password):
user = User.query.filter_by(username=username).first()
if not user:
raise ValueError("用户名或密码错误")
if not bcrypt.checkpw(password.encode("utf-8"), user.password.encode("utf-8")):
raise ValueError("用户名或密码错误")
if not user.is_active:
raise ValueError("用户已被禁用")
return user
+35
View File
@@ -0,0 +1,35 @@
from ..models import Channel
class ChannelService:
@staticmethod
def create_channel(data):
channel = Channel(
type=data["type"],
name=data["name"],
config=data["config"],
is_active=data.get("is_active", True),
)
channel.save()
return channel
@staticmethod
def update_channel(channel, data):
if "name" in data:
channel.name = data["name"]
if "config" in data:
channel.config = data["config"]
if "is_active" in data:
channel.is_active = data["is_active"]
channel.save()
return channel
@staticmethod
def delete_channel(channel_id):
channel = Channel.query.get(channel_id)
if not channel:
raise ValueError("渠道不存在")
channel.delete()
+24
View File
@@ -0,0 +1,24 @@
from ..models import Conversation
class ConversationService:
@staticmethod
def create_conversation(workspace_id, data):
conversation = Conversation(
workspace_id=workspace_id,
agent_id=data["agent_id"],
title=data.get("title"),
)
conversation.save()
return conversation
@staticmethod
def update_conversation(conversation, data):
if "title" in data:
conversation.title = data["title"]
if "status" in data:
conversation.status = data["status"]
conversation.save()
return conversation
+39
View File
@@ -0,0 +1,39 @@
from ..models import CronJob
class CronJobService:
@staticmethod
def create_cron_job(agent_id, data):
cron_job = CronJob(
agent_id=agent_id,
name=data["name"],
cron_expression=data["cron_expression"],
prompt=data["prompt"],
is_active=data.get("is_active", True),
)
cron_job.save()
return cron_job
@staticmethod
def update_cron_job(cron_job, data):
if "name" in data:
cron_job.name = data["name"]
if "cron_expression" in data:
cron_job.cron_expression = data["cron_expression"]
if "prompt" in data:
cron_job.prompt = data["prompt"]
if "is_active" in data:
cron_job.is_active = data["is_active"]
cron_job.save()
return cron_job
@staticmethod
def delete_cron_job(cron_job_id):
cron_job = CronJob.query.get(cron_job_id)
if not cron_job:
raise ValueError("定时任务不存在")
cron_job.delete()
+34
View File
@@ -0,0 +1,34 @@
from ..models import Memory
class MemoryService:
@staticmethod
def create_memory(workspace_id, data):
memory = Memory(
workspace_id=workspace_id,
agent_id=data.get("agent_id"),
type=data["type"],
content=data["content"],
tags=data.get("tags"),
importance=data.get("importance", 5),
)
memory.save()
return memory
@staticmethod
def update_memory(memory, data):
if "content" in data:
memory.content = data["content"]
if "tags" in data:
memory.tags = data["tags"]
if "importance" in data:
memory.importance = data["importance"]
memory.save()
return memory
@staticmethod
def delete_memory(memory):
memory.delete()
+15
View File
@@ -0,0 +1,15 @@
from ..models import Message
class MessageService:
@staticmethod
def create_message(conversation_id, data):
message = Message(
conversation_id=conversation_id,
role=data["role"],
content=data["content"],
tool_calls=data.get("tool_calls"),
metadata=data.get("metadata"),
)
message.save()
return message
+51
View File
@@ -0,0 +1,51 @@
from ..models import Model
class ModelService:
@staticmethod
def create_model(data):
model = Model(
provider=data["provider"],
name=data["name"],
model_id=data["model_id"],
api_key=data.get("api_key"),
base_url=data.get("base_url"),
is_default=data.get("is_default", False),
is_active=data.get("is_active", True),
)
if model.is_default:
Model.query.filter_by(is_default=True).update({"is_default": False})
model.save()
return model
@staticmethod
def update_model(model, data):
if "name" in data:
model.name = data["name"]
if "api_key" in data:
model.api_key = data["api_key"]
if "base_url" in data:
model.base_url = data["base_url"]
if "is_default" in data and data["is_default"]:
Model.query.filter(Model.id != model.id, Model.is_default == True).update(
{"is_default": False}
)
model.is_default = True
if "is_active" in data:
model.is_active = data["is_active"]
model.save()
return model
@staticmethod
def delete_model(model_id):
model = Model.query.get(model_id)
if model.is_default:
raise ValueError("默认模型不能删除")
model.delete()
+29
View File
@@ -0,0 +1,29 @@
from ..models import Skill
class SkillService:
@staticmethod
def install_skill(skill_id):
skill = Skill.query.get(skill_id)
if not skill:
raise ValueError("技能不存在")
if skill.is_installed:
raise ValueError("技能已安装")
skill.is_installed = True
skill.save()
return skill
@staticmethod
def uninstall_skill(skill_id):
skill = Skill.query.get(skill_id)
if not skill:
raise ValueError("技能不存在")
if not skill.is_installed:
raise ValueError("技能未安装")
skill.is_installed = False
skill.save()
return skill
+35
View File
@@ -0,0 +1,35 @@
from ..models import Tool, AgentTool
class ToolService:
@staticmethod
def create_tool(data):
if Tool.query.filter_by(name=data["name"]).first():
raise ValueError("工具名称已存在")
tool = Tool(
name=data["name"],
type=data["type"],
description=data.get("description"),
config=data.get("config"),
)
tool.save()
return tool
@staticmethod
def add_tool_to_agent(agent_id, tool_id):
if AgentTool.query.filter_by(agent_id=agent_id, tool_id=tool_id).first():
raise ValueError("工具已添加到此 Agent")
agent_tool = AgentTool(agent_id=agent_id, tool_id=tool_id)
agent_tool.save()
return agent_tool
@staticmethod
def remove_tool_from_agent(agent_id, tool_id):
agent_tool = AgentTool.query.filter_by(
agent_id=agent_id, tool_id=tool_id
).first()
if not agent_tool:
raise ValueError("工具关联不存在")
agent_tool.delete()
+21
View File
@@ -0,0 +1,21 @@
from ..models import User
class UserService:
@staticmethod
def update_user(user, data):
if "email" in data:
if (
data["email"]
and User.query.filter(
User.email == data["email"], User.id != user.id
).first()
):
raise ValueError("邮箱已被使用")
user.email = data["email"]
if "avatar" in data:
user.avatar = data["avatar"]
user.save()
return user
+26
View File
@@ -0,0 +1,26 @@
from ..models import Workspace
class WorkspaceService:
@staticmethod
def create_workspace(user_id, name, description=None):
workspace = Workspace(user_id=user_id, name=name, description=description)
workspace.save()
return workspace
@staticmethod
def update_workspace(workspace, data):
if "name" in data:
workspace.name = data["name"]
if "description" in data:
workspace.description = data["description"]
workspace.save()
return workspace
@staticmethod
def delete_workspace(workspace):
if workspace.is_default:
raise ValueError("默认工作空间不能删除")
workspace.delete()
+5
View File
@@ -0,0 +1,5 @@
from .manager import SkillManager
from .clawhub import ClawHubMarket
__all__ = ["SkillManager", "ClawHubMarket"]
+6
View File
@@ -0,0 +1,6 @@
class ClawHubMarket:
def search_skills(self, query):
pass
def download_skill(self, skill_id):
pass
+12
View File
@@ -0,0 +1,12 @@
class SkillManager:
def __init__(self):
self.skills = {}
def install_skill(self, skill_path):
pass
def uninstall_skill(self, skill_name):
pass
def list_skills(self):
return self.skills
+5
View File
@@ -0,0 +1,5 @@
from .manager import ToolManager
from .mcp_adapter import MCPAdapter
__all__ = ["ToolManager", "MCPAdapter"]
+11
View File
@@ -0,0 +1,11 @@
class ToolManager:
def __init__(self):
self.tools = {}
def register_tool(self, name, tool_func):
self.tools[name] = tool_func
def execute_tool(self, name, **kwargs):
if name not in self.tools:
raise ValueError(f"Tool {name} not found")
return self.tools[name](**kwargs)
+9
View File
@@ -0,0 +1,9 @@
class MCPAdapter:
def __init__(self, config):
self.config = config
def connect(self):
pass
def call_tool(self, tool_name, params):
pass
+5
View File
@@ -0,0 +1,5 @@
from .manager import WorkspaceManager
from .file_manager import FileManager
__all__ = ["WorkspaceManager", "FileManager"]
+53
View File
@@ -0,0 +1,53 @@
import os
class FileManager:
def __init__(self, workspace_id, base_dir="workspaces"):
self.workspace_id = workspace_id
self.base_dir = base_dir
self.workspace_path = os.path.join(base_dir, str(workspace_id))
self._ensure_workspace_dir()
def _ensure_workspace_dir(self):
if not os.path.exists(self.workspace_path):
os.makedirs(self.workspace_path)
def save_file(self, filename, content):
file_path = os.path.join(self.workspace_path, filename)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, "w", encoding="utf-8") as f:
f.write(content)
def read_file(self, filename):
file_path = os.path.join(self.workspace_path, filename)
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件 {filename} 不存在")
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
def delete_file(self, filename):
file_path = os.path.join(self.workspace_path, filename)
if os.path.exists(file_path):
os.remove(file_path)
def list_files(self, directory=""):
dir_path = (
os.path.join(self.workspace_path, directory)
if directory
else self.workspace_path
)
if not os.path.exists(dir_path):
return []
files = []
for item in os.listdir(dir_path):
item_path = os.path.join(dir_path, item)
if os.path.isfile(item_path):
files.append(item)
elif os.path.isdir(item_path):
files.extend(
[
os.path.join(directory, f)
for f in self.list_files(os.path.join(directory, item))
]
)
return files
+18
View File
@@ -0,0 +1,18 @@
class WorkspaceManager:
def __init__(self, workspace_id):
self.workspace_id = workspace_id
def create_file(self, filename, content):
pass
def read_file(self, filename):
pass
def update_file(self, filename, content):
pass
def delete_file(self, filename):
pass
def list_files(self):
pass