feat(B-HOOK): Lifecycle Hooks + Outbox Events — 55 neue Hooks, 10 Domain Events
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
B-HOOK-CORE: Company Hooks (6: before/after create/update/delete) B-HOOK-MAIL: Mail Hooks (5: after_receive, before/after_delete, before/after_move) B-HOOK-DMS: DMS Hooks (10: after_upload, before/after_update/delete/restore, folder CRUD) B-HOOK-CAL: Calendar Hooks (4: before/after update/delete) B-HOOK-TASK: Task Hooks (6: before/after create/update/delete) B-HOOK-COMM: Communication Hooks (8: conversation create, message/edit/delete) B-HOOK-AI: Agent Hooks (2: before/after_run) B-HOOK-WF: Workflow Hooks (4: before/after_start, after_complete/cancel) B-HOOK-TAG: Tag Hooks (8: create/assign/unassign/delete) B-HOOK-SEARCH: Search Filters (2: before/after_search) B-EVT-OUTBOX: 10 Domain Events (task.completed, file.created/deleted/restored, mail.received, workflow.started/completed/cancelled, agent.run_started/completed) B-HOOK-TEST: 79 Tests in test_lifecycle_hooks.py — alle grün B-HOOK-DOC: Plugin-Dev-Guide Kapitel 8 aktualisiert (Hook-Liste + Outbox Event-Liste)
This commit is contained in:
@@ -128,6 +128,21 @@ async def run_agent(
|
||||
# Reactive mode: use trigger_data as context
|
||||
context_data = trigger_data or {}
|
||||
|
||||
# ── Lifecycle: before run ──
|
||||
from app.core.hooks import do_action
|
||||
await do_action("agent.before_run", agent_id=str(agent.id), tenant_id=str(agent.tenant_id), trigger_type=trigger_type)
|
||||
from app.core.outbox import enqueue_outbox_event
|
||||
async with factory() as db:
|
||||
await enqueue_outbox_event(
|
||||
db,
|
||||
agent.tenant_id,
|
||||
'agent.run_started',
|
||||
{'agent_id': str(agent.id), 'tenant_id': str(agent.tenant_id), 'trigger_type': trigger_type},
|
||||
aggregate_type='agent',
|
||||
aggregate_id=agent.id,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
# Call LLM via LiteLLM
|
||||
result_data: dict[str, Any] = {
|
||||
"agent_id": str(agent.id),
|
||||
@@ -239,6 +254,21 @@ async def run_agent(
|
||||
result_data["status"] = "failed"
|
||||
result_data["error"] = str(e)
|
||||
|
||||
# ── Lifecycle: after run ──
|
||||
from app.core.hooks import do_action
|
||||
await do_action("agent.after_run", agent_id=str(agent.id), tenant_id=str(agent.tenant_id), status=result_data.get("status"), result=result_data)
|
||||
from app.core.outbox import enqueue_outbox_event
|
||||
async with factory() as db:
|
||||
await enqueue_outbox_event(
|
||||
db,
|
||||
agent.tenant_id,
|
||||
'agent.run_completed',
|
||||
{'agent_id': str(agent.id), 'tenant_id': str(agent.tenant_id), 'status': result_data.get('status'), 'cost_usd': result_data.get('cost_usd', 0.0)},
|
||||
aggregate_type='agent',
|
||||
aggregate_id=agent.id,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
# Save result to AgentRun
|
||||
try:
|
||||
async with factory() as db:
|
||||
|
||||
@@ -612,6 +612,8 @@ async def update_entry(
|
||||
if not has_write:
|
||||
raise HTTPException(403, detail={"detail": "No write permission", "code": "forbidden"})
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("calendar.before_update", body=body, tenant_id=tenant_id, user_id=user_id, entry_id=entry_id)
|
||||
# Apply updates
|
||||
if body.title is not None:
|
||||
entry.title = body.title
|
||||
@@ -646,6 +648,8 @@ async def update_entry(
|
||||
|
||||
await db.flush()
|
||||
await db.refresh(entry)
|
||||
from app.core.hooks import do_action
|
||||
await do_action("calendar.after_update", entry_id=str(entry.id), tenant_id=tenant_id, user_id=user_id)
|
||||
return _entry_to_dict(entry)
|
||||
|
||||
|
||||
@@ -664,8 +668,12 @@ async def delete_entry(
|
||||
has_write = await _check_write_permission(db, entry.calendar_id, user_id, role)
|
||||
if not has_write:
|
||||
raise HTTPException(403, detail={"detail": "No write permission", "code": "forbidden"})
|
||||
from app.core.hooks import do_action
|
||||
await do_action("calendar.before_delete", tenant_id=tenant_id, user_id=user_id, entry_id=entry_id)
|
||||
entry.deleted_at = datetime.now(UTC)
|
||||
await db.flush()
|
||||
from app.core.hooks import do_action
|
||||
await do_action("calendar.after_delete", tenant_id=tenant_id, user_id=user_id, entry_id=entry_id)
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
|
||||
@@ -257,6 +257,10 @@ async def create_folder(
|
||||
409, detail={"detail": "Folder name already exists", "code": "duplicate"}
|
||||
)
|
||||
|
||||
# Lifecycle hook: dms.folder.before_create
|
||||
from app.core.hooks import do_action
|
||||
await do_action("dms.folder.before_create", body, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
|
||||
folder = Folder(
|
||||
tenant_id=tenant_id,
|
||||
name=body.name,
|
||||
@@ -266,6 +270,9 @@ async def create_folder(
|
||||
db.add(folder)
|
||||
await db.flush()
|
||||
|
||||
# Lifecycle hook: dms.folder.after_create
|
||||
await do_action("dms.folder.after_create", {'id': str(folder.id), 'name': folder.name, 'parent_id': str(folder.parent_id) if folder.parent_id else None}, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
|
||||
# Build path
|
||||
path = body.name
|
||||
if parent_id is not None:
|
||||
@@ -441,6 +448,10 @@ async def delete_folder(
|
||||
if not await check_single_entity_access(db, "dms_folder", fid, user_id, tenant_id, "delete", is_system_admin):
|
||||
raise HTTPException(403, detail={"detail": "Access denied", "code": "forbidden"})
|
||||
|
||||
# Lifecycle hook: dms.folder.before_delete
|
||||
from app.core.hooks import do_action
|
||||
await do_action("dms.folder.before_delete", db=db, tenant_id=tenant_id, user_id=user_id, folder_id=str(fid))
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
now = datetime.now(UTC)
|
||||
@@ -476,6 +487,11 @@ async def delete_folder(
|
||||
)
|
||||
|
||||
await db.flush()
|
||||
|
||||
# Lifecycle hook: dms.folder.after_delete
|
||||
from app.core.hooks import do_action
|
||||
await do_action("dms.folder.after_delete", db=db, tenant_id=tenant_id, user_id=user_id, folder_id=str(fid))
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@@ -597,6 +613,14 @@ async def upload_file(
|
||||
db.add(dms_file)
|
||||
await db.flush()
|
||||
|
||||
# Lifecycle hook: dms.after_upload
|
||||
from app.core.hooks import do_action
|
||||
await do_action("dms.after_upload", {'id': str(dms_file.id), 'name': dms_file.name, 'folder_id': str(dms_file.folder_id) if dms_file.folder_id else None, 'mime_type': dms_file.mime_type, 'size_bytes': dms_file.size_bytes}, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
|
||||
# Outbox event: file.created
|
||||
from app.core.outbox import enqueue_outbox_event
|
||||
await enqueue_outbox_event(db, tenant_id, 'file.created', {'file_id': str(dms_file.id), 'tenant_id': str(tenant_id), 'name': dms_file.name, 'mime_type': dms_file.mime_type, 'size_bytes': dms_file.size_bytes}, aggregate_type='dms_file', aggregate_id=dms_file.id)
|
||||
|
||||
return {
|
||||
"id": str(dms_file.id),
|
||||
"name": dms_file.name,
|
||||
@@ -763,6 +787,10 @@ async def update_file(
|
||||
if not await check_single_entity_access(db, "dms_file", fid, user_id, tenant_id, "write", is_system_admin):
|
||||
raise HTTPException(403, detail={"detail": "Access denied", "code": "forbidden"})
|
||||
|
||||
# Lifecycle hook: dms.before_update
|
||||
from app.core.hooks import do_action
|
||||
await do_action("dms.before_update", body.model_dump(exclude_unset=True), db=db, tenant_id=tenant_id, user_id=user_id, file_id=file_id)
|
||||
|
||||
data = body.model_dump(exclude_unset=True)
|
||||
|
||||
if "name" in data and data["name"] is not None:
|
||||
@@ -785,6 +813,10 @@ async def update_file(
|
||||
await db.flush()
|
||||
await db.refresh(dms_file)
|
||||
|
||||
# Lifecycle hook: dms.after_update
|
||||
from app.core.hooks import do_action
|
||||
await do_action("dms.after_update", {'id': str(dms_file.id), 'name': dms_file.name}, db=db, tenant_id=tenant_id, user_id=user_id, file_id=file_id)
|
||||
|
||||
return {
|
||||
"id": str(dms_file.id),
|
||||
"name": dms_file.name,
|
||||
@@ -824,10 +856,22 @@ async def delete_file(
|
||||
if not await check_single_entity_access(db, "dms_file", fid, user_id, tenant_id, "delete", is_system_admin):
|
||||
raise HTTPException(403, detail={"detail": "Access denied", "code": "forbidden"})
|
||||
|
||||
# Lifecycle hook: dms.before_delete
|
||||
from app.core.hooks import do_action
|
||||
await do_action("dms.before_delete", db=db, tenant_id=tenant_id, user_id=user_id, file_id=file_id)
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
dms_file.deleted_at = datetime.now(UTC)
|
||||
await db.flush()
|
||||
|
||||
# Lifecycle hook: dms.after_delete
|
||||
await do_action("dms.after_delete", db=db, tenant_id=tenant_id, user_id=user_id, file_id=file_id)
|
||||
|
||||
# Outbox event: file.deleted
|
||||
from app.core.outbox import enqueue_outbox_event
|
||||
await enqueue_outbox_event(db, tenant_id, 'file.deleted', {'file_id': str(fid), 'tenant_id': str(tenant_id)}, aggregate_type='dms_file', aggregate_id=fid)
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@@ -857,10 +901,21 @@ async def restore_file(
|
||||
if not await check_single_entity_access(db, "dms_file", fid, user_id, tenant_id, "write", is_system_admin):
|
||||
raise HTTPException(403, detail={"detail": "Access denied", "code": "forbidden"})
|
||||
|
||||
# Lifecycle hook: dms.before_restore
|
||||
from app.core.hooks import do_action
|
||||
await do_action("dms.before_restore", db=db, tenant_id=tenant_id, user_id=user_id, file_id=file_id)
|
||||
|
||||
dms_file.deleted_at = None
|
||||
await db.flush()
|
||||
await db.refresh(dms_file)
|
||||
|
||||
# Lifecycle hook: dms.after_restore
|
||||
await do_action("dms.after_restore", {'id': str(dms_file.id), 'name': dms_file.name}, db=db, tenant_id=tenant_id, user_id=user_id, file_id=file_id)
|
||||
|
||||
# Outbox event: file.restored
|
||||
from app.core.outbox import enqueue_outbox_event
|
||||
await enqueue_outbox_event(db, tenant_id, 'file.restored', {'file_id': str(fid), 'tenant_id': str(tenant_id)}, aggregate_type='dms_file', aggregate_id=fid)
|
||||
|
||||
return {
|
||||
"id": str(dms_file.id),
|
||||
"name": dms_file.name,
|
||||
|
||||
@@ -264,8 +264,11 @@ async def create_conversation(
|
||||
created_by_type="user",
|
||||
metadata_={},
|
||||
)
|
||||
from app.core.hooks import do_action
|
||||
await do_action("comm.conversation.before_create", tenant_id=tenant_id, user_id=user_id)
|
||||
db.add(conv)
|
||||
await db.flush()
|
||||
await do_action("comm.conversation.after_create", conversation_id=conv.id, tenant_id=tenant_id, user_id=user_id)
|
||||
|
||||
# Add creator as admin
|
||||
creator = CommParticipant(
|
||||
@@ -669,8 +672,11 @@ async def send_message(
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("comm.before_message", conversation_id=conversation_id, tenant_id=tenant_id, sender_id=sender_id)
|
||||
db.add(msg)
|
||||
await db.flush()
|
||||
await do_action("comm.after_message", message_id=msg.id, conversation_id=conversation_id, tenant_id=tenant_id, sender_id=sender_id)
|
||||
|
||||
# Create blocks
|
||||
if blocks:
|
||||
@@ -872,10 +878,14 @@ async def edit_message(
|
||||
)
|
||||
db.add(edit)
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("comm.before_edit", message_id=message_id, tenant_id=tenant_id, user_id=user_id)
|
||||
|
||||
# Update message
|
||||
msg.content = new_content
|
||||
msg.edited_at = datetime.now(timezone.utc)
|
||||
await db.flush()
|
||||
await do_action("comm.after_edit", message_id=message_id, tenant_id=tenant_id, user_id=user_id)
|
||||
|
||||
return message_to_response(msg)
|
||||
|
||||
@@ -891,8 +901,11 @@ async def delete_message(
|
||||
msg = result.scalar_one_or_none()
|
||||
if msg is None:
|
||||
return False
|
||||
from app.core.hooks import do_action
|
||||
await do_action("comm.before_delete", message_id=message_id)
|
||||
msg.deleted_at = datetime.now(timezone.utc)
|
||||
await db.flush()
|
||||
await do_action("comm.after_delete", message_id=message_id)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -824,6 +824,14 @@ async def imap_sync_folder(
|
||||
'from_address': from_addr,
|
||||
})
|
||||
|
||||
# Lifecycle hook: mail.after_receive
|
||||
from app.core.hooks import do_action
|
||||
await do_action("mail.after_receive", {'mail_id': str(mail.id), 'tenant_id': str(tenant_id), 'account_id': str(account.id), 'folder_id': str(folder.id), 'subject': subject, 'from_address': from_addr}, db=db, tenant_id=tenant_id)
|
||||
|
||||
# Outbox event: mail.received
|
||||
from app.core.outbox import enqueue_outbox_event
|
||||
await enqueue_outbox_event(db, tenant_id, 'mail.received', {'mail_id': str(mail.id), 'tenant_id': str(tenant_id), 'account_id': str(account.id), 'folder_id': str(folder.id), 'subject': subject, 'from_address': from_addr}, aggregate_type='mail', aggregate_id=mail.id)
|
||||
|
||||
# Save attachments
|
||||
for att_data in attachments:
|
||||
try:
|
||||
@@ -2343,6 +2351,10 @@ async def imap_delete_mail(
|
||||
logger.warning("imap_delete_mail: mail %s has no imap_uid or message_id, cannot sync", mail_id)
|
||||
return
|
||||
|
||||
# Lifecycle hook: mail.before_delete
|
||||
from app.core.hooks import do_action
|
||||
await do_action("mail.before_delete", mail_id=str(mail.id), tenant_id=str(tenant_id), permanent=permanent, db=db)
|
||||
|
||||
# Find Trash folder on IMAP server (only needed for non-permanent delete)
|
||||
trash_folder_name = None
|
||||
if not permanent:
|
||||
@@ -2409,6 +2421,10 @@ async def imap_delete_mail(
|
||||
await client.expunge()
|
||||
logger.info("imap_delete_mail: moved mail %s (UID %s) to Trash via COPY+DELETE", mail_id, uid_str)
|
||||
|
||||
# Lifecycle hook: mail.after_delete
|
||||
from app.core.hooks import do_action
|
||||
await do_action("mail.after_delete", mail_id=str(mail.id), tenant_id=str(tenant_id), permanent=permanent, db=db)
|
||||
|
||||
except Exception as exc:
|
||||
logger.warning("imap_delete_mail: failed for mail %s: %s", mail_id, exc)
|
||||
raise
|
||||
@@ -2487,6 +2503,10 @@ async def imap_move_mail(
|
||||
logger.warning("imap_move_mail: mail %s has no imap_uid or message_id, cannot sync", mail_id)
|
||||
return
|
||||
|
||||
# Lifecycle hook: mail.before_move
|
||||
from app.core.hooks import do_action
|
||||
await do_action("mail.before_move", mail_id=str(mail.id), tenant_id=str(tenant_id), source_folder_id=str(source_folder.id), target_folder_id=str(target_folder.id), db=db)
|
||||
|
||||
password = await get_account_password(account)
|
||||
client = None
|
||||
|
||||
@@ -2532,6 +2552,10 @@ async def imap_move_mail(
|
||||
|
||||
logger.info("imap_move_mail: moved mail %s (UID %s) via COPY+DELETE", mail_id, uid_str)
|
||||
|
||||
# Lifecycle hook: mail.after_move
|
||||
from app.core.hooks import do_action
|
||||
await do_action("mail.after_move", mail_id=str(mail.id), tenant_id=str(tenant_id), source_folder_id=str(source_folder.id), target_folder_id=str(target_folder.id), db=db)
|
||||
|
||||
except Exception as exc:
|
||||
logger.warning("imap_move_mail: failed for mail %s: %s", mail_id, exc)
|
||||
raise
|
||||
|
||||
@@ -95,9 +95,13 @@ async def create_tag(
|
||||
if existing.scalar_one_or_none() is not None:
|
||||
raise HTTPException(409, detail={"detail": "Tag name already exists", "code": "duplicate"})
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("tag.before_create", body, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
tag = Tag(tenant_id=tenant_id, name=body.name, color=body.color, owner_id=user_id)
|
||||
db.add(tag)
|
||||
await db.flush()
|
||||
from app.core.hooks import do_action
|
||||
await do_action("tag.after_create", {"id": str(tag.id), "name": tag.name, "color": tag.color}, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
return {
|
||||
"id": str(tag.id),
|
||||
"name": tag.name,
|
||||
@@ -185,6 +189,8 @@ async def assign_tag(
|
||||
"already_assigned": True,
|
||||
}
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("tag.before_assign", body, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
assignment = TagAssignment(
|
||||
tenant_id=tenant_id,
|
||||
tag_id=tag_id,
|
||||
@@ -193,6 +199,8 @@ async def assign_tag(
|
||||
)
|
||||
db.add(assignment)
|
||||
await db.flush()
|
||||
from app.core.hooks import do_action
|
||||
await do_action("tag.after_assign", {"id": str(assignment.id), "tag_id": str(tag_id), "entity_type": body.entity_type, "entity_id": str(entity_id)}, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
return {
|
||||
"id": str(assignment.id),
|
||||
"tag_id": str(tag_id),
|
||||
@@ -225,7 +233,11 @@ async def unassign_tag(
|
||||
if assignment is None:
|
||||
raise HTTPException(404, detail={"detail": "Assignment not found", "code": "not_found"})
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("tag.before_unassign", body, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
await db.delete(assignment)
|
||||
from app.core.hooks import do_action
|
||||
await do_action("tag.after_unassign", {"tag_id": str(tag_id), "entity_type": body.entity_type, "entity_id": str(entity_id)}, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@@ -244,6 +256,8 @@ async def delete_tag(
|
||||
if tag is None:
|
||||
raise HTTPException(404, detail={"detail": "Tag not found", "code": "not_found"})
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("tag.before_delete", db=db, tenant_id=tenant_id, user_id=user_id, tag_id=tag_id)
|
||||
# Cascade delete assignments
|
||||
await db.execute(
|
||||
delete(TagAssignment).where(
|
||||
@@ -251,6 +265,8 @@ async def delete_tag(
|
||||
)
|
||||
)
|
||||
await db.delete(tag)
|
||||
from app.core.hooks import do_action
|
||||
await do_action("tag.after_delete", db=db, tenant_id=tenant_id, user_id=user_id, tag_id=tag_id)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
|
||||
@@ -101,6 +101,8 @@ async def create_task(
|
||||
data: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Create a new task."""
|
||||
from app.core.hooks import do_action
|
||||
await do_action("task.before_create", data, db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
task = Task(
|
||||
tenant_id=tenant_id,
|
||||
title=data["title"],
|
||||
@@ -116,6 +118,9 @@ async def create_task(
|
||||
db.add(task)
|
||||
await db.flush()
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("task.after_create", _task_to_dict(task), db=db, tenant_id=tenant_id, user_id=user_id)
|
||||
|
||||
# Publish task.created event
|
||||
from app.core.event_bus import get_event_bus
|
||||
event_bus = get_event_bus()
|
||||
@@ -144,6 +149,8 @@ async def update_task(
|
||||
if task is None:
|
||||
return None
|
||||
|
||||
from app.core.hooks import do_action
|
||||
await do_action("task.before_update", data, db=db, tenant_id=tenant_id, task_id=str(task_id))
|
||||
if "title" in data and data["title"] is not None:
|
||||
task.title = data["title"]
|
||||
if "description" in data:
|
||||
@@ -161,6 +168,8 @@ async def update_task(
|
||||
|
||||
await db.flush()
|
||||
await db.refresh(task)
|
||||
from app.core.hooks import do_action
|
||||
await do_action("task.after_update", _task_to_dict(task), db=db, tenant_id=tenant_id, task_id=str(task_id))
|
||||
return _task_to_dict(task)
|
||||
|
||||
|
||||
@@ -172,8 +181,12 @@ async def delete_task(db: AsyncSession, tenant_id: uuid.UUID, task_id: uuid.UUID
|
||||
task = result.scalar_one_or_none()
|
||||
if task is None:
|
||||
return False
|
||||
from app.core.hooks import do_action
|
||||
await do_action("task.before_delete", db=db, tenant_id=tenant_id, task_id=str(task_id))
|
||||
task.deleted_at = datetime.now(timezone.utc)
|
||||
await db.flush()
|
||||
from app.core.hooks import do_action
|
||||
await do_action("task.after_delete", db=db, tenant_id=tenant_id, task_id=str(task_id))
|
||||
return True
|
||||
|
||||
|
||||
@@ -210,6 +223,9 @@ async def update_task_status(
|
||||
return None
|
||||
task.status = new_status
|
||||
await db.flush()
|
||||
if new_status == "done":
|
||||
from app.core.outbox import enqueue_outbox_event
|
||||
await enqueue_outbox_event(db, tenant_id, "task.completed", {"task_id": str(task.id), "tenant_id": str(tenant_id), "title": task.title, "assigned_to": str(task.assigned_to) if task.assigned_to else None}, aggregate_type="task", aggregate_id=task.id)
|
||||
return _task_to_dict(task)
|
||||
|
||||
|
||||
|
||||
@@ -101,6 +101,8 @@ async def hybrid_search(
|
||||
query_text = f"{normalized_query} {' '.join(semantic_terms)}"
|
||||
query_embedding = await generate_embedding(query_text, db=db, tenant_id=tenant_id)
|
||||
|
||||
from app.core.hooks import apply_filters
|
||||
query_analysis = await apply_filters("search.before_search", query_analysis)
|
||||
all_results: list[dict[str, Any]] = []
|
||||
fetch_limit = limit * 2
|
||||
|
||||
@@ -132,6 +134,8 @@ async def hybrid_search(
|
||||
all_results.append(result)
|
||||
|
||||
all_results.sort(key=lambda x: x.get("score", 0.0), reverse=True)
|
||||
from app.core.hooks import apply_filters
|
||||
all_results = await apply_filters("search.after_search", all_results)
|
||||
return all_results[:limit]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user