From 0ffc86589a8c2bd8a9c8a0378cf3846d34282778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Sun, 15 Mar 2026 15:59:53 +0100 Subject: [PATCH] fix: chat agent auth, auto-submit/dispatch, no confirmation prompts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - All httpx tool calls use real user_id instead of uuid(int=0) for service token — fixes 401 Unauthorized on dispatch/override - create_order auto-submits and auto-dispatches in one step - System prompt updated: execute immediately without asking for confirmation, respond in user's language - Product search returns CAD dimensions (dim_x/y/z_mm) - query_database tool description includes cad_files schema Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/services/chat_service.py | 62 ++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/backend/app/services/chat_service.py b/backend/app/services/chat_service.py index 3ff842e..671278c 100644 --- a/backend/app/services/chat_service.py +++ b/backend/app/services/chat_service.py @@ -29,7 +29,7 @@ You can: - Check material mapping status - Query the database for statistics -Always be concise and helpful. When creating orders or dispatching renders, confirm what you're about to do before executing.""" +Always be concise and helpful. Execute actions immediately without asking for confirmation — the user expects you to act, not ask. If creating an order, also submit and dispatch it automatically. Combine multiple steps into one action when possible. Respond in the same language the user writes in.""" # ── Tool definitions (OpenAI function-calling schema) ──────────────────────── @@ -276,17 +276,17 @@ async def _execute_tool( elif name == "create_order": return await _tool_create_order(db, tenant_id, user_id, **arguments) elif name == "dispatch_renders": - return await _tool_dispatch_renders(db, tenant_id, **arguments) + return await _tool_dispatch_renders(db, tenant_id, user_id, **arguments) elif name == "get_order_status": return await _tool_get_order_status(db, tenant_id, **arguments) elif name == "set_material_override": - return await _tool_set_material_override(db, tenant_id, **arguments) + return await _tool_set_material_override(db, tenant_id, user_id, **arguments) elif name == "set_render_overrides": - return await _tool_set_render_overrides(db, tenant_id, **arguments) + return await _tool_set_render_overrides(db, tenant_id, user_id, **arguments) elif name == "get_render_stats": return await _tool_get_render_stats(db, tenant_id) elif name == "check_materials": - return await _tool_check_materials(db, tenant_id, **arguments) + return await _tool_check_materials(db, tenant_id, user_id, **arguments) elif name == "query_database": return await _tool_query_database(db, tenant_id, **arguments) else: @@ -400,17 +400,45 @@ async def _tool_create_order( ) resp.raise_for_status() data = resp.json() - return json.dumps({ - "order_id": data["id"], - "order_number": data["order_number"], + order_id = data["id"] + order_number = data["order_number"] + result_info = { + "order_id": order_id, + "order_number": order_number, "status": data["status"], "line_count": data.get("line_count", len(lines)), - }, indent=2) + } + + # Auto-submit the order + try: + submit_resp = await client.post( + f"/api/orders/{order_id}/submit", + headers={"Authorization": f"Bearer {token}"}, + ) + submit_resp.raise_for_status() + result_info["status"] = "submitted" + + # Auto-dispatch renders + try: + dispatch_resp = await client.post( + f"/api/orders/{order_id}/dispatch-renders", + headers={"Authorization": f"Bearer {token}"}, + ) + dispatch_resp.raise_for_status() + dispatch_data = dispatch_resp.json() + result_info["status"] = "processing" + result_info["dispatched"] = dispatch_data.get("dispatched", 0) + except Exception: + result_info["dispatch_note"] = "Order submitted but dispatch failed — dispatch manually" + except Exception: + result_info["submit_note"] = "Order created but submit failed — submit manually" + + return json.dumps(result_info, indent=2) except Exception as exc: return json.dumps({"error": f"Failed to create order: {exc}"}) -async def _tool_dispatch_renders(db: AsyncSession, tenant_id: str, order_id: str = "") -> str: +async def _tool_dispatch_renders(db: AsyncSession, tenant_id: str, user_id: str = "", order_id: str = "") -> str: """Dispatch renders via internal httpx call.""" import httpx from app.utils.auth import create_access_token @@ -423,7 +451,7 @@ async def _tool_dispatch_renders(db: AsyncSession, tenant_id: str, order_id: str if not check.first(): return json.dumps({"error": "Order not found or not in your tenant"}) - token = create_access_token(str(uuid.UUID(int=0)), "global_admin", tenant_id) + token = create_access_token(user_id, "global_admin", tenant_id) try: async with httpx.AsyncClient(base_url="http://localhost:8888", timeout=60) as client: resp = await client.post( @@ -460,7 +488,7 @@ async def _tool_get_order_status(db: AsyncSession, tenant_id: str, order_id: str return json.dumps(dict(row), indent=2, default=str) -async def _tool_set_material_override(db: AsyncSession, tenant_id: str, order_id: str = "", material_name: str = "") -> str: +async def _tool_set_material_override(db: AsyncSession, tenant_id: str, user_id: str = "", order_id: str = "", material_name: str = "") -> str: """Set material override via internal httpx call.""" import httpx from app.utils.auth import create_access_token @@ -472,7 +500,7 @@ async def _tool_set_material_override(db: AsyncSession, tenant_id: str, order_id if not check.first(): return json.dumps({"error": "Order not found or not in your tenant"}) - token = create_access_token(str(uuid.UUID(int=0)), "global_admin", tenant_id) + token = create_access_token(user_id, "global_admin", tenant_id) try: async with httpx.AsyncClient(base_url="http://localhost:8888", timeout=30) as client: resp = await client.post( @@ -486,7 +514,7 @@ async def _tool_set_material_override(db: AsyncSession, tenant_id: str, order_id return json.dumps({"error": f"Failed to set material override: {exc}"}) -async def _tool_set_render_overrides(db: AsyncSession, tenant_id: str, order_id: str = "", render_overrides: dict | None = None) -> str: +async def _tool_set_render_overrides(db: AsyncSession, tenant_id: str, user_id: str = "", order_id: str = "", render_overrides: dict | None = None) -> str: """Set render overrides via internal httpx call.""" import httpx from app.utils.auth import create_access_token @@ -498,7 +526,7 @@ async def _tool_set_render_overrides(db: AsyncSession, tenant_id: str, order_id: if not check.first(): return json.dumps({"error": "Order not found or not in your tenant"}) - token = create_access_token(str(uuid.UUID(int=0)), "global_admin", tenant_id) + token = create_access_token(user_id, "global_admin", tenant_id) try: async with httpx.AsyncClient(base_url="http://localhost:8888", timeout=30) as client: resp = await client.post( @@ -538,7 +566,7 @@ async def _tool_get_render_stats(db: AsyncSession, tenant_id: str) -> str: return json.dumps(dict(row) if row else {}, indent=2, default=str) -async def _tool_check_materials(db: AsyncSession, tenant_id: str, order_id: str = "") -> str: +async def _tool_check_materials(db: AsyncSession, tenant_id: str, user_id: str = "", order_id: str = "") -> str: """Check unmapped materials for an order — uses internal API call.""" import httpx from app.utils.auth import create_access_token @@ -550,7 +578,7 @@ async def _tool_check_materials(db: AsyncSession, tenant_id: str, order_id: str if not check.first(): return json.dumps({"error": "Order not found or not in your tenant"}) - token = create_access_token(str(uuid.UUID(int=0)), "global_admin", tenant_id) + token = create_access_token(user_id, "global_admin", tenant_id) try: async with httpx.AsyncClient(base_url="http://localhost:8888", timeout=30) as client: resp = await client.get(