Skip to content

Commit eb681bb

Browse files
committed
Add support for custom provider models
1 parent bbc3d3d commit eb681bb

File tree

8 files changed

+88
-43
lines changed

8 files changed

+88
-43
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Support custom LLM providers via config.
6+
57
## 0.4.3
68

79
- Improve context query performance.

docs/configuration.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,16 @@ interface Config {
117117
mcpTimeoutSeconds: number;
118118
mcpServers: {[key: string]: {
119119
command: string;
120-
args: string[];
121-
disabled: boolean;
120+
args?: string[];
121+
disabled?: boolean;
122+
}};
123+
customProviders: {[key: string]: {
124+
api: 'openai' | 'anthropic';
125+
models: string[];
126+
url?: string;
127+
urlEnv?: string;
128+
key?: string;
129+
keyEnv?: string;
122130
}};
123131
ollama?: {
124132
host: string;
@@ -148,6 +156,7 @@ interface Config {
148156
"excludeCommands": []}},
149157
"mcpTimeoutSeconds" : 10,
150158
"mcpServers" : [],
159+
"customProviders": {},
151160
"ollama" : {
152161
"host" : "http://localhost",
153162
"port" : 11434,

src/eca/config.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
:port 11434
2929
:useTools true}
3030
:chat {:welcomeMessage "Welcome to ECA! What you have in mind?\n\n"}
31+
:customProviders {}
3132
:index {:ignoreFiles [{:type :gitignore}]}})
3233

3334
(defn get-env [env] (System/getenv env))

src/eca/db.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"claude-opus-4-0" {:tools true
2121
:web-search true}
2222
"claude-3-5-haiku-latest" {:tools true
23-
:web-search true}} ;; + ollama local models
23+
:web-search true}} ;; + ollama local models + custom provider models
2424
:mcp-clients {}})
2525

2626
(defonce db* (atom initial-db))

src/eca/handlers.clj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@
1111
(set! *warn-on-reflection* true)
1212

1313
(defn ^:private initialize-extra-models! [db* config]
14+
(when-let [custom-providers (seq (:customProviders config))]
15+
(swap! db* update :models merge
16+
(reduce
17+
(fn [models [provider {provider-models :models}]]
18+
(reduce
19+
(fn [m model]
20+
(assoc m
21+
(str (name provider) "/" model)
22+
{:tools true}))
23+
models
24+
provider-models))
25+
{}
26+
custom-providers)))
1427
(when-let [ollama-models (seq (llm-api/extra-models config))]
1528
(swap! db* update :models merge
1629
(reduce

src/eca/llm_api.clj

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,19 @@
2121

2222
(defn ^:private anthropic-api-key [config]
2323
(or (:anthropicApiKey config)
24-
(System/getenv "ANTHROPIC_API_KEY")))
24+
(config/get-env "ANTHROPIC_API_KEY")))
25+
26+
(defn ^:private anthropic-api-url []
27+
(or (System/getenv "ANTHROPIC_API_URL")
28+
llm-providers.anthropic/base-url))
2529

2630
(defn ^:private openai-api-key [config]
27-
(or (:openaiApiKey config)
28-
(System/getenv "OPENAI_API_KEY")))
31+
(or (:openaiapikey config)
32+
(config/get-env "openai_api_key")))
33+
34+
(defn ^:private openai-api-url []
35+
(or (config/get-env "OPENAI_API_URL")
36+
llm-providers.openai/base-url))
2937

3038
(defn default-model
3139
"Returns the default LLM model checking this waterfall:
@@ -68,7 +76,16 @@
6876
(on-error args)))
6977
tools (when (:tools model-config)
7078
(mapv tool->llm-tool tools))
71-
web-search (:web-search model-config)]
79+
web-search (:web-search model-config)
80+
custom-providers (:customProviders config)
81+
custom-models (set (mapcat (fn [[k v]]
82+
(map #(str (name k) "/" %) (:models v)))
83+
custom-providers))
84+
callbacks {:on-message-received on-message-received-wrapper
85+
:on-error on-error-wrapper
86+
:on-prepare-tool-call on-prepare-tool-call-wrapper
87+
:on-tool-called on-tool-called
88+
:on-reason on-reason}]
7289
(cond
7390
(contains? #{"o4-mini"
7491
"o3"
@@ -80,12 +97,9 @@
8097
:past-messages past-messages
8198
:tools tools
8299
:web-search web-search
100+
:api-url (openai-api-url)
83101
:api-key (openai-api-key config)}
84-
{:on-message-received on-message-received-wrapper
85-
:on-error on-error-wrapper
86-
:on-prepare-tool-call on-prepare-tool-call-wrapper
87-
:on-tool-called on-tool-called
88-
:on-reason on-reason})
102+
callbacks)
89103

90104
(contains? #{"claude-sonnet-4-0"
91105
"claude-opus-4-0"
@@ -97,27 +111,39 @@
97111
:past-messages past-messages
98112
:tools tools
99113
:web-search web-search
114+
:api-url (anthropic-api-url)
100115
:api-key (anthropic-api-key config)}
101-
{:on-message-received on-message-received-wrapper
102-
:on-error on-error-wrapper
103-
:on-prepare-tool-call on-prepare-tool-call-wrapper
104-
:on-tool-called on-tool-called
105-
:on-reason on-reason})
116+
callbacks)
106117

107118
(string/starts-with? model config/ollama-model-prefix)
108119
(llm-providers.ollama/completion!
109120
{:host (-> config :ollama :host)
110121
:port (-> config :ollama :port)
111122
:model (string/replace-first model config/ollama-model-prefix "")
112-
:past-messages past-messages
113123
:context context
114-
:tools tools
115-
:user-prompt user-prompt}
116-
{:on-message-received on-message-received-wrapper
117-
:on-error on-error-wrapper
118-
:on-prepare-tool-call on-prepare-tool-call-wrapper
119-
:on-tool-called on-tool-called
120-
:on-reason on-reason})
124+
:user-prompt user-prompt
125+
:past-messages past-messages
126+
:tools tools}
127+
callbacks)
128+
129+
(contains? custom-models model)
130+
(let [[provider model] (string/split model #"/")
131+
provider-config (get custom-providers (keyword provider))
132+
provider-fn (case (:api provider-config)
133+
"openai" llm-providers.openai/completion!
134+
"anthropic" llm-providers.anthropic/completion!
135+
(on-error-wrapper {:msg (format "Unknown custom model %s for provider %s" (:api provider-config) provider)}))
136+
url (or (:url provider-config) (config/get-env (:urlEnv provider-config)))
137+
key (or (:key provider-config) (config/get-env (:keyEnv provider-config)))]
138+
(provider-fn
139+
{:model model
140+
:context context
141+
:user-prompt user-prompt
142+
:past-messages past-messages
143+
:tools tools
144+
:api-url url
145+
:api-key key}
146+
callbacks))
121147

122148
:else
123149
(on-error-wrapper {:msg (str "ECA Unsupported model: " model)}))))

src/eca/llm_providers/anthropic.clj

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,9 @@
1111

1212
(def ^:private logger-tag "[ANTHROPIC]")
1313

14-
(def ^:private anthropic-url "https://api.anthropic.com")
1514
(def ^:private messages-path "/v1/messages")
1615

17-
(defn ^:private url [path]
18-
(format "%s%s"
19-
(or (System/getenv "ANTHROPIC_API_URL")
20-
anthropic-url)
21-
path))
16+
(def base-url "https://api.anthropic.com")
2217

2318
(defn ^:private ->tools [tools web-search]
2419
(cond->
@@ -30,8 +25,8 @@
3025
:max_uses 10
3126
:cache_control {:type "ephemeral"}})))
3227

33-
(defn ^:private base-request! [{:keys [rid body api-key content-block* on-error on-response]}]
34-
(let [url (url messages-path)]
28+
(defn ^:private base-request! [{:keys [rid body api-url api-key content-block* on-error on-response]}]
29+
(let [url (str api-url messages-path)]
3530
(llm-util/log-request logger-tag rid url body)
3631
(http/post
3732
url
@@ -82,7 +77,7 @@
8277

8378
(defn completion!
8479
[{:keys [model user-prompt temperature context max-tokens
85-
api-key past-messages tools web-search]
80+
api-url api-key past-messages tools web-search]
8681
:or {max-tokens 4096
8782
temperature 1.0}}
8883
{:keys [on-message-received on-error on-prepare-tool-call on-tool-called]}]
@@ -137,6 +132,7 @@
137132
(base-request!
138133
{:rid (llm-util/gen-rid)
139134
:body (assoc body :messages messages)
135+
:api-url api-url
140136
:api-key api-key
141137
:content-block* (atom nil)
142138
:on-error on-error
@@ -150,6 +146,7 @@
150146
(base-request!
151147
{:rid (llm-util/gen-rid)
152148
:body body
149+
:api-url api-url
153150
:api-key api-key
154151
:content-block* (atom nil)
155152
:on-error on-error

src/eca/llm_providers/openai.clj

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,12 @@
1010

1111
(def ^:private logger-tag "[OPENAI]")
1212

13-
(def ^:private openai-url "https://api.openai.com")
1413
(def ^:private responses-path "/v1/responses")
1514

16-
(defn ^:private url [path]
17-
(format "%s%s"
18-
(or (System/getenv "OPENAI_API_URL")
19-
openai-url)
20-
path))
15+
(def base-url "https://api.openai.com")
2116

22-
(defn ^:private base-completion-request! [{:keys [rid body api-key on-error on-response]}]
23-
(let [url (url responses-path)]
17+
(defn ^:private base-completion-request! [{:keys [rid body api-url api-key on-error on-response]}]
18+
(let [url (str api-url responses-path)]
2419
(llm-util/log-request logger-tag rid url body)
2520
(http/post
2621
url
@@ -59,7 +54,7 @@
5954
msg))
6055
past-messages))
6156

62-
(defn completion! [{:keys [model user-prompt context temperature api-key past-messages tools web-search]
57+
(defn completion! [{:keys [model user-prompt context temperature api-key api-url past-messages tools web-search]
6358
:or {temperature 1.0}}
6459
{:keys [on-message-received on-error on-prepare-tool-call on-tool-called on-reason]}]
6560
(let [input (conj (past-messages->input past-messages)
@@ -98,6 +93,7 @@
9893
(base-completion-request!
9994
{:rid (llm-util/gen-rid)
10095
:body (assoc body :input input)
96+
:api-url api-url
10197
:api-key api-key
10298
:on-error on-error
10399
:on-response handle-response})
@@ -136,6 +132,7 @@
136132
(base-completion-request!
137133
{:rid (llm-util/gen-rid)
138134
:body body
135+
:api-url api-url
139136
:api-key api-key
140137
:on-error on-error
141138
:on-response on-response-fn})))

0 commit comments

Comments
 (0)