remdb 0.2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of remdb might be problematic. Click here for more details.
- rem/__init__.py +2 -0
- rem/agentic/README.md +650 -0
- rem/agentic/__init__.py +39 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +8 -0
- rem/agentic/context.py +148 -0
- rem/agentic/context_builder.py +329 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +107 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +151 -0
- rem/agentic/providers/phoenix.py +674 -0
- rem/agentic/providers/pydantic_ai.py +572 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +396 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +231 -0
- rem/api/README.md +420 -0
- rem/api/main.py +324 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +536 -0
- rem/api/mcp_router/server.py +213 -0
- rem/api/mcp_router/tools.py +584 -0
- rem/api/routers/auth.py +229 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/completions.py +281 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +124 -0
- rem/api/routers/chat/streaming.py +185 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +26 -0
- rem/auth/middleware.py +100 -0
- rem/auth/providers/__init__.py +13 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +455 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +126 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +565 -0
- rem/cli/commands/configure.py +423 -0
- rem/cli/commands/db.py +493 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1124 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +245 -0
- rem/cli/commands/schema.py +183 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +88 -0
- rem/config.py +237 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +64 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +628 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +243 -0
- rem/models/entities/__init__.py +43 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +35 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +191 -0
- rem/models/entities/ontology_config.py +131 -0
- rem/models/entities/resource.py +95 -0
- rem/models/entities/schema.py +87 -0
- rem/models/entities/user.py +85 -0
- rem/py.typed +0 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -0
- rem/schemas/agents/core/moment-builder.yaml +178 -0
- rem/schemas/agents/core/rem-query-agent.yaml +226 -0
- rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
- rem/schemas/agents/core/simple-assistant.yaml +19 -0
- rem/schemas/agents/core/user-profile-builder.yaml +163 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
- rem/schemas/agents/examples/contract-extractor.yaml +134 -0
- rem/schemas/agents/examples/cv-parser.yaml +263 -0
- rem/schemas/agents/examples/hello-world.yaml +37 -0
- rem/schemas/agents/examples/query.yaml +54 -0
- rem/schemas/agents/examples/simple.yaml +21 -0
- rem/schemas/agents/examples/test.yaml +29 -0
- rem/schemas/agents/rem.yaml +128 -0
- rem/schemas/evaluators/hello-world/default.yaml +77 -0
- rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
- rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
- rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
- rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
- rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
- rem/services/__init__.py +16 -0
- rem/services/audio/INTEGRATION.md +308 -0
- rem/services/audio/README.md +376 -0
- rem/services/audio/__init__.py +15 -0
- rem/services/audio/chunker.py +354 -0
- rem/services/audio/transcriber.py +259 -0
- rem/services/content/README.md +1269 -0
- rem/services/content/__init__.py +5 -0
- rem/services/content/providers.py +806 -0
- rem/services/content/service.py +657 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +336 -0
- rem/services/dreaming/moment_service.py +264 -0
- rem/services/dreaming/ontology_service.py +54 -0
- rem/services/dreaming/user_model_service.py +297 -0
- rem/services/dreaming/utils.py +39 -0
- rem/services/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +120 -0
- rem/services/embeddings/worker.py +421 -0
- rem/services/fs/README.md +662 -0
- rem/services/fs/__init__.py +62 -0
- rem/services/fs/examples.py +206 -0
- rem/services/fs/examples_paths.py +204 -0
- rem/services/fs/git_provider.py +935 -0
- rem/services/fs/local_provider.py +760 -0
- rem/services/fs/parsing-hooks-examples.md +172 -0
- rem/services/fs/paths.py +276 -0
- rem/services/fs/provider.py +460 -0
- rem/services/fs/s3_provider.py +1042 -0
- rem/services/fs/service.py +186 -0
- rem/services/git/README.md +1075 -0
- rem/services/git/__init__.py +17 -0
- rem/services/git/service.py +469 -0
- rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
- rem/services/phoenix/README.md +453 -0
- rem/services/phoenix/__init__.py +46 -0
- rem/services/phoenix/client.py +686 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +575 -0
- rem/services/postgres/__init__.py +23 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
- rem/services/postgres/register_type.py +352 -0
- rem/services/postgres/repository.py +337 -0
- rem/services/postgres/schema_generator.py +379 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +354 -0
- rem/services/rem/README.md +304 -0
- rem/services/rem/__init__.py +23 -0
- rem/services/rem/exceptions.py +71 -0
- rem/services/rem/executor.py +293 -0
- rem/services/rem/parser.py +145 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +527 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +6 -0
- rem/services/session/compression.py +360 -0
- rem/services/session/reload.py +77 -0
- rem/settings.py +1235 -0
- rem/sql/002_install_models.sql +1068 -0
- rem/sql/background_indexes.sql +42 -0
- rem/sql/install_models.sql +1038 -0
- rem/sql/migrations/001_install.sql +503 -0
- rem/sql/migrations/002_install_models.sql +1202 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +583 -0
- rem/utils/__init__.py +43 -0
- rem/utils/agentic_chunking.py +622 -0
- rem/utils/batch_ops.py +343 -0
- rem/utils/chunking.py +108 -0
- rem/utils/clip_embeddings.py +276 -0
- rem/utils/dict_utils.py +98 -0
- rem/utils/embeddings.py +423 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/markdown.py +16 -0
- rem/utils/model_helpers.py +236 -0
- rem/utils/schema_loader.py +229 -0
- rem/utils/sql_types.py +348 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +330 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +5 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- remdb-0.2.6.dist-info/METADATA +1191 -0
- remdb-0.2.6.dist-info/RECORD +187 -0
- remdb-0.2.6.dist-info/WHEEL +4 -0
- remdb-0.2.6.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Phoenix configuration for REM.
|
|
2
|
+
|
|
3
|
+
Loads connection settings from environment variables with sensible defaults.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PhoenixConfig:
|
|
11
|
+
"""Phoenix connection configuration.
|
|
12
|
+
|
|
13
|
+
Environment Variables:
|
|
14
|
+
- PHOENIX_BASE_URL: Phoenix server URL (default: http://localhost:6006)
|
|
15
|
+
- PHOENIX_API_KEY: API key for authentication (required for cluster Phoenix)
|
|
16
|
+
|
|
17
|
+
Deployment Patterns:
|
|
18
|
+
--------------------
|
|
19
|
+
|
|
20
|
+
**Production (Cluster Phoenix)** - RECOMMENDED:
|
|
21
|
+
REM typically runs on Kubernetes alongside Phoenix in the observability namespace.
|
|
22
|
+
Experiments run directly on the cluster where Phoenix is deployed:
|
|
23
|
+
|
|
24
|
+
# 1. Deploy experiment as K8s Job or run from rem-api pod
|
|
25
|
+
kubectl exec -it deployment/rem-api -- rem experiments run my-experiment
|
|
26
|
+
|
|
27
|
+
# 2. Phoenix accessible via service DNS
|
|
28
|
+
export PHOENIX_BASE_URL=http://phoenix-svc.observability.svc.cluster.local:6006
|
|
29
|
+
export PHOENIX_API_KEY=<your-key>
|
|
30
|
+
|
|
31
|
+
**Development (Port-Forward)** - For local testing:
|
|
32
|
+
Port-forward Phoenix from cluster to local machine:
|
|
33
|
+
|
|
34
|
+
# 1. Port-forward Phoenix service
|
|
35
|
+
kubectl port-forward -n observability svc/phoenix-svc 6006:6006
|
|
36
|
+
|
|
37
|
+
# 2. Set API key
|
|
38
|
+
export PHOENIX_API_KEY=<your-key>
|
|
39
|
+
|
|
40
|
+
# 3. Run experiments locally
|
|
41
|
+
rem experiments run my-experiment
|
|
42
|
+
# Connects to localhost:6006 → cluster Phoenix
|
|
43
|
+
|
|
44
|
+
**Local Development (Local Phoenix)** - For offline work:
|
|
45
|
+
Run Phoenix locally without cluster connection:
|
|
46
|
+
|
|
47
|
+
# 1. Start local Phoenix
|
|
48
|
+
python -m phoenix.server.main serve
|
|
49
|
+
|
|
50
|
+
# 2. Run experiments (no API key needed)
|
|
51
|
+
rem experiments run my-experiment
|
|
52
|
+
# Connects to localhost:6006 → local Phoenix
|
|
53
|
+
|
|
54
|
+
Override Defaults:
|
|
55
|
+
------------------
|
|
56
|
+
Commands respect PHOENIX_BASE_URL and PHOENIX_API_KEY environment variables.
|
|
57
|
+
Default is localhost:6006 for local development compatibility.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
base_url: str | None = None,
|
|
63
|
+
api_key: str | None = None,
|
|
64
|
+
):
|
|
65
|
+
"""Initialize Phoenix configuration.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
base_url: Phoenix server URL (overrides env var)
|
|
69
|
+
api_key: API key for authentication (overrides env var)
|
|
70
|
+
"""
|
|
71
|
+
self.base_url = base_url or os.getenv(
|
|
72
|
+
"PHOENIX_BASE_URL", "http://localhost:6006"
|
|
73
|
+
)
|
|
74
|
+
self.api_key = api_key or os.getenv("PHOENIX_API_KEY")
|
|
75
|
+
|
|
76
|
+
logger.debug(f"Phoenix config: base_url={self.base_url}, api_key={'***' if self.api_key else 'None'}")
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_settings(cls) -> "PhoenixConfig":
|
|
80
|
+
"""Load Phoenix configuration from REM settings.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
PhoenixConfig with values from settings
|
|
84
|
+
|
|
85
|
+
Note: Currently loads from env vars. Could be extended to use
|
|
86
|
+
rem.settings.Settings if Phoenix settings are added there.
|
|
87
|
+
"""
|
|
88
|
+
return cls()
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"""Phoenix prompt label management via GraphQL.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for creating and assigning labels to prompts
|
|
4
|
+
in Phoenix using the GraphQL API.
|
|
5
|
+
|
|
6
|
+
Based on carrier implementation with adaptations for REM:
|
|
7
|
+
- Standard REM labels: rem, golden-set, agent-results, evaluator
|
|
8
|
+
- GraphQL operations: create, list, assign labels
|
|
9
|
+
- Color-coded label system
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from loguru import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PromptLabelQueries:
|
|
18
|
+
"""GraphQL queries for prompt and dataset label operations."""
|
|
19
|
+
|
|
20
|
+
CREATE_PROMPT_LABEL = """
|
|
21
|
+
mutation CreateLabel($input: CreatePromptLabelInput!) {
|
|
22
|
+
createPromptLabel(input: $input) {
|
|
23
|
+
promptLabels {
|
|
24
|
+
id
|
|
25
|
+
name
|
|
26
|
+
color
|
|
27
|
+
description
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
CREATE_DATASET_LABEL = """
|
|
34
|
+
mutation CreateDatasetLabel($input: CreateDatasetLabelInput!) {
|
|
35
|
+
createDatasetLabel(input: $input) {
|
|
36
|
+
datasetLabel {
|
|
37
|
+
id
|
|
38
|
+
name
|
|
39
|
+
color
|
|
40
|
+
description
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
LIST_PROMPT_LABELS = """
|
|
47
|
+
query {
|
|
48
|
+
promptLabels {
|
|
49
|
+
edges {
|
|
50
|
+
node {
|
|
51
|
+
id
|
|
52
|
+
name
|
|
53
|
+
color
|
|
54
|
+
description
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
LIST_DATASET_LABELS = """
|
|
62
|
+
query {
|
|
63
|
+
datasetLabels {
|
|
64
|
+
edges {
|
|
65
|
+
node {
|
|
66
|
+
id
|
|
67
|
+
name
|
|
68
|
+
color
|
|
69
|
+
description
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
GET_DATASET_BY_NAME = """
|
|
77
|
+
query GetDataset($name: String!) {
|
|
78
|
+
datasets(first: 1, filterBy: {name: {equals: $name}}) {
|
|
79
|
+
edges {
|
|
80
|
+
node {
|
|
81
|
+
id
|
|
82
|
+
name
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
ASSIGN_PROMPT_LABELS = """
|
|
90
|
+
mutation SetLabels($promptId: ID!, $promptLabelIds: [ID!]!) {
|
|
91
|
+
setPromptLabels(input: {
|
|
92
|
+
promptId: $promptId
|
|
93
|
+
promptLabelIds: $promptLabelIds
|
|
94
|
+
}) {
|
|
95
|
+
query {
|
|
96
|
+
node(id: $promptId) {
|
|
97
|
+
__typename
|
|
98
|
+
... on Prompt {
|
|
99
|
+
id
|
|
100
|
+
name
|
|
101
|
+
labels {
|
|
102
|
+
id
|
|
103
|
+
name
|
|
104
|
+
color
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
ASSIGN_DATASET_LABELS = """
|
|
114
|
+
mutation SetDatasetLabels($datasetId: ID!, $datasetLabelIds: [ID!]!) {
|
|
115
|
+
setDatasetLabels(input: {
|
|
116
|
+
datasetId: $datasetId
|
|
117
|
+
datasetLabelIds: $datasetLabelIds
|
|
118
|
+
}) {
|
|
119
|
+
dataset {
|
|
120
|
+
id
|
|
121
|
+
labels {
|
|
122
|
+
id
|
|
123
|
+
name
|
|
124
|
+
color
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class GraphQLClient:
|
|
133
|
+
"""Simple GraphQL client for Phoenix API."""
|
|
134
|
+
|
|
135
|
+
def __init__(self, base_url: str, api_key: Optional[str] = None):
|
|
136
|
+
"""Initialize GraphQL client.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
base_url: Phoenix base URL
|
|
140
|
+
api_key: Optional API key for auth
|
|
141
|
+
"""
|
|
142
|
+
self.base_url = base_url
|
|
143
|
+
self.api_key = api_key
|
|
144
|
+
|
|
145
|
+
def execute(self, query: str, variables: Optional[dict] = None) -> dict:
|
|
146
|
+
"""Execute GraphQL query.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
query: GraphQL query/mutation string
|
|
150
|
+
variables: Optional variables dict
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Response data dict
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
httpx.HTTPStatusError: On HTTP errors
|
|
157
|
+
"""
|
|
158
|
+
headers = {"Content-Type": "application/json"}
|
|
159
|
+
if self.api_key:
|
|
160
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
161
|
+
|
|
162
|
+
response = httpx.post(
|
|
163
|
+
f"{self.base_url}/graphql",
|
|
164
|
+
headers=headers,
|
|
165
|
+
json={"query": query, "variables": variables},
|
|
166
|
+
timeout=10,
|
|
167
|
+
)
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
|
|
170
|
+
result = response.json()
|
|
171
|
+
|
|
172
|
+
# Log errors but don't raise
|
|
173
|
+
if result.get("errors"):
|
|
174
|
+
error_msgs = [e.get("message", "Unknown error") for e in result["errors"]]
|
|
175
|
+
logger.debug(f"GraphQL errors: {error_msgs}")
|
|
176
|
+
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class PhoenixPromptLabels:
|
|
181
|
+
"""Helper for managing Phoenix prompt labels via GraphQL."""
|
|
182
|
+
|
|
183
|
+
def __init__(self, base_url: str = "http://localhost:6006", api_key: Optional[str] = None):
|
|
184
|
+
"""Initialize label manager.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
base_url: Phoenix base URL
|
|
188
|
+
api_key: Phoenix API key (optional, for auth)
|
|
189
|
+
"""
|
|
190
|
+
self.client = GraphQLClient(base_url, api_key)
|
|
191
|
+
self._label_cache: Optional[dict[str, str]] = None
|
|
192
|
+
|
|
193
|
+
def create_prompt_label(
|
|
194
|
+
self,
|
|
195
|
+
name: str,
|
|
196
|
+
color: str,
|
|
197
|
+
description: str = "",
|
|
198
|
+
) -> Optional[str]:
|
|
199
|
+
"""Create a new prompt label.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
name: Label name (lowercase, hyphens, underscores)
|
|
203
|
+
color: Color in rgba(r, g, b, a) format
|
|
204
|
+
description: Optional description
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Label ID if created, None if already exists
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
httpx.HTTPStatusError: On HTTP errors
|
|
211
|
+
"""
|
|
212
|
+
variables = {"input": {"name": name, "color": color, "description": description}}
|
|
213
|
+
result = self.client.execute(PromptLabelQueries.CREATE_PROMPT_LABEL, variables)
|
|
214
|
+
|
|
215
|
+
# Check if already exists
|
|
216
|
+
if result.get("errors"):
|
|
217
|
+
for error in result["errors"]:
|
|
218
|
+
if "already exists" in error.get("message", ""):
|
|
219
|
+
logger.debug(f"Prompt label '{name}' already exists")
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
# Extract label ID from response
|
|
223
|
+
if result.get("data") and result["data"].get("createPromptLabel"):
|
|
224
|
+
labels = result["data"]["createPromptLabel"]["promptLabels"]
|
|
225
|
+
if labels:
|
|
226
|
+
label_id = labels[0]["id"]
|
|
227
|
+
logger.info(f"Created prompt label '{name}' ({label_id})")
|
|
228
|
+
# Invalidate cache
|
|
229
|
+
self._label_cache = None
|
|
230
|
+
return label_id
|
|
231
|
+
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
def create_dataset_label(
|
|
235
|
+
self,
|
|
236
|
+
name: str,
|
|
237
|
+
color: str,
|
|
238
|
+
description: str = "",
|
|
239
|
+
) -> Optional[str]:
|
|
240
|
+
"""Create a new dataset label.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
name: Label name
|
|
244
|
+
color: Color in rgba(r, g, b, a) format
|
|
245
|
+
description: Optional description
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Label ID if created, None if already exists
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
httpx.HTTPStatusError: On HTTP errors
|
|
252
|
+
"""
|
|
253
|
+
variables = {"input": {"name": name, "color": color, "description": description}}
|
|
254
|
+
result = self.client.execute(PromptLabelQueries.CREATE_DATASET_LABEL, variables)
|
|
255
|
+
|
|
256
|
+
# Check if already exists
|
|
257
|
+
if result.get("errors"):
|
|
258
|
+
for error in result["errors"]:
|
|
259
|
+
if "already exists" in error.get("message", ""):
|
|
260
|
+
logger.debug(f"Dataset label '{name}' already exists")
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
# Extract label ID from response
|
|
264
|
+
if result.get("data") and result["data"].get("createDatasetLabel"):
|
|
265
|
+
label = result["data"]["createDatasetLabel"]["datasetLabel"]
|
|
266
|
+
label_id = label["id"]
|
|
267
|
+
logger.info(f"Created dataset label '{name}' ({label_id})")
|
|
268
|
+
return label_id
|
|
269
|
+
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
def list_prompt_labels(self, force_refresh: bool = False) -> dict[str, str]:
|
|
273
|
+
"""List all available prompt labels.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
force_refresh: Force refresh cache
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Dict mapping label name to label ID
|
|
280
|
+
"""
|
|
281
|
+
if self._label_cache and not force_refresh:
|
|
282
|
+
return self._label_cache
|
|
283
|
+
|
|
284
|
+
result = self.client.execute(PromptLabelQueries.LIST_PROMPT_LABELS)
|
|
285
|
+
labels = {}
|
|
286
|
+
|
|
287
|
+
if result.get("data", {}).get("promptLabels"):
|
|
288
|
+
for edge in result["data"]["promptLabels"]["edges"]:
|
|
289
|
+
label = edge["node"]
|
|
290
|
+
labels[label["name"]] = label["id"]
|
|
291
|
+
|
|
292
|
+
self._label_cache = labels
|
|
293
|
+
return labels
|
|
294
|
+
|
|
295
|
+
def list_dataset_labels(self) -> dict[str, str]:
|
|
296
|
+
"""List all available dataset labels.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Dict mapping label name to label ID
|
|
300
|
+
"""
|
|
301
|
+
result = self.client.execute(PromptLabelQueries.LIST_DATASET_LABELS)
|
|
302
|
+
labels = {}
|
|
303
|
+
|
|
304
|
+
if result.get("data", {}).get("datasetLabels"):
|
|
305
|
+
for edge in result["data"]["datasetLabels"]["edges"]:
|
|
306
|
+
label = edge["node"]
|
|
307
|
+
labels[label["name"]] = label["id"]
|
|
308
|
+
|
|
309
|
+
return labels
|
|
310
|
+
|
|
311
|
+
def ensure_prompt_labels(self, labels: list[tuple[str, str, str]]) -> dict[str, str]:
|
|
312
|
+
"""Ensure prompt labels exist, creating them if needed.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
labels: List of (name, color, description) tuples
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Dict mapping label name to label ID
|
|
319
|
+
"""
|
|
320
|
+
existing = self.list_prompt_labels(force_refresh=True)
|
|
321
|
+
|
|
322
|
+
for name, color, description in labels:
|
|
323
|
+
if name not in existing:
|
|
324
|
+
label_id = self.create_prompt_label(name, color, description)
|
|
325
|
+
if label_id:
|
|
326
|
+
existing[name] = label_id
|
|
327
|
+
|
|
328
|
+
return existing
|
|
329
|
+
|
|
330
|
+
def ensure_dataset_labels(self, labels: list[tuple[str, str, str]]) -> dict[str, str]:
|
|
331
|
+
"""Ensure dataset labels exist, creating them if needed.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
labels: List of (name, color, description) tuples
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Dict mapping label name to label ID
|
|
338
|
+
"""
|
|
339
|
+
existing = self.list_dataset_labels()
|
|
340
|
+
|
|
341
|
+
for name, color, description in labels:
|
|
342
|
+
if name not in existing:
|
|
343
|
+
label_id = self.create_dataset_label(name, color, description)
|
|
344
|
+
if label_id:
|
|
345
|
+
existing[name] = label_id
|
|
346
|
+
|
|
347
|
+
return existing
|
|
348
|
+
|
|
349
|
+
def assign_prompt_labels(
|
|
350
|
+
self,
|
|
351
|
+
prompt_id: str,
|
|
352
|
+
label_names: list[str],
|
|
353
|
+
) -> list[dict]:
|
|
354
|
+
"""Assign labels to a prompt.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
prompt_id: Prompt ID (from prompts GraphQL query)
|
|
358
|
+
label_names: List of label names to assign
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
List of assigned labels with id, name, color
|
|
362
|
+
|
|
363
|
+
Raises:
|
|
364
|
+
ValueError: If label not found
|
|
365
|
+
httpx.HTTPStatusError: On HTTP errors
|
|
366
|
+
"""
|
|
367
|
+
available_labels = self.list_prompt_labels()
|
|
368
|
+
|
|
369
|
+
# Convert label names to IDs
|
|
370
|
+
label_ids = []
|
|
371
|
+
for name in label_names:
|
|
372
|
+
if name not in available_labels:
|
|
373
|
+
raise ValueError(
|
|
374
|
+
f"Prompt label '{name}' not found. Available: {list(available_labels.keys())}"
|
|
375
|
+
)
|
|
376
|
+
label_ids.append(available_labels[name])
|
|
377
|
+
|
|
378
|
+
variables = {"promptId": prompt_id, "promptLabelIds": label_ids}
|
|
379
|
+
result = self.client.execute(PromptLabelQueries.ASSIGN_PROMPT_LABELS, variables)
|
|
380
|
+
|
|
381
|
+
if result.get("data") and result["data"].get("setPromptLabels"):
|
|
382
|
+
node = result["data"]["setPromptLabels"]["query"]["node"]
|
|
383
|
+
labels = node.get("labels", [])
|
|
384
|
+
logger.info(f"Assigned {len(label_names)} labels to prompt {node['name']}")
|
|
385
|
+
return labels
|
|
386
|
+
|
|
387
|
+
return []
|
|
388
|
+
|
|
389
|
+
def get_dataset_id(self, dataset_name: str) -> str | None:
|
|
390
|
+
"""Get dataset ID by name.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
dataset_name: Dataset name
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Dataset ID if found, None otherwise
|
|
397
|
+
"""
|
|
398
|
+
variables = {"name": dataset_name}
|
|
399
|
+
result = self.client.execute(PromptLabelQueries.GET_DATASET_BY_NAME, variables)
|
|
400
|
+
|
|
401
|
+
if result.get("data") and result["data"].get("datasets"):
|
|
402
|
+
edges = result["data"]["datasets"]["edges"]
|
|
403
|
+
if edges:
|
|
404
|
+
return edges[0]["node"]["id"]
|
|
405
|
+
|
|
406
|
+
return None
|
|
407
|
+
|
|
408
|
+
def assign_dataset_labels(
|
|
409
|
+
self,
|
|
410
|
+
dataset_id: str,
|
|
411
|
+
label_names: list[str],
|
|
412
|
+
) -> bool:
|
|
413
|
+
"""Assign labels to a dataset.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
dataset_id: Dataset ID (from datasets GraphQL query)
|
|
417
|
+
label_names: List of label names to assign
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
True if successful
|
|
421
|
+
|
|
422
|
+
Raises:
|
|
423
|
+
ValueError: If label not found
|
|
424
|
+
httpx.HTTPStatusError: On HTTP errors
|
|
425
|
+
"""
|
|
426
|
+
available_labels = self.list_dataset_labels()
|
|
427
|
+
|
|
428
|
+
# Convert label names to IDs
|
|
429
|
+
label_ids = []
|
|
430
|
+
for name in label_names:
|
|
431
|
+
if name not in available_labels:
|
|
432
|
+
raise ValueError(
|
|
433
|
+
f"Dataset label '{name}' not found. Available: {list(available_labels.keys())}"
|
|
434
|
+
)
|
|
435
|
+
label_ids.append(available_labels[name])
|
|
436
|
+
|
|
437
|
+
variables = {"datasetId": dataset_id, "datasetLabelIds": label_ids}
|
|
438
|
+
result = self.client.execute(PromptLabelQueries.ASSIGN_DATASET_LABELS, variables)
|
|
439
|
+
|
|
440
|
+
if result.get("data") and result["data"].get("setDatasetLabels"):
|
|
441
|
+
dataset = result["data"]["setDatasetLabels"]["dataset"]
|
|
442
|
+
labels = dataset.get("labels", [])
|
|
443
|
+
logger.info(f"Assigned {len(labels)} labels to dataset {dataset_id}")
|
|
444
|
+
return True
|
|
445
|
+
|
|
446
|
+
return False
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# Standard REM labels (shared by prompts and datasets)
|
|
450
|
+
# Phoenix expects hex colors (e.g., #3b82f6), not rgba format
|
|
451
|
+
REM_LABELS = [
|
|
452
|
+
("REM", "#3b82f6", "All REM integration artifacts (auto-added)"),
|
|
453
|
+
("Agent", "#10b981", "Agent prompts (auto-added for agents)"),
|
|
454
|
+
("Evaluator", "#8b5cf6", "Evaluator prompts (auto-added for evaluators)"),
|
|
455
|
+
("Ground Truth", "#22c55e", "Ground truth/golden set datasets (auto-added)"),
|
|
456
|
+
("Test", "#f59e0b", "Test and integration artifacts"),
|
|
457
|
+
("HelloWorld", "#ec4899", "Hello world test examples"),
|
|
458
|
+
]
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def setup_rem_labels(
|
|
462
|
+
base_url: str = "http://localhost:6006",
|
|
463
|
+
api_key: Optional[str] = None,
|
|
464
|
+
) -> PhoenixPromptLabels:
|
|
465
|
+
"""Setup standard REM prompt and dataset labels.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
base_url: Phoenix base URL
|
|
469
|
+
api_key: Phoenix API key
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Configured PhoenixPromptLabels instance
|
|
473
|
+
"""
|
|
474
|
+
helper = PhoenixPromptLabels(base_url=base_url, api_key=api_key)
|
|
475
|
+
helper.ensure_prompt_labels(REM_LABELS)
|
|
476
|
+
helper.ensure_dataset_labels(REM_LABELS)
|
|
477
|
+
return helper
|