| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- """
- 内容树 API 工具
- 封装内容树搜索接口:
- 1. search_content_tree - 关键词搜索分类和元素
- 2. get_category_tree - 获取指定分类的完整路径和子树
- """
- import logging
- from typing import Optional
- import httpx
- from agent.tools import tool
- from agent.tools.models import ToolResult
- logger = logging.getLogger(__name__)
- BASE_URL = "http://8.147.104.190:8001"
- @tool(description="在内容树中搜索分类(category)和元素(element),支持获取祖先路径和子孙节点")
- async def search_content_tree(
- q: str,
- source_type: str,
- entity_type: str = "all",
- top_k: int = 20,
- use_description: bool = False,
- include_ancestors: bool = False,
- descendant_depth: int = 0,
- ) -> ToolResult:
- """
- 关键词搜索内容树中的分类和元素。
- Args:
- q: 搜索关键词
- source_type: 维度,必须是 "实质" / "形式" / "意图" 之一
- entity_type: 搜索对象类型,"category" / "element" / "all"(默认)
- top_k: 返回结果数量,1-100(默认20)
- use_description: 是否同时搜索描述字段(默认仅搜索名称)
- include_ancestors: 是否返回祖先路径
- descendant_depth: 返回子孙节点深度,0=不返回,1=直接子节点,2=子+孙...
- """
- params = {
- "q": q,
- "source_type": source_type,
- "entity_type": entity_type,
- "top_k": top_k,
- "use_description": str(use_description).lower(),
- "include_ancestors": str(include_ancestors).lower(),
- "descendant_depth": descendant_depth,
- }
- try:
- async with httpx.AsyncClient(timeout=30.0) as client:
- resp = await client.get(f"{BASE_URL}/api/search", params=params)
- resp.raise_for_status()
- data = resp.json()
- count = data.get("count", 0)
- results = data.get("results", [])
- # 格式化输出
- lines = [f"搜索「{q}」({source_type}维度)共找到 {count} 条结果:\n"]
- for r in results:
- etype = r.get("entity_type", "")
- name = r.get("name", "")
- score = r.get("score", 0)
- if etype == "category":
- sid = r.get("stable_id", "")
- path = r.get("path", "")
- desc = r.get("description", "")
- lines.append(f"[分类] stable_id={sid} | {path} | score={score:.2f}")
- if desc:
- lines.append(f" 描述: {desc}")
- ancestors = r.get("ancestors", [])
- if ancestors:
- anc_names = " > ".join(a["name"] for a in ancestors)
- lines.append(f" 祖先: {anc_names}")
- descendants = r.get("descendants", [])
- if descendants:
- desc_names = ", ".join(d["name"] for d in descendants[:10])
- lines.append(f" 子孙({len(descendants)}): {desc_names}")
- else:
- eid = r.get("entity_id", "")
- belong = r.get("belong_category_stable_id", "")
- occ = r.get("occurrence_count", 0)
- lines.append(f"[元素] entity_id={eid} | {name} | belong_category={belong} | 出现次数={occ} | score={score:.2f}")
- edesc = r.get("description", "")
- if edesc:
- lines.append(f" 描述: {edesc}")
- lines.append("")
- return ToolResult(
- title=f"内容树搜索: {q} ({source_type}) → {count} 条",
- output="\n".join(lines),
- )
- except httpx.HTTPError as e:
- return ToolResult(title="内容树搜索失败", output=f"HTTP 错误: {e}")
- except Exception as e:
- logger.exception("search_content_tree error")
- return ToolResult(title="内容树搜索失败", output=f"错误: {e}")
- @tool(description="获取指定分类节点的完整路径、祖先和子孙结构(通过 stable_id 精确查询)")
- async def get_category_tree(
- stable_id: int,
- source_type: str,
- include_ancestors: bool = True,
- descendant_depth: int = -1,
- ) -> ToolResult:
- """
- 获取指定分类的完整路径和子树结构。
- Args:
- stable_id: 分类的 stable_id
- source_type: 维度,"实质" / "形式" / "意图"
- include_ancestors: 是否返回祖先路径(默认 True)
- descendant_depth: 子孙深度,-1=全部,0=仅当前,1=子节点,2=子+孙...
- """
- params = {
- "source_type": source_type,
- "include_ancestors": str(include_ancestors).lower(),
- "descendant_depth": descendant_depth,
- }
- try:
- async with httpx.AsyncClient(timeout=30.0) as client:
- resp = await client.get(f"{BASE_URL}/api/search/category/{stable_id}", params=params)
- resp.raise_for_status()
- data = resp.json()
- current = data.get("current", {})
- ancestors = data.get("ancestors", [])
- descendants = data.get("descendants", [])
- lines = []
- lines.append(f"分类节点: {current.get('name', '')} (stable_id={stable_id})")
- lines.append(f"路径: {current.get('path', '')}")
- if current.get("description"):
- lines.append(f"描述: {current['description']}")
- lines.append("")
- if ancestors:
- lines.append("祖先路径:")
- for a in ancestors:
- lines.append(f" L{a.get('level', '?')} {a.get('name', '')} (stable_id={a.get('stable_id', '')})")
- lines.append("")
- if descendants:
- lines.append(f"子孙节点 ({len(descendants)} 个):")
- for d in descendants:
- indent = " " * d.get("depth_from_parent", 1)
- leaf_mark = " [叶]" if d.get("is_leaf") else ""
- lines.append(f"{indent}L{d.get('level', '?')} {d.get('name', '')} (stable_id={d.get('stable_id', '')}){leaf_mark}")
- return ToolResult(
- title=f"分类树: {current.get('name', stable_id)} (stable_id={stable_id})",
- output="\n".join(lines),
- )
- except httpx.HTTPError as e:
- return ToolResult(title="获取分类树失败", output=f"HTTP 错误: {e}")
- except Exception as e:
- logger.exception("get_category_tree error")
- return ToolResult(title="获取分类树失败", output=f"错误: {e}")
|