-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathdefine_metric.py
More file actions
70 lines (58 loc) · 2.89 KB
/
define_metric.py
File metadata and controls
70 lines (58 loc) · 2.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""define_metric — scope-aware definition writer (★④ federation).
Writes one :class:`SemanticEntry` to a federation scope via the
:class:`ScopeResolverPort`. With no explicit scope it lands at the identity's
default write scope (current channel), so ``#marketing`` and ``#finance`` can
hold different definitions of the same name without conflict.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..core.ports.audit import AuditEvent
from ..core.identity import Scope, ScopeLevel
from ..core.types import ToolResult, ToolSpec
from ..semantic.types import SemanticEntry, SemanticKind
if TYPE_CHECKING:
from ..harness.context import HarnessContext
class DefineMetric:
@property
def spec(self) -> ToolSpec:
return ToolSpec(
name="define_metric",
description=(
"Define a metric/dimension/rule for the current scope (channel by "
"default). Later questions in this scope use this definition."
),
parameters={
"type": "object",
"properties": {
"name": {"type": "string"},
"definition": {"type": "string"},
"kind": {"type": "string", "enum": ["metric", "dimension", "rule"]},
"scope": {"type": "string", "enum": ["channel", "guild"],
"description": "where to store it; default channel"},
},
"required": ["name", "definition"],
},
)
async def run(self, args: dict[str, Any], ctx: "HarnessContext") -> ToolResult:
if ctx.scope_resolver is None:
return ToolResult(call_id="", content="semantic layer unavailable", is_error=True)
name = (args.get("name") or "").strip()
definition = (args.get("definition") or "").strip()
if not name or not definition:
return ToolResult(call_id="", content="name and definition are required", is_error=True)
kind = SemanticKind(args.get("kind", "metric"))
scope = self._resolve_scope(args.get("scope"), ctx)
entry = SemanticEntry(kind=kind, name=name, definition=definition,
created_by=ctx.identity.user_id)
await ctx.scope_resolver.define(scope, entry)
if ctx.audit is not None:
await ctx.audit.record(
AuditEvent(actor=ctx.identity.user_id, action="define_metric",
scope=str(scope), detail={"name": name, "kind": kind.value})
)
return ToolResult(call_id="", content=f"✅ {kind.value} '{name}' defined at {scope}.")
@staticmethod
def _resolve_scope(requested: str | None, ctx: "HarnessContext") -> Scope:
if requested == "guild" and ctx.identity.guild_id:
return Scope(ScopeLevel.GUILD, ctx.identity.guild_id)
return ctx.identity.default_write_scope()