celai 0.1.0__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.
Files changed (130) hide show
  1. cel/__init__.py +0 -0
  2. cel/assets/celia_logo.png +0 -0
  3. cel/assistants/base_assistant.py +227 -0
  4. cel/assistants/common.py +28 -0
  5. cel/assistants/function_context.py +24 -0
  6. cel/assistants/function_response.py +23 -0
  7. cel/assistants/macaw/custom_chat_models/chat_open_router.py +53 -0
  8. cel/assistants/macaw/macaw_assistant.py +203 -0
  9. cel/assistants/macaw/macaw_history_adapter.py +33 -0
  10. cel/assistants/macaw/macaw_inference_context.py +28 -0
  11. cel/assistants/macaw/macaw_nlp.py +281 -0
  12. cel/assistants/macaw/macaw_settings.py +49 -0
  13. cel/assistants/macaw/macaw_test_chat_model.py +37 -0
  14. cel/assistants/macaw/macaw_utils.py +42 -0
  15. cel/assistants/stream_content_chunk.py +21 -0
  16. cel/cache.py +23 -0
  17. cel/comms/client_command.py +32 -0
  18. cel/comms/sentense_detection.py +116 -0
  19. cel/comms/utils.py +46 -0
  20. cel/config.py +15 -0
  21. cel/connectors/cli/cli_connector.py +169 -0
  22. cel/connectors/cli/model/cli_lead.py +40 -0
  23. cel/connectors/cli/model/cli_message.py +49 -0
  24. cel/connectors/telegram/__init__.py +4 -0
  25. cel/connectors/telegram/model/telegram_attachment.py +173 -0
  26. cel/connectors/telegram/model/telegram_lead.py +46 -0
  27. cel/connectors/telegram/model/telegram_message.py +46 -0
  28. cel/connectors/telegram/samples/hi.mp3 +0 -0
  29. cel/connectors/telegram/samples/hi.oga +0 -0
  30. cel/connectors/telegram/samples/hi.ogg +0 -0
  31. cel/connectors/telegram/samples/message.json +21 -0
  32. cel/connectors/telegram/samples/message_audio.json +27 -0
  33. cel/connectors/telegram/samples/message_callback_query.json +50 -0
  34. cel/connectors/telegram/samples/message_img.json +44 -0
  35. cel/connectors/telegram/samples/message_imgx2.json +44 -0
  36. cel/connectors/telegram/samples/message_location.json +24 -0
  37. cel/connectors/telegram/samples/message_location_place.json +34 -0
  38. cel/connectors/telegram/samples/sample_message.json +21 -0
  39. cel/connectors/telegram/stt_utils.py +53 -0
  40. cel/connectors/telegram/telegram_connector.py +263 -0
  41. cel/connectors/vapi/README.md +15 -0
  42. cel/connectors/vapi/model/vapi_lead.py +63 -0
  43. cel/connectors/vapi/model/vapi_message.py +59 -0
  44. cel/connectors/vapi/sample_data/chunk_vapi_1.json +21 -0
  45. cel/connectors/vapi/sample_data/chunk_vapi_2.json +21 -0
  46. cel/connectors/vapi/sample_data/chunk_vapi_3.json +21 -0
  47. cel/connectors/vapi/sample_data/chunk_vapi_last.json +21 -0
  48. cel/connectors/vapi/sample_data/request.json +31 -0
  49. cel/connectors/vapi/utils.py +26 -0
  50. cel/connectors/vapi/vapi_connector.py +162 -0
  51. cel/connectors/whatsapp/__init__.py +4 -0
  52. cel/connectors/whatsapp/components/component.py +7 -0
  53. cel/connectors/whatsapp/components/document.py +43 -0
  54. cel/connectors/whatsapp/components/document_url.py +40 -0
  55. cel/connectors/whatsapp/components/image.py +50 -0
  56. cel/connectors/whatsapp/components/reply_button.py +31 -0
  57. cel/connectors/whatsapp/components/text.py +28 -0
  58. cel/connectors/whatsapp/components/utils.py +14 -0
  59. cel/connectors/whatsapp/components/video.py +49 -0
  60. cel/connectors/whatsapp/constants.py +1 -0
  61. cel/connectors/whatsapp/functions/buttons.py +114 -0
  62. cel/connectors/whatsapp/functions/utils.py +328 -0
  63. cel/connectors/whatsapp/model/media_utils.py +143 -0
  64. cel/connectors/whatsapp/model/samples/img_caption_message.json +42 -0
  65. cel/connectors/whatsapp/model/samples/img_message.json +42 -0
  66. cel/connectors/whatsapp/model/samples/react_message.json +40 -0
  67. cel/connectors/whatsapp/model/samples/text_message.json +39 -0
  68. cel/connectors/whatsapp/model/whatsapp_attachment.py +254 -0
  69. cel/connectors/whatsapp/model/whatsapp_lead.py +51 -0
  70. cel/connectors/whatsapp/model/whatsapp_message.py +128 -0
  71. cel/connectors/whatsapp/phone_utils.py +10 -0
  72. cel/connectors/whatsapp/utils.py +6 -0
  73. cel/connectors/whatsapp/whatsapp_connector.py +485 -0
  74. cel/decoders/gcp_geocoding.py +30 -0
  75. cel/gateway/__init__.py +2 -0
  76. cel/gateway/message_gateway.py +401 -0
  77. cel/gateway/model/__init__.py +6 -0
  78. cel/gateway/model/attachment.py +93 -0
  79. cel/gateway/model/base_connector.py +36 -0
  80. cel/gateway/model/conversation_lead.py +46 -0
  81. cel/gateway/model/conversation_peer.py +42 -0
  82. cel/gateway/model/message.py +41 -0
  83. cel/gateway/model/message_gateway_context.py +10 -0
  84. cel/gateway/model/outgoing/__init__.py +5 -0
  85. cel/gateway/model/outgoing/outgoing_message.py +54 -0
  86. cel/gateway/model/outgoing/outgoing_message_factory.py +38 -0
  87. cel/gateway/model/outgoing/outgoing_message_link.py +62 -0
  88. cel/gateway/model/outgoing/outgoing_message_select.py +64 -0
  89. cel/gateway/model/outgoing/outgoing_message_text.py +48 -0
  90. cel/gateway/request_context.py +47 -0
  91. cel/message_enhancers/default_message_enhancer.py +23 -0
  92. cel/message_enhancers/smart_message_enhancer_openai.py +115 -0
  93. cel/middlewares/__init__.py +6 -0
  94. cel/middlewares/deepgram_stt.py +63 -0
  95. cel/middlewares/geodecoding.py +48 -0
  96. cel/middlewares/in_mem_blacklist.py +43 -0
  97. cel/middlewares/redis_blacklist.py +44 -0
  98. cel/middlewares/redis_blacklist_async.py +44 -0
  99. cel/middlewares/session_middleware.py +141 -0
  100. cel/model/common.py +37 -0
  101. cel/prompt/prompt_template.py +67 -0
  102. cel/rag/__init__.py +0 -0
  103. cel/rag/providers/markdown_rag.py +118 -0
  104. cel/rag/providers/rag_retriever.py +15 -0
  105. cel/rag/slicers/__init__.py +0 -0
  106. cel/rag/slicers/base_slicer.py +37 -0
  107. cel/rag/slicers/markdown/__init__.py +1 -0
  108. cel/rag/slicers/markdown/markdown.py +117 -0
  109. cel/rag/slicers/markdown/utils.py +74 -0
  110. cel/rag/slicers/utils.py +0 -0
  111. cel/rag/stores/chroma/chroma_store.py +113 -0
  112. cel/rag/stores/vector_store.py +57 -0
  113. cel/rag/text2vec/cached_openai.py +76 -0
  114. cel/rag/text2vec/utils.py +19 -0
  115. cel/stores/common/async_cache_aside_redis.py +146 -0
  116. cel/stores/common/cache_aside_redis.py +162 -0
  117. cel/stores/common/key_value_store.py +45 -0
  118. cel/stores/common/list_redis_store_async.py +36 -0
  119. cel/stores/common/memory_cache.py +82 -0
  120. cel/stores/history/base_history_provider.py +24 -0
  121. cel/stores/history/history_inmemory_provider.py +50 -0
  122. cel/stores/history/history_redis_provider.py +57 -0
  123. cel/stores/history/history_redis_provider_async.py +55 -0
  124. cel/stores/state/base_state_provider.py +31 -0
  125. cel/stores/state/state_inmemory_provider.py +43 -0
  126. cel/stores/state/state_redis_provider.py +58 -0
  127. cel/voice/deepgram_adapter.py +34 -0
  128. celai-0.1.0.dist-info/METADATA +95 -0
  129. celai-0.1.0.dist-info/RECORD +130 -0
  130. celai-0.1.0.dist-info/WHEEL +4 -0
cel/__init__.py ADDED
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"}))
@@ -0,0 +1,203 @@
1
+ import json
2
+ from loguru import logger as log
3
+ from cel.assistants.base_assistant import BaseAssistant
4
+ from cel.assistants.macaw.macaw_inference_context import MacawNlpInferenceContext
5
+ from cel.assistants.macaw.macaw_nlp import MacawFunctionCall, blend_message, process_insights, process_new_message
6
+ from cel.assistants.macaw.macaw_settings import MacawSettings
7
+ from cel.gateway.model.conversation_lead import ConversationLead
8
+ from cel.prompt.prompt_template import PromptTemplate
9
+ from cel.stores.history.base_history_provider import BaseHistoryProvider
10
+ from cel.stores.history.history_inmemory_provider import InMemoryHistoryProvider
11
+ from cel.stores.state.base_state_provider import BaseChatStateProvider
12
+ from cel.stores.state.state_inmemory_provider import InMemoryStateProvider
13
+ from langchain.load.load import loads, load
14
+ from langchain.load.dump import dumps
15
+
16
+
17
+ class MacawAssistant(BaseAssistant):
18
+ """Macaw Assistant is a conversational assistant that uses Langchain as core NLP.
19
+ This assistant perform different NLP tasks like blending, insights, and more.
20
+ To perform these tasks, Macaw Assistant uses differents models and tools. You can
21
+ configure the assistant with different settings and tools for each task.
22
+
23
+ If you use this assistant make sure to setup your LLM API key in the environment variable:
24
+ - OPENAI_API_KEY: The OpenAI API key for the assistant.
25
+
26
+ Tasks:
27
+ - Blending: Blend a message with the current context.
28
+ - Insights: Get insights from the current context.
29
+ - Process new message: Process a new message with the current context and return the response.
30
+
31
+ Args:
32
+ state (dict, optional): Initial state for the assistant. Defaults to None.
33
+ history_store (BaseHistoryProvider, optional): History store provider. Defaults to None.
34
+ state_store (BaseChatStateProvider, optional): State store provider. Defaults to None.
35
+ prompt (PromptTemplate, optional): Prompt template for the assistant. Defaults to None.
36
+ insight_targets (dict, optional): Insight targets for the assistant. Defaults to {}.
37
+ settings (MacawSettings, optional): Settings for the assistant. Defaults to None.
38
+
39
+ """
40
+
41
+ def __init__(self,
42
+ state: dict = None,
43
+ history_store:BaseHistoryProvider=None,
44
+ state_store: BaseChatStateProvider=None,
45
+ prompt: PromptTemplate = None,
46
+ insight_targets: dict = {},
47
+ settings: MacawSettings = None,
48
+ llm=None,
49
+ ):
50
+
51
+ super().__init__(prompt=prompt)
52
+ self.state = state or {}
53
+ self.insight_targets = insight_targets
54
+ self._state_store = state_store or InMemoryStateProvider()
55
+ self._history_store = history_store or InMemoryHistoryProvider()
56
+ if settings is None:
57
+ log.warning("No settings provided for Macaw Assistant, using default settings")
58
+ self.settings = settings or MacawSettings()
59
+ self.llm = llm
60
+ log.debug(f"Macaw Assistant initialized with settings: {self.settings}")
61
+
62
+
63
+ async def new_message(self, lead: ConversationLead, message: str, local_state: dict = {}):
64
+ # create context
65
+ ctx = MacawNlpInferenceContext(
66
+ lead=lead,
67
+ prompt=self.prompt,
68
+ init_state=self.state,
69
+ local_state=local_state,
70
+ functions=self.get_functions(),
71
+ history_store=self._history_store,
72
+ state_store=self._state_store,
73
+ settings=self.settings,
74
+ rag_retriever=self.rag_retriever,
75
+ llm=self.llm
76
+ )
77
+
78
+ async def on_function_call(ctx: MacawNlpInferenceContext, call: MacawFunctionCall):
79
+ return await self.call_function(
80
+ func_name=call.name,
81
+ params=call.args,
82
+ lead=ctx.lead
83
+ )
84
+ try:
85
+ # stream this message content from string
86
+ async for chunk in process_new_message(ctx, message, on_function_call):
87
+ yield chunk
88
+
89
+ except Exception as e:
90
+ log.error(f"Macaw Assistant: error processing new message: {e}")
91
+
92
+
93
+ async def blend(self, lead: ConversationLead, text: str, history_length: int = None):
94
+ # create context
95
+ ctx = MacawNlpInferenceContext(
96
+ lead=lead,
97
+ prompt=self.prompt,
98
+ init_state=self.state,
99
+ local_state={},
100
+ history_store=self._history_store,
101
+ state_store=self._state_store,
102
+ settings=self.settings
103
+ )
104
+
105
+
106
+ return await blend_message(ctx, message=text)
107
+
108
+ async def do_insights(self, lead: ConversationLead, targets: dict = {}, history_length: int = 10):
109
+ assert isinstance(targets, dict), "targets must be a dictionary"
110
+
111
+ if self.settings.insights_enabled is False:
112
+ log.warning("Insights are disabled, returning None")
113
+ return None
114
+
115
+ log.debug(f"Getting insights for lead: {lead} with targets: {targets}")
116
+
117
+ mix_targets = {**self.insight_targets, **targets}
118
+
119
+ # create context
120
+ ctx = MacawNlpInferenceContext(
121
+ lead=lead,
122
+ prompt=self.prompt,
123
+ init_state=self.state,
124
+ local_state={},
125
+ history_store=self._history_store,
126
+ state_store=self._state_store,
127
+ settings=self.settings
128
+ )
129
+
130
+ insights = await process_insights(ctx, targets=mix_targets)
131
+
132
+ # raise insight event
133
+ await self.call_event("insigths", lead, data=insights)
134
+ return insights
135
+
136
+
137
+ async def process_client_command(self, lead: ConversationLead, command: str, args: list[str]):
138
+
139
+ if command == "reset":
140
+
141
+ if args and args[0] == "all":
142
+ await self._history_store.clear_history(lead.get_session_id())
143
+ yield "History cleared"
144
+ self._state_store.set_store(lead.get_session_id(), {})
145
+ yield "State cleared"
146
+ return
147
+
148
+ await self._history_store.clear_history(lead.get_session_id())
149
+ yield "History cleared"
150
+ return
151
+
152
+ if command == "state":
153
+ state = self._state_store.get_store(lead.get_session_id())
154
+ if state is None:
155
+ yield "No state found"
156
+ return
157
+ for k, v in state.items():
158
+ yield f"{k}: {v}"
159
+ return
160
+
161
+ if command == "history":
162
+ history = await self._history_store.get_history(lead.get_session_id()) or []
163
+
164
+ if history is None or len(history) == 0:
165
+ yield "History is empty"
166
+ return
167
+
168
+ for h in history:
169
+ aux = load(h)
170
+ log.debug(f"History: {aux}")
171
+ yield dumps(aux)
172
+
173
+ return
174
+
175
+ if command == "set":
176
+ if len(args) < 2:
177
+ yield "Not enough arguments"
178
+ return
179
+ key = args[0]
180
+ value = args[1]
181
+ state = self._state_store.get_store(lead.get_session_id())
182
+ state[key] = value
183
+ self._state_store.set_store(lead.get_session_id(), state)
184
+ yield f"State updated: {key}: {value}"
185
+ return
186
+
187
+ if command == "prompt":
188
+ yield "Macaw Assistant v0.1"
189
+
190
+ if args and args[0] == "all":
191
+ prompt = self.prompt
192
+ # split into 250 chars aprox chunks
193
+ for i in range(0, len(prompt), 250):
194
+ yield prompt[i:i+250]
195
+ return
196
+
197
+ # first 250 chars
198
+ yield self.prompt[:250]
199
+ return
200
+
201
+
202
+
203
+
@@ -0,0 +1,33 @@
1
+ from cel.gateway.model.conversation_lead import ConversationLead
2
+ from langchain.load.dump import dumpd
3
+ from langchain.load.load import load
4
+ from langchain_core.messages import BaseMessage
5
+ from cel.stores.history.base_history_provider import BaseHistoryProvider
6
+
7
+
8
+ class MacawHistoryAdapter:
9
+ def __init__(self, store: BaseHistoryProvider):
10
+ self.store = store
11
+
12
+ async def append_to_history(self, lead: ConversationLead, entry: BaseMessage, metadata=None, ttl=None):
13
+ assert isinstance(lead, ConversationLead), f"Expected ConversationLead, got {type (lead)}"
14
+ aux = dumpd(entry)
15
+ await self.store.append_to_history(lead.get_session_id(), aux, metadata, ttl)
16
+
17
+ async def get_history(self, lead: ConversationLead) -> list[BaseMessage]:
18
+ assert isinstance(lead, ConversationLead), f"Expected ConversationLead, got {type (lead)}"
19
+ history = await self.store.get_history(lead.get_session_id())
20
+ return [load(h) for h in history]
21
+
22
+
23
+ async def clear_history(self, lead: ConversationLead, keep_last_messages=None):
24
+ assert isinstance(lead, ConversationLead), f"Expected ConversationLead, got {type (lead)}"
25
+ await self.store.clear_history(lead.get_session_id(), keep_last_messages)
26
+
27
+ async def get_last_messages(self, lead: ConversationLead, count) -> list[BaseMessage]:
28
+ assert isinstance(lead, ConversationLead), f"Expected ConversationLead, got {type (lead)}"
29
+ msgs = await self.store.get_last_messages(lead.get_session_id(), count)
30
+ return [load(m) for m in msgs]
31
+
32
+ async def close_conversation(self, lead: ConversationLead):
33
+ raise NotImplementedError
@@ -0,0 +1,28 @@
1
+ from cel.assistants.common import FunctionDefinition
2
+ from cel.assistants.macaw.macaw_settings import MacawSettings
3
+ from cel.gateway.model.conversation_lead import ConversationLead
4
+ from cel.prompt.prompt_template import PromptTemplate
5
+ from cel.rag.providers.rag_retriever import RAGRetriever
6
+ from cel.stores.history.base_history_provider import BaseHistoryProvider
7
+ from cel.stores.state.base_state_provider import BaseChatStateProvider
8
+
9
+
10
+ from abc import ABC
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+
15
+ @dataclass
16
+ class MacawNlpInferenceContext(ABC):
17
+ lead: ConversationLead
18
+ settings: MacawSettings
19
+ prompt: PromptTemplate = ''
20
+ init_state: dict = None
21
+ local_state: dict = None
22
+ functions: list[FunctionDefinition] = None
23
+ rag_retriever: RAGRetriever = None
24
+ # default value {} is used to avoid mutable default arguments
25
+ llm_kwargs: dict[str, Any] = None
26
+ history_store: BaseHistoryProvider = None
27
+ state_store: BaseChatStateProvider = None
28
+ llm: Any = None