6f7b94d6f8
Shira AI backend using OpenAI-compatible agent with tool-calling loop, replacing the smart-assistant route in ai-gateway. Components: - FastAPI adapter (same HTTP contract as ai-gateway) - Agent runner with agentic tool-calling loop via ai-gateway /v1/chat/completions - EspoCRM REST API client - 20 MCP tools (CRM, documents, legal assistance) - Prompt builder ported from ai-gateway JS to Python - SOUL.md personality definition - Dockerfile for Coolify deployment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
156 lines
6.4 KiB
Python
156 lines
6.4 KiB
Python
"""Document tools: list, read, read_multiple, batch_rename, generate_from_template."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from mcp_server.espocrm_client import EspoCrmClient
|
|
|
|
from mcp_server.tools._helpers import ok, fail
|
|
|
|
|
|
def register_document_tools(
|
|
tools: dict,
|
|
crm: "EspoCrmClient",
|
|
case_id: str | None,
|
|
context: dict,
|
|
):
|
|
"""Register document-related tools."""
|
|
|
|
async def list_documents() -> str:
|
|
docs = context.get("documents", {})
|
|
if not docs.get("totalFiles"):
|
|
return ok("לא נמצאו מסמכים")
|
|
return ok(json.dumps(docs, ensure_ascii=False))
|
|
|
|
async def read_document(filePath: str) -> str:
|
|
try:
|
|
result = await crm.post("SmartAssistant/action/readDocument", {"filePath": filePath})
|
|
if not result.get("success") or not result.get("text"):
|
|
return fail(f'לא ניתן לחלץ טקסט מ-"{result.get("fileName", filePath)}"')
|
|
return ok(f'=== {result["fileName"]} ({result.get("charCount", "?")} תווים) ===\n\n{result["text"]}')
|
|
except Exception as e:
|
|
return fail(f"שגיאה בקריאת מסמך: {e}")
|
|
|
|
async def read_multiple_documents(filePaths: list[str], maxCharsPerFile: int = 50000) -> str:
|
|
try:
|
|
result = await crm.post("SmartAssistant/action/readMultipleDocuments", {
|
|
"filePaths": filePaths,
|
|
"maxCharsPerFile": maxCharsPerFile,
|
|
})
|
|
lines = []
|
|
for doc in result.get("documents", []):
|
|
lines.append(f'=== {doc["fileName"]} ({doc.get("charCount", "?")} תווים) ===')
|
|
if doc.get("error"):
|
|
lines.append(f'שגיאה: {doc["error"]}')
|
|
elif not doc.get("text"):
|
|
lines.append("לא ניתן לחלץ טקסט")
|
|
else:
|
|
lines.append(doc["text"])
|
|
lines.append("")
|
|
return ok("\n".join(lines))
|
|
except Exception as e:
|
|
return fail(f"שגיאה בקריאת מסמכים: {e}")
|
|
|
|
async def batch_rename_documents(renames: list[dict]) -> str:
|
|
try:
|
|
result = await crm.post("SmartAssistant/action/batchRename", {"renames": renames})
|
|
lines = [f'שינוי שמות: {result.get("successCount", 0)}/{result.get("totalCount", 0)} הצליחו']
|
|
for r in result.get("results", []):
|
|
if r.get("success"):
|
|
lines.append(f'✅ "{r.get("oldName", "?")}" → "{r.get("newName", "?")}"')
|
|
else:
|
|
lines.append(f'❌ "{r.get("oldName", "?")}": {r.get("error", "unknown")}')
|
|
return ok("\n".join(lines))
|
|
except Exception as e:
|
|
return fail(f"שגיאה בשינוי שמות: {e}")
|
|
|
|
async def generate_from_template(
|
|
templateId: str,
|
|
documentSubject: str = "",
|
|
customPlaceholders: dict | None = None,
|
|
) -> str:
|
|
if not case_id:
|
|
return fail("צריך להיות בתוך תיק כדי לייצר מסמך מתבנית")
|
|
try:
|
|
result = await crm.post("SmartAssistant/action/generateFromTemplate", {
|
|
"templateId": templateId,
|
|
"caseId": case_id,
|
|
"documentSubject": documentSubject or None,
|
|
"customPlaceholders": customPlaceholders or {},
|
|
})
|
|
return ok(result.get("message", "✅ מסמך נוצר בהצלחה"))
|
|
except Exception as e:
|
|
return fail(f"שגיאה ביצירת מסמך מתבנית: {e}")
|
|
|
|
# Register
|
|
tools["list_documents"] = {
|
|
"description": "List documents in the case folder. Shows folder structure with file names and paths.",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
"handler": list_documents,
|
|
}
|
|
|
|
tools["read_document"] = {
|
|
"description": "Read and extract text content from a document (PDF, DOCX, TXT). Requires the file path from list_documents.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"filePath": {"type": "string", "description": "Full path of the document (from list_documents results)"},
|
|
},
|
|
"required": ["filePath"],
|
|
},
|
|
"handler": read_document,
|
|
}
|
|
|
|
tools["read_multiple_documents"] = {
|
|
"description": "Read and extract text from multiple documents at once.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"filePaths": {"type": "array", "items": {"type": "string"}, "description": "Array of file paths to read"},
|
|
"maxCharsPerFile": {"type": "integer", "description": "Max chars per file (default 50000)"},
|
|
},
|
|
"required": ["filePaths"],
|
|
},
|
|
"handler": read_multiple_documents,
|
|
}
|
|
|
|
tools["batch_rename_documents"] = {
|
|
"description": "Rename multiple documents at once.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"renames": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"sourcePath": {"type": "string", "description": "Current file path"},
|
|
"newName": {"type": "string", "description": "New file name (without path)"},
|
|
},
|
|
"required": ["sourcePath", "newName"],
|
|
},
|
|
"description": "Array of rename operations",
|
|
},
|
|
},
|
|
"required": ["renames"],
|
|
},
|
|
"handler": batch_rename_documents,
|
|
}
|
|
|
|
tools["generate_from_template"] = {
|
|
"description": "Generate a document from any template. Templates are listed in context under availableTemplates.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"templateId": {"type": "string", "description": "DocumentTemplate ID (from availableTemplates in context)"},
|
|
"documentSubject": {"type": "string", "description": "Custom document name"},
|
|
"customPlaceholders": {"type": "object", "description": "Extra placeholder values"},
|
|
},
|
|
"required": ["templateId"],
|
|
},
|
|
"handler": generate_from_template,
|
|
}
|