Skip to content
Open
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
4 changes: 2 additions & 2 deletions docs/source/get-started/tutorials/create-a-new-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async def text_file_ingest_function(config: TextFileIngestFunctionConfig, builde
```


Examining the `webquery_tool` function (`examples/getting_started/simple_web_query/src/nat_simple_web_query/register.py`), you can observe that at the heart of the tool is the [`langchain_community.document_loaders.WebBaseLoader`](https://python.langchain.com/docs/integrations/document_loaders/web_base) class.
Examining the `webquery_tool` function (`examples/getting_started/simple_web_query/src/nat_simple_web_query/register.py`), you can observe that at the heart of the tool is the `langchain_community.document_loaders.WebBaseLoader` class.

```python
loader = WebBaseLoader(config.webpage_url)
Expand Down Expand Up @@ -174,7 +174,7 @@ async def text_file_ingest_function(config: TextFileIngestFunctionConfig, builde

## Creating the Workflow Configuration

Starting from the `custom_config.yml` file you created in the previous section, replace the two `webpage_query` tools with the new `text_file_ingest` tool. For the data source, you can use a collection of text files located in the `examples/documentation_guides/workflows/text_file_ingest/data` directory that describes [DOCA GPUNetIO](https://docs.nvidia.com/doca/sdk/DOCA-GPUNetIO/index.html).
Starting from the `custom_config.yml` file you created in the previous section, replace the two `webpage_query` tools with the new `text_file_ingest` tool. For the data source, you can use a collection of text files located in the `examples/documentation_guides/workflows/text_file_ingest/data` directory that describes `DOCA GPUNetIO`.

:::{note}
<!-- path-check-skip-next-line -->
Expand Down
2 changes: 2 additions & 0 deletions packages/nvidia_nat_core/src/nat/data_models/span.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class SpanAttributes(Enum):
NAT_SPAN_KIND = f"{_SPAN_PREFIX}.span.kind"
INPUT_VALUE = "input.value"
INPUT_MIME_TYPE = "input.mime_type"
LLM_MODEL_NAME = "llm.model_name"
LLM_PROVIDER = "llm.provider"
LLM_TOKEN_COUNT_PROMPT = "llm.token_count.prompt"
LLM_TOKEN_COUNT_COMPLETION = "llm.token_count.completion"
LLM_TOKEN_COUNT_TOTAL = "llm.token_count.total"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from nat.data_models.span import Span
from nat.data_models.span import SpanAttributes
from nat.data_models.span import SpanContext
from nat.data_models.span import SpanKind
from nat.data_models.span import event_type_to_span_kind
from nat.observability.exporter.base_exporter import IsolatedAttribute
from nat.observability.exporter.processing_exporter import ProcessingExporter
Expand Down Expand Up @@ -210,6 +211,11 @@ def _process_start_event(self, event: IntermediateStep):

span_kind = event_type_to_span_kind(event.event_type)
sub_span.set_attribute(f"{self._span_prefix}.span.kind", span_kind.value)
if span_kind == SpanKind.LLM:
if event.payload.name:
sub_span.set_attribute(SpanAttributes.LLM_MODEL_NAME.value, event.payload.name)
if event.payload.framework:
sub_span.set_attribute(SpanAttributes.LLM_PROVIDER.value, event.payload.framework.value)

# Enable session grouping by setting session.id from conversation_id
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,42 @@ async def test_process_end_event(self, span_exporter, sample_start_event, sample
assert exported_span.attributes[SpanAttributes.OUTPUT_VALUE.value] == "Test output"
assert "nat.metadata" in exported_span.attributes

async def test_llm_span_exports_cost_lookup_attributes(self, span_exporter):
"""Minimal reproducer for Phoenix cost lookup attributes on LLM spans."""
event_id = str(uuid.uuid4())

start_event = create_intermediate_step(UUID=event_id,
event_type=IntermediateStepType.LLM_START,
framework=LLMFrameworkEnum.LANGCHAIN,
name="gemini-2.5-flash",
event_timestamp=datetime.now().timestamp(),
data=StreamEventData(input="What is the capital of France?"),
metadata={})

end_event = create_intermediate_step(UUID=event_id,
event_type=IntermediateStepType.LLM_END,
framework=LLMFrameworkEnum.LANGCHAIN,
name="gemini-2.5-flash",
event_timestamp=datetime.now().timestamp(),
span_event_timestamp=datetime.now().timestamp(),
data=StreamEventData(output="Paris"),
metadata={},
usage_info=UsageInfo(num_llm_calls=1,
seconds_between_calls=0,
token_usage=TokenUsageBaseModel(prompt_tokens=7,
completion_tokens=1,
total_tokens=8)))

async with span_exporter.start():
span_exporter.export(start_event)
span_exporter.export(end_event)
await span_exporter.wait_for_tasks()

exported_span = span_exporter.exported_spans[0]
assert exported_span.attributes[SpanAttributes.LLM_MODEL_NAME.value] == "gemini-2.5-flash"
assert exported_span.attributes[SpanAttributes.LLM_PROVIDER.value] == LLMFrameworkEnum.LANGCHAIN.value
assert exported_span.attributes[SpanAttributes.LLM_TOKEN_COUNT_TOTAL.value] == 8

def test_process_end_event_missing_span(self, span_exporter, sample_end_event):
"""Test processing END event with missing span."""
with patch('nat.observability.exporter.span_exporter.logger') as mock_logger:
Expand Down
Loading