完成和大模型的通信
This commit is contained in:
@@ -5,20 +5,26 @@ from pathlib import Path
|
||||
|
||||
CONFIG_PATH = Path(__file__).parent.parent / "config.json"
|
||||
|
||||
DEFAULT_CONFIG: dict[str, str] = {
|
||||
DEFAULT_CONFIG: dict = {
|
||||
"language": "Chinese",
|
||||
"raw_dir": "raw",
|
||||
"wiki_dir": "wiki",
|
||||
}
|
||||
|
||||
DEFAULT_LLM_CONFIG: dict = {
|
||||
"model": "qwen/qwen3.6-27b",
|
||||
"api_base": "http://100.123.83.113:1234/v1",
|
||||
"api_key": "123456",
|
||||
}
|
||||
|
||||
def load_config() -> dict[str, str]:
|
||||
|
||||
def load_config() -> dict:
|
||||
"""
|
||||
加载项目配置。
|
||||
|
||||
返回
|
||||
-------
|
||||
dict[str, str]
|
||||
dict
|
||||
配置字典,包含 language、raw_dir、wiki_dir 等键。
|
||||
"""
|
||||
if CONFIG_PATH.is_file():
|
||||
@@ -38,3 +44,19 @@ def get_language() -> str | None:
|
||||
if language.lower() == "auto":
|
||||
return None
|
||||
return language
|
||||
|
||||
|
||||
def get_llm_config() -> dict[str, str]:
|
||||
"""
|
||||
获取 LLM 配置。
|
||||
|
||||
返回
|
||||
-------
|
||||
dict[str, str]
|
||||
包含 model, api_base, api_key 的字典。
|
||||
"""
|
||||
config = load_config()
|
||||
llm_raw = config.get("llm", {})
|
||||
# 过滤空值,避免空字符串覆盖默认配置
|
||||
llm_filtered = {k: v for k, v in llm_raw.items() if v}
|
||||
return {**DEFAULT_LLM_CONFIG, **llm_filtered}
|
||||
|
||||
86
src/llm.py
Normal file
86
src/llm.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""LLM client wrapper."""
|
||||
|
||||
import logging
|
||||
|
||||
from prompt import build_analysis_prompt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _create_llm():
|
||||
"""根据配置文件创建 LLM 实例。"""
|
||||
from config import get_llm_config
|
||||
|
||||
from llama_index.llms.openai_like import OpenAILike
|
||||
|
||||
llm_config = get_llm_config()
|
||||
return OpenAILike(
|
||||
model=llm_config["model"],
|
||||
api_base=llm_config["api_base"],
|
||||
api_key=llm_config.get("api_key", "lm-studio"),
|
||||
temperature=0.1,
|
||||
max_tokens=128000,
|
||||
request_timeout=300.0,
|
||||
is_chat_model=True,
|
||||
)
|
||||
|
||||
|
||||
def query_analysis(
|
||||
purpose: str,
|
||||
index: str,
|
||||
source_content: str = "",
|
||||
configured_language: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
构建分析提示并发送给 LLM,返回分析报告。
|
||||
|
||||
参数
|
||||
----------
|
||||
purpose : str
|
||||
wiki/purpose.md 的内容。
|
||||
index : str
|
||||
wiki/index.md 的内容。
|
||||
source_content : str
|
||||
源文档文本。
|
||||
configured_language : str | None
|
||||
显式覆盖语言设置。
|
||||
|
||||
返回
|
||||
-------
|
||||
str
|
||||
LLM 返回的分析报告。
|
||||
"""
|
||||
from config import get_llm_config
|
||||
|
||||
from llama_index.core.llms import ChatMessage
|
||||
|
||||
system_prompt = build_analysis_prompt(
|
||||
purpose,
|
||||
index,
|
||||
source_content=source_content,
|
||||
configured_language=configured_language,
|
||||
)
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="system", content=system_prompt),
|
||||
ChatMessage(
|
||||
role="user",
|
||||
content=f"Analyze this source document:\n\n---\n\n{source_content}",
|
||||
),
|
||||
]
|
||||
|
||||
llm_config = get_llm_config()
|
||||
llm = _create_llm()
|
||||
logger.info("开始请求 LLM (model=%s, base=%s)", llm_config["model"], llm_config["api_base"])
|
||||
try:
|
||||
parts = []
|
||||
for resp in llm.stream_chat(messages):
|
||||
delta = resp.delta
|
||||
if delta:
|
||||
parts.append(delta)
|
||||
text = "".join(parts)
|
||||
logger.info("LLM 请求完成,响应总长度: %d 字符", len(text))
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.error("LLM 请求失败: %s", e)
|
||||
raise
|
||||
188
src/main.py
188
src/main.py
@@ -1,115 +1,93 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# 辅助函数
|
||||
# ---------------------------------------------------------------------------
|
||||
"""
|
||||
Unified entry point.
|
||||
|
||||
Re-exports from sub-modules for backward compatibility.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from prompt import build_analysis_prompt, language_rule
|
||||
from llm import query_analysis
|
||||
|
||||
__all__ = ["language_rule", "build_analysis_prompt", "query_analysis"]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def language_rule(source_content: str, configured_language: str | None = None) -> str:
|
||||
"""
|
||||
生成语言规则提示。
|
||||
def _setup_logging() -> None:
|
||||
"""配置日志格式和级别,同时输出到终端和文件。"""
|
||||
from pathlib import Path
|
||||
|
||||
参数
|
||||
----------
|
||||
source_content : str
|
||||
用于语言检测回退的源文档文本。
|
||||
configured_language : str | None
|
||||
明确配置的输出语言(如 "Chinese"、"English"、"auto")。
|
||||
传入 ``None`` 或 ``"auto"`` 以自动检测。
|
||||
log_path = Path(__file__).parent.parent / "llmwiki.log"
|
||||
|
||||
返回
|
||||
-------
|
||||
str
|
||||
语言规则提示字符串。
|
||||
"""
|
||||
if configured_language and configured_language.lower() != "auto":
|
||||
return f"使用 {configured_language} 语言输出分析报告。"
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s [%(levelname)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
# 简单的语言检测回退
|
||||
if source_content and any("\u4e00" <= char <= "\u9fff" for char in source_content[:500]):
|
||||
return "源文档包含中文内容,使用简体中文输出分析报告。"
|
||||
file_handler = logging.FileHandler(log_path, encoding="utf-8")
|
||||
file_handler.setFormatter(formatter)
|
||||
|
||||
return "使用与源文档相同的语言输出分析报告。"
|
||||
stream_handler = logging.StreamHandler()
|
||||
stream_handler.setFormatter(formatter)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
handlers=[file_handler, stream_handler],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 主函数:build_analysis_prompt
|
||||
# ---------------------------------------------------------------------------
|
||||
def main() -> None:
|
||||
"""执行入口:遍历 raw 目录,对每个源文件进行分析。"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_setup_logging()
|
||||
|
||||
from config import load_config
|
||||
|
||||
config = load_config()
|
||||
raw_dir = Path(config.get("raw_dir", "raw"))
|
||||
wiki_dir = Path(config.get("wiki_dir", "wiki"))
|
||||
|
||||
# 读取 wiki 上下文
|
||||
purpose_path = wiki_dir / "purpose.md"
|
||||
index_path = wiki_dir / "index.md"
|
||||
|
||||
purpose = purpose_path.read_text(encoding="utf-8") if purpose_path.exists() else ""
|
||||
index = index_path.read_text(encoding="utf-8") if index_path.exists() else ""
|
||||
|
||||
logger.info("purpose.md: %s", "已加载" if purpose else "未找到")
|
||||
logger.info("index.md: %s", "已加载" if index else "未找到")
|
||||
|
||||
# 收集 raw 目录下的 .md 文件
|
||||
md_files = sorted(raw_dir.rglob("*.md"))
|
||||
if not md_files:
|
||||
logger.info("未找到 %s 下的 .md 文件", raw_dir)
|
||||
sys.exit(0)
|
||||
|
||||
logger.info("共发现 %d 个源文件", len(md_files))
|
||||
|
||||
for fp in md_files:
|
||||
source_content = fp.read_text(encoding="utf-8")
|
||||
logger.info("=" * 60)
|
||||
logger.info("分析: %s", fp.relative_to(raw_dir.parent))
|
||||
logger.info("=" * 60)
|
||||
|
||||
try:
|
||||
result = query_analysis(
|
||||
purpose=purpose,
|
||||
index=index,
|
||||
source_content=source_content,
|
||||
)
|
||||
logger.info("分析完成,结果长度: %d 字符", len(result))
|
||||
logger.info("结果: %s", result)
|
||||
except Exception as e:
|
||||
logger.error("分析失败: %s", e, exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def build_analysis_prompt(
|
||||
purpose: str,
|
||||
index: str,
|
||||
source_content: str = "",
|
||||
configured_language: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
第一步提示:AI 阅读源文档并生成结构化分析报告。
|
||||
这是「讨论」步骤——AI 在撰写 wiki 页面之前先对源文档进行推理。
|
||||
|
||||
参数
|
||||
----------
|
||||
purpose : str
|
||||
wiki/purpose.md 的内容(可能为空)。
|
||||
index : str
|
||||
wiki/index.md 的内容(可能为空)。
|
||||
source_content : str
|
||||
用于语言检测回退的源文档文本。
|
||||
configured_language : str | None
|
||||
显式覆盖语言设置。传入 ``None`` 时从配置文件读取。
|
||||
|
||||
返回
|
||||
-------
|
||||
str
|
||||
完整的系统提示字符串。
|
||||
"""
|
||||
from config import get_language
|
||||
|
||||
if configured_language is None:
|
||||
configured_language = get_language()
|
||||
parts: list[str] = [
|
||||
"你是一位专业研究分析师。阅读源文档并产出一份结构化的分析报告。",
|
||||
"不要输出思维链、隐藏推理或思考过程。在内部进行推理,只撰写简洁的最终分析。",
|
||||
"",
|
||||
language_rule(source_content, configured_language),
|
||||
"",
|
||||
"你的分析需要包含:",
|
||||
"",
|
||||
"## 关键实体",
|
||||
"列出文档中提到的人物、组织、产品、数据集、工具、芯片、算法等。对于每一项:",
|
||||
"- 名称和类型",
|
||||
"- 在源文档中的角色(核心还是边缘)",
|
||||
"- 是否可能已存在于 wiki 中(查阅索引)",
|
||||
"",
|
||||
"## 关键概念",
|
||||
"列出理论、方法、技术、现象。对于每一项:",
|
||||
"- 名称和简要定义",
|
||||
"- 为什么在源文档中重要",
|
||||
"- 是否可能已存在于 wiki 中",
|
||||
"",
|
||||
"## 主要论点与发现",
|
||||
"- 核心主张或结果是什么?",
|
||||
"- 有什么证据支持这些主张?",
|
||||
"- 证据的强度如何?",
|
||||
"",
|
||||
"## 与现有 Wiki 的关联",
|
||||
"- 源文档与哪些现有页面相关?",
|
||||
"- 它是否强化、挑战或扩展了现有知识?",
|
||||
"",
|
||||
"## 矛盾与张力",
|
||||
"- 源文档中是否有任何内容与现有 wiki 内容冲突?",
|
||||
"- 是否存在内部矛盾或注意事项?",
|
||||
"",
|
||||
"## 建议",
|
||||
"- 应该创建或更新哪些 wiki 页面?",
|
||||
"- 应该强调什么,弱化什么?",
|
||||
"- 是否有任何需要向用户指出的开放性问题?",
|
||||
"",
|
||||
"分析要全面但简洁,专注于真正重要的内容。",
|
||||
"",
|
||||
"如果提供了文件夹上下文,请将其作为分类的提示——文件夹结构通常反映了用户的组织意图(例如,'papers/energy' 表示该文件是与能源相关的论文)。",
|
||||
"",
|
||||
f"## Wiki 目的(供参考)\n{purpose}" if purpose else "",
|
||||
(f"## 当前 Wiki 索引(用于检查现有内容)\n{index}" if index else ""),
|
||||
]
|
||||
|
||||
# 过滤空字符串并用换行符连接
|
||||
return "\n".join(part for part in parts if part)
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
108
src/prompt.py
Normal file
108
src/prompt.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Analysis prompt builder."""
|
||||
|
||||
|
||||
def language_rule(source_content: str, configured_language: str | None = None) -> str:
|
||||
"""
|
||||
生成语言规则提示。
|
||||
|
||||
参数
|
||||
----------
|
||||
source_content : str
|
||||
用于语言检测回退的源文档文本。
|
||||
configured_language : str | None
|
||||
明确配置的输出语言(如 "Chinese"、"English"、"auto")。
|
||||
传入 ``None`` 或 ``"auto"`` 以自动检测。
|
||||
|
||||
返回
|
||||
-------
|
||||
str
|
||||
语言规则提示字符串。
|
||||
"""
|
||||
if configured_language and configured_language.lower() != "auto":
|
||||
return f"使用 {configured_language} 语言输出分析报告。"
|
||||
|
||||
# 简单的语言检测回退
|
||||
if source_content and any("\u4e00" <= char <= "\u9fff" for char in source_content[:500]):
|
||||
return "源文档包含中文内容,使用简体中文输出分析报告。"
|
||||
|
||||
return "使用与源文档相同的语言输出分析报告。"
|
||||
|
||||
|
||||
def build_analysis_prompt(
|
||||
purpose: str,
|
||||
index: str,
|
||||
source_content: str = "",
|
||||
configured_language: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
第一步提示:AI 阅读源文档并生成结构化分析报告。
|
||||
这是「讨论」步骤——AI 在撰写 wiki 页面之前先对源文档进行推理。
|
||||
|
||||
参数
|
||||
----------
|
||||
purpose : str
|
||||
wiki/purpose.md 的内容(可能为空)。
|
||||
index : str
|
||||
wiki/index.md 的内容(可能为空)。
|
||||
source_content : str
|
||||
用于语言检测回退的源文档文本。
|
||||
configured_language : str | None
|
||||
显式覆盖语言设置。传入 ``None`` 时从配置文件读取。
|
||||
|
||||
返回
|
||||
-------
|
||||
str
|
||||
完整的系统提示字符串。
|
||||
"""
|
||||
from config import get_language
|
||||
|
||||
if configured_language is None:
|
||||
configured_language = get_language()
|
||||
parts: list[str] = [
|
||||
"你是一位专业研究分析师。阅读源文档并产出一份结构化的分析报告。",
|
||||
"不要输出思维链、隐藏推理或思考过程。在内部进行推理,只撰写简洁的最终分析。",
|
||||
"",
|
||||
language_rule(source_content, configured_language),
|
||||
"",
|
||||
"你的分析需要包含:",
|
||||
"",
|
||||
"## 关键实体",
|
||||
"列出文档中提到的人物、组织、产品、数据集、工具、芯片、算法等。对于每一项:",
|
||||
"- 名称和类型",
|
||||
"- 在源文档中的角色(核心还是边缘)",
|
||||
"- 是否可能已存在于 wiki 中(查阅索引)",
|
||||
"",
|
||||
"## 关键概念",
|
||||
"列出理论、方法、技术、现象。对于每一项:",
|
||||
"- 名称和简要定义",
|
||||
"- 为什么在源文档中重要",
|
||||
"- 是否可能已存在于 wiki 中",
|
||||
"",
|
||||
"## 主要论点与发现",
|
||||
"- 核心主张或结果是什么?",
|
||||
"- 有什么证据支持这些主张?",
|
||||
"- 证据的强度如何?",
|
||||
"",
|
||||
"## 与现有 Wiki 的关联",
|
||||
"- 源文档与哪些现有页面相关?",
|
||||
"- 它是否强化、挑战或扩展了现有知识?",
|
||||
"",
|
||||
"## 矛盾与张力",
|
||||
"- 源文档中是否有任何内容与现有 wiki 内容冲突?",
|
||||
"- 是否存在内部矛盾或注意事项?",
|
||||
"",
|
||||
"## 建议",
|
||||
"- 应该创建或更新哪些 wiki 页面?",
|
||||
"- 应该强调什么,弱化什么?",
|
||||
"- 是否有任何需要向用户指出的开放性问题?",
|
||||
"",
|
||||
"分析要全面但简洁,专注于真正重要的内容。",
|
||||
"",
|
||||
"如果提供了文件夹上下文,请将其作为分类的提示——文件夹结构通常反映了用户的组织意图(例如,'papers/energy' 表示该文件是与能源相关的论文)。",
|
||||
"",
|
||||
f"## Wiki 目的(供参考)\n{purpose}" if purpose else "",
|
||||
(f"## 当前 Wiki 索引(用于检查现有内容)\n{index}" if index else ""),
|
||||
]
|
||||
|
||||
# 过滤空字符串并用换行符连接
|
||||
return "\n".join(part for part in parts if part)
|
||||
Reference in New Issue
Block a user