reyfetch 1.0.35__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
reyfetch/rali.py ADDED
@@ -0,0 +1,990 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2025-07-17 22:32:37
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Ali Web fetch methods.
9
+ """
10
+
11
+
12
+ from typing import Any, TypedDict, NotRequired, Literal, overload, NoReturn
13
+ from collections.abc import Hashable, Iterable, Generator
14
+ from json import loads as json_loads
15
+ from reydb import rorm
16
+ from reydb.rdb import Database
17
+ from reykit.rbase import throw
18
+ from reykit.rnet import request as reykit_request
19
+ from reykit.rtime import now
20
+
21
+ from .rbase import FetchRequest, FetchRequestWithDatabase, FetchRequestDatabaseRecord
22
+
23
+
24
+ __all__ = (
25
+ 'DatabaseTableAliQwen',
26
+ 'FetchRequestAli',
27
+ 'FetchRequestAliQwen'
28
+ )
29
+
30
+
31
+ # Key 'role' value 'system' only in first.
32
+ # Key 'role' value 'user' and 'assistant' can mix.
33
+ type ChatRecordRole = Literal['system', 'user', 'assistant']
34
+ ChatRecordToken = TypedDict('ChatRecordToken', {'total': int, 'input': int, 'output': int, 'output_think': int | None})
35
+ ChatResponseWebItem = TypedDict('ChatResponseWebItem', {'site': str | None, 'icon': str | None, 'index': int, 'url': str, 'title': str})
36
+ type ChatResponseWeb = list[ChatResponseWebItem]
37
+ ChatRecord = TypedDict(
38
+ 'ChatRecord',
39
+ {
40
+ 'time': int,
41
+ 'role': ChatRecordRole,
42
+ 'content': str | None,
43
+ 'len': int | None,
44
+ 'token': ChatRecordToken | None,
45
+ 'web': ChatResponseWeb | None,
46
+ 'think': str | None
47
+ }
48
+ )
49
+ type ChatRecords = list[ChatRecord]
50
+ type ChatRecordsIndex = Hashable
51
+ type ChatRecordsData = dict[ChatRecordsIndex, ChatRecords]
52
+ ChatRecordsAppend = TypedDict('ChatRecordsAppend', {'time': NotRequired[int], 'role': NotRequired[ChatRecordRole], 'content': str})
53
+ type ChatRecordsAppends = list[ChatRecordsAppend]
54
+ ChatReplyGenerator = Generator[str, Any, None]
55
+ ChatThinkGenerator = Generator[str, Any, None]
56
+
57
+
58
+ class DatabaseTableAliQwen(rorm.Model, table=True):
59
+ """
60
+ Database `ali_qwen` table model.
61
+ """
62
+
63
+ __name__ = 'ali_qwen'
64
+ __comment__ = 'Ali API qwen model request record table.'
65
+ id: int = rorm.Field(rorm.types_mysql.INTEGER(unsigned=True), key_auto=True, comment='ID.')
66
+ request_time: rorm.Datetime = rorm.Field(not_null=True, comment='Request time.')
67
+ response_time: rorm.Datetime = rorm.Field(not_null=True, comment='Response time, when is stream response, then is full return after time.')
68
+ messages: str = rorm.Field(rorm.types.JSON, not_null=True, comment='Input messages data.')
69
+ reply: str = rorm.Field(rorm.types.TEXT, not_null=True, comment='Output reply text.')
70
+ think: str = rorm.Field(rorm.types.TEXT, comment='Output deep think text.')
71
+ web: str = rorm.Field(rorm.types.JSON, not_null=True, comment='Web search data.')
72
+ token_total: int = rorm.Field(rorm.types_mysql.MEDIUMINT(unsigned=True), not_null=True, comment='Usage total Token.')
73
+ token_input: int = rorm.Field(rorm.types_mysql.MEDIUMINT(unsigned=True), not_null=True, comment='Usage input Token.')
74
+ token_output: int = rorm.Field(rorm.types_mysql.MEDIUMINT(unsigned=True), not_null=True, comment='Usage output Token.')
75
+ token_output_think: int = rorm.Field(rorm.types_mysql.MEDIUMINT(unsigned=True), comment='Usage output think Token.')
76
+ model: str = rorm.Field(rorm.types.VARCHAR(100), not_null=True, comment='Model name.')
77
+
78
+
79
+ class FetchRequestAli(FetchRequest):
80
+ """
81
+ Reuqest Ali API fetch type.
82
+ """
83
+
84
+
85
+ class FetchRequestAliQwen(FetchRequestAli, FetchRequestWithDatabase):
86
+ """
87
+ Request Ali QWen API fetch type.
88
+ Can create database used `self.build_db` method.
89
+
90
+ Attributes
91
+ ----------
92
+ url_api : API request URL.
93
+ url_doc : API document URL.
94
+ model = API AI model type.
95
+ db_names : Database table name mapping dictionary.
96
+ """
97
+
98
+ url_api = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation'
99
+ url_doc = 'https://help.aliyun.com/zh/model-studio/use-qwen-by-calling-api?spm=a2c4g.11186623.0.0.330e7d9dSBCaZQ'
100
+ model = 'qwen-turbo-latest'
101
+ db_names = {
102
+ 'ali_qwen': 'ali_qwen',
103
+ 'stats_ali_qwen': 'stats_ali_qwen'
104
+ }
105
+
106
+
107
+ def __init__(
108
+ self,
109
+ key: str,
110
+ db: Database | None = None,
111
+ system: str | None = None,
112
+ rand: float = 0.5,
113
+ history_max_char: int | None = None,
114
+ history_max_time: float | None = None
115
+ ) -> None:
116
+ """
117
+ Build instance attributes.
118
+
119
+ Parameters
120
+ ----------
121
+ key : API key.
122
+ db : `Database` instance, insert request record to table.
123
+ system : AI system description.
124
+ rand : AI reply randomness, value range is `[0,1]`.
125
+ history_max_char : History messages record maximum character count.
126
+ history_max_time : History messages record maximum second.
127
+ """
128
+
129
+ # Check.
130
+ if not 0 <= rand <= 1:
131
+ throw(ValueError, rand)
132
+
133
+ # Build.
134
+ self.key = key
135
+ self.auth = 'Bearer ' + key
136
+ self.db = db
137
+ self.system = system
138
+ self.rand = rand
139
+ self.history_max_char = history_max_char
140
+ self.history_max_time = history_max_time
141
+ self.data: ChatRecordsData = {}
142
+
143
+ # Database.
144
+ self.db_record = FetchRequestDatabaseRecord(self, 'api', 'ali_qwen')
145
+
146
+
147
+ @overload
148
+ def request(self, json: dict, stream: Literal[True]) -> Iterable[str]: ...
149
+
150
+ @overload
151
+ def request(self, json: dict, stream: Literal[False]) -> dict: ...
152
+
153
+ def request(self, json: dict, stream: bool) -> dict | Iterable[str]:
154
+ """
155
+ Request API.
156
+
157
+ Parameters
158
+ ----------
159
+ json : Request body.
160
+ stream : Whether use stream response.
161
+
162
+ Returns
163
+ -------
164
+ Response json or iterable.
165
+ """
166
+
167
+ # Parameter.
168
+ json['model'] = self.model
169
+ json_params = json.setdefault('parameters', {})
170
+ json_params_temperature = self.rand * 2
171
+ if json_params_temperature == 2:
172
+ json_params_temperature = 1.99
173
+ json_params['temperature'] = json_params_temperature
174
+ json_params['presence_penalty'] = self.rand * 4 - 2
175
+ headers = {'Authorization': self.auth, 'Content-Type': 'application/json'}
176
+ if stream:
177
+ headers['X-DashScope-SSE'] = 'enable'
178
+ json_params['incremental_output'] = True
179
+
180
+ # Request.
181
+ response = reykit_request(
182
+ self.url_api,
183
+ json=json,
184
+ headers=headers,
185
+ stream=stream,
186
+ check=True
187
+ )
188
+
189
+ # Stream.
190
+ if stream:
191
+ iterable: Iterable[str] = response.iter_lines(decode_unicode=True)
192
+ return iterable
193
+
194
+ # Check.
195
+ content_type = response.headers['Content-Type']
196
+ if content_type.startswith('application/json'):
197
+ response_json: dict = response.json()
198
+ if 'code' in response_json:
199
+ throw(AssertionError, response_json)
200
+ else:
201
+ throw(AssertionError, content_type)
202
+
203
+ return response_json
204
+
205
+
206
+ def extract_response_text(self, response_json: dict) -> str | None:
207
+ """
208
+ Extract reply text from response JSON.
209
+
210
+ Parameters
211
+ ----------
212
+ response_json : Response JSON.
213
+
214
+ Returns
215
+ -------
216
+ Reply text.
217
+ """
218
+
219
+ # Extract.
220
+ output_data = response_json.get('output')
221
+ if output_data is not None:
222
+ response_text: str = output_data['choices'][0]['message']['content']
223
+ else:
224
+ response_text = None
225
+
226
+ return response_text
227
+
228
+
229
+ def extract_response_token(self, response_json: dict) -> ChatRecordToken | None:
230
+ """
231
+ Extract usage token data from response JSON.
232
+
233
+ Parameters
234
+ ----------
235
+ response_json : Response JSON.
236
+
237
+ Returns
238
+ -------
239
+ Usage token data.
240
+ """
241
+
242
+ # Extract.
243
+ token_data: dict | None = response_json.get('usage')
244
+ if token_data is not None:
245
+ token_data = {
246
+ 'total': token_data['total_tokens'],
247
+ 'input': token_data['input_tokens'],
248
+ 'output': token_data['output_tokens'],
249
+ 'output_think': token_data.get('output_tokens_details', {}).get('reasoning_tokens')
250
+ }
251
+
252
+ return token_data
253
+
254
+
255
+ def extract_response_web(self, response_json: dict) -> ChatResponseWeb | None:
256
+ """
257
+ Extract web data from response JSON.
258
+
259
+ Parameters
260
+ ----------
261
+ response_json : Response JSON.
262
+
263
+ Returns
264
+ -------
265
+ Web data.
266
+ """
267
+
268
+ # Extract.
269
+ json_output: dict = response_json['output']
270
+ search_info: dict = json_output.get('search_info', {})
271
+ web_data: list[dict] = search_info.get('search_results', [])
272
+ for item in web_data:
273
+ item.setdefault('site_name', None)
274
+ item.setdefault('icon', None)
275
+ item['site'] = item.pop('site_name')
276
+ if item['site'] == '':
277
+ item['site'] = None
278
+ if item['icon'] == '':
279
+ item['icon'] = None
280
+ web_data = web_data or None
281
+
282
+ return web_data
283
+
284
+
285
+ def extract_response_think(self, response_json: dict) -> str | None:
286
+ """
287
+ Extract deep think text from response JSON.
288
+
289
+ Parameters
290
+ ----------
291
+ response_json : Response JSON.
292
+
293
+ Returns
294
+ -------
295
+ Deep think text.
296
+ """
297
+
298
+ # Extract.
299
+ json_message: dict = response_json['output']['choices'][0]['message']
300
+ response_think = json_message.get('reasoning_content')
301
+ response_think = response_think or None
302
+
303
+ return response_think
304
+
305
+
306
+ def extract_response_record(self, response_json: dict) -> ChatRecord:
307
+ """
308
+ Extract reply record from response JSON.
309
+
310
+ Parameters
311
+ ----------
312
+ response_json : Response JSON.
313
+
314
+ Returns
315
+ -------
316
+ Reply record.
317
+ """
318
+
319
+ # Extract.
320
+ response_text = self.extract_response_text(response_json)
321
+ response_token = self.extract_response_token(response_json)
322
+ response_web = self.extract_response_web(response_json)
323
+ response_think = self.extract_response_think(response_json)
324
+ if response_text is None:
325
+ response_text_len = None
326
+ else:
327
+ response_text_len = len(response_text)
328
+ chat_record_reply = {
329
+ 'time': now('timestamp'),
330
+ 'role': 'assistant',
331
+ 'content': response_text,
332
+ 'len': response_text_len,
333
+ 'token': response_token,
334
+ 'web': response_web,
335
+ 'think': response_think
336
+ }
337
+
338
+ return chat_record_reply
339
+
340
+
341
+ def extract_response_generator(self, response_iter: Iterable[str]):
342
+ """
343
+ Extract reply generator from response JSON.
344
+
345
+ Parameters
346
+ ----------
347
+ response_iter : Response iterable.
348
+
349
+ Returns
350
+ -------
351
+ Reply Generator.
352
+ """
353
+
354
+ # First.
355
+ response_line_first = None
356
+ for response_line in response_iter:
357
+ if not response_line.startswith(('data:{', 'data: {')):
358
+ continue
359
+ response_line_first = response_line
360
+ break
361
+
362
+ ## Check.
363
+ if response_line_first is None:
364
+ throw(AssertionError, response_line_first)
365
+
366
+ response_line_first = response_line_first[5:].strip()
367
+ response_json_first: dict = json_loads(response_line_first)
368
+ chat_record_reply = self.extract_response_record(response_json_first)
369
+ is_think_emptied = not bool(chat_record_reply['think'])
370
+
371
+ ### Define.
372
+ def _generator(mode: Literal['text', 'think']) -> Generator[str, Any, None]:
373
+ """
374
+ Generator function of stream response.
375
+
376
+ Parameters
377
+ ----------
378
+ mode : Generate value type.
379
+ - `Literal['text']`: Reply text.
380
+ - `Literal['think']`: Deep think text.
381
+
382
+ Returns
383
+ -------
384
+ Generator.
385
+ """
386
+
387
+ # Parameter.
388
+ nonlocal is_think_emptied
389
+ chat_record_reply['content'] = chat_record_reply['content'] or ''
390
+ chat_record_reply['think'] = chat_record_reply['think'] or ''
391
+
392
+ # Check.
393
+ if (
394
+ not is_think_emptied
395
+ and mode == 'text'
396
+ ):
397
+ text = 'must first used up think generator'
398
+ throw(AssertionError, text=text)
399
+
400
+ # First.
401
+ if mode == 'text':
402
+ yield chat_record_reply['content']
403
+ elif mode == 'think':
404
+ yield chat_record_reply['think']
405
+
406
+ # Next.
407
+ for response_line in response_iter:
408
+
409
+ ## Filter.
410
+ if not response_line.startswith(('data:{', 'data: {')):
411
+ continue
412
+
413
+ ## JSON.
414
+ response_line = response_line[5:]
415
+ response_line = response_line.strip()
416
+ response_json: dict = json_loads(response_line)
417
+
418
+ ## Token.
419
+ response_token = self.extract_response_token(response_json)
420
+ chat_record_reply['token'] = response_token
421
+
422
+ ## Web.
423
+ if chat_record_reply['web'] is None:
424
+ response_web = self.extract_response_web(response_json)
425
+ chat_record_reply['web'] = response_web
426
+
427
+ ## Text.
428
+ if mode == 'text':
429
+ response_text = self.extract_response_text(response_json)
430
+ if response_text is None:
431
+ continue
432
+ chat_record_reply['content'] += response_text
433
+ chat_record_reply['len'] += len(response_text)
434
+ yield response_text
435
+
436
+ ## Think.
437
+ elif mode == 'think':
438
+ response_think = self.extract_response_think(response_json)
439
+
440
+ ### Last.
441
+ if response_think is None:
442
+ is_think_emptied = True
443
+ response_text = self.extract_response_text(response_json)
444
+ chat_record_reply['content'] = response_text
445
+ if response_text is not None:
446
+ chat_record_reply['len'] += len(response_text)
447
+ break
448
+
449
+ chat_record_reply['think'] += response_think
450
+ yield response_think
451
+
452
+ # Database.
453
+ else:
454
+ self.insert_db(chat_record_reply)
455
+
456
+
457
+ generator_text = _generator('text')
458
+ generator_think = _generator('think')
459
+
460
+ return chat_record_reply, generator_text, generator_think
461
+
462
+
463
+ def append_chat_records_history(
464
+ self,
465
+ records: ChatRecordsAppend | ChatRecordsAppends | str | list[str],
466
+ index: ChatRecordsIndex,
467
+ history_max_char: int | None = None,
468
+ history_max_time: float | None = None
469
+ ) -> None:
470
+ """
471
+ Append chat records.
472
+ Delete records of beyond the range from history.
473
+
474
+ Parameters
475
+ ----------
476
+ records: Chat reocrds.
477
+ - `Key 'role'`: Message sender role, default `user`.
478
+ - `Key 'content'`: Message content, required.
479
+ - `str`: Message content.
480
+ - `list[str]`: Message content list.
481
+ index : Chat records index.
482
+ history_max_char : History messages record maximum character count.
483
+ - `None`: Use `self.history_max_char`.
484
+ history_max_time : History messages record maximum second.
485
+ - `None`: Use `self.history_max_time`.
486
+ """
487
+
488
+ # Parameter.
489
+ if type(records) == str:
490
+ records = [{'content': records}]
491
+ elif type(records) == dict:
492
+ records = [records]
493
+ elif type(records) == list:
494
+ records = [
495
+ {'content': records}
496
+ if type(record) == str
497
+ else record
498
+ for record in records
499
+ ]
500
+ now_timestamp = now('timestamp')
501
+ records = [
502
+ {
503
+ 'time': record.get('time', now_timestamp),
504
+ 'role': record.get('role', 'user'),
505
+ 'content': record['content'],
506
+ 'len': len(record['content']),
507
+ 'token': None,
508
+ 'web': None,
509
+ 'think': None
510
+ }
511
+ for record in records
512
+ ]
513
+ chat_records_history: ChatRecords = self.data.setdefault(index, [])
514
+
515
+ # Append.
516
+ chat_records_history.extend(records)
517
+
518
+ # Sort.
519
+ sort_key = lambda chat_record: chat_record['time']
520
+ chat_records_history.sort(key=sort_key)
521
+
522
+ # Beyond.
523
+ self.get_chat_records_history(index, history_max_char, history_max_time, True)
524
+
525
+
526
+ def get_chat_records_history(
527
+ self,
528
+ index: ChatRecordsIndex,
529
+ history_max_char: int | None = None,
530
+ history_max_time: float | None = None,
531
+ delete: bool = False
532
+ ) -> ChatRecords:
533
+ """
534
+ Get chat records.
535
+
536
+ Parameters
537
+ ----------
538
+ index : Chat records index.
539
+ history_max_char : History messages record maximum character count.
540
+ - `None`: Use `self.history_max_char`.
541
+ history_max_time : History messages record maximum second.
542
+ - `None`: Use `self.history_max_time`.
543
+ delete : Whether delete records of beyond the range from history.
544
+
545
+ Returns
546
+ -------
547
+ Chat records.
548
+ """
549
+
550
+ # Parameter.
551
+ now_timestamp = now('timestamp')
552
+ chat_records_history: ChatRecords = self.data.setdefault(index, [])
553
+
554
+ # Max.
555
+ if history_max_char is None:
556
+ history_max_char = self.history_max_char
557
+ if history_max_time is None:
558
+ history_max_time = self.history_max_time
559
+ if history_max_time is not None:
560
+ history_max_time_us = history_max_time * 1000
561
+ char_len = 0
562
+ chat_records_history_reverse = chat_records_history[::-1]
563
+ beyond_index = None
564
+ for index, chat_record in enumerate(chat_records_history_reverse):
565
+ if (
566
+ (
567
+ history_max_char is not None
568
+ and (char_len := char_len + chat_record['len']) > history_max_char
569
+ )
570
+ or (
571
+ history_max_time is not None
572
+ and now_timestamp - chat_record['time'] > history_max_time_us
573
+ )
574
+ ):
575
+ beyond_index = -index
576
+ break
577
+
578
+ # Beyond.
579
+ if beyond_index is not None:
580
+
581
+ ## Delete.
582
+ if delete:
583
+ if beyond_index == 0:
584
+ chat_records_history.clear()
585
+ else:
586
+ del chat_records_history[:beyond_index]
587
+
588
+ ## Keep.
589
+ else:
590
+ if beyond_index == 0:
591
+ chat_records_history = []
592
+ else:
593
+ chat_records_history = chat_records_history[beyond_index:]
594
+
595
+ return chat_records_history
596
+
597
+
598
+ @overload
599
+ def chat(
600
+ self,
601
+ text: str,
602
+ index: ChatRecordsIndex | None = None,
603
+ role: str | None = None,
604
+ web: bool = False,
605
+ web_mark: bool = False,
606
+ history_max_char: int | None = None,
607
+ history_max_time: float | None = None
608
+ ) -> ChatRecord: ...
609
+
610
+ @overload
611
+ def chat(
612
+ self,
613
+ text: str,
614
+ index: ChatRecordsIndex | None = None,
615
+ system: str | None = None,
616
+ web: bool = False,
617
+ web_mark: bool = False,
618
+ *,
619
+ stream: Literal[True],
620
+ history_max_char: int | None = None,
621
+ history_max_time: float | None = None
622
+ ) -> tuple[ChatRecord, ChatReplyGenerator]: ...
623
+
624
+ @overload
625
+ def chat(
626
+ self,
627
+ text: str,
628
+ index: ChatRecordsIndex | None = None,
629
+ system: str | None = None,
630
+ web: bool = False,
631
+ web_mark: bool = False,
632
+ *,
633
+ think: Literal[True],
634
+ stream: Literal[True],
635
+ history_max_char: int | None = None,
636
+ history_max_time: float | None = None
637
+ ) -> tuple[ChatRecord, ChatReplyGenerator, ChatThinkGenerator]: ...
638
+
639
+ @overload
640
+ def chat(
641
+ self,
642
+ text: str,
643
+ index: ChatRecordsIndex | None = None,
644
+ system: str | None = None,
645
+ web: bool = False,
646
+ web_mark: bool = False,
647
+ *,
648
+ think: Literal[True],
649
+ history_max_char: int | None = None,
650
+ history_max_time: float | None = None
651
+ ) -> NoReturn: ...
652
+
653
+ def chat(
654
+ self,
655
+ text: str,
656
+ index: ChatRecordsIndex | None = None,
657
+ system: str | None = None,
658
+ web: bool = False,
659
+ web_mark: bool = False,
660
+ think: bool = False,
661
+ stream: bool = False,
662
+ history_max_char: int | None = None,
663
+ history_max_time: float | None = None
664
+ ) -> ChatRecord | tuple[ChatRecord, ChatReplyGenerator] | tuple[ChatRecord, ChatReplyGenerator, ChatThinkGenerator]:
665
+ """
666
+ Chat with AI.
667
+
668
+ Parameters
669
+ ----------
670
+ text : User chat text.
671
+ index : Chat records index.
672
+ `None`: Not use record.
673
+ system : Extra AI system description, will be connected to `self.system`.
674
+ web : Whether use web search.
675
+ web_mark : Whether display web search citation mark, format is `[ref_<number>]`.
676
+ think : Whether use deep think, when is `True`, then parameter `stream` must also be `True`.
677
+ stream : Whether use stream response, record after full return values.
678
+ history_max_char : History messages record maximum character count.
679
+ - `None`: Use `self.history_max_char`.
680
+ history_max_time : History messages record maximum second.
681
+ - `None`: Use `self.history_max_time`.
682
+
683
+ Returns
684
+ -------
685
+ Response content.
686
+ """
687
+
688
+ # Check.
689
+ if text == '':
690
+ throw(ValueError, text)
691
+ if think and not stream:
692
+ throw(ValueError, think, stream)
693
+
694
+ # Parameter.
695
+ if (
696
+ system is not None
697
+ and self.system is not None
698
+ ):
699
+ system = ''.join([self.system, system])
700
+ elif system is None:
701
+ system = self.system
702
+ json = {'input': {}, 'parameters': {}}
703
+
704
+ ## History.
705
+ if index is not None:
706
+ chat_records_history = self.get_chat_records_history(index, history_max_char, history_max_time, True)
707
+ else:
708
+ chat_records_history: ChatRecords = []
709
+
710
+ ### Role.
711
+ if system is not None:
712
+ chat_record_role: ChatRecord = {
713
+ 'time': now('timestamp'),
714
+ 'role': 'system',
715
+ 'content': system,
716
+ 'len': len(system),
717
+ 'token': None,
718
+ 'web': None,
719
+ 'think': None
720
+ }
721
+ chat_records_role: ChatRecords = [chat_record_role]
722
+ else:
723
+ chat_records_role: ChatRecords = []
724
+
725
+ ### Now.
726
+ chat_record_now: ChatRecord= {
727
+ 'time': now('timestamp'),
728
+ 'role': 'user',
729
+ 'content': text,
730
+ 'len': len(text),
731
+ 'token': None,
732
+ 'web': None,
733
+ 'think': None
734
+ }
735
+ chat_records_now: ChatRecords = [chat_record_now]
736
+
737
+ messages: ChatRecords = chat_records_role + chat_records_history + chat_records_now
738
+ messages = [
739
+ {
740
+ 'role': message['role'],
741
+ 'content': message['content']
742
+ }
743
+ for message in messages
744
+ ]
745
+
746
+ ## Database.
747
+ self.db_record['messages'] = messages
748
+ self.db_record['model'] = self.model
749
+
750
+ ## Message.
751
+ json['input']['messages'] = messages
752
+ json['parameters']['result_format'] = 'message'
753
+
754
+ ## Web.
755
+ if web:
756
+ json['parameters']['enable_search'] = True
757
+ json['parameters']['search_options'] = {
758
+ 'enable_source': True,
759
+ 'enable_citation': web_mark,
760
+ 'citation_format': '[ref_<number>]',
761
+ 'forced_search': False,
762
+ 'search_strategy': 'max',
763
+ 'prepend_search_result': False,
764
+ 'enable_search_extension': True
765
+ }
766
+ else:
767
+ json['parameters']['enable_search'] = False
768
+
769
+ ## Think.
770
+ json['parameters']['enable_thinking'] = think
771
+
772
+ ## Stream.
773
+ json['stream'] = stream
774
+
775
+ # Request.
776
+ self.db_record['request_time'] = now()
777
+ response = self.request(json, stream)
778
+ self.db_record['response_time'] = now()
779
+
780
+ # Extract.
781
+
782
+ ## Stream.
783
+ if stream:
784
+ response_iter: Iterable[str] = response
785
+ chat_record_reply, generator_text, generator_think = self.extract_response_generator(response_iter)
786
+
787
+ ## Not Stream.
788
+ else:
789
+ response_json: dict = response
790
+ chat_record_reply = self.extract_response_record(response_json)
791
+
792
+ # Record.
793
+ if index is not None:
794
+ chat_records_history.append(chat_record_now)
795
+ chat_records_history.append(chat_record_reply)
796
+
797
+ # Return.
798
+ if stream:
799
+ if think:
800
+ return chat_record_reply, generator_text, generator_think
801
+ else:
802
+ return chat_record_reply, generator_text
803
+ else:
804
+
805
+ ## Database.
806
+ self.insert_db(chat_record_reply)
807
+
808
+ return chat_record_reply
809
+
810
+
811
+ def polish(self, text: str) -> str:
812
+ """
813
+ Let AI polish text.
814
+
815
+ Parameters
816
+ ----------
817
+ text : Text.
818
+
819
+ Returns
820
+ -------
821
+ Polished text.
822
+ """
823
+
824
+ # Parameter.
825
+ text = '润色冒号后的内容(注意!只返回润色后的内容正文,之后会直接整段使用):' + text
826
+ record = self.chat(text)
827
+ result: str = record['content']
828
+ result = result.strip()
829
+
830
+ return result
831
+
832
+
833
+ def build_db(self) -> None:
834
+ """
835
+ Check and build database tables, by `self.db_names`.
836
+ """
837
+
838
+ # Check.
839
+ if self.db is None:
840
+ throw(ValueError, self.db)
841
+
842
+ # Parameter.
843
+
844
+ ## Table.
845
+ tables = [DatabaseTableAliQwen]
846
+ DatabaseTableAliQwen._set_name(self.db_names['ali_qwen'])
847
+
848
+ ## View stats.
849
+ views_stats = [
850
+ {
851
+ 'path': self.db_names['stats_ali_qwen'],
852
+ 'items': [
853
+ {
854
+ 'name': 'count',
855
+ 'select': (
856
+ 'SELECT COUNT(1)\n'
857
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
858
+ ),
859
+ 'comment': 'Request count.'
860
+ },
861
+ {
862
+ 'name': 'past_day_count',
863
+ 'select': (
864
+ 'SELECT COUNT(1)\n'
865
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
866
+ 'WHERE TIMESTAMPDIFF(DAY, `request_time`, NOW()) = 0'
867
+ ),
868
+ 'comment': 'Request count in the past day.'
869
+ },
870
+ {
871
+ 'name': 'past_week_count',
872
+ 'select': (
873
+ 'SELECT COUNT(1)\n'
874
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
875
+ 'WHERE TIMESTAMPDIFF(DAY, `request_time`, NOW()) <= 6'
876
+ ),
877
+ 'comment': 'Request count in the past week.'
878
+ },
879
+ {
880
+ 'name': 'past_month_count',
881
+ 'select': (
882
+ 'SELECT COUNT(1)\n'
883
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
884
+ 'WHERE TIMESTAMPDIFF(DAY, `request_time`, NOW()) <= 29'
885
+ ),
886
+ 'comment': 'Request count in the past month.'
887
+ },
888
+ {
889
+ 'name': 'total_token',
890
+ 'select': (
891
+ 'SELECT FORMAT(SUM(`token_total`), 0)\n'
892
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
893
+ ),
894
+ 'comment': 'Usage total Token.'
895
+ },
896
+ {
897
+ 'name': 'total_token_input',
898
+ 'select': (
899
+ 'SELECT FORMAT(SUM(`token_input`), 0)\n'
900
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
901
+ ),
902
+ 'comment': 'Usage input total Token.'
903
+ },
904
+ {
905
+ 'name': 'total_token_output',
906
+ 'select': (
907
+ 'SELECT FORMAT(SUM(`token_output`), 0)\n'
908
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
909
+ ),
910
+ 'comment': 'Usage output total Token.'
911
+ },
912
+ {
913
+ 'name': 'total_token_output_think',
914
+ 'select': (
915
+ 'SELECT FORMAT(SUM(`token_output_think`), 0)\n'
916
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
917
+ ),
918
+ 'comment': 'Usage output think total Token.'
919
+ },
920
+ {
921
+ 'name': 'avg_token',
922
+ 'select': (
923
+ 'SELECT FORMAT(AVG(`token_total`), 0)\n'
924
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
925
+ ),
926
+ 'comment': 'Usage average Token.'
927
+ },
928
+ {
929
+ 'name': 'avg_token_input',
930
+ 'select': (
931
+ 'SELECT FORMAT(AVG(`token_input`), 0)\n'
932
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
933
+ ),
934
+ 'comment': 'Usage input average Token.'
935
+ },
936
+ {
937
+ 'name': 'avg_token_output',
938
+ 'select': (
939
+ 'SELECT FORMAT(AVG(`token_output`), 0)\n'
940
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
941
+ ),
942
+ 'comment': 'Usage output average Token.'
943
+ },
944
+ {
945
+ 'name': 'avg_token_output_think',
946
+ 'select': (
947
+ 'SELECT FORMAT(AVG(`token_output_think`), 0)\n'
948
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
949
+ ),
950
+ 'comment': 'Usage output think average Token.'
951
+ },
952
+ {
953
+ 'name': 'last_time',
954
+ 'select': (
955
+ 'SELECT MAX(`request_time`)\n'
956
+ f'FROM `{self.db.database}`.`{self.db_names['ali_qwen']}`'
957
+ ),
958
+ 'comment': 'Last record request time.'
959
+ }
960
+ ]
961
+ }
962
+ ]
963
+
964
+ # Build.
965
+ self.db.build.build(tables=tables, views_stats=views_stats, skip=True)
966
+
967
+
968
+ def insert_db(self, record: ChatRecord) -> None:
969
+ """
970
+ Insert record to table of database.
971
+
972
+ Parameters
973
+ ----------
974
+ record : Record data.
975
+ """
976
+
977
+ # Parameter.
978
+ self.db_record['reply'] = record['content']
979
+ self.db_record['think'] = record['think']
980
+ self.db_record['web'] = record['web']
981
+ self.db_record['token_total'] = record['token']['total']
982
+ self.db_record['token_input'] = record['token']['input']
983
+ self.db_record['token_output'] = record['token']['output']
984
+ self.db_record['token_output_think'] = record['token']['output_think']
985
+
986
+ # Insert.
987
+ self.db_record.record()
988
+
989
+
990
+ __call__ = chat