|
1 | | -"""TrackedChat implementation for managing AI chat conversations.""" |
| 1 | +"""Chat module for LaunchDarkly AI SDK.""" |
2 | 2 |
|
3 | | -import asyncio |
4 | | -from typing import Any, Dict, List, Optional |
| 3 | +from ldai.chat.tracked_chat import TrackedChat |
5 | 4 |
|
6 | | -from ldai.models import AICompletionConfig, LDMessage |
7 | | -from ldai.providers.ai_provider import AIProvider |
8 | | -from ldai.providers.types import ChatResponse, JudgeResponse |
9 | | -from ldai.judge import AIJudge |
10 | | -from ldai.tracker import LDAIConfigTracker |
11 | | - |
12 | | - |
13 | | -class TrackedChat: |
14 | | - """ |
15 | | - Concrete implementation of TrackedChat that provides chat functionality |
16 | | - by delegating to an AIProvider implementation. |
17 | | - |
18 | | - This class handles conversation management and tracking, while delegating |
19 | | - the actual model invocation to the provider. |
20 | | - """ |
21 | | - |
22 | | - def __init__( |
23 | | - self, |
24 | | - ai_config: AICompletionConfig, |
25 | | - tracker: LDAIConfigTracker, |
26 | | - provider: AIProvider, |
27 | | - judges: Optional[Dict[str, AIJudge]] = None, |
28 | | - logger: Optional[Any] = None, |
29 | | - ): |
30 | | - """ |
31 | | - Initialize the TrackedChat. |
32 | | - |
33 | | - :param ai_config: The completion AI configuration |
34 | | - :param tracker: The tracker for the completion configuration |
35 | | - :param provider: The AI provider to use for chat |
36 | | - :param judges: Optional dictionary of judge instances keyed by their configuration keys |
37 | | - :param logger: Optional logger for logging |
38 | | - """ |
39 | | - self._ai_config = ai_config |
40 | | - self._tracker = tracker |
41 | | - self._provider = provider |
42 | | - self._judges = judges or {} |
43 | | - self._logger = logger |
44 | | - self._messages: List[LDMessage] = [] |
45 | | - |
46 | | - async def invoke(self, prompt: str) -> ChatResponse: |
47 | | - """ |
48 | | - Invoke the chat model with a prompt string. |
49 | | - |
50 | | - This method handles conversation management and tracking, delegating to the provider's invoke_model method. |
51 | | - |
52 | | - :param prompt: The user prompt to send to the chat model |
53 | | - :return: ChatResponse containing the model's response and metrics |
54 | | - """ |
55 | | - # Convert prompt string to LDMessage with role 'user' and add to conversation history |
56 | | - user_message: LDMessage = LDMessage(role='user', content=prompt) |
57 | | - self._messages.append(user_message) |
58 | | - |
59 | | - # Prepend config messages to conversation history for model invocation |
60 | | - config_messages = self._ai_config.messages or [] |
61 | | - all_messages = config_messages + self._messages |
62 | | - |
63 | | - # Delegate to provider-specific implementation with tracking |
64 | | - response = await self._tracker.track_metrics_of( |
65 | | - lambda result: result.metrics, |
66 | | - lambda: self._provider.invoke_model(all_messages), |
67 | | - ) |
68 | | - |
69 | | - # Start judge evaluations as async tasks (don't await them) |
70 | | - if ( |
71 | | - self._ai_config.judge_configuration |
72 | | - and self._ai_config.judge_configuration.judges |
73 | | - and len(self._ai_config.judge_configuration.judges) > 0 |
74 | | - ): |
75 | | - evaluation_tasks = self._start_judge_evaluations(self._messages, response) |
76 | | - response.evaluations = evaluation_tasks |
77 | | - |
78 | | - # Add the response message to conversation history |
79 | | - self._messages.append(response.message) |
80 | | - return response |
81 | | - |
82 | | - def _start_judge_evaluations( |
83 | | - self, |
84 | | - messages: List[LDMessage], |
85 | | - response: ChatResponse, |
86 | | - ) -> List[asyncio.Task[Optional[JudgeResponse]]]: |
87 | | - """ |
88 | | - Start judge evaluations as async tasks without awaiting them. |
89 | | - |
90 | | - Returns a list of async tasks that can be awaited later. |
91 | | - |
92 | | - :param messages: Array of messages representing the conversation history |
93 | | - :param response: The AI response to be evaluated |
94 | | - :return: List of async tasks that will return judge evaluation results |
95 | | - """ |
96 | | - if not self._ai_config.judge_configuration or not self._ai_config.judge_configuration.judges: |
97 | | - return [] |
98 | | - |
99 | | - judge_configs = self._ai_config.judge_configuration.judges |
100 | | - |
101 | | - # Start all judge evaluations as tasks |
102 | | - async def evaluate_judge(judge_config): |
103 | | - judge = self._judges.get(judge_config.key) |
104 | | - if not judge: |
105 | | - if self._logger: |
106 | | - self._logger.warn( |
107 | | - f"Judge configuration is not enabled: {judge_config.key}", |
108 | | - ) |
109 | | - return None |
110 | | - |
111 | | - eval_result = await judge.evaluate_messages( |
112 | | - messages, response, judge_config.sampling_rate |
113 | | - ) |
114 | | - |
115 | | - if eval_result and eval_result.success: |
116 | | - self._tracker.track_eval_scores(eval_result.evals) |
117 | | - |
118 | | - return eval_result |
119 | | - |
120 | | - # Create tasks for each judge evaluation |
121 | | - tasks = [ |
122 | | - asyncio.create_task(evaluate_judge(judge_config)) |
123 | | - for judge_config in judge_configs |
124 | | - ] |
125 | | - |
126 | | - return tasks |
127 | | - |
128 | | - def get_config(self) -> AICompletionConfig: |
129 | | - """ |
130 | | - Get the underlying AI configuration used to initialize this TrackedChat. |
131 | | - |
132 | | - :return: The AI completion configuration |
133 | | - """ |
134 | | - return self._ai_config |
135 | | - |
136 | | - def get_tracker(self) -> LDAIConfigTracker: |
137 | | - """ |
138 | | - Get the underlying AI configuration tracker used to initialize this TrackedChat. |
139 | | - |
140 | | - :return: The tracker instance |
141 | | - """ |
142 | | - return self._tracker |
143 | | - |
144 | | - def get_provider(self) -> AIProvider: |
145 | | - """ |
146 | | - Get the underlying AI provider instance. |
147 | | - |
148 | | - This provides direct access to the provider for advanced use cases. |
149 | | - |
150 | | - :return: The AI provider instance |
151 | | - """ |
152 | | - return self._provider |
153 | | - |
154 | | - def get_judges(self) -> Dict[str, AIJudge]: |
155 | | - """ |
156 | | - Get the judges associated with this TrackedChat. |
157 | | - |
158 | | - Returns a dictionary of judge instances keyed by their configuration keys. |
159 | | - |
160 | | - :return: Dictionary of judge instances |
161 | | - """ |
162 | | - return self._judges |
163 | | - |
164 | | - def append_messages(self, messages: List[LDMessage]) -> None: |
165 | | - """ |
166 | | - Append messages to the conversation history. |
167 | | - |
168 | | - Adds messages to the conversation history without invoking the model, |
169 | | - which is useful for managing multi-turn conversations or injecting context. |
170 | | - |
171 | | - :param messages: Array of messages to append to the conversation history |
172 | | - """ |
173 | | - self._messages.extend(messages) |
174 | | - |
175 | | - def get_messages(self, include_config_messages: bool = False) -> List[LDMessage]: |
176 | | - """ |
177 | | - Get all messages in the conversation history. |
178 | | - |
179 | | - :param include_config_messages: Whether to include the config messages from the AIConfig. |
180 | | - Defaults to False. |
181 | | - :return: Array of messages. When include_config_messages is True, returns both config |
182 | | - messages and conversation history with config messages prepended. When False, |
183 | | - returns only the conversation history messages. |
184 | | - """ |
185 | | - if include_config_messages: |
186 | | - config_messages = self._ai_config.messages or [] |
187 | | - return config_messages + self._messages |
188 | | - return list(self._messages) |
| 5 | +__all__ = ['TrackedChat'] |
189 | 6 |
|
0 commit comments