Repository: mlflow/mlflow
Pull request #15574: create mlflow tracing for smolagents
Author: login: y-okt
Labels: name: rn/feature
description: Mention under Features in Changelogs.
name: area/tracking
description: Tracking service, tracking client APIs, autologging
Created: 2025-05-01T02:32:24Z | Merged: 2025-05-02T13:59:26Z

<details><summary>&#x1F6E0 DevTools &#x1F6E0</summary>
<p>

[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/y-okt/mlflow/pull/15574?quickstart=1)

#### Install mlflow from this PR

```
# mlflow
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/15574/merge
# mlflow-skinny
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/15574/merge#subdirectory=skinny
```

For Databricks, use the following command:

```
%sh curl -LsSf https://raw.githubusercontent.com/mlflow/mlflow/HEAD/dev/install-skinny.sh | sh -s 15574
```

</p>
</details>

### Related Issues/PRs

<!-- Uncomment 'Resolve' if this PR can close the linked items. -->
<!-- Resolve --> #15401 

### What changes are proposed in this pull request?

<!-- Please fill in changes proposed in this PR. -->
[Automatic logging with MLflow Tracking](https://mlflow.org/docs/latest/tracking/autolog) for [Smolagents](https://huggingface.co/docs/smolagents/en/index).

There are two options: one is to use OpenTelemetry's integration, and the other is to patch each method manually.
In this implementation, I used a manual patch solution. The reason is that in the first option, `mlflow.get_current_active_span` gets the Otel span which lacks `request_id`. However, we need `request_id`, which means that from MLflow side, we can't fetch the active span. Moreover, the [OpenTelemetry's integration](https://github.com/Arize-ai/openinference/blob/50eaf3c943d818f12fdc8e37b7c305c763c82050/python/instrumentation/openinference-instrumentation-smolagents/src/openinference/instrumentation/smolagents/__init__.py#L49-L90) that OpenInference provides also relies on manual patching. This means that the same patching mechanisms can be implemented on our side, which is more straightforward.

[Design doc](https://docs.google.com/document/d/1p7JOou00YCYwS74cRzxyLjoLC9ANAfg3FlrkQ3Euj9c/edit?usp=sharing)

### How is this PR tested?

- [x] Existing unit/integration tests
- [x] New unit/integration tests
- [x] Manual tests

<!-- Attach code, screenshot, video used for manual testing here. -->
MLflow
<img width="1442" alt="スクリーンショット 2025-05-01 14 01 46" src="https://github.com/user-attachments/assets/6b190960-5815-4c77-9eae-16817f8b6cbd" />

Documentation
![スクリーンショット 2025-05-01 14 32 11](https://github.com/user-attachments/assets/f1bd3113-78ac-42d0-823f-bc3c9119f38c)
![スクリーンショット 2025-05-01 14 32 18](https://github.com/user-attachments/assets/e31c06b0-d40c-418a-9e51-f001d32432bb)



### Does this PR require documentation update?

- [ ] No. You can skip the rest of this section.
- [x] Yes. I've updated:
  - [ ] Examples
  - [ ] API references
  - [ ] Instructions

### Release Notes

#### Is this a user-facing change?

- [ ] No. You can skip the rest of this section.
- [x] Yes. Give a description of this change to be included in the release notes for MLflow users.

Support auto tracing for HuggingFace's [smolagents](https://huggingface.co/docs/smolagents/en/index) library.

#### What component(s), interfaces, languages, and integrations does this PR affect?

Components

- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/deployments`: MLflow Deployments client APIs, server, and third-party Deployments integrations
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [x] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface

- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

Language

- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

Integrations

- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations

<!--
Insert an empty named anchor here to allow jumping to this section with a fragment URL
(e.g. https://github.com/mlflow/mlflow/pull/123#user-content-release-note-category).
Note that GitHub prefixes anchor names in markdown with "user-content-".
-->

<a name="release-note-category"></a>

#### How should the PR be classified in the release notes? Choose one:

- [ ] `rn/none` - No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" section
- [ ] `rn/breaking-change` - The PR will be mentioned in the "Breaking Changes" section
- [x] `rn/feature` - A new user-facing feature worth mentioning in the release notes
- [ ] `rn/bug-fix` - A user-facing bug fix worth mentioning in the release notes
- [ ] `rn/documentation` - A user-facing documentation change worth mentioning in the release notes

#### Should this PR be included in the next patch release?

`Yes` should be selected for bug fixes, documentation updates, and other small changes. `No` should be selected for new features and larger changes. If you're unsure about the release classification of this PR, leave this unchecked to let the maintainers decide.

<details>
<summary>What is a minor/patch release?</summary>

- Minor release: a release that increments the second part of the version number (e.g., 1.2.0 -> 1.3.0).
  Bug fixes, doc updates and new features usually go into minor releases.
- Patch release: a release that increments the third part of the version number (e.g., 1.2.0 -> 1.2.1).
  Bug fixes and doc updates usually go into patch releases.

</details>

<!-- patch -->

- [ ] Yes (this PR will be cherry-picked and included in the next patch release)
- [x] No (this PR will be included in the next minor release)



Diff:
diff --git a/conftest.py b/conftest.py
index cb96f86ebae81..b87a6d20a38a4 100644
--- a/conftest.py
+++ b/conftest.py
@@ -196,6 +196,7 @@ def pytest_ignore_collect(collection_path, config):
             "tests/sentence_transformers",
             "tests/shap",
             "tests/sklearn",
+            "tests/smolagents",
             "tests/spacy",
             "tests/spark",
             "tests/statsmodels",
diff --git a/docs/api_reference/source/python_api/mlflow.smolagents.rst b/docs/api_reference/source/python_api/mlflow.smolagents.rst
new file mode 100644
index 0000000000000..c2bf4c09a9b11
--- /dev/null
+++ b/docs/api_reference/source/python_api/mlflow.smolagents.rst
@@ -0,0 +1,7 @@
+mlflow.smolagents
+==================
+
+.. automodule:: mlflow.smolagents
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/docs/docs/tracing/index.mdx b/docs/docs/tracing/index.mdx
index 39c9826dd0a28..c8b14552dc939 100644
--- a/docs/docs/tracing/index.mdx
+++ b/docs/docs/tracing/index.mdx
@@ -214,6 +214,9 @@ MLflow Tracing is integrated with various GenAI libraries and provide **one-line
   <SmallLogoCard link="/tracing/integrations/txtai">
     <span>![txtai Logo](/images/logos/txtai-logo.png)</span>
   </SmallLogoCard>
+  <SmallLogoCard link="/tracing/integrations/smolagents">
+    <span>![Smolagents Logo](/images/logos/smolagents-logo.png)</span>
+  </SmallLogoCard>
 </CardGroup>
 <br />
 
diff --git a/docs/docs/tracing/integrations/index.mdx b/docs/docs/tracing/integrations/index.mdx
index 44a0c4c9fcebf..19c4aed90f21c 100644
--- a/docs/docs/tracing/integrations/index.mdx
+++ b/docs/docs/tracing/integrations/index.mdx
@@ -72,6 +72,9 @@ import TabItem from "@theme/TabItem";
   <SmallLogoCard link="/tracing/integrations/instructor">
     <span>![txtai Logo](/images/logos/txtai-logo.png)</span>
   </SmallLogoCard>
+  <SmallLogoCard link="/tracing/integrations/smolagents">
+    <span>![Smolagents Logo](/images/logos/smolagents-logo.png)</span>
+  </SmallLogoCard>
 </CardGroup>
 <br />
 
diff --git a/docs/docs/tracing/integrations/smolagents.mdx b/docs/docs/tracing/integrations/smolagents.mdx
new file mode 100644
index 0000000000000..7d16ca690dcc7
--- /dev/null
+++ b/docs/docs/tracing/integrations/smolagents.mdx
@@ -0,0 +1,37 @@
+---
+sidebar_position: 11
+sidebar_label: Smolagents
+---
+
+import { APILink } from "@site/src/components/APILink";
+
+# Tracing Smolagents
+
+![Smolagents tracing via autolog](/images/llms/smolagents/smolagents-tracing.png)
+
+MLflow Tracing provides automatic tracing capability when using Smolagents.
+When Smolagents auto-tracing is enabled by calling the <APILink fn="mlflow.smolagents.autolog" /> function,
+usage of the Smolagents SDK will automatically record generated traces during interactive development.
+
+Note that only synchronous calls are supported, and that asynchronous API and streaming methods are not traced.
+
+### Example Usage
+
+```python
+from smolagents import CodeAgent, LiteLLMModel
+import mlflow
+
+# Turn on auto tracing for Smolagents by calling mlflow.smolagents.autolog()
+mlflow.smolagents.autolog()
+
+model = LiteLLMModel(model_id="openai/gpt-4o-mini", api_key=API_KEY)
+agent = CodeAgent(tools=[], model=model, add_base_tools=True)
+
+result = agent.run(
+    "Could you give me the 118th number in the Fibonacci sequence?",
+)
+```
+
+## Disable auto-tracing
+
+Auto tracing for Smolagents can be disabled globally by calling `mlflow.smolagents.autolog(disable=True)` or `mlflow.autolog(disable=True)`.
\ No newline at end of file
diff --git a/docs/static/images/llms/smolagents/smolagents-tracing.png b/docs/static/images/llms/smolagents/smolagents-tracing.png
new file mode 100644
index 0000000000000..84bcbbd6ae84b
Binary files /dev/null and b/docs/static/images/llms/smolagents/smolagents-tracing.png differ
diff --git a/docs/static/images/logos/smolagents-logo.png b/docs/static/images/logos/smolagents-logo.png
new file mode 100644
index 0000000000000..414556560b46b
Binary files /dev/null and b/docs/static/images/logos/smolagents-logo.png differ
diff --git a/examples/smolagents/tracing.py b/examples/smolagents/tracing.py
new file mode 100644
index 0000000000000..4d3e8f0923735
--- /dev/null
+++ b/examples/smolagents/tracing.py
@@ -0,0 +1,18 @@
+"""
+This is an example for leveraging MLflow's auto tracing capabilities for Smolagents.
+For more information about MLflow Tracing, see: https://mlflow.org/docs/latest/llms/tracing/index.html
+"""
+
+from smolagents import CodeAgent, LiteLLMModel
+
+import mlflow
+
+# Turn on auto tracing for Smolagents by calling mlflow.smolagents.autolog()
+mlflow.smolagents.autolog()
+
+model = LiteLLMModel(model_id="openai/gpt-4o-mini", api_key="API_KEY")
+agent = CodeAgent(tools=[], model=model, add_base_tools=True)
+
+result = agent.run(
+    "Could you give me the 118th number in the Fibonacci sequence?",
+)
diff --git a/mlflow/__init__.py b/mlflow/__init__.py
index ebacda3a88ee6..6c21e1f7b4063 100644
--- a/mlflow/__init__.py
+++ b/mlflow/__init__.py
@@ -92,6 +92,7 @@
 )
 shap = LazyLoader("mlflow.shap", globals(), "mlflow.shap")
 sklearn = LazyLoader("mlflow.sklearn", globals(), "mlflow.sklearn")
+smolagents = LazyLoader("mlflow.smolagents", globals(), "mlflow.smolagents")
 spacy = LazyLoader("mlflow.spacy", globals(), "mlflow.spacy")
 spark = LazyLoader("mlflow.spark", globals(), "mlflow.spark")
 statsmodels = LazyLoader("mlflow.statsmodels", globals(), "mlflow.statsmodels")
@@ -137,6 +138,7 @@
         sentence_transformers,
         shap,
         sklearn,
+        smolagents,
         spacy,
         spark,
         statsmodels,
diff --git a/mlflow/ml-package-versions.yml b/mlflow/ml-package-versions.yml
index 4478bf335d4ff..a228ae2b92fb1 100644
--- a/mlflow/ml-package-versions.yml
+++ b/mlflow/ml-package-versions.yml
@@ -839,6 +839,20 @@ crewai:
     requirements:
     run: pytest tests/crewai
 
+smolagents:
+  package_info:
+    pip_release: "smolagents"
+    module_name: "smolagents"
+    install_dev: |
+      pip install "git+https://github.com/huggingface/smolagents"
+  autologging:
+    minimum: "1.14.0"
+    maximum: "1.14.0"
+    python:
+      ">=1.14.0": "3.10"
+    requirements:
+    run: pytest tests/smolagents
+
 mistral:
   package_info:
     pip_release: "mistralai"
diff --git a/mlflow/ml_package_versions.py b/mlflow/ml_package_versions.py
index e60132ada796c..e52594d89c887 100644
--- a/mlflow/ml_package_versions.py
+++ b/mlflow/ml_package_versions.py
@@ -320,6 +320,16 @@
             "maximum": "0.114.0"
         }
     },
+    "smolagents": {
+        "package_info": {
+            "pip_release": "smolagents",
+            "module_name": "smolagents"
+        },
+        "autologging": {
+            "minimum": "1.14.0",
+            "maximum": "1.14.0"
+        }
+    },
     "mistral": {
         "package_info": {
             "pip_release": "mistralai",
@@ -410,6 +420,7 @@
     "gemini": "google.genai",
     "anthropic": "anthropic",
     "crewai": "crewai",
+    "smolagents": "smolagents",
     "mistral": "mistralai",
     "litellm": "litellm",
     "groq": "groq",
diff --git a/mlflow/smolagents/__init__.py b/mlflow/smolagents/__init__.py
new file mode 100644
index 0000000000000..da3fce7863769
--- /dev/null
+++ b/mlflow/smolagents/__init__.py
@@ -0,0 +1,66 @@
+"""
+The ``mlflow.smolagents`` module provides an API for tracing Smolagents AI agents.
+"""
+
+import logging
+
+from mlflow.smolagents.autolog import (
+    patched_class_call,
+)
+from mlflow.utils.annotations import experimental
+from mlflow.utils.autologging_utils import autologging_integration, safe_patch
+
+_logger = logging.getLogger(__name__)
+
+FLAVOR_NAME = "smolagents"
+
+
+@experimental
+@autologging_integration(FLAVOR_NAME)
+def autolog(
+    log_traces: bool = True,
+    disable: bool = False,
+    silent: bool = False,
+):
+    """
+    Enables (or disables) and configures autologging from Smolagents to MLflow.
+    Note that asynchnorous APIs and Tool calling are not recorded now.
+
+    Args:
+        log_traces: If ``True``, traces are logged for Smolagents agents.
+            If ``False``, no traces are collected during inference. Default to ``True``.
+        disable: If ``True``, disables the Smolagents autologging. Default to ``False``.
+        silent: If ``True``, suppress all event logs and warnings from MLflow during Smolagents
+            autologging. If ``False``, show all events and warnings.
+    """
+    import smolagents
+    from smolagents import models
+
+    class_method_map = {
+        "MultiStepAgent": ["run"],
+        "CodeAgent": ["step"],
+        "ToolCallingAgent": ["step"],
+        "Tool": ["__call__"],
+    }
+
+    try:
+        for _, attr in vars(smolagents).items():
+            if isinstance(attr, type) and issubclass(attr, models.Model):
+                class_method_map.setdefault(attr.__name__, []).append("__call__")
+    except Exception as e:
+        _logger.warn("the error happens while registering models to class_method_map: %s", e)
+
+    try:
+        for class_name, methods in class_method_map.items():
+            cls = getattr(smolagents, class_name)
+            for method in methods:
+                safe_patch(
+                    FLAVOR_NAME,
+                    cls,
+                    method,
+                    patched_class_call,
+                )
+    except (AttributeError, ModuleNotFoundError) as e:
+        _logger.error(
+            "An exception happens when applying auto-tracing to smolagents. Exception: %s", e
+        )
diff --git a/mlflow/smolagents/autolog.py b/mlflow/smolagents/autolog.py
new file mode 100644
index 0000000000000..cc2e48ffb4b8c
--- /dev/null
+++ b/mlflow/smolagents/autolog.py
@@ -0,0 +1,139 @@
+import inspect
+import logging
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.smolagents.chat import set_span_chat_attributes
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of smolagents API for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                if span_type == SpanType.CHAT_MODEL:
+                    set_span_chat_attributes(
+                        span=span, messages=inputs.get("messages", []), output=outputs
+                    )
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+        return SpanType.AGENT
+    elif isinstance(instance, Tool):
+        return SpanType.TOOL
+    elif isinstance(instance, models.Model):
+        return SpanType.CHAT_MODEL
+
+    return SpanType.UNKNOWN
+
+
+def _construct_full_inputs(func, *args, **kwargs):
+    signature = inspect.signature(func)
+    # This does not create copy. So values should not be mutated directly
+    arguments = signature.bind_partial(*args, **kwargs).arguments
+
+    if "self" in arguments:
+        arguments.pop("self")
+
+    # Avoid non serializable objects and circular references
+    return {
+        k: v.__dict__ if hasattr(v, "__dict__") else v
+        for k, v in arguments.items()
+        if v is not None
+    }
+
+
+def _set_span_attributes(span: LiveSpan, instance):
+    # Smolagents is available only python >= 3.10, so importing libraries inside methods.
+    try:
+        from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            agent = _get_agent_attributes(instance)
+            for key, value in agent.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+        elif isinstance(instance, Tool):
+            tool = _get_tool_attributes(instance)
+            for key, value in tool.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+        elif issubclass(type(instance), models.Model):
+            model = _get_model_attributes(instance)
+            for key, value in model.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+    except Exception as e:
+        _logger.warn("An exception happens when saving span attributes. Exception: %s", e)
+
+
+def _get_agent_attributes(instance):
+    agent = {}
+    for key, value in instance.__dict__.items():
+        if key == "tools":
+            value = _parse_tools(value)
+        if value is None:
+            continue
+        agent[key] = str(value)
+
+    return agent
+
+
+def _inner_get_tool_attributes(tool_dict):
+    res = {}
+    if hasattr(tool_dict, "name") and tool_dict.name is not None:
+        res["name"] = tool_dict.name
+    if hasattr(tool_dict, "description") and tool_dict.description is not None:
+        res["description"] = tool_dict.description
+    result = {}
+    if res:
+        result["type"] = "function"
+        result["function"] = res
+    return result
+
+
+def _get_tool_attributes(instance):
+    instance_dict = instance.__dict__
+    return _inner_get_tool_attributes(instance_dict)
+
+
+def _parse_tools(tools):
+    result = []
+    for tool in tools:
+        res = _inner_get_tool_attributes(tool)
+        result.append(res)
+    return result
+
+
+def _get_model_attributes(instance):
+    model = {}
+    for key, value in instance.__dict__.items():
+        if value is None or key == "api_key":
+            continue
+        model[key] = str(value)
+    return model
diff --git a/mlflow/smolagents/chat.py b/mlflow/smolagents/chat.py
new file mode 100644
index 0000000000000..632916863062a
--- /dev/null
+++ b/mlflow/smolagents/chat.py
@@ -0,0 +1,29 @@
+import logging
+from typing import Any
+
+from mlflow.entities.span import LiveSpan
+from mlflow.exceptions import MlflowException
+from mlflow.tracing import set_span_chat_messages
+
+_logger = logging.getLogger(__name__)
+
+
+def set_span_chat_attributes(span: LiveSpan, messages: list[dict[str, Any]], output: Any):
+    """
+    This method logs chat messages to a span when LLM class is called.
+
+    Args:
+        span: A span object to record the chat messages.
+        messages: Input messages for the LLM call.
+        output: Response from LLM. Though the signature is str, tool response might be returned.
+    """
+    content = str(output.get("content", "")) if isinstance(output, dict) else str(output)
+    output_message = {"role": "assistant", "content": content}
+
+    try:
+        set_span_chat_messages(span, [*messages, output_message])
+    except MlflowException:
+        _logger.debug(
+            "Failed to set chat messages on span",
+            exc_info=True,
+        )
diff --git a/mlflow/tracking/fluent.py b/mlflow/tracking/fluent.py
index 2af6198a28099..b7171774ea374 100644
--- a/mlflow/tracking/fluent.py
+++ b/mlflow/tracking/fluent.py
@@ -2964,6 +2964,7 @@ def print_auto_logged_info(r):
         "langchain": "mlflow.langchain",
         "dspy": "mlflow.dspy",
         "crewai": "mlflow.crewai",
+        "smolagents": "mlflow.smolagents",
         "groq": "mlflow.groq",
         "boto3": "mlflow.bedrock",
         "mistralai": "mlflow.mistral",
diff --git a/tests/smolagents/test_smolagents_autolog.py b/tests/smolagents/test_smolagents_autolog.py
new file mode 100644
index 0000000000000..c2c7f7006dd76
--- /dev/null
+++ b/tests/smolagents/test_smolagents_autolog.py
@@ -0,0 +1,177 @@
+from unittest.mock import patch
+
+import pytest
+
+import mlflow
+from mlflow.entities.span import SpanType
+
+from tests.tracing.helper import get_traces
+
+_DUMMY_INPUT = "Explain quantum mechanics in simple terms."
+
+
+def clear_autolog_state():
+    from mlflow.utils.autologging_utils import AUTOLOGGING_INTEGRATIONS
+
+    for key in AUTOLOGGING_INTEGRATIONS.keys():
+        AUTOLOGGING_INTEGRATIONS[key].clear()
+    mlflow.utils.import_hooks._post_import_hooks = {}
+
+
+def test_run_autolog():
+    from smolagents import ChatMessage, CodeAgent, InferenceClientModel
+
+    _DUMMY_OUTPUT = ChatMessage(
+        role="user",
+        content='[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]',
+    )
+    clear_autolog_state()
+    agent = CodeAgent(
+        tools=[],
+        model=InferenceClientModel(model_id="gpt-3.5-turbo", token="test_id"),
+        max_steps=2,
+    )
+    with patch("smolagents.InferenceClientModel.__call__", return_value=_DUMMY_OUTPUT):
+        mlflow.smolagents.autolog()
+        agent.run(_DUMMY_INPUT)
+
+    traces = get_traces()
+    assert len(traces) == 1
+    assert traces[0].info.status == "OK"
+    assert len(traces[0].data.spans) == 6
+    # CodeAgent
+    span_0 = traces[0].data.spans[0]
+    assert span_0.name == "CodeAgent.run"
+    assert span_0.span_type == SpanType.AGENT
+    assert span_0.parent_id is None
+    assert span_0.inputs == {"task": _DUMMY_INPUT}
+    assert span_0.outputs == {
+        "_value": '[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]'
+    }
+    # CodeAgent
+    span_1 = traces[0].data.spans[1]
+    assert span_1.name == "CodeAgent.step_1"
+    assert span_1.span_type == SpanType.AGENT
+    assert span_1.parent_id == span_0.span_id
+    assert span_1.inputs["memory_step"]["step_number"] == 1
+    assert span_1.outputs is None
+    # InferenceClientModel
+    span_2 = traces[0].data.spans[2]
+    assert span_2.name == "InferenceClientModel.call_original_1"
+    assert span_2.span_type == SpanType.CHAT_MODEL
+    assert span_2.parent_id == span_1.span_id
+    assert span_2.inputs is not None
+    assert span_2.outputs is not None
+    # CodeAgent
+    span_3 = traces[0].data.spans[3]
+    assert span_3.name == "CodeAgent.step_2"
+    assert span_3.span_type == SpanType.AGENT
+    assert span_3.parent_id == span_0.span_id
+    assert span_3.inputs is not None
+    assert span_3.outputs is None
+    # InferenceClientModel
+    span_4 = traces[0].data.spans[4]
+    assert span_4.name == "InferenceClientModel.call_original_2"
+    assert span_4.span_type == SpanType.CHAT_MODEL
+    assert span_4.parent_id == span_3.span_id
+    assert span_4.inputs is not None
+    assert span_4.outputs is not None
+    # InferenceClientModel
+    span_5 = traces[0].data.spans[5]
+    assert span_5.name == "InferenceClientModel.call_original_3"
+    assert span_5.span_type == SpanType.CHAT_MODEL
+    assert span_5.parent_id == span_0.span_id
+    assert span_5.inputs is not None
+    assert span_5.outputs is not None
+
+    clear_autolog_state()
+
+
+def test_run_failure():
+    from smolagents import CodeAgent, InferenceClientModel
+
+    clear_autolog_state()
+    mlflow.smolagents.autolog()
+    agent = CodeAgent(
+        tools=[],
+        model=InferenceClientModel(model_id="gpt-3.5-turbo", token="test_id"),
+        max_steps=1,
+    )
+    with patch("smolagents.InferenceClientModel.__call__", side_effect=Exception("error")):
+        with pytest.raises(Exception, match="error"):
+            agent.run(_DUMMY_INPUT)
+    traces = get_traces()
+    assert len(traces) == 1
+    assert traces[0].info.status == "ERROR"
+    assert len(traces[0].data.spans) == 2
+    # CodeAgent
+    span_0 = traces[0].data.spans[0]
+    assert span_0.name == "CodeAgent.run"
+    assert span_0.span_type == SpanType.AGENT
+    assert span_0.parent_id is None
+    assert span_0.inputs == {"task": _DUMMY_INPUT}
+    assert span_0.outputs is None
+    # InferenceClientModel
+    span_1 = traces[0].data.spans[1]
+    assert span_1.name == "CodeAgent.step"
+    assert span_1.span_type == SpanType.AGENT
+    assert span_1.parent_id == span_0.span_id
+    assert span_1.inputs is not None
+    assert span_1.outputs is None
+
+    clear_autolog_state()
+
+
+def test_tool_autolog():
+    from smolagents import ChatMessage, CodeAgent, DuckDuckGoSearchTool, InferenceClientModel
+
+    _DUMMY_OUTPUT = ChatMessage(
+        role="user",
+        content='[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]',
+    )
+    clear_autolog_state()
+    agent = CodeAgent(
+        tools=[
+            DuckDuckGoSearchTool(),
+        ],
+        model=InferenceClientModel(model_id="gpt-3.5-turbo", token="test_id"),
+        max_steps=1,
+    )
+    with patch("smolagents.InferenceClientModel.__call__", return_value=_DUMMY_OUTPUT):
+        mlflow.smolagents.autolog()
+        agent.run(_DUMMY_INPUT)
+
+    traces = get_traces()
+    assert len(traces) == 1
+    assert traces[0].info.status == "OK"
+    assert len(traces[0].data.spans) == 4
+    # CodeAgent
+    span_0 = traces[0].data.spans[0]
+    assert span_0.name == "CodeAgent.run"
+    assert span_0.span_type == SpanType.AGENT
+    assert span_0.parent_id is None
+    assert span_0.inputs is not None
+    assert span_0.outputs is not None
+    # InferenceClientModel
+    span_1 = traces[0].data.spans[1]
+    assert span_1.name == "CodeAgent.step"
+    assert span_1.span_type == SpanType.AGENT
+    assert span_1.parent_id == span_0.span_id
+    assert span_1.inputs is not None
+    assert span_1.outputs is None
+    # InferenceClientModel
+    span_2 = traces[0].data.spans[2]
+    assert span_2.name == "InferenceClientModel.call_original_1"
+    assert span_2.span_type == SpanType.CHAT_MODEL
+    assert span_2.parent_id == span_1.span_id
+    assert span_2.inputs is not None
+    assert span_2.outputs is not None
+    # CodeAgent
+    span_3 = traces[0].data.spans[3]
+    assert span_3.name == "InferenceClientModel.call_original_2"
+    assert span_3.span_type == SpanType.CHAT_MODEL
+    assert span_3.parent_id == span_0.span_id
+    assert span_3.inputs is not None
+    assert span_3.outputs is not None
+
+    clear_autolog_state()
diff --git a/tests/tracking/fluent/test_fluent_autolog.py b/tests/tracking/fluent/test_fluent_autolog.py
index eea75343f94cf..ede0f2b3c4c59 100644
--- a/tests/tracking/fluent/test_fluent_autolog.py
+++ b/tests/tracking/fluent/test_fluent_autolog.py
@@ -71,6 +71,8 @@
     boto3: mlflow.bedrock,
     groq: mlflow.groq,
     mistralai: mlflow.mistral,
+    # TODO: once Python 3.10 is introduced, enable smolagents
+    # smolagents: mlflow.smolagents,
 }
 
 library_to_mlflow_module_traditional_ai = {
@@ -111,6 +113,8 @@ def reset_global_states():
         except Exception:
             pass
 
+    # TODO: Remove this when we run ci with Python >= 3.10
+    mlflow.utils.import_hooks._post_import_hooks.pop("smolagents", None)
     # TODO: Remove this when we run ci with Python >= 3.10
     mlflow.utils.import_hooks._post_import_hooks.pop("crewai", None)
     # TODO: Remove this line when we stop supporting google.generativeai
@@ -475,8 +479,8 @@ def test_autolog_genai_import(disable, flavor_and_module):
 
     # pytorch-lightning is not valid flavor name.
     # paddle autologging is not in the list of autologging integrations.
-    # crewai requires Python 3.10+ (our CI runs on Python 3.9).
-    if flavor in {"pytorch-lightning", "paddle", "crewai"}:
+    # crewai and smolagents require Python 3.10+ (our CI runs on Python 3.9).
+    if flavor in {"pytorch-lightning", "paddle", "crewai", "smolagents"}:
         return
 
     with reset_module_import():


Reviews:
user: login: B-Step62
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T07:21:40Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:16:59Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:17:01Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:17:02Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:17:04Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:17:05Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:17:06Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:17:07Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:17:09Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:17:11Z
user: login: B-Step62
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T22:39:49Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T23:51:46Z
user: login: B-Step62
body: LGTM! Thank you so much for the contribution, @y-okt!

Good to merge once the remaining two comments are addressed.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-02T02:24:00Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T02:57:35Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T02:58:14Z
user: login: B-Step62
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T05:23:38Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T05:25:15Z
user: login: B-Step62
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T05:25:30Z
user: login: y-okt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T05:55:00Z

Review comments:
diff_hunk: @@ -0,0 +1,63 @@
+"""
+The ``mlflow.smolagents`` module provides an API for tracing Smolagents AI agents.
+"""
+
+import logging
+
+from mlflow.smolagents.autolog import (
+    patched_class_call,
+)
+from mlflow.utils.annotations import experimental
+from mlflow.utils.autologging_utils import autologging_integration, safe_patch
+
+_logger = logging.getLogger(__name__)
+
+FLAVOR_NAME = "smolagents"
+
+
+@experimental
+@autologging_integration(FLAVOR_NAME)
+def autolog(
+    log_traces: bool = True,
+    disable: bool = False,
+    silent: bool = False,
+):
+    """
+    Enables (or disables) and configures autologging from Smolagents to MLflow.
+    Note that asynchnorous APIs and Tool calling are not recorded now.
+
+    Args:
+        log_traces: If ``True``, traces are logged for Smolagents agents.
+            If ``False``, no traces are collected during inference. Default to ``True``.
+        disable: If ``True``, disables the Smolagents autologging. Default to ``False``.
+        silent: If ``True``, suppress all event logs and warnings from MLflow during Smolagents
+            autologging. If ``False``, show all events and warnings.
+    """
+    import smolagents
+    from smolagents import models
+
+    class_method_map = {
+        "MultiStepAgent": ["run"],
+        "CodeAgent": ["step"],
+        "ToolCallingAgent": ["step"],
+        "Tool": ["__call__"],
+    }
+
+    for _, attr in vars(smolagents).items():
path: mlflow/smolagents/__init__.py
user: login: B-Step62
body: Is it guaranteed that all model classes are importable without additionoal dependencies? Shall we add a try-catch to fail gracefully if not?
created_at: 2025-05-01T06:17:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
path: mlflow/smolagents/autolog.py
user: login: B-Step62
body: Do we need this for smolagents?
created_at: 2025-05-01T06:19:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
path: mlflow/smolagents/autolog.py
user: login: B-Step62
body: Can we add a similar logic as [this one](https://github.com/mlflow/mlflow/blob/master/mlflow/crewai/autolog.py#L30-L33), to set the `mlflow.chat.messages` attribute? This is used for rendering a nicer chat UI on the span.
created_at: 2025-05-01T07:15:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    try:
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            return SpanType.AGENT
+        elif isinstance(instance, Tool):
+            return SpanType.TOOL
+        elif isinstance(instance, models.Model):
+            return SpanType.CHAT_MODEL
+    except AttributeError as e:
+        _logger.warn("An exception happens when resolving the span type. Exception: %s", e)
path: mlflow/smolagents/autolog.py
user: login: B-Step62
body: Do we need this try-catch? When does the `AttributeError` happen?
created_at: 2025-05-01T07:16:36Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    try:
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            return SpanType.AGENT
+        elif isinstance(instance, Tool):
+            return SpanType.TOOL
+        elif isinstance(instance, models.Model):
+            return SpanType.CHAT_MODEL
+    except AttributeError as e:
+        _logger.warn("An exception happens when resolving the span type. Exception: %s", e)
+
+    return SpanType.UNKNOWN
+
+
+def _construct_full_inputs(func, *args, **kwargs):
+    signature = inspect.signature(func)
+    # This does not create copy. So values should not be mutated directly
+    arguments = signature.bind_partial(*args, **kwargs).arguments
+
+    if "self" in arguments:
+        arguments.pop("self")
+
+    # Avoid non serializable objects and circular references
+    return {
+        k: v.__dict__ if hasattr(v, "__dict__") else v
+        for k, v in arguments.items()
+        if v is not None and _is_serializable(v)
+    }
+
+
+def _is_serializable(value):
+    try:
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore")
+            # There is type mismatch in some crewai class, suppress warning here
+            json.dumps(value, cls=TraceJSONEncoder, ensure_ascii=False)
+        return True
+    except (TypeError, ValueError):
+        return False
path: mlflow/smolagents/autolog.py
user: login: B-Step62
body: Is this logic relevant to smolagents?
created_at: 2025-05-01T07:17:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    try:
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            return SpanType.AGENT
+        elif isinstance(instance, Tool):
+            return SpanType.TOOL
+        elif isinstance(instance, models.Model):
+            return SpanType.CHAT_MODEL
+    except AttributeError as e:
+        _logger.warn("An exception happens when resolving the span type. Exception: %s", e)
+
+    return SpanType.UNKNOWN
+
+
+def _construct_full_inputs(func, *args, **kwargs):
+    signature = inspect.signature(func)
+    # This does not create copy. So values should not be mutated directly
+    arguments = signature.bind_partial(*args, **kwargs).arguments
+
+    if "self" in arguments:
+        arguments.pop("self")
+
+    # Avoid non serializable objects and circular references
+    return {
+        k: v.__dict__ if hasattr(v, "__dict__") else v
+        for k, v in arguments.items()
+        if v is not None and _is_serializable(v)
+    }
+
+
+def _is_serializable(value):
+    try:
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore")
+            # There is type mismatch in some crewai class, suppress warning here
+            json.dumps(value, cls=TraceJSONEncoder, ensure_ascii=False)
+        return True
+    except (TypeError, ValueError):
+        return False
+
+
+def _set_span_attributes(span: LiveSpan, instance):
+    # Smolagents is available only python >= 3.10, so importring libraries inside methods.
+    try:
+        from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent
+
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            agent = _get_agent_attributes(instance)
+            for key, value in agent.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+        elif isinstance(instance, Tool):
+            tool = _get_tool_attributes(instance)
+            for key, value in tool.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+    except AttributeError as e:
path: mlflow/smolagents/autolog.py
user: login: B-Step62
body: Shouldn't we catch other types of exceptions as well?
created_at: 2025-05-01T07:18:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,168 @@
+from unittest.mock import patch
+
+import pytest
+from smolagents import ChatMessage, CodeAgent, DuckDuckGoSearchTool, InferenceClientModel
+
+import mlflow
+from mlflow.entities.span import SpanType
+
+from tests.tracing.helper import get_traces
+
+_DUMMY_INPUT = "Explain quantum mechanics in simple terms."
+
+_DUMMY_OUTPUT = ChatMessage(
+    role="user", content='[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]'
+)
+
+
+def clear_autolog_state():
+    from mlflow.utils.autologging_utils import AUTOLOGGING_INTEGRATIONS
+
+    for key in AUTOLOGGING_INTEGRATIONS.keys():
+        AUTOLOGGING_INTEGRATIONS[key].clear()
+    mlflow.utils.import_hooks._post_import_hooks = {}
+
+
+def test_run_autolog():
+    with patch("smolagents.InferenceClientModel.__call__", return_value=_DUMMY_OUTPUT):
path: tests/smolagents/test_smolagents_autolog.py
user: login: B-Step62
body: nit: Let's minimize the scope of `with patch` like the next test case.
created_at: 2025-05-01T07:19:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,168 @@
+from unittest.mock import patch
+
+import pytest
+from smolagents import ChatMessage, CodeAgent, DuckDuckGoSearchTool, InferenceClientModel
+
+import mlflow
+from mlflow.entities.span import SpanType
+
+from tests.tracing.helper import get_traces
+
+_DUMMY_INPUT = "Explain quantum mechanics in simple terms."
+
+_DUMMY_OUTPUT = ChatMessage(
+    role="user", content='[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]'
+)
+
+
+def clear_autolog_state():
+    from mlflow.utils.autologging_utils import AUTOLOGGING_INTEGRATIONS
+
+    for key in AUTOLOGGING_INTEGRATIONS.keys():
+        AUTOLOGGING_INTEGRATIONS[key].clear()
+    mlflow.utils.import_hooks._post_import_hooks = {}
+
+
+def test_run_autolog():
+    with patch("smolagents.InferenceClientModel.__call__", return_value=_DUMMY_OUTPUT):
+        clear_autolog_state()
+        mlflow.smolagents.autolog()
+        agent = CodeAgent(
+            tools=[],
+            model=InferenceClientModel(model_id="gpt-3.5-turbo", token="test_id"),
+            max_steps=2,
+        )
+        agent.run(_DUMMY_INPUT)
+
+    traces = get_traces()
+    assert len(traces) == 1
+    assert traces[0].info.status == "OK"
+    assert len(traces[0].data.spans) == 6
+    # CodeAgent
+    span_0 = traces[0].data.spans[0]
+    assert span_0.name == "CodeAgent.run"
+    assert span_0.span_type == SpanType.AGENT
+    assert span_0.parent_id is None
+    assert span_0.inputs == {"task": _DUMMY_INPUT}
+    assert span_0.outputs == {
+        "_value": '[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]'
+    }
+    # InferenceClientModel
path: tests/smolagents/test_smolagents_autolog.py
user: login: B-Step62
body: ```suggestion
    # CodeAgent
```
nit
created_at: 2025-05-01T07:20:18Z
author_association: COLLABORATOR
diff_hunk: @@ -25,6 +25,7 @@
 import pytorch_lightning
 import setfit
 import sklearn
+import smolagents
path: tests/tracking/fluent/test_fluent_autolog.py
user: login: B-Step62
body: Do we need to avoid global import for smolagents here?
created_at: 2025-05-01T07:21:38Z
author_association: COLLABORATOR
diff_hunk: @@ -25,6 +25,7 @@
 import pytorch_lightning
 import setfit
 import sklearn
+import smolagents
path: tests/tracking/fluent/test_fluent_autolog.py
user: login: y-okt
body: When I refer to the implementation of `test_crewai_autolog.py`, I assumed we don't need to because `crewai` is imported at a top level, although crewai is only importable by Python 3.10. However, let me move that inside functions.
created_at: 2025-05-01T16:16:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,168 @@
+from unittest.mock import patch
+
+import pytest
+from smolagents import ChatMessage, CodeAgent, DuckDuckGoSearchTool, InferenceClientModel
+
+import mlflow
+from mlflow.entities.span import SpanType
+
+from tests.tracing.helper import get_traces
+
+_DUMMY_INPUT = "Explain quantum mechanics in simple terms."
+
+_DUMMY_OUTPUT = ChatMessage(
+    role="user", content='[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]'
+)
+
+
+def clear_autolog_state():
+    from mlflow.utils.autologging_utils import AUTOLOGGING_INTEGRATIONS
+
+    for key in AUTOLOGGING_INTEGRATIONS.keys():
+        AUTOLOGGING_INTEGRATIONS[key].clear()
+    mlflow.utils.import_hooks._post_import_hooks = {}
+
+
+def test_run_autolog():
+    with patch("smolagents.InferenceClientModel.__call__", return_value=_DUMMY_OUTPUT):
+        clear_autolog_state()
+        mlflow.smolagents.autolog()
+        agent = CodeAgent(
+            tools=[],
+            model=InferenceClientModel(model_id="gpt-3.5-turbo", token="test_id"),
+            max_steps=2,
+        )
+        agent.run(_DUMMY_INPUT)
+
+    traces = get_traces()
+    assert len(traces) == 1
+    assert traces[0].info.status == "OK"
+    assert len(traces[0].data.spans) == 6
+    # CodeAgent
+    span_0 = traces[0].data.spans[0]
+    assert span_0.name == "CodeAgent.run"
+    assert span_0.span_type == SpanType.AGENT
+    assert span_0.parent_id is None
+    assert span_0.inputs == {"task": _DUMMY_INPUT}
+    assert span_0.outputs == {
+        "_value": '[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]'
+    }
+    # InferenceClientModel
path: tests/smolagents/test_smolagents_autolog.py
user: login: y-okt
body: Thanks for the catch!
created_at: 2025-05-01T16:17:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,168 @@
+from unittest.mock import patch
+
+import pytest
+from smolagents import ChatMessage, CodeAgent, DuckDuckGoSearchTool, InferenceClientModel
+
+import mlflow
+from mlflow.entities.span import SpanType
+
+from tests.tracing.helper import get_traces
+
+_DUMMY_INPUT = "Explain quantum mechanics in simple terms."
+
+_DUMMY_OUTPUT = ChatMessage(
+    role="user", content='[{"type": "text", "text": "Explain quantum mechanics in simple terms."}]'
+)
+
+
+def clear_autolog_state():
+    from mlflow.utils.autologging_utils import AUTOLOGGING_INTEGRATIONS
+
+    for key in AUTOLOGGING_INTEGRATIONS.keys():
+        AUTOLOGGING_INTEGRATIONS[key].clear()
+    mlflow.utils.import_hooks._post_import_hooks = {}
+
+
+def test_run_autolog():
+    with patch("smolagents.InferenceClientModel.__call__", return_value=_DUMMY_OUTPUT):
path: tests/smolagents/test_smolagents_autolog.py
user: login: y-okt
body: Thanks, changed
created_at: 2025-05-01T16:17:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    try:
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            return SpanType.AGENT
+        elif isinstance(instance, Tool):
+            return SpanType.TOOL
+        elif isinstance(instance, models.Model):
+            return SpanType.CHAT_MODEL
+    except AttributeError as e:
+        _logger.warn("An exception happens when resolving the span type. Exception: %s", e)
+
+    return SpanType.UNKNOWN
+
+
+def _construct_full_inputs(func, *args, **kwargs):
+    signature = inspect.signature(func)
+    # This does not create copy. So values should not be mutated directly
+    arguments = signature.bind_partial(*args, **kwargs).arguments
+
+    if "self" in arguments:
+        arguments.pop("self")
+
+    # Avoid non serializable objects and circular references
+    return {
+        k: v.__dict__ if hasattr(v, "__dict__") else v
+        for k, v in arguments.items()
+        if v is not None and _is_serializable(v)
+    }
+
+
+def _is_serializable(value):
+    try:
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore")
+            # There is type mismatch in some crewai class, suppress warning here
+            json.dumps(value, cls=TraceJSONEncoder, ensure_ascii=False)
+        return True
+    except (TypeError, ValueError):
+        return False
+
+
+def _set_span_attributes(span: LiveSpan, instance):
+    # Smolagents is available only python >= 3.10, so importring libraries inside methods.
+    try:
+        from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent
+
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            agent = _get_agent_attributes(instance)
+            for key, value in agent.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+        elif isinstance(instance, Tool):
+            tool = _get_tool_attributes(instance)
+            for key, value in tool.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+    except AttributeError as e:
path: mlflow/smolagents/autolog.py
user: login: y-okt
body: Makes sense. Thank you!
created_at: 2025-05-01T16:17:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    try:
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            return SpanType.AGENT
+        elif isinstance(instance, Tool):
+            return SpanType.TOOL
+        elif isinstance(instance, models.Model):
+            return SpanType.CHAT_MODEL
+    except AttributeError as e:
+        _logger.warn("An exception happens when resolving the span type. Exception: %s", e)
+
+    return SpanType.UNKNOWN
+
+
+def _construct_full_inputs(func, *args, **kwargs):
+    signature = inspect.signature(func)
+    # This does not create copy. So values should not be mutated directly
+    arguments = signature.bind_partial(*args, **kwargs).arguments
+
+    if "self" in arguments:
+        arguments.pop("self")
+
+    # Avoid non serializable objects and circular references
+    return {
+        k: v.__dict__ if hasattr(v, "__dict__") else v
+        for k, v in arguments.items()
+        if v is not None and _is_serializable(v)
+    }
+
+
+def _is_serializable(value):
+    try:
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore")
+            # There is type mismatch in some crewai class, suppress warning here
+            json.dumps(value, cls=TraceJSONEncoder, ensure_ascii=False)
+        return True
+    except (TypeError, ValueError):
+        return False
path: mlflow/smolagents/autolog.py
user: login: y-okt
body: After carefully looking at codes, I don't think I need this. Let me remove this.
created_at: 2025-05-01T16:17:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    try:
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            return SpanType.AGENT
+        elif isinstance(instance, Tool):
+            return SpanType.TOOL
+        elif isinstance(instance, models.Model):
+            return SpanType.CHAT_MODEL
+    except AttributeError as e:
+        _logger.warn("An exception happens when resolving the span type. Exception: %s", e)
path: mlflow/smolagents/autolog.py
user: login: y-okt
body: Seems redundant, removed. Thanks
created_at: 2025-05-01T16:17:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
path: mlflow/smolagents/autolog.py
user: login: y-okt
body: Added a logic, thanks
created_at: 2025-05-01T16:17:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
path: mlflow/smolagents/autolog.py
user: login: y-okt
body: This visualization is useful for outputting `assistant`. If we combine that, it will be like the first picture. Otherwise, it will be like the second picture.
![スクリーンショット 2025-05-02 0 50 24](https://github.com/user-attachments/assets/a08a99b6-a3e8-4cbc-97fb-2021fcd2c463)
![スクリーンショット 2025-05-02 0 50 37](https://github.com/user-attachments/assets/9909752c-a9b6-48ad-a65c-1d94b1089504)

created_at: 2025-05-01T16:17:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,63 @@
+"""
+The ``mlflow.smolagents`` module provides an API for tracing Smolagents AI agents.
+"""
+
+import logging
+
+from mlflow.smolagents.autolog import (
+    patched_class_call,
+)
+from mlflow.utils.annotations import experimental
+from mlflow.utils.autologging_utils import autologging_integration, safe_patch
+
+_logger = logging.getLogger(__name__)
+
+FLAVOR_NAME = "smolagents"
+
+
+@experimental
+@autologging_integration(FLAVOR_NAME)
+def autolog(
+    log_traces: bool = True,
+    disable: bool = False,
+    silent: bool = False,
+):
+    """
+    Enables (or disables) and configures autologging from Smolagents to MLflow.
+    Note that asynchnorous APIs and Tool calling are not recorded now.
+
+    Args:
+        log_traces: If ``True``, traces are logged for Smolagents agents.
+            If ``False``, no traces are collected during inference. Default to ``True``.
+        disable: If ``True``, disables the Smolagents autologging. Default to ``False``.
+        silent: If ``True``, suppress all event logs and warnings from MLflow during Smolagents
+            autologging. If ``False``, show all events and warnings.
+    """
+    import smolagents
+    from smolagents import models
+
+    class_method_map = {
+        "MultiStepAgent": ["run"],
+        "CodeAgent": ["step"],
+        "ToolCallingAgent": ["step"],
+        "Tool": ["__call__"],
+    }
+
+    for _, attr in vars(smolagents).items():
path: mlflow/smolagents/__init__.py
user: login: y-okt
body: In my environment, the error didn't happen, but I agree that in the future, this concern can happen. Let me add `try-catch`. 
created_at: 2025-05-01T16:17:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
path: mlflow/smolagents/autolog.py
user: login: B-Step62
body: Makes sense, can we update the comment from `generate_content` to actual API of smolagent?
created_at: 2025-05-01T22:39:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,138 @@
+import inspect
+import json
+import logging
+import warnings
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.tracing.utils import TraceJSONEncoder
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of generate_content for better visualization
path: mlflow/smolagents/autolog.py
user: login: y-okt
body: Thank you! Updated. Could you please take a look?
created_at: 2025-05-01T23:51:46Z
author_association: CONTRIBUTOR
path: docs/static/images/llms/smolagents/smolagents-tracing.png
user: login: B-Step62
body: Can you update the image to the new one with the nicer UI?

![Screenshot 2025-05-02 at 11 19 52](https://github.com/user-attachments/assets/bcd9b2f4-aadc-48fe-a503-356d7a06ecd4)

created_at: 2025-05-02T02:04:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,126 @@
+import inspect
+import logging
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.smolagents.chat import set_span_chat_attributes
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of smolagents API for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                if span_type == SpanType.CHAT_MODEL:
+                    set_span_chat_attributes(
+                        span=span, messages=inputs.get("messages", []), output=outputs
+                    )
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+        return SpanType.AGENT
+    elif isinstance(instance, Tool):
+        return SpanType.TOOL
+    elif isinstance(instance, models.Model):
+        return SpanType.CHAT_MODEL
+
+    return SpanType.UNKNOWN
+
+
+def _construct_full_inputs(func, *args, **kwargs):
+    signature = inspect.signature(func)
+    # This does not create copy. So values should not be mutated directly
+    arguments = signature.bind_partial(*args, **kwargs).arguments
+
+    if "self" in arguments:
+        arguments.pop("self")
+
+    # Avoid non serializable objects and circular references
+    return {
+        k: v.__dict__ if hasattr(v, "__dict__") else v
+        for k, v in arguments.items()
+        if v is not None
+    }
+
+
+def _set_span_attributes(span: LiveSpan, instance):
+    # Smolagents is available only python >= 3.10, so importring libraries inside methods.
+    try:
+        from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent
+
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            agent = _get_agent_attributes(instance)
+            for key, value in agent.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+        elif isinstance(instance, Tool):
+            tool = _get_tool_attributes(instance)
+            for key, value in tool.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
path: mlflow/smolagents/autolog.py
user: login: B-Step62
body: Let's record `model.model_id` to the span attribute of the Model span. There may be other useful fields for debugging as well.
created_at: 2025-05-02T02:23:07Z
author_association: COLLABORATOR
path: docs/static/images/llms/smolagents/smolagents-tracing.png
user: login: y-okt
body: Thank you! Updated
created_at: 2025-05-02T02:57:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,126 @@
+import inspect
+import logging
+
+import mlflow
+from mlflow.entities import SpanType
+from mlflow.entities.span import LiveSpan
+from mlflow.smolagents.chat import set_span_chat_attributes
+from mlflow.utils.autologging_utils.config import AutoLoggingConfig
+
+_logger = logging.getLogger(__name__)
+
+
+def patched_class_call(original, self, *args, **kwargs):
+    try:
+        config = AutoLoggingConfig.init(flavor_name=mlflow.smolagents.FLAVOR_NAME)
+
+        if config.log_traces:
+            fullname = f"{self.__class__.__name__}.{original.__name__}"
+            span_type = _get_span_type(self)
+            with mlflow.start_span(name=fullname, span_type=span_type) as span:
+                inputs = _construct_full_inputs(original, self, *args, **kwargs)
+                span.set_inputs(inputs)
+                _set_span_attributes(span=span, instance=self)
+                result = original(self, *args, **kwargs)
+
+                # Need to convert the response of smolagents API for better visualization
+                outputs = result.__dict__ if hasattr(result, "__dict__") else result
+                if span_type == SpanType.CHAT_MODEL:
+                    set_span_chat_attributes(
+                        span=span, messages=inputs.get("messages", []), output=outputs
+                    )
+                span.set_outputs(outputs)
+                return result
+    except Exception as e:
+        _logger.error("the error occurred while patching")
+        raise e
+
+
+def _get_span_type(instance) -> str:
+    from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent, models
+
+    if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+        return SpanType.AGENT
+    elif isinstance(instance, Tool):
+        return SpanType.TOOL
+    elif isinstance(instance, models.Model):
+        return SpanType.CHAT_MODEL
+
+    return SpanType.UNKNOWN
+
+
+def _construct_full_inputs(func, *args, **kwargs):
+    signature = inspect.signature(func)
+    # This does not create copy. So values should not be mutated directly
+    arguments = signature.bind_partial(*args, **kwargs).arguments
+
+    if "self" in arguments:
+        arguments.pop("self")
+
+    # Avoid non serializable objects and circular references
+    return {
+        k: v.__dict__ if hasattr(v, "__dict__") else v
+        for k, v in arguments.items()
+        if v is not None
+    }
+
+
+def _set_span_attributes(span: LiveSpan, instance):
+    # Smolagents is available only python >= 3.10, so importring libraries inside methods.
+    try:
+        from smolagents import CodeAgent, MultiStepAgent, Tool, ToolCallingAgent
+
+        if isinstance(instance, (MultiStepAgent, CodeAgent, ToolCallingAgent)):
+            agent = _get_agent_attributes(instance)
+            for key, value in agent.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
+        elif isinstance(instance, Tool):
+            tool = _get_tool_attributes(instance)
+            for key, value in tool.items():
+                if value is not None:
+                    span.set_attribute(key, str(value) if isinstance(value, list) else value)
+
path: mlflow/smolagents/autolog.py
user: login: y-okt
body: Thank you! Added some span attributes!

![スクリーンショット 2025-05-02 11 54 20](https://github.com/user-attachments/assets/cf6d8fac-bf00-4e08-82ae-f8356aab6954)

created_at: 2025-05-02T02:58:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -196,6 +196,7 @@ def pytest_ignore_collect(collection_path, config):
             "tests/sentence_transformers",
             "tests/shap",
             "tests/sklearn",
+            "test/smolagents",
path: conftest.py
user: login: B-Step62
body: ```suggestion
            "tests/smolagents",
```

@y-okt This should fix the python/windows CI jobs
created_at: 2025-05-02T05:23:38Z
author_association: COLLABORATOR
diff_hunk: @@ -196,6 +196,7 @@ def pytest_ignore_collect(collection_path, config):
             "tests/sentence_transformers",
             "tests/shap",
             "tests/sklearn",
+            "test/smolagents",
path: conftest.py
user: login: y-okt
body: Thank you!! Applied @B-Step62 
created_at: 2025-05-02T05:25:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,6 +72,7 @@
     boto3: mlflow.bedrock,
     groq: mlflow.groq,
     mistralai: mlflow.mistral,
+    smolagents: mlflow.smolagents,
path: tests/tracking/fluent/test_fluent_autolog.py
user: login: B-Step62
body: It seems we need to remove this line and L28. This list cannot include libraries that require Python 3.10 (e.g. crewai). Could you replace this with a TODO comment to add them back once we bump minimal supported version to 3.10?
created_at: 2025-05-02T05:25:30Z
author_association: COLLABORATOR
diff_hunk: @@ -71,6 +72,7 @@
     boto3: mlflow.bedrock,
     groq: mlflow.groq,
     mistralai: mlflow.mistral,
+    smolagents: mlflow.smolagents,
path: tests/tracking/fluent/test_fluent_autolog.py
user: login: y-okt
body: @B-Step62 Thank you! I replaced this. It seems that `auto-merge` was disabled, if this changes look OK, could you please enable `auto-merge` again?
created_at: 2025-05-02T05:55:00Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-01T02:32:55Z
body: <!-- documentation preview -->


Documentation preview for ab530981d2002dd46cdd8b393fa111a84c64b2f2 will be available when [this CircleCI job](https://circleci.com/gh/mlflow/mlflow/156722)
completes successfully. You may encounter a `{"message":"not found"}` error when reloading
a page. If so, add `/index.html` to the URL.

- [Top page](https://output.circle-artifacts.com/output/job/27cd55fe-430e-4c64-bdfe-585bba35c8e1/artifacts/0/docs/build/latest/index.html)
- [Changed pages](https://output.circle-artifacts.com/output/job/27cd55fe-430e-4c64-bdfe-585bba35c8e1/artifacts/0/docs/build/latest/diff.html) (⚠️ only MDX file changes are detected ⚠️)

<details>
<summary>More info</summary>

- Ignore this comment if this PR does not change the documentation.
- It takes a few minutes for the preview to be available.
- The preview is updated when a new commit is pushed to this PR.
- This comment was created by https://github.com/mlflow/mlflow/actions/runs/14793154536.

</details>

author_association: CONTRIBUTOR
user: login: y-okt
created_at: 2025-05-02T03:19:02Z
body: @B-Step62 it says it’s waiting approvals from maintainer. I’m not sure because I have already gotten approval from you, but could you check?
![IMG_6204](https://github.com/user-attachments/assets/14947063-c47b-4425-ac04-ec4b846054f5)

author_association: CONTRIBUTOR
user: login: B-Step62
created_at: 2025-05-02T04:44:42Z
body: > @B-Step62 it says it’s waiting approvals from maintainer. I’m not sure because I have already gotten approval from you, but could you check?

This is approval for "workflow run" not the PR - CI jobs are only executed after manual approval from maintainers due to security reasons.
author_association: COLLABORATOR
user: login: B-Step62
created_at: 2025-05-02T10:02:42Z
body: @y-okt I think this commit should be reverted: https://github.com/mlflow/mlflow/pull/15574/commits/cbd0b94cce467e594c27b2a0bec233ad4bdc80bf
author_association: COLLABORATOR
user: login: y-okt
created_at: 2025-05-02T11:14:21Z
body: Disabling "keras", "litellm", "dspy" made the test run successfully in my local environment, so I guess this code has no problem with smolagents.
author_association: CONTRIBUTOR
user: login: y-okt
created_at: 2025-05-02T13:55:45Z
body: All the pipelines succeeded. Could you please merge? @B-Step62 
author_association: CONTRIBUTOR
user: login: y-okt
created_at: 2025-05-02T13:57:55Z
body: Thank you very much for your support for this FR! I really enjoyed working on this! @B-Step62 
author_association: CONTRIBUTOR
user: login: B-Step62
created_at: 2025-05-02T14:03:00Z
body: > Thank you very much for your support for this FR! I really enjoyed working on this! @B-Step62

Fantastic work! Thank *you* for contributing on this feature!😄
author_association: COLLABORATOR