New anthropic claude service to replace openai chatgpt

This commit is contained in:
Dillon Ngo
2026-01-25 15:31:00 +08:00
parent 9a1b33a413
commit 7be3614f96
5 changed files with 232 additions and 14 deletions
@@ -0,0 +1,66 @@
<?php
namespace App\Classes\Modules\Anthropic\Services;
use Illuminate\Support\Facades\Http;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Exceptions\ConnectionErrorException;
use Illuminate\Support\Facades\Log;
class CreatesClaudeResponse
{
/**
* @param string $userPrompt
* @param string $model
* @return null|object
* @throws MalformedRequestException
*/
public function execute(string $userPrompt, string $model = "") {
try {
$content = [];
$content[] = [
'type' => 'text',
'text' => $userPrompt,
];
$response = Http::withHeaders([
'x-api-key' => config('anthropic.api_key'),
'anthropic-version' => config('anthropic.api_version', '2023-06-01'),
'Content-Type' => 'application/json',
])->post(config('anthropic.base_url') . '/v1/messages', [
'model' => config('anthropic.default_model', 'claude-sonnet-4-5-20250929'),
'max_tokens' => 8192,
'messages' => [
[
'role' => 'user',
'content' => $content,
],
],
]);
if ($response->successful()) {
return $response->json();
} else {
Log::info('CreatesClaudeResponse error: ' . $response->body());
return null;
}
}
catch (\Illuminate\Http\Client\ConnectionException $exception) {
Log::info('ConnectionException' . json_encode($exception));
throw new ConnectionErrorException(
'Failed to connect to Claude API',
$exception->getMessage(),
$userPrompt,
$exception->getTraceAsString()
);
}
catch (\Exception $exception) {
Log::info('Exception' . json_encode($exception));
throw new MalformedRequestException(
'Unable to get correct response from Claude API: ' . $exception->getMessage()
);
}
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Classes\Modules\Anthropic\Services;
use Illuminate\Support\Facades\Http;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Exceptions\ConnectionErrorException;
class CreatesClaudeResponseWithFiles
{
public function execute(string $userPrompt, array $fileIds)
{
try {
$content = [];
// User prompt
$content[] = [
'type' => 'text',
'text' => $userPrompt,
];
// Attach files
foreach ($fileIds as $fileId) {
$content[] = [
'type' => 'document',
'source' => [
'type' => 'file',
'file_id' => $fileId,
],
];
}
$response = Http::withHeaders([
'x-api-key' => config('anthropic.api_key'),
'anthropic-version' => config('anthropic.api_version', '2023-06-01'),
'anthropic-beta' => 'files-api-2025-04-14',
'Content-Type' => 'application/json',
])->post(config('anthropic.base_url') . '/v1/messages', [
'model' => config('anthropic.default_model', 'claude-sonnet-4-5-20250929'),
'max_tokens' => 16384,
'messages' => [
[
'role' => 'user',
'content' => $content,
],
],
]);
return $response->json();
}
catch (\Illuminate\Http\Client\ConnectionException $exception) {
$error = 'Failed to connect to Claude API';
throw new ConnectionErrorException(
$error,
$exception->getMessage(),
$userPrompt,
$exception->getTraceAsString()
);
}
catch (\Exception $exception) {
throw new MalformedRequestException(
'Unable to get correct response from Claude API: ' . $exception->getMessage()
);
}
}
}
@@ -0,0 +1,57 @@
<?php
namespace App\Classes\Modules\Anthropic\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Exceptions\ConnectionErrorException;
class UploadsClaudeFiles
{
public function execute(array $paths): array
{
try {
$ids = [];
foreach ($paths as $path) {
$response = Http::withHeaders([
'x-api-key' => config('anthropic.api_key'),
'anthropic-version' => '2023-06-01',
'anthropic-beta' => 'files-api-2025-04-14',
])->attach(
'file',
fopen($path, 'r'),
basename($path)
)->post(config('anthropic.base_url') . '/v1/files');
$data = $response->json();
if ($response->successful() && isset($data['id'])) {
$ids[] = $data['id'];
} else {
Log::error('Claude file upload failed', [
'path' => $path,
'response' => $data,
]);
}
}
return $ids;
}
catch (\Illuminate\Http\Client\ConnectionException $exception) {
$error = 'Failed to connect to Claude API';
throw new ConnectionErrorException(
$error,
$exception->getMessage(),
$exception->getTraceAsString()
);
}
catch (\Exception $exception) {
throw new MalformedRequestException(
'Unable to get correct response from Claude API: ' . $exception->getMessage()
);
}
}
}
@@ -3,6 +3,7 @@
namespace App\Classes\Modules\PackingLists\Processors\V2;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Modules\Anthropic\Services\CreatesClaudeResponse;
use App\Classes\Modules\Orders\Services\FetchesDataFromYDPortal;
use App\Classes\Modules\OpenAI\Services\CreatesChatGPTResponse;
use App\Models\PackingList;
@@ -19,14 +20,19 @@ class FetchByTrakingNoYdPortalV2Processor
/** @var CreatesChatGPTResponse */
private $createsChatGPTResponse;
/** @var CreatesClaudeResponse */
private $createsClaudeResponse;
/**
* @param FetchesDataFromYDPortal $fetchesDataFRomYDPortal
* @param CreatesChatGPTResponse $createsChatGPTResponse
* @param CreatesClaudeResponse $createsClaudeResponse
*/
public function __construct(FetchesDataFromYDPortal $fetchesDataFRomYDPortal, CreatesChatGPTResponse $createsChatGPTResponse)
public function __construct(FetchesDataFromYDPortal $fetchesDataFRomYDPortal, CreatesChatGPTResponse $createsChatGPTResponse, CreatesClaudeResponse $createsClaudeResponse)
{
$this->fetchesDataFRomYDPortal = $fetchesDataFRomYDPortal;
$this->createsChatGPTResponse = $createsChatGPTResponse;
$this->createsClaudeResponse = $createsClaudeResponse;
}
@@ -129,9 +135,9 @@ class FetchByTrakingNoYdPortalV2Processor
else{
$isUpdated = false;
if($item['tracking']){
$isTranslatedPreviouly = $this->checkIfTranslationAlreadyExist(!empty($item['tracking']) ? $item['tracking'] : "");
$result = $isTranslatedPreviouly;
if (!$isTranslatedPreviouly){
$isTranslatedPreviously = $this->checkIfTranslationAlreadyExist(!empty($item['tracking']) ? $item['tracking'] : "");
$result = $isTranslatedPreviously;
if (!$isTranslatedPreviously){
$result = $this->translateTracking($item['tracking']);
}
if ($result && $existingRecord->tracking_english !== $result) {
@@ -143,9 +149,9 @@ class FetchByTrakingNoYdPortalV2Processor
}
if($item['remark']) {
$isTranslatedPreviouly = $this->checkIfTranslationAlreadyExist(!empty($item['remark']) ? $item['remark'] : "");
$result = $isTranslatedPreviouly;
if (!$isTranslatedPreviouly){
$isTranslatedPreviously = $this->checkIfTranslationAlreadyExist(!empty($item['remark']) ? $item['remark'] : "");
$result = $isTranslatedPreviously;
if (!$isTranslatedPreviously){
$result = $this->translateTracking($item['remark']);
}
@@ -189,7 +195,7 @@ class FetchByTrakingNoYdPortalV2Processor
$trackingDate = Carbon::parse($trackingtime);
$trackingYear = $trackingDate->year;
if($remark && preg_match('/\b\d{1,2}[-\/.]\d{1,2}\b/', $remark) && $eta === null && $etd === null){
if($remark && $eta === null && $etd === null){
$existingRecord = YDOrderTracking::where('remark', $remark)->where(function ($query) {
$query->whereNotNull('eta')
->orWhereNotNull('etd');
@@ -203,14 +209,27 @@ class FetchByTrakingNoYdPortalV2Processor
];
}
$userPrompt = "Show me the answer in json string without escape, e.g. {'ETA': '', 'ETD': ''}. ".$remark." What is the ETA and ETD?";
$userPrompt = "You must respond with raw JSON only.
No explanations.
No markdown.
No code fences.
No comments.
Only this JSON structure:
{\"ETA\":\"\", \"ETD\":\"\"}
Dates must be strings in YYYY-MM-DD format.
{$remark}
What are the ETA and ETD?";
Log::info('prompt: '.$userPrompt);
$result = $this->createsChatGPTResponse->execute($userPrompt);
$result = $this->createsClaudeResponse->execute($userPrompt);
Log::info('result (json_encoded): '.json_encode($result));
$content = $result['choices'][0]['message']['content'] ?? null;
// $content = $result['choices'][0]['message']['content'] ?? null;
$content = $result['content'][0]['text'] ?? '[]';
Log::info('content (json_encoded):'. json_encode($content));
if ($content) {
$parsedContent = json_decode(str_replace("'", '"', $content), true);
$parsedContent = json_decode($content, true);
// $parsedContent = json_decode(str_replace("'", '"', $content), true);
if (is_array($parsedContent) && isset($parsedContent['ETA'], $parsedContent['ETD'])) {
Log::info('parsedContent ETA:'. $parsedContent['ETA']);
Log::info('parsedContent ETD:'. $parsedContent['ETD']);
@@ -310,9 +329,10 @@ class FetchByTrakingNoYdPortalV2Processor
{
$userPrompt = "Please translate mandarin to english and return result in this format, e.g. {'mandarin': '.$textToTranslate.', 'english': ''}. ".$textToTranslate." ";
Log::info('prompt: '.$userPrompt);
$result = $this->createsChatGPTResponse->execute($userPrompt);
$result = $this->createsClaudeResponse->execute($userPrompt);
Log::info('result (json_encoded): '.json_encode($result));
$content = $result['choices'][0]['message']['content'] ?? null;
// $content = $result['choices'][0]['message']['content'] ?? null;
$content = $result['content'][0]['text'] ?? '[]';
Log::info('content (json_encoded):'. json_encode($content));
$parsedContent = json_decode(str_replace("'", '"', $content), true);
+8
View File
@@ -0,0 +1,8 @@
<?php
return [
'base_url' => env('ANTHROPIC_BASE_URL', 'https://api.anthropic.com'),
'api_key' => env('ANTHROPIC_API_KEY', ''),
'api_version' => env('ANTHROPIC_API_VERSION', '2023-06-01'),
// 'is_enabled' => env('OPENAI_IS_ENABLED', 'true'),
];