| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- """知识图谱智能体 —— 从已保存论文中抽取主题/方法/引用关系并构建可视化图谱."""
- from __future__ import annotations
- import json
- import logging
- from typing import Any
- from hello_agents import SimpleAgent
- from ..utils import parse_llm_json
- from ..services.llm.agent_config import papergraph_agent_config
- from .base import BaseAgent
- from .prompts.knowledge_graph import REL_PROMPT
- logger = logging.getLogger(__name__)
- class KnowledgeGraphAgent(BaseAgent):
- """知识图谱构建智能体 —— 从论文集中抽取关系、生成节点与边数据."""
- def __init__(
- self,
- *,
- agent: SimpleAgent | None = None,
- min_score: float = 0.55,
- max_edges: int = 12,
- chunk_size: int = 20,
- ) -> None:
- super().__init__()
- self.min_score = min_score
- self.max_edges = max_edges
- self.chunk_size = chunk_size
- self._agent = agent or SimpleAgent(
- name="papergraph_kg_rel",
- llm=self.llm,
- system_prompt=REL_PROMPT,
- config=papergraph_agent_config(),
- )
- def _candidate_id(self, paper: dict[str, Any]) -> int | None:
- for key in ("paper_id", "id", "target_paper_id"):
- try:
- value = int(paper.get(key))
- if value > 0:
- return value
- except (TypeError, ValueError):
- continue
- return None
- def _compact_paper(self, paper: dict[str, Any]) -> dict[str, Any]:
- out = {
- "paper_id": self._candidate_id(paper),
- "title": self._clip(paper.get("title"), 300),
- "abstract": self._clip(paper.get("abstract"), 2000),
- "keywords": list((paper.get("keywords") or [])[:12]),
- "source": paper.get("source"),
- "year": paper.get("year"),
- "category": paper.get("category"),
- "pdf_excerpt": self._clip(paper.get("pdf_excerpt"), 1200),
- "related_work_excerpt": self._clip(paper.get("related_work_excerpt"), 1200),
- }
- return {k: v for k, v in out.items() if v not in (None, "", [], {})}
- def _validate_edges(self, edges: Any, allowed_ids: set[int]) -> list[dict[str, Any]]:
- if not isinstance(edges, list):
- raise ValueError("edges is not a list")
- best: dict[int, dict[str, Any]] = {}
- for e in edges:
- if not isinstance(e, dict):
- continue
- try:
- tid = int(e.get("target_paper_id"))
- score = float(e.get("score") or 0.0)
- except Exception:
- continue
- if tid not in allowed_ids or score < self.min_score:
- continue
- relation = str(e.get("relation") or "").strip()
- if not relation:
- continue
- edge = {"target_paper_id": tid, "relation": relation,
- "score": max(0.0, min(1.0, score)),
- "evidence": str(e.get("evidence") or "").strip()[:80]}
- if tid not in best or score > best[tid]["score"]:
- best[tid] = edge
- return sorted(best.values(), key=lambda x: x["score"], reverse=True)
- def _chunks(self, items: list[dict[str, Any]]) -> list[list[dict[str, Any]]]:
- return [items[i : i + self.chunk_size] for i in range(0, len(items), self.chunk_size)]
- def infer_edges(
- self, *, new_paper: dict[str, Any], candidates: list[dict[str, Any]]
- ) -> tuple[list[dict[str, Any]], str | None]:
- compact_new = self._compact_paper(new_paper)
- compact_candidates: list[dict[str, Any]] = []
- for c in candidates:
- tid = self._candidate_id(c)
- if tid is None:
- continue
- item = self._compact_paper(c)
- item["paper_id"] = tid
- compact_candidates.append(item)
- if not compact_candidates:
- return [], None
- merged: dict[int, dict[str, Any]] = {}
- for chunk in self._chunks(compact_candidates):
- payload = {"new_paper": compact_new, "candidates": chunk}
- try:
- raw = self._agent.run(json.dumps(payload, ensure_ascii=False))
- except Exception as exc:
- logger.exception("kg_llm_run_failed")
- raise RuntimeError("kg_llm_run_failed") from exc
- data = parse_llm_json(raw)
- if data is None:
- raise ValueError("kg_llm_parse_failed")
- try:
- valid_edges = self._validate_edges(
- data.get("edges"),
- allowed_ids={int(x["paper_id"]) for x in chunk},
- )
- except Exception as exc:
- raise ValueError("kg_edge_validation_failed") from exc
- for edge in valid_edges:
- tid = edge["target_paper_id"]
- if tid not in merged or edge["score"] > merged[tid]["score"]:
- merged[tid] = edge
- edges = sorted(merged.values(), key=lambda x: x["score"], reverse=True)[: self.max_edges]
- try:
- from ..services.memory.agent_memory import get_agent_memory
- am = get_agent_memory()
- title = str(new_paper.get("title") or "")[:120]
- am.add(agent_name="knowledge_graph", content=f"关系抽取:{title}", memory_type="working", importance=0.45, shared=False)
- if edges:
- am.add(
- agent_name="knowledge_graph",
- content=f"关系抽取要点:top_relation={edges[0].get('relation')} score={edges[0].get('score')}",
- memory_type="working",
- importance=0.5,
- shared=True,
- )
- except Exception:
- logger.debug("kg_memory_write_failed", exc_info=True)
- return edges, None
|