celai 0.1.0__tar.gz
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.
- celai-0.1.0/PKG-INFO +95 -0
- celai-0.1.0/README.md +53 -0
- celai-0.1.0/cel/__init__.py +0 -0
- celai-0.1.0/cel/assets/celia_logo.png +0 -0
- celai-0.1.0/cel/assistants/base_assistant.py +227 -0
- celai-0.1.0/cel/assistants/common.py +28 -0
- celai-0.1.0/cel/assistants/function_context.py +24 -0
- celai-0.1.0/cel/assistants/function_response.py +23 -0
- celai-0.1.0/cel/assistants/macaw/custom_chat_models/chat_open_router.py +53 -0
- celai-0.1.0/cel/assistants/macaw/macaw_assistant.py +203 -0
- celai-0.1.0/cel/assistants/macaw/macaw_history_adapter.py +33 -0
- celai-0.1.0/cel/assistants/macaw/macaw_inference_context.py +28 -0
- celai-0.1.0/cel/assistants/macaw/macaw_nlp.py +281 -0
- celai-0.1.0/cel/assistants/macaw/macaw_settings.py +49 -0
- celai-0.1.0/cel/assistants/macaw/macaw_test_chat_model.py +37 -0
- celai-0.1.0/cel/assistants/macaw/macaw_utils.py +42 -0
- celai-0.1.0/cel/assistants/stream_content_chunk.py +21 -0
- celai-0.1.0/cel/cache.py +23 -0
- celai-0.1.0/cel/comms/client_command.py +32 -0
- celai-0.1.0/cel/comms/sentense_detection.py +116 -0
- celai-0.1.0/cel/comms/utils.py +46 -0
- celai-0.1.0/cel/config.py +15 -0
- celai-0.1.0/cel/connectors/cli/cli_connector.py +169 -0
- celai-0.1.0/cel/connectors/cli/model/cli_lead.py +40 -0
- celai-0.1.0/cel/connectors/cli/model/cli_message.py +49 -0
- celai-0.1.0/cel/connectors/telegram/__init__.py +4 -0
- celai-0.1.0/cel/connectors/telegram/model/telegram_attachment.py +173 -0
- celai-0.1.0/cel/connectors/telegram/model/telegram_lead.py +46 -0
- celai-0.1.0/cel/connectors/telegram/model/telegram_message.py +46 -0
- celai-0.1.0/cel/connectors/telegram/samples/hi.mp3 +0 -0
- celai-0.1.0/cel/connectors/telegram/samples/hi.oga +0 -0
- celai-0.1.0/cel/connectors/telegram/samples/hi.ogg +0 -0
- celai-0.1.0/cel/connectors/telegram/samples/message.json +21 -0
- celai-0.1.0/cel/connectors/telegram/samples/message_audio.json +27 -0
- celai-0.1.0/cel/connectors/telegram/samples/message_callback_query.json +50 -0
- celai-0.1.0/cel/connectors/telegram/samples/message_img.json +44 -0
- celai-0.1.0/cel/connectors/telegram/samples/message_imgx2.json +44 -0
- celai-0.1.0/cel/connectors/telegram/samples/message_location.json +24 -0
- celai-0.1.0/cel/connectors/telegram/samples/message_location_place.json +34 -0
- celai-0.1.0/cel/connectors/telegram/samples/sample_message.json +21 -0
- celai-0.1.0/cel/connectors/telegram/stt_utils.py +53 -0
- celai-0.1.0/cel/connectors/telegram/telegram_connector.py +263 -0
- celai-0.1.0/cel/connectors/vapi/README.md +15 -0
- celai-0.1.0/cel/connectors/vapi/model/vapi_lead.py +63 -0
- celai-0.1.0/cel/connectors/vapi/model/vapi_message.py +59 -0
- celai-0.1.0/cel/connectors/vapi/sample_data/chunk_vapi_1.json +21 -0
- celai-0.1.0/cel/connectors/vapi/sample_data/chunk_vapi_2.json +21 -0
- celai-0.1.0/cel/connectors/vapi/sample_data/chunk_vapi_3.json +21 -0
- celai-0.1.0/cel/connectors/vapi/sample_data/chunk_vapi_last.json +21 -0
- celai-0.1.0/cel/connectors/vapi/sample_data/request.json +31 -0
- celai-0.1.0/cel/connectors/vapi/utils.py +26 -0
- celai-0.1.0/cel/connectors/vapi/vapi_connector.py +162 -0
- celai-0.1.0/cel/connectors/whatsapp/__init__.py +4 -0
- celai-0.1.0/cel/connectors/whatsapp/components/component.py +7 -0
- celai-0.1.0/cel/connectors/whatsapp/components/document.py +43 -0
- celai-0.1.0/cel/connectors/whatsapp/components/document_url.py +40 -0
- celai-0.1.0/cel/connectors/whatsapp/components/image.py +50 -0
- celai-0.1.0/cel/connectors/whatsapp/components/reply_button.py +31 -0
- celai-0.1.0/cel/connectors/whatsapp/components/text.py +28 -0
- celai-0.1.0/cel/connectors/whatsapp/components/utils.py +14 -0
- celai-0.1.0/cel/connectors/whatsapp/components/video.py +49 -0
- celai-0.1.0/cel/connectors/whatsapp/constants.py +1 -0
- celai-0.1.0/cel/connectors/whatsapp/functions/buttons.py +114 -0
- celai-0.1.0/cel/connectors/whatsapp/functions/utils.py +328 -0
- celai-0.1.0/cel/connectors/whatsapp/model/media_utils.py +143 -0
- celai-0.1.0/cel/connectors/whatsapp/model/samples/img_caption_message.json +42 -0
- celai-0.1.0/cel/connectors/whatsapp/model/samples/img_message.json +42 -0
- celai-0.1.0/cel/connectors/whatsapp/model/samples/react_message.json +40 -0
- celai-0.1.0/cel/connectors/whatsapp/model/samples/text_message.json +39 -0
- celai-0.1.0/cel/connectors/whatsapp/model/whatsapp_attachment.py +254 -0
- celai-0.1.0/cel/connectors/whatsapp/model/whatsapp_lead.py +51 -0
- celai-0.1.0/cel/connectors/whatsapp/model/whatsapp_message.py +128 -0
- celai-0.1.0/cel/connectors/whatsapp/phone_utils.py +10 -0
- celai-0.1.0/cel/connectors/whatsapp/utils.py +6 -0
- celai-0.1.0/cel/connectors/whatsapp/whatsapp_connector.py +485 -0
- celai-0.1.0/cel/decoders/gcp_geocoding.py +30 -0
- celai-0.1.0/cel/gateway/__init__.py +2 -0
- celai-0.1.0/cel/gateway/message_gateway.py +401 -0
- celai-0.1.0/cel/gateway/model/__init__.py +6 -0
- celai-0.1.0/cel/gateway/model/attachment.py +93 -0
- celai-0.1.0/cel/gateway/model/base_connector.py +36 -0
- celai-0.1.0/cel/gateway/model/conversation_lead.py +46 -0
- celai-0.1.0/cel/gateway/model/conversation_peer.py +42 -0
- celai-0.1.0/cel/gateway/model/message.py +41 -0
- celai-0.1.0/cel/gateway/model/message_gateway_context.py +10 -0
- celai-0.1.0/cel/gateway/model/outgoing/__init__.py +5 -0
- celai-0.1.0/cel/gateway/model/outgoing/outgoing_message.py +54 -0
- celai-0.1.0/cel/gateway/model/outgoing/outgoing_message_factory.py +38 -0
- celai-0.1.0/cel/gateway/model/outgoing/outgoing_message_link.py +62 -0
- celai-0.1.0/cel/gateway/model/outgoing/outgoing_message_select.py +64 -0
- celai-0.1.0/cel/gateway/model/outgoing/outgoing_message_text.py +48 -0
- celai-0.1.0/cel/gateway/request_context.py +47 -0
- celai-0.1.0/cel/message_enhancers/default_message_enhancer.py +23 -0
- celai-0.1.0/cel/message_enhancers/smart_message_enhancer_openai.py +115 -0
- celai-0.1.0/cel/middlewares/__init__.py +6 -0
- celai-0.1.0/cel/middlewares/deepgram_stt.py +63 -0
- celai-0.1.0/cel/middlewares/geodecoding.py +48 -0
- celai-0.1.0/cel/middlewares/in_mem_blacklist.py +43 -0
- celai-0.1.0/cel/middlewares/redis_blacklist.py +44 -0
- celai-0.1.0/cel/middlewares/redis_blacklist_async.py +44 -0
- celai-0.1.0/cel/middlewares/session_middleware.py +141 -0
- celai-0.1.0/cel/model/common.py +37 -0
- celai-0.1.0/cel/prompt/prompt_template.py +67 -0
- celai-0.1.0/cel/rag/__init__.py +0 -0
- celai-0.1.0/cel/rag/providers/markdown_rag.py +118 -0
- celai-0.1.0/cel/rag/providers/rag_retriever.py +15 -0
- celai-0.1.0/cel/rag/slicers/__init__.py +0 -0
- celai-0.1.0/cel/rag/slicers/base_slicer.py +37 -0
- celai-0.1.0/cel/rag/slicers/markdown/__init__.py +1 -0
- celai-0.1.0/cel/rag/slicers/markdown/markdown.py +117 -0
- celai-0.1.0/cel/rag/slicers/markdown/utils.py +74 -0
- celai-0.1.0/cel/rag/slicers/utils.py +0 -0
- celai-0.1.0/cel/rag/stores/chroma/chroma_store.py +113 -0
- celai-0.1.0/cel/rag/stores/vector_store.py +57 -0
- celai-0.1.0/cel/rag/text2vec/cached_openai.py +76 -0
- celai-0.1.0/cel/rag/text2vec/utils.py +19 -0
- celai-0.1.0/cel/stores/common/async_cache_aside_redis.py +146 -0
- celai-0.1.0/cel/stores/common/cache_aside_redis.py +162 -0
- celai-0.1.0/cel/stores/common/key_value_store.py +45 -0
- celai-0.1.0/cel/stores/common/list_redis_store_async.py +36 -0
- celai-0.1.0/cel/stores/common/memory_cache.py +82 -0
- celai-0.1.0/cel/stores/history/base_history_provider.py +24 -0
- celai-0.1.0/cel/stores/history/history_inmemory_provider.py +50 -0
- celai-0.1.0/cel/stores/history/history_redis_provider.py +57 -0
- celai-0.1.0/cel/stores/history/history_redis_provider_async.py +55 -0
- celai-0.1.0/cel/stores/state/base_state_provider.py +31 -0
- celai-0.1.0/cel/stores/state/state_inmemory_provider.py +43 -0
- celai-0.1.0/cel/stores/state/state_redis_provider.py +58 -0
- celai-0.1.0/cel/voice/deepgram_adapter.py +34 -0
- celai-0.1.0/pyproject.toml +62 -0
celai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: celai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Alex Martin
|
|
6
|
+
Author-email: alejamp@gmail.com
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Requires-Dist: aiogram (>=3.6.0)
|
|
14
|
+
Requires-Dist: aioredis (>=2.0.1)
|
|
15
|
+
Requires-Dist: beautifulsoup4 (>=4.12.3)
|
|
16
|
+
Requires-Dist: chardet (>=5.2.0)
|
|
17
|
+
Requires-Dist: chromadb (>=0.5.0)
|
|
18
|
+
Requires-Dist: deepgram-sdk (>=3.2.7)
|
|
19
|
+
Requires-Dist: dictdiffer (>=0.9.0)
|
|
20
|
+
Requires-Dist: diskcache (>=5.6.3)
|
|
21
|
+
Requires-Dist: fakeredis (>=2.23.2)
|
|
22
|
+
Requires-Dist: geopy (>=2.4.1)
|
|
23
|
+
Requires-Dist: halo (>=0.0.31)
|
|
24
|
+
Requires-Dist: langchain (>=0.2.0)
|
|
25
|
+
Requires-Dist: langchain-chroma (>=0.1.1)
|
|
26
|
+
Requires-Dist: langchain-community (>=0.2.1)
|
|
27
|
+
Requires-Dist: langchain-openai (>=0.1.8)
|
|
28
|
+
Requires-Dist: loguru (>=0.7.2)
|
|
29
|
+
Requires-Dist: lxml (>=5.2.2)
|
|
30
|
+
Requires-Dist: marko (>=2.1.0,<3.0.0)
|
|
31
|
+
Requires-Dist: numpy (>=1.26.4,<2.0.0)
|
|
32
|
+
Requires-Dist: openai (>=1.30.1)
|
|
33
|
+
Requires-Dist: openai-responses (>=0.3.2)
|
|
34
|
+
Requires-Dist: pybars3 (>=0.9.7)
|
|
35
|
+
Requires-Dist: pysbd (>=0.3.4)
|
|
36
|
+
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
37
|
+
Requires-Dist: redis (>=4.6.0)
|
|
38
|
+
Requires-Dist: shortuuid (>=1.0.13)
|
|
39
|
+
Project-URL: Homepage, https://github.com/cel-ai/celai
|
|
40
|
+
Project-URL: Issues, https://github.com/cel-ai/celai/issues
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
<!-- A centered logo of celia -->
|
|
44
|
+
<p align="center">
|
|
45
|
+
<img src="cel/assets/celia_logo.png" width="250" />
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
# Introduction
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Cel.ai is a powerful Python framework designed to accelerate the development of omnichannel virtual assistants. Whether you need to integrate with platforms like WhatsApp, Telegram, or VoIP services such as VAPI.com, Cel.ai provides the tools and flexibility to get your assistant up and running quickly.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
pip install from github:
|
|
57
|
+
```bash
|
|
58
|
+
pip install git+https://github.com/cel-ai/celai
|
|
59
|
+
```
|
|
60
|
+
## Getting Started
|
|
61
|
+
|
|
62
|
+
Create a new Python file, `assistant.py`, and open it in your favorite text editor. Add the following code to set up a basic assistant:
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# Import Cel.ai modules
|
|
67
|
+
from cel.connectors.cli.cli_connector import CliConnector
|
|
68
|
+
from cel.gateway.message_gateway import MessageGateway, StreamMode
|
|
69
|
+
from cel.message_enhancers.smart_message_enhancer_openai import SmartMessageEnhancerOpenAI
|
|
70
|
+
from cel.assistants.macaw.macaw_assistant import MacawAssistant
|
|
71
|
+
from cel.prompt.prompt_template import PromptTemplate
|
|
72
|
+
|
|
73
|
+
# Setup prompt
|
|
74
|
+
prompt = """You are an AI assistant. Called Celia. You can help a user to buy Bitcoins."""
|
|
75
|
+
prompt_template = PromptTemplate(prompt)
|
|
76
|
+
|
|
77
|
+
# Create the assistant based on the Macaw Assistant
|
|
78
|
+
ast = MacawAssistant(
|
|
79
|
+
prompt=prompt_template
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Create the Message Gateway - This component is the core of the assistant
|
|
83
|
+
# It handles the communication between the assistant and the connectors
|
|
84
|
+
gateway = MessageGateway(ast)
|
|
85
|
+
|
|
86
|
+
# For this example, we will use the CLI connector
|
|
87
|
+
conn = CliConnector(
|
|
88
|
+
stream_mode=StreamMode.FULL
|
|
89
|
+
)
|
|
90
|
+
# Register the connector with the gateway
|
|
91
|
+
gateway.register_connector(conn)
|
|
92
|
+
|
|
93
|
+
# Then start the gateway and begin processing messages
|
|
94
|
+
gateway.run()
|
|
95
|
+
```
|
celai-0.1.0/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!-- A centered logo of celia -->
|
|
2
|
+
<p align="center">
|
|
3
|
+
<img src="cel/assets/celia_logo.png" width="250" />
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
# Introduction
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Cel.ai is a powerful Python framework designed to accelerate the development of omnichannel virtual assistants. Whether you need to integrate with platforms like WhatsApp, Telegram, or VoIP services such as VAPI.com, Cel.ai provides the tools and flexibility to get your assistant up and running quickly.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
pip install from github:
|
|
15
|
+
```bash
|
|
16
|
+
pip install git+https://github.com/cel-ai/celai
|
|
17
|
+
```
|
|
18
|
+
## Getting Started
|
|
19
|
+
|
|
20
|
+
Create a new Python file, `assistant.py`, and open it in your favorite text editor. Add the following code to set up a basic assistant:
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
# Import Cel.ai modules
|
|
25
|
+
from cel.connectors.cli.cli_connector import CliConnector
|
|
26
|
+
from cel.gateway.message_gateway import MessageGateway, StreamMode
|
|
27
|
+
from cel.message_enhancers.smart_message_enhancer_openai import SmartMessageEnhancerOpenAI
|
|
28
|
+
from cel.assistants.macaw.macaw_assistant import MacawAssistant
|
|
29
|
+
from cel.prompt.prompt_template import PromptTemplate
|
|
30
|
+
|
|
31
|
+
# Setup prompt
|
|
32
|
+
prompt = """You are an AI assistant. Called Celia. You can help a user to buy Bitcoins."""
|
|
33
|
+
prompt_template = PromptTemplate(prompt)
|
|
34
|
+
|
|
35
|
+
# Create the assistant based on the Macaw Assistant
|
|
36
|
+
ast = MacawAssistant(
|
|
37
|
+
prompt=prompt_template
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Create the Message Gateway - This component is the core of the assistant
|
|
41
|
+
# It handles the communication between the assistant and the connectors
|
|
42
|
+
gateway = MessageGateway(ast)
|
|
43
|
+
|
|
44
|
+
# For this example, we will use the CLI connector
|
|
45
|
+
conn = CliConnector(
|
|
46
|
+
stream_mode=StreamMode.FULL
|
|
47
|
+
)
|
|
48
|
+
# Register the connector with the gateway
|
|
49
|
+
gateway.register_connector(conn)
|
|
50
|
+
|
|
51
|
+
# Then start the gateway and begin processing messages
|
|
52
|
+
gateway.run()
|
|
53
|
+
```
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
from loguru import logger as log
|
|
5
|
+
from cel.assistants.function_context import FunctionContext
|
|
6
|
+
from cel.assistants.function_response import FunctionResponse
|
|
7
|
+
from cel.gateway.model.base_connector import BaseConnector
|
|
8
|
+
from cel.assistants.common import EventResponse, FunctionDefinition
|
|
9
|
+
from cel.gateway.model.message import Message, ConversationLead
|
|
10
|
+
from cel.prompt.prompt_template import PromptTemplate
|
|
11
|
+
from cel.rag.providers.rag_retriever import RAGRetriever
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Events:
|
|
15
|
+
START: str = "start"
|
|
16
|
+
NEW_MESSAGE: str = "new_message"
|
|
17
|
+
IMAGE: str = "image"
|
|
18
|
+
AUDIO: str = "audio"
|
|
19
|
+
END: str = "end"
|
|
20
|
+
|
|
21
|
+
class BaseAssistant(ABC):
|
|
22
|
+
|
|
23
|
+
def __init__(self, prompt: PromptTemplate = None):
|
|
24
|
+
|
|
25
|
+
self.function_handlers = {}
|
|
26
|
+
self.event_handlers = {}
|
|
27
|
+
self.client_commands_handlers = {}
|
|
28
|
+
self.timeout_handlers = {}
|
|
29
|
+
self.rag_retriever: RAGRetriever = None
|
|
30
|
+
if prompt:
|
|
31
|
+
assert isinstance(prompt, PromptTemplate), "prompt must be an instance of PromptTemplate"
|
|
32
|
+
self.prompt = prompt or PromptTemplate('')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def set_rag_retrieval(self, rag_retrieval: RAGRetriever):
|
|
37
|
+
# check type
|
|
38
|
+
assert isinstance(rag_retrieval, RAGRetriever), "rag_retrieval must be an instance of RAGRetriever"
|
|
39
|
+
self.rag_retriever = rag_retrieval
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def function(self, name, desc, params):
|
|
43
|
+
def decorator(func):
|
|
44
|
+
self.function_handlers[name] = {
|
|
45
|
+
# 'desc': desc,
|
|
46
|
+
# 'params': params,
|
|
47
|
+
'definition': FunctionDefinition(name=name, description=desc, parameters=params),
|
|
48
|
+
'func': func
|
|
49
|
+
}
|
|
50
|
+
def wrapper(*args, **kwargs):
|
|
51
|
+
return func(*args, **kwargs)
|
|
52
|
+
|
|
53
|
+
return wrapper
|
|
54
|
+
return decorator
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def event(self, name: Events | str):
|
|
58
|
+
def decorator(func):
|
|
59
|
+
self.event_handlers[name] = {
|
|
60
|
+
'func': func
|
|
61
|
+
}
|
|
62
|
+
def wrapper(*args, **kwargs):
|
|
63
|
+
return func(*args, **kwargs)
|
|
64
|
+
|
|
65
|
+
return wrapper
|
|
66
|
+
return decorator
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def client_command(self, name):
|
|
70
|
+
def decorator(func):
|
|
71
|
+
self.client_commands_handlers[name] = {
|
|
72
|
+
'func': func
|
|
73
|
+
}
|
|
74
|
+
def wrapper(*args, **kwargs):
|
|
75
|
+
return func(*args, **kwargs)
|
|
76
|
+
|
|
77
|
+
return wrapper
|
|
78
|
+
return decorator
|
|
79
|
+
|
|
80
|
+
def timeout(self, label):
|
|
81
|
+
def decorator(func):
|
|
82
|
+
self.timeout_handlers[label] = {
|
|
83
|
+
'func': func
|
|
84
|
+
}
|
|
85
|
+
def wrapper(*args, **kwargs):
|
|
86
|
+
return func(*args, **kwargs)
|
|
87
|
+
|
|
88
|
+
return wrapper
|
|
89
|
+
return decorator
|
|
90
|
+
|
|
91
|
+
async def eval_client_command(self, command: str, args: list[str], message: Message):
|
|
92
|
+
"""Evaluate the client command and call the respective handler"""
|
|
93
|
+
lead = message.lead
|
|
94
|
+
connector = lead.connector
|
|
95
|
+
|
|
96
|
+
if command in self.client_commands_handlers:
|
|
97
|
+
func = self.client_commands_handlers[command]['func']
|
|
98
|
+
# Build args_dict
|
|
99
|
+
from cel.gateway.request_context import RequestContext
|
|
100
|
+
ctx = RequestContext(lead=lead,
|
|
101
|
+
connector=connector,
|
|
102
|
+
assistant=self)
|
|
103
|
+
args_dict = {
|
|
104
|
+
'session': lead.get_session_id(),
|
|
105
|
+
'ctx': ctx,
|
|
106
|
+
'lead': lead,
|
|
107
|
+
'message': None,
|
|
108
|
+
'connector': connector,
|
|
109
|
+
'command': command,
|
|
110
|
+
'args': args
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Build kwargs using args and args_dict
|
|
114
|
+
args = inspect.getfullargspec(func).args
|
|
115
|
+
kwargs = {}
|
|
116
|
+
for arg in args:
|
|
117
|
+
if arg in args_dict:
|
|
118
|
+
kwargs[arg] = args_dict[arg]
|
|
119
|
+
|
|
120
|
+
# Check if the function is a coroutine (async function)
|
|
121
|
+
if inspect.iscoroutinefunction(func):
|
|
122
|
+
await func(**kwargs)
|
|
123
|
+
else:
|
|
124
|
+
func(**kwargs)
|
|
125
|
+
return False
|
|
126
|
+
else:
|
|
127
|
+
log.warning(f"Client command {command} not found in assistant implementation")
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
async def call_event(self, event_name: str, lead: ConversationLead, message: Message = None, connector: BaseConnector = None, data: dict= None) -> EventResponse:
|
|
131
|
+
"""Call the respective event handler, if exists"""
|
|
132
|
+
if event_name in self.event_handlers:
|
|
133
|
+
func = self.event_handlers[event_name]['func']
|
|
134
|
+
connector = connector or lead.connector
|
|
135
|
+
from cel.gateway.request_context import RequestContext
|
|
136
|
+
ctx = RequestContext(lead=lead,
|
|
137
|
+
message=message,
|
|
138
|
+
assistant=self,
|
|
139
|
+
connector=connector)
|
|
140
|
+
|
|
141
|
+
# Build args_dict
|
|
142
|
+
args_dict = {
|
|
143
|
+
'session': lead.get_session_id(),
|
|
144
|
+
'ctx': ctx,
|
|
145
|
+
'lead': lead,
|
|
146
|
+
'message': message,
|
|
147
|
+
'connector': connector,
|
|
148
|
+
'data': data
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# Build kwargs using args and args_dict
|
|
152
|
+
args = inspect.getfullargspec(func).args
|
|
153
|
+
kwargs = {}
|
|
154
|
+
for arg in args:
|
|
155
|
+
if arg in args_dict:
|
|
156
|
+
kwargs[arg] = args_dict[arg]
|
|
157
|
+
|
|
158
|
+
# Check if the function is a coroutine (async function)
|
|
159
|
+
response = None
|
|
160
|
+
if inspect.iscoroutinefunction(func):
|
|
161
|
+
response = await func(**kwargs)
|
|
162
|
+
else:
|
|
163
|
+
response = func(**kwargs)
|
|
164
|
+
|
|
165
|
+
assert isinstance(response, EventResponse) or response is None, "Event handler must return an instance of EventResponse or None"
|
|
166
|
+
return response
|
|
167
|
+
else:
|
|
168
|
+
# raise ValueError(f"Event {event_name} not found")
|
|
169
|
+
log.error(f"Event {event_name} not found")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def call_function(self, func_name: str, params: dict, lead: ConversationLead) -> FunctionResponse:
|
|
173
|
+
"""Call the respective function handler, if exists. Map function arguments to the function handler signature"""
|
|
174
|
+
connector = lead.connector
|
|
175
|
+
if func_name in self.function_handlers:
|
|
176
|
+
func = self.function_handlers[func_name]['func']
|
|
177
|
+
args = inspect.getfullargspec(func).args
|
|
178
|
+
|
|
179
|
+
ctx = FunctionContext(lead=lead, connector=connector)
|
|
180
|
+
args_dict = {
|
|
181
|
+
'session': lead.get_session_id(),
|
|
182
|
+
'ctx': ctx,
|
|
183
|
+
'params': params,
|
|
184
|
+
'lead': lead,
|
|
185
|
+
'message': None,
|
|
186
|
+
'connector': connector
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Build kwargs using args and args_dict
|
|
190
|
+
kwargs = {}
|
|
191
|
+
for arg in args:
|
|
192
|
+
if arg in args_dict:
|
|
193
|
+
kwargs[arg] = args_dict[arg]
|
|
194
|
+
|
|
195
|
+
# Check if the function is a coroutine (async function)
|
|
196
|
+
response = None
|
|
197
|
+
if inspect.iscoroutinefunction(func):
|
|
198
|
+
response = await func(**kwargs)
|
|
199
|
+
else:
|
|
200
|
+
response = func(**kwargs)
|
|
201
|
+
|
|
202
|
+
assert isinstance(response, FunctionResponse) or response is None, "Function handler must return an instance of CommandResponse or None"
|
|
203
|
+
return response
|
|
204
|
+
|
|
205
|
+
else:
|
|
206
|
+
raise ValueError(f"Function {func_name} not found")
|
|
207
|
+
|
|
208
|
+
@abstractmethod
|
|
209
|
+
async def new_message(self, lead: ConversationLead, message: str, local_state: dict = {}):
|
|
210
|
+
raise NotImplementedError
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@abstractmethod
|
|
214
|
+
async def blend(self, lead: ConversationLead, text: str, history_length: int = 3):
|
|
215
|
+
raise NotImplementedError
|
|
216
|
+
|
|
217
|
+
@abstractmethod
|
|
218
|
+
async def process_client_command(self, lead: ConversationLead, command: str, args: list[str]):
|
|
219
|
+
yield "Command not found"
|
|
220
|
+
|
|
221
|
+
def get_functions(self) -> list[FunctionDefinition]:
|
|
222
|
+
return [f['definition'] for f in self.function_handlers.values()]
|
|
223
|
+
|
|
224
|
+
def get_events(self):
|
|
225
|
+
return self.event_handlers
|
|
226
|
+
|
|
227
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class Param(ABC):
|
|
8
|
+
name: str
|
|
9
|
+
type: str
|
|
10
|
+
description: str
|
|
11
|
+
required: bool = True
|
|
12
|
+
enum: list[str] = None
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class FunctionDefinition(ABC):
|
|
16
|
+
name: str
|
|
17
|
+
description: str
|
|
18
|
+
parameters: list[Param]
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class EventResponse(ABC):
|
|
22
|
+
text: str = None
|
|
23
|
+
image: str = None
|
|
24
|
+
audio: str = None
|
|
25
|
+
video: str = None
|
|
26
|
+
disable_ai_response: bool = False
|
|
27
|
+
blend: bool = False
|
|
28
|
+
is_private: bool = False
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from cel.assistants.function_response import FunctionResponse, RequestMode
|
|
2
|
+
from cel.gateway.model.base_connector import BaseConnector
|
|
3
|
+
from cel.gateway.model.message import ConversationLead, Message
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class FunctionContext:
|
|
11
|
+
lead: ConversationLead
|
|
12
|
+
message: Message | None = None
|
|
13
|
+
connector: BaseConnector | None = None
|
|
14
|
+
state: dict | None = field(default_factory=dict)
|
|
15
|
+
history: list[dict] | None = field(default_factory=list)
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def response_text(text: str, request_mode: RequestMode = RequestMode.SINGLE):
|
|
19
|
+
return FunctionResponse(text=text, request_mode=request_mode)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# @staticmethod
|
|
23
|
+
# def response_image(image_url: str, request_mode: RequestMode = RequestMode.SINGLE):
|
|
24
|
+
# return FunctionResponse(image_url=image_url, request_mode=request_mode)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RequestMode:
|
|
6
|
+
STREAM = "stream"
|
|
7
|
+
SINGLE = "single"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class FunctionResponse:
|
|
12
|
+
text: str
|
|
13
|
+
|
|
14
|
+
""" the execution function response will be sent as a stream of chunks or as a single response """
|
|
15
|
+
request_mode: str = RequestMode.SINGLE
|
|
16
|
+
|
|
17
|
+
""" If you want to disable direct response from Asiistant AI, use callback to handle the response.
|
|
18
|
+
It is useful when you want to handle the response in a different way, like sending the response to a different service.
|
|
19
|
+
Another use case is when oyu need to use specific platform features to send the response, like interactives messages, buttons, etc.
|
|
20
|
+
"""
|
|
21
|
+
callback: Callable = None
|
|
22
|
+
|
|
23
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# https://medium.com/@gal.peretz/openrouter-langchain-leverage-opensource-models-without-the-ops-hassle-9ffbf0016da7
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from langchain_community.chat_models import ChatOpenAI
|
|
6
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
OPENROUTER_API_BASE = "https://openrouter.ai/api/v1"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# TODO: OpenRouter Models has problems with Langchain tooling
|
|
13
|
+
# This LLM model wrapper throws an error when used with Langchain tooling
|
|
14
|
+
# specifically with bind_tools. The error is due to the fact that the
|
|
15
|
+
# OpenRouter models do not support tooling??? I am not sure about this.
|
|
16
|
+
# --------------------------------------------------------------------
|
|
17
|
+
class ChatOpenRouter(ChatOpenAI):
|
|
18
|
+
openai_api_base: str
|
|
19
|
+
openai_api_key: str
|
|
20
|
+
model_name: str
|
|
21
|
+
|
|
22
|
+
def __init__(self,
|
|
23
|
+
model: str,
|
|
24
|
+
openai_api_key: Optional[str] = None,
|
|
25
|
+
openai_api_base: str = OPENROUTER_API_BASE,
|
|
26
|
+
**kwargs):
|
|
27
|
+
openai_api_key = openai_api_key or os.getenv('OPENROUTER_API_KEY')
|
|
28
|
+
super().__init__(openai_api_base=openai_api_base,
|
|
29
|
+
openai_api_key=openai_api_key,
|
|
30
|
+
model_name=model, **kwargs)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def ChatOpenAIOpenRouter(**kwargs):
|
|
34
|
+
return ChatOpenAI(
|
|
35
|
+
**kwargs,
|
|
36
|
+
openai_api_base=OPENROUTER_API_BASE,
|
|
37
|
+
openai_api_key = os.getenv('OPENROUTER_API_KEY')
|
|
38
|
+
)
|
|
39
|
+
# --------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# if __name__ == '__main__':
|
|
43
|
+
|
|
44
|
+
# # Load .env variables
|
|
45
|
+
# from dotenv import load_dotenv
|
|
46
|
+
# load_dotenv()
|
|
47
|
+
|
|
48
|
+
# llm = ChatOpenRouter(
|
|
49
|
+
# model="mistralai/mixtral-8x7b-instruct"
|
|
50
|
+
# )
|
|
51
|
+
# prompt = ChatPromptTemplate.from_template("tell me a short joke about {topic}")
|
|
52
|
+
# openrouter_chain = prompt | llm
|
|
53
|
+
# print(openrouter_chain.invoke({"topic": "banana"}))
|