Martechsol commited on
Commit ·
392a1db
1
Parent(s): bd428d3
Refine RAG pipeline for comprehensive leave retrieval, improve system prompt, and fix Gradio HTML rendering
Browse files- .gitignore +3 -1
- app/core/config.py +1 -1
- app/services/llm.py +89 -41
- app/ui_gradio.py +54 -55
- data/index/.gitkeep +0 -0
- data/sessions/.gitkeep +0 -0
- get_models.py +25 -0
- test_groq.py +26 -0
.gitignore
CHANGED
|
@@ -4,4 +4,6 @@ __pycache__/
|
|
| 4 |
.mypy_cache/
|
| 5 |
.venv/
|
| 6 |
venv/
|
| 7 |
-
#
|
|
|
|
|
|
|
|
|
| 4 |
.mypy_cache/
|
| 5 |
.venv/
|
| 6 |
venv/
|
| 7 |
+
# Ignoring data directory to prevent pushing binary index files
|
| 8 |
+
data/index/
|
| 9 |
+
data/sessions/
|
app/core/config.py
CHANGED
|
@@ -31,7 +31,7 @@ class Settings(BaseSettings):
|
|
| 31 |
chunk_size_tokens: int = 350 # Reduced for TPM safety
|
| 32 |
chunk_overlap_tokens: int = 80
|
| 33 |
top_k: int = Field(default=20, alias="TOP_K")
|
| 34 |
-
max_context_chunks: int =
|
| 35 |
request_timeout_s: float = 20.0
|
| 36 |
cors_allow_origins: str = Field(default="*", alias="CORS_ALLOW_ORIGINS")
|
| 37 |
api_key: str = Field(default="", alias="API_KEY")
|
|
|
|
| 31 |
chunk_size_tokens: int = 350 # Reduced for TPM safety
|
| 32 |
chunk_overlap_tokens: int = 80
|
| 33 |
top_k: int = Field(default=20, alias="TOP_K")
|
| 34 |
+
max_context_chunks: int = 10 # Increased to 10 to ensure all list items (like leaves) are captured
|
| 35 |
request_timeout_s: float = 20.0
|
| 36 |
cors_allow_origins: str = Field(default="*", alias="CORS_ALLOW_ORIGINS")
|
| 37 |
api_key: str = Field(default="", alias="API_KEY")
|
app/services/llm.py
CHANGED
|
@@ -8,38 +8,76 @@ import re
|
|
| 8 |
# ═══════════════════════════════════════════════════════════════════════
|
| 9 |
SYSTEM_PROMPT = """You are the Martechsol HR Assistant — intelligent, precise, and formal.
|
| 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 |
"""Combines retrieved chunks into a clean context string, capped at max_words.
|
| 44 |
Prevents TPM spikes when chunks are unexpectedly large."""
|
| 45 |
if not chunks:
|
|
@@ -82,15 +120,25 @@ class LLMService:
|
|
| 82 |
|
| 83 |
async def generate_multi_queries(self, query: str, history: List[Dict[str, str]]) -> List[str]:
|
| 84 |
"""Generates multiple search queries to capture broader context from the document."""
|
| 85 |
-
prompt = f"""You are an HR search optimizer
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
-
|
| 90 |
-
-
|
| 91 |
-
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
Queries:"""
|
| 96 |
|
|
|
|
| 8 |
# ═══════════════════════════════════════════════════════════════════════
|
| 9 |
SYSTEM_PROMPT = """You are the Martechsol HR Assistant — intelligent, precise, and formal.
|
| 10 |
|
| 11 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 12 |
+
STEP 1 — UNDERSTAND THE INTENT
|
| 13 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 14 |
+
Read the question carefully. Identify the SINGLE core topic being asked. Apply intelligent defaults:
|
| 15 |
+
• "timing" / "timings" (no context) → office working hours ONLY — not payment or any other timing
|
| 16 |
+
• "leaves" / "leave" (no context) → leave names + day counts ONLY — NOT leave policies or eligibility
|
| 17 |
+
• "paid leaves" / "all leaves" → enumerate EVERY leave type with its name and count
|
| 18 |
+
• "salary" / "pay" (no context) → salary structure or amount — NOT payment date unless explicitly asked
|
| 19 |
+
• "benefits" / "perks" / "allowances" → list EVERY benefit with its name and value
|
| 20 |
+
• "terminate" / "termination" → resignation/termination procedure — NOT general policies
|
| 21 |
+
If a question has an obvious workplace context, always default to the most common interpretation.
|
| 22 |
+
|
| 23 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 24 |
+
STEP 2 — STRICT SCOPE DISCIPLINE
|
| 25 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 26 |
+
Answer ONLY what was asked. NEVER expand into:
|
| 27 |
+
• Policies, approval processes, eligibility rules, or consequences — unless user asks for policy/process
|
| 28 |
+
• Related topics the user did not mention
|
| 29 |
+
• Broad overviews when a specific fact was requested
|
| 30 |
+
• Context that wasn't in the question
|
| 31 |
+
|
| 32 |
+
Scope examples:
|
| 33 |
+
"How many sick leaves?" → ONE number. Not the sick leave policy.
|
| 34 |
+
"What are office timings?" → ONE sentence with time range. Not break times or exceptions.
|
| 35 |
+
"What are paid leaves?" → Complete list of all paid leave types + counts. Not rules for taking them.
|
| 36 |
+
"Tell me about maternity leave" → Count + brief key fact. Not full policies unless asked.
|
| 37 |
+
|
| 38 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 39 |
+
STEP 3 — CHOOSE THE RIGHT FORMAT
|
| 40 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 41 |
+
|
| 42 |
+
FORMAT A — SINGLE FACT (default for most questions)
|
| 43 |
+
When: One direct answer needed (timings, a specific leave count, a single number/date)
|
| 44 |
+
Rule: ONE complete sentence. Maximum 25–30 words. Never cut mid-sentence.
|
| 45 |
+
Example: Office hours are <b>9:00 AM to 6:00 PM</b>, Monday to Saturday.
|
| 46 |
+
|
| 47 |
+
FORMAT B — EXHAUSTIVE LIST
|
| 48 |
+
When: User asks for ALL items in a category ("all leaves", "all benefits", "list all X", "paid leaves")
|
| 49 |
+
Rule:
|
| 50 |
+
• Include EVERY single item found — omitting even one is FORBIDDEN
|
| 51 |
+
• One item per line: <b>Item Name:</b> value<br>
|
| 52 |
+
• No intro sentence, no closing sentence, no extra commentary
|
| 53 |
+
• Continue until ALL items are listed
|
| 54 |
+
Example:
|
| 55 |
+
<b>Casual Leave:</b> 10 days<br>
|
| 56 |
+
<b>Sick Leave:</b> 10 days<br>
|
| 57 |
+
<b>Annual Leave:</b> 14 days<br>
|
| 58 |
+
<b>Maternity Leave:</b> 90 days<br>
|
| 59 |
+
<b>Paternity Leave:</b> 3 days<br>
|
| 60 |
+
<b>Hajj Leave:</b> 30 days<br>
|
| 61 |
+
...(list every item — do NOT stop early)
|
| 62 |
+
|
| 63 |
+
FORMAT C — BRIEF EXPLANATION
|
| 64 |
+
When: User asks HOW something works, or asks for a process/procedure
|
| 65 |
+
Rule: Maximum 3 bullet points. Each bullet = one complete, factual sentence. No filler words.
|
| 66 |
+
|
| 67 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 68 |
+
STRICT QUALITY RULES
|
| 69 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 70 |
+
✓ ZERO hallucination — every fact must exist in Expert Data only. No guessing.
|
| 71 |
+
✓ If not in Expert Data → reply exactly: "I don't have that information."
|
| 72 |
+
✓ Never cut a sentence mid-way — always complete every sentence fully
|
| 73 |
+
✓ NEVER mention: "document", "handbook", "manual", "policy file", or any source reference
|
| 74 |
+
✓ Use <b>bold</b> for names, numbers, dates, leave types, and all key terms
|
| 75 |
+
✓ Use <br> between list items for clean vertical spacing
|
| 76 |
+
✓ Tone: formal, warm, and professional — never robotic, never chatty
|
| 77 |
+
✓ Do NOT add greetings, closings, or "Is there anything else?" type phrases"""
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def _build_context(chunks: List[Dict[str, str]], max_words: int = 1500) -> str:
|
| 81 |
"""Combines retrieved chunks into a clean context string, capped at max_words.
|
| 82 |
Prevents TPM spikes when chunks are unexpectedly large."""
|
| 83 |
if not chunks:
|
|
|
|
| 120 |
|
| 121 |
async def generate_multi_queries(self, query: str, history: List[Dict[str, str]]) -> List[str]:
|
| 122 |
"""Generates multiple search queries to capture broader context from the document."""
|
| 123 |
+
prompt = f"""You are an intelligent HR search optimizer. A user asked: "{query}"
|
| 124 |
+
|
| 125 |
+
STEP 1 — UNDERSTAND THE INTENT:
|
| 126 |
+
What is the user ACTUALLY asking about? Apply workplace common sense:
|
| 127 |
+
- "timing" / "timings" (no qualifier) = office working hours, workday schedule
|
| 128 |
+
- "leaves" / "paid leaves" / "all leaves" = all leave types available to employees
|
| 129 |
+
- "salary" / "pay" = salary structure or amount
|
| 130 |
+
- "benefits" / "allowances" = employee benefits and perks
|
| 131 |
+
- "terminate" / "resign" = termination or resignation process
|
| 132 |
+
|
| 133 |
+
STEP 2 — GENERATE 3 TARGETED SEARCH QUERIES:
|
| 134 |
+
Write 3 diverse search queries to retrieve ALL relevant information from an employee handbook.
|
| 135 |
+
For the identified intent, cover synonyms, related terms, and sub-categories:
|
| 136 |
+
- For office timings → search: working hours, office schedule, workday timing, shift hours
|
| 137 |
+
- For leaves → cover ALL types: casual leave, sick leave, annual leave, maternity leave, paternity leave, hajj leave, study leave, earned leave, bereavement leave, unauthorized leave
|
| 138 |
+
- For salary → cover: salary structure, payroll, compensation, increments, deductions
|
| 139 |
+
- For benefits → cover: allowances, perks, medical, bonuses, reimbursements
|
| 140 |
+
|
| 141 |
+
Output ONLY 3 queries, one per line. No numbers, no bullets, no explanations.
|
| 142 |
|
| 143 |
Queries:"""
|
| 144 |
|
app/ui_gradio.py
CHANGED
|
@@ -126,71 +126,66 @@ async def chat_fn(message: str, chat_history: List[Dict[str, str]], session_id:
|
|
| 126 |
logger.error(f"Unexpected error: {str(e)}\n{traceback.format_exc()}")
|
| 127 |
reply = f"⚠️ Oops! Something went wrong."
|
| 128 |
|
| 129 |
-
# ── Humanistic letter-by-letter typing
|
| 130 |
-
displayed = ""
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
for
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
do_typo = (
|
| 139 |
-
|
| 140 |
-
and len(
|
| 141 |
-
and random.random() < TYPO_CHANCE
|
| 142 |
-
and word.isalpha()
|
| 143 |
)
|
| 144 |
|
| 145 |
if do_typo:
|
| 146 |
-
|
| 147 |
-
typo_pos = random.randint(2, len(word) - 1)
|
| 148 |
-
|
| 149 |
-
# 1. Type the word correctly up to the typo position
|
| 150 |
-
for char in (separator + word[:typo_pos]):
|
| 151 |
-
displayed += char
|
| 152 |
-
chat_history[-1] = {"role": "assistant", "content": displayed}
|
| 153 |
-
yield gr.update(value="", interactive=False), chat_history
|
| 154 |
-
await asyncio.sleep(random.uniform(0.03, 0.07))
|
| 155 |
-
|
| 156 |
-
# 2. Type the wrong character
|
| 157 |
-
wrong_char = _nearby_char(word[typo_pos])
|
| 158 |
displayed += wrong_char
|
| 159 |
chat_history[-1] = {"role": "assistant", "content": displayed}
|
| 160 |
yield gr.update(value="", interactive=False), chat_history
|
| 161 |
-
await asyncio.sleep(random.uniform(0.15, 0.3))
|
| 162 |
-
|
| 163 |
-
#
|
| 164 |
-
await asyncio.sleep(random.uniform(0.2, 0.4))
|
| 165 |
displayed = displayed[:-1]
|
| 166 |
chat_history[-1] = {"role": "assistant", "content": displayed}
|
| 167 |
yield gr.update(value="", interactive=False), chat_history
|
| 168 |
await asyncio.sleep(random.uniform(0.1, 0.2))
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
else:
|
| 177 |
-
|
| 178 |
-
for char in (separator + word):
|
| 179 |
-
displayed += char
|
| 180 |
-
chat_history[-1] = {"role": "assistant", "content": displayed}
|
| 181 |
-
yield gr.update(value="", interactive=False), chat_history
|
| 182 |
-
|
| 183 |
-
# Faster typing for letters, slight pause for space
|
| 184 |
-
if char == " ":
|
| 185 |
-
await asyncio.sleep(random.uniform(0.08, 0.15))
|
| 186 |
-
else:
|
| 187 |
-
await asyncio.sleep(random.uniform(0.02, 0.06))
|
| 188 |
-
|
| 189 |
-
# Punctuation pauses (at the end of words)
|
| 190 |
-
if word and word[-1] in ".!?":
|
| 191 |
-
await asyncio.sleep(random.uniform(0.4, 0.8))
|
| 192 |
-
elif word and word[-1] in ",:;":
|
| 193 |
-
await asyncio.sleep(random.uniform(0.2, 0.4))
|
| 194 |
|
| 195 |
# ── Fire-and-forget: persist to session store (never blocks the UI) ───
|
| 196 |
# We only save successful responses, not error messages
|
|
@@ -245,8 +240,12 @@ with gr.Blocks(title="Martechsol Assistant", theme=gr.themes.Soft(), css=custom_
|
|
| 245 |
gr.Markdown("# Martechsol Assistant")
|
| 246 |
gr.Markdown("Welcome! How can I help you today?")
|
| 247 |
|
| 248 |
-
chatbot = gr.Chatbot(
|
| 249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
with gr.Row():
|
| 251 |
msg = gr.Textbox(
|
| 252 |
placeholder="Type your question here...",
|
|
|
|
| 126 |
logger.error(f"Unexpected error: {str(e)}\n{traceback.format_exc()}")
|
| 127 |
reply = f"⚠️ Oops! Something went wrong."
|
| 128 |
|
| 129 |
+
# ── Humanistic letter-by-letter typing — HTML-safe ────────────────────
|
| 130 |
+
displayed = "" # what is shown in the chat bubble
|
| 131 |
+
buffer = "" # invisible accumulator for mid-HTML-tag characters
|
| 132 |
+
in_tag = False # True while we are inside a < … > sequence
|
| 133 |
+
|
| 134 |
+
for char in reply:
|
| 135 |
+
if char == '<':
|
| 136 |
+
in_tag = True
|
| 137 |
+
buffer += char
|
| 138 |
+
continue
|
| 139 |
+
|
| 140 |
+
if in_tag:
|
| 141 |
+
buffer += char
|
| 142 |
+
if char == '>':
|
| 143 |
+
# Tag is now complete — flush it to the display all at once
|
| 144 |
+
in_tag = False
|
| 145 |
+
displayed += buffer
|
| 146 |
+
buffer = ""
|
| 147 |
+
chat_history[-1] = {"role": "assistant", "content": displayed}
|
| 148 |
+
yield gr.update(value="", interactive=False), chat_history
|
| 149 |
+
# Small pause after a <br> to mimic line-break pacing
|
| 150 |
+
if displayed.endswith('<br>') or displayed.endswith('<br/>'):
|
| 151 |
+
await asyncio.sleep(random.uniform(0.1, 0.2))
|
| 152 |
+
continue
|
| 153 |
+
|
| 154 |
+
# ── Normal character (not inside a tag) ───────────────────────────
|
| 155 |
+
# Decide typo chance only for plain alphabetic words
|
| 156 |
do_typo = (
|
| 157 |
+
char.isalpha()
|
| 158 |
+
and len(displayed) > 8 # skip the very beginning
|
| 159 |
+
and random.random() < TYPO_CHANCE
|
|
|
|
| 160 |
)
|
| 161 |
|
| 162 |
if do_typo:
|
| 163 |
+
wrong_char = _nearby_char(char)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
displayed += wrong_char
|
| 165 |
chat_history[-1] = {"role": "assistant", "content": displayed}
|
| 166 |
yield gr.update(value="", interactive=False), chat_history
|
| 167 |
+
await asyncio.sleep(random.uniform(0.15, 0.3))
|
| 168 |
+
|
| 169 |
+
# Backspace the wrong character
|
|
|
|
| 170 |
displayed = displayed[:-1]
|
| 171 |
chat_history[-1] = {"role": "assistant", "content": displayed}
|
| 172 |
yield gr.update(value="", interactive=False), chat_history
|
| 173 |
await asyncio.sleep(random.uniform(0.1, 0.2))
|
| 174 |
+
|
| 175 |
+
# Type the correct character
|
| 176 |
+
displayed += char
|
| 177 |
+
chat_history[-1] = {"role": "assistant", "content": displayed}
|
| 178 |
+
yield gr.update(value="", interactive=False), chat_history
|
| 179 |
+
|
| 180 |
+
# Pacing: punctuation pauses, space micro-pause, normal char speed
|
| 181 |
+
if char in '.!?':
|
| 182 |
+
await asyncio.sleep(random.uniform(0.35, 0.7))
|
| 183 |
+
elif char in ',:;':
|
| 184 |
+
await asyncio.sleep(random.uniform(0.15, 0.3))
|
| 185 |
+
elif char == ' ':
|
| 186 |
+
await asyncio.sleep(random.uniform(0.06, 0.13))
|
| 187 |
else:
|
| 188 |
+
await asyncio.sleep(random.uniform(0.02, 0.06))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
# ── Fire-and-forget: persist to session store (never blocks the UI) ───
|
| 191 |
# We only save successful responses, not error messages
|
|
|
|
| 240 |
gr.Markdown("# Martechsol Assistant")
|
| 241 |
gr.Markdown("Welcome! How can I help you today?")
|
| 242 |
|
| 243 |
+
chatbot = gr.Chatbot(
|
| 244 |
+
show_label=False,
|
| 245 |
+
elem_id="chatbot-window",
|
| 246 |
+
type="messages",
|
| 247 |
+
render_markdown=True,
|
| 248 |
+
)
|
| 249 |
with gr.Row():
|
| 250 |
msg = gr.Textbox(
|
| 251 |
placeholder="Type your question here...",
|
data/index/.gitkeep
DELETED
|
File without changes
|
data/sessions/.gitkeep
DELETED
|
File without changes
|
get_models.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import os
|
| 3 |
+
import httpx
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
async def main():
|
| 9 |
+
api_key = os.getenv("GROQ_API_KEY")
|
| 10 |
+
if not api_key:
|
| 11 |
+
print("No API Key")
|
| 12 |
+
return
|
| 13 |
+
url = "https://api.groq.com/openai/v1/models"
|
| 14 |
+
headers = {"Authorization": f"Bearer {api_key}"}
|
| 15 |
+
async with httpx.AsyncClient() as client:
|
| 16 |
+
resp = await client.get(url, headers=headers)
|
| 17 |
+
if resp.status_code == 200:
|
| 18 |
+
models = resp.json().get("data", [])
|
| 19 |
+
for m in models:
|
| 20 |
+
if "deepseek" in m["id"].lower() or "qwen" in m["id"].lower():
|
| 21 |
+
print(m["id"])
|
| 22 |
+
else:
|
| 23 |
+
print(resp.status_code, resp.text)
|
| 24 |
+
|
| 25 |
+
asyncio.run(main())
|
test_groq.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import os
|
| 3 |
+
import httpx
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
async def main():
|
| 9 |
+
api_key = os.getenv("GROQ_API_KEY")
|
| 10 |
+
url = "https://api.groq.com/openai/v1/chat/completions"
|
| 11 |
+
headers = {
|
| 12 |
+
"Authorization": f"Bearer {api_key}",
|
| 13 |
+
"Content-Type": "application/json"
|
| 14 |
+
}
|
| 15 |
+
payload = {
|
| 16 |
+
"model": "deepseek-r1-distill-llama-70b",
|
| 17 |
+
"temperature": 0.0,
|
| 18 |
+
"max_tokens": 1200,
|
| 19 |
+
"messages": [{"role": "system", "content": "hello"}, {"role": "user", "content": "test"}]
|
| 20 |
+
}
|
| 21 |
+
async with httpx.AsyncClient() as client:
|
| 22 |
+
resp = await client.post(url, headers=headers, json=payload)
|
| 23 |
+
print(resp.status_code)
|
| 24 |
+
print(resp.text)
|
| 25 |
+
|
| 26 |
+
asyncio.run(main())
|