Skip to content
← How we build

backend/src/skills/generic_chat_skill.py

"""GenericChatSkill — general-knowledge chat.

Phase 1's handler for the ``generic_chat`` route (greetings, "help," etc.) *and*
the low-confidence/no-route catch-all, since ``FallbackAgentSkill`` doesn't exist
until Phase 2+ (skills-design.md §3).
"""

from __future__ import annotations

import time

from models.llm import LLMRequest
from models.skills import SkillRequest, SkillResult
from skills.base_skill import BaseSkill

DEFAULT_SYSTEM_PROMPT = (
    "You are MarketCompass, a helpful equity-research assistant built by "
    "CompassFoundry Labs. Answer clearly and concisely."
)


class GenericChatSkill(BaseSkill):
    name = "generic_chat"
    required_tools: list[str] = []

    async def execute(self, request: SkillRequest) -> SkillResult:
        trace_id = request.context.trace_id
        session_id = request.context.session_id
        self._emit_start(
            trace_id=trace_id,
            session_id=session_id,
            fields={"route": request.routing.route_name},
        )
        started = time.perf_counter()

        system_prompt = request.context.system_prompt or DEFAULT_SYSTEM_PROMPT
        prompt = f"{system_prompt}\n\nUser: {request.context.user_query}"

        try:
            llm_response = await self.llm_service.generate(
                LLMRequest(prompt=prompt, context=request.context)
            )
        except Exception as exc:
            latency_ms = (time.perf_counter() - started) * 1000
            self._emit_complete(
                success=False,
                latency_ms=latency_ms,
                trace_id=trace_id,
                session_id=session_id,
                error=str(exc),
            )
            raise

        latency_ms = (time.perf_counter() - started) * 1000
        self._emit_complete(
            success=True,
            latency_ms=latency_ms,
            trace_id=trace_id,
            session_id=session_id,
            fields={"model_used": llm_response.model_used},
        )
        return SkillResult(
            content=llm_response.content,
            confidence=1.0,
            metadata={
                "model_used": llm_response.model_used,
                "input_tokens": llm_response.input_tokens,
                "output_tokens": llm_response.output_tokens,
                "estimated_cost_usd": llm_response.estimated_cost_usd,
                "retries_used": llm_response.retries_used,
            },
        )

Follow the work.

Occasional updates on SignalFoundry, MarketCompass, and what we are building at CompassFoundry Labs.

No spam. Unsubscribe anytime.