Skip to content

Commit ca74480

Browse files
Merge pull request #50 from lucaromagnoli/feat/anthropic-integration
Feat/anthropic integration
2 parents 0c418eb + e776c5b commit ca74480

20 files changed

+2543
-22
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ name: CI
22

33
on:
44
push:
5-
paths-ignore: # Skip CI for documentation changes
6-
- '**.md'
5+
branches-ignore: # Skip CI for documentation branches
76
- 'docs/**'
8-
- 'LICENSE'
97

108
jobs:
119
build-and-test:

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ set(LLMCPP_SOURCES
6363
src/core/LLMTypes.cpp
6464
src/core/LLMClient.cpp
6565
src/core/JsonSchemaBuilder.cpp
66+
src/core/ClientFactory.cpp
6667
src/providers/ClientManager.cpp
6768
src/providers/ClientFactory.cpp
6869
src/openai/OpenAIClient.cpp
@@ -71,6 +72,8 @@ set(LLMCPP_SOURCES
7172
src/openai/OpenAISchemaBuilder.cpp
7273
src/openai/OpenAIModels.cpp
7374
src/openai/OpenAIUtils.cpp
75+
src/anthropic/AnthropicClient.cpp
76+
src/anthropic/AnthropicHttpClient.cpp
7477
)
7578

7679
# Create library

README.md

Lines changed: 187 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ A modern C++20 library providing a unified interface for Large Language Model AP
1212
## Features
1313

1414
- **🚀 Modern C++20**: Uses latest C++ features and standard library
15-
- **🔄 Multi-provider support**: OpenAI (with more providers coming)
15+
- **🔄 Multi-provider support**: OpenAI, Anthropic Claude
1616
- **⚡ Async requests**: Non-blocking API calls using std::future
1717
- **🔒 Type-safe**: Strong C++ typing with nlohmann/json
1818
- **🎯 Header-only friendly**: Easy integration into any C++ project
1919
- **🌐 Cross-platform**: Works on Linux, macOS, and Windows
2020
- **✅ Production ready**: Full OpenAI Responses API implementation
2121
- **📝 Flexible input**: Support for both simple prompts and structured context
2222
- **🎯 Type-safe models**: Strongly typed Model enum for compile-time safety
23+
- **📊 Performance benchmarks**: Comprehensive model comparison and cost analysis
2324

2425
## Quick Start
2526

@@ -30,7 +31,10 @@ A modern C++20 library providing a unified interface for Large Language Model AP
3031

3132
int main() {
3233
// Create OpenAI client
33-
OpenAIClient client("your-api-key-here");
34+
OpenAIClient client("your-openai-api-key");
35+
36+
// Or create Anthropic client
37+
llmcpp::AnthropicClient anthropicClient("your-anthropic-api-key");
3438

3539
// Method 1: Using Model enum (recommended - type-safe)
3640
auto response = client.sendRequest(OpenAI::Model::GPT_4o_Mini, "Hello! How are you?");
@@ -146,6 +150,187 @@ auto response2 = client.sendRequest(OpenAI::Model::GPT_4_1_Mini, "Summarize this
146150
147151
---
148152

153+
## Anthropic Claude
154+
155+
**llmcpp** now includes full support for [Anthropic's Claude models](https://docs.anthropic.com/en/docs/about-claude/models/overview) via the Messages API.
156+
157+
### Basic Anthropic Usage
158+
159+
```cpp
160+
#include <llmcpp.h>
161+
162+
int main() {
163+
// Create Anthropic client
164+
llmcpp::AnthropicClient client("your-anthropic-api-key");
165+
166+
// Method 1: Using Model enum (recommended)
167+
LLMRequestConfig config;
168+
config.model = Anthropic::toString(Anthropic::Model::CLAUDE_HAIKU_3_5);
169+
config.maxTokens = 100;
170+
171+
LLMRequest request(config, "Write a haiku about programming.");
172+
auto response = client.sendRequest(request);
173+
174+
if (response.success) {
175+
std::cout << "Response: " << response.result["text"].get<std::string>() << std::endl;
176+
std::cout << "Usage: " << response.usage.toString() << std::endl;
177+
}
178+
179+
return 0;
180+
}
181+
```
182+
183+
### Direct Anthropic Messages API
184+
185+
```cpp
186+
// Use the native Anthropic Messages API
187+
Anthropic::MessagesRequest request;
188+
request.model = "claude-3-5-sonnet-20241022";
189+
request.maxTokens = 150;
190+
191+
// Add user message
192+
Anthropic::Message userMsg;
193+
userMsg.role = Anthropic::MessageRole::USER;
194+
userMsg.content.push_back({.type = "text", .text = "Explain quantum computing"});
195+
request.messages.push_back(userMsg);
196+
197+
auto response = client.sendMessagesRequest(request);
198+
199+
for (const auto& content : response.content) {
200+
if (content.type == "text") {
201+
std::cout << content.text << std::endl;
202+
}
203+
}
204+
```
205+
206+
### Available Claude Models
207+
208+
Based on the [official Anthropic documentation](https://docs.anthropic.com/en/docs/about-claude/models/overview):
209+
210+
**Claude 4 series (Latest - 2025):**
211+
- CLAUDE_OPUS_4_1 (claude-opus-4-1-20250805) - Most capable and intelligent
212+
- CLAUDE_OPUS_4 (claude-opus-4-20250514) - Previous flagship model
213+
- CLAUDE_SONNET_4 (claude-sonnet-4-20250514) - High-performance model
214+
215+
**Claude 3.7 series:**
216+
- CLAUDE_SONNET_3_7 (claude-3-7-sonnet-20250219) - High-performance with extended thinking
217+
218+
**Claude 3.5 series:**
219+
- CLAUDE_SONNET_3_5_V2 (claude-3-5-sonnet-20241022) - Latest 3.5 Sonnet (upgraded)
220+
- CLAUDE_SONNET_3_5 (claude-3-5-sonnet-20240620) - Previous 3.5 Sonnet
221+
- CLAUDE_HAIKU_3_5 (claude-3-5-haiku-20241022) - Fastest model
222+
223+
**Claude 3 series (Legacy):**
224+
- CLAUDE_OPUS_3 (claude-3-opus-20240229) - Legacy opus
225+
- CLAUDE_HAIKU_3 (claude-3-haiku-20240307) - Fast and compact legacy model
226+
227+
### Using ClientFactory with Anthropic
228+
229+
```cpp
230+
// Create client via factory
231+
auto client = llmcpp::ClientFactory::createClient("anthropic", "your-api-key");
232+
233+
// Use common LLMRequest interface
234+
LLMRequestConfig config;
235+
config.model = "claude-3-5-haiku-20241022";
236+
config.maxTokens = 100;
237+
238+
LLMRequest request(config, "Hello, Claude!");
239+
auto response = client->sendRequest(request);
240+
```
241+
242+
> **Note:** For the latest Claude model recommendations and capabilities, consult the [Anthropic documentation](https://docs.anthropic.com/en/docs/about-claude/models/overview).
243+
244+
---
245+
246+
## 🚀 Performance Benchmarks
247+
248+
The `llmcpp` library includes comprehensive benchmarks comparing OpenAI and Anthropic models across different tasks. Run benchmarks with:
249+
250+
```bash
251+
# Set environment variables
252+
export OPENAI_API_KEY="your-openai-key"
253+
export ANTHROPIC_API_KEY="your-anthropic-key"
254+
export LLMCPP_RUN_BENCHMARKS=1
255+
256+
# Run unified benchmarks
257+
./tests/llmcpp_tests "[unified][benchmark]"
258+
```
259+
260+
### 🏆 Performance Leaders
261+
262+
Based on real API testing with consistent Responses API usage:
263+
264+
| Category | Winner | Latency | Throughput | Cost-Effectiveness |
265+
|----------|--------|---------|------------|-------------------|
266+
| **Simple Text** | `gpt-4o-mini` | 1.2s | 26.2 tok/s | $0.0021/10K tokens |
267+
| **Structured Output** | `gpt-4o-mini` | 1.2s | 51.7 tok/s | Excellent |
268+
| **Reasoning Tasks** | `gpt-5` | 1.9s | 13.3 tok/s | Premium |
269+
| **Premium Quality** | `claude-opus-4-1` | 2.7s | 25.2 tok/s | $0.225/10K tokens |
270+
271+
### 📊 Detailed Benchmark Results
272+
273+
#### Simple Text Generation
274+
```
275+
Provider Model Latency Tokens/sec Cost-Effectiveness
276+
────────────────────────────────────────────────────────────────────────────
277+
OpenAI gpt-4o-mini 1.22s 26.2 ⭐⭐⭐⭐⭐
278+
Anthropic claude-3-5-sonnet-v2 1.51s 21.8 ⭐⭐⭐⭐
279+
OpenAI gpt-5 1.89s 13.3 ⭐⭐⭐
280+
Anthropic claude-sonnet-4 1.94s 34.5 ⭐⭐⭐
281+
OpenAI gpt-4o 2.25s 16.9 ⭐⭐⭐
282+
Anthropic claude-opus-4-1 2.66s 25.2 ⭐⭐
283+
```
284+
285+
#### Structured JSON Output
286+
```
287+
Provider Model Latency Tokens/sec Schema Validation
288+
─────────────────────────────────────────────────────────────────────────────
289+
OpenAI gpt-4o-mini 1.24s 51.7 ✅ Strict Schema
290+
Anthropic claude-3-5-sonnet-v2 1.35s 28.9 ✅ Natural JSON
291+
OpenAI gpt-4o 1.50s 42.6 ✅ Strict Schema
292+
Anthropic claude-opus-4-1 1.76s 25.0 ✅ Natural JSON
293+
OpenAI gpt-5-mini 1.89s 38.1 ✅ Strict Schema
294+
```
295+
296+
### 💡 Model Selection Guide
297+
298+
**For Cost-Conscious Applications:**
299+
- **Winner:** `gpt-4o-mini` - Excellent performance at $0.0021 per 10K tokens
300+
- **Alternative:** `claude-3-5-haiku` - Fast and affordable at $0.00375 per 10K tokens
301+
302+
**For Balanced Performance:**
303+
- **Winner:** `claude-3-5-sonnet-v2` - Great quality/speed ratio
304+
- **Alternative:** `gpt-4o` - Reliable with good structured output
305+
306+
**For Maximum Capability:**
307+
- **Winner:** `claude-opus-4-1` - Highest reasoning and creative ability
308+
- **Alternative:** `claude-sonnet-4` - Strong performance with good speed
309+
310+
**For Reasoning Tasks:**
311+
- **Winner:** `gpt-5` with reasoning mode - Advanced logical thinking
312+
- **Alternative:** `o3-mini` - Cost-effective reasoning capabilities
313+
314+
### 🔬 Benchmark Methodology
315+
316+
- **Consistent API Usage:** All tests use OpenAI Responses API for standardization
317+
- **Real-World Conditions:** Actual API calls with network latency
318+
- **Multiple Runs:** Results averaged across multiple test executions
319+
- **Task Variety:** Simple text, structured output, and reasoning scenarios
320+
- **Cost Analysis:** Based on current provider pricing (as of 2025)
321+
322+
### ⚡ Quick Performance Tips
323+
324+
1. **For Speed:** Use `gpt-4o-mini` for fastest responses
325+
2. **For Cost:** Choose `claude-3-5-haiku` for budget-friendly options
326+
3. **For Quality:** Select `claude-opus-4-1` when quality matters most
327+
4. **For JSON:** Use OpenAI models with strict schema validation
328+
5. **For Reasoning:** Enable `reasoning: {"effort": "low"}` for reasoning models
329+
330+
> **Note:** Benchmark results may vary based on network conditions, API load, and specific use cases. Run your own benchmarks for mission-critical applications.
331+
332+
---
333+
149334
## CMake Integration
150335

151336
### After install (recommended)

examples/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ target_link_libraries(basic_usage PRIVATE llmcpp)
88
add_executable(async_example async_example.cpp)
99
target_link_libraries(async_example PRIVATE llmcpp)
1010

11+
add_executable(anthropic_example anthropic_example.cpp)
12+
target_link_libraries(anthropic_example PRIVATE llmcpp)
13+
1114
# Install examples (optional)
12-
install(TARGETS basic_usage async_example
15+
install(TARGETS basic_usage async_example anthropic_example
1316
DESTINATION ${CMAKE_INSTALL_BINDIR}/examples
1417
)

examples/anthropic_example.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#include <chrono>
2+
#include <cstdlib>
3+
#include <iostream>
4+
#include <thread>
5+
6+
#include "llmcpp.h"
7+
8+
int main() {
9+
// Get API key from environment
10+
const char* apiKey = std::getenv("ANTHROPIC_API_KEY");
11+
if (!apiKey) {
12+
std::cerr << "Error: ANTHROPIC_API_KEY environment variable not set" << std::endl;
13+
return 1;
14+
}
15+
16+
try {
17+
// Create Anthropic client
18+
llmcpp::AnthropicClient client(apiKey);
19+
20+
std::cout << "=== Anthropic Claude API Example ===" << std::endl;
21+
std::cout << "Using client: " << client.getClientName() << std::endl;
22+
23+
// Example 1: Simple text completion using LLMRequest interface
24+
std::cout << "\n--- Example 1: Simple completion ---" << std::endl;
25+
26+
LLMRequestConfig config;
27+
config.model = "claude-3-5-haiku-20241022"; // Fast and affordable model
28+
config.maxTokens = 100;
29+
config.temperature = 0.7f;
30+
31+
LLMRequest request(config, "Write a haiku about artificial intelligence.");
32+
33+
auto response = client.sendRequest(request);
34+
35+
if (response.success) {
36+
std::cout << "Response: " << response.result["text"].get<std::string>() << std::endl;
37+
std::cout << "Usage: " << response.usage.inputTokens << " input, "
38+
<< response.usage.outputTokens << " output tokens" << std::endl;
39+
} else {
40+
std::cerr << "Error: " << response.errorMessage << std::endl;
41+
}
42+
43+
// Example 2: Using Anthropic-specific API with different models
44+
std::cout << "\n--- Example 2: Direct Anthropic API ---" << std::endl;
45+
46+
Anthropic::MessagesRequest directRequest;
47+
directRequest.model = Anthropic::toString(Anthropic::Model::CLAUDE_SONNET_3_5_V2);
48+
directRequest.maxTokens = 150;
49+
directRequest.temperature = 0.3;
50+
51+
// Add user message
52+
Anthropic::Message userMsg;
53+
userMsg.role = Anthropic::MessageRole::USER;
54+
userMsg.content.push_back(
55+
{.type = "text", .text = "Explain the concept of machine learning in simple terms."});
56+
directRequest.messages.push_back(userMsg);
57+
58+
auto directResponse = client.sendMessagesRequest(directRequest);
59+
60+
std::cout << "Model: " << directResponse.model << std::endl;
61+
std::cout << "Stop reason: " << directResponse.stopReason << std::endl;
62+
63+
for (const auto& content : directResponse.content) {
64+
if (content.type == "text") {
65+
std::cout << "Response: " << content.text << std::endl;
66+
}
67+
}
68+
std::cout << "Usage: " << directResponse.usage.inputTokens << " input, "
69+
<< directResponse.usage.outputTokens << " output tokens" << std::endl;
70+
71+
// Example 3: Show available models
72+
std::cout << "\n--- Example 3: Available models ---" << std::endl;
73+
auto models = client.getAvailableModels();
74+
std::cout << "Available Anthropic models:" << std::endl;
75+
for (const auto& model : models) {
76+
std::cout << " - " << model << std::endl;
77+
}
78+
79+
// Example 4: Using ClientFactory
80+
std::cout << "\n--- Example 4: Using ClientFactory ---" << std::endl;
81+
auto factoryClient = llmcpp::ClientFactory::createClient("anthropic", std::string(apiKey));
82+
if (factoryClient) {
83+
std::cout << "Created client via factory: " << factoryClient->getClientName()
84+
<< std::endl;
85+
86+
LLMRequestConfig simpleConfig;
87+
simpleConfig.model = "claude-3-5-haiku-20241022";
88+
simpleConfig.maxTokens = 50;
89+
90+
LLMRequest simpleRequest(simpleConfig, "Say hello in French.");
91+
92+
// Use async version with callback
93+
LLMResponse simpleResponse;
94+
bool responseReceived = false;
95+
96+
factoryClient->sendRequest(simpleRequest, [&](const LLMResponse& response) {
97+
simpleResponse = response;
98+
responseReceived = true;
99+
});
100+
101+
// Wait for response (simple busy wait for demo)
102+
while (!responseReceived) {
103+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
104+
}
105+
106+
if (simpleResponse.success) {
107+
std::cout << "Factory client response: "
108+
<< simpleResponse.result["text"].get<std::string>() << std::endl;
109+
}
110+
}
111+
112+
} catch (const std::exception& e) {
113+
std::cerr << "Exception: " << e.what() << std::endl;
114+
return 1;
115+
}
116+
117+
return 0;
118+
}

0 commit comments

Comments
 (0)