Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/0.3.3 #741

Merged
merged 3 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/backend/bisheng/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
finetune_router, flows_router, group_router, knowledge_router,
qa_router, report_router, server_router, skillcenter_router,
user_router, validate_router, variable_router, audit_router, evaluation_router)
from bisheng.api.v2 import chat_router_rpc, knowledge_router_rpc, rpc_router_rpc, flow_router
from bisheng.api.v2 import chat_router_rpc, knowledge_router_rpc, rpc_router_rpc, flow_router, assistant_router_rpc
from fastapi import APIRouter

router = APIRouter(prefix='/api/v1', )
Expand All @@ -30,3 +30,4 @@
router_rpc.include_router(chat_router_rpc)
router_rpc.include_router(rpc_router_rpc)
router_rpc.include_router(flow_router)
router_rpc.include_router(assistant_router_rpc)
84 changes: 56 additions & 28 deletions src/backend/bisheng/api/services/assistant_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import time
import uuid
from pathlib import Path
from typing import Dict, List
from typing import Dict, List, Any
from uuid import UUID

import httpx
Expand Down Expand Up @@ -338,51 +338,79 @@ def choose_tools(self, tool_list: List[Dict[str, str]], prompt: str) -> List[str
tool_selector = ToolSelector(llm=self.llm, tools=tool_list)
return tool_selector.select(self.assistant.name, prompt)

async def fake_callback(self, callback: Callbacks):
if not callback:
return
# 假回调,将已下线的技能回调给前端
for one in self.offline_flows:
run_id = uuid.uuid4()
await callback[0].on_tool_start({
'name': one,
}, input_str='flow if offline', run_id=run_id)
await callback[0].on_tool_end(output='flow is offline', name=one, run_id=run_id)

async def record_chat_history(self, message: List[Any]):
# 记录助手的聊天历史
if not os.getenv("BISHENG_RECORD_HISTORY"):
return
try:
os.makedirs("/app/data/history", exist_ok=True)
with open(f"/app/data/history/{self.assistant.id}_{time.time()}.json", "w", encoding="utf-8") as f:
json.dump({
"system": self.assistant.prompt,
"message": message,
"tools": [format_tool_to_openai_tool(t) for t in self.tools]
}, f, ensure_ascii=False)
except Exception as e:
logger.error(f"record assistant history error: {str(e)}")

async def arun(self, query: str, chat_history: List = None, callback: Callbacks = None):
await self.fake_callback(callback)

if chat_history:
chat_history.append(HumanMessage(content=query))
inputs = chat_history
else:
inputs = [HumanMessage(content=query)]

async for one in self.agent.astream(inputs, config=RunnableConfig(callbacks=callback)):
yield one

async def run(self, query: str, chat_history: List = None, callback: Callbacks = None):
"""
运行智能体对话
"""
# 假回调,将已下线的技能回调给前端
for one in self.offline_flows:
if callback is not None:
run_id = uuid.uuid4()
await callback[0].on_tool_start({
'name': one,
}, input_str='flow if offline', run_id=run_id)
await callback[0].on_tool_end(output='flow is offline', name=one, run_id=run_id)
if self.current_agent_executor == 'ReAct':
return await self.react_run(query, chat_history, callback)
await self.fake_callback(callback)

if chat_history:
chat_history.append(HumanMessage(content=query))
inputs = chat_history
else:
inputs = [HumanMessage(content=query)]
result = await self.agent.ainvoke(inputs, config=RunnableConfig(callbacks=callback))

if self.current_agent_executor == 'ReAct':
result = await self.react_run(inputs, callback)
else:
result = await self.agent.ainvoke(inputs, config=RunnableConfig(callbacks=callback))
# 包含了history,将history排除, 默认取最后一个为最终结果
res = [result[-1]]
# 记录助手的聊天历史
if os.getenv("BISHENG_RECORD_HISTORY"):
try:
os.makedirs("/app/data/history", exist_ok=True)
with open(f"/app/data/history/{self.assistant.id}_{time.time()}.json", "w", encoding="utf-8") as f:
json.dump({
"system": self.assistant.prompt,
"message": [one.to_json() for one in result],
"tools": [format_tool_to_openai_tool(t) for t in self.tools]
}, f, ensure_ascii=False)
except Exception as e:
logger.error(f"record assistant history error: {str(e)}")

# 记录聊天历史
await self.record_chat_history([one.to_json() for one in result])

return res

async def react_run(self, query: str, chat_history: List = None, callback: Callbacks = None):
async def react_run(self, inputs: List, callback: Callbacks = None):
""" react 模式的输入和执行 """
result = await self.agent.ainvoke({
'input': query,
'chat_history': chat_history
'input': inputs[-1].content,
'chat_history': inputs[:-1],
}, config=RunnableConfig(callbacks=callback))
logger.debug(f"react_run result: {result}")
output = result['agent_outcome'].return_values['output']
if isinstance(output, dict):
output = list(output.values())[0]
return [AIMessage(content=output)]
for one in result['intermediate_steps']:
inputs.append(one[0])
inputs.append(AIMessage(content=output))
return inputs
9 changes: 8 additions & 1 deletion src/backend/bisheng/api/services/user_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from bisheng.database.models.assistant import Assistant, AssistantDao
from bisheng.database.models.flow import Flow, FlowDao, FlowRead
from bisheng.database.models.knowledge import Knowledge, KnowledgeDao, KnowledgeRead
from bisheng.database.models.role import AdminRole
from bisheng.database.models.role_access import AccessType, RoleAccessDao
from bisheng.database.models.user import User, UserDao
from bisheng.database.models.user_group import UserGroupDao
Expand All @@ -35,7 +36,13 @@ def __init__(self, **kwargs):
self.user_name = kwargs.get('user_name')

def is_admin(self):
return self.user_role == 'admin'
if self.user_role == 'admin':
return True
if isinstance(self.user_role, list):
for one in self.user_role:
if one == AdminRole:
return True
return False

@staticmethod
def wrapper_access_check(func):
Expand Down
4 changes: 2 additions & 2 deletions src/backend/bisheng/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import Dict, List

import aiohttp
from fastapi import Request
from fastapi import Request, WebSocket
from fastapi_jwt_auth import AuthJWT
from platformdirs import user_cache_dir
from sqlalchemy import delete
Expand Down Expand Up @@ -420,7 +420,7 @@ async def get_url_content(url: str) -> str:
return res.decode('utf-8')


def get_request_ip(request: Request) -> str:
def get_request_ip(request: Request | WebSocket) -> str:
""" 获取客户端真实IP """
x_forwarded_for = request.headers.get('X-Forwarded-For')
if x_forwarded_for:
Expand Down
32 changes: 30 additions & 2 deletions src/backend/bisheng/api/v1/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,12 @@ class UploadFileResponse(BaseModel):

class StreamData(BaseModel):
event: str
data: dict
data: dict | str

def __str__(self) -> str:
return f'event: {self.event}\ndata: {orjson.dumps(self.data).decode()}\n\n'
if isinstance(self.data, dict):
return f'event: {self.event}\ndata: {orjson.dumps(self.data).decode()}\n\n'
return f'event: {self.event}\ndata: {self.data}\n\n'


class FinetuneCreateReq(BaseModel):
Expand Down Expand Up @@ -320,3 +322,29 @@ class CreateUserReq(BaseModel):
user_name: str = Field(max_length=30, description='用户名')
password: str = Field(description='密码')
group_roles: List[GroupAndRoles] = Field(description='要加入的用户组和角色列表')


class OpenAIChatCompletionReq(BaseModel):
messages: List[dict] = Field(..., description="聊天消息列表,只支持user、assistant。system用数据库内的数据")
model: str = Field(..., description="助手的唯一ID")
n: int = Field(default=1, description="返回的答案个数, 助手侧默认为1,暂不支持多个回答")
stream: bool = Field(default=False, description="是否开启流式回复")
temperature: float = Field(default=0.0, description="模型温度, 传入0或者不传表示不覆盖")
tools: List[dict] = Field(default=[], description="工具列表, 助手暂不支持,使用助手的配置")


class OpenAIChoice(BaseModel):
index: int = Field(..., description="选项的索引")
message: dict = Field(default=None, description="对应的消息内容,和输入的格式一致")
finish_reason: str = Field(default='stop', description="结束原因, 助手只有stop")
delta: dict = Field(default=None, description="对应的openai流式返回消息内容")


class OpenAIChatCompletionResp(BaseModel):
id: str = Field(..., description="请求的唯一ID")
object: str = Field(default='chat.completion', description="返回的类型")
created: int = Field(default=..., description="返回的创建时间戳")
model: str = Field(..., description="返回的模型,对应助手的id")
choices: List[OpenAIChoice] = Field(..., description="返回的答案列表")
usage: dict = Field(default=None, description="返回的token用量, 助手此值为空")
system_fingerprint: Optional[str] = Field(default=None, description="系统指纹")
4 changes: 3 additions & 1 deletion src/backend/bisheng/api/v1/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ async def regist(*, user: UserCreate):
# check if user already exist
user_exists = UserDao.get_user_by_username(db_user.user_name)
if user_exists:
raise HTTPException(status_code=500, detail='账号已存在')
raise HTTPException(status_code=500, detail='用户名已存在')
if len(db_user.user_name)>30:
raise HTTPException(status_code=500, detail='用户名最长 30 个字符')
try:
db_user.password = UserService.decrypt_md5_password(user.password)
# 判断下admin用户是否存在
Expand Down
3 changes: 2 additions & 1 deletion src/backend/bisheng/api/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
from bisheng.api.v2.filelib import router as knowledge_router_rpc
from bisheng.api.v2.rpc import router as rpc_router_rpc
from bisheng.api.v2.flow import router as flow_router
__all__ = ['knowledge_router_rpc', 'chat_router_rpc', 'rpc_router_rpc', 'flow_router']
from bisheng.api.v2.assistant import router as assistant_router_rpc
__all__ = ['knowledge_router_rpc', 'chat_router_rpc', 'rpc_router_rpc', 'flow_router', 'assistant_router_rpc']
Loading