feat: init
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
LLM_API_KEY=replace-with-your-api-key
|
||||||
|
LLM_MODEL=replace-with-your-model-name
|
||||||
|
# Optional: your provider endpoint
|
||||||
|
# LLM_BASE_URL=https://your-provider.example/v1
|
||||||
|
EMBEDDING_BATCH_SIZE=32
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
.idea/
|
||||||
|
.jbeval/
|
||||||
|
.pytest_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from functools import cache
|
||||||
|
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def get_llm() -> ChatOpenAI:
|
||||||
|
return ChatOpenAI(
|
||||||
|
model=settings.llm_model,
|
||||||
|
api_key=settings.llm_api_key,
|
||||||
|
base_url=settings.llm_base_url,
|
||||||
|
timeout=settings.llm_timeout,
|
||||||
|
max_retries=settings.llm_max_retries,
|
||||||
|
)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import os
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Settings:
|
||||||
|
# LLM 配置
|
||||||
|
llm_api_key: str = field(
|
||||||
|
default_factory=lambda: os.environ["LLM_API_KEY"],
|
||||||
|
repr=False,
|
||||||
|
)
|
||||||
|
llm_model: str = field(default_factory=lambda: os.environ["LLM_MODEL"])
|
||||||
|
llm_base_url: str | None = field(
|
||||||
|
default_factory=lambda: os.getenv("LLM_BASE_URL") or None
|
||||||
|
)
|
||||||
|
llm_timeout: float = 60.0
|
||||||
|
llm_max_retries: int = 2
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
class AppError(Exception):
|
||||||
|
"""应用业务异常。"""
|
||||||
|
|
||||||
|
|
||||||
|
class ImportProcessError(AppError):
|
||||||
|
"""导入流程异常,保留节点名称和原始异常信息"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, message: str, node_name: str = "", cause: Exception | None = None
|
||||||
|
):
|
||||||
|
self.node_name = node_name
|
||||||
|
self.cause = cause
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
parts = []
|
||||||
|
if self.node_name:
|
||||||
|
parts.append(f"[{self.node_name}]")
|
||||||
|
parts.append(super().__str__())
|
||||||
|
if self.cause is not None:
|
||||||
|
parts.append(f"(原因: {self.cause})")
|
||||||
|
return " ".join(parts)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
from app.config import Settings, settings
|
||||||
|
from app.exceptions import AppError, ImportProcessError
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class BaseNode(ABC):
|
||||||
|
name: str = "base_node"
|
||||||
|
|
||||||
|
def __init__(self, config: Settings | None = None):
|
||||||
|
"""传入配置时使用传入值,否则使用全局配置"""
|
||||||
|
self.config = config if config is not None else settings
|
||||||
|
self.logger = logging.getLogger(f"import.{self.name}")
|
||||||
|
|
||||||
|
def __call__(self, state: T) -> T:
|
||||||
|
"""执行节点,统一记录日志、耗时并保留业务异常类型"""
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
self.logger.info("--- %s 开始 ---", self.name)
|
||||||
|
try:
|
||||||
|
result = self.process(state)
|
||||||
|
except AppError:
|
||||||
|
self.logger.exception("%s 执行失败", self.name)
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.exception("%s 执行失败", self.name)
|
||||||
|
raise ImportProcessError(str(e), node_name=self.name) from e
|
||||||
|
else:
|
||||||
|
self.logger.info("--- %s 完成 ---", self.name)
|
||||||
|
return result
|
||||||
|
finally:
|
||||||
|
self.logger.info(
|
||||||
|
"%s 耗时 %.3f 秒", self.name, time.perf_counter() - start_time
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def process(self, state: T) -> T:
|
||||||
|
"""子类实现节点核心逻辑,返回状态更新"""
|
||||||
|
|
||||||
|
def log_step(self, step_name: str, message: str = ""):
|
||||||
|
"""记录步骤日志。"""
|
||||||
|
log_msg = f"[{step_name}]"
|
||||||
|
if message:
|
||||||
|
log_msg += f" {message}"
|
||||||
|
self.logger.info(log_msg)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# 这是一个示例 Python 脚本。
|
||||||
|
|
||||||
|
# 按 Shift+F10 执行或将其替换为您的代码。
|
||||||
|
# 按 双击 Shift 在所有地方搜索类、文件、工具窗口、操作和设置。
|
||||||
|
|
||||||
|
|
||||||
|
def print_hi(name):
|
||||||
|
# 在下面的代码行中使用断点来调试脚本。
|
||||||
|
print(f'Hi, {name}') # 按 Ctrl+F8 切换断点。
|
||||||
|
|
||||||
|
|
||||||
|
# 按装订区域中的绿色按钮以运行脚本。
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print_hi('PyCharm')
|
||||||
|
|
||||||
|
# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "my-v0-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"langchain-openai>=1.6.2",
|
||||||
|
"langgraph>=1.2.12",
|
||||||
|
"python-dotenv>=1.2.3",
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user