Skip to content

Commit 64e16cb

Browse files
authored
feat: add lark_send_message (#3688)
1 parent 08bd137 commit 64e16cb

File tree

3 files changed

+120
-4
lines changed

3 files changed

+120
-4
lines changed

camel/toolkits/lark_toolkit.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,72 @@ def lark_get_chat_messages(
356356
logger.error(f"Error getting chat messages: {e}")
357357
return {"error": f"Error getting chat messages: {e!s}"}
358358

359+
def lark_send_message(
360+
self,
361+
receive_id: str,
362+
text: str,
363+
receive_id_type: Literal[
364+
"open_id", "user_id", "union_id", "email", "chat_id"
365+
] = "chat_id",
366+
) -> Dict[str, object]:
367+
r"""Sends a message to a user or chat. If send message to
368+
a chat, use lark_list_chats to get chat_id first, if send
369+
message to a user, need user provide open_id, user_id,
370+
union_id or email.
371+
372+
Args:
373+
receive_id (str): The recipient identifier.
374+
text (str): The text message content.
375+
receive_id_type (str): The recipient ID type. Options:
376+
- "open_id"
377+
- "user_id"
378+
- "union_id"
379+
- "email"
380+
- "chat_id" (default)
381+
382+
Returns:
383+
Dict[str, object]: A dictionary containing:
384+
- message_id: The sent message ID
385+
- chat_id: The chat ID the message belongs to
386+
- msg_type: Message type (text)
387+
"""
388+
# NOTE: Currently supports plain text messages only.
389+
try:
390+
url = f"{self._domain}/open-apis/im/v1/messages"
391+
headers = self._get_tenant_http_headers()
392+
params = {"receive_id_type": receive_id_type}
393+
payload = {
394+
"receive_id": receive_id,
395+
"msg_type": "text",
396+
"content": json.dumps({"text": text}),
397+
}
398+
399+
response = requests.post(
400+
url, headers=headers, params=params, json=payload, timeout=30
401+
)
402+
result = response.json()
403+
404+
if result.get("code") != 0:
405+
logger.error(
406+
f"Failed to send message: {result.get('code')} - "
407+
f"{result.get('msg')}"
408+
)
409+
return {
410+
"error": f"Failed to send message: {result.get('msg')}",
411+
"code": result.get("code"),
412+
}
413+
414+
data = result.get("data", {}) or {}
415+
return {
416+
"message_id": data.get("message_id"),
417+
"chat_id": data.get("chat_id"),
418+
"msg_type": data.get("msg_type"),
419+
}
420+
421+
except Exception as e:
422+
logger.error(f"Error sending message: {e}")
423+
return {"error": f"Error sending message: {e!s}"}
424+
359425
def lark_get_message_resource(
360426
self,
361427
message_id: str,
@@ -507,6 +573,7 @@ def get_tools(self) -> List[FunctionTool]:
507573
return [
508574
FunctionTool(self.lark_list_chats),
509575
FunctionTool(self.lark_get_chat_messages),
576+
FunctionTool(self.lark_send_message),
510577
FunctionTool(self.lark_get_message_resource),
511578
FunctionTool(self.lark_get_message_resource_key),
512579
]

examples/toolkits/lark_toolkit_example.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,17 @@
6464
)
6565
print(f"Response: {response.msgs[0].content}\n")
6666

67-
# Example 4: Direct toolkit usage (without agent)
67+
# Example 4: Send a message to chat
6868
print("=" * 60)
69-
print("Example 4: Direct toolkit usage")
69+
print("Example 4: Send a message to chat")
70+
print("=" * 60)
71+
72+
response = agent.step("Say hi to my first chat ")
73+
print(f"Response: {response.msgs[0].content}\n")
74+
75+
# Example 5: Direct toolkit usage (without agent)
76+
print("=" * 60)
77+
print("Example 5: Direct toolkit usage")
7078
print("=" * 60)
7179

7280
# List chats directly

test/toolkits/test_lark_toolkit.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
# See the License for the specific language governing permissions and
1212
# limitations under the License.
1313
# ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
14+
import json
1415
import os
15-
from unittest.mock import patch
16+
from unittest.mock import Mock, patch
1617

1718
import pytest
1819

@@ -74,11 +75,12 @@ def test_lark_toolkit_init_with_feishu(mock_env_vars):
7475
def test_lark_toolkit_get_tools(lark_toolkit):
7576
"""Test only the expected tools are exposed."""
7677
tools = lark_toolkit.get_tools()
77-
assert len(tools) == 4
78+
assert len(tools) == 5
7879
assert all(isinstance(tool, FunctionTool) for tool in tools)
7980
assert [tool.func.__name__ for tool in tools] == [
8081
"lark_list_chats",
8182
"lark_get_chat_messages",
83+
"lark_send_message",
8284
"lark_get_message_resource",
8385
"lark_get_message_resource_key",
8486
]
@@ -169,6 +171,45 @@ def test_lark_get_chat_messages_time_filters(lark_toolkit):
169171
assert params["sort_type"] == "ByCreateTimeAsc"
170172

171173

174+
def test_lark_send_message(lark_toolkit):
175+
"""Test sending a text message."""
176+
with patch("requests.post") as mock_post:
177+
tenant_response = Mock()
178+
tenant_response.json.return_value = {
179+
"code": 0,
180+
"tenant_access_token": "token",
181+
"expire": 7200,
182+
}
183+
send_response = Mock()
184+
send_response.json.return_value = {
185+
"code": 0,
186+
"data": {
187+
"message_id": "om_123",
188+
"chat_id": "oc_456",
189+
"msg_type": "text",
190+
},
191+
}
192+
mock_post.side_effect = [tenant_response, send_response]
193+
194+
result = lark_toolkit.lark_send_message(
195+
receive_id="oc_456",
196+
text="test content",
197+
receive_id_type="chat_id",
198+
)
199+
200+
assert result["message_id"] == "om_123"
201+
assert result["chat_id"] == "oc_456"
202+
assert result["msg_type"] == "text"
203+
204+
call_args = mock_post.call_args_list[1]
205+
params = call_args[1]["params"]
206+
payload = call_args[1]["json"]
207+
assert params["receive_id_type"] == "chat_id"
208+
assert payload["receive_id"] == "oc_456"
209+
assert payload["msg_type"] == "text"
210+
assert payload["content"] == json.dumps({"text": "test content"})
211+
212+
172213
def test_lark_get_message_resource(tmp_path, mock_env_vars):
173214
"""Test downloading a message resource."""
174215
from camel.toolkits import LarkToolkit

0 commit comments

Comments
 (0)