Skip to content

Commit 00e7528

Browse files
committed
Fix langchain runtime, change examples.
Change-Id: Ie30079d23e168c3b79faf8fae5beaec8d1128ad1
1 parent 5637151 commit 00e7528

16 files changed

Lines changed: 72 additions & 129 deletions

File tree

cozeloop/integration/langchain/trace_callback.py

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
from __future__ import annotations
55
import json
6-
import threading
76
import time
87
import traceback
98
from typing import List, Dict, Union, Any, Optional
@@ -23,7 +22,8 @@
2322
from cozeloop.integration.langchain.trace_model.runtime import RuntimeInfo
2423
from cozeloop.integration.langchain.util import calc_token_usage, get_prompt_tag
2524

26-
_trace_callback_client = None
25+
_trace_callback_client: Optional[Client] = None
26+
2727

2828
class LoopTracer:
2929
@classmethod
@@ -48,16 +48,15 @@ def __init__(self):
4848

4949
def on_llm_start(self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any) -> Any:
5050
span_tags = {}
51+
span_name = serialized.get('name', 'unknown')
52+
53+
flow_span = self._new_flow_span(span_name, 'model', **kwargs)
5154
try:
5255
span_tags['input'] = ModelTraceInput([BaseMessage(type='', content=prompt) for prompt in prompts],
5356
kwargs.get('invocation_params', {})).to_json()
54-
span_name = serialized['name']
5557
except Exception as e:
56-
span_name = 'unknown'
57-
span_tags['internal_error'] = repr(e)
58-
span_tags['internal_error_trace'] = traceback.format_exc()
58+
flow_span.set_error(e)
5959
finally:
60-
flow_span = self._new_flow_span(span_name, 'model', **kwargs)
6160
span_tags.update(_get_model_span_tags(**kwargs))
6261
self._set_span_tags(flow_span, span_tags)
6362
# Store some pre-aspect information.
@@ -67,15 +66,14 @@ def on_llm_start(self, serialized: Dict[str, Any], prompts: List[str], **kwargs:
6766

6867
def on_chat_model_start(self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], **kwargs: Any) -> Any:
6968
span_tags = {}
69+
span_name = serialized.get('name', 'unknown')
70+
71+
flow_span = self._new_flow_span(span_name, 'model', **kwargs)
7072
try:
7173
span_tags['input'] = ModelTraceInput(messages, kwargs.get('invocation_params', {})).to_json()
72-
span_name = serialized['name']
7374
except Exception as e:
74-
span_name = 'unknown'
75-
span_tags['internal_error'] = repr(e)
76-
span_tags['internal_error_trace'] = traceback.format_exc()
75+
flow_span.set_error(e)
7776
finally:
78-
flow_span = self._new_flow_span(span_name, 'model', **kwargs)
7977
span_tags.update(_get_model_span_tags(**kwargs))
8078
self._set_span_tags(flow_span, span_tags)
8179
# Store some pre-aspect information.
@@ -105,13 +103,8 @@ def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:
105103
if run_info is not None and run_info.model_meta is not None:
106104
model_name = run_info.model_meta.model_name
107105
input_messages = run_info.model_meta.message
108-
token_usage = {
109-
'input_tokens': calc_token_usage(input_messages, model_name),
110-
'output_tokens': calc_token_usage(response, model_name),
111-
'tokens': 0
112-
}
113-
token_usage['tokens'] = token_usage['input_tokens'] + token_usage['output_tokens']
114-
self._set_span_tags(flow_span, token_usage, need_convert_tag_value=False)
106+
flow_span.set_input_tokens(calc_token_usage(input_messages, model_name))
107+
flow_span.set_output_tokens(calc_token_usage(response, model_name))
115108
# finish flow_span
116109
flow_span.finish()
117110

@@ -139,24 +132,17 @@ def on_chain_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: A
139132
if flow_span is None:
140133
span_name = '_Exception' if isinstance(error, Exception) else '_KeyboardInterrupt'
141134
flow_span = self._new_flow_span(span_name, 'chain_error', **kwargs)
142-
flow_span.set_tags({'error': repr(error)})
135+
flow_span.set_error(error)
143136
flow_span.set_tags({'error_trace': traceback.format_exc()})
144-
flow_span.set_tags({'_status_code': -1})
145137
flow_span.finish()
146138

147139
def on_tool_start(
148140
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
149141
) -> Any:
150142
span_tags = {'input': input_str, **serialized}
151-
try:
152-
span_name = serialized['name']
153-
except Exception as e:
154-
span_name = 'unknown'
155-
span_tags['internal_error'] = repr(e)
156-
span_tags['internal_error_trace'] = traceback.format_exc()
157-
finally:
158-
flow_span = self._new_flow_span(span_name, 'tool', **kwargs)
159-
self._set_span_tags(flow_span, span_tags)
143+
span_name = serialized.get('name', 'unknown')
144+
flow_span = self._new_flow_span(span_name, 'tool', **kwargs)
145+
self._set_span_tags(flow_span, span_tags)
160146

161147
def on_tool_end(self, output: str, **kwargs: Any) -> Any:
162148
flow_span = self._get_flow_span(**kwargs)
@@ -170,9 +156,8 @@ def on_tool_error(
170156
if flow_span is None:
171157
span_name = '_Exception' if isinstance(error, Exception) else '_KeyboardInterrupt'
172158
flow_span = self._new_flow_span(span_name, 'tool_error', **kwargs)
173-
flow_span.set_tags({'error': repr(error)})
159+
flow_span.set_error(error)
174160
flow_span.set_tags({'error_trace': traceback.format_exc()})
175-
flow_span.set_tags({'_status_code': -1})
176161
flow_span.finish()
177162

178163
def on_text(self, text: str, **kwargs: Any) -> Any:
@@ -241,9 +226,7 @@ def _new_flow_span(self, span_name: str, span_type: str, **kwargs: Any) -> Span:
241226
run_id = str(kwargs['run_id'])
242227
self.run_map[run_id] = Run(run_id, flow_span, span_type)
243228
# set default tags
244-
# flow_span.set_tags({'space_id': self._space_id})
245-
flow_span.set_tags({'span_type': span_type})
246-
flow_span.set_tags({'runtime': RuntimeInfo().to_json()})
229+
flow_span.set_runtime(RuntimeInfo())
247230
return flow_span
248231

249232
def _get_flow_span(self, **kwargs: Any) -> Span:
@@ -252,13 +235,6 @@ def _get_flow_span(self, **kwargs: Any) -> Span:
252235
return self.run_map[run_id].span
253236
return None
254237

255-
def _set_internal_error_span(self, error: Exception, **kwargs: Any) -> None:
256-
flow_span = self._new_flow_span('internal_error', 'error', **kwargs)
257-
flow_span.set_tags({'internal_error': error})
258-
flow_span.set_tags({'internal_error_trace': traceback.format_exc()})
259-
flow_span.set_tags({'_status_code': -1})
260-
flow_span.finish()
261-
262238
def _set_span_tags(self, flow_span: Span, tags: Dict[str, Any], need_convert_tag_value=True) -> None:
263239
for key, value in tags.items():
264240
report_value = value

cozeloop/integration/langchain/trace_model/runtime.py

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,23 @@
22
# SPDX-License-Identifier: MIT
33

44
import json
5-
import platform as platform_pkg
65
import importlib.metadata as metadata
7-
from typing import Optional
8-
from pydantic.dataclasses import dataclass
6+
from typing import Optional, Any
97

10-
from cozeloop.internal.version import VERSION
8+
from cozeloop.spec import tracespce
119

12-
@dataclass
13-
class RuntimeInfo:
14-
language: Optional[str] = 'python'
15-
library: Optional[str] = 'langchain'
16-
runtime: Optional[str] = 'python'
17-
runtime_version: Optional[str] = platform_pkg.python_version()
18-
py_implementation: Optional[str] = platform_pkg.python_implementation()
19-
loop_sdk_version: Optional[str] = None
20-
langchain_version: Optional[str] = None
21-
langchain_core_version: Optional[str] = None
2210

23-
def __post_init__(self):
11+
class RuntimeInfo(tracespce.Runtime):
12+
language: Optional[str] = tracespce.V_LANG_PYTHON
13+
library: Optional[str] = tracespce.V_LIB_LANGCHAIN
14+
15+
def model_post_init(self, context: Any) -> None:
2416
try:
2517
langchain_version = metadata.version('langchain')
2618
except metadata.PackageNotFoundError:
2719
langchain_version = ''
28-
try:
29-
langchain_core_version = metadata.version('langchain-core')
30-
except metadata.PackageNotFoundError:
31-
langchain_core_version = ''
32-
self.loop_sdk_version = VERSION
33-
self.langchain_version = langchain_version
34-
self.langchain_core_version = langchain_core_version
20+
21+
self.library_version = langchain_version
3522

3623
def to_json(self):
3724
return json.dumps(

cozeloop/internal/consts/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@
6161
COMMA = ","
6262

6363
# On the basis of W3C, the "loop" prefix is added to avoid conflicts with other traces that use W3C.
64-
TRACE_CONTEXT_HEADER_PARENT = "X-Loop-Traceparent"
65-
TRACE_CONTEXT_HEADER_BAGGAGE = "X-Loop-Tracestate"
64+
TRACE_CONTEXT_HEADER_PARENT = "X-Cozeloop-Traceparent"
65+
TRACE_CONTEXT_HEADER_BAGGAGE = "X-Cozeloop-Tracestate"
6666

6767
TRACE_PROMPT_HUB_SPAN_TYPE = "prompt_hub"
6868
TRACE_PROMPT_TEMPLATE_SPAN_TYPE = "prompt"

cozeloop/internal/httpclient/user_agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
from cozeloop import internal
1010

1111
# User agent components
12-
USER_AGENT_SDK = "loop-python"
12+
USER_AGENT_SDK = "cozeloop-python"
1313
USER_AGENT_LANG = "python"
1414
USER_AGENT_LANG_VERSION = platform.python_version()
1515
USER_AGENT_OS_NAME = platform.system().lower()
1616
USER_AGENT_OS_VERSION = os.getenv("OSVERSION", "unknown")
17-
SCENE = "loop"
17+
SCENE = "cozeloop"
1818
SOURCE = "openapi"
1919

2020
USER_AGENT = f"{USER_AGENT_SDK}/{internal.__version__} {USER_AGENT_LANG}/{USER_AGENT_LANG_VERSION} {USER_AGENT_OS_NAME}/{USER_AGENT_OS_VERSION}"

cozeloop/internal/trace/noop_span.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from cozeloop.entities.prompt import Prompt
1010
from cozeloop.span import Span
11+
from cozeloop.spec.tracespce import Runtime
1112

1213

1314
class NoopSpan(Span, ABC):
@@ -106,6 +107,9 @@ def set_output_tokens(self, output_tokens: int) -> None:
106107
def set_start_time_first_resp(self, start_time_first_resp: int) -> None:
107108
pass
108109

110+
def set_runtime(self, runtime: Runtime) -> None:
111+
pass
112+
109113
def __enter__(self):
110114
return self
111115

cozeloop/internal/trace/span.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,13 @@ def set_output_tokens(self, output_tokens: int):
342342
def set_start_time_first_resp(self, start_time_first_resp: int):
343343
self.set_tags({START_TIME_FIRST_RESP: start_time_first_resp})
344344

345+
def set_runtime(self, runtime: Runtime) -> None:
346+
r = Runtime(scene=V_SCENE_CUSTOM, library=runtime.library, library_version=runtime.library_version)
347+
with self.lock:
348+
if self.system_tag_map is None:
349+
self.system_tag_map = {}
350+
self.system_tag_map[RUNTIME_] = r
351+
345352
def get_rectified_map(self, input_map: Dict[str, Any]) -> (Dict[str, Any], List[str], int):
346353
validate_map = {}
347354
cut_off_keys = []

cozeloop/span.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Dict, Any
77
from datetime import datetime
88
from cozeloop.entities.prompt import Prompt
9+
from cozeloop.spec.tracespce import Runtime
910

1011

1112
class SpanContext(ABC):
@@ -159,6 +160,12 @@ def set_start_time_first_resp(self, start_time_first_resp: int) -> None:
159160
Key: `start_time_first_resp`.
160161
"""
161162

163+
@abstractmethod
164+
def set_runtime(self, runtime: Runtime) -> None:
165+
"""
166+
Set the runtime of the span. Only used for integration.
167+
Key: `runtime`.
168+
"""
162169

163170
class Span(CommonSpanSetter, SpanContext):
164171
"""

cozeloop/spec/tracespce/span_value.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
V_SCENE_CUSTOM = "custom" # user custom, it has the same meaning as blank.
3434
V_SCENE_PROMPT_HUB = "prompt_hub" # get_prompt
3535
V_SCENE_PROMPT_TEMPLATE = "prompt_template" # prompt_template
36+
V_SCENE_INTEGRATION = "integration" # integration like langchain
3637

3738
# Tag values for prompt input.
3839
V_PROMPT_ARG_SOURCE_INPUT = "input"

examples/lcel/lcel.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
# SPDX-License-Identifier: MIT
33

44
import logging
5-
import os
6-
import time
75

8-
from langchain.callbacks.tracers import ConsoleCallbackHandler
96
from langchain_core.runnables import RunnableConfig
10-
from langchain_openai import AzureChatOpenAI
7+
from langchain_openai import ChatOpenAI
118
from langchain_core.output_parsers import StrOutputParser
129

1310
from cozeloop import set_log_level, new_client
@@ -18,30 +15,27 @@
1815
def do_lcel_demo():
1916
# Configure the parameters for the large model. The keys in os.environ are standard keys for Langchain and must be
2017
# followed. This is just a demo, and the connectivity of the large model needs to be ensured by the user.
21-
# os.environ['AZURE_OPENAI_API_KEY'] = 'xxx' # need set a llm api key
22-
# os.environ['OPENAI_API_VERSION'] = '2024-05-13' # llm version, see more: https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning
23-
# os.environ['AZURE_OPENAI_ENDPOINT'] = 'https://xxx' # llm endpoint
24-
# os.environ['AUZURE_DEPLOYMENT'] = 'gpt-4o-2024-05-13'
18+
# os.environ['OPENAI_API_KEY'] = 'xxx' # need set a openai key
2519

26-
# Configure the Loop environment variables. This is just a demo, and the keys in os.environ are not for reference.
20+
# Configure the CozeLoop environment variables. This is just a demo, and the keys in os.environ are not for reference.
2721
# The specific implementation method is determined by the business side.
2822
# Set the following environment variables first (Assuming you are using a PAT token.).
2923
# os.environ['COZELOOP_API_TOKEN'] = 'your token'
3024
# os.environ['COZELOOP_WORKSPACE_ID'] = 'your workspace id'
3125

32-
client = new_client(ultra_large_report=True)
26+
client = new_client()
3327
trace_callback_handler = LoopTracer.get_callback_handler(client)
3428
# init llm model
35-
llm_model = AzureChatOpenAI(azure_deployment=os.environ['AUZURE_DEPLOYMENT'])
29+
llm_model = ChatOpenAI(model="doubao-1-5-vision-pro-32k-250115", base_url="https://ark.cn-beijing.volces.com/api/v3")
3630

3731
# execute lcel, and print intermediate results.
3832
lcel_sequence = llm_model | StrOutputParser()
3933
output = lcel_sequence.invoke(
4034
input='用你所学的技巧,帮我生成几个有意思的问题',
41-
config=RunnableConfig(callbacks=[ConsoleCallbackHandler(), trace_callback_handler])
35+
config=RunnableConfig(callbacks=[trace_callback_handler])
4236
)
43-
time.sleep(5) # async report, so sleep wait for report finish
4437
print('\n====== model output start ======\n' + output + '\n====== model output finish ======\n')
38+
client.close()
4539

4640

4741
if __name__ == "__main__":

examples/lcel/lcel_stream.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,32 @@
55
import os
66
import time
77

8-
from langchain.callbacks.tracers import ConsoleCallbackHandler
98
from langchain_core.runnables import RunnableConfig
10-
from langchain_openai import AzureChatOpenAI
9+
from langchain_openai import ChatOpenAI
1110
from langchain_core.output_parsers import StrOutputParser
1211

13-
from cozeloop import set_log_level
12+
from cozeloop import set_log_level, new_client
1413
from cozeloop.integration.langchain.trace_callback import LoopTracer
1514

1615
logger = logging.getLogger(__name__)
1716

1817
def do_lcel_stream_demo():
1918
# Configure the parameters for the llm. The keys in os.environ are standard keys for Langchain and must be
2019
# followed. This is just a demo, and the connectivity of the llm needs to be ensured by the user.
21-
# os.environ['AZURE_OPENAI_API_KEY'] = 'xxx' # need set a llm api key
22-
# os.environ['OPENAI_API_VERSION'] = '2024-05-13' # llm version, see more: https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning
23-
# os.environ['AZURE_OPENAI_ENDPOINT'] = 'https://xxx' # llm endpoint
24-
# os.environ['AUZURE_DEPLOYMENT'] = 'gpt-4o-2024-05-13'
20+
# os.environ['OPENAI_API_KEY'] = 'xxx' # need set a openai key
2521

26-
# Configure the Loop environment variables. This is just a demo, and the keys in os.environ are not for reference.
22+
# Configure the CozeLoop environment variables. This is just a demo, and the keys in os.environ are not for reference.
2723
# The specific implementation method is determined by the business side.
28-
2924
# Set the following environment variables first (Assuming you are using a PAT token.).
3025
# COZELOOP_WORKSPACE_ID=your workspace id
3126
# COZELOOP_API_TOKEN=your token
3227
# os.environ['COZELOOP_API_TOKEN'] = 'your token'
3328
# os.environ['COZELOOP_WORKSPACE_ID'] = 'your workspace'
3429

35-
trace_callback_handler = LoopTracer.get_callback_handler()
30+
client = new_client()
31+
trace_callback_handler = LoopTracer.get_callback_handler(client)
3632
# init llm model
37-
llm_model = AzureChatOpenAI(azure_deployment=os.environ['AUZURE_DEPLOYMENT'])
33+
llm_model = ChatOpenAI(model="doubao-1-5-vision-pro-32k-250115", base_url="https://ark.cn-beijing.volces.com/api/v3")
3834

3935
# execute lcel, and print intermediate results.
4036
lcel_sequence = llm_model | StrOutputParser()
@@ -46,8 +42,8 @@ def do_lcel_stream_demo():
4642
chunks.append(chunk)
4743
print(chunk, end='', flush=True)
4844

49-
time.sleep(5) # async report, so sleep wait for report finish
5045
print('\n====== model output start ======\n' + ''.join(chunks) + '\n====== model output finish ======\n')
46+
client.close()
5147

5248

5349
if __name__ == "__main__":

0 commit comments

Comments
 (0)