feat: AI agent knows material library — list_materials tool with alias search
Added list_materials tool to the chat agent: - Searches SCHAEFFLER library materials by name, description, or alias - Returns material name + schaeffler_code + aliases - Enables: "zeig mir ein Bild mit Durotect-Material" → agent searches for "durotect" → finds SCHAEFFLER_020101_Durotect-Blue → uses as material_override System prompt updated with rules 10-11: - Explains alias → library material mapping - Always use full SCHAEFFLER name for material_override Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,9 @@ RULES:
|
||||
6. Combine multiple steps into one action. If creating an order, also submit and dispatch it automatically.
|
||||
7. Respond in the same language the user writes in.
|
||||
8. Be concise — short answers are better than long ones.
|
||||
9. When the user says "beliebig", "any", "random", "irgendein" — just pick one yourself, don't ask back."""
|
||||
9. When the user says "beliebig", "any", "random", "irgendein" — just pick one yourself, don't ask back.
|
||||
10. Material system: Materials have SCHAEFFLER library names (e.g. SCHAEFFLER_020101_Durotect-Blue). Common names like "Durotect", "Stahl", "Bronze" are aliases that map to these library names. When the user asks for a material by a common name, use list_materials to find the correct SCHAEFFLER name, then use that for material_override.
|
||||
11. When setting material_override, always use the full SCHAEFFLER library name (e.g. SCHAEFFLER_020101_Durotect-Blue), never the alias."""
|
||||
|
||||
# ── Tool definitions (OpenAI function-calling schema) ────────────────────────
|
||||
|
||||
@@ -241,6 +243,23 @@ TOOLS = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "list_materials",
|
||||
"description": "List available SCHAEFFLER library materials with their aliases. Use this to find the correct material name for material_override. Materials have names like SCHAEFFLER_010101_Steel-Bare. Aliases map common names (Stahl, Bronze, Durotect, etc.) to these library materials. When user asks for a material by a common name, search aliases to find the correct SCHAEFFLER library material name.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search term to filter materials by name or alias (e.g. 'durotect', 'steel', 'bronze'). Empty for all.",
|
||||
"default": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -323,6 +342,8 @@ async def _execute_tool(
|
||||
return await _tool_get_render_stats(db, tenant_id)
|
||||
elif name == "check_materials":
|
||||
return await _tool_check_materials(db, tenant_id, user_id, **arguments)
|
||||
elif name == "list_materials":
|
||||
return await _tool_list_materials(db, tenant_id, **arguments)
|
||||
elif name == "find_product_renders":
|
||||
return await _tool_find_product_renders(db, tenant_id, **arguments)
|
||||
elif name == "query_database":
|
||||
@@ -634,6 +655,47 @@ async def _tool_check_materials(db: AsyncSession, tenant_id: str, user_id: str =
|
||||
return json.dumps({"error": f"Failed to check materials: {exc}"})
|
||||
|
||||
|
||||
async def _tool_list_materials(db: AsyncSession, tenant_id: str, query: str = "") -> str:
|
||||
"""List library materials with their aliases."""
|
||||
sql = """
|
||||
SELECT m.id, m.name, m.schaeffler_code, m.description,
|
||||
COALESCE(
|
||||
(SELECT json_agg(ma.alias) FROM material_aliases ma WHERE ma.material_id = m.id),
|
||||
'[]'::json
|
||||
) AS aliases
|
||||
FROM materials m
|
||||
WHERE m.schaeffler_code IS NOT NULL
|
||||
"""
|
||||
params: dict = {}
|
||||
if query:
|
||||
sql += """
|
||||
AND (m.name ILIKE :q OR m.description ILIKE :q
|
||||
OR EXISTS (SELECT 1 FROM material_aliases ma WHERE ma.material_id = m.id AND ma.alias ILIKE :q))
|
||||
"""
|
||||
params["q"] = f"%{query}%"
|
||||
sql += " ORDER BY m.name LIMIT 30"
|
||||
|
||||
result = await db.execute(text(sql), params)
|
||||
rows = result.mappings().all()
|
||||
if not rows:
|
||||
return json.dumps({"message": f"No materials found matching '{query}'.", "materials": []})
|
||||
|
||||
materials = []
|
||||
for r in rows:
|
||||
aliases = r["aliases"] if isinstance(r["aliases"], list) else []
|
||||
materials.append({
|
||||
"name": r["name"],
|
||||
"schaeffler_code": r["schaeffler_code"],
|
||||
"description": r["description"],
|
||||
"aliases": aliases[:10], # cap to avoid token bloat
|
||||
})
|
||||
|
||||
return json.dumps({
|
||||
"message": f"Found {len(materials)} material(s). Use the 'name' field for material_override.",
|
||||
"materials": materials,
|
||||
}, indent=2, default=str)
|
||||
|
||||
|
||||
async def _tool_find_product_renders(
|
||||
db: AsyncSession, tenant_id: str,
|
||||
product_name: str = "", product_id: str = "", transparent_only: bool = False,
|
||||
|
||||
Reference in New Issue
Block a user