Files
AutoGPT/autogpt_platform/backend/backend/api/features/otto/service.py
Reinier van der Leer 113e87a23c refactor(backend): Reduce circular imports (#12068)
I'm getting circular import issues because there is a lot of
cross-importing between `backend.data`, `backend.blocks`, and other
modules. This change reduces block-related cross-imports and thus risk
of breaking circular imports.

### Changes 🏗️

- Strip down `backend.data.block`
- Move `Block` base class and related class/enum defs to
`backend.blocks._base`
  - Move `is_block_auth_configured` to `backend.blocks._utils`
- Move `get_blocks()`, `get_io_block_ids()` etc. to `backend.blocks`
(`__init__.py`)
  - Update imports everywhere
- Remove unused and poorly typed `Block.create()`
  - Change usages from `block_cls.create()` to `block_cls()`
- Improve typing of `load_all_blocks` and `get_blocks`
- Move cross-import of `backend.api.features.library.model` from
`backend/data/__init__.py` to `backend/data/integrations.py`
- Remove deprecated attribute `NodeModel.webhook`
  - Re-generate OpenAPI spec and fix frontend usage
- Eliminate module-level `backend.blocks` import from `blocks/agent.py`
- Eliminate module-level `backend.data.execution` and
`backend.executor.manager` imports from `blocks/helpers/review.py`
- Replace `BlockInput` with `GraphInput` for graph inputs

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - CI static type-checking + tests should be sufficient for this
2026-02-12 12:07:49 +00:00

141 lines
4.9 KiB
Python

import asyncio
import logging
from typing import Optional
import aiohttp
from fastapi import HTTPException
from backend.blocks import get_block
from backend.data import graph as graph_db
from backend.util.settings import Settings
from .models import ApiResponse, ChatRequest, GraphData
logger = logging.getLogger(__name__)
settings = Settings()
OTTO_API_URL = settings.config.otto_api_url
class OttoService:
@staticmethod
async def _fetch_graph_data(
request: ChatRequest, user_id: str
) -> Optional[GraphData]:
"""Fetch graph data if requested and available."""
if not (request.include_graph_data and request.graph_id):
return None
try:
graph = await graph_db.get_graph(
graph_id=request.graph_id, version=None, user_id=user_id
)
if not graph:
return None
nodes_data = []
for node in graph.nodes:
block = get_block(node.block_id)
if not block:
continue
node_data = {
"id": node.id,
"block_id": node.block_id,
"block_name": block.name,
"block_type": (
block.block_type.value if hasattr(block, "block_type") else None
),
"data": {
k: v
for k, v in (node.input_default or {}).items()
if k not in ["credentials"] # Exclude sensitive data
},
}
nodes_data.append(node_data)
# Create a GraphData object with the required fields
return GraphData(
nodes=nodes_data,
edges=[],
graph_name=graph.name,
graph_description=graph.description,
)
except Exception as e:
logger.error(f"Failed to fetch graph data: {str(e)}")
return None
@staticmethod
async def ask(request: ChatRequest, user_id: str) -> ApiResponse:
"""
Send request to Otto API and handle the response.
"""
# Check if Otto API URL is configured
if not OTTO_API_URL:
logger.error("Otto API URL is not configured")
raise HTTPException(
status_code=503, detail="Otto service is not configured"
)
try:
async with aiohttp.ClientSession() as session:
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
# If graph data is requested, fetch it
graph_data = await OttoService._fetch_graph_data(request, user_id)
# Prepare the payload with optional graph data
payload = {
"query": request.query,
"conversation_history": [
msg.model_dump() for msg in request.conversation_history
],
"user_id": user_id,
"message_id": request.message_id,
}
if graph_data:
payload["graph_data"] = graph_data.model_dump()
logger.info(f"Sending request to Otto API for user {user_id}")
logger.debug(f"Request payload: {payload}")
async with session.post(
OTTO_API_URL,
json=payload,
headers=headers,
timeout=aiohttp.ClientTimeout(total=60),
) as response:
if response.status != 200:
error_text = await response.text()
logger.error(f"Otto API error: {error_text}")
raise HTTPException(
status_code=response.status,
detail=f"Otto API request failed: {error_text}",
)
data = await response.json()
logger.info(
f"Successfully received response from Otto API for user {user_id}"
)
return ApiResponse(**data)
except aiohttp.ClientError as e:
logger.error(f"Connection error to Otto API: {str(e)}")
raise HTTPException(
status_code=503, detail="Failed to connect to Otto service"
)
except asyncio.TimeoutError:
logger.error("Timeout error connecting to Otto API after 60 seconds")
raise HTTPException(
status_code=504, detail="Request to Otto service timed out"
)
except Exception as e:
logger.error(f"Unexpected error in Otto API proxy: {str(e)}")
raise HTTPException(
status_code=500, detail="Internal server error in Otto proxy"
)