feat: V1 - DAG编排/告警系统/Agent循环/知识库RAG/Webhook + 审核管理富上下文修复

This commit is contained in:
2026-08-13 00:46:50 +08:00
parent c30cabe2a1
commit edfaf3cdc8
+109
View File
@@ -0,0 +1,109 @@
"""Knowledge base service: document CRUD + simple RAG search."""
import json
import re
from sqlalchemy.orm import Session
from typing import List, Optional
from app.models.knowledge import KnowledgeDoc
class KnowledgeService:
@staticmethod
def list_docs(db, tenant_id, project_id=None):
q = db.query(KnowledgeDoc).filter(KnowledgeDoc.tenant_id == tenant_id)
if project_id:
q = q.filter(
(KnowledgeDoc.project_id == project_id) | (KnowledgeDoc.project_id == None)
)
return q.order_by(KnowledgeDoc.created_at.desc()).all()
@staticmethod
def get_doc(db, tenant_id, doc_id):
return db.query(KnowledgeDoc).filter(
KnowledgeDoc.id == doc_id, KnowledgeDoc.tenant_id == tenant_id
).first()
@staticmethod
def create_doc(db, tenant_id, req):
keywords = KnowledgeService._extract_keywords(req.content + " " + req.title + " " + req.tags)
doc = KnowledgeDoc(
tenant_id=tenant_id, project_id=req.project_id,
title=req.title, content=req.content,
doc_type=req.doc_type, source=req.source,
tags=req.tags, keywords=json.dumps(keywords, ensure_ascii=False),
)
db.add(doc)
db.commit()
db.refresh(doc)
return doc
@staticmethod
def update_doc(db, tenant_id, doc_id, content=None, title=None):
doc = KnowledgeService.get_doc(db, tenant_id, doc_id)
if not doc:
return None
if content is not None:
doc.content = content
if title is not None:
doc.title = title
doc.keywords = json.dumps(
KnowledgeService._extract_keywords(doc.content + " " + doc.title),
ensure_ascii=False,
)
db.commit()
db.refresh(doc)
return doc
@staticmethod
def delete_doc(db, tenant_id, doc_id):
doc = KnowledgeService.get_doc(db, tenant_id, doc_id)
if not doc:
return False
db.delete(doc)
db.commit()
return True
@staticmethod
def search(db, tenant_id, query, project_id=None, limit=5):
"""Simple keyword-based search (MVP RAG without vector DB)."""
docs = KnowledgeService.list_docs(db, tenant_id, project_id)
query_lower = query.lower()
query_terms = set(re.findall(r'\w+', query_lower))
scored = []
for doc in docs:
content_lower = doc.content.lower()
title_lower = doc.title.lower()
keywords = set(json.loads(doc.keywords or "[]"))
score = 0.0
# Title match (high weight)
for term in query_terms:
if term in title_lower:
score += 3.0
if term in keywords:
score += 2.0
count = content_lower.count(term)
score += count * 0.5
if score > 0:
snippet = doc.content[:200] + "..." if len(doc.content) > 200 else doc.content
scored.append({
"doc_id": doc.id,
"title": doc.title,
"snippet": snippet,
"score": round(score, 2),
})
scored.sort(key=lambda x: x["score"], reverse=True)
return scored[:limit]
@staticmethod
def _extract_keywords(text):
"""Extract keywords from text (simple: Chinese 2-4 char segments + English words)."""
# English words
en_words = re.findall(r'[a-zA-Z]{2,20}', text)
# Chinese segments (2-4 chars)
cn_segs = re.findall(r'[\u4e00-\u9fff]{2,4}', text)
# Deduplicate and limit
all_kw = list(set(en_words + cn_segs))[:20]
return all_kw