davidkhala.ai 0.0.3__py3-none-any.whl → 0.0.5__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.
- davidkhala/ai/agent/langgraph.py +2 -4
- davidkhala/ai/api/__init__.py +27 -45
- davidkhala/ai/api/openrouter.py +60 -0
- davidkhala/ai/api/siliconflow.py +5 -2
- davidkhala/ai/model.py +1 -1
- davidkhala/ai/openai/__init__.py +28 -22
- {davidkhala_ai-0.0.3.dist-info → davidkhala_ai-0.0.5.dist-info}/METADATA +4 -4
- {davidkhala_ai-0.0.3.dist-info → davidkhala_ai-0.0.5.dist-info}/RECORD +9 -10
- davidkhala/ai/agent/dify.py +0 -0
- davidkhala/ai/api/open.py +0 -35
- {davidkhala_ai-0.0.3.dist-info → davidkhala_ai-0.0.5.dist-info}/WHEEL +0 -0
davidkhala/ai/agent/langgraph.py
CHANGED
|
@@ -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:
|
|
19
|
+
def __init__(self, api_key, leaderboard: dict = None):
|
|
22
20
|
self.api_key = api_key
|
|
23
21
|
|
|
24
|
-
if leaderboard
|
|
22
|
+
if leaderboard:
|
|
25
23
|
self.headers = {
|
|
26
24
|
"HTTP-Referer": leaderboard['url'],
|
|
27
25
|
"X-Title": leaderboard['name'],
|
davidkhala/ai/api/__init__.py
CHANGED
|
@@ -1,65 +1,47 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from abc import abstractmethod
|
|
2
|
+
from abc import abstractmethod
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
from davidkhala.http_request import Request
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
from davidkhala.ai.model import AbstractClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class API(AbstractClient):
|
|
8
10
|
def __init__(self, api_key: str, base_url: str):
|
|
9
|
-
self.
|
|
10
|
-
self.
|
|
11
|
-
self.
|
|
12
|
-
|
|
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
|
-
def
|
|
20
|
-
data["model"] = self.model
|
|
21
|
-
def chat(self, prompt, system_prompt: str = None):
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
def chat(self, *user_prompt: str, **kwargs):
|
|
24
21
|
messages = [
|
|
25
|
-
|
|
22
|
+
*self.messages,
|
|
23
|
+
*[{
|
|
26
24
|
"role": "user",
|
|
27
|
-
"content":
|
|
28
|
-
}
|
|
25
|
+
"content": _
|
|
26
|
+
} for _ in user_prompt],
|
|
29
27
|
]
|
|
30
|
-
|
|
31
|
-
messages.append({
|
|
32
|
-
"role": "system",
|
|
33
|
-
"content": system_prompt
|
|
34
|
-
})
|
|
28
|
+
|
|
35
29
|
json = {
|
|
36
|
-
"messages": messages
|
|
30
|
+
"messages": messages,
|
|
31
|
+
**kwargs,
|
|
37
32
|
}
|
|
38
|
-
self.pre_request(self.headers, json)
|
|
39
|
-
# timeout=50 to cater siliconflow
|
|
40
|
-
response = requests.post(f"{self.base_url}/chat/completions", headers=self.headers, json=json, timeout=50)
|
|
41
|
-
parsed_response = API.parse(response)
|
|
42
33
|
|
|
34
|
+
response = self._.request(f"{self.base_url}/chat/completions", "POST", json=json)
|
|
43
35
|
|
|
44
36
|
return {
|
|
45
|
-
"data": list(map(lambda x: x['message']['content'],
|
|
37
|
+
"data": list(map(lambda x: x['message']['content'], response['choices'])),
|
|
46
38
|
"meta": {
|
|
47
|
-
"usage":
|
|
48
|
-
"created": datetime.datetime.fromtimestamp(
|
|
49
|
-
}
|
|
39
|
+
"usage": response['usage'],
|
|
40
|
+
"created": datetime.datetime.fromtimestamp(response['created'])
|
|
41
|
+
},
|
|
42
|
+
'model': response['model'],
|
|
50
43
|
}
|
|
51
|
-
@staticmethod
|
|
52
|
-
def parse(response):
|
|
53
|
-
parsed_response = response.json()
|
|
54
44
|
|
|
55
|
-
match parsed_response:
|
|
56
|
-
case dict():
|
|
57
|
-
err = parsed_response.get('error')
|
|
58
|
-
if err is not None:
|
|
59
|
-
raise Exception(err)
|
|
60
|
-
case str():
|
|
61
|
-
raise Exception(parsed_response)
|
|
62
|
-
return parsed_response
|
|
63
45
|
def list_models(self):
|
|
64
|
-
response =
|
|
65
|
-
return
|
|
46
|
+
response = self._.request(f"{self.base_url}/models", "GET")
|
|
47
|
+
return response['data']
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from davidkhala.http_request import default_on_response
|
|
5
|
+
from requests import Response
|
|
6
|
+
|
|
7
|
+
from davidkhala.ai.api import API
|
|
8
|
+
|
|
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
|
+
@staticmethod
|
|
20
|
+
def on_response(response: requests.Response):
|
|
21
|
+
r = default_on_response(response)
|
|
22
|
+
# openrouter special error on response.ok
|
|
23
|
+
err = r.get('error')
|
|
24
|
+
if err:
|
|
25
|
+
derived_response = Response()
|
|
26
|
+
derived_response.status_code = err['code']
|
|
27
|
+
derived_response.reason = err['message']
|
|
28
|
+
derived_response.metadata = err.get("metadata")
|
|
29
|
+
|
|
30
|
+
derived_response.raise_for_status()
|
|
31
|
+
return r
|
|
32
|
+
|
|
33
|
+
def __init__(self, api_key: str, *models: str, **kwargs):
|
|
34
|
+
|
|
35
|
+
super().__init__(api_key, 'https://openrouter.ai/api')
|
|
36
|
+
|
|
37
|
+
if 'leaderboard' in kwargs and type(kwargs['leaderboard']) is dict:
|
|
38
|
+
self._.options["headers"]["HTTP-Referer"] = kwargs['leaderboard'][
|
|
39
|
+
'url'] # Site URL for rankings on openrouter.ai.
|
|
40
|
+
self._.options["headers"]["X-Title"] = kwargs['leaderboard'][
|
|
41
|
+
'name'] # Site title for rankings on openrouter.ai.
|
|
42
|
+
self.models = models
|
|
43
|
+
|
|
44
|
+
self._.on_response = OpenRouter.on_response
|
|
45
|
+
|
|
46
|
+
def chat(self, *user_prompt: str, **kwargs):
|
|
47
|
+
if self.models:
|
|
48
|
+
kwargs["models"] = self.models
|
|
49
|
+
else:
|
|
50
|
+
kwargs["model"] = self.model
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
r = super().chat(*user_prompt, **kwargs)
|
|
54
|
+
except requests.HTTPError as e:
|
|
55
|
+
if e.response.status_code == 429 and kwargs.get('retry'):
|
|
56
|
+
time.sleep(1)
|
|
57
|
+
return self.chat(*user_prompt, **kwargs)
|
|
58
|
+
if self.models:
|
|
59
|
+
assert r['model'] in self.models
|
|
60
|
+
return r
|
davidkhala/ai/api/siliconflow.py
CHANGED
|
@@ -32,6 +32,9 @@ class SiliconFlow(API):
|
|
|
32
32
|
'Kwai-Kolors/Kolors'
|
|
33
33
|
]
|
|
34
34
|
|
|
35
|
-
def __init__(self, api_key: str
|
|
35
|
+
def __init__(self, api_key: str):
|
|
36
36
|
super().__init__(api_key, 'https://api.siliconflow.cn')
|
|
37
|
-
self.
|
|
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
davidkhala/ai/openai/__init__.py
CHANGED
|
@@ -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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
message
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
45
|
-
message
|
|
46
|
-
],
|
|
49
|
+
messages=messages,
|
|
50
|
+
n=self.n
|
|
47
51
|
)
|
|
48
|
-
|
|
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,12 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: davidkhala.ai
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: misc AI modules
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Provides-Extra: ali
|
|
7
7
|
Requires-Dist: dashscope; extra == 'ali'
|
|
8
8
|
Provides-Extra: api
|
|
9
|
-
Requires-Dist:
|
|
9
|
+
Requires-Dist: davidkhala-utils[http-request]; extra == 'api'
|
|
10
|
+
Provides-Extra: azure
|
|
11
|
+
Requires-Dist: openai; extra == 'azure'
|
|
10
12
|
Provides-Extra: google
|
|
11
13
|
Requires-Dist: google-adk; extra == 'google'
|
|
12
14
|
Requires-Dist: google-genai; extra == 'google'
|
|
@@ -14,7 +16,5 @@ Provides-Extra: langchain
|
|
|
14
16
|
Requires-Dist: langchain; extra == 'langchain'
|
|
15
17
|
Requires-Dist: langchain-openai; extra == 'langchain'
|
|
16
18
|
Requires-Dist: langgraph; extra == 'langchain'
|
|
17
|
-
Provides-Extra: openai
|
|
18
|
-
Requires-Dist: openai; extra == 'openai'
|
|
19
19
|
Provides-Extra: telemetry
|
|
20
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=
|
|
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/
|
|
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=
|
|
10
|
-
davidkhala/ai/api/
|
|
11
|
-
davidkhala/ai/api/siliconflow.py,sha256=
|
|
8
|
+
davidkhala/ai/api/__init__.py,sha256=Ekz1jPAkDPzeNmhlp5HUSFjbeuniXLd_y5JlpGJUEcY,1316
|
|
9
|
+
davidkhala/ai/api/openrouter.py,sha256=HmXjU_yrsejtviL2aIjVj-A3kgMqEMMtI0yIwgvJJ7w,2047
|
|
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=
|
|
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.
|
|
19
|
-
davidkhala_ai-0.0.
|
|
20
|
-
davidkhala_ai-0.0.
|
|
17
|
+
davidkhala_ai-0.0.5.dist-info/METADATA,sha256=UaT2haHwRMJrmqRSWx775DgLHdK1jrFHjkrKu9TJyAM,698
|
|
18
|
+
davidkhala_ai-0.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
19
|
+
davidkhala_ai-0.0.5.dist-info/RECORD,,
|
davidkhala/ai/agent/dify.py
DELETED
|
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
|
-
# TODO Hard to multi-model supports here
|
|
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]
|
|
File without changes
|