diff --git a/posthog/test/ai/test_prompts.py b/posthog/test/ai/test_prompts.py index 9a860f38..15d4b911 100644 --- a/posthog/test/ai/test_prompts.py +++ b/posthog/test/ai/test_prompts.py @@ -820,9 +820,15 @@ def test_mutating_a_result_config_does_not_pollute_the_cache( self, mock_get_session ): """Callers often mutate config before spreading it into an LLM call; that must not leak into later cache hits.""" + # Nested values included: a shallow copy would pass the top-level mutations + # below but leak the nested one into the cache. + nested_config = { + "model": "gpt-4o", + "tools": [{"name": "search", "parameters": {"depth": 1}}], + } mock_get = mock_get_session.return_value.get mock_get.return_value = MockResponse( - json_data={**self.mock_prompt_response, "config": self.mock_config} + json_data={**self.mock_prompt_response, "config": nested_config} ) prompts = Prompts(self.create_mock_posthog()) @@ -831,11 +837,18 @@ def test_mutating_a_result_config_does_not_pollute_the_cache( assert first.config is not None first.config["temperature"] = 0.9 first.config.pop("model") + first.config["tools"][0]["parameters"]["depth"] = 99 second = prompts.get("test-prompt", with_metadata=True) self.assertEqual(second.source, "cache") - self.assertEqual(second.config, self.mock_config) + self.assertEqual( + second.config, + { + "model": "gpt-4o", + "tools": [{"name": "search", "parameters": {"depth": 1}}], + }, + ) @parameterized.expand( [ diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index 90f4f940..d83fbb74 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -425,6 +425,7 @@ attribute posthog.ai.openai_agents.processor.log = logging.getLogger('posthog') attribute posthog.ai.otel.spans.AI_SPAN_PREFIXES = ('gen_ai.', 'llm.', 'ai.', 'traceloop.') attribute posthog.ai.otel.spans.DEFAULT_HOST = 'https://us.i.posthog.com' attribute posthog.ai.prompts.APP_ENDPOINT = 'https://us.posthog.com' +attribute posthog.ai.prompts.CachedPrompt.config = config attribute posthog.ai.prompts.CachedPrompt.fetched_at = fetched_at attribute posthog.ai.prompts.CachedPrompt.label = label attribute posthog.ai.prompts.CachedPrompt.name = name @@ -432,6 +433,7 @@ attribute posthog.ai.prompts.CachedPrompt.prompt = prompt attribute posthog.ai.prompts.CachedPrompt.version = version attribute posthog.ai.prompts.DEFAULT_CACHE_TTL_SECONDS = 300 attribute posthog.ai.prompts.PromptCacheKey = tuple[str, Optional[int], Optional[str]] +attribute posthog.ai.prompts.PromptResult.config: Optional[Dict[str, Any]] = None attribute posthog.ai.prompts.PromptResult.label: Optional[str] = None attribute posthog.ai.prompts.PromptResult.name: Optional[str] = None attribute posthog.ai.prompts.PromptResult.prompt: str @@ -887,8 +889,8 @@ class posthog.ai.openai.openai_providers.AzureOpenAI(posthog_client: Optional[Po class posthog.ai.openai_agents.processor.PostHogTracingProcessor(client: Optional[Client] = None, distinct_id: Optional[Union[str, Callable[[Trace], Optional[str]]]] = None, privacy_mode: bool = False, groups: Optional[Dict[str, Any]] = None, properties: Optional[Dict[str, Any]] = None) class posthog.ai.otel.exporter.PostHogTraceExporter(api_key: str, host: str = DEFAULT_HOST) class posthog.ai.otel.processor.PostHogSpanProcessor(api_key: str, host: str = DEFAULT_HOST) -class posthog.ai.prompts.CachedPrompt(prompt: str, fetched_at: float, name: str, version: int, label: Optional[str] = None) -class posthog.ai.prompts.PromptResult(source: PromptSource, prompt: str, name: Optional[str] = None, version: Optional[int] = None, label: Optional[str] = None) +class posthog.ai.prompts.CachedPrompt(prompt: str, fetched_at: float, name: str, version: int, label: Optional[str] = None, config: Optional[Dict[str, Any]] = None) +class posthog.ai.prompts.PromptResult(source: PromptSource, prompt: str, name: Optional[str] = None, version: Optional[int] = None, label: Optional[str] = None, config: Optional[Dict[str, Any]] = None) class posthog.ai.prompts.Prompts(posthog: Optional[Any] = None, *, personal_api_key: Optional[str] = None, project_api_key: Optional[str] = None, host: Optional[str] = None, default_cache_ttl_seconds: Optional[int] = None, capture_errors: bool = False) class posthog.ai.stream.AsyncStreamWrapper(generator: AsyncGenerator[T, None], stream: Optional[Any] = None) class posthog.ai.types.FormattedFunctionCall