diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 55a1350..67ab0b2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,71 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Cannot access an offset on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Cannot access offset ''completion_tokens'' on Prism\\Prism\\ValueObjects\\Usage\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Cannot access offset ''message'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Cannot access offset ''prompt_tokens'' on Prism\\Prism\\ValueObjects\\Usage\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Cannot access offset ''total_tokens'' on Prism\\Prism\\ValueObjects\\Usage\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Cannot access offset ''user'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Cannot cast Prism\\Prism\\Text\\Response to string\.$#' - identifier: cast.string - count: 2 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Method Kirschbaum\\Loop\\Http\\Controllers\\LoopController\:\:getStoredMessages\(\) should return array\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Method Kirschbaum\\Loop\\Http\\Controllers\\LoopController\:\:storeMessageInCache\(\) has parameter \$message with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: src/Http/Controllers/LoopController.php - - - - message: '#^Cannot access offset ''message'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 2 - path: src/Loop.php - - - - message: '#^Cannot access offset ''user'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Loop.php - - message: '#^Cannot call method build\(\) on Kirschbaum\\Loop\\Contracts\\Tool\|null\.$#' identifier: method.nonObject @@ -78,24 +12,6 @@ parameters: count: 1 path: src/Loop.php - - - message: '#^Parameter \#1 \$content of class Prism\\Prism\\ValueObjects\\Messages\\AssistantMessage constructor expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Loop.php - - - - message: '#^Parameter \#1 \$content of class Prism\\Prism\\ValueObjects\\Messages\\UserMessage constructor expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Loop.php - - - - message: '#^Parameter \#1 \$tools of method Prism\\Prism\\Text\\PendingRequest\:\:withTools\(\) expects array\, Illuminate\\Support\\Collection given\.$#' - identifier: argument.type - count: 1 - path: src/Loop.php - - message: '#^Instanceof between Prism\\Prism\\Tool and Prism\\Prism\\Tool will always evaluate to true\.$#' identifier: instanceof.alwaysTrue diff --git a/src/Http/Controllers/LoopController.php b/src/Http/Controllers/LoopController.php deleted file mode 100644 index db9eaf3..0000000 --- a/src/Http/Controllers/LoopController.php +++ /dev/null @@ -1,145 +0,0 @@ -loop = $loop; - } - - /** - * Ask the AI a question - */ - public function ask(AskRequest $request): JsonResponse - { - info('Request received', $request->all()); - - /** @var array $messages */ - $messages = $request->input('messages'); - - // If no messages are provided, use the stored messages - if (empty($messages)) { - $messages = $this->getStoredMessages(); - } - - $response = $this->loop->ask( - $request->string('message'), - collect($messages) - ); - - // Store the user question and AI response - $this->storeMessageInCache([ - 'user' => 'User', - 'message' => $request->input('message'), - ]); - - $this->storeMessageInCache([ - 'user' => 'AI', - 'message' => (string) $response, - ]); - - // Format response according to MCP specification - return response()->json([ - 'id' => now()->timestamp, - 'created_at' => now()->toIso8601String(), - 'message' => [ - 'role' => 'assistant', - 'content' => (string) $response, - ], - 'model' => $response->provider ?? config('loop.default_model', 'gpt-4o-mini'), - 'usage' => [ - 'prompt_tokens' => $response->usage['prompt_tokens'] ?? 0, - 'completion_tokens' => $response->usage['completion_tokens'] ?? 0, - 'total_tokens' => $response->usage['total_tokens'] ?? 0, - ], - ]); - } - - /** - * Store a new message in the conversation - */ - public function storeMessage(Request $request): JsonResponse - { - // Validate the request - $request->validate([ - 'message' => 'required|string', - 'role' => 'required|string|in:user,assistant,system', - ]); - - $message = [ - 'user' => $request->input('role') === 'user' ? 'User' : 'AI', - 'message' => $request->input('message'), - ]; - - // Store the message - $this->storeMessageInCache($message); - - return response()->json([ - 'success' => true, - 'message' => 'Message stored successfully', - ]); - } - - /** - * Get all messages in the conversation - */ - public function getMessages(): JsonResponse - { - $messages = $this->getStoredMessages(); - - return response()->json([ - 'messages' => collect($messages)->map(function ($message) { - return [ - 'role' => $message['user'] === 'User' ? 'user' : 'assistant', - 'content' => $message['message'], - ]; - }), - ]); - } - - /** - * Clear all messages in the conversation - */ - public function clearMessages(): JsonResponse - { - Cache::forget($this->cacheKey); - - return response()->json([ - 'success' => true, - 'message' => 'Messages cleared successfully', - ]); - } - - /** - * Store a message in the cache - */ - protected function storeMessageInCache(array $message): void - { - $messages = Cache::get($this->cacheKey, []); - $messages[] = $message; - Cache::put($this->cacheKey, $messages, 3600); // Store for 1 hour - } - - /** - * Get stored messages from cache - * - * @return array - */ - protected function getStoredMessages(): array - { - return Cache::get($this->cacheKey, []); - } -} diff --git a/src/Loop.php b/src/Loop.php index 50ccb78..e907b39 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -3,15 +3,9 @@ namespace Kirschbaum\Loop; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Auth; use Kirschbaum\Loop\Contracts\Tool; use Kirschbaum\Loop\Contracts\Toolkit; -use Prism\Prism\Enums\Provider; -use Prism\Prism\Prism; -use Prism\Prism\Text\Response; use Prism\Prism\Tool as PrismTool; -use Prism\Prism\ValueObjects\Messages\AssistantMessage; -use Prism\Prism\ValueObjects\Messages\UserMessage; class Loop { @@ -42,53 +36,6 @@ public function toolkit(Toolkit $toolkit): static return $this; } - /** - * @param Collection $messages - */ - public function ask(string $question, Collection $messages): Response - { - $prompt = sprintf( - " - You are a helpful assistant. You will have many tools available to you. You need to give informations about the data and ask the user what you need to give him what he needs. \n\n - Today is %s. Current month is %s. Current day is %s. Database being used is %s. \n\n - When using the tools, always pass all the parameters listed in the tool. If you don't have all the information, ask the user for it. If it's optional, pass null. \n - When a field is tagged with a access_type read, it means that the field is automatically calculated and is not stored in the database. \n - When referencing an ID, try to fetch the resource of that ID from the database and give additional informations about it. \n\n - When giving the final output, please compress the information to the minimum needed to answer the question. No need to explain what math you did unless explicitly asked. \n\n - Parameter names in tools never include the $ symbol. \n\n - %s \n\n - You are logged in as %s (User ID: %s) - ", - now()->format('Y-m-d'), - now()->format('F'), - now()->format('d'), - config()->string('database.default'), - $this->context, - Auth::user()?->name, /** @phpstan-ignore property.notFound */ - Auth::user()?->id, - ); - dump($prompt); - - $messages = $messages - ->reject(fn ($message) => empty($message['message'])) - ->map(function ($message) { - return $message['user'] === 'AI' - ? new AssistantMessage($message['message']) - : new UserMessage($message['message']); - })->toArray(); - - $messages[] = new UserMessage($question); - - return Prism::text() - // ->using(Provider::Anthropic, 'claude-3-5-sonnet-latest') - ->using(Provider::OpenAI, 'gpt-4o-mini') - ->withMaxSteps(10) - ->withMessages($messages) - ->withSystemPrompt($prompt) - ->withTools($this->getPrismTools()) - ->asText(); - } - public function getPrismTools(): Collection { return $this->loopTools