Skip to content

Commit 0c57793

Browse files
jsonbaileyclaude
andcommitted
chore: update examples for AI SDK ManagedResult and tracker factory APIs
Migrate examples to the AI SDK API surface introduced by the upcoming ldai 0.18 / openai 0.4 / langchain 0.5 release stack: - ManagedModel.invoke() -> .run(); ModelResponse.message.content -> ManagedResult.content - chat_response.evaluations is a single asyncio.Task[List[JudgeResult]]; await it directly instead of asyncio.gather over a list - JudgeResult is a single value (not List); access .sampled / .success / .metric_key / .score / .reasoning / .error_message directly - AIConfig.tracker (field) -> AIConfig.create_tracker() (factory) - track_metrics_of(metrics_extractor, func) parameter order; use track_metrics_of_async for awaited callables (langchain example) - aiclient.create_judge() is sync; drop the unnecessary `await` - direct_judge default key updated to `sample-ai-judge` (configured) - chat_observability now declares launchdarkly-server-sdk-ai-openai as a dependency since it uses aiclient.create_model() which loads providers via the runner factory Bumps published version pins to ^0.18 / ^0.4 / ^0.5 to match the new API. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 78fe661 commit 0c57793

5 files changed

Lines changed: 55 additions & 46 deletions

File tree

examples/chat_observability/chat_observability_example.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,22 @@ async def async_main():
8888
user_input_1 = "What is feature flagging in 2 sentences?"
8989
print("User Input:", user_input_1)
9090

91-
response_1 = await chat.invoke(user_input_1)
92-
print("Chat Response:", response_1.message.content)
91+
response_1 = await chat.run(user_input_1)
92+
print("Chat Response:", response_1.content)
9393

9494
user_input_2 = "Give me a specific use case example."
9595
print("\nUser Input:", user_input_2)
9696

97-
response_2 = await chat.invoke(user_input_2)
98-
print("Chat Response:", response_2.message.content)
97+
response_2 = await chat.run(user_input_2)
98+
print("Chat Response:", response_2.content)
9999

100-
# Judge evaluations run asynchronously. Await them (e.g. with asyncio.gather) so they
100+
# Judge evaluations run asynchronously. Await them so they
101101
# complete before the process or request ends—even if you don't need to log or use
102102
# the results.
103-
if response_1.evaluations:
104-
await asyncio.gather(*response_1.evaluations)
105-
if response_2.evaluations:
106-
await asyncio.gather(*response_2.evaluations)
103+
if response_1.evaluations is not None:
104+
await response_1.evaluations
105+
if response_2.evaluations is not None:
106+
await response_2.evaluations
107107

108108
print("\nSuccess.")
109109

examples/judge/chat_judge_example.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
from dotenv import load_dotenv
3-
import json
43
import asyncio
54
import ldclient
65
from ldclient import Context
@@ -63,33 +62,38 @@ async def async_main():
6362
user_input = 'How can LaunchDarkly help me?'
6463
print("User Input:", user_input)
6564

66-
# The invoke method will automatically evaluate the chat response with any judges defined in the AI config
67-
chat_response = await chat.invoke(user_input)
68-
print("Chat Response:", chat_response.message.content)
65+
# The run method will automatically evaluate the chat response with any judges defined in the AI config
66+
chat_response = await chat.run(user_input)
67+
print("Chat Response:", chat_response.content)
6968

70-
# Judge evaluations run asynchronously. Await them (e.g. with asyncio.gather) so they
71-
# complete before the process or request ends—even if you don't need to log or use
72-
# the results. Below we await and then log the results for demonstration.
69+
# Judge evaluations run asynchronously. Await them so they complete before the
70+
# process or request ends—even if you don't need to log or use the results.
71+
# Below we await and then log the results for demonstration.
7372

7473
# Log judge evaluation results with full detail
75-
if chat_response.evaluations is not None and len(chat_response.evaluations) > 0:
74+
if chat_response.evaluations is not None:
7675
# Note: Judge evaluations run asynchronously and do not block your application.
7776
# Results are automatically sent to LaunchDarkly for AI config metrics.
7877
# You only need to await if you want to access the evaluation results in your code.
7978
print("\nNote: Awaiting judge results (optional - done here for demonstration only).")
80-
eval_results = await asyncio.gather(*chat_response.evaluations)
81-
82-
# Convert results, replacing None with a message
83-
results_to_display = [
84-
result.to_dict() if result is not None else "not evaluated"
85-
for result in eval_results
86-
]
87-
79+
eval_results = await chat_response.evaluations
80+
8881
print("Judge results:")
89-
print(json.dumps(results_to_display, indent=2, default=str))
90-
91-
if None in eval_results:
92-
print("\nNote: Some judge evaluations were skipped.")
82+
for result in eval_results:
83+
print(f" - sampled: {result.sampled}")
84+
print(f" success: {result.success}")
85+
if result.error_message is not None:
86+
print(f" error_message: {result.error_message}")
87+
if result.metric_key is not None:
88+
print(f" metric_key: {result.metric_key}")
89+
if result.score is not None:
90+
print(f" score: {result.score}")
91+
if result.reasoning is not None:
92+
print(f" reasoning: {result.reasoning}")
93+
94+
skipped = [r for r in eval_results if not r.sampled]
95+
if skipped:
96+
print("\nNote: Some judge evaluations were skipped (not sampled).")
9397
print("This typically happens when the sample rate doesn't require this evaluation, or due to a configuration issue.")
9498
print("Check application logs for more details.")
9599
else:

examples/judge/direct_judge_example.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
from dotenv import load_dotenv
3-
import json
43
import asyncio
54
import ldclient
65
from ldclient import Context
@@ -13,7 +12,7 @@
1312
sdk_key = os.getenv('LAUNCHDARKLY_SDK_KEY')
1413

1514
# Set judge_key to the Judge key you want to use.
16-
judge_key = os.getenv('LAUNCHDARKLY_AI_JUDGE_KEY', 'sample-ai-judge-accuracy')
15+
judge_key = os.getenv('LAUNCHDARKLY_AI_JUDGE_KEY', 'sample-ai-judge')
1716

1817

1918
async def async_main():
@@ -54,8 +53,8 @@ async def async_main():
5453
# {'role': 'user', 'content': 'RESPONSE TO EVALUATE: {{response_to_evaluate}}'},
5554
# ],
5655
# )
57-
# judge = await aiclient.create_judge(judge_key, context, default)
58-
judge = await aiclient.create_judge(judge_key, context)
56+
# judge = aiclient.create_judge(judge_key, context, default)
57+
judge = aiclient.create_judge(judge_key, context)
5958

6059
if not judge:
6160
print(f"*** Failed to create judge for key: {judge_key}")
@@ -70,20 +69,26 @@ async def async_main():
7069

7170
judge_response = await judge.evaluate(input_text, output_text)
7271

73-
if judge_response is None:
74-
print("\nJudge evaluation was skipped.")
75-
print("This typically happens when the sample rate doesn't require this evaluation, or due to a configuration issue.")
76-
print("Check application logs for more details.")
77-
return
78-
7972
# Track the judge evaluation scores on the tracker for the aiConfig you are evaluating
8073
# Example:
8174
# aiConfig.tracker.track_eval_scores(judge_response.evals)
8275

83-
# Convert JudgeResponse to dict for display using to_dict()
84-
judge_response_dict = judge_response.to_dict()
8576
print("Judge Response:")
86-
print(json.dumps(judge_response_dict, indent=2, default=str))
77+
print(f" sampled: {judge_response.sampled}")
78+
print(f" success: {judge_response.success}")
79+
if judge_response.error_message is not None:
80+
print(f" error_message: {judge_response.error_message}")
81+
if judge_response.metric_key is not None:
82+
print(f" metric_key: {judge_response.metric_key}")
83+
if judge_response.score is not None:
84+
print(f" score: {judge_response.score}")
85+
if judge_response.reasoning is not None:
86+
print(f" reasoning: {judge_response.reasoning}")
87+
88+
if not judge_response.sampled:
89+
print("\nNote: Judge evaluation was not sampled.")
90+
print("This typically happens when the sample rate doesn't require this evaluation, or due to a configuration issue.")
91+
print("Check application logs for more details.")
8792

8893
print("Success.")
8994
except Exception as err:

examples/langchain/langchain_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ async def async_main():
9191
messages.append({'role': 'user', 'content': USER_INPUT})
9292

9393
# Track the LangChain completion with LaunchDarkly metrics using the LD LangChain provider's extractor
94-
completion = await tracker.track_metrics_of(
95-
lambda: llm.ainvoke(messages),
94+
completion = await tracker.track_metrics_of_async(
9695
get_ai_metrics_from_response,
96+
lambda: llm.ainvoke(messages),
9797
)
9898
ai_response = completion.content
9999

examples/openai/openai_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def main():
6666
return
6767

6868
tracker = config_value.create_tracker()
69-
69+
7070
messages = [message.to_dict() for message in (config_value.messages or [])]
7171

7272
# Add the user input to the conversation
@@ -76,12 +76,12 @@ def main():
7676

7777
# Track the OpenAI completion with LaunchDarkly metrics using the LD OpenAI provider's extractor
7878
completion = tracker.track_metrics_of(
79+
get_ai_metrics_from_response,
7980
lambda:
8081
openai_client.chat.completions.create(
8182
model=config_value.model.name,
8283
messages=messages,
8384
),
84-
get_ai_metrics_from_response,
8585
)
8686
ai_response = completion.choices[0].message.content
8787

0 commit comments

Comments
 (0)