first commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
[flake8]
|
||||
max-line-length = 100
|
||||
extend-ignore = E203, W503
|
||||
exclude =
|
||||
.git,
|
||||
__pycache__,
|
||||
.venv,
|
||||
venv,
|
||||
build,
|
||||
dist,
|
||||
*.egg-info
|
||||
@@ -0,0 +1,18 @@
|
||||
FROM python:3.9-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
default-libmysqlclient-dev \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["python", "run.py"]
|
||||
@@ -0,0 +1,43 @@
|
||||
"""A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
script_location = alembic
|
||||
|
||||
sqlalchemy.url = mysql+pymysql://sentclaw:sentclaw123@localhost:3306/sentclaw
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
@@ -0,0 +1,63 @@
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
|
||||
from alembic import context
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from app import create_app
|
||||
from app.models import db
|
||||
|
||||
config = context.config
|
||||
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
target_metadata = db.metadata
|
||||
|
||||
|
||||
def get_engine():
|
||||
app = create_app()
|
||||
return app.extensions["sqlalchemy"].engine
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
configuration = config.get_section(config.config_ini_section, {})
|
||||
configuration["sqlalchemy.url"] = os.environ.get(
|
||||
"DATABASE_URL", configuration["sqlalchemy.url"]
|
||||
)
|
||||
|
||||
connectable = engine_from_config(
|
||||
configuration,
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -0,0 +1,24 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
from .engine import AgentEngine
|
||||
from .react import ReActAgent
|
||||
from .plan_execute import PlanAndExecuteAgent
|
||||
|
||||
|
||||
__all__ = ["AgentEngine", "ReActAgent", "PlanAndExecuteAgent"]
|
||||
@@ -0,0 +1,6 @@
|
||||
class AgentEngine:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def run(self, prompt, tools=None):
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,6 @@
|
||||
from .engine import AgentEngine
|
||||
|
||||
|
||||
class PlanAndExecuteAgent(AgentEngine):
|
||||
def run(self, prompt, tools=None):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
from .engine import AgentEngine
|
||||
|
||||
|
||||
class ReActAgent(AgentEngine):
|
||||
def run(self, prompt, tools=None):
|
||||
pass
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
from .base import BaseLLMClient
|
||||
|
||||
|
||||
class AnthropicClient(BaseLLMClient):
|
||||
def chat(self, messages, **kwargs):
|
||||
pass
|
||||
|
||||
def stream_chat(self, messages, **kwargs):
|
||||
pass
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
from .base import BaseLLMClient
|
||||
|
||||
|
||||
class DashScopeClient(BaseLLMClient):
|
||||
def chat(self, messages, **kwargs):
|
||||
pass
|
||||
|
||||
def stream_chat(self, messages, **kwargs):
|
||||
pass
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
from .base import BaseLLMClient
|
||||
|
||||
|
||||
class OpenAIClient(BaseLLMClient):
|
||||
def chat(self, messages, **kwargs):
|
||||
pass
|
||||
|
||||
def stream_chat(self, messages, **kwargs):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
from .manager import MemoryManager
|
||||
from .extractor import MemoryExtractor
|
||||
from .integrator import MemoryIntegrator
|
||||
|
||||
|
||||
__all__ = ["MemoryManager", "MemoryExtractor", "MemoryIntegrator"]
|
||||
@@ -0,0 +1,6 @@
|
||||
class MemoryExtractor:
|
||||
def __init__(self, llm_client):
|
||||
self.llm_client = llm_client
|
||||
|
||||
def extract_from_conversation(self, conversation):
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
class MemoryIntegrator:
|
||||
def integrate_memories(self, memories):
|
||||
pass
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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="是否激活")
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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="下次运行时间")
|
||||
@@ -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)"
|
||||
)
|
||||
@@ -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
|
||||
@@ -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='是否激活')
|
||||
@@ -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="是否激活")
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,5 @@
|
||||
from .manager import SkillManager
|
||||
from .clawhub import ClawHubMarket
|
||||
|
||||
|
||||
__all__ = ["SkillManager", "ClawHubMarket"]
|
||||
@@ -0,0 +1,6 @@
|
||||
class ClawHubMarket:
|
||||
def search_skills(self, query):
|
||||
pass
|
||||
|
||||
def download_skill(self, skill_id):
|
||||
pass
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
from .manager import ToolManager
|
||||
from .mcp_adapter import MCPAdapter
|
||||
|
||||
|
||||
__all__ = ["ToolManager", "MCPAdapter"]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
from .manager import WorkspaceManager
|
||||
from .file_manager import FileManager
|
||||
|
||||
|
||||
__all__ = ["WorkspaceManager", "FileManager"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,35 @@
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py39']
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
/(
|
||||
# 默认排除
|
||||
\.eggs
|
||||
| \.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| _build
|
||||
| buck-out
|
||||
| build
|
||||
| dist
|
||||
)/
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 100
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.9"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = false
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
@@ -0,0 +1,57 @@
|
||||
# Flask 核心
|
||||
Flask==3.0.0
|
||||
Flask-CORS==4.0.0
|
||||
Flask-Migrate==4.0.7
|
||||
Flask-JWT-Extended==4.6.0
|
||||
|
||||
# 数据库
|
||||
SQLAlchemy==2.0.23
|
||||
PyMySQL==1.1.0
|
||||
cryptography==41.0.7
|
||||
|
||||
# Redis
|
||||
redis==5.0.1
|
||||
|
||||
# 数据验证和序列化
|
||||
marshmallow==3.20.1
|
||||
flask-marshmallow==0.15.0
|
||||
marshmallow-sqlalchemy==0.29.0
|
||||
|
||||
# HTTP 客户端
|
||||
requests==2.31.0
|
||||
httpx==0.25.2
|
||||
|
||||
# LLM 集成
|
||||
openai==1.3.7
|
||||
dashscope==1.14.1
|
||||
anthropic==0.7.8
|
||||
|
||||
# 工具
|
||||
python-dotenv==1.0.0
|
||||
pyyaml==6.0.1
|
||||
python-dateutil==2.8.2
|
||||
|
||||
# 任务调度
|
||||
APScheduler==3.10.4
|
||||
|
||||
# MCP 协议
|
||||
mcp==0.1.0
|
||||
|
||||
# 密码加密
|
||||
bcrypt==4.1.2
|
||||
|
||||
# JSON 处理
|
||||
orjson==3.9.12
|
||||
|
||||
# 日志
|
||||
colorlog==6.8.0
|
||||
|
||||
# 工具库
|
||||
tenacity==8.2.3
|
||||
|
||||
# 开发工具
|
||||
black==24.1.1
|
||||
flake8==7.0.0
|
||||
mypy==1.8.0
|
||||
pytest==7.4.4
|
||||
pytest-cov==4.1.0
|
||||
@@ -0,0 +1,22 @@
|
||||
from app import create_app
|
||||
import os
|
||||
|
||||
app = create_app(os.environ.get("FLASK_ENV", "development"))
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return {"message": "SentClaw API", "version": "0.1.0", "status": "running"}
|
||||
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
return {"status": "healthy"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(
|
||||
host=app.config["Flask_HOST"],
|
||||
port=app.config["Flask_PORT"],
|
||||
debug=app.config["Flask_DEBUG"],
|
||||
)
|
||||
Reference in New Issue
Block a user