Skip to content

Commit 7be5643

Browse files
committed
add to_runnable decorator & lcc tool message add name and tool_call_id
1 parent 2a6eeb2 commit 7be5643

File tree

5 files changed

+142
-2
lines changed

5 files changed

+142
-2
lines changed

CHANGLOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## [0.1.25] - 2026-01-19
2+
### Added
3+
- add to_runnable decorator
4+
- lcc tool message add name and tool_call_id
5+
16
## [0.1.24] - 2026-01-16
27
### Added
38
- client init set default client if not exist

cozeloop/decorator/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55

66
coze_loop_decorator= CozeLoopDecorator()
77
observe = coze_loop_decorator.observe
8+
to_runnable = coze_loop_decorator.to_runnable

cozeloop/decorator/decorator.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from typing import Optional, Callable, Any, overload, Dict, Generic, Iterator, TypeVar, List, cast, AsyncIterator
55
from functools import wraps
66

7+
from langchain_core.runnables import RunnableLambda
8+
79
from cozeloop import Client, Span, start_span
810
from cozeloop.decorator.utils import is_async_func, is_gen_func, is_async_gen_func, is_class_func
911

@@ -312,6 +314,136 @@ async def async_stream_wrapper(*args: Any, **kwargs: Any):
312314
return decorator(func)
313315

314316

317+
def to_runnable(
318+
self,
319+
func: Callable = None,
320+
) -> Callable:
321+
"""
322+
Decorator to be RunnableLambda.
323+
324+
:param func: The function to be decorated, Requirements are as follows:
325+
1. The input parameter should only have one field, not multiple fields. You can use dict or Class as the input parameter.
326+
2. When the func is called, parameter config(RunnableConfig) is required, you must use the config containing cozeloop callback handler of 'current request', otherwise, the trace may be lost!
327+
328+
Examples:
329+
@to_runnable
330+
def runnable_func(my_input: dict) -> str:
331+
return input
332+
333+
async def scorer_leader(state: MyState) -> dict | str:
334+
await runnable_func({"a": "111", "b": 222, "c": "333"}, config=state.config) # config is required
335+
"""
336+
337+
def decorator(func: Callable):
338+
339+
@wraps(func)
340+
def sync_wrapper(*args: Any, **kwargs: Any):
341+
config = kwargs.pop("config", None)
342+
res = None
343+
try:
344+
inp = None
345+
if len(args) == 1:
346+
inp = args[0]
347+
else:
348+
inp = kwargs
349+
res = RunnableLambda(func).invoke(input=inp, config=config, **kwargs)
350+
if hasattr(res, "__iter__"):
351+
return res
352+
except StopIteration:
353+
pass
354+
except Exception as e:
355+
raise e
356+
finally:
357+
if res is not None:
358+
return res
359+
360+
@wraps(func)
361+
async def async_wrapper(*args: Any, **kwargs: Any):
362+
config = kwargs.pop("config", None)
363+
res = None
364+
try:
365+
inp = None
366+
if len(args) == 1:
367+
inp = args[0]
368+
else:
369+
inp = kwargs
370+
res = await RunnableLambda(func).ainvoke(input=inp, config=config, **kwargs)
371+
if hasattr(res, "__aiter__"):
372+
return res
373+
except StopIteration:
374+
pass
375+
except StopAsyncIteration:
376+
pass
377+
except Exception as e:
378+
if e.args and e.args[0] == 'coroutine raised StopIteration': # coroutine StopIteration
379+
pass
380+
else:
381+
raise e
382+
finally:
383+
if res is not None:
384+
return res
385+
386+
@wraps(func)
387+
def gen_wrapper(*args: Any, **kwargs: Any):
388+
config = kwargs.pop("config", None)
389+
try:
390+
inp = None
391+
if len(args) == 1:
392+
inp = args[0]
393+
else:
394+
inp = kwargs
395+
gen = RunnableLambda(func).invoke(input=inp, config=config, **kwargs)
396+
try:
397+
for item in gen:
398+
yield item
399+
except StopIteration:
400+
pass
401+
except Exception as e:
402+
raise e
403+
404+
@wraps(func)
405+
async def async_gen_wrapper(*args: Any, **kwargs: Any):
406+
config = kwargs.pop("config", None)
407+
try:
408+
inp = None
409+
if len(args) == 1:
410+
inp = args[0]
411+
else:
412+
inp = kwargs
413+
gen = RunnableLambda(func).invoke(input=inp, config=config, **kwargs)
414+
items = []
415+
try:
416+
async for item in gen:
417+
items.append(item)
418+
yield item
419+
finally:
420+
pass
421+
except StopIteration:
422+
pass
423+
except StopAsyncIteration:
424+
pass
425+
except Exception as e:
426+
if e.args and e.args[0] == 'coroutine raised StopIteration':
427+
pass
428+
else:
429+
raise e
430+
431+
432+
if is_async_gen_func(func):
433+
return async_gen_wrapper
434+
if is_gen_func(func):
435+
return gen_wrapper
436+
elif is_async_func(func):
437+
return async_wrapper
438+
else:
439+
return sync_wrapper
440+
441+
if func is None:
442+
return decorator
443+
else:
444+
return decorator(func)
445+
446+
315447
class _CozeLoopTraceStream(Generic[S]):
316448
def __init__(
317449
self,

cozeloop/integration/langchain/trace_model/llm_model.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class Message:
5353
tool_calls: List[ToolCall] = None
5454
metadata: Optional[dict] = None
5555
reasoning_content: Optional[str] = None
56+
name: Optional[str] = None
57+
tool_call_id: Optional[str] = None
5658

5759
def __post_init__(self):
5860
if self.role is not None and (self.role == 'AIMessageChunk' or self.role == 'ai'):
@@ -155,7 +157,7 @@ def __init__(self, messages: List[Union[BaseMessage, List[BaseMessage]]], invoca
155157
if message.additional_kwargs is not None and message.additional_kwargs.get('name', ''):
156158
name = message.additional_kwargs.get('name', '')
157159
tool_call = ToolCall(id=message.tool_call_id, type=message.type, function=ToolFunction(name=name))
158-
self._messages.append(Message(role=message.type, content=message.content, tool_calls=[tool_call]))
160+
self._messages.append(Message(role=message.type, content=message.content, tool_calls=[tool_call], name=name, tool_call_id=message.tool_call_id))
159161
else:
160162
self._messages.append(Message(role=message.type, content=message.content))
161163

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "cozeloop"
3-
version = "0.1.24"
3+
version = "0.1.25a1"
44
description = "coze loop sdk"
55
authors = ["JiangQi715 <jiangqi.rrt@bytedance.com>"]
66
license = "MIT"

0 commit comments

Comments
 (0)