-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathllm_client.py
More file actions
233 lines (192 loc) · 7.91 KB
/
llm_client.py
File metadata and controls
233 lines (192 loc) · 7.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
"""
Real LLM Client for SochDB Test Harness
Uses Azure OpenAI for real embeddings and text generation.
No mocking - actual API calls with synthetic ground-truth for validation.
"""
import os
from typing import List, Optional, Dict, Any
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
class AzureOpenAIClient:
"""Real Azure OpenAI client for embeddings and text generation."""
def __init__(self):
self.api_key = os.getenv("AZURE_OPENAI_API_KEY")
self.endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
self.api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview")
self.deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT", "gpt-4.1")
self.embedding_deployment = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT", "text-embedding-3-small")
if not self.api_key or not self.endpoint:
raise ValueError(
"Azure OpenAI credentials not found. "
"Set AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT in .env"
)
# Import here to avoid dependency if not using real LLM
try:
from openai import AzureOpenAI
self.client = AzureOpenAI(
api_key=self.api_key,
api_version=self.api_version,
azure_endpoint=self.endpoint
)
except ImportError:
raise ImportError(
"openai package not installed. "
"Run: pip install openai"
)
def get_embedding(self, text: str) -> List[float]:
"""
Get real embedding from Azure OpenAI.
Args:
text: Text to embed
Returns:
Embedding vector (1536-dim for text-embedding-3-small)
"""
response = self.client.embeddings.create(
input=text,
model=self.embedding_deployment
)
return response.data[0].embedding
def get_embeddings_batch(self, texts: List[str]) -> List[List[float]]:
"""
Get embeddings for multiple texts in one call.
Args:
texts: List of texts to embed
Returns:
List of embedding vectors
"""
response = self.client.embeddings.create(
input=texts,
model=self.embedding_deployment
)
return [item.embedding for item in response.data]
def generate_text(
self,
prompt: str,
max_tokens: int = 150,
temperature: float = 0.7,
system_prompt: Optional[str] = None
) -> str:
"""
Generate text using Azure OpenAI chat completion.
Args:
prompt: User prompt
max_tokens: Maximum tokens to generate
temperature: Sampling temperature
system_prompt: Optional system prompt
Returns:
Generated text
"""
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
response = self.client.chat.completions.create(
model=self.deployment,
messages=messages,
max_tokens=max_tokens,
temperature=temperature
)
return response.choices[0].message.content
def generate_support_doc(self, topic_keywords: List[str], doc_type: str = "support") -> str:
"""
Generate realistic support document using LLM.
Args:
topic_keywords: Keywords related to the document topic
doc_type: Type of document (support, runbook, contract, log)
Returns:
Generated document text
"""
templates = {
"support": (
"You are a technical support specialist. "
"Write a brief support article (50-100 words) about troubleshooting issues with: "
f"{', '.join(topic_keywords)}. "
"Include common symptoms, diagnostic steps, and solutions."
),
"runbook": (
"You are a DevOps engineer. "
"Write a runbook entry (50-100 words) for handling incidents related to: "
f"{', '.join(topic_keywords)}. "
"Include detection, diagnosis, and remediation steps."
),
"contract": (
"You are a legal contract specialist. "
"Write a contract clause (50-100 words) regarding: "
f"{', '.join(topic_keywords)}. "
"Include obligations, terms, and conditions."
),
"log": (
"You are a system engineer. "
"Write a log entry (30-50 words) about an issue with: "
f"{', '.join(topic_keywords)}. "
"Include severity, affected components, and error details."
),
}
prompt = templates.get(doc_type, templates["support"])
return self.generate_text(prompt, max_tokens=150, temperature=0.7)
def generate_query(self, topic_keywords: List[str], query_type: str = "support") -> str:
"""
Generate realistic user query using LLM.
Args:
topic_keywords: Keywords the query should be about
query_type: Type of query (support, runbook, contract)
Returns:
Generated query text
"""
templates = {
"support": (
"Write a brief user question (10-20 words) asking how to fix or troubleshoot: "
f"{', '.join(topic_keywords)}"
),
"runbook": (
"Write a brief incident description (10-20 words) about an issue with: "
f"{', '.join(topic_keywords)}"
),
"contract": (
"Write a brief legal query (10-20 words) about contract terms regarding: "
f"{', '.join(topic_keywords)}"
),
}
prompt = templates.get(query_type, templates["support"])
return self.generate_text(prompt, max_tokens=50, temperature=0.7)
def generate_paraphrases(self, original_text: str, num_paraphrases: int = 3) -> List[str]:
"""
Generate paraphrases of a query for cache testing.
Args:
original_text: Original query text
num_paraphrases: Number of paraphrases to generate
Returns:
List of paraphrased queries
"""
prompt = (
f"Generate {num_paraphrases} different ways to ask this same question. "
f"Each should have the same meaning but different wording.\n\n"
f"Original: {original_text}\n\n"
f"Output only the {num_paraphrases} paraphrases, one per line, without numbering."
)
response = self.generate_text(prompt, max_tokens=200, temperature=0.8)
# Parse paraphrases from response
paraphrases = [line.strip() for line in response.split('\n') if line.strip()]
return paraphrases[:num_paraphrases]
# Singleton instance
_llm_client = None
def get_llm_client() -> AzureOpenAIClient:
"""Get singleton LLM client instance."""
global _llm_client
if _llm_client is None:
_llm_client = AzureOpenAIClient()
return _llm_client
def get_embedding_dimension() -> int:
"""Get the embedding dimension for the configured model."""
# text-embedding-3-small = 1536 dimensions
# text-embedding-3-large = 3072 dimensions
embedding_model = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT", "text-embedding-3-small")
if "small" in embedding_model.lower():
return 1536
elif "large" in embedding_model.lower():
return 3072
elif "ada" in embedding_model.lower():
return 1536
else:
return 1536 # Default