davidkhala.ai 0.0.2__py3-none-any.whl → 0.0.4__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.
@@ -1,8 +1,6 @@
1
1
  from langchain_openai import ChatOpenAI
2
2
  from langgraph.prebuilt import create_react_agent
3
3
 
4
- from davidkhala.ai.api.open import Leaderboard
5
-
6
4
 
7
5
  class Agent:
8
6
 
@@ -18,10 +16,10 @@ class Agent:
18
16
 
19
17
 
20
18
  class OpenRouterModel:
21
- def __init__(self, api_key, leaderboard: Leaderboard = None):
19
+ def __init__(self, api_key, leaderboard: dict = None):
22
20
  self.api_key = api_key
23
21
 
24
- if leaderboard is not None:
22
+ if leaderboard:
25
23
  self.headers = {
26
24
  "HTTP-Referer": leaderboard['url'],
27
25
  "X-Title": leaderboard['name'],
@@ -1,66 +1,46 @@
1
1
  import datetime
2
- from abc import abstractmethod, ABC
2
+ from abc import abstractmethod
3
3
 
4
- import requests
4
+ from davidkhala.http_request import Request
5
5
 
6
+ from davidkhala.ai.model import AbstractClient
6
7
 
7
- class API(ABC):
8
+
9
+ class API(AbstractClient):
8
10
  def __init__(self, api_key: str, base_url: str):
9
- self.base_url = base_url+'/v1'
10
- self.model = None
11
- self.headers = {
12
- "Authorization": f"Bearer {api_key}",
13
- }
11
+ self.api_key = api_key
12
+ self.base_url = base_url + '/v1'
13
+ self._ = Request({"bearer": api_key})
14
+
14
15
  @property
15
16
  @abstractmethod
16
- def free_models(self)->list[str]:
17
+ def free_models(self) -> list[str]:
17
18
  ...
18
19
 
19
- @abstractmethod
20
- def pre_request(self, headers: dict, data: dict):
21
- data["model"] = self.model
22
- def chat(self, prompt, system_prompt: str = None):
23
-
24
-
20
+ def chat(self, *user_prompt: str, **kwargs):
25
21
  messages = [
26
- {
22
+ *self.messages,
23
+ *[{
27
24
  "role": "user",
28
- "content": prompt
29
- }
25
+ "content": _
26
+ } for _ in user_prompt],
30
27
  ]
31
- if system_prompt is not None:
32
- messages.append({
33
- "role": "system",
34
- "content": system_prompt
35
- })
28
+
36
29
  json = {
37
- "messages": messages
30
+ "messages": messages,
31
+ **kwargs,
38
32
  }
39
- self.pre_request(self.headers, json)
40
- # timeout=50 to cater siliconflow
41
- response = requests.post(f"{self.base_url}/chat/completions", headers=self.headers, json=json, timeout=50)
42
- parsed_response = API.parse(response)
43
33
 
34
+ response = self._.request(f"{self.base_url}/chat/completions", "POST", json=json)
44
35
 
45
36
  return {
46
- "data": list(map(lambda x: x['message']['content'], parsed_response['choices'])),
37
+ "data": list(map(lambda x: x['message']['content'], response['choices'])),
47
38
  "meta": {
48
- "usage": parsed_response['usage'],
49
- "created": datetime.datetime.fromtimestamp(parsed_response['created'])
39
+ "usage": response['usage'],
40
+ "created": datetime.datetime.fromtimestamp(response['created'])
50
41
  }
51
42
  }
52
- @staticmethod
53
- def parse(response):
54
- parsed_response = response.json()
55
43
 
56
- match parsed_response:
57
- case dict():
58
- err = parsed_response.get('error')
59
- if err is not None:
60
- raise Exception(err)
61
- case str():
62
- raise Exception(parsed_response)
63
- return parsed_response
64
44
  def list_models(self):
65
- response = requests.get(f"{self.base_url}/models", headers=self.headers)
66
- return API.parse(response)['data']
45
+ response = self._.request(f"{self.base_url}/models", "GET")
46
+ return response['data']
@@ -0,0 +1,51 @@
1
+ import requests
2
+ from davidkhala.http_request import default_on_response
3
+ from requests import HTTPError, Response
4
+
5
+ from davidkhala.ai.api import API
6
+
7
+ class OpenRouter(API):
8
+ @property
9
+ def free_models(self) -> list[str]:
10
+ return list(
11
+ map(lambda model: model['id'],
12
+ filter(lambda model: model['id'].endswith(':free'), self.list_models())
13
+ )
14
+ )
15
+
16
+ @staticmethod
17
+ def on_response(response: requests.Response):
18
+ r = default_on_response(response)
19
+ # openrouter special error on response.ok
20
+ err = r.get('error')
21
+ if err:
22
+ derived_response = Response()
23
+ derived_response.status_code = err.pop('code', None)
24
+ derived_response._content = err.pop('message', None)
25
+ http_err = HTTPError(response=derived_response)
26
+ http_err.metadata = err.get("metadata")
27
+ raise http_err
28
+ return r
29
+
30
+ def __init__(self, api_key: str, *models: str, **kwargs):
31
+
32
+ super().__init__(api_key, 'https://openrouter.ai/api')
33
+
34
+ if 'leaderboard' in kwargs and type(kwargs['leaderboard']) is dict:
35
+ self._.options["headers"]["HTTP-Referer"] = kwargs['leaderboard'][
36
+ 'url'] # Site URL for rankings on openrouter.ai.
37
+ self._.options["headers"]["X-Title"] = kwargs['leaderboard'][
38
+ 'name'] # Site title for rankings on openrouter.ai.
39
+ if not models:
40
+ models = [self.free_models[0]]
41
+ self.models = models
42
+
43
+ self._.on_response = OpenRouter.on_response
44
+
45
+ def chat(self, *user_prompt: str, **kwargs):
46
+ if len(self.models) > 1:
47
+ kwargs["models"] = self.models
48
+ else:
49
+ kwargs["model"] = self.models[0]
50
+
51
+ return super().chat(*user_prompt, **kwargs)
@@ -32,8 +32,9 @@ class SiliconFlow(API):
32
32
  'Kwai-Kolors/Kolors'
33
33
  ]
34
34
 
35
- def __init__(self, api_key: str, model: str):
35
+ def __init__(self, api_key: str):
36
36
  super().__init__(api_key, 'https://api.siliconflow.cn')
37
- self.model = model
38
- def pre_request(self, headers: dict, data: dict):
39
- super().pre_request(headers, data)
37
+ self._.options['timeout'] = 50
38
+ def chat(self, *user_prompt: str, **kwargs):
39
+ kwargs['model'] = self.model
40
+ return super().chat(*user_prompt, **kwargs)
davidkhala/ai/model.py CHANGED
@@ -16,7 +16,7 @@ class AbstractClient(ABC):
16
16
  def as_embeddings(self, model: str):
17
17
  self.model = model
18
18
 
19
- def chat(self, user_prompt: str, **kwargs):
19
+ def chat(self, *user_prompt, **kwargs):
20
20
  ...
21
21
 
22
22
  def encode(self, *_input: str) -> List[List[float]]:
@@ -9,6 +9,7 @@ from davidkhala.ai.model import AbstractClient
9
9
  class Client(AbstractClient):
10
10
  client: OpenAI
11
11
  encoding_format: Literal["float", "base64"] = "float"
12
+ n = 1
12
13
 
13
14
  def connect(self):
14
15
  self.client.models.list()
@@ -21,31 +22,36 @@ class Client(AbstractClient):
21
22
  )
22
23
  return [item.embedding for item in response.data]
23
24
 
24
- def chat(self, user_prompt, image: str = None):
25
-
26
- message = {
27
- "role": "user"
28
- }
29
- if image is None:
30
- message['content'] = user_prompt
31
- else:
32
- message['content'] = [
33
- {"type": "text", "text": user_prompt},
34
- {
35
- "type": "image_url",
36
- "image_url": {
37
- "url": image,
38
- }
39
- },
40
- ]
25
+ def chat(self, *user_prompt):
26
+
27
+ messages = [
28
+ *self.messages,
29
+ ]
30
+ for prompt in user_prompt:
31
+ message = {
32
+ "role": "user"
33
+ }
34
+ if type(prompt) == str:
35
+ message['content'] = prompt
36
+ elif type(prompt) == dict:
37
+ message['content'] = [
38
+ {"type": "text", "text": prompt['text']},
39
+ {
40
+ "type": "image_url",
41
+ "image_url": {
42
+ "url": prompt['image_url'],
43
+ }
44
+ },
45
+ ]
46
+ messages.append(message)
41
47
  response = self.client.chat.completions.create(
42
48
  model=self.model,
43
- messages=[
44
- *self.messages,
45
- message
46
- ],
49
+ messages=messages,
50
+ n=self.n
47
51
  )
48
- return response.choices[0].message.content
52
+ contents = [choice.message.content for choice in response.choices]
53
+ assert len(contents) == self.n
54
+ return contents
49
55
 
50
56
  def disconnect(self):
51
57
  self.client.close()
@@ -1,13 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: davidkhala.ai
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: misc AI modules
5
- Requires-Python: ==3.13.*
6
- Requires-Dist: opik
5
+ Requires-Python: >=3.13
7
6
  Provides-Extra: ali
8
7
  Requires-Dist: dashscope; extra == 'ali'
9
8
  Provides-Extra: api
10
- Requires-Dist: requests; extra == 'api'
9
+ Requires-Dist: davidkhala-utils[http-request]; extra == 'api'
10
+ Provides-Extra: azure
11
+ Requires-Dist: openai; extra == 'azure'
11
12
  Provides-Extra: google
12
13
  Requires-Dist: google-adk; extra == 'google'
13
14
  Requires-Dist: google-genai; extra == 'google'
@@ -15,5 +16,5 @@ Provides-Extra: langchain
15
16
  Requires-Dist: langchain; extra == 'langchain'
16
17
  Requires-Dist: langchain-openai; extra == 'langchain'
17
18
  Requires-Dist: langgraph; extra == 'langchain'
18
- Provides-Extra: openai
19
- Requires-Dist: openai; extra == 'openai'
19
+ Provides-Extra: telemetry
20
+ Requires-Dist: opik; (python_version < '3.14') and extra == 'telemetry'
@@ -1,20 +1,19 @@
1
1
  davidkhala/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- davidkhala/ai/model.py,sha256=ujc88wQUP7SAyg9K8SQ45AuFOPFGtwAUyL05jYlUEBQ,657
2
+ davidkhala/ai/model.py,sha256=5rqnbb9sGJ9xDY31bRWx6O1d7nDqQjsYCeSDCm1Bv4A,653
3
3
  davidkhala/ai/opik.py,sha256=YU1XuweMUAzUkhpjxhltt-SBBDBkR3z-PCNo0DqzBRs,39
4
4
  davidkhala/ai/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- davidkhala/ai/agent/dify.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- davidkhala/ai/agent/langgraph.py,sha256=VEA2NQov_-KffTjgzO7nOIHiI4U9Y6enLTe3MQqKihc,1121
5
+ davidkhala/ai/agent/langgraph.py,sha256=jrc_Yvgo7eJjd3y5UJn0t1FzpnObDGYscwgsuVl2O_I,1052
7
6
  davidkhala/ai/ali/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
7
  davidkhala/ai/ali/dashscope.py,sha256=b49unSTVXhjHCA0F1Qt6_-Zs7zjSgEetIcVK5E2nDMQ,1968
9
- davidkhala/ai/api/__init__.py,sha256=IKT7TmMUrfk6bllpsXdBd-w13lHgWADU5Qk5ln01DLY,2018
10
- davidkhala/ai/api/open.py,sha256=_63n7Trrvj5pK-ZCud3HIJ-cpZWDAPpCQUjXGusFdU0,1221
11
- davidkhala/ai/api/siliconflow.py,sha256=uts5K1TpBOg8Eo6UZkNJWktQPoolfmn2sID01wiac6s,1314
8
+ davidkhala/ai/api/__init__.py,sha256=bcrAFJKlhLHW5CB0aqjQ04FginKVwifxVQeHF0nRRCE,1274
9
+ davidkhala/ai/api/openrouter.py,sha256=sQ5Yef9RccKB-qCCinK4uOooyasHmlJ6f2BtO_Ly0CQ,1870
10
+ davidkhala/ai/api/siliconflow.py,sha256=B5Zccpq1peNY5mQJS7duBEonj-aeQVxvmU7F7AVIN_s,1356
12
11
  davidkhala/ai/google/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
12
  davidkhala/ai/google/adk.py,sha256=QwxYoOzT2Hol03V4NM0PF_HAzUGb4fB18VUAYacYbAY,657
14
13
  davidkhala/ai/google/gemini.py,sha256=Xf4HDOOcK4-jEBERzuLnQNFsU61P2fFx4K0z-ijvNHE,214
15
- davidkhala/ai/openai/__init__.py,sha256=SvQvUgVgw5vRQtF4S3WUYqg1DOzGSQ7BtHvTSEwkmpY,1597
14
+ davidkhala/ai/openai/__init__.py,sha256=B0LG4h4cquK2sS4DqBywV-Kf6pwOVp8hU5H8hUp-pXA,1870
16
15
  davidkhala/ai/openai/azure.py,sha256=jAtZNW1d8ub3odhB_f1MXxM7MImg64MXDnqLm1-Idkk,828
17
16
  davidkhala/ai/openai/native.py,sha256=-XuHn0KSQnuFdJ4jTwZla1ts5iOW2L2Hosfo8o8iip8,257
18
- davidkhala_ai-0.0.2.dist-info/METADATA,sha256=rR3VkQp9l3KG5QVtiLIYZHHWVvwH4eoYBIdaa4p3wlQ,602
19
- davidkhala_ai-0.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- davidkhala_ai-0.0.2.dist-info/RECORD,,
17
+ davidkhala_ai-0.0.4.dist-info/METADATA,sha256=dGW0vJNd8_iDS_dXSQtvmkzCX2VqhZLFUVDWPKRgdfU,698
18
+ davidkhala_ai-0.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ davidkhala_ai-0.0.4.dist-info/RECORD,,
File without changes
davidkhala/ai/api/open.py DELETED
@@ -1,35 +0,0 @@
1
- from typing import TypedDict, Optional
2
-
3
- from davidkhala.ai.api import API
4
-
5
-
6
- class Leaderboard(TypedDict):
7
- url:Optional[str]
8
- name:Optional[str]
9
-
10
- class OpenRouter(API):
11
- @property
12
- def free_models(self) -> list[str]:
13
- return list(
14
- map(lambda model: model['id'],
15
- filter(lambda model: model['id'].endswith(':free'), self.list_models())
16
- )
17
- )
18
-
19
- def __init__(self, api_key: str, models: list[str] = None, *,
20
- leaderboard: Leaderboard = None):
21
-
22
- super().__init__(api_key, 'https://openrouter.ai/api')
23
- self.leaderboard = leaderboard
24
- if models is None:
25
- models = [self.free_models[0]]
26
- self.models = models
27
-
28
- def pre_request(self, headers: dict, data: dict):
29
- if self.leaderboard is not None:
30
- headers["HTTP-Referer"] = self.leaderboard['url'], # Optional. Site URL for rankings on openrouter.ai.
31
- headers["X-Title"] = self.leaderboard['name'], # Optional. Site title for rankings on openrouter.ai.
32
- if len(self.models) > 1:
33
- data["models"] = self.models
34
- else:
35
- data["model"] = self.models[0]