This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
shira-hermes/api/routes/smart_assistant.py
T
chaim 6f7b94d6f8 feat: initial shira-hermes service
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>
2026-04-13 17:29:35 +00:00

104 lines
3.2 KiB
Python

"""SmartAssistant chat endpoint — same HTTP contract as ai-gateway."""
from __future__ import annotations
import os
import logging
from fastapi import APIRouter, Request, HTTPException
from pydantic import BaseModel
from api.services.prompt_builder import build_case_prompt, build_office_prompt
from api.services.agent_runner import AgentRunner
logger = logging.getLogger("shira.api")
router = APIRouter()
def _get_api_key() -> str:
return os.environ.get("API_KEY", "")
def _verify_auth(request: Request):
"""Verify X-Api-Key header if API_KEY is configured."""
expected = _get_api_key()
if not expected:
return # No auth configured
provided = request.headers.get("X-Api-Key", "")
if provided != expected:
raise HTTPException(status_code=401, detail="Unauthorized")
def _get_runner() -> AgentRunner:
return AgentRunner(
ai_gateway_url=os.environ.get("AI_GATEWAY_URL", "http://localhost:3000"),
ai_gateway_api_key=os.environ.get("AI_GATEWAY_API_KEY", ""),
model=os.environ.get("CLAUDE_MODEL", "sonnet"),
max_tokens=int(os.environ.get("MAX_OUTPUT_TOKENS", "4096")),
max_iterations=int(os.environ.get("MAX_AGENT_ITERATIONS", "10")),
)
@router.post("/api/smart-assistant/chat")
async def smart_assistant_chat(request: Request):
_verify_auth(request)
body = await request.json()
message = body.get("message", "")
context = body.get("context", {})
conversation_id = body.get("conversationId", "")
conversation_history = body.get("conversationHistory", [])
mode = body.get("mode", "case")
# EspoCRM credentials
espocrm = body.get("espocrm", {})
espocrm_url = espocrm.get("url") or os.environ.get("ESPOCRM_URL", "")
espocrm_api_key = espocrm.get("apiKey") or os.environ.get("ESPOCRM_API_KEY", "")
if not message and not conversation_history:
return {"text": "no message", "actions": [], "conversationId": conversation_id}
# Build system prompt
system_prompt = build_office_prompt(context) if mode == "office" else build_case_prompt(context)
# Build messages from conversation history
messages = []
for m in conversation_history:
if m.get("role") == "tool_result":
messages.append({
"role": "user",
"content": f'[תוצאת כלי {m.get("toolName", "")}]:\n{m.get("content", "")}',
})
else:
messages.append({
"role": "assistant" if m.get("role") == "assistant" else "user",
"content": m.get("content", ""),
})
if message:
messages.append({"role": "user", "content": message})
logger.info(
'[smart-assistant] mode=%s conv=%s espocrm=%s msg="%s..."',
mode, conversation_id, espocrm_url, message[:50],
)
runner = _get_runner()
text = await runner.run(
system_prompt=system_prompt,
messages=messages,
context=context,
espocrm_url=espocrm_url,
espocrm_api_key=espocrm_api_key,
)
logger.info("[smart-assistant] response: %s...", text[:80])
return {
"text": text,
"actions": [],
"executedActions": [],
"conversationId": conversation_id,
}