microsoft-agents-a365-observability-extensions-langchain 0.2.1.dev9__py3-none-any.whl → 0.2.1.dev10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- microsoft_agents_a365/observability/extensions/langchain/tracer.py +61 -20
- microsoft_agents_a365/observability/extensions/langchain/tracer_instrumentor.py +1 -1
- microsoft_agents_a365/observability/extensions/langchain/utils.py +200 -28
- {microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev9.dist-info → microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev10.dist-info}/METADATA +1 -1
- {microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev9.dist-info → microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev10.dist-info}/RECORD +7 -7
- {microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev9.dist-info → microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev10.dist-info}/WHEEL +0 -0
- {microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev9.dist-info → microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev10.dist-info}/top_level.txt +0 -0
|
@@ -15,6 +15,10 @@ from uuid import UUID
|
|
|
15
15
|
|
|
16
16
|
from langchain_core.tracers import BaseTracer, LangChainTracer
|
|
17
17
|
from langchain_core.tracers.schemas import Run
|
|
18
|
+
from microsoft_agents_a365.observability.core.constants import (
|
|
19
|
+
GEN_AI_AGENT_NAME_KEY,
|
|
20
|
+
INVOKE_AGENT_OPERATION_NAME,
|
|
21
|
+
)
|
|
18
22
|
from microsoft_agents_a365.observability.core.inference_operation_type import InferenceOperationType
|
|
19
23
|
from microsoft_agents_a365.observability.core.utils import (
|
|
20
24
|
DictWithLock,
|
|
@@ -37,11 +41,14 @@ from microsoft_agents_a365.observability.extensions.langchain.utils import (
|
|
|
37
41
|
function_calls,
|
|
38
42
|
input_messages,
|
|
39
43
|
invocation_parameters,
|
|
44
|
+
invoke_agent_input_message,
|
|
45
|
+
invoke_agent_output_message,
|
|
40
46
|
llm_provider,
|
|
41
47
|
metadata,
|
|
42
48
|
model_name,
|
|
43
49
|
output_messages,
|
|
44
50
|
prompts,
|
|
51
|
+
set_execution_type,
|
|
45
52
|
token_counts,
|
|
46
53
|
tools,
|
|
47
54
|
)
|
|
@@ -111,8 +118,17 @@ class CustomLangChainTracer(BaseTracer):
|
|
|
111
118
|
# We can't use real time because the handler may be
|
|
112
119
|
# called in a background thread.
|
|
113
120
|
start_time_utc_nano = as_utc_nano(run.start_time)
|
|
121
|
+
|
|
122
|
+
# Determine span name based on run type
|
|
123
|
+
if run.run_type == "chain" and run.name == "LangGraph":
|
|
124
|
+
span_name = f"invoke_agent {run.name}"
|
|
125
|
+
elif run.run_type.lower() == "tool":
|
|
126
|
+
span_name = f"execute_tool {run.name}"
|
|
127
|
+
else:
|
|
128
|
+
span_name = run.name
|
|
129
|
+
|
|
114
130
|
span = self._tracer.start_span(
|
|
115
|
-
name=
|
|
131
|
+
name=span_name,
|
|
116
132
|
context=parent_context,
|
|
117
133
|
start_time=start_time_utc_nano,
|
|
118
134
|
)
|
|
@@ -197,27 +213,52 @@ def _update_span(span: Span, run: Run) -> None:
|
|
|
197
213
|
else:
|
|
198
214
|
span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, run.error))
|
|
199
215
|
|
|
216
|
+
span.set_attributes(dict(get_attributes_from_context()))
|
|
217
|
+
|
|
200
218
|
if run.run_type == "llm" and run.outputs.get("llm_output").get("id").startswith("chat"):
|
|
201
219
|
span.update_name(f"{InferenceOperationType.CHAT.value.lower()} {span.name}")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
span
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
+
is_invoke_agent = span.name.startswith(INVOKE_AGENT_OPERATION_NAME)
|
|
221
|
+
|
|
222
|
+
# If this is an invoke_agent span, update span name with agent name
|
|
223
|
+
if is_invoke_agent:
|
|
224
|
+
agent_name = None
|
|
225
|
+
if hasattr(span, "_attributes") and span._attributes:
|
|
226
|
+
agent_name = span._attributes.get(GEN_AI_AGENT_NAME_KEY)
|
|
227
|
+
if agent_name:
|
|
228
|
+
span.update_name(f"{INVOKE_AGENT_OPERATION_NAME} {agent_name}")
|
|
229
|
+
|
|
230
|
+
# For invoke_agent spans, add input/output messages
|
|
231
|
+
if is_invoke_agent:
|
|
232
|
+
span.set_attributes(
|
|
233
|
+
dict(
|
|
234
|
+
flatten(
|
|
235
|
+
chain(
|
|
236
|
+
set_execution_type(),
|
|
237
|
+
add_operation_type(run),
|
|
238
|
+
invoke_agent_input_message(run.inputs),
|
|
239
|
+
invoke_agent_output_message(run.outputs),
|
|
240
|
+
metadata(run),
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
span.set_attributes(
|
|
247
|
+
dict(
|
|
248
|
+
flatten(
|
|
249
|
+
chain(
|
|
250
|
+
add_operation_type(run),
|
|
251
|
+
prompts(run.inputs),
|
|
252
|
+
input_messages(run.inputs),
|
|
253
|
+
output_messages(run.outputs),
|
|
254
|
+
invocation_parameters(run),
|
|
255
|
+
llm_provider(run.extra),
|
|
256
|
+
model_name(run.outputs, run.extra),
|
|
257
|
+
token_counts(run.outputs),
|
|
258
|
+
function_calls(run.outputs),
|
|
259
|
+
tools(run),
|
|
260
|
+
metadata(run),
|
|
261
|
+
)
|
|
220
262
|
)
|
|
221
263
|
)
|
|
222
264
|
)
|
|
223
|
-
)
|
|
@@ -21,7 +21,7 @@ from wrapt import wrap_function_wrapper
|
|
|
21
21
|
|
|
22
22
|
from microsoft_agents_a365.observability.extensions.langchain.tracer import CustomLangChainTracer
|
|
23
23
|
|
|
24
|
-
_INSTRUMENTS: str = "langchain_core >=
|
|
24
|
+
_INSTRUMENTS: str = "langchain_core >= 1.2.0"
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class CustomLangChainInstrumentor(BaseInstrumentor):
|
|
@@ -9,6 +9,8 @@ from typing import Any
|
|
|
9
9
|
from langchain_core.messages import BaseMessage
|
|
10
10
|
from langchain_core.tracers.schemas import Run
|
|
11
11
|
from microsoft_agents_a365.observability.core.constants import (
|
|
12
|
+
EXECUTE_TOOL_OPERATION_NAME,
|
|
13
|
+
GEN_AI_EXECUTION_TYPE_KEY,
|
|
12
14
|
GEN_AI_INPUT_MESSAGES_KEY,
|
|
13
15
|
GEN_AI_OPERATION_NAME_KEY,
|
|
14
16
|
GEN_AI_OUTPUT_MESSAGES_KEY,
|
|
@@ -25,8 +27,10 @@ from microsoft_agents_a365.observability.core.constants import (
|
|
|
25
27
|
GEN_AI_TOOL_TYPE_KEY,
|
|
26
28
|
GEN_AI_USAGE_INPUT_TOKENS_KEY,
|
|
27
29
|
GEN_AI_USAGE_OUTPUT_TOKENS_KEY,
|
|
30
|
+
INVOKE_AGENT_OPERATION_NAME,
|
|
28
31
|
SESSION_ID_KEY,
|
|
29
32
|
)
|
|
33
|
+
from microsoft_agents_a365.observability.core.execution_type import ExecutionType
|
|
30
34
|
from microsoft_agents_a365.observability.core.inference_operation_type import InferenceOperationType
|
|
31
35
|
from microsoft_agents_a365.observability.core.utils import (
|
|
32
36
|
get_first_value,
|
|
@@ -199,8 +203,8 @@ def _parse_message_data(message_data: Mapping[str, Any] | None) -> Iterator[tupl
|
|
|
199
203
|
@stop_on_exception
|
|
200
204
|
def input_messages(
|
|
201
205
|
inputs: Mapping[str, Any] | None,
|
|
202
|
-
) -> Iterator[tuple[str,
|
|
203
|
-
"""Yields chat messages
|
|
206
|
+
) -> Iterator[tuple[str, str]]:
|
|
207
|
+
"""Yields chat messages as a JSON array of content strings."""
|
|
204
208
|
if not inputs:
|
|
205
209
|
return
|
|
206
210
|
assert hasattr(inputs, "get"), f"expected Mapping, found {type(inputs)}"
|
|
@@ -213,27 +217,29 @@ def input_messages(
|
|
|
213
217
|
# This will only get the first set of messages.
|
|
214
218
|
if not (first_messages := next(iter(multiple_messages), None)):
|
|
215
219
|
return
|
|
216
|
-
|
|
220
|
+
contents: list[str] = []
|
|
217
221
|
if isinstance(first_messages, list):
|
|
218
222
|
for message_data in first_messages:
|
|
219
223
|
if isinstance(message_data, BaseMessage):
|
|
220
|
-
|
|
224
|
+
if hasattr(message_data, "content") and message_data.content:
|
|
225
|
+
contents.append(str(message_data.content))
|
|
221
226
|
elif hasattr(message_data, "get"):
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
227
|
+
if content := message_data.get("content"):
|
|
228
|
+
contents.append(str(content))
|
|
229
|
+
elif kwargs := message_data.get("kwargs"):
|
|
230
|
+
if hasattr(kwargs, "get") and (content := kwargs.get("content")):
|
|
231
|
+
contents.append(str(content))
|
|
225
232
|
elif isinstance(first_messages, BaseMessage):
|
|
226
|
-
|
|
233
|
+
if hasattr(first_messages, "content") and first_messages.content:
|
|
234
|
+
contents.append(str(first_messages.content))
|
|
227
235
|
elif hasattr(first_messages, "get"):
|
|
228
|
-
|
|
236
|
+
if content := first_messages.get("content"):
|
|
237
|
+
contents.append(str(content))
|
|
229
238
|
elif isinstance(first_messages, Sequence) and len(first_messages) == 2:
|
|
230
|
-
# See e.g. https://github.com/langchain-ai/langchain/blob/18cf457eec106d99e0098b42712299f5d0daa798/libs/core/langchain_core/messages/utils.py#L317 # noqa: E501
|
|
231
239
|
role, content = first_messages
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if parsed_messages:
|
|
236
|
-
yield GEN_AI_INPUT_MESSAGES_KEY, parsed_messages
|
|
240
|
+
contents.append(str(content))
|
|
241
|
+
if contents:
|
|
242
|
+
yield GEN_AI_INPUT_MESSAGES_KEY, safe_json_dumps(contents)
|
|
237
243
|
|
|
238
244
|
|
|
239
245
|
@stop_on_exception
|
|
@@ -255,8 +261,8 @@ def metadata(run: Run) -> Iterator[tuple[str, str]]:
|
|
|
255
261
|
@stop_on_exception
|
|
256
262
|
def output_messages(
|
|
257
263
|
outputs: Mapping[str, Any] | None,
|
|
258
|
-
) -> Iterator[tuple[str,
|
|
259
|
-
"""Yields chat messages
|
|
264
|
+
) -> Iterator[tuple[str, str]]:
|
|
265
|
+
"""Yields chat messages as a JSON array of content strings."""
|
|
260
266
|
if not outputs:
|
|
261
267
|
return
|
|
262
268
|
assert hasattr(outputs, "get"), f"expected Mapping, found {type(outputs)}"
|
|
@@ -279,18 +285,21 @@ def output_messages(
|
|
|
279
285
|
assert isinstance(first_generations, Iterable), (
|
|
280
286
|
f"expected Iterable, found {type(first_generations)}"
|
|
281
287
|
)
|
|
282
|
-
|
|
288
|
+
contents: list[str] = []
|
|
283
289
|
for generation in first_generations:
|
|
284
290
|
assert hasattr(generation, "get"), f"expected Mapping, found {type(generation)}"
|
|
285
291
|
if message_data := generation.get("message"):
|
|
286
292
|
if isinstance(message_data, BaseMessage):
|
|
287
|
-
|
|
293
|
+
if hasattr(message_data, "content") and message_data.content:
|
|
294
|
+
contents.append(str(message_data.content))
|
|
288
295
|
elif hasattr(message_data, "get"):
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
296
|
+
if content := message_data.get("content"):
|
|
297
|
+
contents.append(str(content))
|
|
298
|
+
elif kwargs := message_data.get("kwargs"):
|
|
299
|
+
if hasattr(kwargs, "get") and (content := kwargs.get("content")):
|
|
300
|
+
contents.append(str(content))
|
|
301
|
+
if contents:
|
|
302
|
+
yield GEN_AI_OUTPUT_MESSAGES_KEY, safe_json_dumps(contents)
|
|
294
303
|
|
|
295
304
|
|
|
296
305
|
@stop_on_exception
|
|
@@ -305,7 +314,6 @@ def invocation_parameters(run: Run) -> Iterator[tuple[str, str]]:
|
|
|
305
314
|
assert isinstance(invocation_parameters, Mapping), (
|
|
306
315
|
f"expected Mapping, found {type(invocation_parameters)}"
|
|
307
316
|
)
|
|
308
|
-
yield GEN_AI_INPUT_MESSAGES_KEY, safe_json_dumps(invocation_parameters)
|
|
309
317
|
tools = invocation_parameters.get("tools", [])
|
|
310
318
|
for idx, tool in enumerate(tools):
|
|
311
319
|
yield f"{GEN_AI_TOOL_ARGS_KEY}.{idx}", safe_json_dumps(tool)
|
|
@@ -458,7 +466,7 @@ def function_calls(outputs: Mapping[str, Any] | None) -> Iterator[tuple[str, str
|
|
|
458
466
|
return
|
|
459
467
|
|
|
460
468
|
# Tool type (explicit)
|
|
461
|
-
yield GEN_AI_OPERATION_NAME_KEY,
|
|
469
|
+
yield GEN_AI_OPERATION_NAME_KEY, EXECUTE_TOOL_OPERATION_NAME
|
|
462
470
|
yield GEN_AI_TOOL_TYPE_KEY, "function"
|
|
463
471
|
|
|
464
472
|
name = fc.get("name")
|
|
@@ -505,6 +513,34 @@ def tools(run: Run) -> Iterator[tuple[str, str]]:
|
|
|
505
513
|
if description := serialized.get("description"):
|
|
506
514
|
yield GEN_AI_TOOL_DESCRIPTION_KEY, description
|
|
507
515
|
|
|
516
|
+
# Extract tool call ID from run.extra (LangGraph stores it there)
|
|
517
|
+
if run.extra and hasattr(run.extra, "get"):
|
|
518
|
+
if tool_call_id := run.extra.get("tool_call_id"):
|
|
519
|
+
yield GEN_AI_TOOL_CALL_ID_KEY, tool_call_id
|
|
520
|
+
|
|
521
|
+
# Extract tool arguments from inputs
|
|
522
|
+
if run.inputs and hasattr(run.inputs, "get"):
|
|
523
|
+
# LangGraph wraps args in 'input' key as a string
|
|
524
|
+
if input_val := run.inputs.get("input"):
|
|
525
|
+
if isinstance(input_val, str):
|
|
526
|
+
yield GEN_AI_TOOL_ARGS_KEY, input_val
|
|
527
|
+
else:
|
|
528
|
+
yield GEN_AI_TOOL_ARGS_KEY, safe_json_dumps(input_val)
|
|
529
|
+
|
|
530
|
+
# Extract tool result from outputs
|
|
531
|
+
if run.outputs and hasattr(run.outputs, "get"):
|
|
532
|
+
if result := run.outputs.get("output"):
|
|
533
|
+
# Handle ToolMessage or BaseMessage objects
|
|
534
|
+
if isinstance(result, BaseMessage):
|
|
535
|
+
result_content = result.content if hasattr(result, "content") else str(result)
|
|
536
|
+
elif hasattr(result, "content"):
|
|
537
|
+
result_content = result.content
|
|
538
|
+
elif isinstance(result, str):
|
|
539
|
+
result_content = result
|
|
540
|
+
else:
|
|
541
|
+
result_content = safe_json_dumps(result)
|
|
542
|
+
yield GEN_AI_TOOL_CALL_RESULT_KEY, result_content
|
|
543
|
+
|
|
508
544
|
|
|
509
545
|
def add_operation_type(run: Run) -> Iterator[tuple[str, str]]:
|
|
510
546
|
"""Yields operation type based on run type."""
|
|
@@ -512,6 +548,142 @@ def add_operation_type(run: Run) -> Iterator[tuple[str, str]]:
|
|
|
512
548
|
if run_type == "llm":
|
|
513
549
|
yield GEN_AI_OPERATION_NAME_KEY, InferenceOperationType.CHAT.value.lower()
|
|
514
550
|
elif run_type == "chat_model":
|
|
515
|
-
yield GEN_AI_OPERATION_NAME_KEY,
|
|
551
|
+
yield GEN_AI_OPERATION_NAME_KEY, InferenceOperationType.CHAT.value.lower()
|
|
516
552
|
elif run_type == "tool":
|
|
517
|
-
yield GEN_AI_OPERATION_NAME_KEY,
|
|
553
|
+
yield GEN_AI_OPERATION_NAME_KEY, EXECUTE_TOOL_OPERATION_NAME
|
|
554
|
+
elif run_type == "chain" and run.name.startswith(INVOKE_AGENT_OPERATION_NAME):
|
|
555
|
+
yield GEN_AI_OPERATION_NAME_KEY, INVOKE_AGENT_OPERATION_NAME
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _extract_content_from_message(message: Any) -> str | None:
|
|
559
|
+
"""Extract content from a LangChain message object or dict."""
|
|
560
|
+
if message is None:
|
|
561
|
+
return None
|
|
562
|
+
|
|
563
|
+
# Handle BaseMessage objects
|
|
564
|
+
if isinstance(message, BaseMessage):
|
|
565
|
+
return message.content if hasattr(message, "content") else None
|
|
566
|
+
|
|
567
|
+
# Handle dict-like messages
|
|
568
|
+
if hasattr(message, "get"):
|
|
569
|
+
# Direct content field
|
|
570
|
+
if content := message.get("content"):
|
|
571
|
+
return content
|
|
572
|
+
# Nested in kwargs
|
|
573
|
+
if kwargs := message.get("kwargs"):
|
|
574
|
+
if hasattr(kwargs, "get") and (content := kwargs.get("content")):
|
|
575
|
+
return content
|
|
576
|
+
|
|
577
|
+
return None
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _get_message_role(message: Any) -> str | None:
|
|
581
|
+
"""Extract role from a LangChain message object or dict."""
|
|
582
|
+
if message is None:
|
|
583
|
+
return None
|
|
584
|
+
|
|
585
|
+
# Handle BaseMessage objects
|
|
586
|
+
if isinstance(message, BaseMessage):
|
|
587
|
+
return message.type if hasattr(message, "type") else None
|
|
588
|
+
|
|
589
|
+
# Handle dict-like messages
|
|
590
|
+
if hasattr(message, "get"):
|
|
591
|
+
# Check various role indicators
|
|
592
|
+
if role := message.get("role"):
|
|
593
|
+
return role
|
|
594
|
+
if msg_type := message.get("type"):
|
|
595
|
+
return msg_type
|
|
596
|
+
# Check id field for type info (e.g., "HumanMessage", "AIMessage")
|
|
597
|
+
if id_field := message.get("id"):
|
|
598
|
+
if isinstance(id_field, list) and len(id_field) > 0:
|
|
599
|
+
type_name = id_field[-1]
|
|
600
|
+
if "Human" in type_name:
|
|
601
|
+
return "human"
|
|
602
|
+
elif "AI" in type_name or "Assistant" in type_name:
|
|
603
|
+
return "ai"
|
|
604
|
+
elif "System" in type_name:
|
|
605
|
+
return "system"
|
|
606
|
+
|
|
607
|
+
return None
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
@stop_on_exception
|
|
611
|
+
def invoke_agent_input_message(
|
|
612
|
+
inputs: Mapping[str, Any] | None,
|
|
613
|
+
) -> Iterator[tuple[str, str]]:
|
|
614
|
+
"""
|
|
615
|
+
Extract the user input message for invoke_agent spans (LangGraph root).
|
|
616
|
+
We want to find the first user/human message content.
|
|
617
|
+
"""
|
|
618
|
+
if not inputs:
|
|
619
|
+
return
|
|
620
|
+
|
|
621
|
+
assert hasattr(inputs, "get"), f"expected Mapping, found {type(inputs)}"
|
|
622
|
+
|
|
623
|
+
messages = inputs.get("messages")
|
|
624
|
+
if not messages:
|
|
625
|
+
return
|
|
626
|
+
|
|
627
|
+
# Handle nested list structure: [[msg1, msg2, ...]]
|
|
628
|
+
if isinstance(messages, list) and len(messages) > 0:
|
|
629
|
+
first_item = messages[0]
|
|
630
|
+
# If first item is also a list, unwrap it
|
|
631
|
+
if isinstance(first_item, list):
|
|
632
|
+
messages = first_item
|
|
633
|
+
|
|
634
|
+
# Find the first user/human message
|
|
635
|
+
if isinstance(messages, list):
|
|
636
|
+
for message in messages:
|
|
637
|
+
role = _get_message_role(message)
|
|
638
|
+
if role and role.lower() in ("human", "user"):
|
|
639
|
+
content = _extract_content_from_message(message)
|
|
640
|
+
if content:
|
|
641
|
+
yield GEN_AI_INPUT_MESSAGES_KEY, content
|
|
642
|
+
return
|
|
643
|
+
|
|
644
|
+
# If no human message found, just get first message content
|
|
645
|
+
if len(messages) > 0:
|
|
646
|
+
content = _extract_content_from_message(messages[0])
|
|
647
|
+
if content:
|
|
648
|
+
yield GEN_AI_INPUT_MESSAGES_KEY, content
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
@stop_on_exception
|
|
652
|
+
def invoke_agent_output_message(
|
|
653
|
+
outputs: Mapping[str, Any] | None,
|
|
654
|
+
) -> Iterator[tuple[str, str]]:
|
|
655
|
+
"""
|
|
656
|
+
Extract the final output message for invoke_agent spans (LangGraph root).
|
|
657
|
+
We want the last AI/assistant message content.
|
|
658
|
+
"""
|
|
659
|
+
if not outputs:
|
|
660
|
+
return
|
|
661
|
+
|
|
662
|
+
assert hasattr(outputs, "get"), f"expected Mapping, found {type(outputs)}"
|
|
663
|
+
|
|
664
|
+
messages = outputs.get("messages")
|
|
665
|
+
if not messages:
|
|
666
|
+
return
|
|
667
|
+
|
|
668
|
+
# Handle nested list structure if present
|
|
669
|
+
if isinstance(messages, list) and len(messages) > 0:
|
|
670
|
+
first_item = messages[0]
|
|
671
|
+
if isinstance(first_item, list):
|
|
672
|
+
messages = first_item
|
|
673
|
+
|
|
674
|
+
# Find the last AI/assistant message with content (not tool calls)
|
|
675
|
+
if isinstance(messages, list):
|
|
676
|
+
# Iterate in reverse to find the last AI message
|
|
677
|
+
for message in reversed(messages):
|
|
678
|
+
role = _get_message_role(message)
|
|
679
|
+
if role and role.lower() in ("ai", "assistant"):
|
|
680
|
+
content = _extract_content_from_message(message)
|
|
681
|
+
# Make sure it has actual content, not just tool calls
|
|
682
|
+
if content and isinstance(content, str) and content.strip():
|
|
683
|
+
yield GEN_AI_OUTPUT_MESSAGES_KEY, content
|
|
684
|
+
return
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def set_execution_type() -> Iterator[tuple[str, str]]:
|
|
688
|
+
"""Yields the execution type as human_to_agent."""
|
|
689
|
+
yield GEN_AI_EXECUTION_TYPE_KEY, ExecutionType.HUMAN_TO_AGENT.value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microsoft-agents-a365-observability-extensions-langchain
|
|
3
|
-
Version: 0.2.1.
|
|
3
|
+
Version: 0.2.1.dev10
|
|
4
4
|
Summary: LangChain observability and tracing extensions for Microsoft Agent 365
|
|
5
5
|
Author-email: Microsoft <support@microsoft.com>
|
|
6
6
|
License: MIT
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
microsoft_agents_a365/observability/extensions/langchain/__init__.py,sha256=Oj1cfhahvaSq7N5r9mwIXdS4unhYgVweAoHaAxw-tyw,363
|
|
2
|
-
microsoft_agents_a365/observability/extensions/langchain/tracer.py,sha256=
|
|
3
|
-
microsoft_agents_a365/observability/extensions/langchain/tracer_instrumentor.py,sha256=
|
|
4
|
-
microsoft_agents_a365/observability/extensions/langchain/utils.py,sha256=
|
|
5
|
-
microsoft_agents_a365_observability_extensions_langchain-0.2.1.
|
|
6
|
-
microsoft_agents_a365_observability_extensions_langchain-0.2.1.
|
|
7
|
-
microsoft_agents_a365_observability_extensions_langchain-0.2.1.
|
|
8
|
-
microsoft_agents_a365_observability_extensions_langchain-0.2.1.
|
|
2
|
+
microsoft_agents_a365/observability/extensions/langchain/tracer.py,sha256=jqnDUTv5OChS8-22Ajq5YWSsO9R871AOyahEF3c4Jng,10130
|
|
3
|
+
microsoft_agents_a365/observability/extensions/langchain/tracer_instrumentor.py,sha256=Mr6qm2lOgvpCjgU1n3fSybPdKH1KxRr07ZbkN4NR_ws,6280
|
|
4
|
+
microsoft_agents_a365/observability/extensions/langchain/utils.py,sha256=dMqXLBuulM4W5jxhCe776xlMsTCs88pR_EcyBF6nS5k,25366
|
|
5
|
+
microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev10.dist-info/METADATA,sha256=P4O45EoaWzIixu1sbSbixgQkfpggb-dPdatJ6187AhA,3669
|
|
6
|
+
microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
7
|
+
microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev10.dist-info/top_level.txt,sha256=G3c2_4sy5_EM_BWO67SbK2tKj4G8XFn-QXRbh8g9Lgk,22
|
|
8
|
+
microsoft_agents_a365_observability_extensions_langchain-0.2.1.dev10.dist-info/RECORD,,
|
|
File without changes
|