|
1 | 1 | import logging |
2 | 2 | import os |
| 3 | +from pprint import pformat |
3 | 4 |
|
4 | 5 | import httpx |
5 | 6 | import opentelemetry.exporter.otlp.proto.http.trace_exporter as otel_trace_exporter |
| 7 | +import phoenix.otel |
| 8 | +from opentelemetry.sdk.trace.export import BatchSpanProcessor |
6 | 9 |
|
7 | 10 | # https://docs.arize.com/phoenix/tracing/integrations-tracing/haystack |
8 | 11 | # Arize's Phoenix observability platform |
9 | | -import phoenix.client |
10 | | -import phoenix.otel |
11 | | -from opentelemetry.sdk.trace.export import BatchSpanProcessor |
| 12 | +from phoenix.client import Client |
| 13 | +from phoenix.client.types import PromptVersion |
12 | 14 |
|
13 | 15 | from src.app_config import config |
14 | 16 | from src.logging.presidio_pii_filter import PresidioRedactionSpanProcessor |
15 | 17 |
|
16 | 18 | logger = logging.getLogger(__name__) |
17 | 19 |
|
18 | 20 |
|
19 | | -def _create_client() -> phoenix.client.Client: |
20 | | - logger.info("Creating Phoenix client to %s", config.phoenix_collector_endpoint) |
21 | | - # If base_url is None, then phoenix.client.Client defaults to PHOENIX_COLLECTOR_ENDPOINT |
22 | | - # env variable value or "http://localhost:6006" |
23 | | - return phoenix.client.Client(base_url=config.phoenix_collector_endpoint) |
| 21 | +def _create_client( |
| 22 | + url: str = config.phoenix_collector_endpoint, api_key: str | None = None |
| 23 | +) -> Client: |
| 24 | + logger.info("Creating Phoenix client to %s", url) |
| 25 | + return Client(base_url=url, api_key=api_key) |
24 | 26 |
|
25 | 27 |
|
26 | 28 | def service_alive() -> bool: |
@@ -70,3 +72,73 @@ def configure_phoenix(only_if_alive: bool = True) -> None: |
70 | 72 | if config.batch_otel: |
71 | 73 | tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter)) |
72 | 74 | tracer_provider.add_span_processor(pii_processor) |
| 75 | + |
| 76 | + |
| 77 | +def get_prompt_template(prompt_name: str) -> PromptVersion: |
| 78 | + """Retrieve a prompt template from Phoenix by name. |
| 79 | + https://arize.com/docs/phoenix/sdk-api-reference/python/overview#prompt-management |
| 80 | + """ |
| 81 | + prompt_params = which_prompt_version(prompt_name) |
| 82 | + client = _create_client() |
| 83 | + prompt = client.prompts.get(**prompt_params) |
| 84 | + logger.info( |
| 85 | + "Retrieved prompt with %r: id='%s'\n%s", prompt_params, prompt.id, pformat(prompt._dumps()) |
| 86 | + ) |
| 87 | + return prompt |
| 88 | + |
| 89 | + |
| 90 | +def which_prompt_version(prompt_name: str) -> dict: |
| 91 | + if config.environment == "local": |
| 92 | + # Get the latest version regardless of tags |
| 93 | + return {"prompt_identifier": prompt_name} |
| 94 | + |
| 95 | + # Use the hardcoded version ids |
| 96 | + return {"prompt_version_id": config.PROMPT_VERSIONS[prompt_name]} |
| 97 | + |
| 98 | + |
| 99 | +def copy_deployed_prompts() -> None: |
| 100 | + logging.basicConfig(format="%(levelname)s - %(name)s - %(message)s", level=logging.INFO) |
| 101 | + |
| 102 | + url = os.environ.get("DEPLOYED_PHOENIX_URL") |
| 103 | + api_key = os.environ.get("DEPLOYED_PHOENIX_API_KEY") |
| 104 | + logger.info("Copying prompts from %s with API key: %r", url, api_key) |
| 105 | + assert url, "DEPLOYED_PHOENIX_URL is not set -- add it to override.env" |
| 106 | + assert api_key, "DEPLOYED_PHOENIX_API_KEY is not set -- add it to override.env" |
| 107 | + |
| 108 | + src_client = _create_client(url, api_key=api_key) |
| 109 | + local_client = _create_client() |
| 110 | + for prompt in list_prompts(src_client): |
| 111 | + # The prompt id is base64 encoding of 'Prompt:N' where N is simply a counter |
| 112 | + logger.info("Copying prompt: %r with id=%r)", prompt["name"], prompt["id"]) |
| 113 | + copy_prompt(src_client, local_client, prompt["name"]) |
| 114 | + |
| 115 | + |
| 116 | +def list_prompts(client: Client) -> list[dict]: |
| 117 | + "client.prompts doesn't have a list() method, so use the underlying httpx client." |
| 118 | + response = client._client.get("/v1/prompts") |
| 119 | + return response.json()["data"] |
| 120 | + |
| 121 | + |
| 122 | +def copy_prompt(src_client: Client, local_client: Client, prompt_name: str) -> None: |
| 123 | + "Copy a prompt from src_client to local_client" |
| 124 | + if prompt_name not in config.PROMPT_VERSIONS: |
| 125 | + logger.warning("No version id found for prompt %r -- skipping", prompt_name) |
| 126 | + return |
| 127 | + |
| 128 | + prompt_ver = src_client.prompts.get(prompt_version_id=config.PROMPT_VERSIONS[prompt_name]) |
| 129 | + logger.info("Retrieved prompt with id='%s'\n%s", prompt_ver.id, pformat(prompt_ver._dumps())) |
| 130 | + |
| 131 | + logger.info("Creating prompt %r in %r", prompt_name, local_client._client.base_url) |
| 132 | + # If prompt_name already exists, a new prompt version will be created |
| 133 | + local_client.prompts.create( |
| 134 | + version=prompt_ver, name=prompt_name, prompt_description=prompt_ver._description |
| 135 | + ) |
| 136 | + |
| 137 | + |
| 138 | +def list_prompt_version_ids(prompt_name: str, client: Client) -> list[str]: |
| 139 | + "List all version ids for a given prompt name. client.prompts doesn't have a list_versions() method." |
| 140 | + response = client._client.get(f"/v1/prompts/{prompt_name}/versions") |
| 141 | + resp_data = response.json()["data"] |
| 142 | + # version tags are not in the response |
| 143 | + return [ver["id"] for ver in resp_data] |
| 144 | + # To get tags for the version: client.prompts.tags.list(prompt_version_id=version_id) |
0 commit comments