style: 项目重构
1.项目改名为kilostar(千星) 2.后端部分进行大规模重构 3.node功能进行大规模重新设计
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# Copyright 2026 zhaoxi826
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright 2026 zhaoxi826
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Copyright 2026 zhaoxi826
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai.models.openai import OpenAIChatModel
|
||||
from pydantic_ai.models.anthropic import AnthropicModel
|
||||
from pydantic_ai.providers.openai import OpenAIProvider
|
||||
from pydantic_ai.providers.anthropic import AnthropicProvider
|
||||
from kilostar.adapter.model_adapter.deepseek_reasoner import DeepSeekReasonerAgent
|
||||
from kilostar.core.global_state_machine.model_provider import Provider
|
||||
from kilostar.utils.agent_model import ResponseModel, DepsModel
|
||||
from kilostar.utils.error import ModelNotExistError
|
||||
|
||||
|
||||
class AgentFactory:
|
||||
"""AgentFactory 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 AgentFactory 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
def __init__(self):
|
||||
self._models_mapping = {
|
||||
"openai": (OpenAIChatModel, OpenAIProvider),
|
||||
"claude": (AnthropicModel, AnthropicProvider),
|
||||
"deepseek": (OpenAIChatModel, OpenAIProvider),
|
||||
}
|
||||
|
||||
def create_agent(
|
||||
self,
|
||||
provider: Provider,
|
||||
model_id: str,
|
||||
output_type: ResponseModel,
|
||||
system_prompt: str,
|
||||
deps_type: DepsModel,
|
||||
agent_name: str,
|
||||
tools: list = None,
|
||||
) -> Agent:
|
||||
"""
|
||||
create_agent方法,将输入的provider对象实例化为一个pydantic-ai的agent对象
|
||||
|
||||
Args:
|
||||
provider: Provider对象,从global_state_machine中获取
|
||||
model_id: 模型名
|
||||
output_type: 输出格式
|
||||
system_prompt: 系统提示词
|
||||
deps_type: 依赖类型,在agent运行时动态输入的格式化消息
|
||||
agent_name: agent的名字
|
||||
tools: 工具列表
|
||||
|
||||
Returns:
|
||||
返回被实例化的pydantic-ai的Agent对象
|
||||
"""
|
||||
if model_id not in provider.provider_models:
|
||||
raise ModelNotExistError("模型不存在")
|
||||
if provider.provider_type not in self._models_mapping:
|
||||
raise ValueError(f"不支持的协议类型: {provider.provider_type}")
|
||||
model_class, provider_class = self._models_mapping[provider.provider_type]
|
||||
model = model_class(
|
||||
model_id,
|
||||
provider=provider_class(
|
||||
api_key=provider.provider_apikey, base_url=provider.provider_url
|
||||
),
|
||||
)
|
||||
match provider.provider_type:
|
||||
case "deepseek":
|
||||
agent = DeepSeekReasonerAgent(
|
||||
model=model,
|
||||
name=agent_name,
|
||||
output_type=output_type,
|
||||
deps_type=deps_type,
|
||||
system_prompt=system_prompt,
|
||||
tools=tools,
|
||||
retries=3,
|
||||
)
|
||||
case _:
|
||||
agent = Agent(
|
||||
model=model,
|
||||
name=agent_name,
|
||||
system_prompt=system_prompt,
|
||||
output_type=output_type,
|
||||
deps_type=deps_type,
|
||||
tools=tools,
|
||||
)
|
||||
return agent
|
||||
@@ -0,0 +1,193 @@
|
||||
# Copyright 2026 zhaoxi826
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
import json
|
||||
from typing import Type, TypeVar, Any, Generic
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from pydantic_ai import Agent
|
||||
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
|
||||
|
||||
class AgentRunResultProxy:
|
||||
"""AgentRunResultProxy 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 AgentRunResultProxy 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
def __init__(self, original, parsed):
|
||||
self._original = original
|
||||
self._parsed = parsed
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""检索并获取特定的 getattr 数据集合或实例对象。
|
||||
根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。
|
||||
Args: name: 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。
|
||||
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
if name == "data":
|
||||
return self._parsed
|
||||
if name == "output":
|
||||
return self._parsed
|
||||
return getattr(self._original, name)
|
||||
|
||||
|
||||
class DeepSeekReasonerAgent(Generic[T]):
|
||||
"""
|
||||
专为 DeepSeek-V4/R1 设计的适配器。
|
||||
将结构化输出降级为文本解析模式,并支持重试逻辑以确保系统兼容性。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
name,
|
||||
output_type: Any = str,
|
||||
system_prompt: str = "",
|
||||
deps_type: Type[Any] = None,
|
||||
tools: list = None,
|
||||
retries: int = 3,
|
||||
**kwargs,
|
||||
):
|
||||
self.output_schema = output_type
|
||||
self.has_custom_output = output_type is not str and output_type is not None
|
||||
self.tools = tools or []
|
||||
self.retries = retries
|
||||
|
||||
format_instruction = ""
|
||||
if self.has_custom_output:
|
||||
try:
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
schema_dict = TypeAdapter(self.output_schema).json_schema()
|
||||
schema_str = json.dumps(schema_dict, ensure_ascii=False)
|
||||
format_instruction = (
|
||||
f"\n\nCRITICAL: 你必须输出且只能输出一段纯 JSON 格式的数据,"
|
||||
f"并包裹在 ```json 和 ``` 之间。格式必须符合以下 JSON Schema 结构(或对应数据类型):\n"
|
||||
f"{schema_str}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
tool_instruction = ""
|
||||
if self.tools:
|
||||
tool_descs = []
|
||||
for t in self.tools:
|
||||
desc = getattr(t, "__name__", str(t))
|
||||
if hasattr(t, "__doc__") and t.__doc__:
|
||||
desc += f": {t.__doc__.strip()}"
|
||||
tool_descs.append(f"- {desc}")
|
||||
tool_instruction = (
|
||||
"\n\n系统为您提供了以下工具。由于当前处于结构化降级模式,无法原生调用。"
|
||||
"但如果您在思考过程中判断必须使用这些工具,请在返回的结构中(或如果是自由文本)注明意图,由外层逻辑进行调度:\n"
|
||||
+ "\n".join(tool_descs)
|
||||
)
|
||||
|
||||
self.agent = Agent(
|
||||
model=model,
|
||||
name=name,
|
||||
output_type=str, # Force native agent to return str to disable function calling
|
||||
system_prompt=system_prompt + format_instruction + tool_instruction,
|
||||
deps_type=deps_type,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _parse_output(self, text: str) -> Any:
|
||||
"""执行与 parse output 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Args: text (str): 控制逻辑流向的具体字符串参数,指定了期望的 text 内容。
|
||||
Returns: (Any): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
if not self.has_custom_output:
|
||||
return text
|
||||
|
||||
match = re.search(r"```json\s*(.*?)\s*```", text, re.DOTALL)
|
||||
json_str = match.group(1).strip() if match else text
|
||||
|
||||
if not json_str.startswith("{") and not json_str.startswith("["):
|
||||
start_obj = json_str.find("{")
|
||||
start_arr = json_str.find("[")
|
||||
start = -1
|
||||
end = -1
|
||||
if start_obj != -1 and (start_arr == -1 or start_obj < start_arr):
|
||||
start = start_obj
|
||||
end = json_str.rfind("}")
|
||||
elif start_arr != -1:
|
||||
start = start_arr
|
||||
end = json_str.rfind("]")
|
||||
|
||||
if start != -1 and end != -1 and end > start:
|
||||
json_str = json_str[start : end + 1]
|
||||
|
||||
if not json_str:
|
||||
raise ValueError("未找到有效的 JSON 块。请将结果包装在 ```json 中。")
|
||||
|
||||
try:
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
adapter = TypeAdapter(self.output_schema)
|
||||
return adapter.validate_json(json_str)
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"返回的 JSON 无法匹配所需结构:{e}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"返回的不是合法的 JSON:{e}")
|
||||
|
||||
def __getattr__(self, item):
|
||||
# Delegate any unknown attributes (like .system_prompt, .tool) to the underlying pydantic_ai Agent
|
||||
"""检索并获取特定的 getattr 数据集合或实例对象。
|
||||
根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。
|
||||
Args: item: 参与 getattr 逻辑运算或数据构建的上下文依赖对象。
|
||||
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
return getattr(self.agent, item)
|
||||
|
||||
async def run(
|
||||
self, user_prompt: str, deps: Any = None, message_history: list = None, **kwargs
|
||||
) -> Any:
|
||||
# Custom retry loop
|
||||
"""执行与 run 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Args: user_prompt (str): 控制逻辑流向的具体字符串参数,指定了期望的 user prompt 内容。 deps (Any): 参与 run 逻辑运算或数据构建的上下文依赖对象。 message_history (list): 批量操作所需的列表集合,囊括了需要统一处理的多个 message history 元素。
|
||||
Returns: (Any): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
current_history = message_history or []
|
||||
last_exception = None
|
||||
|
||||
for attempt in range(self.retries + 1):
|
||||
result = await self.agent.run(
|
||||
user_prompt, deps=deps, message_history=current_history, **kwargs
|
||||
)
|
||||
|
||||
raw_text = (
|
||||
result.data
|
||||
if hasattr(result, "data")
|
||||
else getattr(result, "output", str(result))
|
||||
)
|
||||
|
||||
try:
|
||||
parsed_data = self._parse_output(raw_text)
|
||||
|
||||
# Proxy the result to inject the parsed data seamlessly
|
||||
return AgentRunResultProxy(result, parsed_data)
|
||||
|
||||
except ValueError as e:
|
||||
last_exception = e
|
||||
# Prepare retry prompt
|
||||
user_prompt = (
|
||||
f"你的上一次输出解析失败,错误原因是: {e}\n请修正格式后重新输出。"
|
||||
)
|
||||
|
||||
# We need to maintain history manually so the model sees what it did wrong
|
||||
# Actually, pydantic-ai manages history inside the result. Let's use the all_messages from result
|
||||
if hasattr(result, "all_messages"):
|
||||
current_history = result.all_messages()
|
||||
|
||||
raise ValueError(
|
||||
f"Exceeded maximum retries ({self.retries}) for output validation. Last error: {last_exception}"
|
||||
)
|
||||
Reference in New Issue
Block a user