add ai image base
This commit is contained in:
50
ai_service/llm/__init__.py
Normal file
50
ai_service/llm/__init__.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import os
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from pydantic import SecretStr
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderEnum(str, Enum):
|
||||||
|
"""支持的 LLM 服务商"""
|
||||||
|
DEEPSEEK = "deepseek"
|
||||||
|
OPENAI = "openai"
|
||||||
|
TONGYI = "tongyi"
|
||||||
|
|
||||||
|
class LLMFactory(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_llm(provider: ProviderEnum, model: str = None, **kwargs):
|
||||||
|
if provider == ProviderEnum.DEEPSEEK:
|
||||||
|
from langchain_deepseek import ChatDeepSeek
|
||||||
|
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||||
|
model = model or "deepseek-chat"
|
||||||
|
return ChatDeepSeek(
|
||||||
|
api_key=SecretStr(api_key),
|
||||||
|
model=model,
|
||||||
|
streaming=True,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
elif provider == ProviderEnum.OPENAI:
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
api_key = os.getenv("OPENAI_API_KEY")
|
||||||
|
model = model or "gpt-3.5-turbo"
|
||||||
|
return ChatOpenAI(
|
||||||
|
api_key=SecretStr(api_key),
|
||||||
|
model=model,
|
||||||
|
streaming=True,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
elif provider == ProviderEnum.TONGYI:
|
||||||
|
from langchain_community.llms import Tongyi
|
||||||
|
api_key = os.getenv("DASHSCOPE_API_KEY")
|
||||||
|
model = model or "qwen-turbo"
|
||||||
|
return Tongyi(
|
||||||
|
api_key=SecretStr(api_key),
|
||||||
|
model=model,
|
||||||
|
streaming=True,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的 LLM 服务商: {provider}")
|
||||||
0
ai_service/llm/adapter/__init__.py
Normal file
0
ai_service/llm/adapter/__init__.py
Normal file
17
ai_service/llm/adapter/deepseek.py
Normal file
17
ai_service/llm/adapter/deepseek.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from langchain_deepseek import ChatDeepSeek
|
||||||
|
|
||||||
|
from llm.base import MultiModalAICapability
|
||||||
|
|
||||||
|
|
||||||
|
class DeepSeekAdapter(MultiModalAICapability):
|
||||||
|
def __init__(self, api_key, model, **kwargs):
|
||||||
|
|
||||||
|
self.llm = ChatDeepSeek(api_key=api_key, model=model, streaming=True)
|
||||||
|
|
||||||
|
async def chat(self, messages, **kwargs):
|
||||||
|
# 兼容 DeepSeek 的调用方式
|
||||||
|
return await self.llm.ainvoke(messages)
|
||||||
|
|
||||||
|
async def stream_chat(self, messages, **kwargs):
|
||||||
|
async for chunk in self.llm.astream(messages):
|
||||||
|
yield chunk
|
||||||
21
ai_service/llm/adapter/genai.py
Normal file
21
ai_service/llm/adapter/genai.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 假设有 google genai sdk
|
||||||
|
# from google_genai import GenAI
|
||||||
|
from llm.base import MultiModalAICapability
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleGenAIAdapter(MultiModalAICapability):
|
||||||
|
def __init__(self, api_key, model, **kwargs):
|
||||||
|
self.api_key = api_key
|
||||||
|
self.model = model
|
||||||
|
# self.llm = GenAI(api_key=api_key, model=model)
|
||||||
|
|
||||||
|
async def chat(self, messages, **kwargs):
|
||||||
|
# return await self.llm.chat(messages)
|
||||||
|
raise NotImplementedError("Google GenAI chat未实现")
|
||||||
|
|
||||||
|
async def stream_chat(self, messages, **kwargs):
|
||||||
|
# async for chunk in self.llm.stream_chat(messages):
|
||||||
|
# yield chunk
|
||||||
|
raise NotImplementedError("Google GenAI stream_chat未实现")
|
||||||
|
|
||||||
|
# 其他能力同理
|
||||||
25
ai_service/llm/adapter/openai.py
Normal file
25
ai_service/llm/adapter/openai.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from llm.base import MultiModalAICapability
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
# from openai import OpenAI # 如需图片/音频/视频等API
|
||||||
|
|
||||||
|
class OpenAIAdapter(MultiModalAICapability):
|
||||||
|
def __init__(self, api_key, model, **kwargs):
|
||||||
|
self.llm = ChatOpenAI(api_key=api_key, model=model, streaming=True)
|
||||||
|
self.api_key = api_key
|
||||||
|
|
||||||
|
async def chat(self, messages, **kwargs):
|
||||||
|
return await self.llm.ainvoke(messages)
|
||||||
|
|
||||||
|
async def stream_chat(self, messages, **kwargs):
|
||||||
|
async for chunk in self.llm.astream(messages):
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
# 如需图片生成(DALL·E),可实现如下
|
||||||
|
def create_image_task(self, prompt, **kwargs):
|
||||||
|
# 伪代码,需用 openai.Image.create
|
||||||
|
# import openai
|
||||||
|
# response = openai.Image.create(api_key=self.api_key, prompt=prompt, ...)
|
||||||
|
# return response
|
||||||
|
raise NotImplementedError("OpenAI 图片生成请用 openai.Image.create 实现")
|
||||||
|
|
||||||
|
# 其他能力同理
|
||||||
51
ai_service/llm/adapter/tongyi.py
Normal file
51
ai_service/llm/adapter/tongyi.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from langchain_deepseek import ChatDeepSeek
|
||||||
|
from http import HTTPStatus
|
||||||
|
from urllib.parse import urlparse, unquote
|
||||||
|
from pathlib import PurePosixPath
|
||||||
|
import requests
|
||||||
|
from dashscope import ImageSynthesis
|
||||||
|
import os
|
||||||
|
|
||||||
|
from llm.base import MultiModalAICapability
|
||||||
|
|
||||||
|
|
||||||
|
class TongYiAdapter(MultiModalAICapability):
|
||||||
|
def __init__(self, api_key, model, **kwargs):
|
||||||
|
self.api_key = api_key
|
||||||
|
self.model = model
|
||||||
|
self.llm = ChatDeepSeek(api_key=api_key, model=model, streaming=True)
|
||||||
|
|
||||||
|
async def chat(self, messages, **kwargs):
|
||||||
|
# 兼容 DeepSeek 的调用方式
|
||||||
|
return await self.llm.ainvoke(messages)
|
||||||
|
|
||||||
|
async def stream_chat(self, messages, **kwargs):
|
||||||
|
async for chunk in self.llm.astream(messages):
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_image_task(api_key, model, prompt: str, style='<watercolor>', size='1024*1024', n=1):
|
||||||
|
"""创建异步图片生成任务"""
|
||||||
|
rsp = ImageSynthesis.async_call(
|
||||||
|
api_key=api_key,
|
||||||
|
model=model,
|
||||||
|
prompt=prompt,
|
||||||
|
n=n,
|
||||||
|
style=style,
|
||||||
|
size=size
|
||||||
|
)
|
||||||
|
if rsp.status_code == HTTPStatus.OK:
|
||||||
|
return rsp
|
||||||
|
else:
|
||||||
|
raise Exception(f"Failed, status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fetch_image_task_status(task):
|
||||||
|
"""获取异步图片任务状态"""
|
||||||
|
status = ImageSynthesis.fetch(task)
|
||||||
|
if status.status_code == HTTPStatus.OK:
|
||||||
|
return status.output.task_status
|
||||||
|
else:
|
||||||
|
raise Exception(f"Failed, status_code: {status.status_code}, code: {status.code}, message: {status.message}")
|
||||||
|
|
||||||
|
|
||||||
37
ai_service/llm/base.py
Normal file
37
ai_service/llm/base.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
class MultiModalAICapability(ABC):
|
||||||
|
# 对话能力
|
||||||
|
async def chat(self, messages, **kwargs):
|
||||||
|
raise NotImplementedError("chat not supported by this provider")
|
||||||
|
|
||||||
|
async def stream_chat(self, messages, **kwargs):
|
||||||
|
raise NotImplementedError("stream_chat not supported by this provider")
|
||||||
|
|
||||||
|
# 图片生成能力
|
||||||
|
def create_image_task(self, prompt, **kwargs):
|
||||||
|
raise NotImplementedError("image generation not supported by this provider")
|
||||||
|
|
||||||
|
def fetch_image_task_status(self, task):
|
||||||
|
raise NotImplementedError("image task status not supported by this provider")
|
||||||
|
|
||||||
|
def fetch_image_result(self, task):
|
||||||
|
raise NotImplementedError("image result not supported by this provider")
|
||||||
|
|
||||||
|
# 视频生成能力
|
||||||
|
def create_video_task(self, prompt, **kwargs):
|
||||||
|
raise NotImplementedError("video generation not supported by this provider")
|
||||||
|
|
||||||
|
def fetch_video_task_status(self, task):
|
||||||
|
raise NotImplementedError("video task status not supported by this provider")
|
||||||
|
|
||||||
|
def fetch_video_result(self, task):
|
||||||
|
raise NotImplementedError("video result not supported by this provider")
|
||||||
|
|
||||||
|
# 知识库能力
|
||||||
|
def query_knowledge(self, query, **kwargs):
|
||||||
|
raise NotImplementedError("knowledge query not supported by this provider")
|
||||||
|
|
||||||
|
# 语音合成能力
|
||||||
|
def synthesize_speech(self, text, **kwargs):
|
||||||
|
raise NotImplementedError("speech synthesis not supported by this provider")
|
||||||
34
ai_service/llm/factory.py
Normal file
34
ai_service/llm/factory.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from .adapter.deepseek import DeepSeekAdapter
|
||||||
|
from .adapter.genai import GoogleGenAIAdapter
|
||||||
|
from .adapter.openai import OpenAIAdapter
|
||||||
|
from .adapter.tongyi import TongYiAdapter
|
||||||
|
|
||||||
|
|
||||||
|
def get_adapter(provider, api_key, model, **kwargs):
|
||||||
|
if provider == 'deepseek':
|
||||||
|
return DeepSeekAdapter(api_key, model, **kwargs)
|
||||||
|
elif provider == 'tongyi':
|
||||||
|
return TongYiAdapter(api_key, model, **kwargs)
|
||||||
|
elif provider == 'openai':
|
||||||
|
return OpenAIAdapter(api_key, model, **kwargs)
|
||||||
|
elif provider == 'google-genai':
|
||||||
|
return GoogleGenAIAdapter(api_key, model, **kwargs)
|
||||||
|
else:
|
||||||
|
raise ValueError('不支持的服务商')
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
# adapter = get_adapter('tongyi', api_key='xxx', model='wanx_v1')
|
||||||
|
|
||||||
|
# 对话
|
||||||
|
# try:
|
||||||
|
# result = await adapter.chat(messages)
|
||||||
|
# except NotImplementedError:
|
||||||
|
# print("该服务商不支持对话能力")
|
||||||
|
|
||||||
|
# # 图片生成
|
||||||
|
# try:
|
||||||
|
# task = adapter.create_image_task(prompt="一只猫")
|
||||||
|
# status = adapter.fetch_image_task_status(task)
|
||||||
|
# result = adapter.fetch_image_result(task)
|
||||||
|
# except NotImplementedError:
|
||||||
|
# print("该服务商不支持图片生成")
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Column, Integer, String, Text, DateTime, Boolean, Float, ForeignKey
|
Column, Integer, String, Text, DateTime, Boolean, Float, ForeignKey, JSON
|
||||||
)
|
)
|
||||||
from sqlalchemy.orm import relationship, declarative_base
|
from sqlalchemy.orm import relationship, declarative_base
|
||||||
|
|
||||||
@@ -250,4 +250,31 @@ class ChatRoleTool(Base):
|
|||||||
__tablename__ = 'ai_chat_role_tool'
|
__tablename__ = 'ai_chat_role_tool'
|
||||||
|
|
||||||
chat_role_id = Column(Integer, ForeignKey('ai_chat_role.id'), primary_key=True)
|
chat_role_id = Column(Integer, ForeignKey('ai_chat_role.id'), primary_key=True)
|
||||||
tool_id = Column(Integer, ForeignKey('ai_tool.id'), primary_key=True)
|
tool_id = Column(Integer, ForeignKey('ai_tool.id'), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Image(CoreModel):
|
||||||
|
__tablename__ = 'ai_image'
|
||||||
|
|
||||||
|
user_id = Column(Integer, ForeignKey('system_users.id'), nullable=True)
|
||||||
|
public_status = Column(Boolean, default=False, nullable=False)
|
||||||
|
|
||||||
|
platform = Column(String(64), nullable=False)
|
||||||
|
model = Column(String(64), nullable=False)
|
||||||
|
|
||||||
|
prompt = Column(Text(length=2000), nullable=False)
|
||||||
|
width = Column(Integer, nullable=False)
|
||||||
|
height = Column(Integer, nullable=False)
|
||||||
|
options = Column(JSON, nullable=True)
|
||||||
|
|
||||||
|
status = Column(String(20), nullable=False)
|
||||||
|
pic_url = Column(String(2048), nullable=True)
|
||||||
|
error_message = Column(String(1024), nullable=True)
|
||||||
|
|
||||||
|
task_id = Column(String(1024), nullable=True)
|
||||||
|
buttons = Column(String(2048), nullable=True)
|
||||||
|
|
||||||
|
user = relationship("DjangoUser", backref="images")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Image #{self.id} ({self.prompt[:30]})"
|
||||||
142
backend/ai/migrations/0005_image.py
Normal file
142
backend/ai/migrations/0005_image.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-07-21 03:06
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("ai", "0004_alter_chatconversation_model_id_and_more"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Image",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"remark",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_comment="备注",
|
||||||
|
help_text="备注",
|
||||||
|
max_length=256,
|
||||||
|
null=True,
|
||||||
|
verbose_name="备注",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"creator",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_comment="创建人",
|
||||||
|
help_text="创建人",
|
||||||
|
max_length=64,
|
||||||
|
null=True,
|
||||||
|
verbose_name="创建人",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modifier",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_comment="修改人",
|
||||||
|
help_text="修改人",
|
||||||
|
max_length=64,
|
||||||
|
null=True,
|
||||||
|
verbose_name="修改人",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"update_time",
|
||||||
|
models.DateTimeField(
|
||||||
|
auto_now=True,
|
||||||
|
db_comment="修改时间",
|
||||||
|
help_text="修改时间",
|
||||||
|
null=True,
|
||||||
|
verbose_name="修改时间",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"create_time",
|
||||||
|
models.DateTimeField(
|
||||||
|
auto_now_add=True,
|
||||||
|
db_comment="创建时间",
|
||||||
|
help_text="创建时间",
|
||||||
|
null=True,
|
||||||
|
verbose_name="创建时间",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_deleted",
|
||||||
|
models.BooleanField(
|
||||||
|
db_comment="是否软删除",
|
||||||
|
default=False,
|
||||||
|
verbose_name="是否软删除",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"public_status",
|
||||||
|
models.BooleanField(default=False, verbose_name="是否发布"),
|
||||||
|
),
|
||||||
|
("platform", models.CharField(max_length=64, verbose_name="平台")),
|
||||||
|
("model", models.CharField(max_length=64, verbose_name="模型")),
|
||||||
|
("prompt", models.TextField(max_length=2000, verbose_name="提示词")),
|
||||||
|
("width", models.IntegerField(verbose_name="图片宽度")),
|
||||||
|
("height", models.IntegerField(verbose_name="图片高度")),
|
||||||
|
("options", models.JSONField(null=True, verbose_name="绘制参数")),
|
||||||
|
("status", models.CharField(max_length=20, verbose_name="绘画状态")),
|
||||||
|
(
|
||||||
|
"pic_url",
|
||||||
|
models.URLField(
|
||||||
|
max_length=2048, null=True, verbose_name="图片地址"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"error_message",
|
||||||
|
models.CharField(
|
||||||
|
max_length=1024, null=True, verbose_name="错误信息"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"task_id",
|
||||||
|
models.CharField(
|
||||||
|
max_length=1024, null=True, verbose_name="任务编号"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"buttons",
|
||||||
|
models.CharField(
|
||||||
|
max_length=2048, null=True, verbose_name="mj buttons 按钮"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
db_comment="用户编号",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="用户",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "AI 绘画表",
|
||||||
|
"verbose_name_plural": "AI 绘画表",
|
||||||
|
"db_table": "ai_image",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -330,3 +330,35 @@ class ChatMessage(CoreModel):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.content[:30]
|
return self.content[:30]
|
||||||
|
|
||||||
|
|
||||||
|
class Image(CoreModel):
|
||||||
|
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
null=True, blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
verbose_name="用户",
|
||||||
|
db_comment="用户编号"
|
||||||
|
)
|
||||||
|
public_status = models.BooleanField(default=False, verbose_name='是否发布')
|
||||||
|
|
||||||
|
platform = models.CharField(max_length=64, verbose_name='平台')
|
||||||
|
model = models.CharField(max_length=64, verbose_name='模型')
|
||||||
|
|
||||||
|
prompt = models.TextField(max_length=2000, verbose_name='提示词')
|
||||||
|
width = models.IntegerField(verbose_name='图片宽度')
|
||||||
|
height = models.IntegerField(verbose_name='图片高度')
|
||||||
|
options = models.JSONField(null=True, verbose_name='绘制参数')
|
||||||
|
|
||||||
|
status = models.CharField(max_length=20, verbose_name='绘画状态')
|
||||||
|
pic_url = models.URLField(max_length=2048, null=True, verbose_name='图片地址')
|
||||||
|
error_message = models.CharField(max_length=1024, null=True, verbose_name='错误信息')
|
||||||
|
|
||||||
|
task_id = models.CharField(max_length=1024, null=True, verbose_name='任务编号')
|
||||||
|
buttons = models.CharField(max_length=2048, null=True, verbose_name='mj buttons 按钮')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'ai_image'
|
||||||
|
verbose_name = 'AI 绘画表'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|||||||
11
web/apps/web-antd/src/views/ai/image/index.vue
Normal file
11
web/apps/web-antd/src/views/ai/image/index.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>dsadsa</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="css">
|
||||||
|
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user