Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGLOG.md
Original file line number Diff line number Diff line change
@@ -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 |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions README.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CozeLoop SDK 是一个用于与 [CozeLoop 平台](https://loop.coze.cn) 进行
主要功能:
- Trace上报
- Prompt拉取
- 执行Prompt as a Service (PTaaS)

## 要求
- Python 3.8 或更高版本
Expand Down
41 changes: 34 additions & 7 deletions cozeloop/integration/langchain/trace_model/llm_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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', {})))
Expand All @@ -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
12 changes: 2 additions & 10 deletions cozeloop/internal/trace/span.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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}"
Expand Down
2 changes: 1 addition & 1 deletion cozeloop/internal/trace/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "cozeloop"
version = "0.1.18"
version = "0.1.19"
description = "coze loop sdk"
authors = ["JiangQi715 <[email protected]>"]
license = "MIT"
Expand Down