Skip to content

Commit b04a8de

Browse files
committed
enhance: feat: Lark integration closes #3571
1 parent 7e674b3 commit b04a8de

File tree

3 files changed

+140
-37
lines changed

3 files changed

+140
-37
lines changed

camel/toolkits/lark_toolkit.py

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
import json
1616
import os
1717
import time
18-
from typing import Dict, List, Literal, Optional, Union
18+
from datetime import datetime, timezone
19+
from typing import Any, Dict, List, Literal, Optional, Union
1920

2021
import requests
2122

@@ -118,6 +119,78 @@ def _get_tenant_http_headers(self) -> Dict[str, str]:
118119
f"Failed to get tenant access token: {result.get('msg')}"
119120
)
120121

122+
def _convert_timestamp(self, ts: Any) -> str:
123+
r"""Convert millisecond timestamp to readable datetime string.
124+
125+
Args:
126+
ts: Timestamp value (can be string or int, in milliseconds).
127+
128+
Returns:
129+
str: ISO format datetime string, or original value if conversion
130+
fails.
131+
"""
132+
try:
133+
ts_int = int(ts)
134+
# Convert milliseconds to seconds
135+
dt = datetime.fromtimestamp(ts_int / 1000, tz=timezone.utc)
136+
return dt.strftime("%Y-%m-%d %H:%M:%S UTC")
137+
except (ValueError, TypeError, OSError):
138+
return str(ts)
139+
140+
def _process_message_items(
141+
self, items: List[Dict[str, Any]]
142+
) -> List[Dict[str, Any]]:
143+
r"""Process message items to agent-friendly format.
144+
145+
Args:
146+
items: List of message items from API response.
147+
148+
Returns:
149+
List[Dict[str, Any]]: Simplified items with only essential fields.
150+
"""
151+
processed = []
152+
for item in items:
153+
# Parse message content
154+
text = ""
155+
body = item.get("body", {})
156+
content = body.get("content", "")
157+
if content:
158+
try:
159+
content_obj = json.loads(content)
160+
if "text" in content_obj:
161+
text = content_obj["text"]
162+
elif "template" in content_obj:
163+
# System message: render template
164+
tpl = content_obj["template"]
165+
for key in ["from_user", "to_chatters"]:
166+
val = content_obj.get(key, [])
167+
if val:
168+
tpl = tpl.replace(
169+
"{" + key + "}", ", ".join(val)
170+
)
171+
else:
172+
tpl = tpl.replace("{" + key + "}", "")
173+
text = tpl.strip()
174+
elif "image_key" in content_obj:
175+
text = f"[Image: {content_obj['image_key']}]"
176+
elif "file_key" in content_obj:
177+
text = f"[File: {content_obj['file_key']}]"
178+
except (json.JSONDecodeError, TypeError):
179+
text = content
180+
181+
# Build simplified message
182+
sender = item.get("sender", {})
183+
msg = {
184+
"message_id": item.get("message_id"),
185+
"msg_type": item.get("msg_type"),
186+
"text": text,
187+
"time": self._convert_timestamp(item.get("create_time", "")),
188+
"sender_id": sender.get("id") or None,
189+
"sender_type": sender.get("sender_type") or None,
190+
}
191+
processed.append(msg)
192+
return processed
193+
121194
def lark_list_chats(
122195
self,
123196
sort_type: Literal["ByCreateTimeAsc", "ByActiveTimeDesc"] = (
@@ -174,7 +247,18 @@ def lark_list_chats(
174247
"code": result.get("code"),
175248
}
176249

177-
return result
250+
# Simplify chat items
251+
data = result.get("data", {})
252+
items = data.get("items", [])
253+
simplified = [
254+
{"chat_id": c.get("chat_id"), "name": c.get("name", "")}
255+
for c in items
256+
]
257+
return {
258+
"chats": simplified,
259+
"has_more": data.get("has_more", False),
260+
"page_token": data.get("page_token", ""),
261+
}
178262

179263
except Exception as e:
180264
logger.error(f"Error listing chats: {e}")
@@ -259,7 +343,14 @@ def lark_get_chat_messages(
259343
"code": result.get("code"),
260344
}
261345

262-
return result
346+
# Process and simplify messages
347+
data = result.get("data", {})
348+
items = data.get("items", [])
349+
return {
350+
"messages": self._process_message_items(items),
351+
"has_more": data.get("has_more", False),
352+
"page_token": data.get("page_token", ""),
353+
}
263354

264355
except Exception as e:
265356
logger.error(f"Error getting chat messages: {e}")
@@ -370,7 +461,12 @@ def lark_get_message_resource_key(
370461
}
371462

372463
data = result.get("data", {}) or {}
373-
body = data.get("body", {}) or {}
464+
items = data.get("items", []) or []
465+
if not items:
466+
return {"error": "No message found."}
467+
# Get the first message item
468+
item = items[0]
469+
body = item.get("body", {}) or {}
374470
content = body.get("content", "")
375471
if not content:
376472
return {"error": "Message content is empty."}

examples/toolkits/lark_toolkit_example.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,37 +34,41 @@
3434
tools=toolkit.get_tools(),
3535
)
3636

37-
# Example: Chat and message operations
37+
# Example 1: List all chats
3838
print("=" * 60)
39-
print("Lark Toolkit Example: Chat and Message Operations")
39+
print("Example 1: List all chats the bot belongs to")
4040
print("=" * 60)
4141

42-
response = agent.step(
43-
"List all chats I belong to, get the recent messages from the first chat, "
44-
"and if there are any images or files, download one of them."
45-
)
46-
print(f"Agent response: {response.msgs[0].content}")
42+
response = agent.step("List all chats I belong to.")
43+
print(f"Response: {response.msgs[0].content}\n")
4744

48-
"""
49-
==========================================================================
50-
Example output:
45+
# Example 2: Get messages from a specific chat
46+
print("=" * 60)
47+
print("Example 2: Get recent messages from a chat")
48+
print("=" * 60)
5149

52-
I've completed the Lark messaging tasks. Here's a summary:
50+
response = agent.step(
51+
"Get the 5 most recent messages from the first chat, "
52+
"sorted by newest first."
53+
)
54+
print(f"Response: {response.msgs[0].content}\n")
5355

54-
1. **Listed Chats**: Found 3 chats you belong to:
55-
- "Engineering Team" (group) - Chat ID: oc_xxx
56-
- "Project Alpha" (group) - Chat ID: oc_yyy
57-
- "John Doe" (p2p) - Chat ID: oc_zzz
56+
# Example 3: Download a resource from a message
57+
print("=" * 60)
58+
print("Example 3: Download image/file from a message")
59+
print("=" * 60)
5860

59-
2. **Retrieved Messages**: Got recent messages from "Engineering Team":
60-
- Found 15 messages in the chat
61-
- Messages include text discussions and file attachments
61+
response = agent.step(
62+
"If there are any images or files in the messages, "
63+
"get the resource key and download one of them."
64+
)
65+
print(f"Response: {response.msgs[0].content}\n")
6266

63-
3. **Downloaded Resource**: Found and downloaded an image:
64-
- Message ID: om_xxx
65-
- File Key: img_v2_xxx
66-
- Content Type: image/png
67-
- Saved to: ./workspace/lark_file/img_v2_xxx
67+
# Example 4: Direct toolkit usage (without agent)
68+
print("=" * 60)
69+
print("Example 4: Direct toolkit usage")
70+
print("=" * 60)
6871

69-
==========================================================================
70-
"""
72+
# List chats directly
73+
result = toolkit.lark_list_chats(page_size=10)
74+
print(f"lark_list_chats result: {result}")

test/toolkits/test_lark_toolkit.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ def test_lark_list_chats(lark_toolkit):
106106

107107
result = lark_toolkit.lark_list_chats()
108108

109-
assert result["code"] == 0
110-
assert len(result["data"]["items"]) == 1
109+
assert len(result["chats"]) == 1
110+
assert result["chats"][0]["chat_id"] == "oc_1"
111+
assert result["chats"][0]["name"] == "Team"
111112
mock_get.assert_called_once()
112113

113114

@@ -128,9 +129,9 @@ def test_lark_get_chat_messages(lark_toolkit):
128129

129130
result = lark_toolkit.lark_get_chat_messages(container_id="oc_123")
130131

131-
assert result["code"] == 0
132-
assert len(result["data"]["items"]) == 1
133-
assert result["data"]["has_more"] is False
132+
assert len(result["messages"]) == 1
133+
assert result["messages"][0]["message_id"] == "msg_1"
134+
assert result["has_more"] is False
134135

135136

136137
def test_lark_get_chat_messages_time_filters(lark_toolkit):
@@ -157,8 +158,8 @@ def test_lark_get_chat_messages_time_filters(lark_toolkit):
157158
sort_type="ByCreateTimeAsc",
158159
)
159160

160-
assert len(result["data"]["items"]) == 1
161-
assert result["data"]["has_more"] is True
161+
assert len(result["messages"]) == 1
162+
assert result["has_more"] is True
162163

163164
call_args = mock_get.call_args
164165
params = call_args[1]["params"]
@@ -206,7 +207,9 @@ def test_lark_get_message_resource_key(lark_toolkit):
206207
mock_get.return_value.json.return_value = {
207208
"code": 0,
208209
"data": {
209-
"body": {"content": ("{\"image_key\":\"img_v3_02tb_abc\"}")}
210+
"items": [
211+
{"body": {"content": '{"image_key":"img_v3_02tb_abc"}'}}
212+
]
210213
},
211214
}
212215

0 commit comments

Comments
 (0)