semantic_similarity.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. #!/usr/bin/env python3
  2. """
  3. 语义相似度分析模块
  4. 使用 AI Agent 判断两个短语之间的语义相似度
  5. """
  6. from agents import Agent, Runner, ModelSettings
  7. from lib.client import get_model
  8. from lib.utils import parse_json_from_text
  9. from lib.config import get_cache_dir
  10. from typing import Dict, Any, Optional, List, Tuple
  11. import hashlib
  12. import json
  13. import os
  14. from datetime import datetime
  15. from pathlib import Path
  16. import asyncio
  17. import numpy as np
  18. # 默认提示词模板
  19. DEFAULT_PROMPT_TEMPLATE = """
  20. 从语意角度,判断"{phrase_a}"和"{phrase_b}"这两个短语的相似度,从0-1打分,输出格式如下:
  21. ```json
  22. {{
  23. "说明": "简明扼要说明理由",
  24. "相似度": 0.0,
  25. }}
  26. ```
  27. """.strip()
  28. def _get_default_cache_dir() -> str:
  29. """获取默认缓存目录(从配置中读取)"""
  30. return get_cache_dir("semantic_similarity")
  31. def _generate_cache_key(
  32. phrase_a: str,
  33. phrase_b: str,
  34. model_name: str,
  35. temperature: float,
  36. max_tokens: int,
  37. prompt_template: str,
  38. instructions: str = None,
  39. tools: str = "[]"
  40. ) -> str:
  41. """
  42. 生成缓存键(哈希值)
  43. Args:
  44. phrase_a: 第一个短语
  45. phrase_b: 第二个短语
  46. model_name: 模型名称
  47. temperature: 温度参数
  48. max_tokens: 最大token数
  49. prompt_template: 提示词模板
  50. instructions: Agent 系统指令
  51. tools: 工具列表的 JSON 字符串
  52. Returns:
  53. 32位MD5哈希值
  54. """
  55. # 创建包含所有参数的字符串
  56. cache_string = f"{phrase_a}||{phrase_b}||{model_name}||{temperature}||{max_tokens}||{prompt_template}||{instructions}||{tools}"
  57. # 生成MD5哈希
  58. return hashlib.md5(cache_string.encode('utf-8')).hexdigest()
  59. def _sanitize_for_filename(text: str, max_length: int = 30) -> str:
  60. """
  61. 将文本转换为安全的文件名部分
  62. Args:
  63. text: 原始文本
  64. max_length: 最大长度
  65. Returns:
  66. 安全的文件名字符串
  67. """
  68. import re
  69. # 移除特殊字符,只保留中文、英文、数字、下划线
  70. sanitized = re.sub(r'[^\w\u4e00-\u9fff]', '_', text)
  71. # 移除连续的下划线
  72. sanitized = re.sub(r'_+', '_', sanitized)
  73. # 截断到最大长度
  74. if len(sanitized) > max_length:
  75. sanitized = sanitized[:max_length]
  76. return sanitized.strip('_')
  77. def _get_cache_filepath(
  78. cache_key: str,
  79. phrase_a: str,
  80. phrase_b: str,
  81. model_name: str,
  82. temperature: float,
  83. cache_dir: Optional[str] = None
  84. ) -> Path:
  85. """
  86. 获取缓存文件路径(可读文件名)
  87. Args:
  88. cache_key: 缓存键(哈希值)
  89. phrase_a: 第一个短语
  90. phrase_b: 第二个短语
  91. model_name: 模型名称
  92. temperature: 温度参数
  93. cache_dir: 缓存目录
  94. Returns:
  95. 缓存文件的完整路径
  96. 文件名格式: {phrase_a}_vs_{phrase_b}_{model}_t{temp}_{hash[:8]}.json
  97. 示例: 宿命感_vs_余华的小说_gpt-4.1-mini_t0.0_a7f3e2d9.json
  98. """
  99. if cache_dir is None:
  100. cache_dir = _get_default_cache_dir()
  101. # 清理短语和模型名
  102. clean_a = _sanitize_for_filename(phrase_a, max_length=20)
  103. clean_b = _sanitize_for_filename(phrase_b, max_length=20)
  104. # 简化模型名(提取关键部分)
  105. model_short = model_name.split('/')[-1] # 例如: openai/gpt-4.1-mini -> gpt-4.1-mini
  106. model_short = _sanitize_for_filename(model_short, max_length=20)
  107. # 格式化温度参数
  108. temp_str = f"t{temperature:.1f}"
  109. # 使用哈希的前8位
  110. hash_short = cache_key[:8]
  111. # 组合文件名
  112. filename = f"{clean_a}_vs_{clean_b}_{model_short}_{temp_str}_{hash_short}.json"
  113. return Path(cache_dir) / filename
  114. def _load_from_cache(
  115. cache_key: str,
  116. phrase_a: str,
  117. phrase_b: str,
  118. model_name: str,
  119. temperature: float,
  120. cache_dir: Optional[str] = None
  121. ) -> Optional[str]:
  122. """
  123. 从缓存加载数据
  124. Args:
  125. cache_key: 缓存键
  126. phrase_a: 第一个短语
  127. phrase_b: 第二个短语
  128. model_name: 模型名称
  129. temperature: 温度参数
  130. cache_dir: 缓存目录
  131. Returns:
  132. 缓存的结果字符串,如果不存在则返回 None
  133. """
  134. if cache_dir is None:
  135. cache_dir = _get_default_cache_dir()
  136. cache_file = _get_cache_filepath(cache_key, phrase_a, phrase_b, model_name, temperature, cache_dir)
  137. # 如果文件不存在,尝试通过哈希匹配查找
  138. if not cache_file.exists():
  139. # 查找所有以该哈希结尾的文件
  140. cache_path = Path(cache_dir)
  141. if cache_path.exists():
  142. hash_short = cache_key[:8]
  143. matching_files = list(cache_path.glob(f"*_{hash_short}.json"))
  144. if matching_files:
  145. cache_file = matching_files[0]
  146. else:
  147. return None
  148. else:
  149. return None
  150. try:
  151. with open(cache_file, 'r', encoding='utf-8') as f:
  152. cached_data = json.load(f)
  153. return cached_data['output']['raw']
  154. except (json.JSONDecodeError, IOError, KeyError):
  155. return None
  156. def _save_to_cache(
  157. cache_key: str,
  158. phrase_a: str,
  159. phrase_b: str,
  160. model_name: str,
  161. temperature: float,
  162. max_tokens: int,
  163. prompt_template: str,
  164. instructions: str,
  165. tools: str,
  166. result: str,
  167. cache_dir: Optional[str] = None
  168. ) -> None:
  169. """
  170. 保存数据到缓存
  171. Args:
  172. cache_key: 缓存键
  173. phrase_a: 第一个短语
  174. phrase_b: 第二个短语
  175. model_name: 模型名称
  176. temperature: 温度参数
  177. max_tokens: 最大token数
  178. prompt_template: 提示词模板
  179. instructions: Agent 系统指令
  180. tools: 工具列表的 JSON 字符串
  181. result: 结果数据(原始字符串)
  182. cache_dir: 缓存目录
  183. """
  184. if cache_dir is None:
  185. cache_dir = _get_default_cache_dir()
  186. cache_file = _get_cache_filepath(cache_key, phrase_a, phrase_b, model_name, temperature, cache_dir)
  187. # 确保缓存目录存在
  188. cache_file.parent.mkdir(parents=True, exist_ok=True)
  189. # 尝试解析 result 为 JSON
  190. parsed_result = parse_json_from_text(result)
  191. # 准备缓存数据(包含完整的输入输出信息)
  192. cache_data = {
  193. "input": {
  194. "phrase_a": phrase_a,
  195. "phrase_b": phrase_b,
  196. "model_name": model_name,
  197. "temperature": temperature,
  198. "max_tokens": max_tokens,
  199. "prompt_template": prompt_template,
  200. "instructions": instructions,
  201. "tools": tools
  202. },
  203. "output": {
  204. "raw": result, # 保留原始响应
  205. "parsed": parsed_result # 解析后的JSON对象
  206. },
  207. "metadata": {
  208. "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  209. "cache_key": cache_key,
  210. "cache_file": str(cache_file.name)
  211. }
  212. }
  213. try:
  214. with open(cache_file, 'w', encoding='utf-8') as f:
  215. json.dump(cache_data, f, ensure_ascii=False, indent=2)
  216. except IOError:
  217. pass # 静默失败,不影响主流程
  218. async def _difference_between_phrases(
  219. phrase_a: str,
  220. phrase_b: str,
  221. model_name: str = 'openai/gpt-4.1-mini',
  222. temperature: float = 0.0,
  223. max_tokens: int = 65536,
  224. prompt_template: str = None,
  225. instructions: str = None,
  226. tools: list = None,
  227. name: str = "Semantic Similarity Analyzer",
  228. use_cache: bool = True,
  229. cache_dir: Optional[str] = None
  230. ) -> str:
  231. """
  232. 从语义角度判断两个短语的相似度
  233. Args:
  234. phrase_a: 第一个短语
  235. phrase_b: 第二个短语
  236. model_name: 使用的模型名称,可选值:
  237. - 'google/gemini-2.5-pro'
  238. - 'anthropic/claude-sonnet-4.5'
  239. - 'google/gemini-2.0-flash-001'
  240. - 'openai/gpt-5-mini'
  241. - 'anthropic/claude-haiku-4.5'
  242. - 'openai/gpt-4.1-mini' (默认)
  243. temperature: 模型温度参数,控制输出随机性,默认 0.0(确定性输出)
  244. max_tokens: 最大生成token数,默认 65536
  245. prompt_template: 自定义提示词模板,使用 {phrase_a} 和 {phrase_b} 作为占位符
  246. 如果为 None,使用默认模板
  247. instructions: Agent 的系统指令,默认为 None
  248. tools: Agent 可用的工具列表,默认为 []
  249. name: Agent 的名称,默认为 "Semantic Similarity Analyzer"(不参与缓存key构建)
  250. use_cache: 是否使用缓存,默认 True
  251. cache_dir: 缓存目录,默认从配置读取(可通过 lib.config 设置)
  252. Returns:
  253. JSON 格式的相似度分析结果字符串
  254. Examples:
  255. >>> # 使用默认模板和缓存
  256. >>> result = await difference_between_phrases("宿命感", "余华的小说")
  257. >>> print(result)
  258. {
  259. "说明": "简明扼要说明理由",
  260. "相似度": 0.0
  261. }
  262. >>> # 禁用缓存
  263. >>> result = await difference_between_phrases(
  264. ... "宿命感", "余华的小说",
  265. ... use_cache=False
  266. ... )
  267. >>> # 使用自定义模板
  268. >>> custom_template = '''
  269. ... 请分析【{phrase_a}】和【{phrase_b}】的语义关联度
  270. ... 输出格式:{{"score": 0.0, "reason": "..."}}
  271. ... '''
  272. >>> result = await difference_between_phrases(
  273. ... "宿命感", "余华的小说",
  274. ... prompt_template=custom_template
  275. ... )
  276. """
  277. # 使用自定义模板或默认模板
  278. if prompt_template is None:
  279. prompt_template = DEFAULT_PROMPT_TEMPLATE
  280. # 默认tools为空列表
  281. if tools is None:
  282. tools = []
  283. # 生成缓存键(tools转为JSON字符串以便哈希)
  284. tools_str = json.dumps(tools, sort_keys=True) if tools else "[]"
  285. cache_key = _generate_cache_key(
  286. phrase_a, phrase_b, model_name, temperature, max_tokens, prompt_template, instructions, tools_str
  287. )
  288. # 尝试从缓存加载
  289. if use_cache:
  290. cached_result = _load_from_cache(cache_key, phrase_a, phrase_b, model_name, temperature, cache_dir)
  291. if cached_result is not None:
  292. return cached_result
  293. # 缓存未命中,调用 API
  294. agent = Agent(
  295. name=name,
  296. model=get_model(model_name),
  297. model_settings=ModelSettings(
  298. temperature=temperature,
  299. max_tokens=max_tokens,
  300. ),
  301. instructions=instructions,
  302. tools=tools,
  303. )
  304. # 格式化提示词
  305. prompt = prompt_template.format(phrase_a=phrase_a, phrase_b=phrase_b)
  306. result = await Runner.run(agent, input=prompt)
  307. final_output = result.final_output
  308. # 注意:不在这里缓存,而是在解析成功后缓存
  309. # 这样可以避免缓存解析失败的响应
  310. return final_output
  311. async def _difference_between_phrases_parsed(
  312. phrase_a: str,
  313. phrase_b: str,
  314. model_name: str = 'openai/gpt-4.1-mini',
  315. temperature: float = 0.0,
  316. max_tokens: int = 65536,
  317. prompt_template: str = None,
  318. instructions: str = None,
  319. tools: list = None,
  320. name: str = "Semantic Similarity Analyzer",
  321. use_cache: bool = True,
  322. cache_dir: Optional[str] = None
  323. ) -> Dict[str, Any]:
  324. """
  325. 从语义角度判断两个短语的相似度,并解析返回结果为字典
  326. Args:
  327. phrase_a: 第一个短语
  328. phrase_b: 第二个短语
  329. model_name: 使用的模型名称
  330. temperature: 模型温度参数,控制输出随机性,默认 0.0(确定性输出)
  331. max_tokens: 最大生成token数,默认 65536
  332. prompt_template: 自定义提示词模板,使用 {phrase_a} 和 {phrase_b} 作为占位符
  333. instructions: Agent 的系统指令,默认为 None
  334. tools: Agent 可用的工具列表,默认为 []
  335. name: Agent 的名称,默认为 "Semantic Similarity Analyzer"
  336. use_cache: 是否使用缓存,默认 True
  337. cache_dir: 缓存目录,默认从配置读取(可通过 lib.config 设置)
  338. Returns:
  339. 解析后的字典,包含:
  340. - 说明: 相似度判断的理由
  341. - 相似度: 0-1之间的浮点数
  342. Raises:
  343. ValueError: 当无法解析AI响应为有效JSON时抛出
  344. Examples:
  345. >>> result = await difference_between_phrases_parsed("宿命感", "余华的小说")
  346. >>> print(result['相似度'])
  347. 0.3
  348. >>> print(result['说明'])
  349. "两个概念有一定关联..."
  350. """
  351. # 使用默认模板或自定义模板
  352. if prompt_template is None:
  353. prompt_template = DEFAULT_PROMPT_TEMPLATE
  354. # 默认tools为空列表
  355. if tools is None:
  356. tools = []
  357. # 生成缓存键
  358. tools_str = json.dumps(tools, sort_keys=True) if tools else "[]"
  359. cache_key = _generate_cache_key(
  360. phrase_a, phrase_b, model_name, temperature, max_tokens, prompt_template, instructions, tools_str
  361. )
  362. # 尝试从缓存加载
  363. if use_cache:
  364. cached_result = _load_from_cache(cache_key, phrase_a, phrase_b, model_name, temperature, cache_dir)
  365. if cached_result is not None:
  366. # 缓存命中,直接解析并返回
  367. parsed_result = parse_json_from_text(cached_result)
  368. if parsed_result:
  369. return parsed_result
  370. # 如果缓存的内容也无法解析,继续执行API调用(可能之前缓存了错误响应)
  371. # 重试机制:最多重试3次
  372. max_retries = 3
  373. last_error = None
  374. for attempt in range(max_retries):
  375. try:
  376. # 调用AI获取原始响应(不传use_cache,因为我们在这里手动处理缓存)
  377. raw_result = await _difference_between_phrases(
  378. phrase_a, phrase_b, model_name, temperature, max_tokens,
  379. prompt_template, instructions, tools, name, use_cache=False, cache_dir=cache_dir
  380. )
  381. # 使用 utils.parse_json_from_text 解析结果
  382. parsed_result = parse_json_from_text(raw_result)
  383. # 如果解析成功,缓存并返回
  384. if parsed_result:
  385. # 只有解析成功后才缓存
  386. if use_cache:
  387. _save_to_cache(
  388. cache_key, phrase_a, phrase_b, model_name,
  389. temperature, max_tokens, prompt_template,
  390. instructions, tools_str, raw_result, cache_dir
  391. )
  392. return parsed_result
  393. # 解析失败,记录错误信息,准备重试
  394. formatted_prompt = prompt_template.format(phrase_a=phrase_a, phrase_b=phrase_b)
  395. error_msg = f"""
  396. JSON解析失败 (尝试 {attempt + 1}/{max_retries})
  397. ================================================================================
  398. 短语A: {phrase_a}
  399. 短语B: {phrase_b}
  400. 模型: {model_name}
  401. 温度: {temperature}
  402. ================================================================================
  403. Prompt:
  404. {formatted_prompt}
  405. ================================================================================
  406. AI响应 (长度: {len(raw_result)}):
  407. {raw_result}
  408. ================================================================================
  409. """
  410. last_error = error_msg
  411. print(error_msg)
  412. if attempt < max_retries - 1:
  413. print(f"⚠️ 将在 1 秒后重试... (剩余重试次数: {max_retries - attempt - 1})")
  414. import asyncio
  415. await asyncio.sleep(1)
  416. except Exception as e:
  417. # 捕获其他异常(如网络错误)
  418. error_msg = f"API调用失败 (尝试 {attempt + 1}/{max_retries}): {str(e)}"
  419. last_error = error_msg
  420. print(error_msg)
  421. if attempt < max_retries - 1:
  422. print(f"⚠️ 将在 1 秒后重试... (剩余重试次数: {max_retries - attempt - 1})")
  423. import asyncio
  424. await asyncio.sleep(1)
  425. # 所有重试都失败了,抛出异常
  426. final_error = f"""
  427. 所有重试均失败!已尝试 {max_retries} 次
  428. ================================================================================
  429. 最后一次错误:
  430. {last_error}
  431. ================================================================================
  432. """
  433. raise ValueError(final_error)
  434. # ========== V1 版本(默认版本) ==========
  435. # 对外接口 - V1
  436. async def compare_phrases(
  437. phrase_a: str,
  438. phrase_b: str,
  439. model_name: str = 'openai/gpt-4.1-mini',
  440. temperature: float = 0.0,
  441. max_tokens: int = 65536,
  442. prompt_template: str = None,
  443. instructions: str = None,
  444. tools: list = None,
  445. name: str = "Semantic Similarity Analyzer",
  446. use_cache: bool = True,
  447. cache_dir: Optional[str] = None
  448. ) -> Dict[str, Any]:
  449. """
  450. 比较两个短语的语义相似度(对外唯一接口)
  451. Args:
  452. phrase_a: 第一个短语
  453. phrase_b: 第二个短语
  454. model_name: 使用的模型名称
  455. temperature: 模型温度参数,控制输出随机性,默认 0.0(确定性输出)
  456. max_tokens: 最大生成token数,默认 65536
  457. prompt_template: 自定义提示词模板,使用 {phrase_a} 和 {phrase_b} 作为占位符
  458. instructions: Agent 的系统指令,默认为 None
  459. tools: Agent 可用的工具列表,默认为 []
  460. name: Agent 的名称,默认为 "Semantic Similarity Analyzer"
  461. use_cache: 是否使用缓存,默认 True
  462. cache_dir: 缓存目录,默认从配置读取(可通过 lib.config 设置)
  463. Returns:
  464. 解析后的字典
  465. """
  466. return await _difference_between_phrases_parsed(
  467. phrase_a, phrase_b, model_name, temperature, max_tokens,
  468. prompt_template, instructions, tools, name, use_cache, cache_dir
  469. )
  470. async def compare_phrases_cartesian(
  471. phrases_a: List[str],
  472. phrases_b: List[str],
  473. max_concurrent: int = 50,
  474. progress_callback: Optional[callable] = None
  475. ) -> List[List[Dict[str, Any]]]:
  476. """
  477. 笛卡尔积批量计算:M×N并发LLM调用(带并发控制和进度回调)
  478. 用于架构统一性,内部通过并发实现(LLM无法真正批处理)
  479. Args:
  480. phrases_a: 第一组短语列表(M个)
  481. phrases_b: 第二组短语列表(N个)
  482. max_concurrent: 最大并发数,默认50
  483. progress_callback: 进度回调函数,每完成一个任务时调用
  484. Returns:
  485. 嵌套列表 List[List[Dict]],每个Dict包含完整的比较结果
  486. results[i][j] = {
  487. "相似度": float,
  488. "说明": str
  489. }
  490. Examples:
  491. >>> results = await compare_phrases_cartesian(
  492. ... ["深度学习"],
  493. ... ["神经网络", "Python"]
  494. ... )
  495. >>> print(results[0][0]['相似度']) # 深度学习 vs 神经网络
  496. >>> print(results[0][1]['说明']) # 深度学习 vs Python
  497. """
  498. # 参数验证
  499. if not phrases_a or not phrases_b:
  500. return [[]]
  501. M, N = len(phrases_a), len(phrases_b)
  502. # 创建信号量控制并发
  503. semaphore = asyncio.Semaphore(max_concurrent)
  504. async def limited_compare(phrase_a: str, phrase_b: str):
  505. async with semaphore:
  506. result = await compare_phrases(phrase_a, phrase_b)
  507. # 调用进度回调
  508. if progress_callback:
  509. progress_callback(1)
  510. return result
  511. # 创建M×N个受控的并发任务
  512. tasks = []
  513. for phrase_a in phrases_a:
  514. for phrase_b in phrases_b:
  515. tasks.append(limited_compare(phrase_a, phrase_b))
  516. # 并发执行所有任务
  517. results = await asyncio.gather(*tasks)
  518. # 返回嵌套列表结构
  519. nested_results = []
  520. for i in range(M):
  521. row_results = results[i * N : (i + 1) * N]
  522. nested_results.append(row_results)
  523. return nested_results
  524. if __name__ == "__main__":
  525. import asyncio
  526. async def main():
  527. """示例使用"""
  528. # 示例 1: 基本使用(使用缓存)
  529. print("示例 1: 基本使用")
  530. result = await compare_phrases("宿命感", "余华的小说")
  531. print(f"相似度: {result.get('相似度')}")
  532. print(f"说明: {result.get('说明')}")
  533. print()
  534. # 示例 2: 再次调用相同参数(应该从缓存读取)
  535. print("示例 2: 测试缓存")
  536. result = await compare_phrases("宿命感", "余华的小说")
  537. print(f"相似度: {result.get('相似度')}")
  538. print()
  539. # 示例 3: 自定义温度
  540. print("示例 3: 自定义温度(创意性输出)")
  541. result = await compare_phrases(
  542. "创意写作", "AI生成",
  543. temperature=0.7
  544. )
  545. print(f"相似度: {result.get('相似度')}")
  546. print(f"说明: {result.get('说明')}")
  547. print()
  548. # 示例 4: 自定义 Agent 名称
  549. print("示例 4: 自定义 Agent 名称")
  550. result = await compare_phrases(
  551. "人工智能", "机器学习",
  552. name="AI语义分析专家"
  553. )
  554. print(f"相似度: {result.get('相似度')}")
  555. print(f"说明: {result.get('说明')}")
  556. print()
  557. # 示例 5: 使用不同的模型
  558. print("示例 5: 使用 Claude 模型")
  559. result = await compare_phrases(
  560. "深度学习", "神经网络",
  561. model_name='anthropic/claude-haiku-4.5'
  562. )
  563. print(f"相似度: {result.get('相似度')}")
  564. print(f"说明: {result.get('说明')}")
  565. asyncio.run(main())
  566. # ========== V2 版本(示例:详细分析版本) ==========
  567. # V2 默认提示词模板(更详细的分析)
  568. DEFAULT_PROMPT_TEMPLATE_V2 = """
  569. 请深入分析【{phrase_a}】和【{phrase_b}】的语义关系,包括:
  570. 1. 语义相似度(0-1)
  571. 2. 关系类型(如:包含、相关、对立、无关等)
  572. 3. 详细说明
  573. 输出格式:
  574. ```json
  575. {{
  576. "相似度": 0.0,
  577. "关系类型": "相关/包含/对立/无关",
  578. "详细说明": "详细分析两者的语义关系...",
  579. "应用场景": "该关系在实际应用中的意义..."
  580. }}
  581. ```
  582. """.strip()
  583. # 对外接口 - V2
  584. async def compare_phrases_v2(
  585. phrase_a: str,
  586. phrase_b: str,
  587. model_name: str = 'anthropic/claude-sonnet-4.5', # V2 默认使用更强的模型
  588. temperature: float = 0.0,
  589. max_tokens: int = 65536,
  590. prompt_template: str = None,
  591. instructions: str = None,
  592. tools: list = None,
  593. name: str = "Advanced Semantic Analyzer",
  594. use_cache: bool = True,
  595. cache_dir: Optional[str] = None
  596. ) -> Dict[str, Any]:
  597. """
  598. 比较两个短语的语义相似度 - V2 版本(详细分析)
  599. V2 特点:
  600. - 默认使用更强的模型(Claude Sonnet 4.5)
  601. - 更详细的分析输出(包含关系类型和应用场景)
  602. - 适合需要深入分析的场景
  603. Args:
  604. phrase_a: 第一个短语
  605. phrase_b: 第二个短语
  606. model_name: 使用的模型名称,默认 'anthropic/claude-sonnet-4.5'
  607. temperature: 模型温度参数,默认 0.0
  608. max_tokens: 最大生成token数,默认 65536
  609. prompt_template: 自定义提示词模板,默认使用 V2 详细模板
  610. instructions: Agent 的系统指令,默认为 None
  611. tools: Agent 可用的工具列表,默认为 []
  612. name: Agent 的名称,默认 "Advanced Semantic Analyzer"
  613. use_cache: 是否使用缓存,默认 True
  614. cache_dir: 缓存目录,默认从配置读取(可通过 lib.config 设置)
  615. Returns:
  616. 解析后的字典,包含:
  617. - 相似度: 0-1之间的浮点数
  618. - 关系类型: 关系分类
  619. - 详细说明: 详细分析
  620. - 应用场景: 应用建议
  621. Examples:
  622. >>> result = await compare_phrases_v2("深度学习", "神经网络")
  623. >>> print(result['相似度'])
  624. 0.9
  625. >>> print(result['关系类型'])
  626. "包含"
  627. >>> print(result['详细说明'])
  628. "深度学习是基于人工神经网络的机器学习方法..."
  629. """
  630. # 使用 V2 默认模板(如果未指定)
  631. if prompt_template is None:
  632. prompt_template = DEFAULT_PROMPT_TEMPLATE_V2
  633. return await _difference_between_phrases_parsed(
  634. phrase_a, phrase_b, model_name, temperature, max_tokens,
  635. prompt_template, instructions, tools, name, use_cache, cache_dir
  636. )