davidkhala.ai 0.1.5__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.
@@ -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, Callable, Any, Optional
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.base import API
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
@@ -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,10 @@
1
+ from pydantic import BaseModel
2
+
3
+ class JsonEntry(BaseModel):
4
+ data: list
5
+
6
+ class Output(BaseModel):
7
+ """Class for result of a Dify node"""
8
+ text: str
9
+ files: list
10
+ json: list[JsonEntry]
@@ -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.5
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/base.py,sha256=V4BIhG9oTg9ayPZnYVxxA1Yobl2C4ukSWEOu2U_l0Tw,259
10
- davidkhala/ai/agent/dify/knowledge.py,sha256=jWx-DmYLvK8ZJ7dMuayKAKnOMW2uehpFJu6jnj-grf8,6328
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
- davidkhala_ai-0.1.5.dist-info/METADATA,sha256=O1e0C_WHO5hAtEgn2o_-lVoVItJk_AYP5vfWe_M3DWs,1098
26
- davidkhala_ai-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- davidkhala_ai-0.1.5.dist-info/RECORD,,
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,,
@@ -1,7 +0,0 @@
1
- from davidkhala.utils.http_request import Request
2
-
3
- class API(Request):
4
- def __init__(self, api_key: str, base_url="https://api.dify.ai/v1"):
5
- super().__init__({'bearer': api_key})
6
- self.base_url = base_url
7
- self.api_key = api_key