davidkhala.ai 0.1.6__py3-none-any.whl → 0.1.9__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/app.py +62 -2
- davidkhala/ai/agent/dify/api/knowledge.py +13 -13
- davidkhala/ai/agent/dify/const.py +10 -0
- davidkhala/ai/agent/dify/interface.py +3 -0
- davidkhala/ai/agent/dify/model.py +31 -0
- davidkhala/ai/agent/dify/ops/console/__init__.py +9 -0
- davidkhala/ai/agent/dify/ops/console/knowledge.py +158 -0
- davidkhala/ai/agent/dify/ops/console/session.py +30 -0
- davidkhala/ai/agent/dify/ops/db/__init__.py +7 -30
- davidkhala/ai/agent/dify/ops/db/app.py +34 -0
- davidkhala/ai/agent/dify/ops/db/knowledge.py +58 -0
- davidkhala/ai/agent/dify/ops/db/orm.py +107 -6
- davidkhala/ai/agent/dify/ops/db/sys.py +6 -0
- davidkhala/ai/agent/dify/plugins/__init__.py +7 -0
- davidkhala/ai/agent/dify/plugins/file.py +19 -0
- davidkhala/ai/agent/dify/plugins/firecrawl.py +22 -0
- davidkhala/ai/agent/dify/plugins/jina.py +4 -0
- davidkhala/ai/ali/agentbay.py +39 -0
- davidkhala/ai/openai/azure.py +2 -3
- davidkhala/ai/openrouter/__init__.py +10 -2
- {davidkhala_ai-0.1.6.dist-info → davidkhala_ai-0.1.9.dist-info}/METADATA +6 -4
- davidkhala_ai-0.1.9.dist-info/RECORD +46 -0
- {davidkhala_ai-0.1.6.dist-info → davidkhala_ai-0.1.9.dist-info}/WHEEL +1 -1
- davidkhala/ai/agent/dify/plugin.py +0 -10
- davidkhala_ai-0.1.6.dist-info/RECORD +0 -33
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from typing import TypedDict
|
|
2
3
|
|
|
3
4
|
import requests
|
|
5
|
+
from davidkhala.utils.http_request.stream import Request as StreamRequest, as_sse
|
|
6
|
+
from requests import Response, Session
|
|
4
7
|
|
|
5
8
|
from davidkhala.ai.agent.dify.api import API
|
|
6
9
|
|
|
@@ -11,6 +14,7 @@ class Feedbacks(API):
|
|
|
11
14
|
when 'rating'='like', content=None
|
|
12
15
|
when 'rating'='dislike', content can be filled by end user
|
|
13
16
|
NOTE: for security reason, api cannot access conversation context associated with the feedback. End user should copy the conversation to comment by themselves.
|
|
17
|
+
# waiting for https://github.com/langgenius/dify/issues/28067
|
|
14
18
|
"""
|
|
15
19
|
response = requests.get(f"{self.base_url}/app/feedbacks", params={"page": page, "limit": size}, **self.options)
|
|
16
20
|
if not response.ok:
|
|
@@ -19,16 +23,17 @@ class Feedbacks(API):
|
|
|
19
23
|
return json.loads(response.text)
|
|
20
24
|
|
|
21
25
|
def list_feedbacks(self):
|
|
22
|
-
# TODO https://github.com/langgenius/dify/issues/28067
|
|
23
26
|
return self.paginate_feedbacks()['data']
|
|
24
27
|
|
|
28
|
+
|
|
25
29
|
class Conversation(API):
|
|
26
30
|
"""
|
|
27
31
|
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
32
|
It means you cannot get user conversation content from API, API call has only access to conversation created by API
|
|
29
33
|
"""
|
|
34
|
+
|
|
30
35
|
def __init__(self, api_key: str, user: str):
|
|
31
|
-
super().__init__(api_key)
|
|
36
|
+
super().__init__(api_key) # base_url need to be configured afterward if not default
|
|
32
37
|
self.user = user # user_id, from_end_user_id
|
|
33
38
|
|
|
34
39
|
def paginate_messages(self, conversation_id):
|
|
@@ -36,3 +41,58 @@ class Conversation(API):
|
|
|
36
41
|
'conversation_id': conversation_id,
|
|
37
42
|
'user': self.user,
|
|
38
43
|
})
|
|
44
|
+
|
|
45
|
+
def _chat_request_from(self, template: str, stream, **kwargs):
|
|
46
|
+
"""
|
|
47
|
+
:param template:
|
|
48
|
+
:param stream: Note: "Agent Chat App does not support blocking mode"
|
|
49
|
+
:param kwargs:
|
|
50
|
+
:return:
|
|
51
|
+
"""
|
|
52
|
+
return {
|
|
53
|
+
'url': f"{self.base_url}/chat-messages",
|
|
54
|
+
'method': "POST",
|
|
55
|
+
'json': {
|
|
56
|
+
'query': template,
|
|
57
|
+
'inputs': kwargs.pop('values', {}), # to substitute query/template
|
|
58
|
+
'response_mode': 'streaming' if stream else 'blocking',
|
|
59
|
+
'conversation_id': kwargs.pop('conversation_id', None),
|
|
60
|
+
'user': self.user,
|
|
61
|
+
'files': kwargs.pop('files', [])
|
|
62
|
+
},
|
|
63
|
+
**kwargs
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def async_chat(self, template: str, **kwargs) -> tuple[Response, Session]:
|
|
67
|
+
s = StreamRequest(self)
|
|
68
|
+
s.session = Session()
|
|
69
|
+
return s.request(**self._chat_request_from(template, True, **kwargs)), s.session
|
|
70
|
+
|
|
71
|
+
class ChatResult(TypedDict, total=False):
|
|
72
|
+
thought: list[str]
|
|
73
|
+
metadata: dict
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def reduce_chat_stream(response: Response) -> ChatResult:
|
|
77
|
+
r: Conversation.ChatResult = {
|
|
78
|
+
'thought': [],
|
|
79
|
+
}
|
|
80
|
+
for data in as_sse(response):
|
|
81
|
+
match data['event']:
|
|
82
|
+
case 'agent_thought':
|
|
83
|
+
r['thought'].append(data['thought'])
|
|
84
|
+
case 'message_end':
|
|
85
|
+
r['metadata'] = data['metadata']
|
|
86
|
+
return r
|
|
87
|
+
|
|
88
|
+
def agent_chat(self, template: str, **kwargs) -> ChatResult:
|
|
89
|
+
r, session = self.async_chat(template, **kwargs)
|
|
90
|
+
reduced = Conversation.reduce_chat_stream(r)
|
|
91
|
+
session.close()
|
|
92
|
+
return reduced
|
|
93
|
+
|
|
94
|
+
def bot_chat(self, template: str, **kwargs):
|
|
95
|
+
r = self.request(**self._chat_request_from(template, False, **kwargs))
|
|
96
|
+
assert r.pop('event') == 'message'
|
|
97
|
+
assert r.pop('mode') == 'chat'
|
|
98
|
+
return r
|
|
@@ -8,6 +8,7 @@ from urllib.parse import urlparse
|
|
|
8
8
|
import requests
|
|
9
9
|
|
|
10
10
|
from davidkhala.ai.agent.dify.api import API, Iterator
|
|
11
|
+
from davidkhala.ai.agent.dify.model import Document as DocumentBase
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class DatasetDict(TypedDict):
|
|
@@ -37,21 +38,14 @@ class DatasetDict(TypedDict):
|
|
|
37
38
|
external_knowledge_info: dict
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
class
|
|
41
|
-
id: str
|
|
42
|
-
position: int
|
|
43
|
-
data_source_type: str
|
|
41
|
+
class Document(DocumentBase):
|
|
44
42
|
data_source_info: dict[str, str]
|
|
45
43
|
data_source_detail_dict: dict[str, dict]
|
|
46
44
|
dataset_process_rule_id: str
|
|
47
|
-
name: str
|
|
48
45
|
created_from: str
|
|
49
46
|
created_by: str
|
|
50
47
|
created_at: int
|
|
51
48
|
tokens: int
|
|
52
|
-
indexing_status: str
|
|
53
|
-
error: str
|
|
54
|
-
enabled: bool
|
|
55
49
|
archived: bool
|
|
56
50
|
display_status: str
|
|
57
51
|
word_count: int
|
|
@@ -91,9 +85,8 @@ class Dataset(API):
|
|
|
91
85
|
|
|
92
86
|
def upload(self, filename, *, path=None, url=None, document_id=None):
|
|
93
87
|
"""
|
|
94
|
-
don't work for html
|
|
95
|
-
work for
|
|
96
|
-
TODO how to simulate console
|
|
88
|
+
don't work for .html
|
|
89
|
+
work for .md
|
|
97
90
|
"""
|
|
98
91
|
files = {}
|
|
99
92
|
if path:
|
|
@@ -124,10 +117,10 @@ class Dataset(API):
|
|
|
124
117
|
'limit': size
|
|
125
118
|
})
|
|
126
119
|
|
|
127
|
-
def list_documents(self) -> Iterable[
|
|
120
|
+
def list_documents(self) -> Iterable[Document]:
|
|
128
121
|
for document_batch in Iterator(self.paginate_documents, None):
|
|
129
122
|
for document in document_batch:
|
|
130
|
-
yield document
|
|
123
|
+
yield Document(**document)
|
|
131
124
|
|
|
132
125
|
def has_document(self, name) -> bool:
|
|
133
126
|
return any(name == item['name'] for row in self.list_documents() for item in row)
|
|
@@ -189,3 +182,10 @@ class Document(API):
|
|
|
189
182
|
def delete(self):
|
|
190
183
|
if self.exist():
|
|
191
184
|
self.request(self.base_url, "DELETE")
|
|
185
|
+
class Chunk(API):
|
|
186
|
+
def __init__(self, d: Document, segment_id: str):
|
|
187
|
+
super().__init__(d.api_key, f"{d.base_url}/segments/{segment_id}")
|
|
188
|
+
def get(self):
|
|
189
|
+
r= self.request(self.base_url, "GET")
|
|
190
|
+
assert r['doc_form'] # optional value text_model
|
|
191
|
+
return r['data']
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
from davidkhala.ai.agent.dify.const import IndexingStatus
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Document(BaseModel):
|
|
7
|
+
id: str
|
|
8
|
+
position: int
|
|
9
|
+
data_source_type: str
|
|
10
|
+
data_source_info: dict[str, str]
|
|
11
|
+
name: str
|
|
12
|
+
indexing_status: IndexingStatus
|
|
13
|
+
error: str | None
|
|
14
|
+
enabled: bool
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Dataset(BaseModel):
|
|
18
|
+
id: str
|
|
19
|
+
name: str
|
|
20
|
+
description: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class JsonData(BaseModel):
|
|
24
|
+
data: list
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NodeOutput(BaseModel):
|
|
28
|
+
"""Schema for Output of a Dify node"""
|
|
29
|
+
text: str
|
|
30
|
+
files: list
|
|
31
|
+
json_: list[JsonData] = Field(alias="json") # avoid conflict with .json()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from time import sleep
|
|
2
|
+
|
|
3
|
+
from davidkhala.utils.http_request.stream import as_sse, Request as StreamRequest
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from davidkhala.ai.agent.dify.interface import IndexingError
|
|
7
|
+
from davidkhala.ai.agent.dify.model import Document, Dataset
|
|
8
|
+
from davidkhala.ai.agent.dify.const import IndexingStatus
|
|
9
|
+
from davidkhala.ai.agent.dify.ops.console import API
|
|
10
|
+
from davidkhala.ai.agent.dify.ops.console.session import ConsoleUser
|
|
11
|
+
from davidkhala.ai.agent.dify.ops.db.orm import Node
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConsoleKnowledge(API):
|
|
15
|
+
def __init__(self, context: ConsoleUser):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.base_url = context.base_url
|
|
18
|
+
self.session.cookies = context.session.cookies
|
|
19
|
+
self.options = context.options
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Datasource(ConsoleKnowledge):
|
|
23
|
+
"""step 1: Choose a Data Source"""
|
|
24
|
+
|
|
25
|
+
class FirecrawlOutput(BaseModel):
|
|
26
|
+
source_url: str
|
|
27
|
+
description: str
|
|
28
|
+
title: str
|
|
29
|
+
credential_id: str
|
|
30
|
+
content: str
|
|
31
|
+
|
|
32
|
+
def run_firecrawl(self, pipeline: str, node: Node,
|
|
33
|
+
*,
|
|
34
|
+
inputs: dict,
|
|
35
|
+
credential_id: str
|
|
36
|
+
):
|
|
37
|
+
|
|
38
|
+
url = f"{self.base_url}/rag/pipelines/{pipeline}/workflows/published/datasource/nodes/{node.id}/run"
|
|
39
|
+
|
|
40
|
+
stream_request = StreamRequest(self)
|
|
41
|
+
response = stream_request.request(url, 'POST', json={
|
|
42
|
+
'inputs': inputs,
|
|
43
|
+
'datasource_type': node.datasource_type,
|
|
44
|
+
'credential_id': credential_id,
|
|
45
|
+
"response_mode": "streaming"
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
for data in as_sse(response):
|
|
49
|
+
event = data['event']
|
|
50
|
+
if event == 'datasource_completed':
|
|
51
|
+
return data['data']
|
|
52
|
+
else:
|
|
53
|
+
assert event == 'datasource_processing'
|
|
54
|
+
print(data)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def upload(self):
|
|
58
|
+
"http://localhost/console/api/files/upload?source=datasets"
|
|
59
|
+
# TODO
|
|
60
|
+
"form data"
|
|
61
|
+
{
|
|
62
|
+
"file": "body"
|
|
63
|
+
}
|
|
64
|
+
r = {
|
|
65
|
+
"id": "3898db5b-eb72-4f11-b507-628ad5d28887",
|
|
66
|
+
"name": "Professional Diploma Meister Power Electrical Engineering - Technological and Higher Education Institute of Hong Kong.html",
|
|
67
|
+
"size": 254362,
|
|
68
|
+
"extension": "html",
|
|
69
|
+
"mime_type": "text\/html",
|
|
70
|
+
"created_by": "dbd0b38b-5ef1-4123-8c3f-0c82eb1feacd",
|
|
71
|
+
"created_at": 1764943811,
|
|
72
|
+
"source_url": "\/files\/3898db5b-eb72-4f11-b507-628ad5d28887\/file-preview?timestamp=1764943811&nonce=43b0ff5a13372415be79de4cc7ef398c&sign=7OJ2wiVYc4tygl7yvM1sPn7s0WXDlhHxgX76bsGTD94%3D"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Operation(ConsoleKnowledge):
|
|
77
|
+
def website_sync(self, dataset: str, document: str, *, wait_until=True):
|
|
78
|
+
"""
|
|
79
|
+
cannot be used towards a pipeline dataset. Otherwise, you will see error "no website import info found"
|
|
80
|
+
"""
|
|
81
|
+
doc_url = f"{self.base_url}/datasets/{dataset}/documents/{document}"
|
|
82
|
+
|
|
83
|
+
r = self.request(f"{doc_url}/website-sync", "GET")
|
|
84
|
+
assert r == {"result": "success"}
|
|
85
|
+
if wait_until:
|
|
86
|
+
return self.wait_until(dataset, document)
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def retry(self, dataset: str, *documents: str, wait_until=True):
|
|
90
|
+
"""
|
|
91
|
+
It cannot trigger rerun on success documents
|
|
92
|
+
"""
|
|
93
|
+
url = f"{self.base_url}/datasets/{dataset}/retry"
|
|
94
|
+
self.request(url, "POST", json={
|
|
95
|
+
'document_ids': documents,
|
|
96
|
+
})
|
|
97
|
+
# response status code will be 204
|
|
98
|
+
if wait_until:
|
|
99
|
+
return [self.wait_until(dataset, document) for document in documents]
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
def rerun(self, dataset: str, *documents: str):
|
|
103
|
+
for document in documents:
|
|
104
|
+
try:
|
|
105
|
+
self.website_sync(dataset, document)
|
|
106
|
+
assert False, "expect IndexingError"
|
|
107
|
+
except IndexingError:
|
|
108
|
+
pass
|
|
109
|
+
return self.retry(dataset, *documents)
|
|
110
|
+
|
|
111
|
+
def wait_until(self, dataset: str, document: str, *,
|
|
112
|
+
expect_status=None,
|
|
113
|
+
from_status=None,
|
|
114
|
+
interval=1
|
|
115
|
+
):
|
|
116
|
+
if not expect_status:
|
|
117
|
+
expect_status = [IndexingStatus.FAILED, IndexingStatus.COMPLETED]
|
|
118
|
+
url = f"{self.base_url}/datasets/{dataset}/documents/{document}/indexing-status"
|
|
119
|
+
if from_status is None:
|
|
120
|
+
from_status = [IndexingStatus.WAITING, IndexingStatus.PARSING]
|
|
121
|
+
r = self.request(url, "GET")
|
|
122
|
+
status = r['indexing_status']
|
|
123
|
+
assert status in from_status, f"current status: {status}, expect: {from_status}"
|
|
124
|
+
while status not in expect_status:
|
|
125
|
+
sleep(interval)
|
|
126
|
+
r = self.request(url, "GET")
|
|
127
|
+
status = r['indexing_status']
|
|
128
|
+
if status == IndexingStatus.FAILED: raise IndexingError(r['error'])
|
|
129
|
+
return r
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class DatasetResult(Dataset):
|
|
133
|
+
chunk_structure: str
|
|
134
|
+
|
|
135
|
+
class RunResult(BaseModel):
|
|
136
|
+
batch: str
|
|
137
|
+
dataset: DatasetResult
|
|
138
|
+
documents: list[Document]
|
|
139
|
+
|
|
140
|
+
class Load(ConsoleKnowledge):
|
|
141
|
+
"""
|
|
142
|
+
Processing Documents
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def async_run(self, pipeline: str, node: Node, inputs: dict, datasource_info_list: list[dict])->RunResult:
|
|
146
|
+
"""Ingest new document"""
|
|
147
|
+
url = f"{self.base_url}/rag/pipelines/{pipeline}/workflows/published/run"
|
|
148
|
+
r = self.request(url, "POST", json={
|
|
149
|
+
'inputs': inputs,
|
|
150
|
+
'start_node_id': node.id,
|
|
151
|
+
'is_preview': False,
|
|
152
|
+
'response_mode': "blocking",
|
|
153
|
+
"datasource_info_list": datasource_info_list,
|
|
154
|
+
'datasource_type': node.datasource_type
|
|
155
|
+
})
|
|
156
|
+
return RunResult(**r)
|
|
157
|
+
|
|
158
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from davidkhala.ai.agent.dify.ops.console import API
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConsoleUser(API):
|
|
5
|
+
def login(self, email, password,
|
|
6
|
+
*,
|
|
7
|
+
remember_me=True,
|
|
8
|
+
language="en-US"
|
|
9
|
+
):
|
|
10
|
+
url = f"{self.base_url}/login"
|
|
11
|
+
|
|
12
|
+
r = self.request(url, "POST", json={
|
|
13
|
+
'email': email,
|
|
14
|
+
'password': password,
|
|
15
|
+
'remember_me': remember_me,
|
|
16
|
+
'language': language,
|
|
17
|
+
})
|
|
18
|
+
assert r == {"result": "success"}
|
|
19
|
+
self.options['headers']['x-csrf-token'] = self.session.cookies.get("csrf_token")
|
|
20
|
+
return self.session.cookies
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def me(self) -> dict:
|
|
24
|
+
url = f"{self.base_url}/account/profile"
|
|
25
|
+
return self.request(url, "GET")
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def workspace(self) -> dict:
|
|
29
|
+
url = f"{self.base_url}/features"
|
|
30
|
+
return self.request(url, "GET")
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
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
4
|
|
|
9
5
|
|
|
10
6
|
class DB(Postgres):
|
|
@@ -13,28 +9,9 @@ class DB(Postgres):
|
|
|
13
9
|
super().__init__(connection_string)
|
|
14
10
|
self.connect()
|
|
15
11
|
|
|
16
|
-
def get_dict(self,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
12
|
+
def get_dict(self,
|
|
13
|
+
template: str,
|
|
14
|
+
values: dict[str, Any] | None = None,
|
|
15
|
+
request_options: dict[str, Any] | None = None
|
|
16
|
+
) -> list[dict]:
|
|
17
|
+
return Postgres.rows_to_dicts(self.query(template, values, request_options))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from davidkhala.ai.agent.dify.ops.db import DB
|
|
2
|
+
from davidkhala.ai.agent.dify.ops.db.orm import AppModelConfig
|
|
3
|
+
from sqlalchemy.orm import Session
|
|
4
|
+
from sqlalchemy import desc
|
|
5
|
+
|
|
6
|
+
class Studio(DB):
|
|
7
|
+
user_feedbacks_sql = """SELECT mf.conversation_id,
|
|
8
|
+
mf.content,
|
|
9
|
+
m.query,
|
|
10
|
+
m.answer
|
|
11
|
+
FROM message_feedbacks mf
|
|
12
|
+
LEFT JOIN messages m ON mf.message_id = m.id
|
|
13
|
+
WHERE mf.from_source = 'user'"""
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def apps(self): return self.get_dict("select id, name, mode from apps where status = 'normal'")
|
|
17
|
+
|
|
18
|
+
def app_config(self, app_id) -> AppModelConfig | None:
|
|
19
|
+
with Session(self.client) as session:
|
|
20
|
+
return (
|
|
21
|
+
session.query(AppModelConfig)
|
|
22
|
+
.filter(AppModelConfig.app_id == app_id)
|
|
23
|
+
.order_by(desc(AppModelConfig.created_at))
|
|
24
|
+
.first()
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def update_app_config(self, record: AppModelConfig, refresh: bool = False) -> AppModelConfig | None:
|
|
28
|
+
with Session(self.client) as session:
|
|
29
|
+
session.add(record)
|
|
30
|
+
session.commit()
|
|
31
|
+
if refresh:
|
|
32
|
+
session.refresh(record)
|
|
33
|
+
return record
|
|
34
|
+
return None
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from davidkhala.ai.agent.dify.ops.db import DB
|
|
2
|
+
from davidkhala.ai.agent.dify.ops.db.orm import Graph
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Dataset(DB):
|
|
6
|
+
|
|
7
|
+
def dataset_queries(self, dataset_id, limit=20) -> list[str]:
|
|
8
|
+
template = "select content from dataset_queries where source = 'app' and created_by_role = 'end_user' and dataset_id = :dataset_id limit :limit"
|
|
9
|
+
return self.query(template, {'dataset_id': dataset_id, 'limit': limit}).scalars().all()
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def datasets(self):
|
|
13
|
+
template = "select id, name, description, indexing_technique, index_struct, embedding_model, embedding_model_provider, collection_binding_id, retrieval_model, icon_info, runtime_mode, pipeline_id, chunk_structure from datasets"
|
|
14
|
+
return self.get_dict(template)
|
|
15
|
+
|
|
16
|
+
def is_pipeline(self, id: str):
|
|
17
|
+
template = "select runtime_mode = 'rag_pipeline' from datasets where id = :id"
|
|
18
|
+
return self.query(template, {'id': id}).scalar()
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def data_source_credentials(self):
|
|
22
|
+
template = "select id, name, plugin_id, auth_type from datasource_providers"
|
|
23
|
+
return self.get_dict(template)
|
|
24
|
+
|
|
25
|
+
def credential_id_by(self, name, provider) -> list[str]:
|
|
26
|
+
template = "select id from datasource_providers where name = :name and provider = :provider"
|
|
27
|
+
return self.query(template, {'name': name, 'provider': provider}).scalars().all()
|
|
28
|
+
|
|
29
|
+
def documents(self, dataset_id: str):
|
|
30
|
+
template = "select id, name,created_from, created_at from documents where dataset_id = :dataset_id"
|
|
31
|
+
return self.query(template, {'dataset_id': dataset_id})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Document(DB):
|
|
35
|
+
def hit_documents(self, top_k: int = 3):
|
|
36
|
+
template = "SELECT dataset_id, document_id, content FROM document_segments ORDER BY hit_count DESC LIMIT :top_k"
|
|
37
|
+
return self.get_dict(template, {'top_k': top_k})
|
|
38
|
+
|
|
39
|
+
def id_by(self, name: str, dataset_id: str = None) -> list[str]:
|
|
40
|
+
"""multiple ids can be found"""
|
|
41
|
+
template = "select id from documents where name = :name"
|
|
42
|
+
if dataset_id:
|
|
43
|
+
template = "select id from documents where name = :name and dataset_id = :dataset_id"
|
|
44
|
+
return [str(uuid) for uuid in self.query(template, {'name': name, 'dataset_id': dataset_id}).scalars().all()]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Pipeline(DB):
|
|
48
|
+
@property
|
|
49
|
+
def pipelines(self):
|
|
50
|
+
"""unique syntax for pgsql"""
|
|
51
|
+
template = "SELECT DISTINCT ON (app_id) app_id, graph, rag_pipeline_variables FROM workflows where type = 'rag-pipeline' ORDER BY app_id, created_at DESC"
|
|
52
|
+
return Graph.convert(*self.get_dict(template))
|
|
53
|
+
|
|
54
|
+
def pipeline(self, app_id):
|
|
55
|
+
template = "select id, graph, rag_pipeline_variables from workflows where type = 'rag-pipeline' and app_id = :app_id"
|
|
56
|
+
dict_result = self.get_dict(template, {'app_id': app_id})
|
|
57
|
+
assert len(dict_result) < 2
|
|
58
|
+
return Graph.convert(*dict_result)
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import json
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from sqlalchemy import Column, String, Text, JSON, TIMESTAMP, func
|
|
5
7
|
from sqlalchemy.dialects.postgresql import UUID
|
|
6
8
|
from sqlalchemy.orm import declarative_base
|
|
7
9
|
|
|
8
10
|
Base = declarative_base()
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
|
|
13
|
+
class DifyBase(Base):
|
|
14
|
+
__abstract__ = True # keyword for SQLAlchemy
|
|
15
|
+
id = Column(UUID(as_uuid=True), primary_key=True, server_default=func.uuid_generate_v4())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AppModelConfig(DifyBase):
|
|
11
19
|
__tablename__ = "app_model_configs"
|
|
12
20
|
__table_args__ = {"schema": "public"}
|
|
13
21
|
|
|
14
|
-
id = Column(UUID(as_uuid=True), primary_key=True, server_default=func.uuid_generate_v4())
|
|
15
22
|
app_id = Column(UUID(as_uuid=True), nullable=False)
|
|
16
23
|
|
|
17
24
|
provider = Column(String(255))
|
|
@@ -48,3 +55,97 @@ class AppModelConfig(Base):
|
|
|
48
55
|
|
|
49
56
|
def __repr__(self):
|
|
50
57
|
return f"<AppModelConfig(id={self.id}, app_id={self.app_id}, provider={self.provider}, model_id={self.model_id})>"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Position(BaseModel):
|
|
61
|
+
x: float
|
|
62
|
+
y: float
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class NodeData(BaseModel):
|
|
66
|
+
class Type(str, Enum):
|
|
67
|
+
SOURCE = 'datasource'
|
|
68
|
+
CHUNKER = 'knowledge-index'
|
|
69
|
+
TOOL = 'tool'
|
|
70
|
+
|
|
71
|
+
type: Type | str # not limit to built-in types
|
|
72
|
+
title: str | None = None
|
|
73
|
+
selected: bool
|
|
74
|
+
|
|
75
|
+
# datasource
|
|
76
|
+
datasource_parameters: dict[str, Any] | None = None
|
|
77
|
+
datasource_configurations: dict[str, Any] | None = None
|
|
78
|
+
plugin_id: str | None = None
|
|
79
|
+
provider_type: str | None = None
|
|
80
|
+
provider_name: str | None = None
|
|
81
|
+
datasource_name: str | None = None
|
|
82
|
+
datasource_label: str | None = None
|
|
83
|
+
plugin_unique_identifier: str | None = None
|
|
84
|
+
|
|
85
|
+
# tool
|
|
86
|
+
tool_parameters: dict[str, Any] | None = None
|
|
87
|
+
tool_configurations: dict[str, Any] | None = None
|
|
88
|
+
tool_node_version: str | None = None
|
|
89
|
+
provider_id: str | None = None
|
|
90
|
+
provider_icon: str | None = None
|
|
91
|
+
tool_name: str | None = None
|
|
92
|
+
tool_label: str | None = None
|
|
93
|
+
tool_description: str | None = None
|
|
94
|
+
is_team_authorization: bool | None = None
|
|
95
|
+
paramSchemas: list[Any] | None = None
|
|
96
|
+
params: dict[str, Any] | None = None
|
|
97
|
+
|
|
98
|
+
# knowledge index
|
|
99
|
+
index_chunk_variable_selector: list[str] | None = None
|
|
100
|
+
keyword_number: int | None = None
|
|
101
|
+
retrieval_model: dict[str, Any] | None = None
|
|
102
|
+
chunk_structure: str | None = None
|
|
103
|
+
indexing_technique: str | None = None
|
|
104
|
+
embedding_model: str | None = None
|
|
105
|
+
embedding_model_provider: str | None = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Node(BaseModel):
|
|
109
|
+
@property
|
|
110
|
+
def datasource_type(self): return self.data.provider_type
|
|
111
|
+
id: str
|
|
112
|
+
type: Literal['custom']
|
|
113
|
+
data: NodeData
|
|
114
|
+
position: Position
|
|
115
|
+
targetPosition: str | None = None
|
|
116
|
+
sourcePosition: str | None = None
|
|
117
|
+
positionAbsolute: Position | None = None
|
|
118
|
+
width: float | None = None
|
|
119
|
+
height: float | None = None
|
|
120
|
+
selected: bool
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Edge(BaseModel):
|
|
124
|
+
id: str
|
|
125
|
+
type: str
|
|
126
|
+
source: str
|
|
127
|
+
target: str
|
|
128
|
+
sourceHandle: str | None = None
|
|
129
|
+
targetHandle: str | None = None
|
|
130
|
+
data: dict[str, Any] | None = None
|
|
131
|
+
zIndex: int | None = None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class Viewport(BaseModel):
|
|
135
|
+
x: float
|
|
136
|
+
y: float
|
|
137
|
+
zoom: float
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Graph(BaseModel):
|
|
141
|
+
nodes: list[Node]
|
|
142
|
+
edges: list[Edge]
|
|
143
|
+
viewport: Viewport
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def datasources(self):
|
|
147
|
+
return [node for node in self.nodes if node.data.type == NodeData.Type.SOURCE]
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def convert(*records: list[dict]):
|
|
151
|
+
return [{**record, "graph": Graph(**json.loads(record["graph"]))} for record in records]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from davidkhala.ai.agent.dify.plugins import DataSourceTypeAware
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FileModel(BaseModel):
|
|
7
|
+
name: str
|
|
8
|
+
size: int
|
|
9
|
+
type: str
|
|
10
|
+
extension: str
|
|
11
|
+
mime_type: str
|
|
12
|
+
transfer_method: str
|
|
13
|
+
url: str
|
|
14
|
+
related_id: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DataSourceOutput(DataSourceTypeAware):
|
|
18
|
+
datasource_type:str = "local_file"
|
|
19
|
+
file: FileModel
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from davidkhala.ai.agent.dify.plugins import DataSourceTypeAware
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DataSourceInfo(BaseModel):
|
|
7
|
+
source_url: str
|
|
8
|
+
content: str
|
|
9
|
+
title: str
|
|
10
|
+
description: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DataSourceOutput(DataSourceTypeAware, DataSourceInfo):
|
|
14
|
+
datasource_type: str = "website_crawl"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CredentialAware(BaseModel):
|
|
18
|
+
credential_id: str | None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Console(DataSourceOutput, CredentialAware):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from agentbay import AgentBay, Session, Config, AgentBayLogger, BrowserOption
|
|
2
|
+
from davidkhala.utils.syntax.interface import ContextAware
|
|
3
|
+
|
|
4
|
+
AgentBayLogger.setup(level='WARNING') # Default to INFO
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Client(ContextAware):
|
|
8
|
+
def __init__(self, api_key, *, timeout_ms=10000):
|
|
9
|
+
self.agent = AgentBay(
|
|
10
|
+
api_key=api_key,
|
|
11
|
+
cfg=Config(endpoint="wuyingai.ap-southeast-1.aliyuncs.com", timeout_ms=timeout_ms)
|
|
12
|
+
)
|
|
13
|
+
self.session: Session | None = None
|
|
14
|
+
|
|
15
|
+
def open(self):
|
|
16
|
+
r = self.agent.create()
|
|
17
|
+
if not r.success:
|
|
18
|
+
return False
|
|
19
|
+
self.session = r.session
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
def close(self):
|
|
23
|
+
self.agent.delete(self.session)
|
|
24
|
+
del self.session
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Browser(ContextAware):
|
|
28
|
+
def __init__(self, session: Session):
|
|
29
|
+
self.session = session
|
|
30
|
+
self.option = BrowserOption()
|
|
31
|
+
self.endpoint_url: str | None = None
|
|
32
|
+
|
|
33
|
+
def open(self) -> bool:
|
|
34
|
+
success = self.session.browser.initialize(self.option)
|
|
35
|
+
self.endpoint_url = self.session.browser.get_endpoint_url()
|
|
36
|
+
return success
|
|
37
|
+
|
|
38
|
+
def close(self):
|
|
39
|
+
self.session.browser.destroy()
|
davidkhala/ai/openai/azure.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from davidkhala.utils.syntax.compat import deprecated
|
|
3
2
|
from openai import AzureOpenAI, OpenAI
|
|
4
3
|
|
|
5
4
|
from davidkhala.ai.openai import Client
|
|
@@ -20,7 +19,7 @@ class ModelDeploymentClient(AzureHosted):
|
|
|
20
19
|
)
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
@
|
|
22
|
+
@deprecated("Azure Open AI is deprecated. Please migrate to Microsoft Foundry")
|
|
24
23
|
class OpenAIClient(AzureHosted):
|
|
25
24
|
|
|
26
25
|
def __init__(self, api_key, project):
|
|
@@ -7,7 +7,7 @@ from openrouter import OpenRouter
|
|
|
7
7
|
class Client(AbstractClient):
|
|
8
8
|
def __init__(self, api_key: str):
|
|
9
9
|
self.api_key = api_key
|
|
10
|
-
self.client = OpenRouter(api_key
|
|
10
|
+
self.client = OpenRouter(api_key)
|
|
11
11
|
|
|
12
12
|
def chat(self, *user_prompt, **kwargs):
|
|
13
13
|
r = self.client.chat.send(
|
|
@@ -20,8 +20,16 @@ class Client(AbstractClient):
|
|
|
20
20
|
return [_.message.content for _ in r.choices]
|
|
21
21
|
def connect(self):
|
|
22
22
|
try:
|
|
23
|
-
self.client.
|
|
23
|
+
self.client.models.list()
|
|
24
24
|
return True
|
|
25
25
|
except UnauthorizedResponseError:
|
|
26
26
|
return False
|
|
27
27
|
|
|
28
|
+
|
|
29
|
+
class Admin:
|
|
30
|
+
def __init__(self, provisioning_key: str):
|
|
31
|
+
self.provisioning_key = provisioning_key
|
|
32
|
+
self.client = OpenRouter(provisioning_key)
|
|
33
|
+
@property
|
|
34
|
+
def keys(self):
|
|
35
|
+
return self.client.api_keys.list().data
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: davidkhala.ai
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: misc AI modules
|
|
5
|
-
Requires-Python: >=3.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
6
|
Provides-Extra: ali
|
|
7
7
|
Requires-Dist: dashscope; extra == 'ali'
|
|
8
|
+
Requires-Dist: davidkhala-utils; extra == 'ali'
|
|
9
|
+
Requires-Dist: wuying-agentbay-sdk; extra == 'ali'
|
|
8
10
|
Provides-Extra: api
|
|
9
11
|
Requires-Dist: davidkhala-utils[http-request]; extra == 'api'
|
|
10
12
|
Provides-Extra: azure
|
|
13
|
+
Requires-Dist: davidkhala-utils; extra == 'azure'
|
|
11
14
|
Requires-Dist: openai; extra == 'azure'
|
|
12
15
|
Provides-Extra: dify
|
|
13
16
|
Requires-Dist: davidkhala-databases[pg]; extra == 'dify'
|
|
@@ -23,10 +26,9 @@ Requires-Dist: onnx; extra == 'hf'
|
|
|
23
26
|
Requires-Dist: onnxruntime; extra == 'hf'
|
|
24
27
|
Provides-Extra: langchain
|
|
25
28
|
Requires-Dist: langchain; extra == 'langchain'
|
|
26
|
-
Requires-Dist: langchain-openai; extra == 'langchain'
|
|
29
|
+
Requires-Dist: langchain-openai; (python_version < '3.14') and extra == 'langchain'
|
|
27
30
|
Requires-Dist: langgraph; extra == 'langchain'
|
|
28
31
|
Provides-Extra: openrouter
|
|
29
|
-
Requires-Dist: davidkhala-utils[http-request]; extra == 'openrouter'
|
|
30
32
|
Requires-Dist: openrouter; extra == 'openrouter'
|
|
31
33
|
Provides-Extra: ragflow
|
|
32
34
|
Requires-Dist: ragflow-sdk; extra == 'ragflow'
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
davidkhala/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
davidkhala/ai/model.py,sha256=1wcXC8X8oqerMatlcPbZmuxZ-nJWdJKmaDSDgiGlUGw,647
|
|
3
|
+
davidkhala/ai/opik.py,sha256=YU1XuweMUAzUkhpjxhltt-SBBDBkR3z-PCNo0DqzBRs,39
|
|
4
|
+
davidkhala/ai/agent/README.md,sha256=kIPsx3gOjrpOw7w2qhNEALuCEQkuh4nYp6uBnijdvHE,178
|
|
5
|
+
davidkhala/ai/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
davidkhala/ai/agent/langgraph.py,sha256=jrc_Yvgo7eJjd3y5UJn0t1FzpnObDGYscwgsuVl2O_I,1052
|
|
7
|
+
davidkhala/ai/agent/ragflow.py,sha256=UaK31us6V0NhAPCthGo07rQsm72vlR-McmihC_NDe1g,273
|
|
8
|
+
davidkhala/ai/agent/dify/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
davidkhala/ai/agent/dify/const.py,sha256=gU4lPBe4U2taakN2jhdPMRWXkqlyCg-YRE8JJmtsblo,218
|
|
10
|
+
davidkhala/ai/agent/dify/interface.py,sha256=bTOI38ZjtkgoSw-ysgFwBZ1QkKVAa92gjOnERDoagQA,118
|
|
11
|
+
davidkhala/ai/agent/dify/model.py,sha256=1LEwKWWkFNmhbBWABEu7I45DRZ_BFGDP5uTHDrvldoo,641
|
|
12
|
+
davidkhala/ai/agent/dify/api/__init__.py,sha256=9-8OesuXF_wPmPrh_gEZpEZP51dcZxb0i6ixOBYKcwQ,876
|
|
13
|
+
davidkhala/ai/agent/dify/api/app.py,sha256=y1mILC-fvQpeH50ASbFBluD9tFAwXu_IWwtwucMV5jM,3801
|
|
14
|
+
davidkhala/ai/agent/dify/api/knowledge.py,sha256=5ePqvzjBHNtQ64Dzt39wBWedYVeQJc23syNe9LFnGw8,5960
|
|
15
|
+
davidkhala/ai/agent/dify/ops/__init__.py,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
16
|
+
davidkhala/ai/agent/dify/ops/console/__init__.py,sha256=-a81jgCJ3s2B3i1GQ7ge1aZRfbvlALwGDHVu_GEET-A,237
|
|
17
|
+
davidkhala/ai/agent/dify/ops/console/knowledge.py,sha256=I1v0iE_b4VPc2Zsyt4ci_oX080Qbgn3oXObP4uVEphg,5788
|
|
18
|
+
davidkhala/ai/agent/dify/ops/console/session.py,sha256=Kt8vzZJUlyqD8G8_OsrOD-WQwyDor8tqNwV0jJ738wE,902
|
|
19
|
+
davidkhala/ai/agent/dify/ops/db/__init__.py,sha256=HYfJEnoFAoJJck2xvTDYx8zpw9Qao7sHXOGvW0diPqw,517
|
|
20
|
+
davidkhala/ai/agent/dify/ops/db/app.py,sha256=IRiSiR0v387p4p3J7M9xEkJ7pfQyO5DL6chpx7Z2IzA,1319
|
|
21
|
+
davidkhala/ai/agent/dify/ops/db/knowledge.py,sha256=GVaK5QmU_VxB8fDxV60uiYiIeR3JEn3IXJTlJHLiT5U,2917
|
|
22
|
+
davidkhala/ai/agent/dify/ops/db/orm.py,sha256=CnZj8mV2RZhw_7hF1YICTUjROQ66hR5_8OCMQvtujnY,4575
|
|
23
|
+
davidkhala/ai/agent/dify/ops/db/sys.py,sha256=U_qqopUMlgsilhHaG_ids6gtd-pNiR_Jm0kAr9hIL7M,188
|
|
24
|
+
davidkhala/ai/agent/dify/plugins/__init__.py,sha256=iTWvutlkN9bXgptesi05M447nTeF5hKFAIfn4EviFj0,183
|
|
25
|
+
davidkhala/ai/agent/dify/plugins/file.py,sha256=o-HjHSFwRTNIYs8IxqZUSnBbh-xr8f-xMUM3iU9wCCQ,390
|
|
26
|
+
davidkhala/ai/agent/dify/plugins/firecrawl.py,sha256=lB_f8W_bdg-7PeBKmF0-HdwYyakV_0D3nET5iT-Z1KM,460
|
|
27
|
+
davidkhala/ai/agent/dify/plugins/jina.py,sha256=dQ5iJxDLWtChXb1IjCtsHctgUtgjOiDfWOuR2u0aUIM,190
|
|
28
|
+
davidkhala/ai/ali/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
davidkhala/ai/ali/agentbay.py,sha256=O5t71GGwtDgBE1zUXJDYe5djMVwSaNOwn5k8zg1xa18,1200
|
|
30
|
+
davidkhala/ai/ali/dashscope.py,sha256=SZIzRhVHlLx3s5I2RNUh2-u8OoSdrbvoN5e1k8Mh8N0,1943
|
|
31
|
+
davidkhala/ai/api/__init__.py,sha256=q2Ro5nhW5kJx2CYR1MRVamjTT5tTexPZwhrS2hwAvFM,1319
|
|
32
|
+
davidkhala/ai/api/openrouter.py,sha256=khccJr5cBnudFy6Jc2O3A1TNCuHH_5W6Q2tXrkwlUYE,2308
|
|
33
|
+
davidkhala/ai/api/siliconflow.py,sha256=JbnOSv8LJLtwYSNNB8_SMBMQzOgHDtQYZKA9A2BC4sY,2139
|
|
34
|
+
davidkhala/ai/google/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
+
davidkhala/ai/google/adk.py,sha256=QwxYoOzT2Hol03V4NM0PF_HAzUGb4fB18VUAYacYbAY,657
|
|
36
|
+
davidkhala/ai/google/gemini.py,sha256=Xf4HDOOcK4-jEBERzuLnQNFsU61P2fFx4K0z-ijvNHE,214
|
|
37
|
+
davidkhala/ai/huggingface/BAAI.py,sha256=LZ9kp5Gfql4UzuTn4osyekI6VV1H3RIfED2IolXFj5c,341
|
|
38
|
+
davidkhala/ai/huggingface/__init__.py,sha256=FJyU8eOfWQWKAvkIa5qwubF9ghsSQ8C0e6p6DKyomgs,521
|
|
39
|
+
davidkhala/ai/huggingface/inference.py,sha256=bYN0PtLF2CaIHzdTP4LaTALJhcawvuLnLR7rhMVqwDE,333
|
|
40
|
+
davidkhala/ai/openai/__init__.py,sha256=GXzWaw2ER3YFGHG6TPD9SmAHV6Tpsnqxj6tXlaWsrko,1897
|
|
41
|
+
davidkhala/ai/openai/azure.py,sha256=WmWSz9pKlUrQLSH25m1jE1l-mNWw9QQARj8uliOv8VU,1138
|
|
42
|
+
davidkhala/ai/openai/native.py,sha256=MB0nDnzCOj_M42RMhdK3HTMVnxGnwpLT2GeLwSrepwI,704
|
|
43
|
+
davidkhala/ai/openrouter/__init__.py,sha256=P8UvolZihN_CVBQ7BT1Fb6mSMFEQLyLY9G5bBDZhC0o,1037
|
|
44
|
+
davidkhala_ai-0.1.9.dist-info/METADATA,sha256=d1eUCeXWEewssHevligqENx8Thz7rd_2wVhcdvAmjKQ,1607
|
|
45
|
+
davidkhala_ai-0.1.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
46
|
+
davidkhala_ai-0.1.9.dist-info/RECORD,,
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
davidkhala/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
davidkhala/ai/model.py,sha256=1wcXC8X8oqerMatlcPbZmuxZ-nJWdJKmaDSDgiGlUGw,647
|
|
3
|
-
davidkhala/ai/opik.py,sha256=YU1XuweMUAzUkhpjxhltt-SBBDBkR3z-PCNo0DqzBRs,39
|
|
4
|
-
davidkhala/ai/agent/README.md,sha256=kIPsx3gOjrpOw7w2qhNEALuCEQkuh4nYp6uBnijdvHE,178
|
|
5
|
-
davidkhala/ai/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
davidkhala/ai/agent/langgraph.py,sha256=jrc_Yvgo7eJjd3y5UJn0t1FzpnObDGYscwgsuVl2O_I,1052
|
|
7
|
-
davidkhala/ai/agent/ragflow.py,sha256=UaK31us6V0NhAPCthGo07rQsm72vlR-McmihC_NDe1g,273
|
|
8
|
-
davidkhala/ai/agent/dify/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
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
|
|
16
|
-
davidkhala/ai/ali/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
davidkhala/ai/ali/dashscope.py,sha256=SZIzRhVHlLx3s5I2RNUh2-u8OoSdrbvoN5e1k8Mh8N0,1943
|
|
18
|
-
davidkhala/ai/api/__init__.py,sha256=q2Ro5nhW5kJx2CYR1MRVamjTT5tTexPZwhrS2hwAvFM,1319
|
|
19
|
-
davidkhala/ai/api/openrouter.py,sha256=khccJr5cBnudFy6Jc2O3A1TNCuHH_5W6Q2tXrkwlUYE,2308
|
|
20
|
-
davidkhala/ai/api/siliconflow.py,sha256=JbnOSv8LJLtwYSNNB8_SMBMQzOgHDtQYZKA9A2BC4sY,2139
|
|
21
|
-
davidkhala/ai/google/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
davidkhala/ai/google/adk.py,sha256=QwxYoOzT2Hol03V4NM0PF_HAzUGb4fB18VUAYacYbAY,657
|
|
23
|
-
davidkhala/ai/google/gemini.py,sha256=Xf4HDOOcK4-jEBERzuLnQNFsU61P2fFx4K0z-ijvNHE,214
|
|
24
|
-
davidkhala/ai/huggingface/BAAI.py,sha256=LZ9kp5Gfql4UzuTn4osyekI6VV1H3RIfED2IolXFj5c,341
|
|
25
|
-
davidkhala/ai/huggingface/__init__.py,sha256=FJyU8eOfWQWKAvkIa5qwubF9ghsSQ8C0e6p6DKyomgs,521
|
|
26
|
-
davidkhala/ai/huggingface/inference.py,sha256=bYN0PtLF2CaIHzdTP4LaTALJhcawvuLnLR7rhMVqwDE,333
|
|
27
|
-
davidkhala/ai/openai/__init__.py,sha256=GXzWaw2ER3YFGHG6TPD9SmAHV6Tpsnqxj6tXlaWsrko,1897
|
|
28
|
-
davidkhala/ai/openai/azure.py,sha256=QR1uZj8qAyhpCjo3Ks5zNV8GfOp3-enyZs6fBvV-MkA,1110
|
|
29
|
-
davidkhala/ai/openai/native.py,sha256=MB0nDnzCOj_M42RMhdK3HTMVnxGnwpLT2GeLwSrepwI,704
|
|
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,,
|