Files
socweb/backend/app/tasks/bus.py
T
gongzhiyongandClaude Sonnet 4.6 be60d9742e fix(backend): add missing storage, tasks, document, sandbox modules
The app.storage and app.tasks packages were never committed to git,
causing ModuleNotFoundError on Azure deployment. Also adds the
document and sandbox tool modules with their config fields.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:03:30 +08:00

111 lines
3.1 KiB
Python

"""Azure Service Bus client for async task dispatch and consumption.
Long-running tasks (document generation, deep search) are published
to a Service Bus queue so the main request can return immediately
with a "task accepted" response. A background consumer picks up
messages and processes them.
"""
from __future__ import annotations
import json
import logging
import uuid
from datetime import datetime
from azure.servicebus.aio import ServiceBusClient, ServiceBusSender, ServiceBusReceiver
from azure.servicebus import ServiceBusMessage
from app.config import settings
logger = logging.getLogger(__name__)
_client: ServiceBusClient | None = None
QUEUE_NAME = "soc-tasks"
async def _get_client() -> ServiceBusClient:
global _client
if _client is None:
_client = ServiceBusClient.from_connection_string(
settings.azure_service_bus_connection_string
)
return _client
async def send_task(
task_type: str,
payload: dict,
conversation_id: str | None = None,
) -> str:
"""Publish an async task message to Service Bus.
Args:
task_type: Type of task (e.g. "document_generate", "deep_search").
payload: Task-specific data.
conversation_id: Optional conversation context.
Returns:
A unique task_id for tracking.
"""
client = await _get_client()
task_id = uuid.uuid4().hex
message_body = json.dumps({
"task_id": task_id,
"task_type": task_type,
"conversation_id": conversation_id,
"payload": payload,
"created_at": datetime.utcnow().isoformat(),
})
sender: ServiceBusSender
async with client.get_queue_sender(queue_name=QUEUE_NAME) as sender:
await sender.send_messages(ServiceBusMessage(message_body))
logger.info("Sent task %s (type=%s) to Service Bus", task_id, task_type)
return task_id
async def receive_tasks(max_messages: int = 10, max_wait_time: int = 5) -> list[dict]:
"""Receive pending task messages from Service Bus.
Args:
max_messages: Maximum number of messages to receive.
max_wait_time: Maximum wait time in seconds.
Returns:
List of task dicts. Each message is completed (removed from queue)
after being returned.
"""
client = await _get_client()
tasks = []
receiver: ServiceBusReceiver
async with client.get_queue_receiver(
queue_name=QUEUE_NAME,
max_wait_time=max_wait_time,
) as receiver:
messages = await receiver.receive_messages(
max_message_count=max_messages,
max_wait_time=max_wait_time,
)
for msg in messages:
try:
body = json.loads(str(msg))
tasks.append(body)
await receiver.complete_message(msg)
except Exception:
logger.exception("Failed to process Service Bus message")
await receiver.dead_letter_message(msg, reason="parse_error")
return tasks
async def close_service_bus() -> None:
"""Close the Service Bus client."""
global _client
if _client is not None:
await _client.close()
_client = None