diff --git a/CHANGLOG.md b/CHANGLOG.md index 2eedf89..68f5c05 100644 --- a/CHANGLOG.md +++ b/CHANGLOG.md @@ -1,3 +1,8 @@ +## [0.1.19] - 2025-11-10 +### Fixed +- fix baggage escape problem +- enhance input tool_calls obtain + ## [0.1.18] - 2025-10-10 ### Added - fix prompt syntax error, use Union instead of | diff --git a/README.md b/README.md index 8eeabb2..3a3d649 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ The CozeLoop SDK is a Python client for interacting with [CozeLoop platform](htt Key features: - Report trace - Get and format prompt +- Execute Prompt as a Service (PTaaS) ## Requirements - Python 3.8 or higher diff --git a/README.zh_CN.md b/README.zh_CN.md index bc03512..6743aab 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -7,6 +7,7 @@ CozeLoop SDK 是一个用于与 [CozeLoop 平台](https://loop.coze.cn) 进行 主要功能: - Trace上报 - Prompt拉取 +- 执行Prompt as a Service (PTaaS) ## 要求 - Python 3.8 或更高版本 diff --git a/cozeloop/integration/langchain/trace_model/llm_model.py b/cozeloop/integration/langchain/trace_model/llm_model.py index 5dbf0e4..172ce61 100644 --- a/cozeloop/integration/langchain/trace_model/llm_model.py +++ b/cozeloop/integration/langchain/trace_model/llm_model.py @@ -5,7 +5,7 @@ import time from typing import List, Optional, Union, Dict, Any from pydantic.dataclasses import dataclass -from langchain_core.messages import BaseMessage, ToolMessage, AIMessageChunk +from langchain_core.messages import BaseMessage, ToolMessage, AIMessageChunk, AIMessage from langchain_core.outputs import Generation, ChatGeneration @@ -122,11 +122,30 @@ def __init__(self, messages: List[Union[BaseMessage, List[BaseMessage]]], invoca elif isinstance(inner_messages, List): for message in inner_messages: process_messages.append(message) + + tool_call_id_name_map = {} + for message in process_messages: + if isinstance(message, (AIMessageChunk, AIMessage)): + for tool_call in message.additional_kwargs.get('tool_calls', []): + if tool_call.get('id', ''): + tool_call_id_name_map[tool_call.get('id', '')] = tool_call.get('function', {}).get('name', '') + for tool_call in message.tool_calls: + if tool_call.get('id', ''): + tool_call_id_name_map[tool_call.get('id', '')] = tool_call.get('name', '') + for message in process_messages: - if isinstance(message, AIMessageChunk): - self._messages.append(Message(role=message.type, content=message.content, tool_calls=convert_tool_calls(message.additional_kwargs.get('tool_calls', [])))) + if isinstance(message, (AIMessageChunk, AIMessage)): + tool_calls = convert_tool_calls_by_additional_kwargs(message.additional_kwargs.get('tool_calls', [])) + if len(tool_calls) == 0: + tool_calls = convert_tool_calls_by_raw(message.tool_calls) + self._messages.append(Message(role=message.type, content=message.content, tool_calls=tool_calls)) elif isinstance(message, ToolMessage): - tool_call = ToolCall(id=message.tool_call_id, type=message.type, function= ToolFunction(name=message.additional_kwargs.get('name', ''))) + name = '' + if tool_call_id_name_map.get(message.tool_call_id, None) is not None: + name = tool_call_id_name_map[message.tool_call_id] + if message.additional_kwargs.get('name', ''): + name = message.additional_kwargs.get('name', '') + tool_call = ToolCall(id=message.tool_call_id, type=message.type, function=ToolFunction(name=name)) self._messages.append(Message(role=message.type, content=message.content, tool_calls=[tool_call])) else: self._messages.append(Message(role=message.type, content=message.content)) @@ -161,7 +180,7 @@ def to_json(self): for i, generation in enumerate(self.generations): choice: Choice = None if isinstance(generation, ChatGeneration): - tool_calls = convert_tool_calls(generation.message.additional_kwargs.get('tool_calls', [])) + tool_calls = convert_tool_calls_by_additional_kwargs(generation.message.additional_kwargs.get('tool_calls', [])) if len(tool_calls) == 0 and 'function_call' in generation.message.additional_kwargs: function_call = generation.message.additional_kwargs.get('function_call', {}) function = ToolFunction(name=function_call.get('name', ''), arguments=json.loads(function_call.get('arguments', {}))) @@ -178,9 +197,17 @@ def to_json(self): ensure_ascii=False) -def convert_tool_calls(tool_calls: list) -> List[ToolCall]: +def convert_tool_calls_by_raw(tool_calls: list) -> List[ToolCall]: + format_tool_calls: List[ToolCall] = [] + for tool_call in tool_calls: + function = ToolFunction(name=tool_call.get('name', ''), arguments=tool_call.get('args', {})) + format_tool_calls.append(ToolCall(id=tool_call.get('id', ''), type=tool_call.get('type', ''), function=function)) + return format_tool_calls + + +def convert_tool_calls_by_additional_kwargs(tool_calls: list) -> List[ToolCall]: format_tool_calls: List[ToolCall] = [] for tool_call in tool_calls: - function = ToolFunction(name=tool_call.get('function', {}).get('name', ''), arguments=json.loads(tool_call.get('function', {}).get('arguments', {}))) + function = ToolFunction(name=tool_call.get('function', {}).get('name', ''), arguments=json.loads(tool_call.get('function', {}).get('arguments', '{}'))) format_tool_calls.append(ToolCall(id=tool_call.get('id', ''), type=tool_call.get('type', ''), function=function)) return format_tool_calls \ No newline at end of file diff --git a/cozeloop/internal/trace/span.py b/cozeloop/internal/trace/span.py index 76e84f5..1a64b6c 100644 --- a/cozeloop/internal/trace/span.py +++ b/cozeloop/internal/trace/span.py @@ -456,26 +456,18 @@ def set_multi_modality_map(self, key: str): self.multi_modality_key_map[key] = True def set_baggage(self, baggage_item: Dict[str, str]): - if not baggage_item: - return - self.set_baggage_escape(baggage_item, True) - - def set_baggage_escape(self, baggage_item: Dict[str, str], escape: bool): if not baggage_item: return try: for key, value in baggage_item.items(): if self.is_valid_baggage_item(key, value): self.set_tags({key: value}) - if escape: - key = urllib.parse.quote(key) - value = urllib.parse.quote(value) self.set_baggage_item(key, value) else: logger.error(f"[trace] invalid baggageItem:{key}:{value}") pass except Exception as e: - logger.error(f"Failed to set_baggage_escape: {e}") + logger.error(f"Failed to set_baggage: {e}") def is_valid_baggage_item(self, key: str, value: str) -> bool: key_limit = get_tag_key_size_limit() @@ -565,7 +557,7 @@ def to_header(self) -> Dict[str, str]: def to_header_baggage(self) -> str: if not self.baggage: return "" - return ",".join(f"{k}={v}" for k, v in self.baggage().items() if k and v) + return ",".join(f"{urllib.parse.quote(k)}={urllib.parse.quote(v)}" for k, v in self.baggage().items() if k and v) def to_header_parent(self) -> str: return f"{GLOBAL_TRACE_VERSION:02x}-{self.trace_id}-{self.span_id}-{self.flags:02x}" diff --git a/cozeloop/internal/trace/trace.py b/cozeloop/internal/trace/trace.py index a99fbf0..94d21d1 100644 --- a/cozeloop/internal/trace/trace.py +++ b/cozeloop/internal/trace/trace.py @@ -145,7 +145,7 @@ def _start_span(self, tag_truncate_conf=self.tag_truncate_conf, ) - span.set_baggage_escape(baggage, False) + span.set_baggage(baggage) return span def flush(self): diff --git a/pyproject.toml b/pyproject.toml index 71f8244..ad1a87d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cozeloop" -version = "0.1.18" +version = "0.1.19" description = "coze loop sdk" authors = ["JiangQi715 "] license = "MIT"