初始化项目,完整业务代码提交
This commit is contained in:
commit
16d736e3aa
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# 忽略Maven编译目录
|
||||
.trae
|
||||
.venv
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (sales-assistant-py)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (sales-assistant-py)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/sales-assistant-py.iml" filepath="$PROJECT_DIR$/.idea/sales-assistant-py.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/sales-assistant-py.iml
generated
Normal file
10
.idea/sales-assistant-py.iml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.12 (sales-assistant-py)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
BIN
app/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/llm.cpython-312.pyc
Normal file
BIN
app/__pycache__/llm.cpython-312.pyc
Normal file
Binary file not shown.
1
app/aa.md
Normal file
1
app/aa.md
Normal file
@ -0,0 +1 @@
|
||||
【日期】 2026年6月11日 \n【沟通对象】 客户Janice \n** 核心诉求 (User Intent)** \n咨询线上角色建模相关课程;销售进一步了解其年级与专业背景。 \n** 处理结果 (Resolution)** \n初步确认客户对角色建模有兴趣,正在收集基本信息。 \n** 待办事项 (Action Items)** \n(无) \n** 关键标签**:#角色建模#初步咨询
|
||||
0
app/client/__init__.py
Normal file
0
app/client/__init__.py
Normal file
BIN
app/client/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/client/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/client/__pycache__/embedding_client_manager.cpython-312.pyc
Normal file
BIN
app/client/__pycache__/embedding_client_manager.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/client/__pycache__/milvus_client_manager.cpython-312.pyc
Normal file
BIN
app/client/__pycache__/milvus_client_manager.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/client/__pycache__/mysql_client_manager.cpython-312.pyc
Normal file
BIN
app/client/__pycache__/mysql_client_manager.cpython-312.pyc
Normal file
Binary file not shown.
70
app/client/embedding_client_manager.py
Normal file
70
app/client/embedding_client_manager.py
Normal file
@ -0,0 +1,70 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
import aiohttp
|
||||
import requests
|
||||
|
||||
from app.conf.app_config import EmbeddingConfig, app_config
|
||||
|
||||
|
||||
class EmbeddingClientManager:
|
||||
def __init__(self, config: EmbeddingConfig):
|
||||
self.config = config
|
||||
self.client = None
|
||||
|
||||
def _get_url(self):
|
||||
return f"http://{self.config.host}:{self.config.port}"
|
||||
|
||||
|
||||
def init(self, wait_for_ready: bool = True):
|
||||
self.client = aiohttp.ClientSession()
|
||||
|
||||
async def close(self):
|
||||
if self.client:
|
||||
await self.client.close()
|
||||
self.client = None
|
||||
|
||||
async def aembed_documents(self, texts: list) -> list:
|
||||
if not self.client:
|
||||
self.init(wait_for_ready=False)
|
||||
|
||||
url = f"{self._get_url()}/embed"
|
||||
payload = {
|
||||
"inputs": texts,
|
||||
"parameters": {"truncate": True}
|
||||
}
|
||||
|
||||
async with self.client.post(url, json=payload) as response:
|
||||
result = await response.json()
|
||||
if isinstance(result, list):
|
||||
return result
|
||||
return result.get("embeddings", [])
|
||||
|
||||
async def aembed_query(self, text: str) -> list:
|
||||
embeddings = await self.aembed_documents([text])
|
||||
return embeddings[0] if embeddings else []
|
||||
|
||||
|
||||
embedding_client = EmbeddingClientManager(app_config.embedding)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Testing EmbeddingClientManager...")
|
||||
|
||||
async def test():
|
||||
try:
|
||||
embedding_client.init(wait_for_ready=True)
|
||||
print("Initialization successful")
|
||||
|
||||
text = "What is Deep Learning?"
|
||||
print(f"Test text: {text}")
|
||||
|
||||
result = await embedding_client.aembed_query(text)
|
||||
print(f"Embedding successful")
|
||||
print(f"Vector length: {len(result)}")
|
||||
print(f"First 10 values: {result[:10]}")
|
||||
|
||||
await embedding_client.close()
|
||||
except Exception as e:
|
||||
print(f"Test failed: {str(e)}")
|
||||
|
||||
asyncio.run(test())
|
||||
59
app/client/milvus_client_manager.py
Normal file
59
app/client/milvus_client_manager.py
Normal file
@ -0,0 +1,59 @@
|
||||
from typing import Optional
|
||||
|
||||
from pymilvus import MilvusClient as PyMilvusClient
|
||||
|
||||
from app.conf.app_config import MilvusConfig, app_config
|
||||
|
||||
|
||||
class MilvusClientWrapper:
|
||||
"""Milvus 客户端单例包装器"""
|
||||
_instance = None
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls, milvus_config: MilvusConfig = None):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(MilvusClientWrapper, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, milvus_config: MilvusConfig = None):
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
if milvus_config is None:
|
||||
milvus_config = app_config.milvus
|
||||
|
||||
self.milvus_config = milvus_config
|
||||
self.client: Optional[PyMilvusClient] = None
|
||||
self._initialized = True
|
||||
|
||||
def init(self):
|
||||
if self.client is not None:
|
||||
return
|
||||
|
||||
uri = f"http://{self.milvus_config.host}:{self.milvus_config.port}"
|
||||
self.client = PyMilvusClient(
|
||||
uri=uri,
|
||||
user=self.milvus_config.user,
|
||||
password=self.milvus_config.password
|
||||
)
|
||||
|
||||
def close(self):
|
||||
if self.client is not None:
|
||||
self.client.close()
|
||||
self.client = None
|
||||
|
||||
def has_collection(self, collection_name: str) -> bool:
|
||||
self._ensure_connection()
|
||||
return self.client.has_collection(collection_name)
|
||||
|
||||
def drop_collection(self, collection_name: str):
|
||||
self._ensure_connection()
|
||||
if self.client.has_collection(collection_name):
|
||||
self.client.drop_collection(collection_name)
|
||||
|
||||
def _ensure_connection(self):
|
||||
if self.client is None:
|
||||
self.init()
|
||||
|
||||
|
||||
milvus_client = MilvusClientWrapper()
|
||||
45
app/client/mysql_client_manager.py
Normal file
45
app/client/mysql_client_manager.py
Normal file
@ -0,0 +1,45 @@
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, async_sessionmaker
|
||||
|
||||
from app.conf.app_config import DBConfig, app_config
|
||||
|
||||
|
||||
class MysqlClientManager:
|
||||
def __init__(self, db_config: DBConfig):
|
||||
self.db_config = db_config
|
||||
self.engine: Optional[AsyncEngine] = None
|
||||
self.session_factory = None
|
||||
|
||||
def _get_url(self):
|
||||
return f"mysql+asyncmy://{self.db_config.user}:{self.db_config.password}@{self.db_config.host}:{self.db_config.port}/{self.db_config.database}?charset=utf8mb4"
|
||||
|
||||
def init(self):
|
||||
self.engine = create_async_engine(url=self._get_url(),
|
||||
pool_size=10,
|
||||
pool_pre_ping=True)
|
||||
self.session_factory = async_sessionmaker(db_assistant_mysql_client_manager.engine,
|
||||
autoflush=True,
|
||||
expire_on_commit=False)
|
||||
|
||||
async def close(self):
|
||||
await self.engine.dispose()
|
||||
|
||||
|
||||
db_assistant_mysql_client_manager = MysqlClientManager(app_config.db_assistant)
|
||||
|
||||
if __name__ == '__main__':
|
||||
db_assistant_mysql_client_manager.init()
|
||||
|
||||
|
||||
async def test():
|
||||
async with db_assistant_mysql_client_manager.session_factory() as session:
|
||||
result = await session.execute(text("select * from archive_messages limit 10"))
|
||||
rows = result.mappings().fetchall()
|
||||
print(type(rows[0]))
|
||||
print(rows)
|
||||
|
||||
|
||||
asyncio.run(test())
|
||||
0
app/conf/__init__.py
Normal file
0
app/conf/__init__.py
Normal file
BIN
app/conf/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/conf/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/conf/__pycache__/app_config.cpython-312.pyc
Normal file
BIN
app/conf/__pycache__/app_config.cpython-312.pyc
Normal file
Binary file not shown.
72
app/conf/app_config.py
Normal file
72
app/conf/app_config.py
Normal file
@ -0,0 +1,72 @@
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
|
||||
# 日志配置
|
||||
@dataclass
|
||||
class File:
|
||||
enable: bool
|
||||
level: str
|
||||
path: str
|
||||
rotation: str
|
||||
retention: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Console:
|
||||
enable: bool
|
||||
level: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoggingConfig:
|
||||
file: File
|
||||
console: Console
|
||||
|
||||
# 数据库配置
|
||||
@dataclass
|
||||
class DBConfig:
|
||||
host: str
|
||||
port: int
|
||||
user: str
|
||||
password: str
|
||||
database: str
|
||||
|
||||
@dataclass
|
||||
class EmbeddingConfig:
|
||||
host: str
|
||||
port: int
|
||||
model: str
|
||||
|
||||
@dataclass
|
||||
class MilvusConfig:
|
||||
host: str
|
||||
port: int
|
||||
user: str
|
||||
password: str
|
||||
embedding_size: int
|
||||
|
||||
@dataclass
|
||||
class LLMConfig:
|
||||
model_name: str
|
||||
api_key: str
|
||||
base_url: str
|
||||
|
||||
@dataclass
|
||||
class AppConfig:
|
||||
logging: LoggingConfig
|
||||
db_assistant: DBConfig
|
||||
embedding: EmbeddingConfig
|
||||
llm: LLMConfig
|
||||
milvus: MilvusConfig
|
||||
|
||||
|
||||
config_file = Path(__file__).parents[2] / 'conf' / 'app_config.yaml'
|
||||
context = OmegaConf.load(config_file)
|
||||
schema = OmegaConf.structured(AppConfig)
|
||||
app_config: AppConfig = OmegaConf.to_object(OmegaConf.merge(schema, context))
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(app_config.db_assistant.host)
|
||||
0
app/dto/__init__.py
Normal file
0
app/dto/__init__.py
Normal file
BIN
app/dto/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/dto/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/dto/__pycache__/summary_dto.cpython-312.pyc
Normal file
BIN
app/dto/__pycache__/summary_dto.cpython-312.pyc
Normal file
Binary file not shown.
9
app/dto/summary_dto.py
Normal file
9
app/dto/summary_dto.py
Normal file
@ -0,0 +1,9 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class SummaryDto:
|
||||
msg_date: str
|
||||
from_user: str
|
||||
to_user: str
|
||||
summary: str
|
||||
5
app/llm.py
Normal file
5
app/llm.py
Normal file
@ -0,0 +1,5 @@
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
from app.conf.app_config import app_config
|
||||
|
||||
llm = init_chat_model(model=app_config.llm.model_name, api_key=app_config.llm.api_key, base_url=app_config.llm.base_url ,temperature=1,extra_body={"thinking": {"type": "disabled"}},)
|
||||
0
app/models/__init__.py
Normal file
0
app/models/__init__.py
Normal file
BIN
app/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
3
app/models/mysql/__init__.py
Normal file
3
app/models/mysql/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .archive_messages import ArchiveMessages
|
||||
|
||||
__all__ = ["ArchiveMessages"]
|
||||
BIN
app/models/mysql/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/models/mysql/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/models/mysql/__pycache__/archive_messages.cpython-312.pyc
Normal file
BIN
app/models/mysql/__pycache__/archive_messages.cpython-312.pyc
Normal file
Binary file not shown.
64
app/models/mysql/archive_messages.py
Normal file
64
app/models/mysql/archive_messages.py
Normal file
@ -0,0 +1,64 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, String, Text, Integer, TIMESTAMP
|
||||
from sqlalchemy.orm import DeclarativeBase, mapped_column
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
class ArchiveMessages(Base):
|
||||
|
||||
|
||||
__tablename__ = 'archive_messages'
|
||||
__table_args__ = {
|
||||
'comment': '企微会话存档消息表'
|
||||
}
|
||||
|
||||
id = mapped_column(BigInteger, primary_key=True, autoincrement=True, comment='自增主键')
|
||||
msgid = mapped_column(String(255), nullable=False, comment='企微消息唯一ID')
|
||||
seq = mapped_column(BigInteger, nullable=False, comment='存档序列号,用于增量拉取')
|
||||
corp_id = mapped_column(String(64), nullable=False, comment='企业ID')
|
||||
action = mapped_column(String(32), default='send', comment='send/recall/agree/disagree')
|
||||
from_user = mapped_column(String(128), nullable=False, comment='发送者userid')
|
||||
from_role = mapped_column(String(32), nullable=False, comment='发送者角色: INTERNAL-企业内部成员 EXTERNAL-外部联系人 SYSTEM-系统')
|
||||
to_user = mapped_column(String(128), nullable=True, comment='接收者userid(单聊)')
|
||||
tolist = mapped_column(Text, nullable=True, comment='接收者列表(群聊),JSON数组字符串')
|
||||
roomid = mapped_column(String(255), nullable=True, comment='群聊ID(群聊时有值)')
|
||||
msgtype = mapped_column(String(64), nullable=False, comment='text/image/voice/video/file/link/location/weapp/chatrecord/voip')
|
||||
msgtime = mapped_column(BigInteger, nullable=False, comment='消息发送时间,UTC毫秒时间戳')
|
||||
content = mapped_column(Text, nullable=True, comment='文本消息内容(长文本使用LONGTEXT)')
|
||||
media_data = mapped_column(Text, nullable=True, comment='媒体消息元数据(sdkfileid/文件大小/时长等),JSON字符串')
|
||||
session_id = mapped_column(String(128), nullable=True, comment='关联的会话ID(业务生成)')
|
||||
decrypt_status = mapped_column(Integer, default=1, comment='解密状态: 1成功 2失败')
|
||||
decrypt_error = mapped_column(String(512), nullable=True, comment='解密失败原因')
|
||||
created_at = mapped_column(TIMESTAMP, nullable=False, default=datetime.now, comment='创建时间')
|
||||
updated_at = mapped_column(TIMESTAMP, nullable=False, default=datetime.now, onupdate=datetime.now, comment='更新时间')
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'msgid': self.msgid,
|
||||
'seq': self.seq,
|
||||
'corp_id': self.corp_id,
|
||||
'action': self.action,
|
||||
'from_user': self.from_user,
|
||||
'from_role': self.from_role,
|
||||
'to_user': self.to_user,
|
||||
'tolist': self.tolist,
|
||||
'roomid': self.roomid,
|
||||
'msgtype': self.msgtype,
|
||||
'msgtime': self.msgtime,
|
||||
'content': self.content,
|
||||
'media_data': self.media_data,
|
||||
'session_id': self.session_id,
|
||||
'decrypt_status': self.decrypt_status,
|
||||
'decrypt_error': self.decrypt_error,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return json.dumps(self.to_dict())
|
||||
0
app/repository/__init__.py
Normal file
0
app/repository/__init__.py
Normal file
BIN
app/repository/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/repository/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
72
app/repository/archive_messages_repository.py
Normal file
72
app/repository/archive_messages_repository.py
Normal file
@ -0,0 +1,72 @@
|
||||
from typing import List
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.mysql import ArchiveMessages
|
||||
|
||||
|
||||
class ArchiveMessagesRepository:
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
async def get_message_statistics_by_date(self, start_date: str = None, end_date: str = None) -> List[ArchiveMessages]:
|
||||
"""
|
||||
根据日期范围获取消息记录
|
||||
|
||||
Args:
|
||||
start_date: 开始日期 (格式: YYYY-MM-DD)
|
||||
end_date: 结束日期 (格式: YYYY-MM-DD)
|
||||
|
||||
Returns:
|
||||
ArchiveMessages 对象列表
|
||||
"""
|
||||
base_query = """
|
||||
SELECT DATE(created_at) AS created_at, from_user
|
||||
FROM archive_messages
|
||||
"""
|
||||
conditions = []
|
||||
params = {}
|
||||
|
||||
if start_date:
|
||||
conditions.append(" and DATE(created_at) >= :start_date")
|
||||
params["start_date"] = start_date
|
||||
|
||||
if end_date:
|
||||
conditions.append("DATE(created_at) <= :end_date")
|
||||
params["end_date"] = end_date
|
||||
base_query += " WHERE 1=1 and roomid='' and msgtype='text' "
|
||||
#base_query += " WHERE (from_user='wmI1AkDQAA3h0jxrRpeaHZhMKeHExA4w' )or( from_user='LiHeYi' and to_user='[\"wmI1AkDQAA3h0jxrRpeaHZhMKeHExA4w\"]') and roomid='' and msgtype='text' "
|
||||
if conditions:
|
||||
base_query += " AND ".join(conditions)+" "
|
||||
base_query += " GROUP BY DATE(created_at), from_user ORDER BY created_at DESC, from_user;"
|
||||
|
||||
query = text(base_query)
|
||||
result = await self.session.execute(query, params)
|
||||
return [ArchiveMessages(**dict(row)) for row in result.mappings().fetchall()]
|
||||
|
||||
|
||||
async def get_message_statistics_by_date_user(self, day: str = None, from_user: str = None, to_user: str = None) -> List[ArchiveMessages]:
|
||||
"""
|
||||
根据日期+发送人+接收人查询记录
|
||||
Returns:
|
||||
ArchiveMessages 对象列表
|
||||
"""
|
||||
if to_user:
|
||||
sql = """
|
||||
SELECT * FROM archive_messages
|
||||
WHERE DATE(created_at) = :day
|
||||
AND from_user = :from_user
|
||||
AND to_user = :to_user and msgtype='text'
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
result = await self.session.execute(text(sql), {"day": day, "from_user": from_user, "to_user": to_user})
|
||||
else :
|
||||
sql = """
|
||||
SELECT from_user,to_user FROM archive_messages
|
||||
WHERE DATE(created_at) = :day
|
||||
AND from_user = :from_user and msgtype='text'
|
||||
group by to_user
|
||||
"""
|
||||
result = await self.session.execute(text(sql), {"day": day, "from_user": from_user})
|
||||
return [ArchiveMessages(**dict(row)) for row in result.mappings().fetchall()]
|
||||
0
app/repository/milvus/__init__.py
Normal file
0
app/repository/milvus/__init__.py
Normal file
BIN
app/repository/milvus/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/repository/milvus/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
156
app/repository/milvus/summary_repository.py
Normal file
156
app/repository/milvus/summary_repository.py
Normal file
@ -0,0 +1,156 @@
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from pymilvus import MilvusClient, DataType
|
||||
|
||||
from app.client.milvus_client_manager import milvus_client
|
||||
from app.conf.app_config import app_config
|
||||
|
||||
|
||||
class SummaryRepository:
|
||||
collection_name: str = 'message_summary'
|
||||
|
||||
def __init__(self):
|
||||
self.dim = app_config.milvus.embedding_size
|
||||
self._ensure_connection()
|
||||
|
||||
def _ensure_connection(self):
|
||||
if milvus_client.client is None:
|
||||
milvus_client.init()
|
||||
|
||||
def create_collection(self, dim: int = None) -> bool:
|
||||
"""
|
||||
创建消息摘要集合
|
||||
|
||||
Args:
|
||||
dim: 向量维度,默认从配置读取
|
||||
|
||||
Returns:
|
||||
是否成功创建(已存在返回False)
|
||||
"""
|
||||
if dim is not None:
|
||||
self.dim = dim
|
||||
|
||||
if milvus_client.has_collection(self.collection_name):
|
||||
return False
|
||||
|
||||
schema = milvus_client.client.create_schema(
|
||||
auto_id=True,
|
||||
enable_dynamic_field=True,
|
||||
description="消息摘要向量集合"
|
||||
)
|
||||
|
||||
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True)
|
||||
schema.add_field(field_name="msg_time", datatype=DataType.VARCHAR, max_length=100)
|
||||
schema.add_field(field_name="msg_type", datatype=DataType.INT8)
|
||||
schema.add_field(field_name="from_user", datatype=DataType.VARCHAR, max_length=150)
|
||||
schema.add_field(field_name="to_user", datatype=DataType.VARCHAR, max_length=150)
|
||||
schema.add_field(field_name="room_id", datatype=DataType.VARCHAR, max_length=150)
|
||||
schema.add_field(field_name="message_context", datatype=DataType.VARCHAR, max_length=65535)
|
||||
schema.add_field(field_name="summary", datatype=DataType.VARCHAR, max_length=65535)
|
||||
schema.add_field(field_name="message_dense_vector", datatype=DataType.FLOAT_VECTOR, dim=self.dim)
|
||||
schema.add_field(field_name="summary_dense_vector", datatype=DataType.FLOAT_VECTOR, dim=self.dim)
|
||||
schema.add_field(field_name="created_at", datatype=DataType.INT64)
|
||||
|
||||
index_params = milvus_client.client.prepare_index_params()
|
||||
index_params.add_index(
|
||||
field_name="message_dense_vector",
|
||||
index_type="HNSW",
|
||||
metric_type="COSINE",
|
||||
params={"M": 32, "efConstruction": 300}
|
||||
)
|
||||
index_params.add_index(
|
||||
field_name="summary_dense_vector",
|
||||
index_type="HNSW",
|
||||
metric_type="COSINE",
|
||||
params={"M": 32, "efConstruction": 300}
|
||||
)
|
||||
milvus_client.client.create_collection(
|
||||
collection_name=self.collection_name,
|
||||
schema=schema,
|
||||
index_params=index_params
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def insert(self, msg_time: List[str], msg_type: List[int], from_user: List[str],
|
||||
to_user: List[str], room_id: List[str],message_context: List[str], summary: List[str], message_dense_vector: List[List[float]],
|
||||
summary_dense_vector: List[List[float]]) -> List[int]:
|
||||
"""
|
||||
插入消息摘要数据
|
||||
|
||||
Args:
|
||||
msg_time: 消息时间列表
|
||||
msg_type: 消息类型列表
|
||||
from_user: 发送者列表
|
||||
to_user: 接收者列表
|
||||
room_id: 群聊ID列表
|
||||
summary: 消息摘要列表
|
||||
dense_vector: 向量列表
|
||||
|
||||
Returns:
|
||||
插入的数据ID列表
|
||||
"""
|
||||
self._ensure_connection()
|
||||
self.create_collection()
|
||||
import time
|
||||
created_at = [int(time.time() * 1000)] * len(msg_time)
|
||||
|
||||
data = []
|
||||
for i in range(len(msg_time)):
|
||||
data.append({
|
||||
"msg_time": msg_time[i],
|
||||
"msg_type": msg_type[i],
|
||||
"from_user": from_user[i],
|
||||
"to_user": to_user[i],
|
||||
"room_id": room_id[i],
|
||||
"message_context": message_context[i],
|
||||
"summary": summary[i],
|
||||
"message_dense_vector": message_dense_vector[i],
|
||||
"summary_dense_vector": summary_dense_vector[i],
|
||||
"created_at": created_at[i]
|
||||
})
|
||||
|
||||
result = milvus_client.client.insert(
|
||||
collection_name=self.collection_name,
|
||||
data=data
|
||||
)
|
||||
|
||||
milvus_client.client.flush(self.collection_name)
|
||||
return result.get("ids", [])
|
||||
|
||||
def delete(self, expr: str):
|
||||
"""
|
||||
删除数据
|
||||
|
||||
Args:
|
||||
expr: 删除条件表达式
|
||||
"""
|
||||
self._ensure_connection()
|
||||
|
||||
milvus_client.client.delete(
|
||||
collection_name=self.collection_name,
|
||||
filter=expr
|
||||
)
|
||||
milvus_client.client.flush(self.collection_name)
|
||||
|
||||
def drop_collection(self):
|
||||
"""删除集合"""
|
||||
milvus_client.drop_collection(self.collection_name)
|
||||
|
||||
def get_collection_stats(self) -> Dict[str, Any]:
|
||||
"""获取集合统计信息"""
|
||||
self._ensure_connection()
|
||||
|
||||
try:
|
||||
result = milvus_client.client.get_collection_stats(collection_name=self.collection_name)
|
||||
return {
|
||||
"num_entities": result.get("row_count", 0),
|
||||
"collection_name": self.collection_name
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"获取统计信息失败: {e}")
|
||||
return {"num_entities": 0, "collection_name": self.collection_name}
|
||||
|
||||
def close(self):
|
||||
"""关闭连接"""
|
||||
milvus_client.close()
|
||||
4
app/summary/__init__.py
Normal file
4
app/summary/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .router import router as summary_router
|
||||
from .service import SummaryService
|
||||
|
||||
__all__ = ["summary_router", "SummaryService"]
|
||||
26
app/summary/router.py
Normal file
26
app/summary/router.py
Normal file
@ -0,0 +1,26 @@
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from app.summary.service import SummaryService
|
||||
|
||||
router = APIRouter(prefix="/summary", tags=["summary"])
|
||||
|
||||
|
||||
@router.get("/message-statistics", response_model=List[Dict[str, Any]])
|
||||
async def get_message_statistics(
|
||||
start_date: Optional[str] = Query(None, description="开始日期 (格式: YYYY-MM-DD)"),
|
||||
end_date: Optional[str] = Query(None, description="结束日期 (格式: YYYY-MM-DD)")
|
||||
):
|
||||
"""
|
||||
获取按日期和发送者分组的消息统计数据
|
||||
|
||||
SQL逻辑:
|
||||
SELECT DATE(created_at) AS created_at, from_user
|
||||
FROM archive_messages
|
||||
GROUP BY DATE(created_at), from_user
|
||||
ORDER BY created_at DESC;
|
||||
"""
|
||||
if start_date or end_date:
|
||||
return await SummaryService.get_message_statistics_by_date(start_date, end_date)
|
||||
return await SummaryService.get_message_statistics()
|
||||
180
app/summary/service.py
Normal file
180
app/summary/service.py
Normal file
@ -0,0 +1,180 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
from langchain_core.prompts import PromptTemplate
|
||||
|
||||
from app.client.embedding_client_manager import EmbeddingClientManager, embedding_client
|
||||
from app.client.milvus_client_manager import milvus_client
|
||||
from app.client.mysql_client_manager import db_assistant_mysql_client_manager
|
||||
from app.dto.summary_dto import SummaryDto
|
||||
from app.llm import llm
|
||||
from app.models.mysql import ArchiveMessages
|
||||
from app.repository.archive_messages_repository import ArchiveMessagesRepository
|
||||
from app.repository.milvus.summary_repository import SummaryRepository
|
||||
|
||||
|
||||
class SummaryService:
|
||||
|
||||
def __init__(self, archive_messages_repository: ArchiveMessagesRepository,
|
||||
embedding_client: EmbeddingClientManager,
|
||||
summary_repository: SummaryRepository):
|
||||
self.archive_messages_repository = archive_messages_repository
|
||||
self.embedding_client = embedding_client
|
||||
self.summary_repository = summary_repository
|
||||
|
||||
async def get_message_summary(self, date: str = None):
|
||||
stime = time.time()
|
||||
if date:
|
||||
day_ArchiveMessages = await self.archive_messages_repository.get_message_statistics_by_date(start_date=date,
|
||||
end_date=date)
|
||||
print(f"{date}共产生了{len(day_ArchiveMessages)}条会话")
|
||||
else:
|
||||
day_ArchiveMessages = await self.archive_messages_repository.get_message_statistics_by_date()
|
||||
|
||||
# 改成map
|
||||
date_user_message_a_set = set()
|
||||
for idx, day_message in enumerate(day_ArchiveMessages, 1): #打印计数
|
||||
print(f"处理第{idx}会话")
|
||||
day_date = str(day_message.created_at)
|
||||
user_a = day_message.from_user
|
||||
# 找出发送人相关的接收人信息
|
||||
date_user_message_a = await self.archive_messages_repository.get_message_statistics_by_date_user(day_date,
|
||||
user_a)
|
||||
|
||||
coro_list = []
|
||||
# 找出发送人与接收人 相关的所有会话信息
|
||||
for date_a in date_user_message_a:
|
||||
|
||||
date_user_b = date_a.to_user
|
||||
# if date_user_b != "[\"wmI1AkDQAA3h0jxrRpeaHZhMKeHExA4w\"]":
|
||||
# continue
|
||||
|
||||
date_user_a = date_a.from_user
|
||||
date_user_b_clean = date_user_b.replace("[\"", "").replace("\"]", "")
|
||||
date_user_a_append = "[\"" + date_user_a + "\"]"
|
||||
# 判断只要存在就跳过
|
||||
if date_user_a + date_user_b in date_user_message_a_set or date_user_b_clean + date_user_a_append in date_user_message_a_set:
|
||||
continue
|
||||
print(f"查询{date_user_a}和{date_user_b}对话开始")
|
||||
list1 = await self.archive_messages_repository.get_message_statistics_by_date_user(day_date,
|
||||
date_user_a,
|
||||
date_user_b)
|
||||
|
||||
list2 = await self.archive_messages_repository.get_message_statistics_by_date_user(day_date,
|
||||
date_user_b_clean,
|
||||
date_user_a_append)
|
||||
# 查询过的聊天账号,后续不再查询
|
||||
date_user_message_a_set.add(date_user_a + date_user_b)
|
||||
date_user_message_a_set.add(date_user_b_clean + date_user_a_append)
|
||||
coro_list = list1 + list2
|
||||
coro_list.sort(key=lambda x: x.created_at, reverse=False)
|
||||
print(f"查询{date_user_a}和{date_user_b}对话共:{len(coro_list)}")
|
||||
# 保留语义骨架,去除闲聊填充
|
||||
filter_gossip_message_res = await self.filter_gossip_message(coro_list)
|
||||
filter_gossip_message_text = str(filter_gossip_message_res.content) if hasattr(
|
||||
filter_gossip_message_res, 'content') else str(filter_gossip_message_res)
|
||||
print(f"查询{date_user_a}和{date_user_b}对话进行保留语义骨架,去除闲聊填充")
|
||||
# 摘要
|
||||
summary_res = await self.date_message_summary(coro_list)
|
||||
summary_text = str(summary_res.content) if hasattr(summary_res, 'content') else str(summary_res)
|
||||
print(f"查询{date_user_a}和{date_user_b}对话进行摘要")
|
||||
# 摘要向量
|
||||
batch_embeddings = await self.embedding_client.aembed_documents(
|
||||
[summary_text, filter_gossip_message_text])
|
||||
|
||||
#
|
||||
try:
|
||||
self.summary_repository.insert(
|
||||
msg_time=[day_date],
|
||||
msg_type=[0],
|
||||
from_user=[date_a.from_user],
|
||||
to_user=[date_user_b_clean],
|
||||
room_id=[date_a.roomid or ""],
|
||||
message_context=[filter_gossip_message_text],
|
||||
summary=[summary_text],
|
||||
message_dense_vector=[batch_embeddings[1]],
|
||||
summary_dense_vector=[batch_embeddings[0]]
|
||||
)
|
||||
print(f"查询{date_user_a}和{date_user_b}对话成功入 库")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(f"运行结束:{(time.time()-stime)}")
|
||||
|
||||
async def filter_gossip_message(self, coro_list: List[ArchiveMessages]) -> str:
|
||||
prompt = """
|
||||
保留语义骨架,去除闲聊填充
|
||||
# 历史对话
|
||||
{message_str}
|
||||
其中INTERNAL表示销售,EXTERNAL表示用户
|
||||
# 例如
|
||||
原文:
|
||||
用户:在吗?
|
||||
销售:在的,您好,请问有什么可以帮您?
|
||||
用户:我想问一下,那个,我的订单怎么还没发货啊?
|
||||
销售:好的,请问您的订单号是多少呢?
|
||||
用户:订单号是 12345,我昨天就下单了。
|
||||
销售:我查一下,稍等... 您的订单正在打包中,预计今天发出。
|
||||
|
||||
结构化提取后:
|
||||
[用户] 咨询订单发货状态,订单号 12345,昨日下单。
|
||||
[销售] 查询后告知正在打包,预计今日发出。
|
||||
"""
|
||||
lines = [f"{msg.created_at} {msg.from_role}:{msg.content}" for msg in coro_list]
|
||||
message_str = "\n".join(lines)
|
||||
|
||||
prompt_template = PromptTemplate(template=prompt, input_variables=["message_str"])
|
||||
chain = prompt_template | llm
|
||||
result = await chain.ainvoke({"message_str": message_str})
|
||||
print(result)
|
||||
return result
|
||||
|
||||
async def date_message_summary(self, coro_list: List[ArchiveMessages]) -> str:
|
||||
prompt = """
|
||||
# 聊天历史轻量化摘要任务
|
||||
任务:生成极简轮次摘要,用于替换原始对话节省上下文窗口
|
||||
# 历史对话
|
||||
{message_str}
|
||||
## 说明
|
||||
其中INTERNAL表示销售,EXTERNAL表示客户
|
||||
输出格式示例(无多余文字):
|
||||
【日期】 202X年X月X日
|
||||
** 核心诉求**
|
||||
用户反馈订单 #12345 物流停滞超7天未更新。
|
||||
询问是否支持跨店满减叠加优惠券。
|
||||
** 处理结果**
|
||||
已联系快递网点核实,包裹因暴雨滞留,预计明日送达。(已安抚情绪并补偿10元无门槛券)
|
||||
明确告知当前活动规则不支持叠加,引导领取店铺专属券。
|
||||
** 待办事项 **
|
||||
[明日 10:00] 跟进物流签收状态并回访用户。
|
||||
** 关键标签**:#物流异常#优惠规则咨询#情绪安抚
|
||||
"""
|
||||
lines = [f"{msg.created_at} {msg.from_role}:{msg.content}" for msg in coro_list]
|
||||
message_str = "\n".join(lines)
|
||||
|
||||
prompt_template = PromptTemplate(template=prompt, input_variables=["message_str"])
|
||||
chain = prompt_template | llm
|
||||
result = await chain.ainvoke({"message_str": message_str})
|
||||
print(result)
|
||||
return result
|
||||
|
||||
|
||||
async def build():
|
||||
db_assistant_mysql_client_manager.init()
|
||||
embedding_client.init()
|
||||
milvus_client.init()
|
||||
try:
|
||||
async with db_assistant_mysql_client_manager.session_factory() as db_assistant:
|
||||
archive_messages_repository = ArchiveMessagesRepository(db_assistant)
|
||||
summary_repository = SummaryRepository()
|
||||
service = SummaryService(archive_messages_repository, embedding_client, summary_repository)
|
||||
await service.get_message_summary("2026-6-11")
|
||||
finally:
|
||||
await embedding_client.close()
|
||||
await db_assistant_mysql_client_manager.close()
|
||||
milvus_client.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(build())
|
||||
|
||||
88
app/summary/task.py
Normal file
88
app/summary/task.py
Normal file
@ -0,0 +1,88 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
|
||||
from app.client.mysql_client_manager import db_assistant_mysql_client_manager
|
||||
from app.repository.archive_messages_repository import ArchiveMessagesRepository
|
||||
from app.summary.service import SummaryService
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[logging.FileHandler('summary_task.log'), logging.StreamHandler()]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def generate_daily_summary(date: Optional[str] = None):
|
||||
"""
|
||||
生成指定日期的消息摘要,如果不传日期则生成前一天的摘要
|
||||
|
||||
Args:
|
||||
date: 日期字符串,格式 YYYY-MM-DD,不传则默认前一天
|
||||
"""
|
||||
if not date:
|
||||
yesterday = datetime.now() - timedelta(days=1)
|
||||
date = yesterday.strftime("%Y-%m-%d")
|
||||
|
||||
logger.info(f"开始生成 {date} 的消息摘要...")
|
||||
|
||||
try:
|
||||
db_assistant_mysql_client_manager.init()
|
||||
async with db_assistant_mysql_client_manager.session_factory() as db_assistant:
|
||||
archive_messages_repository = ArchiveMessagesRepository(db_assistant)
|
||||
service = SummaryService(archive_messages_repository)
|
||||
summaries = await service.get_message_summary(date)
|
||||
|
||||
logger.info(f"生成完成,共生成 {len(summaries)} 条摘要")
|
||||
for summary in summaries:
|
||||
logger.info(f"日期: {summary.date}, 发送人: {summary.from_user}, 接收人: {summary.to_user}")
|
||||
|
||||
return summaries
|
||||
except Exception as e:
|
||||
logger.error(f"生成摘要失败: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def start_scheduler():
|
||||
"""
|
||||
启动定时任务调度器,每天凌晨2点执行
|
||||
"""
|
||||
scheduler = AsyncIOScheduler(timezone='Asia/Shanghai')
|
||||
|
||||
scheduler.add_job(
|
||||
generate_daily_summary,
|
||||
trigger='cron',
|
||||
hour=2,
|
||||
minute=0,
|
||||
second=0,
|
||||
id='daily_summary_task',
|
||||
name='每日消息摘要任务',
|
||||
replace_existing=True
|
||||
)
|
||||
|
||||
logger.info("定时任务调度器已启动,每天凌晨2:00执行消息摘要任务")
|
||||
|
||||
scheduler.start()
|
||||
|
||||
try:
|
||||
asyncio.get_event_loop().run_forever()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
logger.info("正在关闭定时任务调度器...")
|
||||
scheduler.shutdown()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
# 如果传入了日期参数,直接生成指定日期的摘要
|
||||
target_date = sys.argv[1]
|
||||
asyncio.run(generate_daily_summary(target_date))
|
||||
else:
|
||||
# 否则启动定时任务调度器
|
||||
start_scheduler()
|
||||
35
conf/app_config.yaml
Normal file
35
conf/app_config.yaml
Normal file
@ -0,0 +1,35 @@
|
||||
logging:
|
||||
file:
|
||||
enable: true
|
||||
level: INFO
|
||||
path: logs
|
||||
rotation: "10 MB"
|
||||
retention: "7 days"
|
||||
console:
|
||||
enable: true
|
||||
level: INFO
|
||||
|
||||
db_assistant:
|
||||
host: sh-cdb-6fzlwnms.sql.tencentcdb.com
|
||||
port: 63912
|
||||
user: assistant_prod
|
||||
password: U%$4Tu_C3+4
|
||||
database: ai_assistant
|
||||
|
||||
embedding:
|
||||
host: localhost
|
||||
port: 8081
|
||||
model: BAAI/bge-large-zh-v1.5
|
||||
|
||||
|
||||
milvus:
|
||||
host: localhost
|
||||
port: 19530
|
||||
user: root
|
||||
password: Milvus
|
||||
embedding_size: 1024
|
||||
|
||||
llm:
|
||||
model_name: deepseek-v4-flash
|
||||
api_key: sk-8edc9f25d59643b3b08efea5d81c55a9
|
||||
base_url: https://api.deepseek.com
|
||||
0
conf/sql.sql
Normal file
0
conf/sql.sql
Normal file
80
docker/docker-compose.yml
Normal file
80
docker/docker-compose.yml
Normal file
@ -0,0 +1,80 @@
|
||||
services:
|
||||
etcd:
|
||||
container_name: milvus-etcd
|
||||
image: quay.io/coreos/etcd:v3.5.18
|
||||
environment:
|
||||
- ETCD_AUTO_COMPACTION_MODE=revision
|
||||
- ETCD_AUTO_COMPACTION_RETENTION=1000
|
||||
- ETCD_QUOTA_BACKEND_BYTES=4294967296
|
||||
- ETCD_SNAPSHOT_COUNT=50000
|
||||
volumes:
|
||||
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
|
||||
# 核心修改:127.0.0.1 → etcd
|
||||
command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
|
||||
healthcheck:
|
||||
test: ["CMD", "etcdctl", "endpoint", "health"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
|
||||
minio:
|
||||
container_name: milvus-minio
|
||||
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: minioadmin
|
||||
MINIO_SECRET_KEY: minioadmin
|
||||
ports:
|
||||
- "9001:9001"
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
|
||||
command: minio server /minio_data --console-address ":9001"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
|
||||
standalone:
|
||||
container_name: milvus-standalone
|
||||
# 修正镜像tag,删除多余v
|
||||
image: milvusdb/milvus:v2.5.5
|
||||
command: ["milvus", "run", "standalone"]
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
environment:
|
||||
ETCD_ENDPOINTS: etcd:2379
|
||||
MINIO_ADDRESS: minio:9000
|
||||
volumes:
|
||||
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
|
||||
interval: 30s
|
||||
start_period: 90s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
ports:
|
||||
- "19530:19530"
|
||||
- "9091:9091"
|
||||
depends_on:
|
||||
- "etcd"
|
||||
- "minio"
|
||||
|
||||
# Hugging Face TEI 引擎 用 Rust + C 张量库 推理
|
||||
embedding:
|
||||
image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.8
|
||||
container_name: embedding
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8081:80"
|
||||
environment:
|
||||
MODEL_ID: /models/bge-large-zh-v1.5
|
||||
MAX_CONCURRENT_REQUESTS: "16"
|
||||
MAX_BATCH_TOKENS: "16384"
|
||||
volumes:
|
||||
- ./embedding/bge-large-zh-v1.5:/models/bge-large-zh-v1.5
|
||||
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: milvus
|
||||
35
docker/embedding/bge-large-zh-v1.5/.gitattributes
vendored
Normal file
35
docker/embedding/bge-large-zh-v1.5/.gitattributes
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||
*.arrow filter=lfs diff=lfs merge=lfs -text
|
||||
*.bin filter=lfs diff=lfs merge=lfs -text
|
||||
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
||||
*.ftz filter=lfs diff=lfs merge=lfs -text
|
||||
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||
*.h5 filter=lfs diff=lfs merge=lfs -text
|
||||
*.joblib filter=lfs diff=lfs merge=lfs -text
|
||||
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
||||
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
||||
*.model filter=lfs diff=lfs merge=lfs -text
|
||||
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
||||
*.npy filter=lfs diff=lfs merge=lfs -text
|
||||
*.npz filter=lfs diff=lfs merge=lfs -text
|
||||
*.onnx filter=lfs diff=lfs merge=lfs -text
|
||||
*.ot filter=lfs diff=lfs merge=lfs -text
|
||||
*.parquet filter=lfs diff=lfs merge=lfs -text
|
||||
*.pb filter=lfs diff=lfs merge=lfs -text
|
||||
*.pickle filter=lfs diff=lfs merge=lfs -text
|
||||
*.pkl filter=lfs diff=lfs merge=lfs -text
|
||||
*.pt filter=lfs diff=lfs merge=lfs -text
|
||||
*.pth filter=lfs diff=lfs merge=lfs -text
|
||||
*.rar filter=lfs diff=lfs merge=lfs -text
|
||||
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
||||
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
||||
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
||||
*.tar filter=lfs diff=lfs merge=lfs -text
|
||||
*.tflite filter=lfs diff=lfs merge=lfs -text
|
||||
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||
*.wasm filter=lfs diff=lfs merge=lfs -text
|
||||
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.zst filter=lfs diff=lfs merge=lfs -text
|
||||
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
||||
7
docker/embedding/bge-large-zh-v1.5/1_Pooling/config.json
Normal file
7
docker/embedding/bge-large-zh-v1.5/1_Pooling/config.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"word_embedding_dimension": 1024,
|
||||
"pooling_mode_cls_token": true,
|
||||
"pooling_mode_mean_tokens": false,
|
||||
"pooling_mode_max_tokens": false,
|
||||
"pooling_mode_mean_sqrt_len_tokens": false
|
||||
}
|
||||
427
docker/embedding/bge-large-zh-v1.5/README.md
Normal file
427
docker/embedding/bge-large-zh-v1.5/README.md
Normal file
@ -0,0 +1,427 @@
|
||||
---
|
||||
license: mit
|
||||
language:
|
||||
- zh
|
||||
tags:
|
||||
- sentence-transformers
|
||||
- feature-extraction
|
||||
- sentence-similarity
|
||||
- transformers
|
||||
---
|
||||
|
||||
|
||||
<h1 align="center">FlagEmbedding</h1>
|
||||
|
||||
|
||||
<h4 align="center">
|
||||
<p>
|
||||
<a href=#model-list>Model List</a> |
|
||||
<a href=#frequently-asked-questions>FAQ</a> |
|
||||
<a href=#usage>Usage</a> |
|
||||
<a href="#evaluation">Evaluation</a> |
|
||||
<a href="#train">Train</a> |
|
||||
<a href="#contact">Contact</a> |
|
||||
<a href="#citation">Citation</a> |
|
||||
<a href="#license">License</a>
|
||||
<p>
|
||||
</h4>
|
||||
|
||||
For more details please refer to our Github: [FlagEmbedding](https://github.com/FlagOpen/FlagEmbedding).
|
||||
|
||||
If you are looking for a model that supports more languages, longer texts, and other retrieval methods, you can try using [bge-m3](https://huggingface.co/BAAI/bge-m3).
|
||||
|
||||
|
||||
[English](README.md) | [中文](https://github.com/FlagOpen/FlagEmbedding/blob/master/README_zh.md)
|
||||
|
||||
FlagEmbedding focuses on retrieval-augmented LLMs, consisting of the following projects currently:
|
||||
|
||||
- **Long-Context LLM**: [Activation Beacon](https://github.com/FlagOpen/FlagEmbedding/tree/master/Long_LLM/activation_beacon)
|
||||
- **Fine-tuning of LM** : [LM-Cocktail](https://github.com/FlagOpen/FlagEmbedding/tree/master/LM_Cocktail)
|
||||
- **Dense Retrieval**: [BGE-M3](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/BGE_M3), [LLM Embedder](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/llm_embedder), [BGE Embedding](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/baai_general_embedding)
|
||||
- **Reranker Model**: [BGE Reranker](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/reranker)
|
||||
- **Benchmark**: [C-MTEB](https://github.com/FlagOpen/FlagEmbedding/tree/master/C_MTEB)
|
||||
|
||||
## News
|
||||
- 1/30/2024: Release **BGE-M3**, a new member to BGE model series! M3 stands for **M**ulti-linguality (100+ languages), **M**ulti-granularities (input length up to 8192), **M**ulti-Functionality (unification of dense, lexical, multi-vec/colbert retrieval).
|
||||
It is the first embedding model which supports all three retrieval methods, achieving new SOTA on multi-lingual (MIRACL) and cross-lingual (MKQA) benchmarks.
|
||||
[Technical Report](https://github.com/FlagOpen/FlagEmbedding/blob/master/FlagEmbedding/BGE_M3/BGE_M3.pdf) and [Code](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/BGE_M3). :fire:
|
||||
- 1/9/2024: Release [Activation-Beacon](https://github.com/FlagOpen/FlagEmbedding/tree/master/Long_LLM/activation_beacon), an effective, efficient, compatible, and low-cost (training) method to extend the context length of LLM. [Technical Report](https://arxiv.org/abs/2401.03462) :fire:
|
||||
- 12/24/2023: Release **LLaRA**, a LLaMA-7B based dense retriever, leading to state-of-the-art performances on MS MARCO and BEIR. Model and code will be open-sourced. Please stay tuned. [Technical Report](https://arxiv.org/abs/2312.15503) :fire:
|
||||
- 11/23/2023: Release [LM-Cocktail](https://github.com/FlagOpen/FlagEmbedding/tree/master/LM_Cocktail), a method to maintain general capabilities during fine-tuning by merging multiple language models. [Technical Report](https://arxiv.org/abs/2311.13534) :fire:
|
||||
- 10/12/2023: Release [LLM-Embedder](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/llm_embedder), a unified embedding model to support diverse retrieval augmentation needs for LLMs. [Technical Report](https://arxiv.org/pdf/2310.07554.pdf)
|
||||
- 09/15/2023: The [technical report](https://arxiv.org/pdf/2309.07597.pdf) and [massive training data](https://data.baai.ac.cn/details/BAAI-MTP) of BGE has been released
|
||||
- 09/12/2023: New models:
|
||||
- **New reranker model**: release cross-encoder models `BAAI/bge-reranker-base` and `BAAI/bge-reranker-large`, which are more powerful than embedding model. We recommend to use/fine-tune them to re-rank top-k documents returned by embedding models.
|
||||
- **update embedding model**: release `bge-*-v1.5` embedding model to alleviate the issue of the similarity distribution, and enhance its retrieval ability without instruction.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>More</summary>
|
||||
<!-- ### More -->
|
||||
|
||||
- 09/07/2023: Update [fine-tune code](https://github.com/FlagOpen/FlagEmbedding/blob/master/FlagEmbedding/baai_general_embedding/README.md): Add script to mine hard negatives and support adding instruction during fine-tuning.
|
||||
- 08/09/2023: BGE Models are integrated into **Langchain**, you can use it like [this](#using-langchain); C-MTEB **leaderboard** is [available](https://huggingface.co/spaces/mteb/leaderboard).
|
||||
- 08/05/2023: Release base-scale and small-scale models, **best performance among the models of the same size 🤗**
|
||||
- 08/02/2023: Release `bge-large-*`(short for BAAI General Embedding) Models, **rank 1st on MTEB and C-MTEB benchmark!** :tada: :tada:
|
||||
- 08/01/2023: We release the [Chinese Massive Text Embedding Benchmark](https://github.com/FlagOpen/FlagEmbedding/blob/master/C_MTEB) (**C-MTEB**), consisting of 31 test dataset.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Model List
|
||||
|
||||
`bge` is short for `BAAI general embedding`.
|
||||
|
||||
| Model | Language | | Description | query instruction for retrieval [1] |
|
||||
|:-------------------------------|:--------:| :--------:| :--------:|:--------:|
|
||||
| [BAAI/bge-m3](https://huggingface.co/BAAI/bge-m3) | Multilingual | [Inference](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/BGE_M3#usage) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/BGE_M3) | Multi-Functionality(dense retrieval, sparse retrieval, multi-vector(colbert)), Multi-Linguality, and Multi-Granularity(8192 tokens) | |
|
||||
| [BAAI/llm-embedder](https://huggingface.co/BAAI/llm-embedder) | English | [Inference](./FlagEmbedding/llm_embedder/README.md) [Fine-tune](./FlagEmbedding/llm_embedder/README.md) | a unified embedding model to support diverse retrieval augmentation needs for LLMs | See [README](./FlagEmbedding/llm_embedder/README.md) |
|
||||
| [BAAI/bge-reranker-large](https://huggingface.co/BAAI/bge-reranker-large) | Chinese and English | [Inference](#usage-for-reranker) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/reranker) | a cross-encoder model which is more accurate but less efficient [2] | |
|
||||
| [BAAI/bge-reranker-base](https://huggingface.co/BAAI/bge-reranker-base) | Chinese and English | [Inference](#usage-for-reranker) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/reranker) | a cross-encoder model which is more accurate but less efficient [2] | |
|
||||
| [BAAI/bge-large-en-v1.5](https://huggingface.co/BAAI/bge-large-en-v1.5) | English | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | version 1.5 with more reasonable similarity distribution | `Represent this sentence for searching relevant passages: ` |
|
||||
| [BAAI/bge-base-en-v1.5](https://huggingface.co/BAAI/bge-base-en-v1.5) | English | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | version 1.5 with more reasonable similarity distribution | `Represent this sentence for searching relevant passages: ` |
|
||||
| [BAAI/bge-small-en-v1.5](https://huggingface.co/BAAI/bge-small-en-v1.5) | English | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | version 1.5 with more reasonable similarity distribution | `Represent this sentence for searching relevant passages: ` |
|
||||
| [BAAI/bge-large-zh-v1.5](https://huggingface.co/BAAI/bge-large-zh-v1.5) | Chinese | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | version 1.5 with more reasonable similarity distribution | `为这个句子生成表示以用于检索相关文章:` |
|
||||
| [BAAI/bge-base-zh-v1.5](https://huggingface.co/BAAI/bge-base-zh-v1.5) | Chinese | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | version 1.5 with more reasonable similarity distribution | `为这个句子生成表示以用于检索相关文章:` |
|
||||
| [BAAI/bge-small-zh-v1.5](https://huggingface.co/BAAI/bge-small-zh-v1.5) | Chinese | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | version 1.5 with more reasonable similarity distribution | `为这个句子生成表示以用于检索相关文章:` |
|
||||
| [BAAI/bge-large-en](https://huggingface.co/BAAI/bge-large-en) | English | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | :trophy: rank **1st** in [MTEB](https://huggingface.co/spaces/mteb/leaderboard) leaderboard | `Represent this sentence for searching relevant passages: ` |
|
||||
| [BAAI/bge-base-en](https://huggingface.co/BAAI/bge-base-en) | English | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | a base-scale model but with similar ability to `bge-large-en` | `Represent this sentence for searching relevant passages: ` |
|
||||
| [BAAI/bge-small-en](https://huggingface.co/BAAI/bge-small-en) | English | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) |a small-scale model but with competitive performance | `Represent this sentence for searching relevant passages: ` |
|
||||
| [BAAI/bge-large-zh](https://huggingface.co/BAAI/bge-large-zh) | Chinese | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | :trophy: rank **1st** in [C-MTEB](https://github.com/FlagOpen/FlagEmbedding/tree/master/C_MTEB) benchmark | `为这个句子生成表示以用于检索相关文章:` |
|
||||
| [BAAI/bge-base-zh](https://huggingface.co/BAAI/bge-base-zh) | Chinese | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | a base-scale model but with similar ability to `bge-large-zh` | `为这个句子生成表示以用于检索相关文章:` |
|
||||
| [BAAI/bge-small-zh](https://huggingface.co/BAAI/bge-small-zh) | Chinese | [Inference](#usage-for-embedding-model) [Fine-tune](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) | a small-scale model but with competitive performance | `为这个句子生成表示以用于检索相关文章:` |
|
||||
|
||||
[1\]: If you need to search the relevant passages to a query, we suggest to add the instruction to the query; in other cases, no instruction is needed, just use the original query directly. In all cases, **no instruction** needs to be added to passages.
|
||||
|
||||
[2\]: Different from embedding model, reranker uses question and document as input and directly output similarity instead of embedding. To balance the accuracy and time cost, cross-encoder is widely used to re-rank top-k documents retrieved by other simple models.
|
||||
For examples, use bge embedding model to retrieve top 100 relevant documents, and then use bge reranker to re-rank the top 100 document to get the final top-3 results.
|
||||
|
||||
All models have been uploaded to Huggingface Hub, and you can see them at https://huggingface.co/BAAI.
|
||||
If you cannot open the Huggingface Hub, you also can download the models at https://model.baai.ac.cn/models .
|
||||
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
<details>
|
||||
<summary>1. How to fine-tune bge embedding model?</summary>
|
||||
|
||||
<!-- ### How to fine-tune bge embedding model? -->
|
||||
Following this [example](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune) to prepare data and fine-tune your model.
|
||||
Some suggestions:
|
||||
- Mine hard negatives following this [example](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune#hard-negatives), which can improve the retrieval performance.
|
||||
- If you pre-train bge on your data, the pre-trained model cannot be directly used to calculate similarity, and it must be fine-tuned with contrastive learning before computing similarity.
|
||||
- If the accuracy of the fine-tuned model is still not high, it is recommended to use/fine-tune the cross-encoder model (bge-reranker) to re-rank top-k results. Hard negatives also are needed to fine-tune reranker.
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>2. The similarity score between two dissimilar sentences is higher than 0.5</summary>
|
||||
|
||||
<!-- ### The similarity score between two dissimilar sentences is higher than 0.5 -->
|
||||
**Suggest to use bge v1.5, which alleviates the issue of the similarity distribution.**
|
||||
|
||||
Since we finetune the models by contrastive learning with a temperature of 0.01,
|
||||
the similarity distribution of the current BGE model is about in the interval \[0.6, 1\].
|
||||
So a similarity score greater than 0.5 does not indicate that the two sentences are similar.
|
||||
|
||||
For downstream tasks, such as passage retrieval or semantic similarity,
|
||||
**what matters is the relative order of the scores, not the absolute value.**
|
||||
If you need to filter similar sentences based on a similarity threshold,
|
||||
please select an appropriate similarity threshold based on the similarity distribution on your data (such as 0.8, 0.85, or even 0.9).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>3. When does the query instruction need to be used</summary>
|
||||
|
||||
<!-- ### When does the query instruction need to be used -->
|
||||
|
||||
For the `bge-*-v1.5`, we improve its retrieval ability when not using instruction.
|
||||
No instruction only has a slight degradation in retrieval performance compared with using instruction.
|
||||
So you can generate embedding without instruction in all cases for convenience.
|
||||
|
||||
For a retrieval task that uses short queries to find long related documents,
|
||||
it is recommended to add instructions for these short queries.
|
||||
**The best method to decide whether to add instructions for queries is choosing the setting that achieves better performance on your task.**
|
||||
In all cases, the documents/passages do not need to add the instruction.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Usage for Embedding Model
|
||||
|
||||
Here are some examples for using `bge` models with
|
||||
[FlagEmbedding](#using-flagembedding), [Sentence-Transformers](#using-sentence-transformers), [Langchain](#using-langchain), or [Huggingface Transformers](#using-huggingface-transformers).
|
||||
|
||||
#### Using FlagEmbedding
|
||||
```
|
||||
pip install -U FlagEmbedding
|
||||
```
|
||||
If it doesn't work for you, you can see [FlagEmbedding](https://github.com/FlagOpen/FlagEmbedding/blob/master/FlagEmbedding/baai_general_embedding/README.md) for more methods to install FlagEmbedding.
|
||||
|
||||
```python
|
||||
from FlagEmbedding import FlagModel
|
||||
sentences_1 = ["样例数据-1", "样例数据-2"]
|
||||
sentences_2 = ["样例数据-3", "样例数据-4"]
|
||||
model = FlagModel('BAAI/bge-large-zh-v1.5',
|
||||
query_instruction_for_retrieval="为这个句子生成表示以用于检索相关文章:",
|
||||
use_fp16=True) # Setting use_fp16 to True speeds up computation with a slight performance degradation
|
||||
embeddings_1 = model.encode(sentences_1)
|
||||
embeddings_2 = model.encode(sentences_2)
|
||||
similarity = embeddings_1 @ embeddings_2.T
|
||||
print(similarity)
|
||||
|
||||
# for s2p(short query to long passage) retrieval task, suggest to use encode_queries() which will automatically add the instruction to each query
|
||||
# corpus in retrieval task can still use encode() or encode_corpus(), since they don't need instruction
|
||||
queries = ['query_1', 'query_2']
|
||||
passages = ["样例文档-1", "样例文档-2"]
|
||||
q_embeddings = model.encode_queries(queries)
|
||||
p_embeddings = model.encode(passages)
|
||||
scores = q_embeddings @ p_embeddings.T
|
||||
```
|
||||
For the value of the argument `query_instruction_for_retrieval`, see [Model List](https://github.com/FlagOpen/FlagEmbedding/tree/master#model-list).
|
||||
|
||||
By default, FlagModel will use all available GPUs when encoding. Please set `os.environ["CUDA_VISIBLE_DEVICES"]` to select specific GPUs.
|
||||
You also can set `os.environ["CUDA_VISIBLE_DEVICES"]=""` to make all GPUs unavailable.
|
||||
|
||||
|
||||
#### Using Sentence-Transformers
|
||||
|
||||
You can also use the `bge` models with [sentence-transformers](https://www.SBERT.net):
|
||||
|
||||
```
|
||||
pip install -U sentence-transformers
|
||||
```
|
||||
```python
|
||||
from sentence_transformers import SentenceTransformer
|
||||
sentences_1 = ["样例数据-1", "样例数据-2"]
|
||||
sentences_2 = ["样例数据-3", "样例数据-4"]
|
||||
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
|
||||
embeddings_1 = model.encode(sentences_1, normalize_embeddings=True)
|
||||
embeddings_2 = model.encode(sentences_2, normalize_embeddings=True)
|
||||
similarity = embeddings_1 @ embeddings_2.T
|
||||
print(similarity)
|
||||
```
|
||||
For s2p(short query to long passage) retrieval task,
|
||||
each short query should start with an instruction (instructions see [Model List](https://github.com/FlagOpen/FlagEmbedding/tree/master#model-list)).
|
||||
But the instruction is not needed for passages.
|
||||
```python
|
||||
from sentence_transformers import SentenceTransformer
|
||||
queries = ['query_1', 'query_2']
|
||||
passages = ["样例文档-1", "样例文档-2"]
|
||||
instruction = "为这个句子生成表示以用于检索相关文章:"
|
||||
|
||||
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
|
||||
q_embeddings = model.encode([instruction+q for q in queries], normalize_embeddings=True)
|
||||
p_embeddings = model.encode(passages, normalize_embeddings=True)
|
||||
scores = q_embeddings @ p_embeddings.T
|
||||
```
|
||||
|
||||
#### Using Langchain
|
||||
|
||||
You can use `bge` in langchain like this:
|
||||
```python
|
||||
from langchain.embeddings import HuggingFaceBgeEmbeddings
|
||||
model_name = "BAAI/bge-large-en-v1.5"
|
||||
model_kwargs = {'device': 'cuda'}
|
||||
encode_kwargs = {'normalize_embeddings': True} # set True to compute cosine similarity
|
||||
model = HuggingFaceBgeEmbeddings(
|
||||
model_name=model_name,
|
||||
model_kwargs=model_kwargs,
|
||||
encode_kwargs=encode_kwargs,
|
||||
query_instruction="为这个句子生成表示以用于检索相关文章:"
|
||||
)
|
||||
model.query_instruction = "为这个句子生成表示以用于检索相关文章:"
|
||||
```
|
||||
|
||||
|
||||
#### Using HuggingFace Transformers
|
||||
|
||||
With the transformers package, you can use the model like this: First, you pass your input through the transformer model, then you select the last hidden state of the first token (i.e., [CLS]) as the sentence embedding.
|
||||
|
||||
```python
|
||||
from transformers import AutoTokenizer, AutoModel
|
||||
import torch
|
||||
# Sentences we want sentence embeddings for
|
||||
sentences = ["样例数据-1", "样例数据-2"]
|
||||
|
||||
# Load model from HuggingFace Hub
|
||||
tokenizer = AutoTokenizer.from_pretrained('BAAI/bge-large-zh-v1.5')
|
||||
model = AutoModel.from_pretrained('BAAI/bge-large-zh-v1.5')
|
||||
model.eval()
|
||||
|
||||
# Tokenize sentences
|
||||
encoded_input = tokenizer(sentences, padding=True, truncation=True, return_tensors='pt')
|
||||
# for s2p(short query to long passage) retrieval task, add an instruction to query (not add instruction for passages)
|
||||
# encoded_input = tokenizer([instruction + q for q in queries], padding=True, truncation=True, return_tensors='pt')
|
||||
|
||||
# Compute token embeddings
|
||||
with torch.no_grad():
|
||||
model_output = model(**encoded_input)
|
||||
# Perform pooling. In this case, cls pooling.
|
||||
sentence_embeddings = model_output[0][:, 0]
|
||||
# normalize embeddings
|
||||
sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
|
||||
print("Sentence embeddings:", sentence_embeddings)
|
||||
```
|
||||
|
||||
### Usage for Reranker
|
||||
|
||||
Different from embedding model, reranker uses question and document as input and directly output similarity instead of embedding.
|
||||
You can get a relevance score by inputting query and passage to the reranker.
|
||||
The reranker is optimized based cross-entropy loss, so the relevance score is not bounded to a specific range.
|
||||
|
||||
|
||||
#### Using FlagEmbedding
|
||||
```
|
||||
pip install -U FlagEmbedding
|
||||
```
|
||||
|
||||
Get relevance scores (higher scores indicate more relevance):
|
||||
```python
|
||||
from FlagEmbedding import FlagReranker
|
||||
reranker = FlagReranker('BAAI/bge-reranker-large', use_fp16=True) # Setting use_fp16 to True speeds up computation with a slight performance degradation
|
||||
|
||||
score = reranker.compute_score(['query', 'passage'])
|
||||
print(score)
|
||||
|
||||
scores = reranker.compute_score([['what is panda?', 'hi'], ['what is panda?', 'The giant panda (Ailuropoda melanoleuca), sometimes called a panda bear or simply panda, is a bear species endemic to China.']])
|
||||
print(scores)
|
||||
```
|
||||
|
||||
|
||||
#### Using Huggingface transformers
|
||||
|
||||
```python
|
||||
import torch
|
||||
from transformers import AutoModelForSequenceClassification, AutoTokenizer
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained('BAAI/bge-reranker-large')
|
||||
model = AutoModelForSequenceClassification.from_pretrained('BAAI/bge-reranker-large')
|
||||
model.eval()
|
||||
|
||||
pairs = [['what is panda?', 'hi'], ['what is panda?', 'The giant panda (Ailuropoda melanoleuca), sometimes called a panda bear or simply panda, is a bear species endemic to China.']]
|
||||
with torch.no_grad():
|
||||
inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512)
|
||||
scores = model(**inputs, return_dict=True).logits.view(-1, ).float()
|
||||
print(scores)
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
`baai-general-embedding` models achieve **state-of-the-art performance on both MTEB and C-MTEB leaderboard!**
|
||||
For more details and evaluation tools see our [scripts](https://github.com/FlagOpen/FlagEmbedding/blob/master/C_MTEB/README.md).
|
||||
|
||||
- **MTEB**:
|
||||
|
||||
| Model Name | Dimension | Sequence Length | Average (56) | Retrieval (15) |Clustering (11) | Pair Classification (3) | Reranking (4) | STS (10) | Summarization (1) | Classification (12) |
|
||||
|:----:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| [BAAI/bge-large-en-v1.5](https://huggingface.co/BAAI/bge-large-en-v1.5) | 1024 | 512 | **64.23** | **54.29** | 46.08 | 87.12 | 60.03 | 83.11 | 31.61 | 75.97 |
|
||||
| [BAAI/bge-base-en-v1.5](https://huggingface.co/BAAI/bge-base-en-v1.5) | 768 | 512 | 63.55 | 53.25 | 45.77 | 86.55 | 58.86 | 82.4 | 31.07 | 75.53 |
|
||||
| [BAAI/bge-small-en-v1.5](https://huggingface.co/BAAI/bge-small-en-v1.5) | 384 | 512 | 62.17 |51.68 | 43.82 | 84.92 | 58.36 | 81.59 | 30.12 | 74.14 |
|
||||
| [bge-large-en](https://huggingface.co/BAAI/bge-large-en) | 1024 | 512 | 63.98 | 53.9 | 46.98 | 85.8 | 59.48 | 81.56 | 32.06 | 76.21 |
|
||||
| [bge-base-en](https://huggingface.co/BAAI/bge-base-en) | 768 | 512 | 63.36 | 53.0 | 46.32 | 85.86 | 58.7 | 81.84 | 29.27 | 75.27 |
|
||||
| [gte-large](https://huggingface.co/thenlper/gte-large) | 1024 | 512 | 63.13 | 52.22 | 46.84 | 85.00 | 59.13 | 83.35 | 31.66 | 73.33 |
|
||||
| [gte-base](https://huggingface.co/thenlper/gte-base) | 768 | 512 | 62.39 | 51.14 | 46.2 | 84.57 | 58.61 | 82.3 | 31.17 | 73.01 |
|
||||
| [e5-large-v2](https://huggingface.co/intfloat/e5-large-v2) | 1024| 512 | 62.25 | 50.56 | 44.49 | 86.03 | 56.61 | 82.05 | 30.19 | 75.24 |
|
||||
| [bge-small-en](https://huggingface.co/BAAI/bge-small-en) | 384 | 512 | 62.11 | 51.82 | 44.31 | 83.78 | 57.97 | 80.72 | 30.53 | 74.37 |
|
||||
| [instructor-xl](https://huggingface.co/hkunlp/instructor-xl) | 768 | 512 | 61.79 | 49.26 | 44.74 | 86.62 | 57.29 | 83.06 | 32.32 | 61.79 |
|
||||
| [e5-base-v2](https://huggingface.co/intfloat/e5-base-v2) | 768 | 512 | 61.5 | 50.29 | 43.80 | 85.73 | 55.91 | 81.05 | 30.28 | 73.84 |
|
||||
| [gte-small](https://huggingface.co/thenlper/gte-small) | 384 | 512 | 61.36 | 49.46 | 44.89 | 83.54 | 57.7 | 82.07 | 30.42 | 72.31 |
|
||||
| [text-embedding-ada-002](https://platform.openai.com/docs/guides/embeddings) | 1536 | 8192 | 60.99 | 49.25 | 45.9 | 84.89 | 56.32 | 80.97 | 30.8 | 70.93 |
|
||||
| [e5-small-v2](https://huggingface.co/intfloat/e5-base-v2) | 384 | 512 | 59.93 | 49.04 | 39.92 | 84.67 | 54.32 | 80.39 | 31.16 | 72.94 |
|
||||
| [sentence-t5-xxl](https://huggingface.co/sentence-transformers/sentence-t5-xxl) | 768 | 512 | 59.51 | 42.24 | 43.72 | 85.06 | 56.42 | 82.63 | 30.08 | 73.42 |
|
||||
| [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) | 768 | 514 | 57.78 | 43.81 | 43.69 | 83.04 | 59.36 | 80.28 | 27.49 | 65.07 |
|
||||
| [sgpt-bloom-7b1-msmarco](https://huggingface.co/bigscience/sgpt-bloom-7b1-msmarco) | 4096 | 2048 | 57.59 | 48.22 | 38.93 | 81.9 | 55.65 | 77.74 | 33.6 | 66.19 |
|
||||
|
||||
|
||||
|
||||
- **C-MTEB**:
|
||||
We create the benchmark C-MTEB for Chinese text embedding which consists of 31 datasets from 6 tasks.
|
||||
Please refer to [C_MTEB](https://github.com/FlagOpen/FlagEmbedding/blob/master/C_MTEB/README.md) for a detailed introduction.
|
||||
|
||||
| Model | Embedding dimension | Avg | Retrieval | STS | PairClassification | Classification | Reranking | Clustering |
|
||||
|:-------------------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
|
||||
| [**BAAI/bge-large-zh-v1.5**](https://huggingface.co/BAAI/bge-large-zh-v1.5) | 1024 | **64.53** | 70.46 | 56.25 | 81.6 | 69.13 | 65.84 | 48.99 |
|
||||
| [BAAI/bge-base-zh-v1.5](https://huggingface.co/BAAI/bge-base-zh-v1.5) | 768 | 63.13 | 69.49 | 53.72 | 79.75 | 68.07 | 65.39 | 47.53 |
|
||||
| [BAAI/bge-small-zh-v1.5](https://huggingface.co/BAAI/bge-small-zh-v1.5) | 512 | 57.82 | 61.77 | 49.11 | 70.41 | 63.96 | 60.92 | 44.18 |
|
||||
| [BAAI/bge-large-zh](https://huggingface.co/BAAI/bge-large-zh) | 1024 | 64.20 | 71.53 | 54.98 | 78.94 | 68.32 | 65.11 | 48.39 |
|
||||
| [bge-large-zh-noinstruct](https://huggingface.co/BAAI/bge-large-zh-noinstruct) | 1024 | 63.53 | 70.55 | 53 | 76.77 | 68.58 | 64.91 | 50.01 |
|
||||
| [BAAI/bge-base-zh](https://huggingface.co/BAAI/bge-base-zh) | 768 | 62.96 | 69.53 | 54.12 | 77.5 | 67.07 | 64.91 | 47.63 |
|
||||
| [multilingual-e5-large](https://huggingface.co/intfloat/multilingual-e5-large) | 1024 | 58.79 | 63.66 | 48.44 | 69.89 | 67.34 | 56.00 | 48.23 |
|
||||
| [BAAI/bge-small-zh](https://huggingface.co/BAAI/bge-small-zh) | 512 | 58.27 | 63.07 | 49.45 | 70.35 | 63.64 | 61.48 | 45.09 |
|
||||
| [m3e-base](https://huggingface.co/moka-ai/m3e-base) | 768 | 57.10 | 56.91 | 50.47 | 63.99 | 67.52 | 59.34 | 47.68 |
|
||||
| [m3e-large](https://huggingface.co/moka-ai/m3e-large) | 1024 | 57.05 | 54.75 | 50.42 | 64.3 | 68.2 | 59.66 | 48.88 |
|
||||
| [multilingual-e5-base](https://huggingface.co/intfloat/multilingual-e5-base) | 768 | 55.48 | 61.63 | 46.49 | 67.07 | 65.35 | 54.35 | 40.68 |
|
||||
| [multilingual-e5-small](https://huggingface.co/intfloat/multilingual-e5-small) | 384 | 55.38 | 59.95 | 45.27 | 66.45 | 65.85 | 53.86 | 45.26 |
|
||||
| [text-embedding-ada-002(OpenAI)](https://platform.openai.com/docs/guides/embeddings/what-are-embeddings) | 1536 | 53.02 | 52.0 | 43.35 | 69.56 | 64.31 | 54.28 | 45.68 |
|
||||
| [luotuo](https://huggingface.co/silk-road/luotuo-bert-medium) | 1024 | 49.37 | 44.4 | 42.78 | 66.62 | 61 | 49.25 | 44.39 |
|
||||
| [text2vec-base](https://huggingface.co/shibing624/text2vec-base-chinese) | 768 | 47.63 | 38.79 | 43.41 | 67.41 | 62.19 | 49.45 | 37.66 |
|
||||
| [text2vec-large](https://huggingface.co/GanymedeNil/text2vec-large-chinese) | 1024 | 47.36 | 41.94 | 44.97 | 70.86 | 60.66 | 49.16 | 30.02 |
|
||||
|
||||
|
||||
- **Reranking**:
|
||||
See [C_MTEB](https://github.com/FlagOpen/FlagEmbedding/blob/master/C_MTEB/) for evaluation script.
|
||||
|
||||
| Model | T2Reranking | T2RerankingZh2En\* | T2RerankingEn2Zh\* | MMarcoReranking | CMedQAv1 | CMedQAv2 | Avg |
|
||||
|:-------------------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
|
||||
| text2vec-base-multilingual | 64.66 | 62.94 | 62.51 | 14.37 | 48.46 | 48.6 | 50.26 |
|
||||
| multilingual-e5-small | 65.62 | 60.94 | 56.41 | 29.91 | 67.26 | 66.54 | 57.78 |
|
||||
| multilingual-e5-large | 64.55 | 61.61 | 54.28 | 28.6 | 67.42 | 67.92 | 57.4 |
|
||||
| multilingual-e5-base | 64.21 | 62.13 | 54.68 | 29.5 | 66.23 | 66.98 | 57.29 |
|
||||
| m3e-base | 66.03 | 62.74 | 56.07 | 17.51 | 77.05 | 76.76 | 59.36 |
|
||||
| m3e-large | 66.13 | 62.72 | 56.1 | 16.46 | 77.76 | 78.27 | 59.57 |
|
||||
| bge-base-zh-v1.5 | 66.49 | 63.25 | 57.02 | 29.74 | 80.47 | 84.88 | 63.64 |
|
||||
| bge-large-zh-v1.5 | 65.74 | 63.39 | 57.03 | 28.74 | 83.45 | 85.44 | 63.97 |
|
||||
| [BAAI/bge-reranker-base](https://huggingface.co/BAAI/bge-reranker-base) | 67.28 | 63.95 | 60.45 | 35.46 | 81.26 | 84.1 | 65.42 |
|
||||
| [BAAI/bge-reranker-large](https://huggingface.co/BAAI/bge-reranker-large) | 67.6 | 64.03 | 61.44 | 37.16 | 82.15 | 84.18 | 66.09 |
|
||||
|
||||
\* : T2RerankingZh2En and T2RerankingEn2Zh are cross-language retrieval tasks
|
||||
|
||||
## Train
|
||||
|
||||
### BAAI Embedding
|
||||
|
||||
We pre-train the models using [retromae](https://github.com/staoxiao/RetroMAE) and train them on large-scale pairs data using contrastive learning.
|
||||
**You can fine-tune the embedding model on your data following our [examples](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune).**
|
||||
We also provide a [pre-train example](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/pretrain).
|
||||
Note that the goal of pre-training is to reconstruct the text, and the pre-trained model cannot be used for similarity calculation directly, it needs to be fine-tuned.
|
||||
More training details for bge see [baai_general_embedding](https://github.com/FlagOpen/FlagEmbedding/blob/master/FlagEmbedding/baai_general_embedding/README.md).
|
||||
|
||||
|
||||
|
||||
### BGE Reranker
|
||||
|
||||
Cross-encoder will perform full-attention over the input pair,
|
||||
which is more accurate than embedding model (i.e., bi-encoder) but more time-consuming than embedding model.
|
||||
Therefore, it can be used to re-rank the top-k documents returned by embedding model.
|
||||
We train the cross-encoder on a multilingual pair data,
|
||||
The data format is the same as embedding model, so you can fine-tune it easily following our [example](https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/reranker).
|
||||
More details please refer to [./FlagEmbedding/reranker/README.md](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/reranker)
|
||||
|
||||
|
||||
## Contact
|
||||
If you have any question or suggestion related to this project, feel free to open an issue or pull request.
|
||||
You also can email Shitao Xiao(stxiao@baai.ac.cn) and Zheng Liu(liuzheng@baai.ac.cn).
|
||||
|
||||
|
||||
## Citation
|
||||
|
||||
If you find this repository useful, please consider giving a star :star: and citation
|
||||
|
||||
```
|
||||
@misc{bge_embedding,
|
||||
title={C-Pack: Packaged Resources To Advance General Chinese Embedding},
|
||||
author={Shitao Xiao and Zheng Liu and Peitian Zhang and Niklas Muennighoff},
|
||||
year={2023},
|
||||
eprint={2309.07597},
|
||||
archivePrefix={arXiv},
|
||||
primaryClass={cs.CL}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
FlagEmbedding is licensed under the [MIT License](https://github.com/FlagOpen/FlagEmbedding/blob/master/LICENSE). The released models can be used for commercial purposes free of charge.
|
||||
40
docker/embedding/bge-large-zh-v1.5/config.json
Normal file
40
docker/embedding/bge-large-zh-v1.5/config.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"_name_or_path": "/root/.cache/torch/sentence_transformers/BAAI_bge-large-zh/",
|
||||
"architectures": [
|
||||
"BertModel"
|
||||
],
|
||||
"attention_probs_dropout_prob": 0.1,
|
||||
"bos_token_id": 0,
|
||||
"classifier_dropout": null,
|
||||
"directionality": "bidi",
|
||||
"eos_token_id": 2,
|
||||
"hidden_act": "gelu",
|
||||
"hidden_dropout_prob": 0.1,
|
||||
"hidden_size": 1024,
|
||||
"id2label": {
|
||||
"0": "LABEL_0"
|
||||
},
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 4096,
|
||||
"label2id": {
|
||||
"LABEL_0": 0
|
||||
},
|
||||
"layer_norm_eps": 1e-12,
|
||||
"max_position_embeddings": 512,
|
||||
"model_type": "bert",
|
||||
"num_attention_heads": 16,
|
||||
"num_hidden_layers": 24,
|
||||
"output_past": true,
|
||||
"pad_token_id": 0,
|
||||
"pooler_fc_size": 768,
|
||||
"pooler_num_attention_heads": 12,
|
||||
"pooler_num_fc_layers": 3,
|
||||
"pooler_size_per_head": 128,
|
||||
"pooler_type": "first_token_transform",
|
||||
"position_embedding_type": "absolute",
|
||||
"torch_dtype": "float32",
|
||||
"transformers_version": "4.30.0",
|
||||
"type_vocab_size": 2,
|
||||
"use_cache": true,
|
||||
"vocab_size": 21128
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
{
|
||||
"__version__": {
|
||||
"sentence_transformers": "2.2.2",
|
||||
"transformers": "4.28.1",
|
||||
"pytorch": "1.13.0+cu117"
|
||||
}
|
||||
}
|
||||
20
docker/embedding/bge-large-zh-v1.5/modules.json
Normal file
20
docker/embedding/bge-large-zh-v1.5/modules.json
Normal file
@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"idx": 0,
|
||||
"name": "0",
|
||||
"path": "",
|
||||
"type": "sentence_transformers.models.Transformer"
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"name": "1",
|
||||
"path": "1_Pooling",
|
||||
"type": "sentence_transformers.models.Pooling"
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"name": "2",
|
||||
"path": "2_Normalize",
|
||||
"type": "sentence_transformers.models.Normalize"
|
||||
}
|
||||
]
|
||||
3
docker/embedding/bge-large-zh-v1.5/pytorch_model.bin
Normal file
3
docker/embedding/bge-large-zh-v1.5/pytorch_model.bin
Normal file
@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf84a56fb045c24e090495195584a3922c3e4204107f0bba8b79c11f67a207f2
|
||||
size 1302220525
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"max_seq_length": 512,
|
||||
"do_lower_case": true
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
{
|
||||
"cls_token": "[CLS]",
|
||||
"mask_token": "[MASK]",
|
||||
"pad_token": "[PAD]",
|
||||
"sep_token": "[SEP]",
|
||||
"unk_token": "[UNK]"
|
||||
}
|
||||
21278
docker/embedding/bge-large-zh-v1.5/tokenizer.json
Normal file
21278
docker/embedding/bge-large-zh-v1.5/tokenizer.json
Normal file
File diff suppressed because it is too large
Load Diff
15
docker/embedding/bge-large-zh-v1.5/tokenizer_config.json
Normal file
15
docker/embedding/bge-large-zh-v1.5/tokenizer_config.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"clean_up_tokenization_spaces": true,
|
||||
"cls_token": "[CLS]",
|
||||
"do_basic_tokenize": true,
|
||||
"do_lower_case": true,
|
||||
"mask_token": "[MASK]",
|
||||
"model_max_length": 1000000000000000019884624838656,
|
||||
"never_split": null,
|
||||
"pad_token": "[PAD]",
|
||||
"sep_token": "[SEP]",
|
||||
"strip_accents": null,
|
||||
"tokenize_chinese_chars": true,
|
||||
"tokenizer_class": "BertTokenizer",
|
||||
"unk_token": "[UNK]"
|
||||
}
|
||||
21128
docker/embedding/bge-large-zh-v1.5/vocab.txt
Normal file
21128
docker/embedding/bge-large-zh-v1.5/vocab.txt
Normal file
File diff suppressed because it is too large
Load Diff
28
main.py
Normal file
28
main.py
Normal file
@ -0,0 +1,28 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
from app.client.mysql_client_manager import db_assistant_mysql_client_manager
|
||||
from app.summary import summary_router
|
||||
|
||||
app = FastAPI(title="Sales Assistant API", version="1.0.0")
|
||||
|
||||
app.include_router(summary_router)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
db_assistant_mysql_client_manager.init()
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown():
|
||||
await db_assistant_mysql_client_manager.close()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Sales Assistant API is running"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
23
pyproject.toml
Normal file
23
pyproject.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[project]
|
||||
name = "sales-assistant-py"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"aiohttp>=3.14.1",
|
||||
"apscheduler>=3.11.2",
|
||||
"asyncmy>=0.2.11",
|
||||
"cryptography>=48.0.1",
|
||||
"fastapi[standard]>=0.136.3",
|
||||
"huggingface-hub>=1.18.0",
|
||||
"jieba>=0.42.1",
|
||||
"langchain>=1.3.7",
|
||||
"langchain-deepseek>=1.1.0",
|
||||
"langchain-huggingface>=1.2.2",
|
||||
"langgraph>=1.2.4",
|
||||
"loguru>=0.7.3",
|
||||
"omegaconf>=2.3.0",
|
||||
"pymilvus>=3.0.0",
|
||||
"pyyaml>=6.0.3",
|
||||
"sqlalchemy>=2.0.50",
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user