davidkhala.ai 0.1.4__py3-none-any.whl → 0.1.6__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/dify/api/__init__.py +27 -0
- davidkhala/ai/agent/dify/api/app.py +38 -0
- davidkhala/ai/agent/dify/{knowledge.py → api/knowledge.py} +10 -21
- davidkhala/ai/agent/dify/ops/__init__.py +1 -0
- davidkhala/ai/agent/dify/ops/db/__init__.py +40 -0
- davidkhala/ai/agent/dify/ops/db/orm.py +50 -0
- davidkhala/ai/agent/dify/plugin.py +10 -0
- davidkhala/ai/openrouter/__init__.py +27 -0
- {davidkhala_ai-0.1.4.dist-info → davidkhala_ai-0.1.6.dist-info}/METADATA +10 -2
- {davidkhala_ai-0.1.4.dist-info → davidkhala_ai-0.1.6.dist-info}/RECORD +11 -5
- davidkhala/ai/agent/dify/base.py +0 -7
- {davidkhala_ai-0.1.4.dist-info → davidkhala_ai-0.1.6.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Iterable, Callable, Any, Optional
|
|
2
|
+
|
|
3
|
+
from davidkhala.utils.http_request import Request
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class API(Request):
|
|
7
|
+
def __init__(self, api_key: str, base_url="https://api.dify.ai/v1"):
|
|
8
|
+
super().__init__({'bearer': api_key})
|
|
9
|
+
self.base_url = base_url
|
|
10
|
+
self.api_key = api_key
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Iterator(Iterable):
|
|
14
|
+
def __iter__(self):
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def __init__(self, get_fn: Callable[[int, int], Any], r: Optional[dict]):
|
|
18
|
+
self.response = r
|
|
19
|
+
self.fn = get_fn
|
|
20
|
+
|
|
21
|
+
def __next__(self):
|
|
22
|
+
if self.response and not self.response['has_more']:
|
|
23
|
+
raise StopIteration
|
|
24
|
+
page = 1 if not self.response else self.response['page'] + 1
|
|
25
|
+
limit = None if not self.response else self.response['limit']
|
|
26
|
+
self.response = self.fn(page, limit)
|
|
27
|
+
return self.response['data']
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from davidkhala.ai.agent.dify.api import API
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Feedbacks(API):
|
|
9
|
+
def paginate_feedbacks(self, page=1, size=20):
|
|
10
|
+
"""
|
|
11
|
+
when 'rating'='like', content=None
|
|
12
|
+
when 'rating'='dislike', content can be filled by end user
|
|
13
|
+
NOTE: for security reason, api cannot access conversation context associated with the feedback. End user should copy the conversation to comment by themselves.
|
|
14
|
+
"""
|
|
15
|
+
response = requests.get(f"{self.base_url}/app/feedbacks", params={"page": page, "limit": size}, **self.options)
|
|
16
|
+
if not response.ok:
|
|
17
|
+
response.raise_for_status()
|
|
18
|
+
else:
|
|
19
|
+
return json.loads(response.text)
|
|
20
|
+
|
|
21
|
+
def list_feedbacks(self):
|
|
22
|
+
# TODO https://github.com/langgenius/dify/issues/28067
|
|
23
|
+
return self.paginate_feedbacks()['data']
|
|
24
|
+
|
|
25
|
+
class Conversation(API):
|
|
26
|
+
"""
|
|
27
|
+
Note: The Service API does not share conversations created by the WebApp. Conversations created through the API are isolated from those created in the WebApp interface.
|
|
28
|
+
It means you cannot get user conversation content from API, API call has only access to conversation created by API
|
|
29
|
+
"""
|
|
30
|
+
def __init__(self, api_key: str, user: str):
|
|
31
|
+
super().__init__(api_key) # base_url need to be configured afterward if not default
|
|
32
|
+
self.user = user # user_id, from_end_user_id
|
|
33
|
+
|
|
34
|
+
def paginate_messages(self, conversation_id):
|
|
35
|
+
return self.request(f"{self.base_url}/messages", "GET", params={
|
|
36
|
+
'conversation_id': conversation_id,
|
|
37
|
+
'user': self.user,
|
|
38
|
+
})
|
|
@@ -2,12 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Iterable, TypedDict,
|
|
5
|
+
from typing import Iterable, TypedDict, Optional
|
|
6
6
|
from urllib.parse import urlparse
|
|
7
7
|
|
|
8
8
|
import requests
|
|
9
9
|
|
|
10
|
-
from davidkhala.ai.agent.dify.
|
|
10
|
+
from davidkhala.ai.agent.dify.api import API, Iterator
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class DatasetDict(TypedDict):
|
|
@@ -37,23 +37,6 @@ class DatasetDict(TypedDict):
|
|
|
37
37
|
external_knowledge_info: dict
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class Iterator(Iterable):
|
|
41
|
-
def __iter__(self):
|
|
42
|
-
return self
|
|
43
|
-
|
|
44
|
-
def __init__(self, get_fn: Callable[[int, int], Any], r: Optional[dict]):
|
|
45
|
-
self.response = r
|
|
46
|
-
self.fn = get_fn
|
|
47
|
-
|
|
48
|
-
def __next__(self):
|
|
49
|
-
if self.response and not self.response['has_more']:
|
|
50
|
-
raise StopIteration
|
|
51
|
-
page = 1 if not self.response else self.response['page'] + 1
|
|
52
|
-
limit = None if not self.response else self.response['limit']
|
|
53
|
-
self.response = self.fn(page, limit)
|
|
54
|
-
return self.response['data']
|
|
55
|
-
|
|
56
|
-
|
|
57
40
|
class DocumentDict(TypedDict):
|
|
58
41
|
id: str
|
|
59
42
|
position: int
|
|
@@ -178,12 +161,14 @@ class ChunkDict(TypedDict):
|
|
|
178
161
|
class Document(API):
|
|
179
162
|
def __init__(self, d: Dataset.Instance, document_id: str):
|
|
180
163
|
super().__init__(d.api_key, f"{d.base_url}/documents/{document_id}")
|
|
164
|
+
|
|
165
|
+
def exist(self):
|
|
181
166
|
try:
|
|
182
167
|
self.get()
|
|
183
|
-
|
|
168
|
+
return True
|
|
184
169
|
except requests.exceptions.HTTPError as e:
|
|
185
170
|
if e.response.status_code == 404:
|
|
186
|
-
|
|
171
|
+
return False
|
|
187
172
|
else:
|
|
188
173
|
raise e
|
|
189
174
|
|
|
@@ -200,3 +185,7 @@ class Document(API):
|
|
|
200
185
|
for chunk_batch in Iterator(self.paginate_chunks, None):
|
|
201
186
|
for chunk in chunk_batch:
|
|
202
187
|
yield chunk
|
|
188
|
+
|
|
189
|
+
def delete(self):
|
|
190
|
+
if self.exist():
|
|
191
|
+
self.request(self.base_url, "DELETE")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
from davidkhala.data.base.pg import Postgres
|
|
4
|
+
from sqlalchemy import desc
|
|
5
|
+
from sqlalchemy.orm import Session
|
|
6
|
+
|
|
7
|
+
from davidkhala.ai.agent.dify.ops.db.orm import AppModelConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DB(Postgres):
|
|
11
|
+
|
|
12
|
+
def __init__(self, connection_string: str):
|
|
13
|
+
super().__init__(connection_string)
|
|
14
|
+
self.connect()
|
|
15
|
+
|
|
16
|
+
def get_dict(self, sql): return self.query(sql).mappings().all()
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def accounts(self): return self.get_dict("select name, email from accounts where status = 'active'")
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def apps(self): return self.get_dict("select id, name, mode from apps where status = 'normal'")
|
|
23
|
+
|
|
24
|
+
def app_config(self, app_id) -> AppModelConfig | None:
|
|
25
|
+
with Session(self.client) as session:
|
|
26
|
+
return (
|
|
27
|
+
session.query(AppModelConfig)
|
|
28
|
+
.filter(AppModelConfig.app_id == app_id)
|
|
29
|
+
.order_by(desc(AppModelConfig.created_at))
|
|
30
|
+
.first()
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def update_app_config(self, record: AppModelConfig, refresh:bool=False) -> AppModelConfig | None:
|
|
34
|
+
with Session(self.client) as session:
|
|
35
|
+
session.add(record)
|
|
36
|
+
session.commit()
|
|
37
|
+
if refresh:
|
|
38
|
+
session.refresh(record) # 刷新对象,确保拿到数据库生成的字段(如 id)
|
|
39
|
+
return record
|
|
40
|
+
return None
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from sqlalchemy import (
|
|
2
|
+
Column, String, Text, JSON, TIMESTAMP,
|
|
3
|
+
func
|
|
4
|
+
)
|
|
5
|
+
from sqlalchemy.dialects.postgresql import UUID
|
|
6
|
+
from sqlalchemy.orm import declarative_base
|
|
7
|
+
|
|
8
|
+
Base = declarative_base()
|
|
9
|
+
|
|
10
|
+
class AppModelConfig(Base):
|
|
11
|
+
__tablename__ = "app_model_configs"
|
|
12
|
+
__table_args__ = {"schema": "public"}
|
|
13
|
+
|
|
14
|
+
id = Column(UUID(as_uuid=True), primary_key=True, server_default=func.uuid_generate_v4())
|
|
15
|
+
app_id = Column(UUID(as_uuid=True), nullable=False)
|
|
16
|
+
|
|
17
|
+
provider = Column(String(255))
|
|
18
|
+
model_id = Column(String(255))
|
|
19
|
+
configs = Column(JSON)
|
|
20
|
+
|
|
21
|
+
created_at = Column(TIMESTAMP, nullable=False, server_default=func.current_timestamp())
|
|
22
|
+
updated_at = Column(TIMESTAMP, nullable=False, server_default=func.current_timestamp())
|
|
23
|
+
|
|
24
|
+
opening_statement = Column(Text)
|
|
25
|
+
suggested_questions = Column(Text)
|
|
26
|
+
suggested_questions_after_answer = Column(Text)
|
|
27
|
+
more_like_this = Column(Text)
|
|
28
|
+
model = Column(Text)
|
|
29
|
+
user_input_form = Column(Text)
|
|
30
|
+
pre_prompt = Column(Text)
|
|
31
|
+
agent_mode = Column(Text)
|
|
32
|
+
speech_to_text = Column(Text)
|
|
33
|
+
sensitive_word_avoidance = Column(Text)
|
|
34
|
+
retriever_resource = Column(Text)
|
|
35
|
+
|
|
36
|
+
dataset_query_variable = Column(String(255))
|
|
37
|
+
prompt_type = Column(String(255), nullable=False, server_default="simple")
|
|
38
|
+
|
|
39
|
+
chat_prompt_config = Column(Text)
|
|
40
|
+
completion_prompt_config = Column(Text)
|
|
41
|
+
dataset_configs = Column(Text)
|
|
42
|
+
external_data_tools = Column(Text)
|
|
43
|
+
file_upload = Column(Text)
|
|
44
|
+
text_to_speech = Column(Text)
|
|
45
|
+
|
|
46
|
+
created_by = Column(UUID(as_uuid=True))
|
|
47
|
+
updated_by = Column(UUID(as_uuid=True))
|
|
48
|
+
|
|
49
|
+
def __repr__(self):
|
|
50
|
+
return f"<AppModelConfig(id={self.id}, app_id={self.app_id}, provider={self.provider}, model_id={self.model_id})>"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from openrouter.errors import UnauthorizedResponseError
|
|
2
|
+
|
|
3
|
+
from davidkhala.ai.model import AbstractClient
|
|
4
|
+
from openrouter import OpenRouter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Client(AbstractClient):
|
|
8
|
+
def __init__(self, api_key: str):
|
|
9
|
+
self.api_key = api_key
|
|
10
|
+
self.client = OpenRouter(api_key=api_key)
|
|
11
|
+
|
|
12
|
+
def chat(self, *user_prompt, **kwargs):
|
|
13
|
+
r = self.client.chat.send(
|
|
14
|
+
model=self.model,
|
|
15
|
+
messages=[
|
|
16
|
+
*self.messages,
|
|
17
|
+
*[{'role': 'user', 'content': _} for _ in user_prompt]
|
|
18
|
+
]
|
|
19
|
+
)
|
|
20
|
+
return [_.message.content for _ in r.choices]
|
|
21
|
+
def connect(self):
|
|
22
|
+
try:
|
|
23
|
+
self.client.api_keys.list()
|
|
24
|
+
return True
|
|
25
|
+
except UnauthorizedResponseError:
|
|
26
|
+
return False
|
|
27
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: davidkhala.ai
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: misc AI modules
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Provides-Extra: ali
|
|
@@ -9,6 +9,10 @@ Provides-Extra: api
|
|
|
9
9
|
Requires-Dist: davidkhala-utils[http-request]; extra == 'api'
|
|
10
10
|
Provides-Extra: azure
|
|
11
11
|
Requires-Dist: openai; extra == 'azure'
|
|
12
|
+
Provides-Extra: dify
|
|
13
|
+
Requires-Dist: davidkhala-databases[pg]; extra == 'dify'
|
|
14
|
+
Requires-Dist: davidkhala-utils[http-request]; extra == 'dify'
|
|
15
|
+
Requires-Dist: dify-plugin; extra == 'dify'
|
|
12
16
|
Provides-Extra: google
|
|
13
17
|
Requires-Dist: google-adk; extra == 'google'
|
|
14
18
|
Requires-Dist: google-genai; extra == 'google'
|
|
@@ -21,6 +25,9 @@ Provides-Extra: langchain
|
|
|
21
25
|
Requires-Dist: langchain; extra == 'langchain'
|
|
22
26
|
Requires-Dist: langchain-openai; extra == 'langchain'
|
|
23
27
|
Requires-Dist: langgraph; extra == 'langchain'
|
|
28
|
+
Provides-Extra: openrouter
|
|
29
|
+
Requires-Dist: davidkhala-utils[http-request]; extra == 'openrouter'
|
|
30
|
+
Requires-Dist: openrouter; extra == 'openrouter'
|
|
24
31
|
Provides-Extra: ragflow
|
|
25
32
|
Requires-Dist: ragflow-sdk; extra == 'ragflow'
|
|
26
33
|
Provides-Extra: telemetry
|
|
@@ -29,4 +36,5 @@ Description-Content-Type: text/markdown
|
|
|
29
36
|
|
|
30
37
|
# davidkhala.ai
|
|
31
38
|
|
|
32
|
-
For usage of `azure.ai.agents`, goto https://github.com/davidkhala/azure-utils/tree/main/py
|
|
39
|
+
- For usage of `azure.ai.agents`, goto https://github.com/davidkhala/azure-utils/tree/main/py
|
|
40
|
+
- [openrouter python sdk](https://openrouter.ai/docs/sdks/python)
|
|
@@ -6,8 +6,13 @@ davidkhala/ai/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
|
6
6
|
davidkhala/ai/agent/langgraph.py,sha256=jrc_Yvgo7eJjd3y5UJn0t1FzpnObDGYscwgsuVl2O_I,1052
|
|
7
7
|
davidkhala/ai/agent/ragflow.py,sha256=UaK31us6V0NhAPCthGo07rQsm72vlR-McmihC_NDe1g,273
|
|
8
8
|
davidkhala/ai/agent/dify/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
davidkhala/ai/agent/dify/
|
|
10
|
-
davidkhala/ai/agent/dify/
|
|
9
|
+
davidkhala/ai/agent/dify/plugin.py,sha256=wrX47gev8GBbWkF1g7h_9bx4UpgdC3OhhjRRAXw60zs,209
|
|
10
|
+
davidkhala/ai/agent/dify/api/__init__.py,sha256=9-8OesuXF_wPmPrh_gEZpEZP51dcZxb0i6ixOBYKcwQ,876
|
|
11
|
+
davidkhala/ai/agent/dify/api/app.py,sha256=CJT6fdUfLyuQkvtrFEbtfEcKWIBzhcQDYV4J3nKx-DQ,1624
|
|
12
|
+
davidkhala/ai/agent/dify/api/knowledge.py,sha256=cQPTS2S8DRfUKSECrLqFLC-PtObpYTGv2rHEvhkXW-k,5765
|
|
13
|
+
davidkhala/ai/agent/dify/ops/__init__.py,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
14
|
+
davidkhala/ai/agent/dify/ops/db/__init__.py,sha256=OXEUHs7unxRfw8ozwK_lUhV-SaOgCuEYM27q71F1nXU,1412
|
|
15
|
+
davidkhala/ai/agent/dify/ops/db/orm.py,sha256=NrmVn7oDcWiWw7mCzyJ_QPTTju8ayX3Ar21JICREGpg,1780
|
|
11
16
|
davidkhala/ai/ali/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
17
|
davidkhala/ai/ali/dashscope.py,sha256=SZIzRhVHlLx3s5I2RNUh2-u8OoSdrbvoN5e1k8Mh8N0,1943
|
|
13
18
|
davidkhala/ai/api/__init__.py,sha256=q2Ro5nhW5kJx2CYR1MRVamjTT5tTexPZwhrS2hwAvFM,1319
|
|
@@ -22,6 +27,7 @@ davidkhala/ai/huggingface/inference.py,sha256=bYN0PtLF2CaIHzdTP4LaTALJhcawvuLnLR
|
|
|
22
27
|
davidkhala/ai/openai/__init__.py,sha256=GXzWaw2ER3YFGHG6TPD9SmAHV6Tpsnqxj6tXlaWsrko,1897
|
|
23
28
|
davidkhala/ai/openai/azure.py,sha256=QR1uZj8qAyhpCjo3Ks5zNV8GfOp3-enyZs6fBvV-MkA,1110
|
|
24
29
|
davidkhala/ai/openai/native.py,sha256=MB0nDnzCOj_M42RMhdK3HTMVnxGnwpLT2GeLwSrepwI,704
|
|
25
|
-
|
|
26
|
-
davidkhala_ai-0.1.
|
|
27
|
-
davidkhala_ai-0.1.
|
|
30
|
+
davidkhala/ai/openrouter/__init__.py,sha256=5vciqhkPwQqBcHEwbuTeuwQgESqb6jsnQmb__EC4nWE,798
|
|
31
|
+
davidkhala_ai-0.1.6.dist-info/METADATA,sha256=bgODlj3_Ma0zhfSwxO-6So3k9L7tonkyQkpTz6sa0CU,1497
|
|
32
|
+
davidkhala_ai-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
33
|
+
davidkhala_ai-0.1.6.dist-info/RECORD,,
|
davidkhala/ai/agent/dify/base.py
DELETED
|
File without changes
|