├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── composer.lock
├── examples
├── chat.php
└── demo.gif
├── logo.png
└── src
├── Client.php
├── Conversation.php
├── Prompt.php
└── Tools.php
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | .idea/
3 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Maxime Renou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # HuggingChat client
4 |
5 | [](https://opensource.org/licenses/MIT)
6 | [](https://packagist.org/packages/maximerenou/hugging-chat)
7 | [](https://packagist.org/packages/maximerenou/hugging-chat)
8 | [](https://packagist.org/packages/maximerenou/hugging-chat)
9 |
10 | This is an unofficial PHP client for **HuggingChat** (OpenAssistant's LLaMA model).
11 |
12 | > HuggingChat API [is evolving fast](https://huggingface.co/spaces/huggingchat/chat-ui/commits/main) with recurring breaking changes. I try to keep up with it, but it may not always work as expected. Feel free to open an issue if you encounter any problem.
13 |
14 | ## Installation
15 |
16 | composer require maximerenou/hugging-chat
17 |
18 | ## Demo
19 |
20 | Run `examples/chat.php` to test it.
21 |
22 | 
23 |
24 | ## Usage
25 |
26 | ```php
27 | use MaximeRenou\HuggingChat\Client as HuggingChat;
28 | use MaximeRenou\HuggingChat\Prompt;
29 |
30 | $ai = new HuggingChat();
31 |
32 | $conversation = $ai->createConversation();
33 |
34 | // $answer - full answer
35 | $answer = $conversation->ask(new Prompt("Hello World"));
36 | ```
37 |
38 |
39 | Real-time / progressive answer
40 |
41 | You may pass a function as second argument to get real-time progression:
42 |
43 | ```php
44 | // $current_answer - incomplete answer
45 | // $tokens - last tokens received
46 | $final_answer = $conversation->ask($prompt, function ($current_answer, $tokens) {
47 | echo $tokens;
48 | });
49 | ```
50 |
51 |
52 |
53 |
54 | Resume a conversation
55 |
56 | If you want to resume a previous conversation, you can retrieve its identifiers:
57 |
58 | ```php
59 | // Get current identifiers
60 | $identifiers = $conversation->getIdentifiers();
61 |
62 | // ...
63 | // Resume conversation with $identifiers parameter
64 | $conversation = $ai->resumeConversation($identifiers);
65 | ```
66 |
67 |
68 |
69 |
70 | Use another model
71 |
72 | You can use a specific model:
73 |
74 | ```php
75 | $conversation = $ai->createConversation("bigcode/starcoder");
76 | ```
77 |
78 | Default is OpenAssistant.
79 |
80 |
81 |
82 |
83 | Generate a conversation's summary
84 |
85 | Useful to give a title to a conversation.
86 |
87 | ```php
88 | // Question asked: "Who's Einstein?"
89 | // ...
90 | $summary = $conversation->getSummary();
91 | // Result: Famous genius mathematician.
92 | ```
93 |
94 |
95 |
96 |
97 | Turn on/off data sharing
98 |
99 | HuggingChat share your conversations to improve the model. You can turn on/off data sharing:
100 |
101 | ```php
102 | $conversation->enableSharing(); // on
103 |
104 | $conversation->disableSharing(); // off (module default)
105 | ```
106 |
107 |
108 |
109 |
110 | Delete a conversation
111 |
112 | ```php
113 | $conversation->delete();
114 | ```
115 |
116 |
117 |
118 |
119 | Handle HuggingChat errors
120 |
121 | The code throws exceptions when it receives an error from HuggingChat. You can therefore use a try/catch block to handle errors.
122 |
123 |
124 |
125 |
126 | Answers are sometimes malformed (or dumb)
127 |
128 | Answers quality depends on the model you're using.
129 |
130 |
131 |
132 | ---------------------------------------
133 |
134 | #### Disclaimer
135 |
136 | Using HuggingChat outside huggingface.co/chat may violate HuggingFace terms. Use it at your own risk.
137 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maximerenou/hugging-chat",
3 | "description": "HuggingChat client",
4 | "type": "library",
5 | "autoload": {
6 | "psr-4": {
7 | "MaximeRenou\\HuggingChat\\": "src/"
8 | }
9 | },
10 | "authors": [
11 | {
12 | "name": "Maxime Renou",
13 | "email": "contact@maximerenou.fr"
14 | }
15 | ],
16 | "require": {
17 | "php": ">=7.1",
18 | "ext-curl": "*",
19 | "eislambey/eventsource": "^0.1.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "988f93e2d506d8e0ea9e8adbf9935358",
8 | "packages": [
9 | {
10 | "name": "eislambey/eventsource",
11 | "version": "v0.1.0",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/eislambey/php-eventsource.git",
15 | "reference": "6cb41018eec429b3607fd47b3c42bd2f89a1209f"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/eislambey/php-eventsource/zipball/6cb41018eec429b3607fd47b3c42bd2f89a1209f",
20 | "reference": "6cb41018eec429b3607fd47b3c42bd2f89a1209f",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "ext-curl": "*",
25 | "ext-json": "*"
26 | },
27 | "type": "library",
28 | "autoload": {
29 | "psr-4": {
30 | "EventSource\\": "src/"
31 | }
32 | },
33 | "notification-url": "https://packagist.org/downloads/",
34 | "license": [
35 | "MIT"
36 | ],
37 | "authors": [
38 | {
39 | "name": "emre can islambey",
40 | "email": "eislambey@gmail.com"
41 | }
42 | ],
43 | "description": "A simple EventSource / SSE (Server-Sent Events) client.",
44 | "keywords": [
45 | "Server-Sent Events",
46 | "client",
47 | "eventsource",
48 | "sse"
49 | ],
50 | "support": {
51 | "issues": "https://github.com/eislambey/php-eventsource/issues",
52 | "source": "https://github.com/eislambey/php-eventsource/tree/v0.1.0"
53 | },
54 | "time": "2019-08-07T16:41:36+00:00"
55 | }
56 | ],
57 | "packages-dev": [],
58 | "aliases": [],
59 | "minimum-stability": "stable",
60 | "stability-flags": [],
61 | "prefer-stable": false,
62 | "prefer-lowest": false,
63 | "platform": {
64 | "php": ">=7.1",
65 | "ext-curl": "*"
66 | },
67 | "platform-dev": [],
68 | "plugin-api-version": "2.2.0"
69 | }
70 |
--------------------------------------------------------------------------------
/examples/chat.php:
--------------------------------------------------------------------------------
1 | createConversation();
9 |
10 | echo 'Type "q" to quit' . PHP_EOL;
11 |
12 | while (true) {
13 | echo PHP_EOL . "> ";
14 |
15 | $text = rtrim(fgets(STDIN));
16 |
17 | if ($text == 'q')
18 | break;
19 |
20 | $prompt = new \MaximeRenou\HuggingChat\Prompt($text);
21 |
22 | echo "-";
23 |
24 | try {
25 | $full_answer = $conversation->ask($prompt, function ($answer, $tokens) {
26 | echo $tokens;
27 | });
28 | }
29 | catch (\Exception $exception) {
30 | echo " Sorry, something went wrong: {$exception->getMessage()}.";
31 | }
32 | }
33 |
34 | $conversation->delete();
35 |
36 | exit(0);
37 |
--------------------------------------------------------------------------------
/examples/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maximerenou/php-hugging-chat/3da93fa2bd05f0cca9e14d186987a9fd168e1208/examples/demo.gif
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maximerenou/php-hugging-chat/3da93fa2bd05f0cca9e14d186987a9fd168e1208/logo.png
--------------------------------------------------------------------------------
/src/Client.php:
--------------------------------------------------------------------------------
1 | ';
11 | const DEFAULT_MODEL = 'OpenAssistant/oasst-sft-6-llama-30b-xor';
12 |
13 | // Conversation IDs
14 | public $id;
15 | public $cookie;
16 |
17 | // Conversation data
18 | protected $current_started;
19 | protected $current_text;
20 |
21 | public function __construct($identifiers = null, $model = null)
22 | {
23 | if (is_null($model)) {
24 | $model = self::DEFAULT_MODEL;
25 | }
26 |
27 | if (is_array($identifiers) && ! empty($identifiers['cookie']))
28 | $this->cookie = $identifiers['cookie'];
29 |
30 | if (! is_array($identifiers))
31 | $identifiers = $this->initConversation($model);
32 |
33 | $this->id = $identifiers['id'];
34 | $this->cookie = $identifiers['cookie'];
35 | }
36 |
37 | public function getIdentifiers()
38 | {
39 | return [
40 | 'id' => $this->id,
41 | 'cookie' => $this->cookie
42 | ];
43 | }
44 |
45 | public function initConversation($model)
46 | {
47 | $headers = [
48 | 'method: POST',
49 | 'accept: application/json',
50 | 'accept-language: en-US,en;q=0.9',
51 | 'accept-encoding: gzip, deflate, br',
52 | "referer: https://huggingface.co/chat/",
53 | "origin: https://huggingface.co",
54 | 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
55 | ];
56 |
57 | if (! empty($this->cookie)) {
58 | $headers[] = "cookie: hf-chat={$this->cookie}";
59 | } else {
60 | list($data, $request, $url, $cookies) = Tools::request("https://huggingface.co/chat", [], '', true);
61 |
62 | if (! empty($cookies['hf-chat'])) {
63 | $this->cookie = $cookies['hf-chat'];
64 | $headers[] = "cookie: hf-chat={$this->cookie}";
65 | }
66 |
67 | // Ethics modal
68 | $data = [
69 | 'ethicsModalAccepted' => true,
70 | 'ethicsModalAcceptedAt' => null,
71 | 'shareConversationsWithModelAuthors' => false,
72 | 'activeModel' => $model
73 | ];
74 |
75 | Tools::request("https://huggingface.co/chat/settings", array_merge($headers, [
76 | 'x-sveltekit-action: true',
77 | 'content-type: multipart/form-data'
78 | ]), $data);
79 | }
80 |
81 | // Init conversation
82 | $data = json_encode(['model' => $model]);
83 | $data = Tools::request("https://huggingface.co/chat/conversation", array_merge($headers, ['content-type: application/json']), $data);
84 | $data = json_decode($data, true);
85 |
86 | if (! is_array($data) || empty($data['conversationId']))
87 | throw new \Exception("Failed to init conversation");
88 |
89 | return [
90 | 'id' => $data['conversationId'],
91 | 'cookie' => $this->cookie
92 | ];
93 | }
94 |
95 | public function ask(Prompt $message, $callback = null)
96 | {
97 | $this->current_text = '';
98 |
99 | $es = new EventSource("https://huggingface.co/chat/conversation/{$this->id}");
100 |
101 | $data = [
102 | 'inputs' => $message->text,
103 | 'options' => [
104 | 'use_cache' => $message->cache
105 | ],
106 | 'parameters' => [
107 | 'max_new_tokens' => $message->max_new_tokens,
108 | 'repetition_penalty' => $message->repetition_penalty,
109 | 'return_full_text' => $message->return_full_text,
110 | 'stop' => [self::END_CHAR],
111 | 'temperature' => $message->temperature,
112 | 'top_k' => $message->top_k,
113 | 'top_p' => $message->top_p,
114 | 'truncate' => $message->truncate,
115 | 'watermark' => $message->watermark,
116 | ],
117 | 'stream' => true
118 | ];
119 |
120 | $es->setCurlOptions([
121 | CURLOPT_HTTPHEADER => [
122 | 'method: POST',
123 | 'accept: */*',
124 | "referer: https://huggingface.co/chat/conversation/{$this->id}",
125 | 'content-type: application/json',
126 | "cookie: hf-chat={$this->cookie}"
127 | ],
128 | CURLOPT_POST => 1,
129 | CURLOPT_POSTFIELDS => json_encode($data)
130 | ]);
131 |
132 | $es->onMessage(function (Event $event) use (&$callback) {
133 | $message = $this->handlePacket($event->data);
134 |
135 | if ($message === false)
136 | return;
137 |
138 | $tokens = $message['text'];
139 |
140 | if ($message['final']) {
141 | $offset = strlen($this->current_text);
142 | $this->current_text = $tokens;
143 | $tokens = substr($tokens, $offset);
144 | }
145 | else {
146 | $this->current_text .= $tokens;
147 | }
148 |
149 | if (($pos = strpos($this->current_text, self::END_CHAR)) !== false) {
150 | $this->current_text = substr($this->current_text, 0, $pos);
151 | }
152 |
153 | if (! is_null($callback)) {
154 | $callback($this->current_text, $tokens);
155 | }
156 | });
157 |
158 | @$es->connect();
159 |
160 | return $this->current_text;
161 | }
162 |
163 | protected function handlePacket($raw)
164 | {
165 | $data = json_decode($raw, true);
166 |
167 | if (! $data) {
168 | return false;
169 | }
170 |
171 | if (empty($data['token'])) {
172 | Tools::debug("Drop: $raw");
173 |
174 | if (! empty($data['error'])) {
175 | throw new \Exception($data['error']);
176 | }
177 |
178 | return false;
179 | }
180 |
181 | $text = $data['token']['special'] ? $data['generated_text'] : $data['token']['text'];
182 |
183 | if (($pos = strpos($text, self::END_CHAR)) !== false) {
184 | $text = substr($text, 0, $pos);
185 | }
186 |
187 | return [
188 | 'text' => $text,
189 | 'final' => $data['token']['special']
190 | ];
191 | }
192 |
193 | public function getSummary()
194 | {
195 | $headers = [
196 | 'method: POST',
197 | 'accept: application/json',
198 | "referer: https://huggingface.co/chat",
199 | 'content-type: application/json',
200 | "cookie: hf-chat={$this->cookie}"
201 | ];
202 |
203 | $data = Tools::request("https://huggingface.co/chat/conversation/{$this->id}/summarize", $headers, '', false);
204 | $data = json_decode($data, true);
205 |
206 | if (! is_array($data) || empty($data['title']))
207 | throw new \Exception("Failed to get conversation's summary");
208 |
209 | return trim($data['title'], '"');
210 | }
211 |
212 | public function enableSharing()
213 | {
214 | return $this->withSettings([
215 | 'shareConversationsWithModelAuthors' => true
216 | ]);
217 | }
218 |
219 | public function disableSharing()
220 | {
221 | return $this->withSettings([
222 | 'shareConversationsWithModelAuthors' => false
223 | ]);
224 | }
225 |
226 | public function withSettings($settings)
227 | {
228 | $headers = [
229 | 'method: PATCH',
230 | 'accept: application/json',
231 | "origin: https://huggingface.co",
232 | "referer: https://huggingface.co/chat/privacy",
233 | 'content-type: application/json',
234 | "cookie: hf-chat={$this->cookie}"
235 | ];
236 |
237 | $data = json_encode($settings);
238 |
239 | Tools::request("https://huggingface.co/chat/settings", $headers, $data);
240 |
241 | return $this;
242 | }
243 |
244 | public function delete()
245 | {
246 | $headers = [
247 | 'method: DELETE',
248 | 'accept: application/json',
249 | "referer: https://huggingface.co/chat",
250 | 'content-type: application/json',
251 | "cookie: hf-chat={$this->cookie}"
252 | ];
253 |
254 | Tools::request("https://huggingface.co/chat/conversation/{$this->id}", $headers, '');
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/src/Prompt.php:
--------------------------------------------------------------------------------
1 | text = $text;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Tools.php:
--------------------------------------------------------------------------------
1 |