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 (
|
||||
Column, Integer, String, Text, DateTime, Boolean, Float, ForeignKey
|
||||
Column, Integer, String, Text, DateTime, Boolean, Float, ForeignKey, JSON
|
||||
)
|
||||
from sqlalchemy.orm import relationship, declarative_base
|
||||
|
||||
@@ -250,4 +250,31 @@ class ChatRoleTool(Base):
|
||||
__tablename__ = 'ai_chat_role_tool'
|
||||
|
||||
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):
|
||||
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