github-actions
sync: e3a248f
7d267a5
"""LangGraph capability ๊ณตํ†ต ์ถ”์ƒํ™”."""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
@dataclass
class EvidenceItem:
"""RAG/API ์ถœ์ฒ˜ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋™์ผํ•œ ๊ตฌ์กฐ๋กœ evidence๋ฅผ ํ‘œํ˜„.
source_type: "rag" | "api" | "llm_generated"
"""
source_type: str # "rag" | "api" | "llm_generated"
title: str
excerpt: str # ๋ณธ๋ฌธ ๋ฐœ์ทŒ (์ตœ๋Œ€ 500์ž)
link_or_path: str = "" # URL(API) ๋˜๋Š” file_path(RAG)
page: Optional[int] = None
score: float = 0.0
provider_meta: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
return {
"source_type": self.source_type,
"title": self.title,
"excerpt": self.excerpt,
"link_or_path": self.link_or_path,
"page": self.page,
"score": self.score,
"provider_meta": self.provider_meta,
}
@dataclass
class EvidenceEnvelope:
"""mixed evidence ๊ฒฐ๊ณผ ์ปจํ…Œ์ด๋„ˆ."""
items: List[EvidenceItem] = field(default_factory=list)
summary_text: str = ""
status: str = "ok" # "ok" | "empty" | "partial" | "error"
errors: List[str] = field(default_factory=list)
def to_dict(self) -> Dict[str, Any]:
return {
"items": [item.to_dict() for item in self.items],
"summary_text": self.summary_text,
"status": self.status,
"errors": self.errors,
}
@dataclass
class CapabilityMetadata:
"""LLM agentยทexecutorยทsession trace์—์„œ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” capability ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ."""
name: str # tool registry key (์˜ˆ: "api_lookup")
description: str # LLM agent๊ฐ€ ์ฝ๋Š” ํ•œ๊ตญ์–ด ์„ค๋ช… (1-2๋ฌธ์žฅ)
approval_summary: str # approval_wait ํ”„๋กฌํ”„ํŠธ์— ํ‘œ์‹œ๋˜๋Š” ์š”์•ฝ
provider: str # ๋ฐ์ดํ„ฐ ์ œ๊ณต์ž ์‹๋ณ„์ž (์˜ˆ: "data.go.kr")
timeout_sec: float = 10.0 # ๊ธฐ๋ณธ ํƒ€์ž„์•„์›ƒ
parameters: Dict[str, Any] = field(default_factory=dict) # JSON Schema for tool parameters
risk_level: str = "low" # "low" | "high" โ€” ํ–ฅํ›„ ๊ณ ์œ„ํ—˜ ๋„๊ตฌ ์ถ”๊ฐ€ ์‹œ ์‚ฌ์šฉ
@dataclass
class LookupResult:
"""api_lookup ๊ณตํ†ต ์‘๋‹ต ์Šคํ‚ค๋งˆ."""
success: bool
query: str
results: List[Dict[str, Any]] = field(default_factory=list)
context_text: str = ""
citations: List[Dict[str, Any]] = field(default_factory=list)
provider: str = ""
error: Optional[str] = None
empty_reason: Optional[str] = None # "quota", "no_match", "provider_error"
latency_ms: float = 0.0
evidence: Optional[EvidenceEnvelope] = None # ์ •๊ทœํ™”๋œ evidence (์ƒˆ ํ•„๋“œ)
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {
"success": self.success,
"query": self.query,
"count": len(self.results),
"results": self.results,
"context_text": self.context_text,
"citations": self.citations,
"provider": self.provider,
"error": self.error,
"empty_reason": self.empty_reason,
"latency_ms": round(self.latency_ms, 2),
}
if self.evidence is not None:
d["evidence"] = self.evidence.to_dict()
return d
class CapabilityBase(ABC):
"""LangGraph tool capability ์ถ”์ƒ ๋ฒ ์ด์Šค.
LangGraph ToolNode์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” async callable ์ธํ„ฐํŽ˜์ด์Šค.
"""
@property
@abstractmethod
def metadata(self) -> CapabilityMetadata: ...
@abstractmethod
async def execute(
self,
query: str,
context: Dict[str, Any],
session: Any, # SessionContext (์ˆœํ™˜ import ๋ฐฉ์ง€)
) -> LookupResult: ...
async def __call__(
self,
query: str,
context: Dict[str, Any],
session: Any,
) -> Dict[str, Any]:
"""async callable ์ง„์ž…์ ."""
import time
start = time.monotonic()
result = await self.execute(query, context, session)
result.latency_ms = (time.monotonic() - start) * 1000
return result.to_dict()