halyn 2.1.1__tar.gz → 2.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {halyn-2.1.1/src/halyn.egg-info → halyn-2.1.2}/PKG-INFO +79 -13
  2. {halyn-2.1.1 → halyn-2.1.2}/README.md +78 -12
  3. {halyn-2.1.1 → halyn-2.1.2}/pyproject.toml +1 -1
  4. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/__init__.py +1 -1
  5. halyn-2.1.2/src/halyn/llm.py +362 -0
  6. {halyn-2.1.1 → halyn-2.1.2/src/halyn.egg-info}/PKG-INFO +79 -13
  7. halyn-2.1.1/src/halyn/llm.py +0 -180
  8. {halyn-2.1.1 → halyn-2.1.2}/LICENSE +0 -0
  9. {halyn-2.1.1 → halyn-2.1.2}/setup.cfg +0 -0
  10. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/__main__.py +0 -0
  11. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/audit.py +0 -0
  12. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/auth.py +0 -0
  13. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/autonomy.py +0 -0
  14. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/cli.py +0 -0
  15. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/config.py +0 -0
  16. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/consent.py +0 -0
  17. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/control_plane.py +0 -0
  18. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/dashboard.py +0 -0
  19. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/discovery.py +0 -0
  20. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/__init__.py +0 -0
  21. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/browser.py +0 -0
  22. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/dds.py +0 -0
  23. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/docker.py +0 -0
  24. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/http_auto.py +0 -0
  25. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/mqtt.py +0 -0
  26. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/opcua.py +0 -0
  27. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/ros2.py +0 -0
  28. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/serial.py +0 -0
  29. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/socket_raw.py +0 -0
  30. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/ssh.py +0 -0
  31. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/unitree.py +0 -0
  32. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/drivers/websocket.py +0 -0
  33. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/engine.py +0 -0
  34. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/integrations/__init__.py +0 -0
  35. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/intent.py +0 -0
  36. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/mcp.py +0 -0
  37. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/mcp_serve.py +0 -0
  38. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/memory/__init__.py +0 -0
  39. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/memory/store.py +0 -0
  40. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/nrp_bridge.py +0 -0
  41. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/py.typed +0 -0
  42. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/sanitizer.py +0 -0
  43. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/security/__init__.py +0 -0
  44. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/security/audit_guard.py +0 -0
  45. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/security/ebpf_monitor.py +0 -0
  46. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/security/fs_watch.py +0 -0
  47. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/security/process_guard.py +0 -0
  48. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/security/proxy.py +0 -0
  49. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/server.py +0 -0
  50. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/shield.py +0 -0
  51. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/types.py +0 -0
  52. {halyn-2.1.1 → halyn-2.1.2}/src/halyn/watchdog.py +0 -0
  53. {halyn-2.1.1 → halyn-2.1.2}/src/halyn.egg-info/SOURCES.txt +0 -0
  54. {halyn-2.1.1 → halyn-2.1.2}/src/halyn.egg-info/dependency_links.txt +0 -0
  55. {halyn-2.1.1 → halyn-2.1.2}/src/halyn.egg-info/entry_points.txt +0 -0
  56. {halyn-2.1.1 → halyn-2.1.2}/src/halyn.egg-info/requires.txt +0 -0
  57. {halyn-2.1.1 → halyn-2.1.2}/src/halyn.egg-info/top_level.txt +0 -0
  58. {halyn-2.1.1 → halyn-2.1.2}/tests/test_halyn.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: halyn
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: Halyn — The governance layer for AI agents. Every action intercepted. Every decision auditable.
5
5
  Author-email: Elmadani SALKA <contact@halyn.dev>
6
6
  License: BSL-1.1
@@ -157,18 +157,84 @@ Deployed via `/etc/opt/chrome/policies/managed/halyn.json` — the agent cannot
157
157
 
158
158
  ---
159
159
 
160
- ## Supported Agents
161
-
162
- | Agent | Type | Integration |
163
- |-------|------|-------------|
164
- | Claude Sonnet 4.6 / Opus 4.6 (Anthropic) | Cloud | Proxy + MCP |
165
- | GPT-4.1 / o3 (OpenAI) | Cloud | Proxy |
166
- | Gemini 3.1 Pro / Flash (Google) | Cloud | Proxy |
167
- | Ollama | Local | Direct |
168
- | LM Studio | Local | Direct |
169
- | Jan.ai | Local | Direct |
170
- | OpenClaw | Agentic | Interceptor |
171
- | Any MCP agent | Any | MCP server |
160
+ ## Compatible AI
161
+
162
+ Halyn intercepts at the kernel and proxy level. It does not care which AI is running — it audits all of them equally. No AI is excluded.
163
+
164
+ ### How compatibility works
165
+
166
+ Halyn intercepts three things:
167
+ - **API calls** (iptables REDIRECT on port 443/80) — catches any HTTP request to any AI provider
168
+ - **Filesystem access** (inotify/FSEvents/eBPF) catches any agent touching files, regardless of origin
169
+ - **Process syscalls** (eBPF, Linux ≥5.8) catches any agent at the kernel level
170
+
171
+ This means: if an AI agent makes an API call or accesses your system, Halyn sees it.
172
+
173
+ ### Cloud AI
174
+
175
+ | Provider | Models (March 2026) | API |
176
+ |----------|---------------------|-----|
177
+ | **Anthropic** | Claude Sonnet 4.6, Claude Opus 4.6, Claude Haiku 4.5 | api.anthropic.com |
178
+ | **OpenAI** | GPT-4.1, GPT-4.1 mini, GPT-4.1 nano, o3, o4-mini | api.openai.com |
179
+ | **Google** | Gemini 3.1 Pro, Gemini 3.1 Flash, Gemini 3.1 Flash-Lite | generativelanguage.googleapis.com |
180
+ | **Mistral AI** | Mistral Large 2, Mistral Small 3, Codestral | api.mistral.ai |
181
+ | **xAI** | Grok-3, Grok-3 mini | api.x.ai |
182
+ | **DeepSeek** | DeepSeek-V3, DeepSeek-R1 | api.deepseek.com |
183
+ | **Cohere** | Command R+, Command R, Aya | api.cohere.com |
184
+ | **Perplexity** | Sonar Pro, Sonar, Sonar Reasoning | api.perplexity.ai |
185
+ | **01.AI** | Yi-Large, Yi-Vision | api.01.ai |
186
+ | **Alibaba** | Qwen-Max, Qwen-Plus, Qwen-Turbo | dashscope.aliyuncs.com |
187
+ | **Baidu** | ERNIE 4.5, ERNIE Speed | aip.baidubce.com |
188
+ | **Amazon Bedrock** | Claude, Titan, Llama, Mistral (via AWS) | bedrock.amazonaws.com |
189
+ | **Azure OpenAI** | GPT-4.1, o3 (via Microsoft) | *.openai.azure.com |
190
+ | **NVIDIA NIM** | Llama 3.3, Mistral, DeepSeek-R1 (on NVIDIA cloud) | integrate.api.nvidia.com |
191
+ | **Together AI** | 50+ open models via API | api.together.xyz |
192
+ | **Groq** | Llama, Mixtral, Gemma (ultra-fast inference) | api.groq.com |
193
+ | **Fireworks AI** | Llama, Mixtral, DeepSeek | api.fireworks.ai |
194
+
195
+ ### Local AI
196
+
197
+ Any local model is compatible — Halyn intercepts at the process level, not the network level.
198
+
199
+ | Runtime | Models | Notes |
200
+ |---------|--------|-------|
201
+ | **Ollama** | Llama 3.3, Qwen2.5, Mistral, DeepSeek-R1, Phi-4, Gemma 3, ... | OpenAI-compatible API |
202
+ | **LM Studio** | Any GGUF model | OpenAI-compatible server |
203
+ | **Jan.ai** | Any GGUF or ONNX model | Desktop + server mode |
204
+ | **GPT4All** | Llama, Mistral, Phi variants | Offline, no telemetry |
205
+ | **llama.cpp** | Any GGUF model directly | Server mode (`--server`) |
206
+ | **LocalAI** | 100+ models, any GGUF | Drop-in OpenAI replacement |
207
+ | **text-generation-webui** | Any HuggingFace model | Extension ecosystem |
208
+ | **KoboldCpp** | Any GGUF model | Focus on creative writing |
209
+ | **OpenWebUI** | Ollama + OpenAI frontend | Browser-based |
210
+ | **AnythingLLM** | Multi-model workspace | Team-friendly |
211
+ | **Xinference** | HuggingFace + GGUF | Enterprise local inference |
212
+ | **vLLM** | HuggingFace models | High-throughput server |
213
+ | **TGI (HuggingFace)** | HuggingFace models | Production inference |
214
+
215
+ ### Agentic frameworks
216
+
217
+ Halyn intercepts any agentic system. The agent framework doesn't matter.
218
+
219
+ | Framework | Notes |
220
+ |-----------|-------|
221
+ | **OpenClaw** | Full interceptor — every action audited |
222
+ | **Claude Cowork** | Proxy + filesystem hooks |
223
+ | **Claude Code** | Process-level monitoring |
224
+ | **LangChain** | API calls intercepted automatically |
225
+ | **LlamaIndex** | API calls intercepted automatically |
226
+ | **AutoGen** | API calls intercepted automatically |
227
+ | **CrewAI** | API calls intercepted automatically |
228
+ | **Semantic Kernel** | API calls intercepted automatically |
229
+ | **BeeQ** | Native AAP integration |
230
+ | **Any MCP agent** | MCP server passthrough |
231
+ | **Any A2A agent** | Network-level interception |
232
+ | **Any OpenAI-compatible API** | Universal proxy compatibility |
233
+
234
+ ### The rule
235
+
236
+ > If an AI touches your machine or calls an API — Halyn sees it.
237
+ > No exception. No exclusion. That's the point.
172
238
 
173
239
  ---
174
240
 
@@ -124,18 +124,84 @@ Deployed via `/etc/opt/chrome/policies/managed/halyn.json` — the agent cannot
124
124
 
125
125
  ---
126
126
 
127
- ## Supported Agents
128
-
129
- | Agent | Type | Integration |
130
- |-------|------|-------------|
131
- | Claude Sonnet 4.6 / Opus 4.6 (Anthropic) | Cloud | Proxy + MCP |
132
- | GPT-4.1 / o3 (OpenAI) | Cloud | Proxy |
133
- | Gemini 3.1 Pro / Flash (Google) | Cloud | Proxy |
134
- | Ollama | Local | Direct |
135
- | LM Studio | Local | Direct |
136
- | Jan.ai | Local | Direct |
137
- | OpenClaw | Agentic | Interceptor |
138
- | Any MCP agent | Any | MCP server |
127
+ ## Compatible AI
128
+
129
+ Halyn intercepts at the kernel and proxy level. It does not care which AI is running — it audits all of them equally. No AI is excluded.
130
+
131
+ ### How compatibility works
132
+
133
+ Halyn intercepts three things:
134
+ - **API calls** (iptables REDIRECT on port 443/80) — catches any HTTP request to any AI provider
135
+ - **Filesystem access** (inotify/FSEvents/eBPF) catches any agent touching files, regardless of origin
136
+ - **Process syscalls** (eBPF, Linux ≥5.8) catches any agent at the kernel level
137
+
138
+ This means: if an AI agent makes an API call or accesses your system, Halyn sees it.
139
+
140
+ ### Cloud AI
141
+
142
+ | Provider | Models (March 2026) | API |
143
+ |----------|---------------------|-----|
144
+ | **Anthropic** | Claude Sonnet 4.6, Claude Opus 4.6, Claude Haiku 4.5 | api.anthropic.com |
145
+ | **OpenAI** | GPT-4.1, GPT-4.1 mini, GPT-4.1 nano, o3, o4-mini | api.openai.com |
146
+ | **Google** | Gemini 3.1 Pro, Gemini 3.1 Flash, Gemini 3.1 Flash-Lite | generativelanguage.googleapis.com |
147
+ | **Mistral AI** | Mistral Large 2, Mistral Small 3, Codestral | api.mistral.ai |
148
+ | **xAI** | Grok-3, Grok-3 mini | api.x.ai |
149
+ | **DeepSeek** | DeepSeek-V3, DeepSeek-R1 | api.deepseek.com |
150
+ | **Cohere** | Command R+, Command R, Aya | api.cohere.com |
151
+ | **Perplexity** | Sonar Pro, Sonar, Sonar Reasoning | api.perplexity.ai |
152
+ | **01.AI** | Yi-Large, Yi-Vision | api.01.ai |
153
+ | **Alibaba** | Qwen-Max, Qwen-Plus, Qwen-Turbo | dashscope.aliyuncs.com |
154
+ | **Baidu** | ERNIE 4.5, ERNIE Speed | aip.baidubce.com |
155
+ | **Amazon Bedrock** | Claude, Titan, Llama, Mistral (via AWS) | bedrock.amazonaws.com |
156
+ | **Azure OpenAI** | GPT-4.1, o3 (via Microsoft) | *.openai.azure.com |
157
+ | **NVIDIA NIM** | Llama 3.3, Mistral, DeepSeek-R1 (on NVIDIA cloud) | integrate.api.nvidia.com |
158
+ | **Together AI** | 50+ open models via API | api.together.xyz |
159
+ | **Groq** | Llama, Mixtral, Gemma (ultra-fast inference) | api.groq.com |
160
+ | **Fireworks AI** | Llama, Mixtral, DeepSeek | api.fireworks.ai |
161
+
162
+ ### Local AI
163
+
164
+ Any local model is compatible — Halyn intercepts at the process level, not the network level.
165
+
166
+ | Runtime | Models | Notes |
167
+ |---------|--------|-------|
168
+ | **Ollama** | Llama 3.3, Qwen2.5, Mistral, DeepSeek-R1, Phi-4, Gemma 3, ... | OpenAI-compatible API |
169
+ | **LM Studio** | Any GGUF model | OpenAI-compatible server |
170
+ | **Jan.ai** | Any GGUF or ONNX model | Desktop + server mode |
171
+ | **GPT4All** | Llama, Mistral, Phi variants | Offline, no telemetry |
172
+ | **llama.cpp** | Any GGUF model directly | Server mode (`--server`) |
173
+ | **LocalAI** | 100+ models, any GGUF | Drop-in OpenAI replacement |
174
+ | **text-generation-webui** | Any HuggingFace model | Extension ecosystem |
175
+ | **KoboldCpp** | Any GGUF model | Focus on creative writing |
176
+ | **OpenWebUI** | Ollama + OpenAI frontend | Browser-based |
177
+ | **AnythingLLM** | Multi-model workspace | Team-friendly |
178
+ | **Xinference** | HuggingFace + GGUF | Enterprise local inference |
179
+ | **vLLM** | HuggingFace models | High-throughput server |
180
+ | **TGI (HuggingFace)** | HuggingFace models | Production inference |
181
+
182
+ ### Agentic frameworks
183
+
184
+ Halyn intercepts any agentic system. The agent framework doesn't matter.
185
+
186
+ | Framework | Notes |
187
+ |-----------|-------|
188
+ | **OpenClaw** | Full interceptor — every action audited |
189
+ | **Claude Cowork** | Proxy + filesystem hooks |
190
+ | **Claude Code** | Process-level monitoring |
191
+ | **LangChain** | API calls intercepted automatically |
192
+ | **LlamaIndex** | API calls intercepted automatically |
193
+ | **AutoGen** | API calls intercepted automatically |
194
+ | **CrewAI** | API calls intercepted automatically |
195
+ | **Semantic Kernel** | API calls intercepted automatically |
196
+ | **BeeQ** | Native AAP integration |
197
+ | **Any MCP agent** | MCP server passthrough |
198
+ | **Any A2A agent** | Network-level interception |
199
+ | **Any OpenAI-compatible API** | Universal proxy compatibility |
200
+
201
+ ### The rule
202
+
203
+ > If an AI touches your machine or calls an API — Halyn sees it.
204
+ > No exception. No exclusion. That's the point.
139
205
 
140
206
  ---
141
207
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "halyn"
3
- version = "2.1.1"
3
+ version = "2.1.2"
4
4
  description = "Halyn — The governance layer for AI agents. Every action intercepted. Every decision auditable."
5
5
  requires-python = ">=3.10"
6
6
  license = {text = "BSL-1.1"}
@@ -9,7 +9,7 @@ Every action intercepted. Every decision auditable.
9
9
  The AI cannot bypass it.
10
10
  """
11
11
 
12
- __version__ = "2.1.1"
12
+ __version__ = "2.1.2"
13
13
  __author__ = "Elmadani SALKA"
14
14
  __license__ = "BSL-1.1"
15
15
  __email__ = "contact@halyn.dev"
@@ -0,0 +1,362 @@
1
+ # Copyright (c) 2026 Elmadani SALKA
2
+ # Licensed under BSL-1.1. See LICENSE file.
3
+ # Commercial use requires a license — contact@halyn.dev
4
+
5
+ """
6
+ LLM Connector — Multi-provider abstraction.
7
+
8
+ Halyn is provider-agnostic. It monitors any AI, regardless of origin.
9
+ This module provides connectors for direct LLM integration (optional).
10
+ The proxy layer works independently — without any connector configured.
11
+
12
+ Supported by the proxy (no connector needed):
13
+ Cloud: Anthropic, OpenAI, Google, Mistral, xAI, DeepSeek, Cohere,
14
+ Perplexity, 01.AI, Alibaba, Baidu, Amazon Bedrock, Azure OpenAI,
15
+ NVIDIA NIM, Together AI, Groq, Fireworks AI, and any HTTP API.
16
+ Local: Ollama, LM Studio, Jan.ai, GPT4All, llama.cpp, LocalAI,
17
+ text-generation-webui, KoboldCpp, vLLM, TGI, and any local server.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import logging
24
+ import os
25
+ import urllib.request
26
+ from abc import ABC, abstractmethod
27
+ from typing import Any
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+
32
+ # ─── Base ─────────────────────────────────────────────────────────────────────
33
+
34
+ class LLMResponse:
35
+ def __init__(self, content: str, model: str = "", usage: dict | None = None) -> None:
36
+ self.content = content
37
+ self.model = model
38
+ self.usage = usage or {}
39
+
40
+
41
+ class LLMConnector(ABC):
42
+ """Base class for all LLM connectors."""
43
+
44
+ @abstractmethod
45
+ def complete(
46
+ self,
47
+ messages: list[dict],
48
+ system: str = "",
49
+ max_tokens: int = 1024,
50
+ ) -> LLMResponse:
51
+ ...
52
+
53
+
54
+ # ─── Cloud providers ──────────────────────────────────────────────────────────
55
+
56
+ class AnthropicConnector(LLMConnector):
57
+ """Anthropic — Claude Sonnet 4.6, Opus 4.6, Haiku 4.5."""
58
+
59
+ def __init__(self, api_key: str = "", model: str = "claude-sonnet-4-6") -> None:
60
+ self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
61
+ self.model = model
62
+ self.endpoint = "https://api.anthropic.com/v1/messages"
63
+
64
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
65
+ body: dict[str, Any] = {"model": self.model, "max_tokens": max_tokens, "messages": messages}
66
+ if system:
67
+ body["system"] = system
68
+ data = self._post(self.endpoint, body, {
69
+ "x-api-key": self.api_key,
70
+ "anthropic-version": "2023-06-01",
71
+ })
72
+ content = data["content"][0]["text"]
73
+ return LLMResponse(content, self.model, data.get("usage"))
74
+
75
+ def _post(self, url: str, body: dict, headers: dict) -> dict:
76
+ payload = json.dumps(body).encode()
77
+ req = urllib.request.Request(url, data=payload, method="POST")
78
+ req.add_header("Content-Type", "application/json")
79
+ for k, v in headers.items():
80
+ req.add_header(k, v)
81
+ with urllib.request.urlopen(req, timeout=60) as resp:
82
+ return json.loads(resp.read())
83
+
84
+
85
+ class OpenAIConnector(LLMConnector):
86
+ """OpenAI — GPT-4.1, GPT-4.1 mini, GPT-4.1 nano, o3, o4-mini.
87
+ Also compatible with: Azure OpenAI, NVIDIA NIM, Together AI, Groq,
88
+ Fireworks AI, and any OpenAI-compatible endpoint.
89
+ """
90
+
91
+ def __init__(
92
+ self,
93
+ api_key: str = "",
94
+ model: str = "gpt-4.1",
95
+ endpoint: str = "https://api.openai.com/v1",
96
+ ) -> None:
97
+ self.api_key = api_key or os.environ.get("OPENAI_API_KEY", "")
98
+ self.model = model
99
+ self.endpoint = endpoint.rstrip("/")
100
+
101
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
102
+ msgs = ([{"role": "system", "content": system}] if system else []) + messages
103
+ body = {"model": self.model, "max_tokens": max_tokens, "messages": msgs}
104
+ data = self._post(f"{self.endpoint}/chat/completions", body)
105
+ content = data["choices"][0]["message"]["content"]
106
+ return LLMResponse(content, self.model, data.get("usage"))
107
+
108
+ def _post(self, url: str, body: dict) -> dict:
109
+ payload = json.dumps(body).encode()
110
+ req = urllib.request.Request(url, data=payload, method="POST")
111
+ req.add_header("Content-Type", "application/json")
112
+ req.add_header("Authorization", f"Bearer {self.api_key}")
113
+ with urllib.request.urlopen(req, timeout=60) as resp:
114
+ return json.loads(resp.read())
115
+
116
+
117
+ class GeminiConnector(LLMConnector):
118
+ """Google — Gemini 3.1 Pro, Gemini 3.1 Flash, Gemini 3.1 Flash-Lite."""
119
+
120
+ def __init__(self, api_key: str = "", model: str = "gemini-3.1-flash-lite-preview") -> None:
121
+ self.api_key = api_key or os.environ.get("GOOGLE_API_KEY", "")
122
+ self.model = model
123
+
124
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
125
+ url = (
126
+ f"https://generativelanguage.googleapis.com/v1beta/models/"
127
+ f"{self.model}:generateContent?key={self.api_key}"
128
+ )
129
+ contents = [{"role": m["role"].replace("assistant", "model"), "parts": [{"text": m["content"]}]}
130
+ for m in messages]
131
+ body: dict[str, Any] = {
132
+ "contents": contents,
133
+ "generationConfig": {"maxOutputTokens": max_tokens},
134
+ }
135
+ if system:
136
+ body["systemInstruction"] = {"parts": [{"text": system}]}
137
+ payload = json.dumps(body).encode()
138
+ req = urllib.request.Request(url, data=payload, method="POST")
139
+ req.add_header("Content-Type", "application/json")
140
+ with urllib.request.urlopen(req, timeout=60) as resp:
141
+ data = json.loads(resp.read())
142
+ content = data["candidates"][0]["content"]["parts"][0]["text"]
143
+ return LLMResponse(content, self.model)
144
+
145
+
146
+ class MistralConnector(LLMConnector):
147
+ """Mistral AI — Mistral Large 2, Mistral Small 3, Codestral."""
148
+
149
+ def __init__(self, api_key: str = "", model: str = "mistral-small-latest") -> None:
150
+ self.api_key = api_key or os.environ.get("MISTRAL_API_KEY", "")
151
+ self.model = model
152
+ self._oa = OpenAIConnector(api_key, model, "https://api.mistral.ai/v1")
153
+
154
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
155
+ self._oa.api_key = self.api_key
156
+ return self._oa.complete(messages, system, max_tokens)
157
+
158
+
159
+ class XAIConnector(LLMConnector):
160
+ """xAI — Grok-3, Grok-3 mini."""
161
+
162
+ def __init__(self, api_key: str = "", model: str = "grok-3-mini") -> None:
163
+ self.api_key = api_key or os.environ.get("XAI_API_KEY", "")
164
+ self._oa = OpenAIConnector(api_key, model, "https://api.x.ai/v1")
165
+
166
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
167
+ self._oa.api_key = self.api_key
168
+ return self._oa.complete(messages, system, max_tokens)
169
+
170
+
171
+ class DeepSeekConnector(LLMConnector):
172
+ """DeepSeek — DeepSeek-V3, DeepSeek-R1."""
173
+
174
+ def __init__(self, api_key: str = "", model: str = "deepseek-chat") -> None:
175
+ self.api_key = api_key or os.environ.get("DEEPSEEK_API_KEY", "")
176
+ self._oa = OpenAIConnector(api_key, model, "https://api.deepseek.com/v1")
177
+
178
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
179
+ self._oa.api_key = self.api_key
180
+ return self._oa.complete(messages, system, max_tokens)
181
+
182
+
183
+ class GroqConnector(LLMConnector):
184
+ """Groq — Llama, Mixtral, Gemma (ultra-fast inference)."""
185
+
186
+ def __init__(self, api_key: str = "", model: str = "llama-3.3-70b-versatile") -> None:
187
+ self.api_key = api_key or os.environ.get("GROQ_API_KEY", "")
188
+ self._oa = OpenAIConnector(api_key, model, "https://api.groq.com/openai/v1")
189
+
190
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
191
+ self._oa.api_key = self.api_key
192
+ return self._oa.complete(messages, system, max_tokens)
193
+
194
+
195
+ class PerplexityConnector(LLMConnector):
196
+ """Perplexity — Sonar Pro, Sonar, Sonar Reasoning."""
197
+
198
+ def __init__(self, api_key: str = "", model: str = "sonar") -> None:
199
+ self.api_key = api_key or os.environ.get("PERPLEXITY_API_KEY", "")
200
+ self._oa = OpenAIConnector(api_key, model, "https://api.perplexity.ai")
201
+
202
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
203
+ self._oa.api_key = self.api_key
204
+ return self._oa.complete(messages, system, max_tokens)
205
+
206
+
207
+ class CohereConnector(LLMConnector):
208
+ """Cohere — Command R+, Command R, Aya."""
209
+
210
+ def __init__(self, api_key: str = "", model: str = "command-r-plus") -> None:
211
+ self.api_key = api_key or os.environ.get("COHERE_API_KEY", "")
212
+ self.model = model
213
+
214
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
215
+ url = "https://api.cohere.com/v2/chat"
216
+ chat_history = [{"role": m["role"], "content": m["content"]} for m in messages[:-1]]
217
+ body: dict[str, Any] = {
218
+ "model": self.model,
219
+ "messages": [{"role": m["role"], "content": m["content"]} for m in messages],
220
+ "max_tokens": max_tokens,
221
+ }
222
+ if system:
223
+ body["system"] = system
224
+ payload = json.dumps(body).encode()
225
+ req = urllib.request.Request(url, data=payload, method="POST")
226
+ req.add_header("Content-Type", "application/json")
227
+ req.add_header("Authorization", f"Bearer {self.api_key}")
228
+ with urllib.request.urlopen(req, timeout=60) as resp:
229
+ data = json.loads(resp.read())
230
+ content = data["message"]["content"][0]["text"]
231
+ return LLMResponse(content, self.model)
232
+
233
+
234
+ # ─── Local providers ──────────────────────────────────────────────────────────
235
+
236
+ class OllamaConnector(LLMConnector):
237
+ """Ollama — any local model: Llama 3.3, Qwen2.5, Mistral, DeepSeek-R1,
238
+ Phi-4, Gemma 3, and hundreds more. OpenAI-compatible API.
239
+ """
240
+
241
+ def __init__(self, model: str = "llama3.2", host: str = "http://localhost:11434") -> None:
242
+ self.model = model
243
+ self._oa = OpenAIConnector("ollama", model, f"{host}/v1")
244
+
245
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
246
+ return self._oa.complete(messages, system, max_tokens)
247
+
248
+
249
+ class LocalAIConnector(LLMConnector):
250
+ """LocalAI / LM Studio / Jan.ai / KoboldCpp / text-generation-webui /
251
+ llama.cpp / vLLM / TGI — any OpenAI-compatible local server.
252
+ """
253
+
254
+ def __init__(
255
+ self,
256
+ model: str = "local-model",
257
+ host: str = "http://localhost:8080",
258
+ api_key: str = "local",
259
+ ) -> None:
260
+ self.model = model
261
+ self._oa = OpenAIConnector(api_key, model, host)
262
+
263
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
264
+ return self._oa.complete(messages, system, max_tokens)
265
+
266
+
267
+ class HuggingFaceConnector(LLMConnector):
268
+ """HuggingFace — any model via transformers pipeline (fully local)."""
269
+
270
+ def __init__(self, model: str = "microsoft/Phi-4") -> None:
271
+ self.model_name = model
272
+ self._pipeline: Any = None
273
+
274
+ def _load(self) -> None:
275
+ if self._pipeline is None:
276
+ try:
277
+ from transformers import pipeline # type: ignore
278
+ except ImportError as e:
279
+ raise ImportError("pip install transformers torch") from e
280
+ log.info("llm.loading model=%s", self.model_name)
281
+ self._pipeline = pipeline("text-generation", model=self.model_name, device_map="auto")
282
+ log.info("llm.loaded model=%s", self.model_name)
283
+
284
+ def complete(self, messages: list[dict], system: str = "", max_tokens: int = 1024) -> LLMResponse:
285
+ self._load()
286
+ prompt = "\n".join(f"{m['role']}: {m['content']}" for m in messages)
287
+ if system:
288
+ prompt = f"system: {system}\n{prompt}"
289
+ result = self._pipeline(prompt, max_new_tokens=max_tokens, do_sample=False)
290
+ text = result[0]["generated_text"][len(prompt):].strip()
291
+ return LLMResponse(text, self.model_name)
292
+
293
+
294
+ # ─── Factory ──────────────────────────────────────────────────────────────────
295
+
296
+ def create_connector(provider: str, **kwargs: Any) -> LLMConnector:
297
+ """
298
+ Create a connector by provider name.
299
+
300
+ Cloud providers: anthropic, openai, azure, google, gemini, mistral,
301
+ xai, grok, deepseek, cohere, perplexity, groq,
302
+ fireworks, together, nvidia, bedrock
303
+ Local providers: ollama, lmstudio, jan, localai, llamacpp, gpt4all,
304
+ kobold, vllm, tgi, huggingface, openai-compatible
305
+ """
306
+ # Normalize aliases
307
+ aliases = {
308
+ # Anthropic
309
+ "anthropic": "anthropic", "claude": "anthropic",
310
+ # OpenAI
311
+ "openai": "openai", "gpt": "openai", "azure": "openai",
312
+ "nvidia": "openai", "together": "openai", "fireworks": "openai",
313
+ "bedrock": "openai", # via OpenAI-compat gateway
314
+ # Google
315
+ "google": "google", "gemini": "google",
316
+ # Mistral
317
+ "mistral": "mistral",
318
+ # xAI
319
+ "xai": "xai", "grok": "xai",
320
+ # DeepSeek
321
+ "deepseek": "deepseek",
322
+ # Groq
323
+ "groq": "groq",
324
+ # Perplexity
325
+ "perplexity": "perplexity", "sonar": "perplexity",
326
+ # Cohere
327
+ "cohere": "cohere", "command": "cohere",
328
+ # Local
329
+ "ollama": "ollama",
330
+ "lmstudio": "local", "jan": "local", "janai": "local",
331
+ "localai": "local", "llamacpp": "local", "llama.cpp": "local",
332
+ "kobold": "local", "koboldcpp": "local",
333
+ "vllm": "local", "tgi": "local",
334
+ "gpt4all": "local", "openwebui": "local",
335
+ "local": "local", "openai-compatible": "local",
336
+ # HuggingFace
337
+ "huggingface": "hf", "hf": "hf", "transformers": "hf",
338
+ }
339
+
340
+ connectors = {
341
+ "anthropic": AnthropicConnector,
342
+ "openai": OpenAIConnector,
343
+ "google": GeminiConnector,
344
+ "mistral": MistralConnector,
345
+ "xai": XAIConnector,
346
+ "deepseek": DeepSeekConnector,
347
+ "groq": GroqConnector,
348
+ "perplexity": PerplexityConnector,
349
+ "cohere": CohereConnector,
350
+ "ollama": OllamaConnector,
351
+ "local": LocalAIConnector,
352
+ "hf": HuggingFaceConnector,
353
+ }
354
+
355
+ key = aliases.get(provider.lower(), provider.lower())
356
+ cls = connectors.get(key)
357
+ if cls is None:
358
+ raise ValueError(
359
+ f"Unknown provider '{provider}'. "
360
+ f"Note: the Halyn proxy works with ANY AI regardless of this connector."
361
+ )
362
+ return cls(**kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: halyn
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: Halyn — The governance layer for AI agents. Every action intercepted. Every decision auditable.
5
5
  Author-email: Elmadani SALKA <contact@halyn.dev>
6
6
  License: BSL-1.1
@@ -157,18 +157,84 @@ Deployed via `/etc/opt/chrome/policies/managed/halyn.json` — the agent cannot
157
157
 
158
158
  ---
159
159
 
160
- ## Supported Agents
161
-
162
- | Agent | Type | Integration |
163
- |-------|------|-------------|
164
- | Claude Sonnet 4.6 / Opus 4.6 (Anthropic) | Cloud | Proxy + MCP |
165
- | GPT-4.1 / o3 (OpenAI) | Cloud | Proxy |
166
- | Gemini 3.1 Pro / Flash (Google) | Cloud | Proxy |
167
- | Ollama | Local | Direct |
168
- | LM Studio | Local | Direct |
169
- | Jan.ai | Local | Direct |
170
- | OpenClaw | Agentic | Interceptor |
171
- | Any MCP agent | Any | MCP server |
160
+ ## Compatible AI
161
+
162
+ Halyn intercepts at the kernel and proxy level. It does not care which AI is running — it audits all of them equally. No AI is excluded.
163
+
164
+ ### How compatibility works
165
+
166
+ Halyn intercepts three things:
167
+ - **API calls** (iptables REDIRECT on port 443/80) — catches any HTTP request to any AI provider
168
+ - **Filesystem access** (inotify/FSEvents/eBPF) catches any agent touching files, regardless of origin
169
+ - **Process syscalls** (eBPF, Linux ≥5.8) catches any agent at the kernel level
170
+
171
+ This means: if an AI agent makes an API call or accesses your system, Halyn sees it.
172
+
173
+ ### Cloud AI
174
+
175
+ | Provider | Models (March 2026) | API |
176
+ |----------|---------------------|-----|
177
+ | **Anthropic** | Claude Sonnet 4.6, Claude Opus 4.6, Claude Haiku 4.5 | api.anthropic.com |
178
+ | **OpenAI** | GPT-4.1, GPT-4.1 mini, GPT-4.1 nano, o3, o4-mini | api.openai.com |
179
+ | **Google** | Gemini 3.1 Pro, Gemini 3.1 Flash, Gemini 3.1 Flash-Lite | generativelanguage.googleapis.com |
180
+ | **Mistral AI** | Mistral Large 2, Mistral Small 3, Codestral | api.mistral.ai |
181
+ | **xAI** | Grok-3, Grok-3 mini | api.x.ai |
182
+ | **DeepSeek** | DeepSeek-V3, DeepSeek-R1 | api.deepseek.com |
183
+ | **Cohere** | Command R+, Command R, Aya | api.cohere.com |
184
+ | **Perplexity** | Sonar Pro, Sonar, Sonar Reasoning | api.perplexity.ai |
185
+ | **01.AI** | Yi-Large, Yi-Vision | api.01.ai |
186
+ | **Alibaba** | Qwen-Max, Qwen-Plus, Qwen-Turbo | dashscope.aliyuncs.com |
187
+ | **Baidu** | ERNIE 4.5, ERNIE Speed | aip.baidubce.com |
188
+ | **Amazon Bedrock** | Claude, Titan, Llama, Mistral (via AWS) | bedrock.amazonaws.com |
189
+ | **Azure OpenAI** | GPT-4.1, o3 (via Microsoft) | *.openai.azure.com |
190
+ | **NVIDIA NIM** | Llama 3.3, Mistral, DeepSeek-R1 (on NVIDIA cloud) | integrate.api.nvidia.com |
191
+ | **Together AI** | 50+ open models via API | api.together.xyz |
192
+ | **Groq** | Llama, Mixtral, Gemma (ultra-fast inference) | api.groq.com |
193
+ | **Fireworks AI** | Llama, Mixtral, DeepSeek | api.fireworks.ai |
194
+
195
+ ### Local AI
196
+
197
+ Any local model is compatible — Halyn intercepts at the process level, not the network level.
198
+
199
+ | Runtime | Models | Notes |
200
+ |---------|--------|-------|
201
+ | **Ollama** | Llama 3.3, Qwen2.5, Mistral, DeepSeek-R1, Phi-4, Gemma 3, ... | OpenAI-compatible API |
202
+ | **LM Studio** | Any GGUF model | OpenAI-compatible server |
203
+ | **Jan.ai** | Any GGUF or ONNX model | Desktop + server mode |
204
+ | **GPT4All** | Llama, Mistral, Phi variants | Offline, no telemetry |
205
+ | **llama.cpp** | Any GGUF model directly | Server mode (`--server`) |
206
+ | **LocalAI** | 100+ models, any GGUF | Drop-in OpenAI replacement |
207
+ | **text-generation-webui** | Any HuggingFace model | Extension ecosystem |
208
+ | **KoboldCpp** | Any GGUF model | Focus on creative writing |
209
+ | **OpenWebUI** | Ollama + OpenAI frontend | Browser-based |
210
+ | **AnythingLLM** | Multi-model workspace | Team-friendly |
211
+ | **Xinference** | HuggingFace + GGUF | Enterprise local inference |
212
+ | **vLLM** | HuggingFace models | High-throughput server |
213
+ | **TGI (HuggingFace)** | HuggingFace models | Production inference |
214
+
215
+ ### Agentic frameworks
216
+
217
+ Halyn intercepts any agentic system. The agent framework doesn't matter.
218
+
219
+ | Framework | Notes |
220
+ |-----------|-------|
221
+ | **OpenClaw** | Full interceptor — every action audited |
222
+ | **Claude Cowork** | Proxy + filesystem hooks |
223
+ | **Claude Code** | Process-level monitoring |
224
+ | **LangChain** | API calls intercepted automatically |
225
+ | **LlamaIndex** | API calls intercepted automatically |
226
+ | **AutoGen** | API calls intercepted automatically |
227
+ | **CrewAI** | API calls intercepted automatically |
228
+ | **Semantic Kernel** | API calls intercepted automatically |
229
+ | **BeeQ** | Native AAP integration |
230
+ | **Any MCP agent** | MCP server passthrough |
231
+ | **Any A2A agent** | Network-level interception |
232
+ | **Any OpenAI-compatible API** | Universal proxy compatibility |
233
+
234
+ ### The rule
235
+
236
+ > If an AI touches your machine or calls an API — Halyn sees it.
237
+ > No exception. No exclusion. That's the point.
172
238
 
173
239
  ---
174
240
 
@@ -1,180 +0,0 @@
1
- # Copyright (c) 2026 Elmadani SALKA
2
- # Licensed under BSL-1.1. See LICENSE file.
3
- # Commercial use requires a license — contact@halyn.dev
4
-
5
- """
6
- LLM Connector — Multi-provider LLM abstraction.
7
-
8
- Supports: Claude API, OpenAI API, Ollama (local), HuggingFace (local),
9
- vLLM (self-hosted), any OpenAI-compatible endpoint.
10
-
11
- The LLM is NOT in the control plane. It connects FROM OUTSIDE via MCP.
12
- This module handles outbound LLM calls when Halyn needs reasoning
13
- (e.g. for autonomous reasoning, incident analysis, summarization).
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import json
19
- import logging
20
- import os
21
- from abc import ABC, abstractmethod
22
- from typing import Any
23
-
24
- log = logging.getLogger("halyn.llm")
25
-
26
-
27
- class LLMConnector(ABC):
28
- """Base class for LLM connections."""
29
-
30
- @abstractmethod
31
- async def complete(self, prompt: str, system: str = "", max_tokens: int = 1000) -> str:
32
- """Send prompt, get response."""
33
-
34
- @abstractmethod
35
- async def is_available(self) -> bool:
36
- """Check if LLM is reachable."""
37
-
38
-
39
- class ClaudeConnector(LLMConnector):
40
- """Anthropic Claude API."""
41
-
42
- def __init__(self, api_key: str = "", model: str = "claude-sonnet-4-6") -> None:
43
- self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
44
- self.model = model
45
- self.endpoint = "https://api.anthropic.com/v1/messages"
46
-
47
- async def complete(self, prompt: str, system: str = "", max_tokens: int = 1000) -> str:
48
- import aiohttp
49
- headers = {
50
- "x-api-key": self.api_key,
51
- "anthropic-version": "2023-06-01",
52
- "content-type": "application/json",
53
- }
54
- body: dict[str, Any] = {
55
- "model": self.model,
56
- "max_tokens": max_tokens,
57
- "messages": [{"role": "user", "content": prompt}],
58
- }
59
- if system:
60
- body["system"] = system
61
- async with aiohttp.ClientSession() as session:
62
- async with session.post(self.endpoint, json=body, headers=headers) as resp:
63
- data = await resp.json()
64
- return data.get("content", [{}])[0].get("text", "")
65
-
66
- async def is_available(self) -> bool:
67
- return bool(self.api_key)
68
-
69
-
70
- class OpenAIConnector(LLMConnector):
71
- """OpenAI or any OpenAI-compatible API (vLLM, LiteLLM, etc.)."""
72
-
73
- def __init__(self, api_key: str = "", model: str = "gpt-4.1",
74
- endpoint: str = "https://api.openai.com/v1") -> None:
75
- self.api_key = api_key or os.environ.get("OPENAI_API_KEY", "")
76
- self.model = model
77
- self.endpoint = endpoint
78
-
79
- async def complete(self, prompt: str, system: str = "", max_tokens: int = 1000) -> str:
80
- import aiohttp
81
- headers = {
82
- "Authorization": f"Bearer {self.api_key}",
83
- "Content-Type": "application/json",
84
- }
85
- messages = []
86
- if system:
87
- messages.append({"role": "system", "content": system})
88
- messages.append({"role": "user", "content": prompt})
89
- body = {"model": self.model, "max_tokens": max_tokens, "messages": messages}
90
- async with aiohttp.ClientSession() as session:
91
- async with session.post(f"{self.endpoint}/chat/completions",
92
- json=body, headers=headers) as resp:
93
- data = await resp.json()
94
- return data.get("choices", [{}])[0].get("message", {}).get("content", "")
95
-
96
- async def is_available(self) -> bool:
97
- return bool(self.api_key)
98
-
99
-
100
- class OllamaConnector(LLMConnector):
101
- """Ollama local inference. Zero cost, zero internet."""
102
-
103
- def __init__(self, model: str = "llama3.2", host: str = "http://localhost:11434") -> None:
104
- self.model = model
105
- self.host = host
106
-
107
- async def complete(self, prompt: str, system: str = "", max_tokens: int = 1000) -> str:
108
- import aiohttp
109
- body: dict[str, Any] = {
110
- "model": self.model,
111
- "prompt": prompt,
112
- "stream": False,
113
- }
114
- if system:
115
- body["system"] = system
116
- async with aiohttp.ClientSession() as session:
117
- async with session.post(f"{self.host}/api/generate", json=body) as resp:
118
- data = await resp.json()
119
- return data.get("response", "")
120
-
121
- async def is_available(self) -> bool:
122
- try:
123
- import aiohttp
124
- async with aiohttp.ClientSession() as session:
125
- async with session.get(f"{self.host}/api/tags", timeout=aiohttp.ClientTimeout(total=3)) as resp:
126
- return resp.status == 200
127
- except Exception:
128
- return False
129
-
130
-
131
- class HuggingFaceConnector(LLMConnector):
132
- """Run any HuggingFace model locally. Zero cloud."""
133
-
134
- def __init__(self, model: str = "mistralai/Mistral-7B-Instruct-v0.3") -> None:
135
- self.model_name = model
136
- self._pipeline: Any = None
137
-
138
- async def complete(self, prompt: str, system: str = "", max_tokens: int = 1000) -> str:
139
- if self._pipeline is None:
140
- self._load()
141
- full_prompt = f"{system}
142
-
143
- {prompt}" if system else prompt
144
- result = self._pipeline(full_prompt, max_new_tokens=max_tokens, do_sample=True, temperature=0.7)
145
- return result[0]["generated_text"][len(full_prompt):]
146
-
147
- async def is_available(self) -> bool:
148
- try:
149
- import transformers # noqa: F401
150
- return True
151
- except ImportError:
152
- return False
153
-
154
- def _load(self) -> None:
155
- from transformers import pipeline
156
- log.info("llm.loading model=%s (this may take a while...)", self.model_name)
157
- self._pipeline = pipeline("text-generation", model=self.model_name, device_map="auto")
158
- log.info("llm.loaded model=%s", self.model_name)
159
-
160
-
161
- # ─── Factory ────────────────────────────────────────
162
-
163
- def create_connector(provider: str, **kwargs: Any) -> LLMConnector:
164
- """Create an LLM connector by name."""
165
- connectors: dict[str, type[LLMConnector]] = {
166
- "claude": ClaudeConnector,
167
- "anthropic": ClaudeConnector,
168
- "openai": OpenAIConnector,
169
- "gpt": OpenAIConnector,
170
- "ollama": OllamaConnector,
171
- "huggingface": HuggingFaceConnector,
172
- "hf": HuggingFaceConnector,
173
- "vllm": OpenAIConnector, # vLLM is OpenAI-compatible
174
- "litellm": OpenAIConnector,
175
- }
176
- cls = connectors.get(provider.lower())
177
- if cls is None:
178
- raise ValueError(f"Unknown LLM provider: {provider}. Available: {list(connectors.keys())}")
179
- return cls(**kwargs)
180
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes