Skip to content

Commit 81b055a

Browse files
committed
openAI API support implemented
1 parent 135f81a commit 81b055a

9 files changed

Lines changed: 648 additions & 14 deletions

File tree

README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,59 @@ anthropic.api.key=sk-ant-...
7575
anthropic.version=2023-06-01
7676
```
7777

78-
The default model is `claude-opus-4-6`. Override via system property:
78+
### LLM Provider
79+
80+
zsmith ships with two clients, selected at runtime via `llm.provider`:
81+
82+
```properties
83+
llm.provider=claude # default — Anthropic Messages API
84+
# llm.provider=openai # OpenAI Chat Completions API
85+
```
86+
87+
Agent code is unchanged either way — request and response are translated internally so the Agent loop only ever sees Anthropic-shaped content blocks.
88+
89+
#### Claude endpoint
90+
91+
By default, requests go to `https://api.anthropic.com/v1/messages`. To point at a local Anthropic-compatible endpoint:
92+
93+
```properties
94+
claude.scheme=http
95+
claude.host=localhost
96+
claude.port=8080
97+
```
98+
99+
`claude.port` is optional — omit it to use the scheme default. `claude.scheme` defaults to `https`, `claude.host` to `api.anthropic.com`. The path `/v1/messages` is fixed.
100+
101+
#### OpenAI endpoint
102+
103+
By default, requests go to `https://api.openai.com/v1/chat/completions`. Configurable knobs:
104+
105+
```properties
106+
openai.api.key=sk-... # optional — omitted Authorization header when blank (useful for local servers)
107+
openai.model=gpt-4o # default
108+
openai.max.tokens=4096 # default
109+
openai.scheme=https # default
110+
openai.host=api.openai.com # default
111+
openai.port= # default unset (uses scheme default port)
112+
```
113+
114+
The OpenAI client has no fallback model — unlike Claude's 529→fallback retry, OpenAI errors propagate directly.
115+
116+
To point at a local Ollama server:
117+
118+
```properties
119+
llm.provider=openai
120+
openai.host=localhost
121+
openai.port=11434
122+
openai.scheme=http
123+
openai.model=llama3.1
124+
```
125+
126+
LM Studio (default port 1234), llama.cpp `--api`, and vLLM expose the same Chat Completions shape and work identically.
127+
128+
### Model
129+
130+
The default Claude model is `claude-opus-4-6`. Override via system property:
79131

80132
```bash
81133
java -Dmodel=sonnet -cp zbo/zsmith.jar MyAgent.java

version.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2026.05.20.01

zsmith/src/main/java/airhacks/zsmith/agent/boundary/Agent.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import airhacks.zsmith.agent.control.Version;
1717
import airhacks.zsmith.agent.entity.AgentTurnEvent;
18-
import airhacks.zsmith.claude.control.Claude;
18+
import airhacks.zsmith.llm.control.LLM;
1919
import airhacks.zsmith.configuration.control.ZCfg;
2020
import airhacks.zsmith.episodicmemory.boundary.EpisodicMemoryStore;
2121
import airhacks.zsmith.episodicmemory.control.RecallMemoryTool;
@@ -273,12 +273,12 @@ public String chat(String userMessage) {
273273
turnEvent.begin();
274274
try {
275275
progress.update(iteration + 1);
276-
var response = Claude.invoke(
276+
var response = LLM.invoke(
277277
this.systemPrompt,
278278
this.memory.toJSON(),
279279
toolDefinitions(),
280280
this.temperature);
281-
progress.addClaudeInvocation();
281+
progress.addLLMInvocation();
282282

283283
var content = response.getJSONArray("content");
284284
var stopReason = response.optString("stop_reason", "end_turn");

zsmith/src/main/java/airhacks/zsmith/claude/control/Claude.java

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

2222
public interface Claude {
2323

24-
String ANTHROPIC_VERSION = ZCfg.requiredString("anthropic.version");
25-
String ANTHROPIC_API_KEY = ZCfg.requiredString("anthropic.api.key");
2624
Models defaultModel = Models.CLAUDE_47_OPUS;
2725
String fallbackModelName = "claude-sonnet-4-6";
2826

27+
static String apiKey() {
28+
return ZCfg.requiredString("anthropic.api.key");
29+
}
30+
31+
static String apiVersion() {
32+
return ZCfg.requiredString("anthropic.version");
33+
}
34+
2935
enum Models {
3036
CLAUDE_47_OPUS("claude-opus-4-7", Claude.fallbackModelName, false, true, true, 32_000),
3137
CLAUDE_46_OPUS("claude-opus-4-6", Claude.fallbackModelName, true, true, true, 32_000),
@@ -96,9 +102,17 @@ public static Optional<Models> fromPartialMatch(String partialName) {
96102
}
97103

98104
HttpClient client = HttpClient.newHttpClient();
99-
URI uri = URI.create("https://api.anthropic.com/v1/messages");
105+
URI uri = endpoint();
100106
Models currentModel = Models.fromSystemProperty();
101107

108+
static URI endpoint() {
109+
var scheme = ZCfg.string("claude.scheme", "https");
110+
var host = ZCfg.string("claude.host", "api.anthropic.com");
111+
var port = ZCfg.integer("claude.port", -1);
112+
var authority = port > 0 ? host + ":" + port : host;
113+
return URI.create("%s://%s/v1/messages".formatted(scheme, authority));
114+
}
115+
102116
static JSONObject invoke(String system, JSONArray messages, JSONArray tools, float temperature) {
103117
var payloadJSON = claudeMessage(messages, temperature, system);
104118
payloadJSON.put("model", currentModel.modelName());
@@ -249,9 +263,9 @@ static String replaceModel(String message, String originalModel, String fallback
249263
static HttpResponse<String> send(String message) {
250264
var request = HttpRequest.newBuilder(uri)
251265
.POST(BodyPublishers.ofString(message))
252-
.header("x-api-key", ANTHROPIC_API_KEY)
266+
.header("x-api-key", apiKey())
253267
.header("content-type", "application/json")
254-
.header("anthropic-version", ANTHROPIC_VERSION)
268+
.header("anthropic-version", apiVersion())
255269
.build();
256270
try {
257271
return client.send(request, BodyHandlers.ofString());
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package airhacks.zsmith.llm.control;
2+
3+
import org.json.JSONArray;
4+
import org.json.JSONObject;
5+
6+
import airhacks.zsmith.claude.control.Claude;
7+
import airhacks.zsmith.configuration.control.ZCfg;
8+
import airhacks.zsmith.openai.control.OpenAI;
9+
10+
public interface LLM {
11+
12+
static String provider() {
13+
return ZCfg.string("llm.provider", "claude");
14+
}
15+
16+
static JSONObject invoke(String system, JSONArray messages, JSONArray tools, float temperature) {
17+
return switch (provider()) {
18+
case "openai" -> OpenAI.invoke(system, messages, tools, temperature);
19+
case "claude" -> Claude.invoke(system, messages, tools, temperature);
20+
default -> throw new IllegalStateException(
21+
"unknown llm.provider '%s' — expected 'claude' or 'openai'".formatted(provider()));
22+
};
23+
}
24+
}

zsmith/src/main/java/airhacks/zsmith/logging/control/ProgressBar.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ public class ProgressBar {
88
static final String RESET = "\u001B[0m";
99

1010
private final int maxIterations;
11-
private int claudeInvocations;
11+
private int llmInvocations;
1212
private int toolInvocations;
1313

1414
public ProgressBar(int maxIterations) {
1515
this.maxIterations = maxIterations;
1616
}
1717

18-
public void addClaudeInvocation() {
19-
this.claudeInvocations++;
18+
public void addLLMInvocation() {
19+
this.llmInvocations++;
2020
}
2121

2222
public void addToolInvocations(int count) {
@@ -40,7 +40,7 @@ String render(int iteration) {
4040
+ violet + EMPTY.repeat(empty) + RESET
4141
+ blue + "]" + RESET
4242
+ " " + iteration + "/" + maxIterations
43-
+ " " + cyan + "claude: " + claudeInvocations + RESET
43+
+ " " + cyan + "llm: " + llmInvocations + RESET
4444
+ " " + magenta + "tools: " + toolInvocations + RESET;
4545
}
4646

@@ -49,7 +49,7 @@ public void summary() {
4949
var magenta = Log.Color.MAGENTA.code;
5050
var green = Log.Color.GREEN.code;
5151
System.out.println(green + "--- summary ---" + RESET
52-
+ " " + cyan + "claude: " + claudeInvocations + RESET
52+
+ " " + cyan + "llm: " + llmInvocations + RESET
5353
+ " " + magenta + "tools: " + toolInvocations + RESET);
5454
}
5555
}

0 commit comments

Comments
 (0)