Gaps: full lifecycle management (get/update/delete), pure PostgreSQL (no web search)
This commit is contained in:
1 parent
adfa630578
commit
a4c4a766c1
5 files changed
+266
-81
No files matched your search
+112
-9
@@ -35,6 +35,13 @@ def _conn():
|
||||
|
||||
|
||||
def dict_row(cursor):
|
||||
"""Row factory: dict по именам колонок.
|
||||
|
||||
cursor.description может быть None (UPDATE/DELETE без RETURNING) —
|
||||
в этом случае возвращаем строку как есть, иначе падаем на итерации None.
|
||||
"""
|
||||
if cursor.description is None:
|
||||
return lambda row: row
|
||||
cols = [d.name for d in cursor.description]
|
||||
return lambda row: dict(zip(cols, row))
|
||||
|
||||
@@ -114,16 +121,67 @@ def _tool_add_gap(args):
|
||||
question = str(args.get("question") or "").strip()
|
||||
if not question:
|
||||
return json.dumps({"error": "пустой question"}, ensure_ascii=False), True
|
||||
research.add_gap(_conn(), question,
|
||||
reason=str(args.get("reason") or "user_query"),
|
||||
priority=float(args.get("priority") or 0.5))
|
||||
return json.dumps({"added": question}, ensure_ascii=False), False
|
||||
try:
|
||||
gap_id = research.add_gap(_conn(), question,
|
||||
reason=str(args.get("reason") or "user_query"),
|
||||
priority=float(args.get("priority") or 0.5))
|
||||
except ValueError as e:
|
||||
return json.dumps({"error": str(e)}, ensure_ascii=False), True
|
||||
return json.dumps({"gap_id": gap_id, "question": question, "status": "open"},
|
||||
ensure_ascii=False), False
|
||||
|
||||
|
||||
def _tool_list_gaps(args):
|
||||
from pegas_memory import research
|
||||
gaps = research.detect_gaps(_conn())
|
||||
return json.dumps({"gaps": gaps}, ensure_ascii=False, indent=2), False
|
||||
status = args.get("status") or None
|
||||
limit = int(args.get("limit") or 50)
|
||||
try:
|
||||
gaps = research.list_gaps(_conn(), status=status, limit=limit)
|
||||
except ValueError as e:
|
||||
return json.dumps({"error": str(e)}, ensure_ascii=False), True
|
||||
return json.dumps({"status": status or "all", "count": len(gaps), "gaps": gaps},
|
||||
ensure_ascii=False, indent=2, default=str), False
|
||||
|
||||
|
||||
def _tool_get_gap(args):
|
||||
from pegas_memory import research
|
||||
gap_id = args.get("gap_id")
|
||||
if gap_id is None:
|
||||
return json.dumps({"error": "нужен gap_id"}, ensure_ascii=False), True
|
||||
gap = research.get_gap(_conn(), int(gap_id))
|
||||
if gap is None:
|
||||
return json.dumps({"error": f"пробел {gap_id} не найден"}, ensure_ascii=False), True
|
||||
return json.dumps(gap, ensure_ascii=False, indent=2, default=str), False
|
||||
|
||||
|
||||
def _tool_update_gap(args):
|
||||
from pegas_memory import research
|
||||
gap_id = args.get("gap_id")
|
||||
if gap_id is None:
|
||||
return json.dumps({"error": "нужен gap_id"}, ensure_ascii=False), True
|
||||
fields = {k: args[k] for k in ("question", "reason", "priority", "status")
|
||||
if args.get(k) is not None}
|
||||
if not fields:
|
||||
return json.dumps({"error": "нечего обновлять (укажи question/reason/priority/status)"},
|
||||
ensure_ascii=False), True
|
||||
try:
|
||||
ok = research.update_gap(_conn(), int(gap_id), **fields)
|
||||
except ValueError as e:
|
||||
return json.dumps({"error": str(e)}, ensure_ascii=False), True
|
||||
if not ok:
|
||||
return json.dumps({"error": f"пробел {gap_id} не найден"}, ensure_ascii=False), True
|
||||
return json.dumps({"updated": int(gap_id), "gap": research.get_gap(_conn(), int(gap_id))},
|
||||
ensure_ascii=False, indent=2, default=str), False
|
||||
|
||||
|
||||
def _tool_delete_gap(args):
|
||||
from pegas_memory import research
|
||||
gap_id = args.get("gap_id")
|
||||
if gap_id is None:
|
||||
return json.dumps({"error": "нужен gap_id"}, ensure_ascii=False), True
|
||||
if not research.delete_gap(_conn(), int(gap_id)):
|
||||
return json.dumps({"error": f"пробел {gap_id} не найден"}, ensure_ascii=False), True
|
||||
return json.dumps({"deleted": int(gap_id)}, ensure_ascii=False), False
|
||||
|
||||
|
||||
def _tool_stats(args):
|
||||
@@ -296,9 +354,51 @@ TOOLS = [
|
||||
},
|
||||
{
|
||||
"name": "memory_list_gaps",
|
||||
"description": "Список открытых пробелов (вопросов, на которые пока нет ответа) в базе, "
|
||||
"отсортированный по приоритету. Вызывай, чтобы узнать, что ещё нужно исследовать.",
|
||||
"inputSchema": {"type": "object", "properties": {}, "required": []},
|
||||
"description": "Список пробелов (вопросов без ответа). По умолчанию — открытые, по приоритету. "
|
||||
"Вызывай, чтобы узнать, что ещё нужно исследовать.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {"type": "string", "description": "Фильтр: open | researching | resolved | dismissed (опционально)."},
|
||||
"limit": {"type": "integer", "default": 50, "description": "Сколько вернуть (1-200)."},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "memory_get_gap",
|
||||
"description": "Получить один пробел по id (все поля).",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {"gap_id": {"type": "integer", "description": "ID пробела."}},
|
||||
"required": ["gap_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "memory_update_gap",
|
||||
"description": "Отредактировать пробел: текст вопроса, причину, приоритет и/или статус. "
|
||||
"Статусы: open (открыт) | researching (взят в работу) | resolved (закрыт) | dismissed (не актуален).",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gap_id": {"type": "integer", "description": "ID пробела."},
|
||||
"question": {"type": "string", "description": "Новая формулировка вопроса."},
|
||||
"reason": {"type": "string", "description": "low_coverage|low_confidence|stale|contested|user_query."},
|
||||
"priority": {"type": "number", "description": "Новый приоритет 0-1."},
|
||||
"status": {"type": "string", "description": "open | researching | resolved | dismissed."},
|
||||
},
|
||||
"required": ["gap_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "memory_delete_gap",
|
||||
"description": "Удалить пробел по id (если он больше не нужен). "
|
||||
"Обычно вместо удаления лучше перевести в status=dismissed или resolved.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {"gap_id": {"type": "integer", "description": "ID пробела."}},
|
||||
"required": ["gap_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "memory_stats",
|
||||
@@ -441,6 +541,9 @@ def handle(msg):
|
||||
"memory_ingest": _tool_ingest,
|
||||
"memory_add_gap": _tool_add_gap,
|
||||
"memory_list_gaps": _tool_list_gaps,
|
||||
"memory_get_gap": _tool_get_gap,
|
||||
"memory_update_gap": _tool_update_gap,
|
||||
"memory_delete_gap": _tool_delete_gap,
|
||||
"memory_stats": _tool_stats,
|
||||
"memory_store_code": _tool_store_code,
|
||||
"memory_search_code": _tool_search_code,
|
||||
|
||||
Reference in new issue
Block a user