Skip to content

Commit 468cf35

Browse files
author
ityaozm@gmail.com
committed
feat(Kimi): Add chatCompletions method to Kimi class
- Added chatCompletions method to Kimi class - Implemented logic for handling chat completions - Included validation for parameters - Handled response and error scenarios
1 parent dc5807f commit 468cf35

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed

app/Support/Kimi.php

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the guanguans/ai-commit.
7+
*
8+
* (c) guanguans <ityaozm@gmail.com>
9+
*
10+
* This source file is subject to the MIT license that is bundled.
11+
*/
12+
13+
namespace App\Support;
14+
15+
use GuzzleHttp\Middleware;
16+
use GuzzleHttp\Psr7\Utils;
17+
use Illuminate\Contracts\Container\BindingResolutionException;
18+
use Illuminate\Http\Client\PendingRequest;
19+
use Illuminate\Http\Client\RequestException;
20+
use Illuminate\Http\Client\Response;
21+
use Psr\Http\Message\ResponseInterface;
22+
23+
/**
24+
* @see https://platform.moonshot.cn/docs/api-reference
25+
*/
26+
final class Kimi extends FoundationSDK
27+
{
28+
/**
29+
* ```ok
30+
* {
31+
* 'id': 'chatcmpl-6pqDoRwRGQAlRvJnesR9QMG9rxpyK',
32+
* 'object': 'chat.completion',
33+
* 'created': 1677813488,
34+
* 'model': 'gpt-3.5-turbo-0301',
35+
* 'usage': {
36+
* 'prompt_tokens': 8,
37+
* 'completion_tokens': 16,
38+
* 'total_tokens': 24
39+
* },
40+
* 'choices': [
41+
* {
42+
* 'delta': {
43+
* 'role': 'assistant',
44+
* 'content': 'PHP (Hypertext Preprocessor) is a server-side scripting language used'
45+
* },
46+
* 'finish_reason': 'length',
47+
* 'index': 0
48+
* }
49+
* ]
50+
* }
51+
* ```.
52+
*
53+
* ```stream ok
54+
* data: {"id":"chatcmpl-6pqQB0NVBCjNcs6aPeFUi4gy1pCoj","object":"chat.completion.chunk","created":1677814255,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"content":" used"},"index":0,"finish_reason":null}]}
55+
*
56+
* data: {"id":"chatcmpl-6pqQB0NVBCjNcs6aPeFUi4gy1pCoj","object":"chat.completion.chunk","created":1677814255,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{},"index":0,"finish_reason":"length"}]}
57+
*
58+
* data: [DONE]
59+
* ```
60+
*
61+
* @psalm-suppress UnusedVariable
62+
* @psalm-suppress UnevaluatedCode
63+
*
64+
* @throws BindingResolutionException
65+
* @throws RequestException
66+
*/
67+
public function chatCompletions(array $parameters, ?callable $writer = null): Response
68+
{
69+
$response = $this
70+
->cloneDefaultPendingRequest()
71+
->when(
72+
($parameters['stream'] ?? false) && \is_callable($writer),
73+
static function (PendingRequest $pendingRequest) use (&$rowData, $writer): PendingRequest {
74+
return $pendingRequest->withOptions([
75+
'curl' => [
76+
CURLOPT_WRITEFUNCTION => static function ($ch, string $data) use (&$rowData, $writer): int {
77+
// $sanitizeData = self::sanitizeData($data);
78+
// if (! str($data)->startsWith('data: [DONE]')) {
79+
// $rowData = $sanitizeData;
80+
// }
81+
82+
$rowData .= $data;
83+
84+
$writer($data, $ch);
85+
86+
return \strlen($data);
87+
},
88+
],
89+
]);
90+
}
91+
)
92+
// ->withMiddleware(
93+
// Middleware::mapResponse(static function (ResponseInterface $response): ResponseInterface {
94+
// $contents = $response->getBody()->getContents();
95+
//
96+
// // $parameters['stream'] === true && $writer === null
97+
// if ($contents && ! \str($contents)->jsonValidate()) {
98+
// $data = \str($contents)
99+
// ->explode("\n\n")
100+
// ->reverse()
101+
// ->skip(2)
102+
// ->reverse()
103+
// ->map(static function (string $rowData): array {
104+
// return json_decode(self::sanitizeData($rowData), true);
105+
// })
106+
// ->reduce(static function (array $data, array $rowData): array {
107+
// if (empty($data)) {
108+
// return $rowData;
109+
// }
110+
//
111+
// foreach ($data['choices'] as $index => $choice) {
112+
// $data['choices'][$index]['text'] .= $rowData['choices'][$index]['text'];
113+
// }
114+
//
115+
// return $data;
116+
// }, []);
117+
//
118+
// return $response->withBody(Utils::streamFor(json_encode($data)));
119+
// }
120+
//
121+
// return $response;
122+
// })
123+
// )
124+
->post('chat/completions', validate($parameters, [
125+
'model' => [
126+
'required',
127+
'string',
128+
'in:moonshot-v1-8k,moonshot-v1-32k,moonshot-v1-128k',
129+
],
130+
'messages' => 'required|array',
131+
'temperature' => 'numeric|between:0,2',
132+
'top_p' => 'numeric|between:0,1',
133+
'n' => 'integer|min:1',
134+
'stream' => 'bool',
135+
// 'stop' => 'nullable|string|array',
136+
'stop' => 'nullable|string',
137+
'max_tokens' => 'integer',
138+
'presence_penalty' => 'numeric|between:-2,2',
139+
'frequency_penalty' => 'numeric|between:-2,2',
140+
'logit_bias' => 'array', // map
141+
'user' => 'string|uuid',
142+
]))
143+
// ->onError(function (Response $response) use ($rowData) {
144+
// if ($rowData && empty($response->body())) {
145+
// (function (Response $response) use ($rowData): void {
146+
// $this->response = $response->toPsrResponse()->withBody(
147+
// Utils::streamFor(OpenAI::sanitizeData($rowData))
148+
// );
149+
// })->call($response, $response);
150+
// }
151+
// })
152+
;
153+
154+
if ($rowData && empty($response->body())) {
155+
$response = new Response($response->toPsrResponse()->withBody(Utils::streamFor(($rowData))));
156+
}
157+
158+
return $response->throw();
159+
}
160+
161+
/**
162+
* @throws RequestException
163+
*/
164+
public function models(): Response
165+
{
166+
return $this->cloneDefaultPendingRequest()->get('models')->throw();
167+
}
168+
169+
/**
170+
* {@inheritDoc}
171+
*
172+
* @throws BindingResolutionException
173+
*/
174+
protected function validateConfig(array $config): array
175+
{
176+
return array_replace_recursive(
177+
[
178+
'http_options' => [
179+
// \GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 30,
180+
// \GuzzleHttp\RequestOptions::TIMEOUT => 180,
181+
],
182+
'retry' => [
183+
// 'times' => 1,
184+
// 'sleep' => 1000,
185+
// 'when' => static function (\Exception $exception): bool {
186+
// return $exception instanceof \Illuminate\Http\Client\ConnectionException;
187+
// },
188+
// // 'throw' => true,
189+
],
190+
'base_url' => 'https://api.moonshot.cn/v1',
191+
],
192+
validate($config, [
193+
'http_options' => 'array',
194+
'retry' => 'array',
195+
'retry.times' => 'integer',
196+
'retry.sleep' => 'integer',
197+
'retry.when' => 'nullable',
198+
// 'retry.throw' => 'bool',
199+
'base_url' => 'string',
200+
'api_key' => 'required|string',
201+
])
202+
);
203+
}
204+
205+
/**
206+
* {@inheritDoc}
207+
*/
208+
protected function buildDefaultPendingRequest(array $config): PendingRequest
209+
{
210+
return parent::buildDefaultPendingRequest($config)
211+
->baseUrl($config['base_url'])
212+
->asJson()
213+
->withToken($config['api_key'])
214+
// ->dump()
215+
// ->throw()
216+
// ->retry(
217+
// $config['retry']['times'],
218+
// $config['retry']['sleep'],
219+
// $config['retry']['when']
220+
// // $config['retry']['throw']
221+
// )
222+
->withOptions($config['http_options']);
223+
}
224+
}

0 commit comments

Comments
 (0)