Graham Paasch commited on
Commit
6f9e2de
·
1 Parent(s): 703134e

Add Blaxel, SambaNova, Nebius, and Hugging Face LLM providers

Browse files
Files changed (1) hide show
  1. agent/llm_client.py +191 -1
agent/llm_client.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
  LLM Client for Overgrowth Pipeline
3
- Supports multiple providers: OpenAI, Anthropic, OpenRouter
4
  """
5
 
6
  import os
@@ -42,6 +42,10 @@ class LLMClient:
42
  # Support both standard and MCP hackathon naming conventions
43
  self.openai_key = _get_env(["OPENAI_API_KEY", "OPENAI_MCP_1ST_BDAY"])
44
  self.anthropic_key = _get_env(["ANTHROPIC_API_KEY", "ANTHROPIC_MCP_1ST_BDAY"])
 
 
 
 
45
 
46
  # Determine which provider to use
47
  self.provider = self._detect_provider()
@@ -67,12 +71,24 @@ class LLMClient:
67
  status = {
68
  "openai_key": "present" if _present(["OPENAI_API_KEY", "OPENAI_MCP_1ST_BDAY"]) else "missing",
69
  "anthropic_key": "present" if _present(["ANTHROPIC_API_KEY", "ANTHROPIC_MCP_1ST_BDAY"]) else "missing",
 
 
 
 
70
  "provider": "unknown",
71
  }
72
  if status["anthropic_key"] == "present":
73
  status["provider"] = "anthropic"
74
  elif status["openai_key"] == "present":
75
  status["provider"] = "openai"
 
 
 
 
 
 
 
 
76
  return status
77
 
78
  def _detect_provider(self) -> Optional[str]:
@@ -81,6 +97,14 @@ class LLMClient:
81
  return "anthropic"
82
  elif self.openai_key:
83
  return "openai"
 
 
 
 
 
 
 
 
84
  return None
85
 
86
  def chat(
@@ -101,6 +125,14 @@ class LLMClient:
101
  return self._call_openai(messages, temperature, max_tokens, stream)
102
  elif self.provider == "anthropic":
103
  return self._call_anthropic(messages, temperature, max_tokens, stream)
 
 
 
 
 
 
 
 
104
 
105
  def chat_stream(
106
  self,
@@ -116,6 +148,9 @@ class LLMClient:
116
  yield from self._stream_openai(messages, temperature, max_tokens)
117
  elif self.provider == "anthropic":
118
  yield from self._stream_anthropic(messages, temperature, max_tokens)
 
 
 
119
 
120
  def _call_openrouter(self, messages, temperature, max_tokens, stream):
121
  """Call OpenRouter API"""
@@ -308,6 +343,161 @@ class LLMClient:
308
  yield from self._stream_openai(messages, temperature, max_tokens)
309
  else:
310
  raise RuntimeError(f"Anthropic streaming failed: {err_msg}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
 
312
  def _mock_response(self, messages: List[LLMMessage]) -> str:
313
  """Deprecated: mocks disabled to avoid hiding real failures."""
 
1
  """
2
  LLM Client for Overgrowth Pipeline
3
+ Supports multiple providers: OpenAI, Anthropic, Blaxel, SambaNova, Nebius, Hugging Face
4
  """
5
 
6
  import os
 
42
  # Support both standard and MCP hackathon naming conventions
43
  self.openai_key = _get_env(["OPENAI_API_KEY", "OPENAI_MCP_1ST_BDAY"])
44
  self.anthropic_key = _get_env(["ANTHROPIC_API_KEY", "ANTHROPIC_MCP_1ST_BDAY"])
45
+ self.blaxel_key = _get_env(["BLAXEL_MCP_1ST_BDAY"])
46
+ self.sambanova_key = _get_env(["SAMBA_NOVA_MCP_1ST_BDAY"])
47
+ self.nebius_key = _get_env(["NEBIUS_MCP_1ST_BDAY"])
48
+ self.huggingface_key = _get_env(["HUGGING_FACE_MCP_1ST_BDAY"])
49
 
50
  # Determine which provider to use
51
  self.provider = self._detect_provider()
 
71
  status = {
72
  "openai_key": "present" if _present(["OPENAI_API_KEY", "OPENAI_MCP_1ST_BDAY"]) else "missing",
73
  "anthropic_key": "present" if _present(["ANTHROPIC_API_KEY", "ANTHROPIC_MCP_1ST_BDAY"]) else "missing",
74
+ "blaxel_key": "present" if _present(["BLAXEL_MCP_1ST_BDAY"]) else "missing",
75
+ "sambanova_key": "present" if _present(["SAMBA_NOVA_MCP_1ST_BDAY"]) else "missing",
76
+ "nebius_key": "present" if _present(["NEBIUS_MCP_1ST_BDAY"]) else "missing",
77
+ "huggingface_key": "present" if _present(["HUGGING_FACE_MCP_1ST_BDAY"]) else "missing",
78
  "provider": "unknown",
79
  }
80
  if status["anthropic_key"] == "present":
81
  status["provider"] = "anthropic"
82
  elif status["openai_key"] == "present":
83
  status["provider"] = "openai"
84
+ elif status["blaxel_key"] == "present":
85
+ status["provider"] = "blaxel"
86
+ elif status["sambanova_key"] == "present":
87
+ status["provider"] = "sambanova"
88
+ elif status["nebius_key"] == "present":
89
+ status["provider"] = "nebius"
90
+ elif status["huggingface_key"] == "present":
91
+ status["provider"] = "huggingface"
92
  return status
93
 
94
  def _detect_provider(self) -> Optional[str]:
 
97
  return "anthropic"
98
  elif self.openai_key:
99
  return "openai"
100
+ elif self.blaxel_key:
101
+ return "blaxel"
102
+ elif self.sambanova_key:
103
+ return "sambanova"
104
+ elif self.nebius_key:
105
+ return "nebius"
106
+ elif self.huggingface_key:
107
+ return "huggingface"
108
  return None
109
 
110
  def chat(
 
125
  return self._call_openai(messages, temperature, max_tokens, stream)
126
  elif self.provider == "anthropic":
127
  return self._call_anthropic(messages, temperature, max_tokens, stream)
128
+ elif self.provider == "blaxel":
129
+ return self._call_blaxel(messages, temperature, max_tokens)
130
+ elif self.provider == "sambanova":
131
+ return self._call_sambanova(messages, temperature, max_tokens)
132
+ elif self.provider == "nebius":
133
+ return self._call_nebius(messages, temperature, max_tokens)
134
+ elif self.provider == "huggingface":
135
+ return self._call_huggingface(messages, temperature, max_tokens)
136
 
137
  def chat_stream(
138
  self,
 
148
  yield from self._stream_openai(messages, temperature, max_tokens)
149
  elif self.provider == "anthropic":
150
  yield from self._stream_anthropic(messages, temperature, max_tokens)
151
+ else:
152
+ # Other providers: no streaming support; fall back to single response
153
+ yield self.chat(messages, temperature=temperature, max_tokens=max_tokens)
154
 
155
  def _call_openrouter(self, messages, temperature, max_tokens, stream):
156
  """Call OpenRouter API"""
 
343
  yield from self._stream_openai(messages, temperature, max_tokens)
344
  else:
345
  raise RuntimeError(f"Anthropic streaming failed: {err_msg}")
346
+
347
+ # ----- Additional Providers (non-streaming) -----
348
+ def _call_blaxel(self, messages, temperature, max_tokens):
349
+ """Call Blaxel sandbox API (OpenAI-style)."""
350
+ import requests
351
+ call_id = str(uuid.uuid4())
352
+ model = os.getenv("BLAXEL_MODEL", "blaxel/claude-3-haiku")
353
+ base_url = os.getenv("BLAXEL_BASE_URL", "https://api.blaxel.ai/v0")
354
+ endpoint = f\"{base_url.rstrip('/')}/chat/completions\"
355
+ if monitor:
356
+ monitor.start_call(call_id, \"llm\", \"blaxel\", model, temperature=temperature)
357
+ payload = {
358
+ \"model\": model,
359
+ \"messages\": [{\"role\": m.role, \"content\": m.content} for m in messages],
360
+ \"temperature\": temperature,
361
+ \"max_tokens\": max_tokens,
362
+ }
363
+ headers = {\"Authorization\": f\"Bearer {self.blaxel_key}\"}
364
+ try:
365
+ resp = requests.post(endpoint, json=payload, headers=headers, timeout=30)
366
+ resp.raise_for_status()
367
+ data = resp.json()
368
+ content = data.get(\"choices\", [{}])[0].get(\"message\", {}).get(\"content\", \"\")
369
+ usage = data.get(\"usage\", {})
370
+ if monitor:
371
+ monitor.complete_call(
372
+ call_id,
373
+ success=True,
374
+ input_tokens=usage.get(\"prompt_tokens\"),
375
+ output_tokens=usage.get(\"completion_tokens\")
376
+ )
377
+ return content
378
+ except Exception as e:
379
+ if monitor:
380
+ monitor.complete_call(call_id, success=False, error_message=str(e))
381
+ raise
382
+
383
+ def _call_sambanova(self, messages, temperature, max_tokens):
384
+ """Call SambaNova API (OpenAI-compatible)."""
385
+ import requests
386
+ call_id = str(uuid.uuid4())
387
+ model = os.getenv("SAMBA_NOVA_MODEL", "Meta-Llama-3-8B-Instruct")
388
+ base_url = os.getenv("SAMBA_NOVA_BASE_URL", "https://api.sambanova.ai/v1")
389
+ endpoint = f\"{base_url.rstrip('/')}/chat/completions\"
390
+ if monitor:
391
+ monitor.start_call(call_id, \"llm\", \"sambanova\", model, temperature=temperature)
392
+ payload = {
393
+ \"model\": model,
394
+ \"messages\": [{\"role\": m.role, \"content\": m.content} for m in messages],
395
+ \"temperature\": temperature,
396
+ \"max_tokens\": max_tokens,
397
+ }
398
+ headers = {
399
+ \"Authorization\": f\"Bearer {self.sambanova_key}\",
400
+ \"Content-Type\": \"application/json\"
401
+ }
402
+ try:
403
+ resp = requests.post(endpoint, json=payload, headers=headers, timeout=30)
404
+ resp.raise_for_status()
405
+ data = resp.json()
406
+ content = data.get(\"choices\", [{}])[0].get(\"message\", {}).get(\"content\", \"\")
407
+ usage = data.get(\"usage\", {})
408
+ if monitor:
409
+ monitor.complete_call(
410
+ call_id,
411
+ success=True,
412
+ input_tokens=usage.get(\"prompt_tokens\"),
413
+ output_tokens=usage.get(\"completion_tokens\")
414
+ )
415
+ return content
416
+ except Exception as e:
417
+ if monitor:
418
+ monitor.complete_call(call_id, success=False, error_message=str(e))
419
+ raise
420
+
421
+ def _call_nebius(self, messages, temperature, max_tokens):
422
+ """Call Nebius Token Factory API (OpenAI-compatible)."""
423
+ import requests
424
+ call_id = str(uuid.uuid4())
425
+ model = os.getenv("NEBIUS_MODEL", "gpt-3.5-turbo")
426
+ base_url = os.getenv("NEBIUS_BASE_URL", "https://api.studio.nebius.ai/v1")
427
+ endpoint = f\"{base_url.rstrip('/')}/chat/completions\"
428
+ if monitor:
429
+ monitor.start_call(call_id, \"llm\", \"nebius\", model, temperature=temperature)
430
+ payload = {
431
+ \"model\": model,
432
+ \"messages\": [{\"role\": m.role, \"content\": m.content} for m in messages],
433
+ \"temperature\": temperature,
434
+ \"max_tokens\": max_tokens,
435
+ }
436
+ headers = {
437
+ \"Authorization\": f\"Bearer {self.nebius_key}\",
438
+ \"Content-Type\": \"application/json\"
439
+ }
440
+ try:
441
+ resp = requests.post(endpoint, json=payload, headers=headers, timeout=30)
442
+ resp.raise_for_status()
443
+ data = resp.json()
444
+ content = data.get(\"choices\", [{}])[0].get(\"message\", {}).get(\"content\", \"\")
445
+ usage = data.get(\"usage\", {})
446
+ if monitor:
447
+ monitor.complete_call(
448
+ call_id,
449
+ success=True,
450
+ input_tokens=usage.get(\"prompt_tokens\"),
451
+ output_tokens=usage.get(\"completion_tokens\")
452
+ )
453
+ return content
454
+ except Exception as e:
455
+ if monitor:
456
+ monitor.complete_call(call_id, success=False, error_message=str(e))
457
+ raise
458
+
459
+ def _call_huggingface(self, messages, temperature, max_tokens):
460
+ """Call Hugging Face Inference API (text generation)."""
461
+ import requests
462
+ call_id = str(uuid.uuid4())
463
+ model = os.getenv("HUGGINGFACE_MODEL", "tiiuae/falcon-7b-instruct")
464
+ endpoint = f\"https://api-inference.huggingface.co/models/{model}\"
465
+ if monitor:
466
+ monitor.start_call(call_id, \"llm\", \"huggingface\", model, temperature=temperature)
467
+ # Simple prompt concatenation
468
+ prompt = \"\\n\".join(f\"{m.role.upper()}: {m.content}\" for m in messages)
469
+ payload = {
470
+ \"inputs\": prompt,
471
+ \"parameters\": {
472
+ \"max_new_tokens\": max_tokens,
473
+ \"temperature\": temperature,
474
+ \"return_full_text\": False
475
+ }
476
+ }
477
+ headers = {
478
+ \"Authorization\": f\"Bearer {self.huggingface_key}\",
479
+ \"Content-Type\": \"application/json\"
480
+ }
481
+ try:
482
+ resp = requests.post(endpoint, json=payload, headers=headers, timeout=60)
483
+ resp.raise_for_status()
484
+ data = resp.json()
485
+ # Response can be list or dict
486
+ text = \"\"
487
+ if isinstance(data, list) and data:
488
+ if isinstance(data[0], dict):
489
+ text = data[0].get(\"generated_text\", \"\") or data[0].get(\"generated_texts\", \"\")
490
+ else:
491
+ text = str(data[0])
492
+ elif isinstance(data, dict):
493
+ text = data.get(\"generated_text\", \"\") or data.get(\"generated_texts\", \"\") or \"\"
494
+ if monitor:
495
+ monitor.complete_call(call_id, success=True)
496
+ return text
497
+ except Exception as e:
498
+ if monitor:
499
+ monitor.complete_call(call_id, success=False, error_message=str(e))
500
+ raise
501
 
502
  def _mock_response(self, messages: List[LLMMessage]) -> str:
503
  """Deprecated: mocks disabled to avoid hiding real failures."""