From edfaf3cdc8cf4f546324a52ccce97bbba4be17d8 Mon Sep 17 00:00:00 2001 From: huangzhuang_3rd Date: Thu, 13 Aug 2026 00:46:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20V1=20-=20DAG=E7=BC=96=E6=8E=92/?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E7=B3=BB=E7=BB=9F/Agent=E5=BE=AA=E7=8E=AF/?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93RAG/Webhook=20+=20=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=AF=8C=E4=B8=8A=E4=B8=8B=E6=96=87=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/knowledge_service.py | 109 ++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 backend/app/services/knowledge_service.py diff --git a/backend/app/services/knowledge_service.py b/backend/app/services/knowledge_service.py new file mode 100644 index 0000000..5079c68 --- /dev/null +++ b/backend/app/services/knowledge_service.py @@ -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