feat(tools): 添加获取用户OKR列表和批量查询OKR详情的工具
新增两个与飞书OKR API集成的工具函数: - get_user_okr_list: 根据用户ID分页查询其OKR列表,支持按周期筛选和语言选择 - batch_get_okr: 根据OKR ID批量获取详细的目标及关键结果信息,并格式化输出进度、状态、截止日期等关键信息 同时在handle_call_tool中添加对应的调用处理逻辑。
This commit is contained in:
@@ -189,6 +189,62 @@ tools = [
|
||||
"required": ["user_id", "order_number", "asset_list", "face_cap", "user_ids"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="get_user_okr_list",
|
||||
description="根据用户user_id获取该用户的OKR列表,返回OKR ID及目标(Objective)和关键结果(Key Result)详情",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"description": "用户的user_id"
|
||||
},
|
||||
"offset": {
|
||||
"type": "string",
|
||||
"description": "分页偏移,默认0"
|
||||
},
|
||||
"limit": {
|
||||
"type": "string",
|
||||
"description": "每页数量,范围0-10,默认10"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"description": "语言版本: zh_cn 或 en_us,默认 zh_cn",
|
||||
"enum": ["zh_cn", "en_us"]
|
||||
},
|
||||
"period_ids": {
|
||||
"type": "array",
|
||||
"description": "OKR周期ID列表,最多10个,不传则返回所有周期",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["user_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="batch_get_okr",
|
||||
description="根据OKR ID批量获取OKR详情,返回目标(Objective)及其关键结果(Key Result)详情",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"okr_ids": {
|
||||
"type": "array",
|
||||
"description": "OKR ID列表,最多10个",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"description": "语言版本: zh_cn 或 en_us,默认 zh_cn",
|
||||
"enum": ["zh_cn", "en_us"]
|
||||
}
|
||||
},
|
||||
"required": ["okr_ids"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="send_feedback_card",
|
||||
description="发送资产变动反馈单,入参为receiver_ids、user_id、order_number、change_time、asset_list和remark",
|
||||
@@ -417,6 +473,107 @@ def _build_asset_options(inputs: List[str], outputs: List[str]) -> tuple[list[Di
|
||||
return options, "、".join(labels)
|
||||
|
||||
|
||||
def get_user_okr_list(
|
||||
token: str,
|
||||
user_id: str,
|
||||
offset: str = "0",
|
||||
limit: str = "10",
|
||||
lang: str = "zh_cn",
|
||||
period_ids: List[str] | None = None,
|
||||
) -> str:
|
||||
url = f"https://open.feishu.cn/open-apis/okr/v1/users/{user_id}/okrs"
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
params: Dict[str, object] = {
|
||||
"user_id_type": "user_id",
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"lang": lang,
|
||||
}
|
||||
if period_ids:
|
||||
params["period_ids"] = period_ids
|
||||
response = requests.get(url, headers=headers, params=params, timeout=15)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
if payload.get("code") not in (0, None):
|
||||
raise RuntimeError(f"获取用户OKR列表失败: {payload}")
|
||||
data = payload.get("data", {})
|
||||
total = data.get("total", 0)
|
||||
okr_list = data.get("okr_list", [])
|
||||
return json.dumps({"total": total, "okr_list": okr_list}, ensure_ascii=False)
|
||||
|
||||
|
||||
def batch_get_okr(
|
||||
token: str,
|
||||
okr_ids: List[str],
|
||||
lang: str = "zh_cn",
|
||||
) -> str:
|
||||
url = "https://open.feishu.cn/open-apis/okr/v1/okrs/batch_get"
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
params: Dict[str, object] = {
|
||||
"okr_ids": okr_ids,
|
||||
"user_id_type": "user_id",
|
||||
"lang": lang,
|
||||
}
|
||||
response = requests.get(url, headers=headers, params=params, timeout=15)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
if payload.get("code") not in (0, None):
|
||||
raise RuntimeError(f"批量获取OKR失败: {payload}")
|
||||
data = payload.get("data", {})
|
||||
okr_list = data.get("okr_list", [])
|
||||
lines: List[str] = [f"共返回 {len(okr_list)} 条OKR记录:"]
|
||||
status_map = {"-1": "暂无", "0": "正常", "1": "风险", "2": "延期"}
|
||||
confirm_map = {0: "初始状态", 1: "待提交", 2: "待确认", 3: "已拒绝", 4: "已确认"}
|
||||
for idx, okr in enumerate(okr_list, 1):
|
||||
okr_name = okr.get("name") or okr.get("id", "")
|
||||
confirm = okr.get("confirm_status")
|
||||
confirm_text = confirm_map.get(confirm, str(confirm)) if confirm is not None else ""
|
||||
lines.append(f"\n{'='*40}")
|
||||
lines.append(f"OKR #{idx}: {okr_name} (周期ID: {okr.get('period_id', '')})")
|
||||
if confirm_text:
|
||||
lines.append(f" 确认状态: {confirm_text}")
|
||||
objective_list = okr.get("objective_list", [])
|
||||
for oi, obj in enumerate(objective_list, 1):
|
||||
content = obj.get("content", "")
|
||||
score = obj.get("score", "")
|
||||
weight = obj.get("weight", "")
|
||||
progress_report = obj.get("progress_report", "")
|
||||
progress = obj.get("progress_rate") or {}
|
||||
percent = progress.get("percent", "")
|
||||
status = status_map.get(str(progress.get("status", "")), "")
|
||||
deadline = obj.get("deadline", "")
|
||||
lines.append(f" O{oi}: {content}")
|
||||
if weight:
|
||||
lines.append(f" 权重: {weight}%")
|
||||
if score:
|
||||
lines.append(f" 评分: {score}")
|
||||
if percent != "":
|
||||
lines.append(f" 进度: {percent}%{(' (' + status + ')') if status else ''}")
|
||||
if deadline and deadline != "0":
|
||||
lines.append(f" 截止: {datetime.fromtimestamp(int(deadline) / 1000, tz=timezone(timedelta(hours=8))).strftime('%Y-%m-%d')}")
|
||||
if progress_report:
|
||||
lines.append(f" 备注: {progress_report}")
|
||||
kr_list = obj.get("kr_list", [])
|
||||
for ki, kr in enumerate(kr_list, 1):
|
||||
kr_content = kr.get("content", "")
|
||||
kr_score = kr.get("score", "")
|
||||
kr_weight = kr.get("kr_weight", "")
|
||||
kr_deadline = kr.get("deadline", "")
|
||||
kr_progress = kr.get("progress_rate") or {}
|
||||
kr_percent = kr_progress.get("percent", "")
|
||||
kr_status = status_map.get(str(kr_progress.get("status", "")), "")
|
||||
lines.append(f" KR{ki}: {kr_content}")
|
||||
if kr_weight:
|
||||
lines.append(f" 权重: {kr_weight}%")
|
||||
if kr_score:
|
||||
lines.append(f" 评分: {kr_score}")
|
||||
if kr_percent != "":
|
||||
lines.append(f" 进度: {kr_percent}%{(' (' + kr_status + ')') if kr_status else ''}")
|
||||
if kr_deadline and kr_deadline != "0":
|
||||
lines.append(f" 截止: {datetime.fromtimestamp(int(kr_deadline) / 1000, tz=timezone(timedelta(hours=8))).strftime('%Y-%m-%d')}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _build_select_options(items: List[str]) -> list[Dict[str, object]]:
|
||||
options: list[Dict[str, object]] = []
|
||||
for item in items:
|
||||
@@ -1883,6 +2040,39 @@ async def handle_call_tool(name: str, arguments: Dict[str, object], token: str)
|
||||
user_ids,
|
||||
remark
|
||||
)
|
||||
elif name == "get_user_okr_list":
|
||||
user_id = str(arguments.get("user_id", "")).strip()
|
||||
if not user_id:
|
||||
raise ValueError("missing user_id")
|
||||
offset = str(arguments.get("offset") or "0").strip()
|
||||
limit = str(arguments.get("limit") or "10").strip()
|
||||
lang = str(arguments.get("lang") or "zh_cn").strip()
|
||||
period_ids = arguments.get("period_ids")
|
||||
if period_ids is not None and not isinstance(period_ids, list):
|
||||
period_ids = None
|
||||
result = get_user_okr_list(
|
||||
token,
|
||||
user_id,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
lang=lang,
|
||||
period_ids=period_ids,
|
||||
)
|
||||
elif name == "batch_get_okr":
|
||||
okr_ids = arguments.get("okr_ids")
|
||||
if not okr_ids or not isinstance(okr_ids, list):
|
||||
raise ValueError("missing or invalid okr_ids, must be a list of OKR ID strings")
|
||||
if len(okr_ids) > 10:
|
||||
raise ValueError("okr_ids supports at most 10 items")
|
||||
okr_ids = [str(oid).strip() for oid in okr_ids if str(oid).strip()]
|
||||
if not okr_ids:
|
||||
raise ValueError("okr_ids is empty after filtering")
|
||||
lang = str(arguments.get("lang") or "zh_cn").strip()
|
||||
result = batch_get_okr(
|
||||
token,
|
||||
okr_ids,
|
||||
lang=lang,
|
||||
)
|
||||
elif name == "send_feedback_card":
|
||||
user_id = str(arguments.get("user_id", "")).strip()
|
||||
order_number = str(arguments.get("order_number", "")).strip()
|
||||
|
||||
Reference in New Issue
Block a user