diff --git a/apps/common/queue.py b/apps/common/queue.py
index 5601c93aa3ef72011f2e2d4b126f782d83e85b78..6e82af915e2a70f743c629902c149b7ebc0f7a0e 100644
--- a/apps/common/queue.py
+++ b/apps/common/queue.py
@@ -56,9 +56,12 @@ class MessageQueue:
flow = MessageFlow(
appId=task.state.app_id,
flowId=task.state.flow_id,
+ flowName=task.state.flow_name,
+ flowStatus=task.state.flow_status,
stepId=task.state.step_id,
stepName=task.state.step_name,
- stepStatus=task.state.status,
+ stepDescription=task.state.step_description,
+ stepStatus=task.state.step_status
)
else:
flow = None
diff --git a/apps/constants.py b/apps/constants.py
index 58158b33c3aa4ea5aa3595f5642bf6444e7d76cb..20cb79b54ac8db5450fdbb296cb400b47a29adf0 100644
--- a/apps/constants.py
+++ b/apps/constants.py
@@ -11,7 +11,7 @@ from apps.common.config import Config
# 新对话默认标题
NEW_CHAT = "新对话"
# 滑动窗口限流 默认窗口期
-SLIDE_WINDOW_TIME = 60
+SLIDE_WINDOW_TIME = 15
# OIDC 访问Token 过期时间(分钟)
OIDC_ACCESS_TOKEN_EXPIRE_TIME = 30
# OIDC 刷新Token 过期时间(分钟)
diff --git a/apps/dependency/user.py b/apps/dependency/user.py
index fce67e51e00dd76b35bec2f7787dfe8039111589..8f5848a3d2961ef21b242d2eee931c623c6481c7 100644
--- a/apps/dependency/user.py
+++ b/apps/dependency/user.py
@@ -1,14 +1,16 @@
# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved.
"""用户鉴权"""
-
+import os
import logging
from fastapi import Depends
from fastapi.security import OAuth2PasswordBearer
+import secrets
from starlette import status
from starlette.exceptions import HTTPException
from starlette.requests import HTTPConnection
+from apps.common.config import Config
from apps.services.api_key import ApiKeyManager
from apps.services.session import SessionManager
@@ -48,6 +50,9 @@ async def get_session(request: HTTPConnection) -> str:
:param request: HTTP请求
:return: Session ID
"""
+ if Config().get_config().no_auth.enable:
+ # 如果启用了无认证访问,直接返回调试用户
+ return secrets.token_hex(16)
session_id = await _get_session_id_from_request(request)
if not session_id:
raise HTTPException(
@@ -69,6 +74,12 @@ async def get_user(request: HTTPConnection) -> str:
:param request: HTTP请求体
:return: 用户sub
"""
+ if Config().get_config().no_auth.enable:
+ # 如果启用了无认证访问,直接返回当前操作系统用户的名称
+ username = os.environ.get('USERNAME') # 适用于 Windows 系统
+ if not username:
+ username = os.environ.get('USER') # 适用于 Linux 和 macOS 系统
+ return username or "admin"
session_id = await _get_session_id_from_request(request)
if not session_id:
raise HTTPException(
diff --git a/apps/llm/embedding.py b/apps/llm/embedding.py
index 28ab86b49a96d19cc6e2db83930d5aff48f82260..df13044a1c9ce0e11968d5dd7c3f0f8ab2345394 100644
--- a/apps/llm/embedding.py
+++ b/apps/llm/embedding.py
@@ -1,8 +1,9 @@
"""Embedding模型"""
import httpx
-
+import logging
from apps.common.config import Config
+logger = logging.getLogger(__name__)
class Embedding:
@@ -15,7 +16,6 @@ class Embedding:
embedding = await cls.get_embedding(["测试文本"])
return len(embedding[0])
-
@classmethod
async def _get_openai_embedding(cls, text: list[str]) -> list[list[float]]:
"""访问OpenAI兼容的Embedding API,获得向量化数据"""
@@ -75,10 +75,18 @@ class Embedding:
:param text: 待向量化文本(多条文本组成List)
:return: 文本对应的向量(顺序与text一致,也为List)
"""
- if Config().get_config().embedding.type == "openai":
- return await cls._get_openai_embedding(text)
- if Config().get_config().embedding.type == "mindie":
- return await cls._get_tei_embedding(text)
+ try:
+ if Config().get_config().embedding.type == "openai":
+ return await cls._get_openai_embedding(text)
+ if Config().get_config().embedding.type == "mindie":
+ return await cls._get_tei_embedding(text)
- err = f"不支持的Embedding API类型: {Config().get_config().embedding.type}"
- raise ValueError(err)
+ err = f"不支持的Embedding API类型: {Config().get_config().embedding.type}"
+ raise ValueError(err)
+ except Exception as e:
+ err = f"获取Embedding失败: {e}"
+ logger.error(err)
+ rt = []
+ for i in range(len(text)):
+ rt.append([0.0]*1024)
+ return rt
diff --git a/apps/llm/function.py b/apps/llm/function.py
index 1f995fe7ba187cead03aa6fc62a4cbce1ec05a65..0d1fbf9a79c325d13831d8823d88544df88dae9a 100644
--- a/apps/llm/function.py
+++ b/apps/llm/function.py
@@ -10,7 +10,7 @@ from typing import Any
from jinja2 import BaseLoader
from jinja2.sandbox import SandboxedEnvironment
from jsonschema import Draft7Validator
-
+from jsonschema import validate
from apps.common.config import Config
from apps.constants import JSON_GEN_MAX_TRIAL, REASONING_END_TOKEN
from apps.llm.prompt import JSON_GEN_BASIC
@@ -42,6 +42,7 @@ class FunctionLLM:
self._params = {
"model": self._config.model,
"messages": [],
+ "timeout": 300
}
if self._config.backend == "ollama":
@@ -68,7 +69,6 @@ class FunctionLLM:
api_key=self._config.api_key,
)
-
async def _call_openai(
self,
messages: list[dict[str, str]],
@@ -123,7 +123,7 @@ class FunctionLLM:
},
]
- response = await self._client.chat.completions.create(**self._params) # type: ignore[arg-type]
+ response = await self._client.chat.completions.create(**self._params) # type: ignore[arg-type]
try:
logger.info("[FunctionCall] 大模型输出:%s", response.choices[0].message.tool_calls[0].function.arguments)
return response.choices[0].message.tool_calls[0].function.arguments
@@ -132,7 +132,6 @@ class FunctionLLM:
logger.info("[FunctionCall] 大模型输出:%s", ans)
return await FunctionLLM.process_response(ans)
-
@staticmethod
async def process_response(response: str) -> str:
"""处理大模型的输出"""
@@ -169,7 +168,6 @@ class FunctionLLM:
return json_str
-
async def _call_ollama(
self,
messages: list[dict[str, str]],
@@ -196,10 +194,9 @@ class FunctionLLM:
"format": schema,
})
- response = await self._client.chat(**self._params) # type: ignore[arg-type]
+ response = await self._client.chat(**self._params) # type: ignore[arg-type]
return await self.process_response(response.message.content or "")
-
async def call(
self,
messages: list[dict[str, Any]],
@@ -237,6 +234,58 @@ class FunctionLLM:
class JsonGenerator:
"""JSON生成器"""
+ @staticmethod
+ async def _parse_result_by_stack(result: str, schema: dict[str, Any]) -> str:
+ """解析推理结果"""
+ left_index = result.find('{')
+ right_index = result.rfind('}')
+ if left_index != -1 and right_index != -1 and left_index < right_index:
+ try:
+ tmp_js = json.loads(result[left_index:right_index + 1])
+ validate(instance=tmp_js, schema=schema)
+ return tmp_js
+ except Exception as e:
+ logger.error("[JsonGenerator] 解析结果失败: %s", e)
+ stack = []
+ json_candidates = []
+ # 定义括号匹配关系
+ bracket_map = {')': '(', ']': '[', '}': '{'}
+
+ for i, char in enumerate(result):
+ # 遇到左括号则入栈
+ if char in bracket_map.values():
+ stack.append((char, i))
+ # 遇到右括号且栈不为空时检查匹配
+ elif char in bracket_map.keys() and stack:
+ if not stack:
+ continue
+ top_char, top_index = stack[-1]
+ # 检查是否匹配当前右括号
+ if top_char == bracket_map[char]:
+ stack.pop()
+ # 当栈为空且当前是右花括号时,认为找到一个完整JSON
+ if not stack and char == '}':
+ json_str = result[top_index:i+1]
+ json_candidates.append(json_str)
+ else:
+ # 如果不匹配,清空栈
+ stack.clear()
+ # 移除重复项并保持顺序
+ seen = set()
+ unique_jsons = []
+ for json_str in json_candidates[::]:
+ if json_str not in seen:
+ seen.add(json_str)
+ unique_jsons.append(json_str)
+
+ for json_str in unique_jsons:
+ try:
+ tmp_js = json.loads(json_str)
+ validate(instance=tmp_js, schema=schema)
+ return tmp_js
+ except Exception as e:
+ logger.error("[JsonGenerator] 解析结果失败: %s", e)
+ return None
def __init__(self, query: str, conversation: list[dict[str, str]], schema: dict[str, Any]) -> None:
"""初始化JSON生成器"""
@@ -254,7 +303,6 @@ class JsonGenerator:
)
self._err_info = ""
-
async def _assemble_message(self) -> str:
"""组装消息"""
# 检查类型
@@ -275,23 +323,20 @@ class JsonGenerator:
"""单次尝试"""
prompt = await self._assemble_message()
messages = [
- {"role": "system", "content": "You are a helpful assistant."},
- {"role": "user", "content": prompt},
+ {"role": "system", "content": prompt},
+ {"role": "user", "content": "please generate a JSON response based on the above information and schema."},
]
function = FunctionLLM()
return await function.call(messages, self._schema, max_tokens, temperature)
-
async def generate(self) -> dict[str, Any]:
"""生成JSON"""
Draft7Validator.check_schema(self._schema)
validator = Draft7Validator(self._schema)
- logger.info("[JSONGenerator] Schema:%s", self._schema)
while self._count < JSON_GEN_MAX_TRIAL:
self._count += 1
result = await self._single_trial()
- logger.info("[JSONGenerator] 得到:%s", result)
try:
validator.validate(result)
except Exception as err: # noqa: BLE001
diff --git a/apps/llm/patterns/core.py b/apps/llm/patterns/core.py
index 4ef8133a9fed1b1e62f1ceb578c6bdb5a93b12a5..c4a58364ec7c21a132f401d5ebfb98e3d7c58b82 100644
--- a/apps/llm/patterns/core.py
+++ b/apps/llm/patterns/core.py
@@ -3,40 +3,52 @@
from abc import ABC, abstractmethod
from textwrap import dedent
+from pydantic import BaseModel, Field
+from apps.schemas.enum_var import LanguageType
class CorePattern(ABC):
"""基础大模型范式抽象类"""
- system_prompt: str = ""
- """系统提示词"""
- user_prompt: str = ""
"""用户提示词"""
input_tokens: int = 0
"""输入Token数量"""
output_tokens: int = 0
"""输出Token数量"""
+ def get_default_prompt(self) -> dict[LanguageType, str]:
+ """
+ 获取默认的用户提示词
+
+ :return: 默认的用户提示词
+ :rtype: dict[LanguageType, str]
+ """
+ return {}, {}
- def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None:
+ def __init__(
+ self,
+ system_prompt: dict[LanguageType, str] | None = None,
+ user_prompt: dict[LanguageType, str] | None = None,
+ ) -> None:
"""
检查是否已经自定义了Prompt;有的话就用自定义的;同时对Prompt进行空格清除
:param system_prompt: 系统提示词,f-string格式
:param user_prompt: 用户提示词,f-string格式
"""
+ default_system_prompt, default_user_prompt = self.get_default_prompt()
if system_prompt is not None:
self.system_prompt = system_prompt
-
+ else:
+ self.system_prompt = default_system_prompt
if user_prompt is not None:
self.user_prompt = user_prompt
+ else:
+ self.user_prompt = default_user_prompt
- if not self.user_prompt:
- err = "必须设置用户提示词!"
- raise ValueError(err)
+ self.system_prompt = {lang: dedent(prompt).strip("\n") for lang, prompt in self.system_prompt.items()}
- self.system_prompt = dedent(self.system_prompt).strip("\n")
- self.user_prompt = dedent(self.user_prompt).strip("\n")
+ self.user_prompt = {lang: dedent(prompt).strip("\n") for lang, prompt in self.user_prompt.items()}
@abstractmethod
async def generate(self, **kwargs): # noqa: ANN003, ANN201
diff --git a/apps/llm/patterns/executor.py b/apps/llm/patterns/executor.py
index f872fd2ac8d691b4079756a56a6107d6b6556585..e2153487a568eaa1289677b14111bbcbcc7b68ea 100644
--- a/apps/llm/patterns/executor.py
+++ b/apps/llm/patterns/executor.py
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any
from apps.llm.patterns.core import CorePattern
from apps.llm.reasoning import ReasoningLLM
from apps.llm.snippet import convert_context_to_prompt, facts_to_prompt
-
+from apps.schemas.enum_var import LanguageType
if TYPE_CHECKING:
from apps.schemas.scheduler import ExecutorBackground
@@ -14,40 +14,79 @@ if TYPE_CHECKING:
class ExecutorThought(CorePattern):
"""通过大模型生成Executor的思考内容"""
- user_prompt: str = r"""
-
-
- 你是一个可以使用工具的智能助手。
- 在回答用户的问题时,你为了获取更多的信息,使用了一个工具。
- 请简明扼要地总结工具的使用过程,提供你的见解,并给出下一步的行动。
-
- 注意:
- 工具的相关信息在标签中给出。
- 为了使你更好的理解发生了什么,你之前的思考过程在标签中给出。
- 输出时请不要包含XML标签,输出时请保持简明和清晰。
-
-
-
-
- {tool_name}
- {tool_description}
-
-
-
-
- {last_thought}
-
-
-
- 你当前需要解决的问题是:
- {user_question}
-
-
- 请综合以上信息,再次一步一步地进行思考,并给出见解和行动:
- """
- """用户提示词"""
-
- def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None:
+ def get_default_prompt(self) -> dict[LanguageType, str]:
+ user_prompt = {
+ LanguageType.CHINESE: r"""
+
+
+ 你是一个可以使用工具的智能助手。
+ 在回答用户的问题时,你为了获取更多的信息,使用了一个工具。
+ 请简明扼要地总结工具的使用过程,提供你的见解,并给出下一步的行动。
+
+ 注意:
+ 工具的相关信息在标签中给出。
+ 为了使你更好的理解发生了什么,你之前的思考过程在标签中给出。
+ 输出时请不要包含XML标签,输出时请保持简明和清晰。
+
+
+
+
+ {tool_name}
+ {tool_description}
+
+
+
+
+ {last_thought}
+
+
+
+ 你当前需要解决的问题是:
+ {user_question}
+
+
+ 请综合以上信息,再次一步一步地进行思考,并给出见解和行动:
+ """,
+ LanguageType.ENGLISH: r"""
+
+
+ You are an intelligent assistant who can use tools.
+ When answering user questions, you use a tool to get more information.
+ Please summarize the process of using the tool briefly, provide your insights, and give the next action.
+
+ Note:
+ The information about the tool is given in the tag.
+ To help you better understand what happened, your previous thought process is given in the tag.
+ Do not include XML tags in the output, and keep the output brief and clear.
+
+
+
+
+ {tool_name}
+ {tool_description}
+
+
+
+
+ {last_thought}
+
+
+
+ The question you need to solve is:
+ {user_question}
+
+
+ Please integrate the above information, think step by step again, provide insights, and give actions:
+ """,
+ }
+ """用户提示词"""
+ return {}, user_prompt
+
+ def __init__(
+ self,
+ system_prompt: dict[LanguageType, str] | None = None,
+ user_prompt: dict[LanguageType, str] | None = None,
+ ) -> None:
"""处理Prompt"""
super().__init__(system_prompt, user_prompt)
@@ -57,19 +96,23 @@ class ExecutorThought(CorePattern):
last_thought: str = kwargs["last_thought"]
user_question: str = kwargs["user_question"]
tool_info: dict[str, Any] = kwargs["tool_info"]
+ language: LanguageType = kwargs.get("language", LanguageType.CHINESE)
except Exception as e:
err = "参数不正确!"
raise ValueError(err) from e
messages = [
{"role": "system", "content": "You are a helpful assistant."},
- {"role": "user", "content": self.user_prompt.format(
- last_thought=last_thought,
- user_question=user_question,
- tool_name=tool_info["name"],
- tool_description=tool_info["description"],
- tool_output=tool_info["output"],
- )},
+ {
+ "role": "user",
+ "content": self.user_prompt[language].format(
+ last_thought=last_thought,
+ user_question=user_question,
+ tool_name=tool_info["name"],
+ tool_description=tool_info["description"],
+ tool_output=tool_info["output"],
+ ),
+ },
]
llm = ReasoningLLM()
@@ -85,30 +128,59 @@ class ExecutorThought(CorePattern):
class ExecutorSummary(CorePattern):
"""使用大模型进行生成Executor初始背景"""
- user_prompt: str = r"""
-
- 根据给定的对话记录和关键事实,生成一个三句话背景总结。这个总结将用于后续对话的上下文理解。
-
- 生成总结的要求如下:
- 1. 突出重要信息点,例如时间、地点、人物、事件等。
- 2. “关键事实”中的内容可在生成总结时作为已知信息。
- 3. 输出时请不要包含XML标签,确保信息准确性,不得编造信息。
- 4. 总结应少于3句话,应少于300个字。
-
- 对话记录将在标签中给出,关键事实将在标签中给出。
-
-
- {conversation}
-
-
- {facts}
-
-
- 现在,请开始生成背景总结:
- """
- """用户提示词"""
-
- def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None:
+ def get_default_prompt(self) -> dict[LanguageType, str]:
+ user_prompt = {
+ LanguageType.CHINESE: r"""
+
+ 根据给定的对话记录和关键事实,生成一个三句话背景总结。这个总结将用于后续对话的上下文理解。
+
+ 生成总结的要求如下:
+ 1. 突出重要信息点,例如时间、地点、人物、事件等。
+ 2. “关键事实”中的内容可在生成总结时作为已知信息。
+ 3. 输出时请不要包含XML标签,确保信息准确性,不得编造信息。
+ 4. 总结应少于3句话,应少于300个字。
+
+ 对话记录将在标签中给出,关键事实将在标签中给出。
+
+
+ {conversation}
+
+
+ {facts}
+
+
+ 现在,请开始生成背景总结:
+ """,
+ LanguageType.ENGLISH: r"""
+
+ Based on the given conversation records and key facts, generate a three-sentence background summary. This summary will be used for context understanding in subsequent conversations.
+
+ The requirements for generating the summary are as follows:
+ 1. Highlight important information points, such as time, location, people, events, etc.
+ 2. The content in the "key facts" can be used as known information when generating the summary.
+ 3. Do not include XML tags in the output, ensure the accuracy of the information, and do not make up information.
+ 4. The summary should be less than 3 sentences and less than 300 words.
+
+ The conversation records will be given in the tag, and the key facts will be given in the tag.
+
+
+ {conversation}
+
+
+ {facts}
+
+
+ Now, please start generating the background summary:
+ """,
+ }
+ """用户提示词"""
+ return {}, user_prompt
+
+ def __init__(
+ self,
+ system_prompt: dict[LanguageType, str] | None = None,
+ user_prompt: dict[LanguageType, str] | None = None,
+ ) -> None:
"""初始化Background模式"""
super().__init__(system_prompt, user_prompt)
@@ -117,13 +189,17 @@ class ExecutorSummary(CorePattern):
background: ExecutorBackground = kwargs["background"]
conversation_str = convert_context_to_prompt(background.conversation)
facts_str = facts_to_prompt(background.facts)
+ language = kwargs.get("language", LanguageType.CHINESE)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
- {"role": "user", "content": self.user_prompt.format(
- facts=facts_str,
- conversation=conversation_str,
- )},
+ {
+ "role": "user",
+ "content": self.user_prompt[language].format(
+ facts=facts_str,
+ conversation=conversation_str,
+ ),
+ },
]
result = ""
diff --git a/apps/llm/patterns/facts.py b/apps/llm/patterns/facts.py
index 0b0381ff40a0e6632fd204c9efcf834a13c4711f..46707d1158f886e5e4fc12ddacef508183eb9b70 100644
--- a/apps/llm/patterns/facts.py
+++ b/apps/llm/patterns/facts.py
@@ -9,6 +9,7 @@ from apps.llm.function import JsonGenerator
from apps.llm.patterns.core import CorePattern
from apps.llm.reasoning import ReasoningLLM
from apps.llm.snippet import convert_context_to_prompt
+from apps.schemas.enum_var import LanguageType
logger = logging.getLogger(__name__)
@@ -22,62 +23,110 @@ class FactsResult(BaseModel):
class Facts(CorePattern):
"""事实提取"""
- system_prompt: str = "You are a helpful assistant."
- """系统提示词(暂不使用)"""
-
- user_prompt: str = r"""
-
-
- 从对话中提取关键信息,并将它们组织成独一无二的、易于理解的事实,包含用户偏好、关系、实体等有用信息。
- 以下是需要关注的信息类型以及有关如何处理输入数据的详细说明。
-
- **你需要关注的信息类型**
- 1. 实体:对话中涉及到的实体。例如:姓名、地点、组织、事件等。
- 2. 偏好:对待实体的态度。例如喜欢、讨厌等。
- 3. 关系:用户与实体之间,或两个实体之间的关系。例如包含、并列、互斥等。
- 4. 动作:对实体产生影响的具体动作。例如查询、搜索、浏览、点击等。
-
- **要求**
- 1. 事实必须准确,只能从对话中提取。不要将样例中的信息体现在输出中。
- 2. 事实必须清晰、简洁、易于理解。必须少于30个字。
- 3. 必须按照以下JSON格式输出:
-
- {{
- "facts": ["事实1", "事实2", "事实3"]
- }}
-
-
-
-
- 杭州西湖有哪些景点?
- 杭州西湖是中国浙江省杭州市的一个著名景点,以其美丽的自然风光和丰富的文化遗产而闻名。西湖周围有许多著名的景点,包括著名的苏堤、白堤、断桥、三潭印月等。西湖以其清澈的湖水和周围的山脉而著名,是中国最著名的湖泊之一。
-
-
-
-
-
-
- {conversation}
-